Access errors
What an API token or OAuth grant is refused with, and what to do about it.
On this page
A call made with an API token (pltu_, plto_, pltd_) or an OAuth access token is decided by Poltura's
gateway against the grant behind it. A refusal always has the same JSON body:
{
"error": "permission_denied",
"message": "this token does not allow orders.write on store:0199f3a2-7c41-7d2e-9b1a-3c5e8f0a1b2c",
"permission": "orders.write",
"resource": "store:0199f3a2-7c41-7d2e-9b1a-3c5e8f0a1b2c",
"documentation_url": "https://developers.poltura.com/api/errors#permission-denied"
}permission and resource appear when the refusal is about one permission on one store or organisation.
invalid-token
401. The credential is unknown, malformed, expired, revoked, disabled or exposed. The response carries
WWW-Authenticate: Bearer error="invalid_token", resource_metadata="…"; an OAuth client starts a new
authorization there, a token holder creates or rolls the token.
invalid-dpop-proof
401. The credential is a Poltura CLI sign-in, which is bound to a key (DPoP),
and the request did not prove it holds that key. The proof is missing, signed by another key, made for another
method, URL or token, too old, or already used. The token was sent as Bearer instead of DPoP, or another
credential was sent with the DPoP scheme. The response carries WWW-Authenticate: DPoP error="invalid_dpop_proof".
Use the CLI on the machine that signed in, or run poltura login again.
use-dpop-nonce
401 (400 from the token endpoint). The proof's time is too far from the server's. The response carries a
DPoP-Nonce header: put that value in the proof's nonce claim and send the request again. The CLI does
this itself.
insufficient-scope
403. An OAuth grant lacks the permission, and asking for it again can help. The challenge lists every
permission the grant already holds plus the missing one:
WWW-Authenticate: Bearer error="insufficient_scope", scope="orders.read products.read orders.write",
resource_metadata="…"Re-authorize with exactly that scope. A grant that already holds the permission is never challenged again.
permission-denied
403. The token's policy does not allow the permission, the grant does not reach this store, or the
person who owns the access no longer holds the permission in their role. No new consent changes that.
not-delegable
403. The permission can never be used through a credential: it needs a second factor, belongs to an owner
only, manages access itself, or moves money. Do it in the admin.
ip-not-allowed
403. The token is limited to addresses this call did not come from.
access-disabled
403. The organisation switched off third-party apps and agents, the store switched off AI agents for
/mcp, the client was blocked, or the store or the grant is suspended.
approval-required
409. An agent action is waiting for a person to approve it. See Approval.
request-too-costly
400. One call would cost more than 100 points, for example a list with a very large limit and many includes. It
was not run. Ask for a smaller page or fewer includes. The costs are on
How agents act on your store.
rate-limited
429. The points budget of this credential, of this client at this store, or of the store as a whole is used up.
The body names the bucket that ran out and retryAfterMs, and the response carries Retry-After in seconds and
the X-Poltura-RateLimit-* headers. The call did not reach the store, so waiting that long and sending it again is
safe. Inside an MCP tool call the same refusal is a tool error with the code rate_limited.
unavailable
503. Poltura could not decide the call just now. Retry after Retry-After seconds; nothing was done.