Routing
How the router chooses a model, and how to read why it chose that one.
Use this page to understand how the router picks a model when you do not name one, and to read back the reason for any choice.
How a model is chosen
A request without a model is routed by the key's rules. The rules are policy data: a task class, a data class, a consequence class and a price ceiling narrow the candidates, and the router picks among those left.
State the classes yourself
A caller may state the classes in these headers:
X-MassRelay-Task-ClassX-MassRelay-Data-ClassX-MassRelay-Consequence-Class
Explain a decision
Every answer carries a decision id. GET /v1/route/explain/{decisionId} returns the rationale, the task class and where it came from, the chosen model and tier, the effort, and the models that were excluded with the reason for each. A decision of another workspace is a 404, the same as an unknown id. The key needs the explain scope.
curl https://api.massrelay.ai/v1/route/explain/DECISION_ID \
-H "Authorization: Bearer $RELAY_KEY"GET /v1/route/explain/{decisionId}
Authentication relay key (bearer)
Path parameters
decisionId required | string |
Example request
curl https://api.massrelay.ai/v1/route/explain/{decisionId} \
-H "Authorization: Bearer $RELAY_KEY"import os, requests
r = requests.get(
"https://api.massrelay.ai/v1/route/explain/{decisionId}",
headers={"Authorization": f"Bearer {os.environ['RELAY_KEY']}"},
)
r.raise_for_status()
print(r.json())const r = await fetch("https://api.massrelay.ai/v1/route/explain/{decisionId}", {
method: "GET",
headers: { Authorization: `Bearer ${process.env.RELAY_KEY}` },
});
if (!r.ok) throw new Error(`${r.status} ${await r.text()}`);
console.log(await r.json());Response
| Field | Type | Description |
|---|---|---|
decisionId required | string | |
rationale required | string | Rendered from rationaleTemplateId. |
rationaleTemplateId | string | |
taskClass required | string | |
classSource | string | |
chosenModel | string or null | |
chosenTier required | string | |
effort | string or null | |
borrowLevel | string | |
createdAt required | string | |
catalogVersion | string or null | |
dataClass | string | |
consequenceClass | string | |
intentSource | string | |
excluded | array of object | |
excluded[].model | string | The model id. In a request it is optional: without it the key's routing chooses. |
excluded[].reason | object | |
excluded[].policies | array | |
excluded[].detail | object |
{
"decisionId": "<decisionId>",
"rationale": "<rationale>",
"rationaleTemplateId": "<rationaleTemplateId>",
"taskClass": "<taskClass>",
"classSource": "<classSource>",
"chosenModel": "<chosenModel>",
"chosenTier": "<chosenTier>",
"effort": "<effort>",
"borrowLevel": "<borrowLevel>",
"createdAt": "<createdAt>",
"catalogVersion": "<catalogVersion>",
"dataClass": "<dataClass>",
"consequenceClass": "<consequenceClass>",
"intentSource": "<intentSource>",
"excluded": [
{
"model": "provider/model-id",
"reason": "<reason>",
"policies": [],
"detail": "<detail>"
}
]
}Common errors
401invalid_api_key403insufficient_scope429over the request rate; read Retry-After
Every error has the same JSON shape; see Errors and Limits.
Full schema (JSON)
Response schema
{
"description": "404 decision_not_found for an unknown id or another workspace's id. The `explain` scope is required on the key.",
"type": "object",
"required": [
"decisionId",
"rationale",
"taskClass",
"chosenTier",
"createdAt"
],
"properties": {
"decisionId": {
"type": "string"
},
"rationale": {
"type": "string",
"description": "Rendered from rationaleTemplateId."
},
"rationaleTemplateId": {
"type": "string"
},
"taskClass": {
"type": "string"
},
"classSource": {
"type": "string"
},
"chosenModel": {
"type": [
"string",
"null"
]
},
"chosenTier": {
"type": "string"
},
"effort": {
"type": [
"string",
"null"
]
},
"borrowLevel": {
"type": "string"
},
"createdAt": {
"type": "string"
},
"catalogVersion": {
"type": [
"string",
"null"
]
},
"dataClass": {
"type": "string"
},
"consequenceClass": {
"type": "string"
},
"intentSource": {
"type": "string"
},
"excluded": {
"type": "array",
"items": {
"type": "object",
"properties": {
"model": {
"type": "string"
},
"reason": {},
"policies": {
"type": "array"
},
"detail": {}
}
}
}
}
}Read the policy
GET /v1/policy authenticates the key and today answers 501 not_ported: it returns no policy document yet. The reference states its body so a caller does not expect more.
Not available yet
Two features are not available yet: a named model id that is a per-request route over real candidates (a virtual model), and a clamp of a requested thinking level to the nearest supported one.