Authentication
Exchange the credential for a token, send it on each call and know what to do when access is refused.
The credential is a client_id and client_secret pair, created by the account holder in the dashboard with the operation PIN. It operates a single account and only the routes of the scopes it was given, and it can have a list of allowed IPs.
Get the token
Send the credential in Authorization: Basic to POST /oauth/token:
POST /api/v1/oauth/token
Authorization: Basic base64(client_id:client_secret)
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials{
"access_token": "eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..mQ3Zy1hbVhpbXBsZQ.ZXhlbXBsbw.c2lnbmF0dXJl",
"token_type": "Bearer",
"expires_in": 900,
"scope": "PAYMENT_WRITE PAYMENT_READ STATEMENT_READ"
}- The token is valid for 15 minutes (
expires_in: 900). Once it expires, the API responds401withTOKEN_INVALID: request another one from the same route. scopelists the credential's scopes, separated by spaces.client_idandclient_secretcan also go in the body, asapplication/x-www-form-urlencodedor JSON.- The route limits exchanges per
client_idand IP. Above the limit, it responds429withRetry-After.
Examples in curl and Node.js are in Getting started.
Other ways to authenticate
The API also accepts the credential directly on each call. The three ways give access to the same account, with the same scopes.
| Way | Header | The secret travels |
|---|---|---|
| Access token | Authorization: Bearer <access_token> | Only in the exchange for the token. |
| Credential in Basic | Authorization: Basic base64(client_id:client_secret) | On every call. |
| Credential token | Authorization: Bearer pzu_<prefix>_<secret> | On every call. The dashboard shows this token when the credential is created. |
The dashboard login does not work on the API routes, and the credential does not work on the dashboard.
Scopes
Each route requires a scope, and no scope includes another: PAYMENT_WRITE does not grant reading, and reading does not grant writing.
| Scope | Allows |
|---|---|
PAYMENT_WRITE | Create a charge. |
PAYMENT_READ | Get and list charges and download the receipt. |
REFUND | Refund a charge and return a deposit. |
WITHDRAW | Withdraw to a Pix key and pay a Pix copy-and-paste code. |
WITHDRAW_READ | Get and list withdrawals and download the receipt. |
INTERNAL_TRANSFER | Transfer to another PayZu account. |
INTERNAL_TRANSFER_READ | Get and list transfers, sent and received, and download the receipt. |
DEPOSIT_READ | Get a Pix received without a charge and download the receipt. |
STATEMENT_READ | Get balance, statement, limits and metrics. |
PIX_KEY_READ | List the account's Pix keys. |
PIX_KEY_WRITE | Create and delete a Pix key and set the default key. |
PIX_DICT_READ | Decode a Pix copy-and-paste code and look up the recipient. |
INFRACTION_READ | Get MED disputes. |
WEBHOOK_READ | List webhook endpoints and see whether the account has a callback secret. |
WEBHOOK_WRITE | Register, update and delete webhook endpoints and issue or rotate the callback secret. |
WITHDRAW, INTERNAL_TRANSFER, REFUND, PIX_KEY_WRITE and WEBHOOK_WRITE are never selected by default in a new credential.
IP list
When the credential's IP list is filled in on the dashboard, a call from another IP receives 403 with TOKEN_IP_NOT_ALLOWED. The rule also applies to POST /oauth/token and to using the token: a token obtained from an allowed IP is refused if it comes from another one. With an empty list, any IP is accepted.
Rotation and revocation
- The
client_secretappears once, at creation. No route returns it afterwards. - Rotating creates a new credential, with another
client_id, anotherclient_secretand the same scopes. The previous one stays valid for 24 hours. The IP list does not carry over to the new one. - Revoking invalidates the credential and the tokens it issued right away.
- Creating, rotating and revoking are done in the dashboard. The API has no route for them.
Credential rejections
They apply to every API route:
| Status | code | When |
|---|---|---|
| 401 | TOKEN_INVALID | Wrong, nonexistent or revoked credential, or expired or tampered token. Request a new token; if the rejection continues, check the credential. |
| 401 | TOKEN_EXPIRED | The credential had an expiration date, and it has passed. |
| 401 | JWT_INVALID_AUTH_FORMAT | The Authorization header is missing, or the scheme is neither Bearer nor Basic. |
| 401 | TOKEN_INVALID_AUTH_FORMAT | The Basic value does not decode to client_id:client_secret. |
| 403 | TOKEN_MISSING_SCOPE | The credential does not have the route's scope. details.scope says which one is missing. |
| 403 | TOKEN_IP_NOT_ALLOWED | The call came from an IP outside the credential's list. |
| 403 | TOKEN_HOLDER_BLOCKED | The account holder is blocked. The credential works again when the block is lifted. |
{
"message": "Esta credencial não tem permissão para esta operação.",
"code": "TOKEN_MISSING_SCOPE",
"details": { "scope": "WITHDRAW" }
}On POST /oauth/token, a missing credential or a Basic value that does not decode responds 401 with TOKEN_INVALID. The route has two more rejections:
| Status | code | When |
|---|---|---|
| 400 | TOKEN_UNSUPPORTED_GRANT_TYPE | grant_type is missing or different from client_credentials. |
| 429 | AUTH_TOO_MANY_REQUESTS | The exchange limit per client_id and IP was exceeded. Wait for the Retry-After. |