Skip to content

Intent API

Submit an Intent — what you want to exchange and the constraints you accept — to the Solver Network. The solver computes an execution path and either accepts it (reserving liquidity and starting settlement) or rejects it with a reason.

Submission is single-step: the response is terminal (accepted or rejected) and there is no separate confirm step — see Status & lifecycle and Cancellation. To lock a price before committing, request a firm quote first and attach its quoteId (see Quote APIFirm quotes, and Amount bounds and firm quotes).

Submit Intent

POST /intents

Synchronous; responds 202 Accepted. The body is an accepted or rejected object. On acceptance the solver reserves balance, derives an HTLC hashlock, and starts the DvP settlement workflow.

Request Body

json
{
  "intentId": "INT-2F8A3B1C",
  "user": "0xabc...def",
  "inputAsset": {
    "type": "erc20",
    "symbol": "USDC",
    "chain": "ethereum",
    "address": "0xA0b8...eB48"
  },
  "outputAsset": {
    "type": "fiat",
    "symbol": "KRW",
    "currency": "KRW"
  },
  "inputAmount": "1000",
  "minOutputAmount": "1400000",
  "deadline": 1737123456789,
  "settlementType": "hybrid",
  "outputRecipient": "kr-bank-account-123",
  "beneficiary": {
    "account": "110-123-456789",
    "name": "Hong Gildong",
    "bank": "KEB Hana"
  }
}
FieldTypeRequiredDescription
intentIdstringYesCaller-supplied unique ID; also used as the API source/reply reference
clientReferencestringNoTarget. Your own correlation key for this transaction (e.g. your payment-pipeline ID). Bound at submission and echoed verbatim on every read surface — Status, Webhooks, Receipts, reconciliation. Independent of the Idempotency-Key header, which is transport-level retry safety (see Idempotency)
userstringYesAccount or address that owns the Intent
inputAssetobjectYesAsset you send. See AssetDescriptor
outputAssetobjectYesAsset you receive. See AssetDescriptor
inputAmountstringYesInput amount you send (see Money & Amounts). In the generalized bounds model this is input.target == input.limit — see Amount bounds and firm quotes
minOutputAmountstringYesMinimum output you accept. Below this → below_minimum_output. In the bounds model this is output.limit
quoteIdstringNoReference to a locked firm quote (LP-committed via RFQ). When attached, the quoted rate and itemized fees are guaranteed for the quote's TTL. See Amount bounds and firm quotes
deadlinenumberYesUnix timestamp expiry
settlementTypestringYesonchain, offchain, or hybrid
outputRecipientstringNoThird-party recipient. If set and ≠ user, this is a remittance and beneficiary is required
beneficiaryobjectConditionalRequired for a remittance, and for any fiat output. See Beneficiary
actorTypestringNoretail or institution. Defaults to institution
sidestringNobuy or sell. Defaults to sell
metadataobjectNoCompliance / routing metadata. See Metadata

settlementMode is not part of the request — the solver currently settles atomic.

Amount bounds and firm quotes

The generalized bounds form and quoteId support destination-fixed payout and firm quote acceptance. The exact-input shape (inputAmount + minOutputAmount) remains supported as backward-compatible sugar.

The Intent amount model generalizes to two orthogonal axes (the same decomposition UniswapX uses — amounts as decaying ranges that can collapse to a point, and firmness supplied by an RFQ winner who commits to fill):

Axis 1 — amounts are bounds that can collapse to a point. Each side carries { target, limit }. When target == limit the amount is fixed (a limit order); when they differ, the amount decays from target toward limit over the intent's lifetime (an auction range).

jsonc
"input":  { "target": "...", "limit": "..." },   // limit = the most you will pay (cap)
"output": { "target": "...", "limit": "..." }    // limit = the least you accept (floor)
ModeinputoutputMeaning
Exact-input (default)target == limit (fixed)limit only (floor)Fix what you send; solvers compete on output. inputAmount/minOutputAmount are sugar for this.
Exact-outputlimit only (cap)target == limit (fixed)Recipient receives an exact amount — a bank wire with sender-paid fees (OUR). Solvers compete on the input within your cap.
Range (auction)capdecaying target → limitUniswapX-style Dutch decay; first profitable solver fills.

