API v1 · 4xx
scope_missing
| HTTP | When |
|---|---|
| 403 | The token authenticates correctly but lacks the required scope. |
An endpoint's listed scopes are all required. When a single named
scope is the one you are missing, the message names it alongside
everything the endpoint requires, and details.required_scope repeats
that one scope for programmatic use. Do not rely on it being there for
every scope_missing: a refusal on a group of alternatives, and a
version-gated refusal like the POST /v1/uploads example below, carry
no details at all — read message in those cases.
Branch on error.code, never on error.message. The message is
human-facing prose meant for your logs and your own error surfaces; its
wording is not part of the API contract and can change without a version
bump. scope_missing is the stable signal.
A <resource>:write scope satisfies a <resource>:read requirement, so
a write-only key is never locked out of reading its own resource. That
is the only implication — holding a read scope never satisfies a write
requirement.
For example, POST /v1/notes requires notes:write. A key holding only
notes:read returns:
{
"error": {
"code": "scope_missing",
"message": "Token lacks notes:write. This endpoint requires all of: notes:write.",
"request_id": "req_…",
"docs_url": "https://docs.scripe.io/api/v1/errors#scope_missing",
"details": { "required_scope": "notes:write" }
}
}An endpoint that requires more than one scope names all of them. For
example PATCH /v1/posts/{postId}/media requires posts:write and
media:read; a key holding only media:read returns
Token lacks posts:write. This endpoint requires all of: posts:write, media:read.
Where an endpoint accepts alternatives it says so rather than leaving
you to guess. POST /v1/uploads takes sources:write or
knowledge:write, and returns As of API version 2026-08-10 this endpoint requires one of: sources:write, knowledge:write. Re-mint the key with that scope, or pin Scripe-Api-Version: 2026-08-01 until you can.
To recover, edit the key in Settings → Developer → API keys and tick the missing scope. Edits take effect on the next request — we bust the auth cache on save; if that invalidation fails the cache TTL bounds the lag at 5 minutes.
On an OAuth connection (MCP)
There is no key to edit: the grant is fixed at consent time, so the message names the only repair there is and says that retrying cannot work.
{
"error": {
"code": "scope_missing",
"message": "This connection was not granted the `posts:destroy` scope. The user has to re-authorize this connection and approve that scope; retrying this call cannot succeed. `posts:destroy` is granted only when it is asked for explicitly — no broader scope (including `posts:write` and the `write` alias) ever implies it.",
"docs_url": "https://docs.scripe.io/api/v1/errors#scope_missing"
}
}The second sentence appears only for the never-implied family —
posts:publish, settings:write and every *:destroy scope. They sit
outside the read / write aliases and outside every implication, so a
consent granted before one of them existed never gains it on deploy
day. Holding posts:write is not evidence that posts:destroy should
have been granted.
See auth.md §1.2 for the scope catalogue.