Offramp intent
Redeem BRS tokens for a BRL payout via PIX. Your backend creates the intent, your user signs the staged burn transaction on Solana, and the payout settles.
An offramp intent (labelled Burn in the Nora dashboard) redeems
BRS for a BRL payout. Unlike onramp, offramp requires
an on-chain signature from the token-holder's wallet after the intent
is created. The signing hand-off has a gap in the current public /v2
contract — see Burn signing for details.
Flow
Create the intent
POST /v2/intents/offramp
| Field | Type | Required | Notes |
|---|---|---|---|
amountCents | integer | Yes | BRL payout amount in cents. Must be at least 500 (R$5.00 = 5 BRS minimum). |
destinationAddress | string | Yes | PIX key or payout reference. Not a wallet — this is the fiat destination, named differently from onramp's destinationWallet. 1–255 chars. |
chainId | string | Yes | "solana" or "polygon" accepted on request; responses emit "solana" only today. |
clientReference | string | No | Your correlation ID. Up to 255 chars. |
Headers:
| Header | Value |
|---|---|
X-API-Key | Your API key. |
idempotency-key | Recommended. UUID format-validated. See Idempotency. |
Example:
curl -X POST https://sandbox.api.nora.finance/v2/intents/offramp \
-H "X-API-Key: $NORA_API_KEY" \
-H "Content-Type: application/json" \
-H "idempotency-key: $(uuidgen)" \
-d '{
"amountCents": 10000,
"destinationAddress": "user@example.com",
"chainId": "solana",
"clientReference": "payout_xyz_42"
}'Response:
{
"id": "8f2a...",
"instanceId": "4e0b...",
"intentType": "offramp",
"flowVersion": 1,
"adapter": null,
"status": "awaiting_burn_approval",
"statusReason": null,
"clientReference": "payout_xyz_42",
"metadata": null,
"expiresAt": null,
"createdAt": "2026-04-22T12:00:00Z",
"updatedAt": "2026-04-22T12:00:00Z",
"pixInfo": null,
"depositInstructions": null,
"chainId": "solana"
}depositInstructions is always null in the current /v2 response.
The burn-approval gap
The offramp signing construction isn't embedded in the public /v2
intent response. In the grant model you don't assemble an approval from
raw values — you call prepare-burn, Nora issues an attested on-chain
burn grant and stages an unsigned user_burn transaction, and the user
signs that staged transaction. See
Burn signing for the full construction and field details.
Similarly, the adapter the intent lands on (adapter: "cuiaba" or
"squads") and adapter-specific mechanics aren't fully modeled in the
public /v2 response today. Treat adapter as an informational field
for now.
Track completion
After burn approval is submitted (via
POST /v2/intents/:id/approve-burn — see
Burn signing), poll the intent until it reaches a
terminal status.
| Status | Terminal? | Notes |
|---|---|---|
created | no | Accepted server-side. |
requires_compliance | no | Held for compliance review. |
awaiting_burn_approval | no | User must sign the staged user_burn transaction — see Burn signing. |
burn_approved | no | Signed user_burn submitted and accepted. |
burning | no | Token burn in flight. |
paying_out | no | PIX payout initiated. |
completed | yes | Payout settled. |
failed / expired / canceled / refunded | yes | Terminal error states. |
(Full status enum includes onramp-only values like
awaiting_fiat_payment, minting, and onchain_received; you won't
see those on offramp.)
Resuming an interrupted intent
If your app crashes between POST /v2/intents/offramp and the
approve-burn call, the intent is still in whatever state it was left
in. Reopen your signing UI using the persisted intent ID;
GET /v2/intents/:id returns the current intent shape, and
approve-burn dedups on txSignature — resubmitting the same signed
transaction is safe. See Burn signing for the
recovery pattern. No new intent is created.
Gotchas
- Minimum R$5.00 (5 BRS).
amountCentsbelow500is rejected at creation with a400— the floor mirrors the on-chainMintConfig.min_amount. destinationAddress, notdestinationWallet. Onramp and offramp diverge on this field name because the offramp destination is fiat, not crypto. UsingdestinationWalleton offramp is a validation error.
- BRS balance is a prerequisite. The wallet must hold enough BRS to cover the intent. If it doesn't, the signing step will refuse.
- Compliance holds don't render differently.
requires_complianceand normal in-flight states share most of the API surface. Key offstatus, not derived UI state.
See also
- Burn signing — the next step: sign the staged
user_burnand submit the signature - Onramp intent — the reverse direction
- Idempotency — why the key lives on your user-intent, not each retry
- API Reference: Intents — full endpoint spec
Onramp intent
Move BRL into BRS tokens minted to a Solana wallet. Your backend creates the intent, Nora returns PIX deposit info, your user pays by PIX, and the tokens land.
Burn signing
Ask Nora to stage an unsigned user_burn transaction, have the user's wallet sign it, broadcast it on Solana, and submit the signature back via /v2/intents/:id/approve-burn.