kamiPay LogokamiPay Docs

Paying Pix

Send payments to Brazilian bank accounts via Pix

This endpoint allows you to make payments from USDT to any given Pix key, which will reflect the balance in seconds into any bank account in Brazil.

Payments are processed through our prepaid system with direct bank settlement for optimal speed and reliability.

Request Body

NameTypeDescription
pix_keystringRequired. The Pix key of the recipient. Can be a CPF/CNPJ, email, phone number, or random key.
currencystringRequired. Either 'BRL' or 'USDT'. Determines how the amount is interpreted.
amountfloatRequired. Amount to be sent. Minimum value is 0.5 (validated as USDT equivalent regardless of the currency field).
from_addressstringRequired. The sender wallet address. Must be a whitelisted address registered in your merchant wallets.
payer_namestringRequired. Full name of the End User.
payer_id_typestringRequired. Type of the End User's identity document, one of the Document Types.
payer_documentstringRequired. Number of the End User's identity document.
payer_countrystringRequired, except with a CPF or CNPJ. Country that issued the End User's identity document, as an ISO 3166-1 alpha-2 or alpha-3 code ("AR" or "ARG").
external_idstringRecommended. Your internal reference ID for this transaction. Must be unique for each payment attempt. Used to prevent duplicates and track transactions. If omitted, idempotency and deduplication via external_id will not be available.
transfer_descriptionstringOptional. Free-text note appended to the recipient's Pix receipt, after the End User fields (see Receipt Description). The final receipt is truncated to 140 characters if needed.

Currency Handling: If the payout currency is set to BRL, kamiPay sends exactly the BRL amount requested. If the payout currency is set to USDT, kamiPay calculates the BRL amount at payout creation time using the current exchange rate and sends that resulting BRL amount.

The minimum payment amount is 0.5 (validated as USDT equivalent). Sending an amount lower than 0.5 will result in a 400 error.

End User

Every payment identifies its End User: the person or company on whose behalf the payment is sent (your customer, or your own company). The End User is not the recipient of the Pix, who is identified by the pix_key.

Send the End User in the four payer_* fields, following these rules:

  • All four fields are required, with one exception: payer_country can be omitted with a CPF or CNPJ (see below). A blank or whitespace-only value counts as missing, and letter case is ignored in all four.
  • payer_country is the country that issued the document, not the End User's nationality or country of residence: a Venezuelan citizen identified with an Argentine DNI is sent as ARG and DNI.
  • With a CPF or CNPJ, payer_country is optional, and the document is taken as issued by Brazil. If you send a country with one, it must be BR or BRA.
  • payer_id_type is a document type that the country issues, as listed in Document Types.
  • payer_document is a valid document of that type. Spaces, dots, dashes and slashes are ignored, so 20-12345678-6 and 20123456786 are the same CUIT. Where the table below lists a check digit, it is verified.
  • payer_name has at least 2 characters and at least one letter, not counting leading or trailing spaces.

The End User fields are also shown on the recipient's Pix receipt (see Receipt Description).

Document Types

Issuing countrypayer_id_typeDocument format
Argentina (AR / ARG)DNI7 or 8 digits.
Argentina (AR / ARG)CUIT, CUIL11 digits, the last one a check digit.
Chile (CL / CHL)RUT7 or 8 digits followed by a check digit (0 to 9, or K).
Uruguay (UY / URY)CI7 or 8 digits, the last one a check digit.
Uruguay (UY / URY)RUT12 digits.
Bolivia (BO / BOL)CI4 to 10 digits, optionally followed by a 2-character complement, the department of issue (such as LP), or both.
Bolivia (BO / BOL)NIT7 to 13 digits, not starting with 0.
Mexico (MX / MEX)CURP18 characters in the official format, with the birth date as YYMMDD. The last one is a check digit.
Mexico (MX / MEX)INEThe 18-character elector key printed on the credential (6 letters, 8 digits, H or M, 3 digits), or the 9 to 13 digits of its CIC or OCR.
Mexico (MX / MEX)RFC12 characters for a company, 13 for an individual, in the official format, with the date of incorporation or birth as YYMMDD.
Brazil (BR / BRA)CPF11 digits, the last two check digits.
Brazil (BR / BRA)CNPJ14 characters, the last two check digits. The first 12 can include letters.
Venezuela (VE / VEN)CI5 to 9 digits, optionally preceded by V or E.
Colombia (CO / COL)CI5 to 10 digits.
Paraguay (PY / PRY)CI5 to 10 digits.
Ecuador (EC / ECU)CI5 to 10 digits.
Peru (PE / PER)DNI8 digits, without the check character printed next to them on the card.
Peru (PE / PER)RUC11 digits, the last one a check digit.
Peru (PE / PER)CE, CPP9 digits.
Any countryPASSPORT6 to 20 letters (A to Z) or digits.
Any countryOTHERAny document not listed above, with at least one character. Send only OTHER as the type and the document number as it appears on the document.

