Home / Docs / Routing

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-Class
  • X-MassRelay-Data-Class
  • X-MassRelay-Consequence-Class
Note A class the key's lane cannot enforce is refused with a 400 rather than ignored.

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
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 requiredstring

Example request

curl
curl https://api.massrelay.ai/v1/route/explain/{decisionId} \
  -H "Authorization: Bearer $RELAY_KEY"
Python
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())
TypeScript
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

FieldTypeDescription
decisionId requiredstring
rationale requiredstringRendered from rationaleTemplateId.
rationaleTemplateIdstring
taskClass requiredstring
classSourcestring
chosenModelstring or null
chosenTier requiredstring
effortstring or null
borrowLevelstring
createdAt requiredstring
catalogVersionstring or null
dataClassstring
consequenceClassstring
intentSourcestring
excludedarray of object
excluded[].modelstringThe model id. In a request it is optional: without it the key's routing chooses.
excluded[].reasonobject
excluded[].policiesarray
excluded[].detailobject
Example response (shape)
{
  "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

  • 401 invalid_api_key
  • 403 insufficient_scope
  • 429 over 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.