joia API · v1

Get paid by Brazilians. Automatically.

One API to collect Pix, Pix Automático and credit cards in up to 21 installments, settled straight to your BRL account. Card installments are paid to you upfront, so you never wait months for your money.

PixSettles in seconds
Pix AutomáticoRecurring, authorized once
Cards, 1–21xPaid to you upfront
PayoutsPer payment or daily
Base URLhttps://api.payjoia.com/v1Keys
joia_test_… sandboxjoia_live_… live
Amounts150000 = R$1,500.00

How money moves

  1. Create a charge with your order ID and amount. You get a hosted payer page and, for Pix, a copy-and-paste code.
  2. The payer pays by Pix, or by card in as many installments as you allow.
  3. joia settles it. Pix is credited instantly. Card installments are paid to you upfront, usually within 2 business days.
  4. joia pays you out by Pix to your registered BRL account, after every payment or once a day.
  5. You're notified at every step by signed webhooks, and you can pull a daily report that reconciles to the centavo.
Your money only goes to accounts you registered. A payout can go to a registered Pix key, or to a brCode whose receiver matches an account holder you registered at onboarding. Even a leaked API key can't send funds anywhere else.

Authentication

Send your secret key as a bearer token on every request. Keys are separate for each environment, and a sandbox key never touches live money.

Keep keys on your server and never ship them to a browser or app. You can roll a key from the dashboard at any time.

Platforms that operate several merchants use their own key and add a Joia-Account header to act on a connected account. See Platforms.

Authenticate
curl https://api.payjoia.com/v1/balance \
  -H "Authorization: Bearer joia_test_your_key"
Act on a connected account
curl https://api.payjoia.com/v1/balance \
  -H "Authorization: Bearer joia_test_your_key" \
  -H "Joia-Account: mer_inflowpay"

Sandbox

Sandbox behaves exactly like live, with the same API, webhooks and reports, and moves no real money. Use the simulators to play the payer:

  • POST /v1/sandbox/charges/{id}/pay with {"method":"pix"} or {"method":"card","installments":12}
  • POST /v1/sandbox/pix-automatic/authorizations/{id}/next-cycle debits the next recurring payment
  • On the hosted page, use the test card 5162 3062 1937 8829 · 05/30 · CVV 318
Simulate the payer
# Pix paid (also approves a Pix Automático first payment)
curl -X POST https://api.payjoia.com/v1/sandbox/charges/chg_…/pay \
  -H "Authorization: Bearer joia_test_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "method": "pix" }'

# Card paid in 12 installments
curl -X POST https://api.payjoia.com/v1/sandbox/charges/chg_…/pay \
  -H "Authorization: Bearer joia_test_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "method": "card", "installments": 12 }'

# Next Pix Automático cycle debited
curl -X POST https://api.payjoia.com/v1/sandbox/pix-automatic/authorizations/SUB-4471/next-cycle \
  -H "Authorization: Bearer joia_test_your_key"

Create a charge

POST/v1/charges

Creates a one-time charge. Sending the same order_id again returns the same charge, so retrying is always safe.

Parameters

order_idstringrequired
Your unique ID for the order. Letters, digits and - _ . : / #.
amountintegerrequired
Amount in BRL cents. Minimum 500 (R$5.00).
methodsarray
Any of pix and card. Default: both.
max_installmentsinteger
1–21. The most installments the payer may pick for card. Default 12.
payerobject
name, document (CPF or CNPJ), email, phone. Required when Pix is enabled.
descriptionstring
Shown to the payer. Up to 200 characters.
expires_in_minutesinteger
15 minutes to 7 days. Default 1440.
metadataobject
Your own key/value strings. Returned on every read and in every webhook.
fees_paid_bystring
merchant (default) or payer. With payer, the installment cost is added to the payer's total and you receive the full amount. See Pricing.

Sending the same order_id again returns the original charge with 200. A new charge returns 201. Reusing an order_id with a different amount or methods returns 409.

