Function ID Routing

Function ID Routing

Overview

Function ID Routing enables you to serve multiple models through a single base
API endpoint. Instead of managing separate URLs for each model, you route
requests based on a function_id parameter.

Why Use Function ID Routing?

Benefits

Single Endpoint - One base URL serves all your models
Easier Management - Update one deployment, not many
Cleaner Architecture - Logical routing within your API
Cost Efficient - One load balancer, one SSL certificate
Scalable - Add new models without new deployments

Use Cases

  • Company with multiple ML models (fraud, churn, pricing)
  • Different model versions (v1, v2, beta)
  • Region-specific models (US, EU, APAC)
  • A/B testing different models
  • Tiered models (basic, premium, enterprise)

How It Works

Request Flow Comparison

Without Function ID (Direct Routing)

POST https://your-api.com/predict
X-API-Key: your-key
Content-Type: application/json

{
  "age": 35,
  "income": 75000
}

Your API receives parameters directly at /predict.

With Function ID (Function Routing)

POST https://your-api.com/
X-API-Key: your-key
Content-Type: application/json

{
  "function_id": "predict_churn",
  "params": {
    "age": 35,
    "income": 75000
  }
}

Your API receives parameters wrapped with a function identifier at base URL /.

Implementation Guide

Step 1: Understand the Request Format

When Function ID is configured in Spartera, requests arrive as:

{
  "function_id": "your_function_name",
  "params": {
    "parameter1": "value1",
    "parameter2": 123
  }
}
FieldTypeDescription
function_idStringIdentifies which model/function to call
paramsObjectThe actual input parameters for your model

Step 2: Create Root Endpoint Handler

Add a POST handler for the root path /:

@app.route('/', methods=['POST'])
@require_api_key
def handle_root_post():
    """Route requests based on function_id"""
    try:
        data = request.json
        function_id = data.get('function_id')
        params = data.get('params', {})

        if function_id == 'predict_churn':
            return predict_churn(params)
        elif function_id == 'predict_fraud':
            return predict_fraud(params)
        elif function_id == 'predict_price':
            return predict_price(params)
        else:
            return jsonify({
                'error': 'Invalid function_id',
                'available_functions': [
                    'predict_churn',
                    'predict_fraud',
                    'predict_price'
                ]
            }), 400

    except Exception as e:
        return jsonify({'error': str(e)}), 500

Step 3: Implement Individual Model Functions

def predict_churn(params):
    """Handle churn prediction"""
    if 'customer_id' not in params:
        return jsonify({'error': 'Missing customer_id'}), 400

    prediction = churn_model.predict(params)

    return jsonify({
        'timestamp': datetime.now(timezone.utc).isoformat(),
        'answer_value': float(prediction),
        'asset_id': request.headers.get('X-Asset-ID')
    })

def predict_fraud(params):
    """Handle fraud detection"""
    if 'transaction_id' not in params:
        return jsonify({'error': 'Missing transaction_id'}), 400

    prediction = fraud_model.predict(params)

    return jsonify({
        'timestamp': datetime.now(timezone.utc).isoformat(),
        'answer_value': int(prediction),
        'asset_id': request.headers.get('X-Asset-ID')
    })

Step 4: Support Both Routing Methods

Important: Your API must support BOTH direct routing and function ID routing.

@app.route('/predict_churn', methods=['POST'])
@require_api_key
def predict_churn_direct():
    params = request.json
    return predict_churn(params)

@app.route('/', methods=['POST'])
@require_api_key
def handle_root_post():
    data = request.json
    function_id = data.get('function_id')
    params = data.get('params', {})

    if function_id == 'predict_churn':
        return predict_churn(params)

Complete Implementation Examples

Python + Flask

from flask import Flask, request, jsonify
from datetime import datetime, timezone
from functools import wraps

app = Flask(__name__)

VALID_API_KEYS = {
    "your-api-key": {"name": "Production"}
}