Axis 2 — firmness comes from a locked quote, not from the amount shape. Without quoteId, an Intent is best-effort: it competes in the auction and may expire unfilled. With quoteId referencing a firm RFQ quote, the quoting LP has committed the rate and itemized fees for the quote's TTL — the LP warehouses the spread risk (exactly like a UniswapX exclusive quoter) and settlement enforces delivery of the quoted output, so the fill is guaranteed within the TTL.

Where does quoteId come from? The firm flow is quote-first — the two flows enter in a different order:

Best-effort (auction):   submit intent ──▶ solvers compete ──▶ fill or expire
                         (no quoteId anywhere — the auction is the price discovery)

Firm (RFQ-accept):       ① request firm quote (Quote API)          — "what will it cost?"
                         ② receive quoteId + locked rate/fees/TTL  — LP commits
                         ③ submit intent WITH quoteId              — "I accept this quote"
                         ④ guaranteed fill within validUntil

The intent never triggers the RFQ; the client obtains the quoteId before submitting, and the intent that carries it is semantically the acceptance of that quote. This is the same handle-passing shape as the firm flow (POST /quotes/firmquoteIdPOST /intents) — except the handle is LP-committed rather than advisory.

Exact-output, best-effort (no quoteId — auction, may expire):

jsonc
{
  "intentId": "INT-9C1D2E3F",
  "user": "0xabc...def",
  "inputAsset":  { "type": "erc20", "symbol": "USDC", "chain": "ethereum", "address": "0xA0b8...eB48" },
  "outputAsset": { "type": "fiat",  "symbol": "USD",  "currency": "USD" },
  "input":  { "limit": "100305000000" },                     // cap: most you'll pay — 100,305 USDC (minor)
  "output": { "target": "100000.00", "limit": "100000.00" }, // exactly USD 100,000.00
  "deadline": 1737123456789,
  "settlementType": "hybrid",
  "outputRecipient": "us-bank-account-123",
  "beneficiary": { "account": "...", "name": "Jane Doe", "bank": "..." }
}

Exact-output, firm (locked quote attached — guaranteed within TTL):

jsonc
{
  // ...same as above, plus:
  "quoteId": "fq_9ab32c1f"    // ← from the firm-quote response (Quote API → Firm quotes):
                              //   locked allInRate, required input,
                              //   itemized fees, validUntil 2026-07-15T12:00:30Z
}

Amounts follow the Money & Amounts convention. For exact-output intents the accepted response returns the computed inputAmount; when a quoteId is attached, the fees breakdown is itemized from the locked quote (see Accepted Response). Firm quotes are issued by the Quote API RFQ flow — the fq_9ab32c1f above is the quoteId returned by the firm-quote response example in Quote API → Firm quotes.

AssetDescriptor

FieldTypeRequiredDescription
typestringYeserc20, fiat, or native
symbolstringYese.g. USDC, KRW, ETH
addressstringNoERC20 contract address (erc20)
chainstringNoe.g. ethereum (on-chain assets)
currencystringNoISO 4217 code (fiat), e.g. KRW

Beneficiary

FieldTypeRequiredDescription
accountstringYesRecipient account
namestringYesRecipient name
bankstringNoRecipient bank
travelRuleDataobjectNoTravel Rule payload

Metadata

FieldTypeDescription
senderKycHashstringSender KYC reference
depositReferencestringDeposit reference for the fiat on-ramp leg
currencystringSettlement currency hint
targetStablecoinstringStablecoin used for the fiat on-ramp leg
travelRuleDataobjectTravel Rule payload
beneficiaryAccountstringDeprecated — use top-level beneficiary.account
beneficiaryNamestringDeprecated — use top-level beneficiary.name

Accepted Response

