Errors and limits
Error format
Errors are RFC 9457 problem details (application/problem+json) with a stable code that your code can rely on. The text in title and detail can change; the code does not.
{
"type": "about:blank",
"title": "Unprocessable Entity",
"status": 422,
"code": "VALIDATION_FAILED",
"detail": "The request is invalid",
"errors": [{ "path": "/action/amount_minor", "message": "must be a whole number of minor units" }]
}
Error codes
Every code the API can return is listed here, with its HTTP status. A code is stable: match on it, not on the detail text. The MCP tools return the same codes inside a tool result.
| HTTP | Code | When |
|---|---|---|
| 400 | MALFORMED_REQUEST | The body is not valid JSON, has the wrong shape, or a parameter has the wrong type. |
| 400 | IDEMPOTENCY_KEY_REQUIRED | authorize without an Idempotency-Key of 1 to 128 characters. |
| 400 | UNSUPPORTED_PROTOCOL_VERSION | An MCP request names a protocol version the server does not support; the detail lists the supported ones. |
| 401 | UNAUTHENTICATED | No key, or an unknown or revoked key. |
| 403 | FORBIDDEN | The key's role may not call this operation. |
| 403 | ORIGIN_NOT_ALLOWED | An MCP request carries an Origin header: MCP clients are programs, not web pages. |
| 404 | NOT_FOUND | No such object, or it belongs to another tenant. |
| 405 | METHOD_NOT_ALLOWED | The path exists, but not for this HTTP method. |
| 409 | IDEMPOTENCY_KEY_REUSED | The key was used with a different body. |
| 409 | IDEMPOTENCY_KEY_EXPIRED | The key is past its 24 hour retention. |
| 409 | ALREADY_REPORTED | The authorization already has its terminal report. |
| 409 | AUTHORIZATION_STILL_VALID | An admin tried to release an authorization the enforcement point can still use until it expires. |
| 409 | MANDATE_REVOKED, MANDATE_SUSPENDED, MANDATE_NOT_ACTIVE | The mandate's state does not allow the operation. |
| 409 | MANDATE_NOT_SUSPENDED | Resume was called on a mandate that is not suspended (a draft is activated, not resumed). |
| 409 | NOTHING_TO_ACTIVATE | The latest version of the mandate is already in force. |
| 409 | POLICY_RETIRED | A retired policy version cannot be published again. |
| 409 | VERSION_EXISTS | This agent version is already registered. |
| 409 | BENEFICIARY_EXISTS | A beneficiary with this external_id already exists. |
| 409 | LAST_ADMIN_KEY | You cannot revoke the last active admin key. |
| 409 | AP2_ALREADY_IMPORTED | This AP2 credential was already imported; the detail names the mandate. |
| 410 | REPORT_WINDOW_CLOSED | More than 24 hours after the authorization expired. |
| 415 | UNSUPPORTED_MEDIA_TYPE | The request is not Content-Type: application/json. |
| 422 | VALIDATION_FAILED | A field is missing, out of range or malformed; errors lists each. |
| 422 | UNKNOWN_PRINCIPAL | The principal named in the request does not exist. |
| 422 | RANGE_TOO_LARGE | An evidence export of more than 10,000 records. |
| 422 | MANDATE_INVALID | The mandate document does not meet the schema; errors lists each problem. |
| 422 | MANDATE_INCOMPLETE | The mandate lacks parts it needs before it can be activated; add a version with them. |
| 422 | MANDATE_NOT_ACTIVATABLE | This mandate version cannot be activated yet; errors says why. |
| 422 | POLICY_INVALID | The policy is not valid Cedar for the Polivor schema; errors carries line and column. |
| 422 | AP2_MALFORMED | The credential is not a well-formed signed SD-JWT, or its mandate is not shaped as expected. |
| 422 | AP2_SIGNATURE_INVALID | The credential signature is wrong, or it is not signed with ES256. |
| 422 | AP2_UNKNOWN_KEY | The credential names no signing key, or one that is not among the trusted issuer's keys. |
| 422 | AP2_DISCLOSURE_INVALID | A disclosure is malformed, repeated, unreferenced or misplaced, or the credential is nested too deeply or uses a hash other than sha-256. |
| 422 | AP2_ISSUER_KEYS_UNAVAILABLE | The issuer's key set could not be fetched, or is not acceptable. |
| 422 | AP2_UNSUPPORTED_MANDATE | The credential is not an Open Payment Mandate. |
| 422 | AP2_EXPIRED | The credential has expired, has no expiry, or its execution window leaves no valid time. |
| 422 | AP2_NOT_YET_VALID | The credential is issued in the future. |
| 422 | AP2_NO_AGENT_KEY | The mandate does not name the agent's key. |
| 422 | AP2_AMOUNT_NOT_INTEGER | The maximum amount is not a whole number of minor units. |
| 422 | AP2_CONSTRAINT_INVALID | A constraint is malformed: no type, a bad currency or time, or a repeated amount range. |
| 422 | AP2_UNENFORCED_CONSTRAINTS | The credential carries constraints Polivor does not enforce, and the import did not accept that. |
| 422 | AP2_UNKNOWN_CONSTRAINT | The credential carries constraint types this adapter does not know. |
| 429 | RATE_LIMITED | Too many requests; see below. |
| 500 | INTERNAL | An unexpected error. The detail carries a reference to quote when you ask for help. |
Every operation in the reference lists the status codes it can return.
Rate limits
Each API key has its own limit: 20 requests per second for agent and enforcer keys and 5 per second for admin keys, with a burst of one second. Over the limit you get 429 RATE_LIMITED with a Retry-After header; wait that long and retry. If one key is not enough, issue more keys for the same agent.
Idempotency
Retrying authorize with the same Idempotency-Key and body is safe and returns the original response for 24 hours. Retry network failures and 5xx answers with the same key.
Conventions
- Money: integer minor units and an ISO 4217 currency. No decimals.
- Times: ISO 8601 in UTC, for example
2026-10-14T10:05:00Z. - Ids: UUIDs. IBANs: spaces and lower case are accepted and normalised; the checksum is verified.
- Unknown fields in requests are ignored where the reference says so, and refused where it does not.
- Response time: the
authorizeresponse carries aServer-Timing: authorize;dur=<milliseconds>header with the time the decision took inside Polivor.