def require_api_key(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        api_key = request.headers.get('X-API-Key')
        if not api_key or api_key not in VALID_API_KEYS:
            return jsonify({'error': 'Invalid API key'}), 401
        return f(*args, **kwargs)
    return decorated

def spartera_response(prediction, asset_id=None):
    """Create Spartera-formatted response"""
    return jsonify({
        'timestamp': datetime.now(timezone.utc).isoformat(),
        'answer_value': prediction,
        'asset_id': asset_id or request.headers.get('X-Asset-ID')
    })

def predict_churn(params):
    """Churn prediction logic"""
    required = ['customer_id', 'tenure', 'monthly_charges']
    missing = [f for f in required if f not in params]
    if missing:
        return jsonify({'error': f'Missing fields: {missing}'}), 400

    prediction = churn_model.predict(params)
    return spartera_response(float(prediction))

@app.route('/', methods=['POST'])
@require_api_key
def handle_function_routing():
    """Route based on function_id"""
    try:
        data = request.json
        function_id = data.get('function_id')
        params = data.get('params', {})

        if function_id == 'predict_churn':
            return predict_churn(params)
        elif function_id == 'predict_fraud':
            return predict_fraud(params)
        else:
            return jsonify({
                'error': 'Invalid function_id',
                'received': function_id,
                'available': ['predict_churn', 'predict_fraud']
            }), 400

    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/predict_churn', methods=['POST'])
@require_api_key
def predict_churn_direct():
    return predict_churn(request.json)

@app.route('/health', methods=['GET'])
def health():
    return jsonify({
        'status': 'healthy',
        'available_functions': ['predict_churn', 'predict_fraud']
    })

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)

Node.js + Express

const express = require('express');
const app = express();

app.use(express.json());

const requireApiKey = (req, res, next) => {
    const apiKey = req.headers['x-api-key'];
    const validKeys = ['your-api-key'];

    if (!apiKey || !validKeys.includes(apiKey)) {
        return res.status(401).json({ error: 'Invalid API key' });
    }
    next();
};

const sparteraResponse = (prediction, assetId = null) => ({
    timestamp: new Date().toISOString(),
    answer_value: prediction,
    asset_id: assetId
});

async function predictChurn(params) {
    if (!params.customer_id) {
        throw new Error('Missing customer_id');
    }
    const prediction = await churnModel.predict(params);
    return prediction;
}

app.post('/', requireApiKey, async (req, res) => {
    try {
        const { function_id, params } = req.body;
        const assetId = req.headers['x-asset-id'] || null;

        let prediction;

        switch (function_id) {
            case 'predict_churn':
                prediction = await predictChurn(params);
                break;
            default:
                return res.status(400).json({
                    error: 'Invalid function_id',
                    available: ['predict_churn']
                });
        }

        res.json(sparteraResponse(prediction, assetId));

    } catch (error) {
        res.status(500).json({ error: error.message });
    }
});

app.listen(8080);

Spartera Configuration

Setting Up Function ID in Spartera

1.Navigate to ConnectionsList
2.Edit your API connection
3.In API Endpoint URL, enter your base URL (not the specific
endpoint):

✅ Correct:
https://your-api.com