json
{
  "intentId": "INT-2F8A3B1C",
  "status": "accepted",
  "planId": "DVP-INT-2F8A",
  "solverId": "solver-1",
  "path": {
    "intentId": "INT-2F8A3B1C",
    "solverId": "solver-1",
    "outputAmount": "1424500",
    "surplus": "24500",
    "estimatedFillTime": 3600,
    "steps": [
      {
        "type": "fiat_gateway",
        "asset": "KRW",
        "amount": "1424500",
        "provider": "fiat-gateway",
        "currency": "KRW",
        "beneficiaryAccount": "110-123-456789"
      }
    ],
    "score": 0,
    "expiresAt": 1737123756789
  }
}

The response is returned directly — it is not wrapped in a { success, data } envelope.

FieldTypeDescription
intentIdstringEchoes the submitted ID
statusstringaccepted
planIdstringSettlement plan ID — DVP- + the first 8 chars of intentId, upper-cased
solverIdstringThe solver that accepted the Intent
pathobjectThe chosen execution plan. See SolvedPath

SolvedPath

FieldTypeDescription
intentIdstringThe Intent this path fulfils
solverIdstringSolver that produced the path
outputAmountstringFinal amount delivered to the recipient
inputAmountstringInput required for this path. For an exact-output intent this is the computed value (≤ input.limit); for exact-input it echoes the request.
surplusstringExact-input: outputAmount − minOutputAmount. Exact-output: the input saved, input.limit − inputAmount.
feesarrayPresent when the intent references a firm quoteId. Itemized from the locked quote, with the same taxonomy as the receipt: [{ type, amountMinor, currency, exponent, collection }] (amounts follow the Money & Amounts convention), type = protocol_fee | payout_fee | network_fee. Quote-time values are the locked charge; the receipt reports what was actually charged — reconcile the two per type. FX spread is not a fee item (it is a component of the all-in rate). Kept separate from principal, not baked into the amounts above.
estimatedFillTimenumberEstimated fill time in seconds (≈30 for crypto output, ≈3600 for fiat output)
stepsarrayExecution steps. See Fill steps
scorenumberSolver-internal score (lower is better)
expiresAtnumberPath validity deadline (Unix ms; ≈ now + 5 min)

Fill steps

Each path.steps[] entry is one of the following types: reserve, dex_swap, cex_otc, bridge, cow_match, fiat_gateway. Every step carries type, asset, amount, and optional chain; type-specific fields are added per kind.

The solver currently emits one step per Intent:

  • fiat_gateway — for fiat output: { type, asset, amount, provider, currency, beneficiaryAccount }
  • reserve — for crypto output: { type, asset, amount, chain, source } (filled from the solver's own reserve)

Rejected Response

json
{
  "intentId": "INT-2F8A3B1C",
  "status": "rejected",
  "reason": "no_liquidity"
}
reasonRaised when
beneficiary_required_for_remittanceoutputRecipientuser, but beneficiary.account / beneficiary.name is missing
no_liquidityNo solver candidate can fill the requested pair
below_minimum_outputThe computed output is less than minOutputAmount / output.limit (exact-input intents)
above_maximum_inputThe input required to deliver the fixed output exceeds input.limit (exact-output intents)
beneficiary_account_required_for_fiat_outputoutputAsset.type is fiat, but no beneficiary account was provided

Validation runs in the order above; the first failure is returned.

Infrastructure errors

Infrastructure failures (e.g. misconfiguration, or the settlement workflow failing to start) propagate as 5xx. When settlement startup fails after a balance reservation, the reservation is released before the error surfaces.

Status & lifecycle

This endpoint is terminal at submission time — it returns only accepted or rejected. After acceptance, settlement proceeds asynchronously through the DvP/HTLC workflow; track progress via the Status API (GET /executions/{reference}) and status webhooks. The authoritative on-chain state is tracked by the Intent settler contract: Created → Filled → Settled, or Cancelled.

Cancellation

There is no DELETE on this endpoint. Cancellation is initiated through other channels:

  • FIX (institutional): an OrderCancelRequest (35=F) cancels the intent, correlated by origClOrdId + sessionId.
  • On-chain: an Intent can be cancelled permissionlessly once its fill deadline has passed.

Notes

  • All amounts are decimal strings — no floating-point.
  • actorType defaults to institution; side defaults to sell.
  • AssetDescriptor has no decimals field; token decimals are resolved internally.