POST /v1/charges
curl -X POST https://api.payjoia.com/v1/charges \
  -H "Authorization: Bearer joia_test_your_key" \
  -H "Content-Type: application/json" \
  -d '{
  "order_id": "INV-10293",
  "amount": 150000,
  "methods": ["pix", "card"],
  "max_installments": 12,
  "description": "Order INV-10293",
  "payer": {
    "name": "Maria Souza",
    "document": "24971563792",
    "email": "maria@example.com"
  },
  "metadata": { "merchant_ref": "ACME-77" }
}'
Response
{
  "id": "chg_8f2kq0x7m1v3c9b4n6z2",
  "object": "charge",
  "type": "one_time",
  "order_id": "INV-10293",
  "status": "pending",
  "amount": 150000,
  "currency": "BRL",
  "methods": ["pix", "card"],
  "max_installments": 12,
  "pay_url": "https://pay.payjoia.com/pay/chg_8f2kq0x7m1v3c9b4n6z2",
  "pix": {
    "copy_paste": "00020101021226820014br.gov.bcb.pix...",
    "qr_code_png_base64": "iVBORw0KGgoAAAANSUhEUg...",
    "expires_at": "2026-11-05 23:59:59"
  },
  "card_checkout_url": "https://...",
  "payer": { "name": "Maria Souza", "document": "24971563792" },
  "metadata": { "merchant_ref": "ACME-77" },
  "created_at": "2026-11-04T18:20:11.204Z"
}

Retrieve & list charges

GET/v1/charges/{id | order_id}
GET/v1/charges?status=paid&limit=50

A charge moves from pending to paid. It can also end as expired, failed, refunded, partially_refunded or chargeback.

Once paid, settlement shows what reached your balance. provider_net is the amount after processing and installment costs, and net_to_balance is what's available to pay out.

GET /v1/charges/{id}
curl https://api.payjoia.com/v1/charges/INV-10293 \
  -H "Authorization: Bearer joia_test_your_key"
Response (paid by card, 12x)
{
  "id": "chg_8f2kq0x7m1v3c9b4n6z2",
  "status": "paid",
  "amount": 150000,
  "installments": 12,
  "settlement": {
    "provider_net": 128298,
    "intermediation_fee": 0,
    "reserve_held": 0,
    "refunds_and_chargebacks": 0,
    "net_to_balance": 128298
  },
  "paid_at": "2026-11-04T18:22:40.118Z"
}

Pricing & installments

Your contract sets a price for each method: Pix, cards paid upfront (all installments settled to you within 2 business days), or cards settled month by month. For every payment you receive exactly gross × (1 − your rate), so the amount you get never depends on processing costs.

  • fees_paid_by: merchant: the payer sees the same total in any number of installments (“12x sem juros”), and you pay the installment rate.
  • fees_paid_by: payer: each installment option shows its own total, as Brazilian law requires, and you receive the full amount. amount_paid on the charge shows what the payer actually paid.

Each installment must be at least R$ 5.00, so max_installments is capped automatically for small amounts. Up to 21x on Visa and Mastercard, and up to 12x on other brands.

Refunds & cancellation

POST/v1/charges/{id}/refund

Refunds the payer in full, or partially if you send amount in cents. The response is 202. Your balance is debited when the refund settles, and charge.refunded follows.

DELETE/v1/charges/{id}

Cancels a pending charge, so its Pix code and any open card attempt stop working, and sends charge.cancelled. Charges also expire on their own at expires_at, which sends charge.expired.

Payer page

Every charge has a hosted page at pay_url. It's in Portuguese, built for phones, and loads in under a second. The payer picks Pix or card, and the page confirms the payment the moment it lands.

Once a charge is paid one way, the other way closes automatically, so it can never be paid twice. You can also skip the hosted page and show pix.copy_paste and the QR image in your own checkout.

Pix Automático

POST/v1/pix-automatic/authorizations