An End User whose document was issued by a country not listed above can only be identified with a PASSPORT, or with OTHER when it has no passport.

Receipt Description

Every Pix payment carries a short description that the recipient sees on their Pix receipt. kamiPay composes this message from the End User fields and your transfer_description, concatenated in a fixed order:

Pagamento via {your company name} {payer_name} {payer_id_type} {payer_document} {payer_country} {transfer_description}

The message always begins with Pagamento via {your company name} (your registered company name), followed by the End User fields in the order above. transfer_description is optional: if you don't send it, or send it blank, it is simply skipped, with no extra spaces. So is a payer_country omitted for a CPF or CNPJ.

Example

Sending the End User plus a note:

{
  "payer_name": "Juan Perez",
  "payer_id_type": "DNI",
  "payer_document": "12345678",
  "payer_country": "ARG",
  "transfer_description": "Order #4821"
}

produces:

Pagamento via {your company name} Juan Perez DNI 12345678 ARG Order #4821

Length handling

The composed message is hard-truncated to 140 characters (the Pix protocol limit for this field). There is no error for long input — the receipt is silently cut to fit.

Because the fields are concatenated in a fixed order, the leading fields are the most protected from truncation: your company name is never lost, while a long transfer_description (the last field) is the first to be cut.

The message is in Portuguese because the Pix recipient is always in Brazil. Only transfer_description is optional: the End User is always part of the message, so the receipt always says who the payment is sent for.

Example Request

const url = `${baseURL}/v1/payments/payToPixKey`;

const body = {
  "currency": "BRL",
  "amount": 4.10,
  "pix_key": "user@example.com", // CPF/CNPJ/email/Phone/pixkey
  "from_address": "0x1c0aCF853xxxxx9488fEdce1ab4",
  "external_id": "payment-unique-id-001", // Must be unique for each attempt
  // End User: the person or company on whose behalf the payment is sent
  "payer_name": "Juan Perez",
  "payer_id_type": "DNI",
  "payer_document": "12345678",
  "payer_country": "ARG", // country that issued the document
  // Optional note for the recipient's receipt (see "Receipt Description");
  // the final receipt is truncated to 140 chars
  // "transfer_description": "Order #4821"
};

const response = await fetch(url, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${access_token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify(body)
});
import requests
import json

url = f"{base_url}/v1/payments/payToPixKey"

body = {
  "currency": "BRL",
  "amount": 4.10,
  "pix_key": "user@example.com", # CPF/CNPJ/email/Phone/pixkey
  "from_address": "0x1c0aCF853xxxxx9488fEdce1ab4",
  "external_id": "payment-unique-id-001", # Must be unique for each attempt
  # End User: the person or company on whose behalf the payment is sent
  "payer_name": "Juan Perez",
  "payer_id_type": "DNI",
  "payer_document": "12345678",
  "payer_country": "ARG", # country that issued the document
  # Optional note for the recipient's receipt (see "Receipt Description");
  # the final receipt is truncated to 140 chars
  # "transfer_description": "Order #4821"
}

headers = {
    'Authorization': f'Bearer {access_token}',
    'Content-Type': 'application/json',
}

response = requests.post(url, json=body, headers=headers)
package main

