Intents
Create onramp / offramp intents, fetch and list them, submit the user_burn transaction signature for offramps.
Intents are the state-tracked transactions between your end-users' fiat
and on-chain balances. Two flavours: onramp (fiat → crypto) and
offramp (crypto → fiat). Offramps additionally require a
client-side signature on the staged user_burn transaction, submitted
back to Nora via approve-burn.
Error envelope
All intents endpoints return errors in the intents-family shape:
{
"code": "machine_readable",
"message": "human-readable",
"validationErrors": [
{ "field": "amountCents", "message": "amountCents must be at least 500 (R$5.00 = 5 BRS minimum)" }
],
"details": { }
}code and message are always present on non-2xx responses.
validationErrors is populated on structural 400s. details is
optional and endpoint-specific. See
Error handling.
The intent response shape
Create (200/201), fetch (200), and the items inside list
(200) all return the same intent object:
| Field | Type | Notes |
|---|---|---|
id | string (uuid) | |
instanceId | string (uuid) | |
intentType | "onramp" | "offramp" | |
flowVersion | integer | Pin on at least the version your code was written against. |
adapter | "squads" | "cuiaba" | null | |
status | see status enum | |
statusReason | string | null | |
clientReference | string | null | Echoed from your body. |
metadata | object | null | |
expiresAt | string | null | ISO 8601. |
createdAt | string | ISO 8601. |
updatedAt | string | |
pixInfo | object | null | PIX deposit info on onramps; null on offramps. |
depositInstructions | null | Always null in the current /v2 response. |
chainId | "solana" | Today the response enum is Solana-only even though requests accept "polygon". |
pixInfo when present:
| Field | Type | Notes |
|---|---|---|
brcode | string | PIX BR Code — present on all payment rails. |
txid | string | Transaction ID. |
amountCents | integer | |
expiresAt | string | null | ISO 8601; null for non-expiring codes. |
Status enum
created, requires_compliance, awaiting_fiat_payment,
fiat_received, minting, awaiting_onchain_transfer,
awaiting_burn_approval, burn_approved, onchain_received,
burning, paying_out, completed, expired, canceled, failed,
refunded.
POST /v2/intents/onramp
Create an onramp intent. The response includes pixInfo to surface to
your user.
Headers
| Header | Required | Notes |
|---|---|---|
X-API-Key | Yes | |
Content-Type | Yes | application/json |
idempotency-key | Recommended | UUID (format-validated). One key per user intent; reuse across retries. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
amountCents | integer | Yes | BRL amount in cents. Must be ≥ 500 (R$5.00 = 5 BRS minimum). |
destinationWallet | string | Yes | Solana address (1–255 chars). |
chainId | string | Yes | "solana" or "polygon" accepted on request. |
clientReference | string | No | Up to 255 chars. Echoed on the intent. |
Response 200 / 201
The intent response shape above.
Example
curl -X POST https://sandbox.api.nora.finance/v2/intents/onramp \
-H "X-API-Key: $NORA_API_KEY" \
-H "Content-Type: application/json" \
-H "idempotency-key: $(uuidgen)" \
-d '{
"amountCents": 10000,
"destinationWallet": "So11111111111111111111111111111111111111112",
"chainId": "solana",
"clientReference": "ord_123"
}'Errors
400— body validation (negative amount, missing field, address format).409— idempotency-key collision with a different body.
POST /v2/intents/offramp
Create an offramp intent. Once created, the user must sign an SPL
Approve and submit the txSignature via approve-burn. See
Burn signing.
Headers
| Header | Required | Notes |
|---|---|---|
X-API-Key | Yes | |
Content-Type | Yes | application/json |
idempotency-key | Recommended | UUID (format-validated). |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
amountCents | integer | Yes | BRL payout amount in cents. Must be ≥ 500 (R$5.00 = 5 BRS minimum). |
destinationAddress | string | Yes | Fiat rail destination (PIX key, 1–255 chars). |
chainId | string | Yes | "solana" or "polygon". |
clientReference | string | No | Up to 255 chars. |
Response 200 / 201
The intent response shape above. pixInfo is null on offramps and
depositInstructions is always null.
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"
}'Errors
400— body validation.409— idempotency-key collision.
GET /v2/intents/:id
Fetch a single intent by id.
Path parameters
| Param | Type | Required |
|---|---|---|
id | string (uuid) | Yes |
Headers
| Header | Required |
|---|---|
X-API-Key | Yes |
Response 200
The intent response shape above.
Errors
404— no intent with that id in this org/instance.
GET /v2/intents
List intents — filterable and paginated with limit / offset.
Headers
| Header | Required |
|---|---|
X-API-Key | Yes |
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
intentType | string | "all" | "onramp", "offramp", or "all". |
status | string | "all" | Any intent status (see enum above) or "all". |
limit | number | 50 | 1–100. |
offset | number | 0 | Zero-based. |
Response 200
{
"intents": [
{ "id": "…", "status": "…", "…": "…" }
],
"total": 42,
"limit": 50,
"offset": 0
}Pagination is offset-based (not cursor-based). The key is intents
(plural), not items. total is the full match count for the
filter.
POST /v2/intents/:id/approve-burn
Submit the user_burn transaction signature for an offramp intent.
Path parameters
| Param | Type | Required |
|---|---|---|
id | string (uuid) | Yes |
Headers
| Header | Required | Notes |
|---|---|---|
X-API-Key | Yes | |
Content-Type | Yes | application/json |
No separate
idempotency-keyheader — the on-chaintxSignatureis the dedup key.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
txSignature | string | Yes | Base58-encoded signature (min length 1). |
tokenHolder | string | Yes | The user's wallet (owner) address in base58 — not the ATA. 32–44 chars. |
Response 200
{ "status": "burn_approved" }Single field — no intent echo. Re-fetch GET /v2/intents/:id to
observe subsequent transitions.
Errors
400— body validation, signature verification failure, wrong signer / wrong delegation amount.404— intent not found.