Brazil's recurring Pix. The payer authorizes once in their bank app, and every later payment is debited automatically, with no card on file and no failed-card churn.

The first payment and the authorization happen together: send the payer first_payment.pay_url or show its Pix code. The authorization becomes active when they approve it in their bank.

Parameters

referencestringrequired
Your unique ID for the subscription or contract.
payerobjectrequired
name, document (CPF/CNPJ), email.
frequencystringrequired
weekly, monthly, quarterly, semiannually or annually.
first_payment_amountintegerrequired
Charged now, together with the authorization. In cents.
start_datedaterequired
First recurring debit, after today.
amountinteger
Fixed amount per cycle, charged automatically. Leave it out for variable amounts, and create each cycle yourself.
end_datedate
Optional end of the authorization.
retry_on_failureboolean
Retry a failed debit up to 3 times within 7 days.
descriptionstring
Shown in the payer's bank app. Up to 35 characters.
POST /v1/pix-automatic/authorizations
curl -X POST https://api.payjoia.com/v1/pix-automatic/authorizations \
  -H "Authorization: Bearer joia_test_your_key" \
  -H "Content-Type: application/json" \
  -d '{
  "reference": "SUB-4471",
  "payer": { "name": "Maria Souza", "document": "24971563792" },
  "frequency": "monthly",
  "amount": 4990,
  "first_payment_amount": 4990,
  "start_date": "2026-12-04",
  "description": "Premium plan"
}'
Response
{
  "id": "pxa_3m9v0c2k7q1x8b4n6z5w",
  "object": "pix_automatic_authorization",
  "reference": "SUB-4471",
  "status": "pending",
  "frequency": "monthly",
  "amount": 4990,
  "amount_type": "fixed",
  "first_payment": {
    "charge_id": "chg_1x8b4n6z5w3m9v0c2k7q",
    "amount": 4990,
    "status": "pending",
    "pay_url": "https://pay.payjoia.com/pay/chg_1x8b4n6z5w3m9v0c2k7q",
    "pix": { "copy_paste": "00020101021226810014br.gov.bcb.pix..." }
  },
  "start_date": "2026-12-04"
}

Cycles & cancellation

POST/v1/pix-automatic/authorizations/{id}/charges

For variable amounts, create each cycle 2 to 10 business days before due_date. The payer's bank schedules the debit, and you receive charge.scheduled, then charge.paid or charge.failed.

Fixed-amount authorizations create their cycles automatically. Each cycle is a regular charge: same settlement, payouts, reports and webhooks.

DELETE/v1/pix-automatic/authorizations/{id}

Cancels the authorization, so no future cycle is debited. Payers can also cancel from their bank app, and you'll receive pix_automatic.authorization.cancelled.

GET/v1/pix-automatic/authorizations/{id}
POST /v1/pix-automatic/authorizations/{id}/charges
curl -X POST https://api.payjoia.com/v1/pix-automatic/authorizations/SUB-4471/charges \
  -H "Authorization: Bearer joia_test_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "order_id": "SUB-4471-2026-12", "amount": 5230, "due_date": "2026-12-10" }'
Cancel
curl -X DELETE https://api.payjoia.com/v1/pix-automatic/authorizations/SUB-4471 \
  -H "Authorization: Bearer joia_test_your_key"

Payouts

Your account has a payout mode, set at onboarding:

  • per_payment (default): your available balance is sent by Pix right after each payment settles.
  • daily: everything available is sent once a day.
  • manual: nothing moves until you call the endpoint below.
POST/v1/payouts

Parameters

Idempotency-Keyheaderrequired
Your unique key for this payout. Retrying with it never pays twice.
amountinteger
In cents. Leave it out to pay out the whole available balance.
brcodestring
Pay a brCode, such as a fresh code from a virtual account. Its receiver must be an account holder you registered.
destination_idstring
Or pay a registered Pix key (GET /v1/destinations). Default: your default account.
referencestring
Free text shown on the Pix transfer.