import (
  "bytes"
  "encoding/json"
  "fmt"
  "net/http"
)

func main() {
  url := fmt.Sprintf("%s/v1/payments/payToPixKey", baseURL)

  // Create request body
  requestBody, _ := json.Marshal(map[string]interface{}{
    "currency": "BRL",
    "amount": 4.10,
    "pix_key": "user@example.com", // CPF/CNPJ/email/Phone/pixkey
    "from_address": "0x1c0aCF853xxxxx9488fEdce1ab4",
    "external_id": "payment-unique-id-001", // Must be unique for each attempt
    // End User: the person or company on whose behalf the payment is sent
    "payer_name": "Juan Perez",
    "payer_id_type": "DNI",
    "payer_document": "12345678",
    "payer_country": "ARG", // country that issued the document
    // Optional note for the recipient's receipt (see "Receipt Description");
    // the final receipt is truncated to 140 chars
    // "transfer_description": "Order #4821"
  })

  // Create request
  req, _ := http.NewRequest("POST", url, bytes.NewBuffer(requestBody))
  req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", access_token))
  req.Header.Add("Content-Type", "application/json")

  // Make the request
  client := &http.Client{}
  resp, err := client.Do(req)
  if err != nil {
    fmt.Println("Error making request:", err)
    return
  }
  defer resp.Body.Close()
}

Response

Payment Successfully Processed

{
  "status": "ok",
  "kamipay_id": "txc_01jrazd7a9fqpp0bym8km1dbd6",
  "data": {
    "status": "broadcasted",
    "usdt_amount": 0.875934
  },
  "external_id": "testing123"
}

Always use kamipay_id and external_id for transaction tracking. The external_id field is only present in the response if it was provided in the request.

Payment Created (Processing)

Returned when the external_id matches an existing transaction that is still being processed or has already been completed.

{
  "status": "created",
  "kamipay_id": "txc_01jkg5vktaer5abc123kjgrsn2",
  "data": null,
  "external_id": "idempotency-1"
}

Bad Request

The response format for 400 errors varies depending on the validation that failed.

Invalid PIX key format:

{
  "detail": {
    "msg": "Description of the format error",
    "reformated_key": null,
    "name": null
  }
}

Invalid amount (below minimum):

{
  "detail": "Wrong amount USDt:0.3. Minimum USDt:0.5"
}

Invalid currency:

{
  "detail": "Currency should be BRL, USDT"
}

Invalid or non-whitelisted from_address:

{
  "detail": "Incorrect Address: 0xInvalidAddress123"
}

Missing pix_key:

{
  "detail": "pix_key is required"
}

Unauthorized

Invalid or expired token:

{
  "detail": "Could not validate credentials"
}

Valid token but insufficient permissions:

{
  "detail": "Token Profile Not Authorized for this Endpoint"
}

Conflict (Previous Transaction Cancelled)

Returned when the external_id matches an existing transaction that was previously cancelled. To retry, you must use a new external_id, since re-sending the same one will return this same 409 response.

{
  "status": "canceled",
  "kamipay_id": "txc_01jkg5vktaer5abc123kjgrsn2",
  "data": null,
  "external_id": "idempotency-1"
}

Validation Error

Returned when the request body is invalid, for example when the End User is missing or invalid. detail has one entry per invalid field; for a missing field, input is the whole request body.

For example, a request with a CUIT issued by Chile and without payer_name returns:

{
  "detail": [
    {
      "type": "value_error",
      "loc": ["body", "payer_id_type"],
      "msg": "payer_id_type CUIT is not valid for country CHL",
      "input": "CUIT"
    },
    {
      "type": "missing",
      "loc": ["body", "payer_name"],
      "msg": "Field required",
      "input": {
        "currency": "BRL",
        "amount": 4.1,
        "pix_key": "user@example.com",
        "from_address": "0x1c0aCF853xxxxx9488fEdce1ab4",
        "external_id": "payment-unique-id-001",
        "payer_id_type": "CUIT",
        "payer_document": "20-12345678-6",
        "payer_country": "CHL"
      }
    }
  ]
}

