Skip to content

Errors

Every failed /api/v1 request returns the same JSON envelope. Branch on the machine-readable code rather than parsing message text — the code is stable across API versions, the message is not.

{
"error": {
"type": "invalid_request",
"code": "invalid_ttl",
"message": "ttl must be one of 1h, 24h, 3d, 7d",
"request_id": "req_9tKf2mVxWyN4rLbJfHd3",
"doc_url": "https://www.personablocks.io/docs/api/errors#invalid_ttl"
}
}
FieldWhat it’s for
typeA coarse category (see the table below) — dispatch broad handling on this.
codeThe specific, stable error identifier — branch your logic on this.
messageA human-readable explanation. For logs and support, not for parsing.
request_idThe req_... id of this request. Quote it in support tickets.
doc_urlA deep link to this code’s entry on this page.

The type maps to the HTTP status by default:

StatustypeMeaning
400invalid_requestThe request was malformed or failed validation.
401authenticationThe API key was missing or invalid.
402billingPayment is required — the account is past due.
403permissionAuthenticated, but not allowed to do this.
404not_foundNo such route or resource.
409conflictThe resource’s current state forbids the operation.
429rate_limitedToo many requests — back off and retry.
5xxinternalSomething failed on our side.

Every code the API can return, grouped by category.

401. No Authorization: Bearer token was supplied. Send your key as Authorization: Bearer pb_sk_....

401. A key was supplied but doesn’t match an active key, or isn’t a pb_sk_test_/pb_sk_live_ secret key. The key may be revoked or mistyped — mint a fresh one from the Developers tab.

401. A short-lived pb_ct_ console token (used by the in-browser API console) is invalid or has expired. Mint a new one from the Developers tab.

403. Your key lacks the scope this route requires. Re-mint the key with the needed scope — see Authentication.

403. A sandbox (pb_sk_test_) key tried to write live customer data (suspend/unsuspend, notes, review resolution, or a reverification mint). These routes touch real records regardless of key mode, so they require a pb_sk_live_ key.

403. You tried to edit a customer note authored by someone else. A key can only edit notes it created.

429. You exceeded the per-key limit for this endpoint class. The response carries Retry-After (seconds) plus X-RateLimit-Limit and X-RateLimit-Remaining. Back off and retry — see Rate limits.

402 · type: billing. The merchant account is past due (frozen). Live API writes are paused until the balance is settled in the billing portal.

403 · type: billing. The subscription has been canceled. Live API access requires an active subscription — reactivate it to resume.

403 · type: billing. There is no active subscription (never activated, or still pending approval). Complete billing setup to enable live API writes.

409. A verification session was asked to move to a status that isn’t a legal forward step from its current one (the status machine is forward-only, and terminal states are final). Re-read the session to see its actual status.

409. You tried to cancel a verification session that is already processing or in a terminal state. Only created or opened sessions can be canceled.

409. This review has already been resolved. Re-read it to see the resulting tier.

409. This review’s tier isn’t in a resolvable state — only REVIEW/FAIL tiers can be resolved.

404. No route matches the method and path. Check the path against the interactive reference.

404. No verification session with that id exists for this key. This also masks a session that belongs to another merchant, or to the other key mode — a test key can never confirm a live session exists, or vice versa.

404. No customer with that address is associated with your merchant account. Sandbox keys also get this on every customer read, since the sandbox has no live roster.

404. No customer note with that id exists.

404. No review with that id exists for your merchant wallet (this also covers a malformed rev_... id, or one that belongs to someone else).

404. No decision ruleset is available for your merchant (neither a custom ruleset nor the platform fallback resolved).

404. No reverification request with that id exists for your merchant wallet (also covers a malformed rvr_... id, or a test key polling — reverification requests are created live-only).

404. No webhook endpoint with that id exists for this key’s mode, or it has been disabled (a disabled endpoint can’t be test-pinged). Deletion, rotation, and test are only valid on an active endpoint.

404. No event with that id exists for this key’s mode.

405. The path exists but not for this HTTP method.

400. The request body or parameters failed the OpenAPI schema (a bad enum value, an over-long field, a missing required field). The message names the first offending field and what was wrong.

400. A required field is missing or malformed on a route without a more specific code — for example, suspend needs vic_id and reason; a reverification mint needs customer_address and vic_id. The message says which.

400. The limit query parameter isn’t an integer between 1 and 100.

400. theme_id was sent when minting a session. It is reserved and not yet supported — remove it and use appearance instead.

400. The created[gte] filter isn’t a unix timestamp in seconds.

400. The created[lte] filter isn’t a unix timestamp in seconds.

400. flows isn’t a non-empty subset of ["wallet", "phone"].

400. ttl isn’t one of 1h, 24h, 3d, 7d.

400. The customer object contains keys outside { emailAddress, phoneNumber }.

400. metadata serializes to more than 4096 bytes. Trim it or store the bulk on your side keyed by the session id.

400. The appearance object exceeds its size cap. Trim it — see the SDK theming reference.

400. appearance contains a key that isn’t in the allowed set. The message names the offending key.

400. return_url isn’t a valid http(s) URL.

400. In live mode, the return_url’s origin isn’t a registered origin for your merchant. Register the origin (via your Store Key connection or a webhook endpoint) before using it.

400. test_outcome or test_delay_seconds was sent with a live key. Those fields exist only in the sandbox — drop them, or use a pb_sk_test_ key.

400. test_outcome isn’t one of approved, review, failed.

400. test_delay_seconds isn’t an integer between 0 and 120.

400. An internal status-machine guard: a verification session was asked to transition to a value that isn’t a known session status. You should not see this from normal API use — quote the request_id if you do.

400. The updated_since filter isn’t an ISO 8601 timestamp or unix seconds.

400. A customer note’s content isn’t a string between 1 and 5000 characters.

400. This resource (decisioning or reverification) is keyed by a merchant wallet address, but your key belongs to an email-based merchant with no wallet. These endpoints are unavailable to wallet-less accounts.

400. A review-resolution outcome isn’t pass or fail.

400. A reverification addons array contains a value outside { bank, phone }.

400. A webhook endpoint url isn’t a valid http(s) URL.

400. A webhook endpoint’s events isn’t a non-empty subset of the event catalog.

5xx · type: internal. Something failed on our side. The message is deliberately generic; the detail is in our logs against this request_id. Retry idempotent reads; quote the request_id in support.

503 · type: internal. A billing-entitlement check couldn’t complete, so the request was blocked rather than allowed (this gate fails closed on live writes). Retry shortly.

503 · type: internal. The on-chain VIC client needed for a suspend/unsuspend write is momentarily unavailable. Note: this condition currently surfaces on the wire as internal_error — 500-class responses are sanitized before they leave the API — so you won’t see this code in a response today. It is documented here because it identifies the condition server-side and may surface directly in a future release. Treat any 5xx as retryable with backoff.