A payout goes from processing to paid, or to failed, in which case the money returns to your balance. A paid payout carries the Pix end_to_end_id and a receipt_url.

GET/v1/payouts/{id}

Recipe: a fresh brCode per collection

If your BRL account issues a new brCode for every deposit, keep the account in manual payout mode and drive payouts yourself:

  1. Receive charge.paid. Its data.amount, order_id and metadata tell you which order settled.
  2. Generate a brCode for that amount at your account provider.
  3. Call POST /v1/payouts with brcode, amount, reference set to your order id, and Idempotency-Key set to your order id plus -payout.
  4. Receive payout.paid with the Pix end_to_end_id. That's your proof of delivery.

joia only pays brCodes whose receiver is an account holder registered for you at onboarding, so a leaked key can't redirect funds. Each payout must fit your available balance, which you can check with GET /v1/balance.

POST /v1/payouts
curl -X POST https://api.payjoia.com/v1/payouts \
  -H "Authorization: Bearer joia_test_your_key" \
  -H "Idempotency-Key: settle-2026-11-04" \
  -H "Content-Type: application/json" \
  -d '{
  "brcode": "00020101021226...6304A1B2",
  "amount": 120000,
  "reference": "Settlement 2026-11-04"
}'
Response
{
  "id": "po_7c2k9q1x8b4n6z5w3m0v",
  "object": "payout",
  "status": "processing",
  "method": "brcode",
  "amount": 120000,
  "currency": "BRL",
  "fee": 0,
  "destination": { "id": null, "pix_key": null, "holder_name": "INFLOWPAY BRL ACCOUNT" },
  "reference": "Settlement 2026-11-04",
  "end_to_end_id": null,
  "receipt_url": null,
  "created_at": "2026-11-04T19:02:11.482Z"
}

Balance

GET/v1/balance

available can be paid out now. reserved is held for a set period, if your account has a reserve.

GET /v1/balance
curl https://api.payjoia.com/v1/balance \
  -H "Authorization: Bearer joia_test_your_key"
Response
{
  "object": "balance",
  "currency": "BRL",
  "available": 1294310,
  "reserved": 0,
  "total": 1294310,
  "payout_mode": "per_payment"
}

Daily report

GET/v1/reports/daily?date=YYYY-MM-DD

Opening and closing balance, settled payments, fees, refunds, chargebacks and payouts for one day in São Paulo time, with a line for every charge and payout. The opening balance plus movements always equals the closing balance. Add &format=csv for a spreadsheet.

GET /v1/reports/daily
curl https://api.payjoia.com/v1/reports/daily?date=2026-11-04 \
  -H "Authorization: Bearer joia_test_your_key"
Response
{
  "object": "daily_report",
  "date": "2026-11-04",
  "timezone": "America/Sao_Paulo",
  "opening_balance": 0,
  "credits": { "settled_payments": 1284310, "reserve_released": 0 },
  "debits": { "intermediation_fee": 0, "refunds": 0, "chargebacks": 0, "payouts": 1284310, "transfer_fees": 0 },
  "closing_balance": 0,
  "charges": [ { "charge_id": "chg_…", "order_id": "INV-10293", "method": "card", "installments": 12, "gross": 150000, "provider_net": 128298 } ],
  "payouts": [ { "payout_id": "po_…", "amount": 128298, "status": "paid", "end_to_end_id": "E1954…" } ]
}

Limits

  • API: 600 requests per minute per account. Every response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset. A 429 includes Retry-After.
  • Charges: from R$ 5.00 to R$ 1,000,000.00. Installments of at least R$ 5.00 each.
  • Payouts: from R$ 1.00. Pix payouts follow the daily and overnight (8 pm–6 am) Pix limits of the settlement account. Payouts above the overnight limit are sent the next morning automatically.
  • Payer's bank statement: the payer's bank shows the receiving institution, Kodano, which collects on behalf of the merchant of record. The hosted payer page explains this to the payer.

