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
}
}
| Field | Type | Description |
|---|---|---|
function_id | String | Identifies which model/function to call |
params | Object | The 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 Connections → List
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 Name | Connection | Function ID | Purpose |
|---|---|---|---|
| Churn Predictor | My API | predict_churn | Customer churn |
| Fraud Detector | My API | predict_fraud | Transaction fraud |
| Price Optimizer | My API | predict_price | Dynamic 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
- Implement root endpoint handler
- Keep existing direct endpoints
- Test both methods work
Phase 2: Update Spartera Configuration
- Change API Endpoint URL to base URL
- Add Function ID to assets
- Test in Spartera
Phase 3: Monitor
- Both methods work simultaneously
- Monitor logs to confirm traffic patterns
- No downtime required
Phase 4: Deprecate (Optional)
- After confirming Function ID routing works
- Can remove direct endpoints if desired
- 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:
- Implement Function ID routing in your API
- Create multiple assets with different Function IDs
- Learn how to Create Assets from APIs
- Review Best Practices for production deployments
