Three kinds of surface live here. Route qualifiers are the operator’s written routing rules, published into the workspace snapshot the gateway loads. Capabilities and bindings are reads over the frozen capability packs, marked with what this workspace can actually reach. Route decisions are the decisions the gateway recorded on receipts. Control never runs the selector. Nothing on this page scores a candidate or says what would be chosen — obol-route::select in the gateway is the only thing that selects, and a second implementation in the dashboard would drift against it.

Authentication

All routes require Authorization: Bearer <session token>, verified as /api-reference/overview describes. Reads need the workspace.read action (viewer and above); qualifier writes need routing.publish (admin or owner). A non-member gets 404 workspace not found; a member without the action gets 403 action not permitted; a frozen workspace answers 409 workspace is frozen on writes.

List route qualifiers

GET /api/v1/workspaces/{workspace_id}/route-qualifiers Every qualifier in the workspace, ordered by qualifier_id.
string
required
Workspace to read.
array
operator_route_qualifier documents. Each carries the workspace’s current publication state as its own copy.
A stored row that somehow carries more than one active constraint cannot be projected honestly, so the list answers 500 with {"code": "route_qualifier_payload_invalid"} and, when it is a safe slug, the offending qualifier_id.

Create or update a route qualifier

POST /api/v1/workspaces/{workspace_id}/route-qualifiers One endpoint for both create and update, keyed by qualifier_id. The request model forbids unknown fields — a body with a misspelled key is a 422, not a silently dropped rule.
string
required
1–128 characters from [a-z0-9_-]. Anything else is 400 qualifier_id must be [a-z0-9_-].
string
The capability this rule applies to, for example cap.web.search. Must be cap. followed by a non-empty tool segment, at most 256 code points, and must contain no line terminators. Anything else is 400 capability_id must use the cap prefix. Omit to apply the qualifier without a capability.
object
default:"{ kind: none }"
A tagged constraint. One of:
  • {"kind": "none"}
  • {"kind": "only", "targets": ["exa", "tavily"]}
  • {"kind": "ignore", "targets": ["exa"]}
  • {"kind": "pin", "target": "exa"}
targets holds at most 32 entries of 1–256 characters each.
array
default:"[]"
Up to 8 of oauth2, api_key.
array
default:"[]"
Up to 8 of mcp, rest.
array
default:"[]"
Up to 8 of native, nango, composio.
integer
0–10000 basis points. A hand-authored prior, not a measurement.
integer
0 to 2^63 − 1. Stored as max_cost in the published payload.
boolean
default:"false"
Reserved. Control accepts, stores, and publishes it; nothing in the gateway acts on it, and no promise is made about it. ADR-0027 makes a fallback a new authorization with its own Cedar decision and idempotency key, so the behavior will arrive with its own contract rather than by this flag changing meaning.
integer
Optimistic concurrency. When present it must equal the stored revision, and the qualifier must already exist — claiming a revision of an absent row is 409 route_qualifier_revision_conflict. The field is body-only; a query parameter of the same name is ignored.

Response

200 with the same operator_route_qualifier document the list returns, carrying the new revision and the publication state after the write.

Errors

Delete a route qualifier

DELETE /api/v1/workspaces/{workspace_id}/route-qualifiers/{qualifier_id}
string
required
The qualifier to remove.
integer
Minimum 1. When present it must equal the stored revision.
Answers 200 with a tombstone: the same document shape, deleted: true, and the revision the row held when it was removed. The tombstone is a response only — the row is gone before the commit, so no deleted flag reaches the published route policy. 404 qualifier not found when the qualifier does not exist in the workspace; 409 route_qualifier_revision_conflict on a revision mismatch.

List capabilities

