Every Pix2Depix operation endpoint speaks the same response envelope, whether it succeeds or fails. The API is synchronous: the connection is held open until the result is ready, and the body comes back inline. Learn the envelope once and you can parse every call the same way. The three authentication endpoints are the one exception, described below.
A non-2xx response carries a single human-readable string under response.errorMessage, with async set to false.
{"response":{"errorMessage":"amountInCents must be between 1 and 10000000"},"async":false}
There is no asynchronous response
The API is synchronous-only. async is always false, there is no 202 Accepted and no urlResponse to poll. Sending X-Async: true is rejected up front with 400 — see Synchronous requests & safe retries.
POST /api/v2/auth/login, /refresh and /logout answer in the OAuth dialect rather than in the envelope above. There is no response wrapper and no async field:
{"error":"invalid_grant","errorMessage":"invalid or expired refresh token"}
error is a stable machine-readable code and is the field to branch on. errorMessage is free text, present so that error handling written against the envelope keeps finding a field by that name.
error
Status
Meaning
invalid_request
400
Malformed body, an unrecognised field, a wrong JSON type, or an oversized body
invalid_client
401
Login failed — unknown, revoked, or wrong secret, deliberately indistinguishable
invalid_grant
401
Refresh failed — unknown, expired, revoked, or already used
rate_limited
429
Too many attempts; honour Retry-After
temporarily_unavailable
503
Try again shortly
server_error
500
Contact support with the time of the request
A 415 means you sent a Content-Type other than application/json. It uses the same two fields, with error set to invalid_request. See Authentication for the full flow.
Branch your logic on the HTTP status code. It is the stable, machine-readable signal.
Status
Meaning for this API
200 OK
Success. Result is under response.
400 Bad Request
Malformed or invalid request (e.g. missing required field, value out of range). Also returned when the request is rejected — including X-Async: true, which is no longer supported.
401 Unauthorized
Missing, malformed, or expired token. Validate with GET /ping.
403 Forbidden
Token is valid but lacks the required scope (deposit, withdraw / withdrawal, user) for this operation, or the partner is not permitted to call it.
404 Record Not Found
The looked-up record (deposit, withdrawal, or user) does not exist.
413 Payload Too Large
The request body exceeds the accepted size limit.
422 Unprocessable Entity
Compliance block. Our risk screening declined this payer or this operation. Definitive — see Compliance blocks below.
429 Too Many Requests
Rate limit exceeded. Read the Retry-After header (seconds) and back off before retrying.
500 Server Error
Unexpected error on our side. Retryable on read-only calls; on POST /deposit and POST /withdraw see Retrying safely first.
502 Bad Gateway
We received an invalid (non-JSON) response from an upstream system. Same retry caveat as 500.
503 Service Unavailable
Temporarily unavailable, for one of two reasons. The Retry-After header tells them apart — see below.
520
A business rejection: the request was well-formed and authenticated, but refused. The reason is in response.errorMessage.
Two kinds of 503, and the header separates them
Retry-After: 300 present — maintenance freeze. The operation was switched off for maintenance and your request was refused before reaching processing. Nothing was created, so there is no outcome to check. Wait the 300 seconds and resend.No Retry-After — timeout. The request reached processing and no result came back in time. On POST /deposit and POST /withdraw the operation may already exist: confirm with GET /deposit-status or GET /withdraw-status before resending. On read-only calls, just retry.The freeze is lifted by hand, so the 300 seconds is a back-off interval, not a promise that it will be over by then.
520 is a rejection, not an outage
520 is a non-standard code inherited from the legacy gateway, and it sits in the 5xx range purely by accident of history. It means your request was refused on its merits — the amount was outside the allowed range, the Pix key did not match the beneficiary, the balance was insufficient.The consequence is the trap: most HTTP libraries retry 5xx automatically. On POST /deposit and POST /withdraw that turns a single rejection into repeated attempts, and if the underlying condition clears in between, into a duplicated operation. Disable automatic retry on these two endpoints, and treat 520 as final.
A 422 means our compliance and anti-fraud screening declined the payer or the operation. You can receive it from POST /deposit and POST /withdraw.Three things to know:
It is not something you can fix in your integration, and it is not the payer abandoning the payment. It is a decision on our side about that payer.
The message is intentionally generic and carries no reason. We do not disclose the granular criteria, so there is nothing to parse out of it.
The body carries a support reference number, and that number is the whole point. Log it and quote it when you contact support. Without it we cannot look the case up.
{"response":{"errorMessage":"After a compliance review, we are unable to process deposits for this payer at this time. If you believe this decision was made in error, please contact our support team and provide the following reference number: a1b2c3d4"},"async":false}
Do not retry a 422
Like 520, a 422 is a definitive refusal, not a transient failure. Retrying will not change the outcome, and because there is no idempotency (see below) a retry can create a second operation. Surface the reference number instead.
A 503 can mean timeout
Because the gateway holds the connection open for a synchronous result, a slow upstream surfaces as 503 (timeout) rather than a deferred response. On a call that moves money, a timeout does not tell you whether the operation was created — check its status before you retry (see Retrying safely).
response.errorMessage is a free-text, human-readable string intended for logs and debugging. It is not a stable, machine-readable error code, and there is no error-code enum. The wording can change at any time, so never match on its contents to drive control flow. Decide what to do based on the HTTP status code instead.
429 is safe to retry once you have waited out its Retry-After, and so are 5xx responses (500, 502, 503) on read-only calls.
There is no idempotency key — retries can duplicate money
The API does not support idempotent retries. X-Nonce is generated by the server and returned to you for tracing; a nonce you send on the request is not read and does not deduplicate anything. There is no other idempotency mechanism.The consequence on POST /deposit and POST /withdraw is direct: a blind retry after a timeout or a 5xxcan execute the operation twice. Before resending, confirm the outcome with GET /deposit-status or GET /withdraw-status, or wait for the webhook. Turning off your HTTP library's automatic retry on these two endpoints is the safest default, since most libraries retry 5xx on their own — and the V1 rejection code 520 falls in that range.
Every response includes an X-Request-ID, and the X-Nonce the server generated, both of which are worth logging and quoting when you contact support.