Skip to Content
Troubleshootingendpoint_scope_mismatch (403)

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:

  1. confirms the scope is granted (else scope_not_granted), then
  2. checks the call’s method + endpoint against the scope’s patterns.

If patterns exist for the scope and none match, the call is denied with endpoint_scope_mismatchbefore 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_unmapped in the audit log. endpoint_scope_mismatch only 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/42 but 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 a POST. 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.

Last updated on