Webhooks

joia sends an HTTPS POST to the URL you set in the dashboard, signed with your webhook secret in the Joia-Signature header. Verify it, and answer 2xx within 10 seconds. If you don't, we retry with backoff for about 24 hours.

Events are delivered at least once and ordered per object. Deduplicate on id.

PUT/v1/webhook_endpoint

Set or change your endpoint URL with { "url": "https://…" }. Rotate the signing secret with POST /v1/webhook_endpoint/rotate_secret. The new secret is returned once.

GET/v1/events?after={event_id}&type=charge.paid

Pull events oldest first, using the last id you processed as after. has_more tells you to keep paging. GET /v1/events/{id} returns a single event. Polling and webhooks carry the same payloads.

Event payload
{
  "id": "evt_q1x8b4n6z5w3m0v7c2k9",
  "type": "payout.paid",
  "created_at": "2026-11-04T19:02:19.003Z",
  "data": {
    "payout_id": "po_7c2k9q1x8b4n6z5w3m0v",
    "amount": 120000,
    "end_to_end_id": "E19540550202611041902abcdEFGH",
    "receipt_url": "https://..."
  }
}
Verify the signature
# Header sent with every webhook:
Joia-Signature: t=1793790139,v1=5f0c…e81a
# v1 = hex(HMAC_SHA256(webhook_secret, t + "." + raw_body))

Event types

Events

charge.paid
The payer paid. Includes payment_method, installments, amount_paid and your metadata.
charge.scheduled
A Pix Automático cycle was scheduled by the payer's bank.
charge.failed
A Pix Automático debit was refused or cancelled.
charge.expired
The charge expired unpaid.
charge.cancelled
You cancelled a pending charge.
charge.refunded
Full or partial refund to the payer.
charge.chargeback
A card payment was disputed. The amount is held from your balance.
pix_automatic.authorization.active
The payer approved the authorization.
pix_automatic.authorization.refused
The payer's bank didn't confirm it.
pix_automatic.authorization.cancelled
Cancelled by you or by the payer.
pix_automatic.authorization.expired
Never approved, or reached its end date.
payout.processing
A payout was sent to the Pix network.
payout.paid
The payout reached your account. Includes end_to_end_id and receipt_url.
payout.failed
The payout failed. The amount is back in your balance.

Reliability

  • Request-Id on every response. Quote it to support and we can trace the exact call.
  • Joia-Version on every response. Breaking changes ship only in a new version, and the API stays the same if we change banking partners.
  • Idempotency: charges by order_id, Pix Automático by reference, payouts by Idempotency-Key. Retry any request safely.
  • Retries: on 502, 503 or a timeout, retry with the same key after a short backoff. 503 provider_unavailable means retry in about 30 seconds.
  • OpenAPI 3.1 spec at /openapi.json, for generating a typed client. System status at /api/health.

Errors

Status codes

400invalid_request
Validation failed. details lists every field.
401unauthorized
Missing, invalid or wrong-environment key.
403account_not_allowed
Joia-Account isn't one of your connected accounts.
404not_found
No such object in this account.
409order_id_conflict · reference_conflict · idempotency_key_reused
Same key, different parameters.
409charge_not_pending · charge_expired
The charge can no longer be paid or changed.
422insufficient_funds · receiver_not_allowed · …
Understood, but not possible.
502provider_unavailable
Temporary. Retry with the same order_id or Idempotency-Key.
Error shape
{
  "error": {
    "code": "insufficient_funds",
    "message": "Amount exceeds the available balance.",
    "details": { "available": 1200 }
  }
}

Platforms

GET/v1/accounts

A platform such as an FX or stablecoin company operates many merchants under one key. Each merchant is a connected account with its own balance, payouts, reports and webhooks. Add Joia-Account: mer_… to any request to act on one of them. New connected accounts are onboarded by joia after KYB.

The API is the contract. joia can change banking partners behind it without changing a single endpoint, field or webhook.