Explain a Route
Dry-run a routing decision through the live resolver: get the ordered candidate chain an operation would use, or a fail-closed explanation, without spending a model call.
POST /v1/ai/routes:explain answers "what would this operation route to right now" without spending a model call. The question runs through the same resolver that serves live traffic, so the answer is evidence rather than a forecast: the ordered candidate chain that would serve the operation, and every candidate the policy discarded, each with the constraint that discarded it. For a compliance review, the rejected list is often the more valuable half, because it shows the policy actively removing what it is supposed to remove.
The request names an operation_type and may pin a model or a class. Unpinned, the resolver uses the operation's effective class and walks its chain. Pinning a model asks "could we be routed to this model for this operation", which is the direct way to verify a denial: a denied model appears under rejected with reason model_denied no matter what else would have permitted it. fallback_scope in the response reports how far fallback may reach for this plan, for example class_chain when the class's whole chain is in play.
When the policy permits no route, the response is still a success (the endpoint returns 201 on every answered question) with routable: false, a reason code, and a plain-language message. This is deliberate: a policy that permits no route is a valid configuration whose consequence is that the live call fails closed. The dry run reports that consequence honestly instead of dressing it up as a client error.
/v1/ai/routes:explainBody fields
cURL: Explain the extraction route
curl -X POST "https://api.talonic.com/v1/ai/routes:explain" \
-H "Authorization: Bearer $TALONIC_API_KEY" \
-H "Content-Type: application/json" \
-d '{"operation_type": "extraction", "model": "claude-sonnet", "class": "standard"}'Response: 201 Routable plan
{
"operation_type": "extraction",
"routable": true,
"class": "standard",
"fallback_scope": "class_chain",
"candidates": [
{ "model": "claude-sonnet", "provider": "bedrock", "region": "eu", "custom_endpoint": false }
],
"rejected": [
{ "model": "gpt-balanced", "provider": "openai", "region": "global", "reason": "provider_not_allowed" }
]
}Response: 201 Fail-closed explanation
{
"operation_type": "document_ocr",
"routable": false,
"reason": "no_compliant_route",
"message": "No route satisfies this workspace policy for this operation, so the call fails closed rather than reaching a provider the policy does not permit.",
"candidates": [],
"rejected": [
{ "model": "mistral-ocr", "provider": "mistral", "region": "global", "reason": "provider_not_allowed" }
]
}candidates is ordered: the first entry is what a live call would try first, and later entries are the fallback order under the reported fallback_scope. Every candidate carries only model, provider, region, and custom_endpoint; wire-level model identifiers, endpoint hosts, and credentials never appear. That projection is the same one every route on this surface applies, so nothing about the platform's infrastructure crosses the API boundary through a dry run either.