The End User errors you can get, each with one example of its msg (the field, document type or country named in it varies):

typeExample msg
missingField required
value_errorpayer_country must be an ISO 3166-1 alpha-2 or alpha-3 code
value_errorpayer_country must be BRA for a CPF
value_errorpayer_id_type must be one of CE, CI, CNPJ, CPF, CPP, CUIL, CUIT, CURP, DNI, INE, NIT, OTHER, PASSPORT, RFC, RUC, RUT
value_errorpayer_id_type CUIT is not valid for country CHL
value_errorpayer_document is not a valid CUIT
value_errorpayer_name must have at least 2 characters and at least one letter

To handle these errors in code, rely on loc and type, not on msg. Some fields can only be checked once the fields they depend on are valid, so fixing one can reveal an error in another.

A request rejected with this error creates no payment, so you can fix it and send it again with the same external_id.

Payment Failed (Transaction Cancelled)

The payment was attempted but failed at the gateway level. The transaction is permanently cancelled.

{
  "status": "canceled",
  "kamipay_id": "txc_01jrazd7a9fqpp0bym8km1dbd6",
  "data": null,
  "external_id": "testing123"
}

Critical: This status indicates the transaction could NOT be processed and should be considered failed. If you want to retry the payment, you must use a new external_id.

Internal Server Error

{
  "detail": "An unexpected error occurred."
}

Service Unavailable

Returned when the payment gateway is under maintenance.

{
  "detail": "Service is temporarily unavailable due to maintenance"
}

Transaction Identifiers

IdentifierDescription
kamipay_idPrimary identifier provided by kamiPay to monitor a specific transaction until completion. Always use this for transaction tracking.
external_idYour transaction identifier. Must be unique for each payment attempt. Essential for preventing duplicates, tracking in webhooks, and status endpoints. Only present in responses if it was provided in the request.

Important: Always use kamipay_id and external_id as your primary tracking mechanisms.

Response Statuses Returned by This Endpoint

This endpoint can return the following status values in the response body:

StatusHTTP CodeDescription
ok200Payment was successfully processed. The data object contains the transaction details.
created201A transaction with this external_id already exists and is either in progress or completed. No new transaction is created.
canceled409A transaction with this external_id was previously cancelled. You must use a new external_id to retry.
canceled424The payment was attempted but failed at the gateway level. The transaction is permanently cancelled. You must use a new external_id to retry.

For detailed transaction status tracking (e.g., intermediate processing states, final settlement confirmation), use the status endpoint or webhooks. This endpoint only returns the initial synchronous result of the payment attempt.

Payment Processing

Payments are processed through our prepaid system, which provides:

  • Fast settlement: Direct bank transfers settled in batches
  • Consistent identifiers: Same tracking system (kamipay_id and external_id) regardless of underlying processing method

Error Handling & Retries

Status Code 424 - Critical Failure: When receiving a 424 status code, the transaction has failed permanently. To retry the payment, you must generate a new external_id. Using the same external_id will result in duplicate prevention mechanisms blocking the request.

Retry Guidelines

  1. Successful payments (200): No retry needed
  2. Processing payments (201): Do not retry. Monitor status via webhooks or status endpoint
  3. Failed payments (424): Generate a new external_id before retrying
  4. Conflicts (409): The previous transaction with this external_id was cancelled. You must use a new external_id to retry, as re-sending the same one will return 409 again
  5. Validation errors (422): Fix the fields listed in detail before retrying. No payment was created, so you can retry with the same external_id
  6. Server errors (500/503): Safe to retry with the same external_id after a short delay

Handling Parallel Requests

Status: Created (201)

When making requests while a previous transaction with the same external_id is still processing:

  • Returns 201 status code
  • The existing transaction is still being processed; no new transaction is created
  • The data field will be null
  • Monitor via status endpoint or webhooks

Status: Cancelled (409/424)

  • 409: A previous transaction with this external_id was cancelled. Use a new external_id to retry
  • 424: The current transaction failed to process at the gateway level. Use a new external_id to retry

On this page