API Introduction
Environments, authentication, response and error formats, pagination and card hashes
Welcome to the NinePay API reference. This API is how you integrate your system with ours.
Our API is RESTful and all of its responses are in JSON, at the base endpoint:
https://payments.cefis.com.br/api/v1/The full list of endpoints, with parameters, responses and examples in several languages, is in the API Reference.
Environments
In addition to the production environment, a sandbox environment is available for integration testing. All communication with the sandbox environment must use the URL:
https://api.sandbox.cefispay.com.br/api/v1/See how to request access in Test Environment.
Authentication
All requests that require authentication must send, in the Authorization header, the API token provided by our team, always for the intended environment, in the following format:
Authorization: Bearer <token>The token is a secret
Use the token only in your back end. Never put it in code that runs in the browser or in your customer's app.
Key prefixes (sk_prod_ and sk_hmlg_), creation, rotation and expiration are covered in Authentication.
Headers
We recommend setting the request headers correctly to work with the data types sent to and received from the API:
- All requests must include the
Content-Type: application/jsonheader, unless the specific endpoint says otherwise. - All requests must include the
Accept: application/jsonheader, unless the specific endpoint says otherwise.
Responses
- Resources come inside
data:GET /customers/{id}responds with{ "data": { ... } }. - Creations respond with 201.
POST /paymentsresponds with{ "customer", "payment", "invoice_url" }(invoice_urlonly for bank slips) andPOST /subscriptionsresponds with{ "customer", "subscription" }. - Amounts are always in cents (integer): R$ 50.00 is
5000.
Errors
Every error has the same format:
{
"code": "VALIDATION_ERROR",
"message": "Os dados informados são inválidos.",
"errors": { "email": ["O campo email é obrigatório."] },
"timestamp": "2026-10-22T12:03:54+00:00",
"request_id": "64d62d36-e797-45ed-a9d5-c203bde81032",
"documentation_url": "https://docs.ninepay.com.br",
"suggestion": "Corrija os campos e tente novamente."
}errors is only present on validation errors, with a list of messages per field. suggestion is present when there is one. Messages are returned in Portuguese. Keep the request_id when contacting support.
code | HTTP | When |
|---|---|---|
AUTHENTICATION_ERROR | 401 | Missing or invalid token |
FORBIDDEN_ERROR | 403 | The resource does not belong to your company. On several routes, an id that does not exist also responds with 403, so as not to reveal which ids exist |
COMPANYBLOCKED_ERROR | 403 | The account is suspended |
RESOURCE_NOT_FOUND_ERROR | 404 | The resource does not exist |
ROUTE_NOT_FOUND_ERROR | 404 | The route does not exist |
METHOD_NOT_ALLOWED_ERROR | 405 | The method is not valid for the route (e.g. PUT /customers without the id) |
INVALID_STATE_ERROR | 400 | The resource is not in a state that allows the action (e.g. canceling a payment that is already paid) |
VALIDATION_ERROR | 422 | Invalid data; see errors |
REFUND_NOT_ALLOWED | 422 | The payment cannot be refunded |
TOO_MANY_ATTEMPTS | 429 | Too many requests; respect the Retry-After header |
UNEXPECTED_ERROR | 500 | Our error; if it happens again, contact support with the request_id |
Pagination
List endpoints accept page (starting at 1) and limit (items per page: 10 by default, from 1 to 100). The response contains data (the items), links and meta (current_page, last_page, per_page, total). To go through everything, increase page up to meta.last_page.
Dates
- Calendar dates (
due_date,next_payment) are sent asYYYY-MM-DDin requests. - In responses, date-time fields come in two formats: ISO 8601 in UTC (
2026-10-27T03:00:00.000000Z, in the payment'sdue_dateandref_date) andYYYY-MM-DD HH:MM:SSin Brasília time (created_atandupdated_at). Parse each field according to its format.
Identifiers
Every resource has a numeric id and a uuid. Use the id in authenticated routes and the uuid in everything that is public (the payment link, the checkout and the bank slip PDF).
Card hash
A card hash is an encrypted version of all of your customer's card data. It is the safest way to send your customer's card data directly to us. Below we explain how it works and how to obtain a hash from the data entered by the customer.
Front end only
This flow must run on the front end, whether web or mobile. Your customer's card data must never be sent to your back end; the card hash, however, can be sent to the back end safely.
Generating an encryption key
The encryption key is an RSA public key used to encrypt the card data. This key can be used only once: whenever you need to encrypt a new card, generate a new encryption key.
To generate an encryption key, make a POST request to the API's /encryption_keys route, sending your merchant_id as a parameter. It does not use the API token (never put it on the front end): the merchant_id identifies your account, and a merchant_id that does not exist responds with 401.
The request must have this format:
curl -X POST https://payments.cefis.com.br/api/v1/encryption_keys \
-H 'Content-Type: application/json' \
-d '{"merchant_id":"SEU_MERCHANT_ID"}'In response you will receive a JSON object in the following format:
{
"id": 1111,
"public_key": "-----BEGIN PUBLIC KEY-----\n ... \n-----END PUBLIC KEY-----\n",
"ip_address": "000.000.000.000",
"created_at": "2017-07-03"
}Encrypting the card data
To encrypt the card data, first format it correctly: create a JSON string containing all the card data in the following format:
{"card_number":"1111111111111111","card_holder_name":"Jose Silva","card_expiration_date":"11/2021","card_cvv":"111"}Then RSA-encrypt this string using the public key (public_key) generated in the previous step, with PKCS1 padding.
After encrypting the data, convert the result to base64. You will have something like this:
GprWNDlg+MKGgtr6y9wJgz7oOUxGuhhdX3EwydmoOvb+yNDWrjDKktDtWicReOrvSo0u5rsNvCqBFjyoMfaRXn4Qq3KdV8vuuFXubb4FT3OcSSVpu6ddaOjm2tf82+GKjdCy54Lgw4T0m9mWdLPveh/zkYP0+4lhHCFhBKX5EqBdR4918wP5oUV1KpzV4V94ldSA+Cy2n1NUxyUiSnv87H2m+HkdqKDIcX9X8KEtUmorC/EjTPzoeEdpRdj8t3ThcL73mirJvDcYthSdT/ADyaFQ2hMRMkKEVOQjUYMn76vzWyQkqsZ+HVQlxQMOBVHPh5yfrmi1HAVd1jLQuOK8wQ==The last step is to concatenate the id of your encryption key (received in the response to the previous request) with the base64 string that represents the card data, as follows:
encryption_key_id + "_" + base64_encrypted_cardThe result is your card hash:
1111_GprWNDlg+MKGgtr6y9wJgz7oOUxGuhhdX3EwydmoOvb+yNDWrjDKktDtWicReOrvSo0u5rsNvCqBFjyoMfaRXn4Qq3KdV8vuuFXubb4FT3OcSSVpu6ddaOjm2tf82+GKjdCy54Lgw4T0m9mWdLPveh/zkYP0+4lhHCFhBKX5EqBdR4918wP5oUV1KpzV4V94ldSA+Cy2n1NUxyUiSnv87H2m+HkdqKDIcX9X8KEtUmorC/EjTPzoeEdpRdj8t3ThcL73mirJvDcYthSdT/ADyaFQ2hMRMkKEVOQjUYMn76vzWyQkqsZ+HVQlxQMOBVHPh5yfrmi1HAVd1jLQuOK8wQ==Done: your customer's card data is now in a safe format to travel over the network and can be sent to your back end to continue the flow (in the card_hash field of POST /payments).
Card hash validity
Each card hash can be used only once and is valid for at most 30 minutes after the encryption key is generated. If you need to use the card more than once, make a request to the cards route and save the card linked to the user who owns it: you will receive a unique ID that can be used in new transactions with the same card.
Support
Questions about the integration: ti@ninepay.com.br. Terms of use: bit.ly/cefispay-contrato.