GET /api/v1/workspaces/{workspace_id}/capabilities Every cap.* capability in the frozen packs, sorted by capability id, with the counts that depend on this workspace.
string
The workspace environment. The evidence floor is derived from it.
array
Capability projections.
string
For example cap.account.create.
string
From the pack’s EffectSpecread, create, update, delete, or external_side_effect. Defaults to read when the pack declares none.
boolean
effect_kind != "read". The same derivation the synthesized tool uses, so the screen and the approval gate cannot disagree.
string
The minimum evidence class a route must be able to reach. Outside production this is always untrusted. In production: gateway_observed for delete and external_side_effect, broker_attested for create and update, and untrusted for everything else.
integer
How many bindings the packs declare for this capability.
integer
How many of those are both non-quarantined and backed by a connection this workspace holds — the set the snapshot actually publishes.
Also returned per capability: display_name, description, feature_vocabulary, and status.

List a capability’s bindings

GET /api/v1/workspaces/{workspace_id}/capabilities/{capability_id}/bindings The providers that can serve one capability, sorted by binding_id.
string
required
A cap.-prefixed capability id. A malformed value is 400 capability_id must use the cap prefix; a well-formed id no pack declares is 404 capability not found.
object
The same capability projection the list returns.
string
The concrete tool, for example salesforce.create_account. connector_slug is its first segment.
string
native, trusted_worker, or federated.
string
The strongest evidence this route can produce. Federated routes cap at broker_attested or untrusted and can never reach verified — see /connectors/federated.
boolean
Whether the ceiling ranks at or above the capability’s derived floor, over the ranking untrusted < broker_attested < connector_attested < vendor_signed < gateway_observed. An unrecognized class ranks below every known one.
object
base_micros_usd is null when the pack publishes no base cost — bundled in a broker’s plan, or simply unpriced. It is never fabricated as 0. bundled_in_broker is a boolean.
integer
A hand-authored prior in basis points. There is no latency or success-rate signal behind it, and any surface rendering it must say so.
boolean
Whether this workspace holds a connection that reaches the binding.
Also returned per binding: binding_id, capability_id, custody, surface, auth_modes, catalog_source, coverage (a list of {feature, claim, fidelity}), shared_quota, idempotency_propagation, and status.

List route decisions

GET /api/v1/workspaces/{workspace_id}/route-decisions Routing decisions the gateway took, newest first. Membership is exactly “the receipt carries a route” — a receipt with no route_capability was never routed.
integer
default:"50"
1–200.
string
Opaque receipt cursor from a previous next_before. A legacy RFC3339 timestamp is still accepted as a bound; newly issued cursors are v1. followed by unpadded base64url of [timestamp, receipt_id] so ties inside a second can be traversed (ADR-0042). A malformed cursor is 400 invalid receipt cursor, never silently replaced with the current time.
string
Filter to one cap.-prefixed capability. A malformed value is refused with 400 capability_id must use the cap prefix rather than ignored.
array
One row per routed receipt.
string
The target the selector chose, with binding_id and reason alongside it. reason comes from a closed vocabulary: best_score, only_candidate, pinned_by_policy, pinned_by_request, fallback_after_failure.
string
What the chosen route could have proven, from the decision envelope.
string
What the call actually achieved. Both are returned so the gap between them is visible.
integer
How many candidates were scored. excluded_count counts the rest, and excluded lists up to 8 of them as {target, binding_id, reason} — the selector bounds both lists at 8, so nothing is paginated inside a row. The exclusion reason is also closed: coverage_missing, qualifier_only, qualifier_ignore, qualifier_pin, auth_mismatch, surface_mismatch, evidence_floor, cost_ceiling, quality_floor, inactive, target_missing, destructive_federated, quarantined, candidate_cap, request_widens_policy, circuit_open, success_floor, not_authorized.
string
Cursor for the next page, or null on the last page.
Also returned per decision: receipt_id, occurred_at, capability, surface, catalog_source, evidence_floor, state, decision, algorithm_version, and snapshot_version. Per-candidate scores and weights stay in the receipt envelope — read one decision at a time through /api-reference/operator.
Related reading: /routing/capabilities for the capability model and /routing/catalog for how bindings reach a snapshot.