Errors
Every failed call answers with JSON in the same shape, whatever your Accept header says. Branch on its code.
The error body
A failure carries a human-readable message and a stable code:
{
"code": "missing_ability",
"message": "Invalid ability provided."
}
A validation failure adds errors: per field, the messages saying what is wrong with it.
{
"code": "validation_failed",
"message": "The lines field is required.",
"errors": {
"lines": ["The lines field is required."]
}
}
The message is meant for people and can change. The code is part of the contract: codes are only ever added, never renamed or removed. If your integration meets a code it doesn't know, go by the HTTP status.
Every code
| Code | Status | What happened | What to do |
|---|---|---|---|
validation_failed |
422 | The request body or query has a field Picked can't accept. errors says which. |
Fix the request. Sending it again unchanged fails again. |
bad_request |
400 | The request is malformed, for example a Picked-Dry-Run header that isn't true or false. Also used for any other 4xx status this table doesn't list. |
Fix the request. |
unauthenticated |
401 | The token is missing, mistyped or revoked, or the member who issued it was removed from the workspace. | Stop, and check the token your integration sends. |
missing_ability |
403 | The token is valid but lacks the ability this endpoint needs. | Stop, and issue a token with the right ability. See Authentication & abilities. |
forbidden |
403 | The token may not act here, for example because you called another workspace's address with it. | Stop, and check the address and the token. |
not_found |
404 | The address doesn't exist, or the thing you asked for isn't in your workspace: an unknown SKU, an unknown receipt id. | Fix the request. Don't retry. |
method_not_allowed |
405 | The address exists, but not for this HTTP method. | Fix the request. |
conflict |
409 | An Idempotency-Key was already used for a different request, or a request with it is still being handled. |
Send a new key; for a request still being handled, retry shortly. |
gone |
410 | The thing you called back on no longer waits for you: a step's callback after it timed out or ended another way. | Stop: the work has gone its own way. Don't retry. |
rate_limited |
429 | Your workspace made too many calls this minute. | Wait the number of seconds in Retry-After, then retry. See Rate limits. |
temporarily_unavailable |
503 | The call ran into a concurrent one, or the work you called back on can't carry on yet. | Wait the number of seconds in Retry-After, then retry. |
server_error |
500 | Something went wrong on Picked's side. | Retry a few times with a growing pause. If it keeps failing, contact us. |
Retrying safely
Only rate_limited, temporarily_unavailable and server_error are worth retrying, always with a pause in between. Every other code means the same request will fail the same way, with two exceptions that Idempotency explains: a receipt whose reference arrived twice at the same moment, and a process event that Picked accepted but couldn't act on yet.
Retry a write only when Picked can recognise the repeat; the same page says which writes it recognises.
Find a failed call
The API tokens page lists each token's recent calls under Requests. It shows only the failed ones until you switch off Errors only. For a failure it keeps the response Picked gave; for a 5xx, only its code. That is the quickest way to see which call went wrong and why.