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 requireAuthorization: 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.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.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
EffectSpec — read, 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.
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.
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.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.