Appearance
Intent
How to lock a rate with a firm quote and execute it by submitting an Intent.
Submission is single-step: the response is terminal — accepted (execution begins immediately) or rejected (with a reason). There is no separate confirm step; rate certainty comes from attaching a firm quote to the Intent instead.
Flow
① POST /v1/quotes/firm → quoteId + locked rate, fees, validUntil
② POST /v1/intents (+ quoteId) → accepted → settlement begins
→ rejected (reason)
③ Track via webhooks / Status APIThe firm quote is optional — an Intent submitted without a quoteId is a best-effort auction order that solvers compete to fill (and may expire unfilled). Attach a quoteId when you need the rate and fees locked. See Quote API → Firm quotes.
Step 1: Lock a rate (firm quote)
Request a firm quote for what you want to exchange. amountType: "target" fixes the recipient amount — the solver computes the required input including fees, so the recipient is never short-paid.
POST /v1/quotes/firmjson
{
"requestId": "RFQ-FIRM-001",
"actorId": "your-partner-id",
"side": "sell",
"inputAsset": { "type": "erc20", "symbol": "USDC", "chain": "ethereum" },
"outputAsset": { "type": "fiat", "symbol": "USD", "currency": "USD" },
"amountMinor": "1000000",
"amountType": "target"
}The response is returned directly (no { success, data } wrapper) and carries everything you need to decide:
jsonc
{
"requestId": "RFQ-FIRM-001",
"quoteId": "fq_9ab32c1f", // ← attach this to the Intent to execute
"pair": "USDC/USD",
"allInRate": "1.000000",
"input": { "amountMinor": "10010250000", "currency": "USDC", "exponent": 6 },
"output": { "amountMinor": "1000000", "currency": "USD", "exponent": 2 },
"fees": [
{ "type": "protocol_fee", "amountMinor": "1000", "currency": "USD", "exponent": 2, "collection": "deducted" },
{ "type": "payout_fee", "amountMinor": "25", "currency": "USD", "exponent": 2, "collection": "deducted" },
{ "type": "network_fee", "amountMinor": "0", "currency": "USDC", "exponent": 6, "collection": "absorbed" }
],
"validUntil": "2026-07-15T12:00:30Z"
}At this point, nothing has been executed. The quoting LP has committed the rate and itemized fees until validUntil — you can review the locked terms and decide whether to proceed. See Quote API → Firm Quote Response for field details.
Step 2: Submit the Intent
Submitting the Intent is the acceptance of the quote — it must arrive before validUntil.
POST /v1/intentsSend an X-Idempotency-Key header (a UUID) so a lost response can be retried safely — see Idempotency. Use clientReference for your own correlation key; it is echoed verbatim on every read surface (status, webhooks, receipts).
json
{
"intentId": "INT-9C1D2E3F",
"clientReference": "your-payment-id-123",
"user": "0xabc...def",
"inputAsset": { "type": "erc20", "symbol": "USDC", "chain": "ethereum", "address": "0xA0b8...eB48" },
"outputAsset": { "type": "fiat", "symbol": "USD", "currency": "USD" },
"input": { "limitAmountMinor": "10100000000" },
"output": { "targetAmountMinor": "1000000", "limitAmountMinor": "1000000" },
"quoteId": "fq_9ab32c1f",
"deadline": 1737123456789,
"settlementType": "hybrid",
"outputRecipient": "us-bank-account-123",
"beneficiary": { "account": "110-123-456789", "name": "Jane Doe", "bank": "..." },
"permit": {
"token": "0xA0b8...eB48",
"amountMinor": "10100000000",
"spender": "0x1c9E...F63a",
"nonce": "0",
"deadline": 1737209856,
"signature": "0x9f2c...8b1c"
}
}Two fields deserve attention:
permit— required for on-chain (erc20) inputs. A signed NfxPermit2 authorization that lets settlement pull the input fromuser's wallet; without it the Intent is rejected withpermit_required. See Intent API → Permit.beneficiary— required for anyfiatoutput, and for remittances (outputRecipient≠user).
Accepted
Responds 202 Accepted with the execution plan, returned directly (no envelope). On acceptance the solver reserves liquidity and settlement starts immediately:
json
{
"intentId": "INT-9C1D2E3F",
"status": "accepted",
"planId": "DVP-INT-9C1D",
"solverId": "solver-1",
"inputAmountMinor": "10010250000",
"fees": [ ... ],
"path": {
"outputAmountMinor": "1000000",
"surplusAmountMinor": "0",
"estimatedFillTime": 3600,
"steps": [ ... ]
}
}With a quoteId attached, top-level fees is itemized from the locked quote — the receipt must later match it per fee type. See Intent API → Accepted Response for every field.
Rejected
A business rejection responds 422 with the original rejection object:
json
{
"intentId": "INT-9C1D2E3F",
"status": "rejected",
"reason": "no_liquidity"
}A rejected Intent never starts executing — nothing to cancel or compensate. See Intent API → Rejected Response for the full list of reasons, and Error Handling & Retries for which errors are safe to retry.
Step 3: Track execution
After acceptance, settlement proceeds asynchronously through three phases — intent → offramp → payout. Track it by:
- Webhooks (push) — status events delivered to your endpoint; see Webhooks.
- Status API (pull) —
GET /v1/executions/{reference}, resolvable by your ownclientReference; see Status API (coming soon — published as the contract of record ahead of rollout).
Both surfaces read from the same store and never disagree. On a timeout or missed event, re-query the Status API rather than re-submitting.
Handling expiry
A firm quote lapses at validUntil. An Intent that arrives after that is rejected, never silently re-priced — request a fresh firm quote and submit again. There is no quote re-fetch endpoint; quotes are short-lived by design.
Cancellation
There is no DELETE on the Intent endpoint — submission is terminal, and an accepted Intent is already executing. Cancellation happens through other channels:
- FIX (institutional): an
OrderCancelRequest(35=F) cancels the intent. - On-chain: an Intent can be cancelled permissionlessly once its fill deadline has passed.
See Intent API → Cancellation.
Full API Reference
For complete field descriptions, amount-bounds semantics, and error codes, see the Intent API and Quote API.
