Meet the new Scripe, live on October 7.Register

API v1 · 4xx

scope_missing

HTTPWhen
403The 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:

json
{
  "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.

json
{
  "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.