❌ Incorrect (don't include /predict):
https://your-api.com/predict

4.While creating assets, in the Asset Logic section, set Function ID:

Example: predict_churn
Example: predict_fraud
Example: predict_price_v2

5.Save and test

Creating Multiple Assets with Same Connection

You can create multiple assets using the same connection but different
function IDs:

Asset NameConnectionFunction IDPurpose
Churn PredictorMy APIpredict_churnCustomer churn
Fraud DetectorMy APIpredict_fraudTransaction fraud
Price OptimizerMy APIpredict_priceDynamic pricing

Benefits:

  • One API deployment serves all three assets
  • Update API once, all assets benefit
  • Easier monitoring and logging
  • Simpler infrastructure management

Best Practices

Function ID Naming Conventions

Good:

predict_churn
predict_fraud
predict_price_v2
forecast_demand_us
classify_sentiment

Avoid:

function1  (not descriptive)
churn  (missing verb)
Predict-Churn  (use snake_case)
predictChurn  (use snake_case, not camelCase)

Error Handling

Always handle unknown function IDs gracefully:

if function_id not in VALID_FUNCTIONS:
    return jsonify({
        'error': 'Invalid function_id',
        'received': function_id,
        'available_functions': VALID_FUNCTIONS,
        'hint': 'Check function_id spelling and case'
    }), 400

Logging

Log function_id for debugging:

logging.info({
    'function_id': function_id,
    'params_count': len(params),
    'asset_id': asset_id,
    'timestamp': timestamp
})

Version Management

Use function IDs for versioning:

'predict_churn_v1'
'predict_churn_v2'
'predict_churn_beta'

This allows gradual rollout and A/B testing.

Testing

Test Direct Routing

curl -X POST https://your-api.com/predict_churn \
  -H "X-API-Key: your-key" \
  -H "Content-Type: application/json" \
  -d '{"customer_id": "123", "tenure": 24}'

Test Function ID Routing

curl -X POST https://your-api.com/ \
  -H "X-API-Key: your-key" \
  -H "Content-Type: application/json" \
  -d '{
    "function_id": "predict_churn",
    "params": {
      "customer_id": "123",
      "tenure": 24
    }
  }'

Both should return identical responses.

Troubleshooting

Function ID Not Recognized

Error:

{
  "error": "Invalid function_id",
  "received": "predict_churn"
}

Solutions:

  • Check function_id spelling in Spartera matches your code exactly
  • Verify case sensitivity (use snake_case consistently)
  • Ensure function is implemented in your API

Parameters Not Extracted

Error:

{
  "error": "Missing required fields"
}

Solution:
Check you're extracting params from request:

params = data.get('params', {})
prediction = predict_churn(params)

Both Methods Not Working

Issue: Direct routing works, Function ID routing doesn't (or vice versa)

Solution: Implement both patterns as shown in examples above.

Migration Guide

Migrating from Direct to Function ID Routing

Phase 1: Add Function ID Support

  1. Implement root endpoint handler
  2. Keep existing direct endpoints
  3. Test both methods work

Phase 2: Update Spartera Configuration

  1. Change API Endpoint URL to base URL
  2. Add Function ID to assets
  3. Test in Spartera

Phase 3: Monitor

  1. Both methods work simultaneously
  2. Monitor logs to confirm traffic patterns
  3. No downtime required

Phase 4: Deprecate (Optional)

  1. After confirming Function ID routing works
  2. Can remove direct endpoints if desired
  3. Or keep both for flexibility

Advanced Patterns

Dynamic Routing

FUNCTION_MAP = {
    'predict_churn': predict_churn,
    'predict_fraud': predict_fraud,
    'predict_price': predict_price,
}

@app.route('/', methods=['POST'])
@require_api_key
def handle_function_routing():
    data = request.json
    function_id = data.get('function_id')
    params = data.get('params', {})

    if function_id in FUNCTION_MAP:
        func = FUNCTION_MAP[function_id]
        return func(params)
    else:
        return jsonify({
            'error': 'Invalid function_id',
            'available': list(FUNCTION_MAP.keys())
        }), 400

Middleware Pattern

def route_by_function_id(function_map):
    """Decorator for function routing"""
    def decorator(f):
        @wraps(f)
        def decorated(*args, **kwargs):
            data = request.json
            function_id = data.get('function_id')
            params = data.get('params', {})

            if function_id in function_map:
                return function_map[function_id](params)
            return jsonify({
                'error': 'Invalid function_id'
            }), 400
        return decorated
    return decorator

@app.route('/', methods=['POST'])
@require_api_key
@route_by_function_id(FUNCTION_MAP)
def root():
    pass

Next Steps: