endpoint_scope_mismatch (403)
What you’ll see
{
"error": "Permission denied",
"reason": "endpoint_scope_mismatch",
"correlation_id": "...",
"report_hint": { /* ... */ }
}HTTP status 403. The agent holds the scope it presented, but the actual
(method, endpoint) of the call is not one that scope is defined to cover.
Why
A scope is more than a label. Each scope maps a name to one or more
(HTTP method, endpoint pattern) pairs (see
Manage platform scopes). On every
POST /v1/actions the proxy:
- confirms the scope is granted (else
scope_not_granted), then - checks the call’s
method+endpointagainst the scope’s patterns.
If patterns exist for the scope and none match, the call is denied with
endpoint_scope_mismatch — before any credential is decrypted and before
any upstream request. An audit row is written with result = "denied". This is
the platform working as designed: it stops a read scope being used to write or
delete, regardless of what the agent claims.
If a scope has no patterns defined yet, the call is not blocked — it passes through (legacy behaviour) and is flagged
scope_unmappedin the audit log.endpoint_scope_mismatchonly fires for scopes that have patterns.
Fix
Triage in this order:
1. The endpoint is legitimate but no pattern covers it
Open Policies → Manage scopes, find the scope, and check its patterns against the call. Common mismatches:
- Wrong wildcard depth.
/api/card/*matches/api/card/42but not/api/card/42/query. Add a**pattern (/api/card/**) or a specific one (POST /api/card/*/query) if the deeper path is intended. - Wrong method. A
GET-only scope won’t allow aPOST. Add the method (or a second pattern) if the write is intended. - Trailing-slash / host / query are normalised and ignored, so those are never the cause.
For a curated scope you can’t edit, add a custom scope with the right pattern and grant that instead.
2. The agent is calling the wrong endpoint
The scope is correct; the agent constructed a path the scope shouldn’t cover
(e.g. asking a read scope to hit a delete endpoint). That’s the guardrail
doing its job — no change needed. Repeated mismatches feed the
circuit breaker.
3. Scope-id mismatch
The scope id the agent presents must exactly equal both the granted scope and
the scope_id on the pattern rows (ids are platform-specific, e.g.
metabase:card.read). If a freshly-defined scope never seems to match, confirm
the id is identical in all three places.