Offramp intent

Redeem BRS tokens for a BRL payout via PIX. Your backend creates the intent, the token holder burns on Solana, Ethereum, Base or Polygon, 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 burn signed by the token holder's wallet after the intent is created — see Burn signing for the hand-off on each chain.

Flow

Loading diagram…

Create the intent

POST /v2/intents/offramp

chainId accepts "solana", "ethereum", or "base". Base is live in both environments (sandbox = Base Sepolia, production = Base mainnet). It also accepts "polygon", live in both environments (sandbox = Polygon Amoy, production = Polygon mainnet).

FieldTypeRequiredNotes
amountCentsintegerYesBRL payout amount in cents. Must be at least 500 (R$5.00 = 5 BRS minimum).
destinationAddressstringYesThe PIX key that receives the BRL. Not a wallet — this is the fiat destination, named differently from onramp's destinationWallet. Must be on your organization's Pix-key list for the environment (dashboard → Pix Keys); accepted types are your own CNPJ, an email, a +55 phone or an EVP key. CPF keys and other companies' CNPJs are rejected.
chainIdstringYes"solana", "ethereum", "base", or "polygon": the chain the BRS is burned on. The token holder must be a whitelisted wallet on it.
clientReferencestringNoYour correlation ID, up to 255 chars. Unique per environment, forever, across onramp and offramp; when omitted, the idempotency-key is stored and echoed as the reference.

Headers:

HeaderValue
X-API-KeyYour API key.
idempotency-keyRecommended. UUID format-validated. See Idempotency.

Example:

PAYOUT_KEY=$(uuidgen)   # one per user payout, reused on retries
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: $PAYOUT_KEY" \
  -d '{
    "amountCents": 10000,
    "destinationAddress": "user@example.com",
    "chainId": "solana",
    "clientReference": "payout_xyz_42"
  }'

Response:

{
  "id": "8f2a...",
  "instanceId": "4e0b...",
  "intentType": "offramp",
  "flowVersion": 2,
  "status": "awaiting_burn_approval",
  "statusReason": null,
  "failureCode": null,
  "providerFailureCode": null,
  "originalFailureCode": null,
  "originalProviderFailureCode": null,
  "clientReference": "payout_xyz_42",
  "metadata": { "chainId": "solana", "amountCents": 10000, "…": "…" },
  "expiresAt": "2026-04-22T12:30:00Z",
  "createdAt": "2026-04-22T12:00:00Z",
  "updatedAt": "2026-04-22T12:00:00Z",
  "pixPayment": null,
  "depositInstructions": null,
  "party": { "displayName": "Your Company", "documentMasked": "64***65", "solanaWallet": null },
  "chainId": "solana"
}

pixPayment and depositInstructions are always null on offramps.

The burn step

The intent response carries nothing to sign. You call prepare-burn with the token holder's address; Nora issues an attested burn authorization for this intent, amount and holder. On Solana that becomes an unsigned user_burn transaction the user signs; on Ethereum it is a burn(...) call the holder sends. See Burn signing for both.

Track completion

After the burn proof is submitted (via POST /v2/intents/:id/approve-burn — see Burn signing), poll the intent until it reaches a terminal status.

StatusTerminal?Notes
creatednoAccepted server-side.
requires_compliancenoHeld for compliance review.
awaiting_burn_approvalnoThe holder must burn — see Burn signing.
burn_approvednoBurn proof accepted; Nora is verifying it on-chain.
burningnoToken burn in flight.
schedulednoPayout processing will not start before scheduledFor, when present. This is not a completion guarantee.
paying_outnoPIX payout initiated.
completedyesPayout settled.
failednoNot terminal, but no automatic recovery: a refund is started by Nora's operators after review, never by an API call, and it may not start. Check failureCode; it can be null (for example when the burn authorization was denied), and then statusReason carries the cause. Contact Nora with the intent id.
refundingnoNora is processing a refund.
refundedyesRefund completed.
expired / canceledyesTerminal error states.

(Full status enum includes onramp-only values like awaiting_fiat_payment and minting; 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. See Burn signing for the recovery pattern on each chain. No new intent is created.

Gotchas

  • Minimum R$5.00 (5 BRS). amountCents below 500 is rejected at creation with a 400 — the floor mirrors the on-chain MintConfig.min_amount.
  • The intent expires at expiresAt. The burn must be prepared, sent and approved inside that window. Afterwards create a new intent, unless a burn was already broadcast: then the BRS is gone and a new intent would burn again; contact Nora with the intent id and the proof.
  • Production needs an approved KYB. In production, creation fails with 400 Production offramp requires an approved KYB case matching the organization CNPJ until your organization's KYB is approved on the dashboard.
  • destinationAddress, not destinationWallet. Onramp and offramp diverge on this field name because the offramp destination is fiat, not crypto. Using destinationWallet on offramp is a validation error.
  • The key must be on your Pix-key list. Otherwise creation fails with 400 The Pix key attempting to receive the offramp is not whitelisted by the organization.; a CNPJ key that is not your own fails with 400 destinationAddress must equal the organization CNPJ for CNPJ-typed Pix keys.
  • Retries with a clientReference are not replayed. The same idempotency-key and body replay the intent only when no clientReference was sent. With one, every retry answers 409 Intent with clientReference 'payout_xyz_42' already exists: the intent exists (references are unique per environment, forever), so page through GET /v2/intents?intentType=offramp and match clientReference (there is no filter for it). A new payout needs a new clientReference.
  • BRS balance is a prerequisite. The wallet must hold enough BRS to cover the intent. If it doesn't, the signing step will refuse on Solana, and the burn call reverts on Ethereum.
  • On Ethereum the holder must be a whitelisted wallet of your organization, registered through the Wallets API. The burn call reverts otherwise.
  • Compliance holds don't render differently. requires_compliance and normal in-flight states share most of the API surface. Key off status, not derived UI state.

See also

On this page