1. Docs
Pix2DePix API
  • ⚡ Quickstart
  • 📝 Changelog
  • 📖 Glossary
  • Docs
    • 🔑 Authentication
    • 🪝 Webhooks
    • ⚠️ Errors & the response envelope
    • ✅ Possible Statuses
      • 📥💰 Deposit Statuses
      • 📤💸 Withdraw Statuses
    • 🧩 Advanced
      • 🔀 Synchronous requests & safe retries
    • 🛡️ Security & Limits
      • ✅ Best Practices
      • 🪲 Bug Bounty
      • 🧱 Firewall
      • 🚦 API Limits
    • 🚀 Features
      • ⏱️ QR Delay
  • API Endpoints
    • Ping
      GET
    • Deposit (PIX ➔ DePix)
      POST
    • Deposit Status
      GET
    • Deposits
      GET
    • User Info
      GET
    • Withdraw
      POST
    • Withdraw Status
      GET
  • Webhooks
    • Deposit Webhook
    • Withdraw Webhook
    • MED Webhook
  • Schemas
    • AllPossibleObjResponses
    • StandardResponse
    • PingObjOrError
    • JWTClaims
    • ObjResponse
    • ErrorObj
    • PingObj
    • ErrorResponse
    • DepositRequest
    • DepositObjOrError
    • DepositObj
    • PingResponse
    • DepositResponse
    • DepositStatusObj
    • DepositStatusResponse
    • DepositWebhookBody
    • DepositsResponse
    • DepositStatus
    • UserInfoResponse
    • WithdrawStatusResponse
    • WithdrawStatusObj
    • WithdrawResponse
    • WithdrawObj
    • WithdrawStatus
    • WithdrawWebhookBody
    • MEDWebhookBody
    • RejectionReasons
  1. Docs

⚠️ Errors & the response envelope

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.

The envelope#

There are exactly two shapes you will receive.

Synchronous success#

A 2xx response wraps the result object under response, with async set to false.
{
  "response": { "...": "endpoint-specific fields" },
  "async": false
}

Synchronous error#

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.

The authentication endpoints are the exception#

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.
errorStatusMeaning
invalid_request400Malformed body, an unrecognised field, a wrong JSON type, or an oversized body
invalid_client401Login failed — unknown, revoked, or wrong secret, deliberately indistinguishable
invalid_grant401Refresh failed — unknown, expired, revoked, or already used
rate_limited429Too many attempts; honour Retry-After
temporarily_unavailable503Try again shortly
server_error500Contact 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.

HTTP status codes#

Branch your logic on the HTTP status code. It is the stable, machine-readable signal.
StatusMeaning for this API
200 OKSuccess. Result is under response.
400 Bad RequestMalformed 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 UnauthorizedMissing, malformed, or expired token. Validate with GET /ping.
403 ForbiddenToken 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 FoundThe looked-up record (deposit, withdrawal, or user) does not exist.
413 Payload Too LargeThe request body exceeds the accepted size limit.
422 Unprocessable EntityCompliance block. Our risk screening declined this payer or this operation. Definitive — see Compliance blocks below.
429 Too Many RequestsRate limit exceeded. Read the Retry-After header (seconds) and back off before retrying.
500 Server ErrorUnexpected error on our side. Retryable on read-only calls; on POST /deposit and POST /withdraw see Retrying safely first.
502 Bad GatewayWe received an invalid (non-JSON) response from an upstream system. Same retry caveat as 500.
503 Service UnavailableTemporarily unavailable, for one of two reasons. The Retry-After header tells them apart — see below.
520A 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.

Compliance blocks (422)#

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).

Example error response#

A 400 from POST /deposit with an out-of-range amount:
{
  "response": { "errorMessage": "amountInCents must be between 1 and 10000000" },
  "async": false
}

errorMessage is not a machine code#

Branch on HTTP status, not on errorMessage text
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.

Retrying safely#

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 5xx can 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.
Modified at 2026-08-12 02:15:53
Previous
🪝 Webhooks
Next
📥💰 Deposit Statuses
Built with