Skip to content

Quote API

Request a quote — the pair, amount, and side you want — from the Solver Network. The solver searches its liquidity sources, scores the results, and returns either a ranked list of indicative candidates or a firm quote with locked terms. You then execute by submitting an intent (see Executing a quote).

Quoting is a two-step flow. Use POST /v1/quotes for indicative candidates, or POST /v1/quotes/firm when you need a lockable RFQ quote. Execution still happens through the Intent API; there is no separate confirm endpoint and no quote re-fetch — a lapsed quote is simply re-requested.

Base URL

All endpoints are served under the /v1 global prefix:

Production:  https://api.ezys.io/v1
Sandbox:     https://api.ezys.sh/v1

The paths below are shown relative to this base — e.g. POST /quotes is POST /v1/quotes.

Request Quotes

POST /v1/quotes

Synchronous; responds 200 OK. The body is a QuoteProposedEvent carrying a candidates array (empty if no liquidity — see Empty candidates). The request context is persisted so a later execution can resolve it by requestId.

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

Request Body

json
{
  "requestId": "RFQ-001",
  "actorId": "hana-ny",
  "actorType": "institution",
  "source": "USDC",
  "target": "KRW",
  "amount": "1000000",
  "side": "sell",
  "settlementPref": "hybrid"
}
FieldTypeRequiredDescription
requestIdstringYesCaller-supplied unique ID; also the context key a later execution uses to resolve this quote
actorIdstringYesAccount or ID that owns the request
sourcestringYesAsset you send, e.g. USDC
targetstringYesAsset you receive, e.g. KRW
amountstringYesAmount of source to quote, decimal string
sidestringYesbuy or sell
actorTypestringNoretail or institution. Defaults to institution
quoteTypestringNoA_retail or B_institution. Defaults to B_institution
settlementPrefstringNoonchain, offchain, or hybrid. Defaults to hybrid
maxSlippageBpsnumberNoMax slippage you accept, in basis points. Mapped to constraints.maxSlippageBps

Quotes Response

The response echoes your requestId and returns the scored candidates (top 5 by default, best first).

json
{
  "requestId": "RFQ-001",
  "timestamp": 1737123456789,
  "candidates": [
    {
      "routeId": "external-lp-sim:LP-A:USDC-KRW",
      "ttlSec": 30,
      "totalCost": "1445.00000000",
      "slippageBps": 5,
      "provider": "LP-A",
      "hops": ["rfq:LP-A:USDC-KRW"],
      "legs": [
        {
          "from": "USDC",
          "to": "KRW",
          "venue": "LP:LP-A",
          "price": "1445.00000000",
          "feeBps": 8,
          "capacity": "700000000"
        }
      ],
      "metadata": {
        "venue": "ExternalLP",
        "lpId": "LP-A",
        "confidence": 0.94,
        "isDynamic": false,
        "latencyMs": 320,
        "basePrice": 1443.1,
        "feesBps": 8,
        "liquidityAvailable": 700000000,
        "score": 1445.02,
        "constraintViolations": []
      }
    }
  ]
}
FieldTypeDescription
requestIdstringEchoes the submitted ID
timestampnumberResponse time (Unix ms)
candidatesarrayScored quote candidates, best first. Empty [] means no liquidity. See Candidate

Candidate

FieldTypeDescription
routeIdstringOpaque route handle identifying the candidate's route. Its internal shape varies by liquidity source (e.g. external-lp-sim:LP-A:USDC-KRW) — treat it as opaque
ttlSecnumberQuote validity window, in seconds. Honor it client-side and re-request once it lapses — see Status & lifecycle
totalCoststringTotal price for the route, decimal string
slippageBpsnumber | nullEstimated slippage in basis points. Nullable in the schema, but populated by all current sources
providerstring | nullLiquidity provider / source ID. Nullable in the schema, but populated by all current sources
hopsarrayRouting path (defaults to []; populated by all current sources)
legsarrayPer-segment breakdown. See Leg
metadataobjectScoring / diagnostic detail — see Metadata

Leg

FieldTypeDescription
fromstringSegment source asset
tostringSegment target asset
venuestring | nullWhere the segment fills, e.g. LP:<lpId>, ExternalLP, AMM-Pool, Orderbook
pricestring | nullSegment price
feeBpsnumber | nullSegment fee in basis points
capacitystring | nullSupply ceiling — the LP's available balance in the target asset. The most this candidate can fill; used to bound split-fill slices

Metadata

Diagnostic detail attached to each candidate on the HTTP response. Some keys are always computed during scoring; others are source-specific.

FieldTypeDescription
latencyMsnumberSource's quote latency, milliseconds (scoring input)
basePricenumberReference/oracle price before spread and fees
feesBpsnumberFee applied by the route, basis points
liquidityAvailablenumberAvailable liquidity considered during scoring
scorenumberSolver's internal score for ranking (lower is better)
constraintViolationsarrayConstraint checks the candidate failed (e.g. slippage>50); always present, [] when none

Source-specific keys may also appear — e.g. ExternalLP adds venue, lpId, lpName, policyBps, confidence, isDynamic; Orderbook adds orderbookDepth. Treat metadata as an open, source-dependent object.

Empty candidates

A 200 response with candidates: [] means no liquidity source can fill the requested pair right now — a normal outcome. Submit a fresh request (optionally adjusting the pair or amount) to try again.

Executing a quote

Execution converges on the Intent API: express what you want as amount bounds, optionally attach a firm quoteId (see Firm quotes) to accept a locked quote, and submit the intent — see Intent API → Amount bounds and firm quotes.

Status & lifecycle

There is no quote re-fetch endpoint — quotes are short-lived by design. Each candidate carries a ttlSec validity window: honor it client-side and request a fresh quote once it lapses. Execution against an expired quote is rejected, never silently re-priced.

Once execution is submitted (see Executing a quote), it proceeds asynchronously through the settlement flow. Track progress via status reads and webhooks — see the Status API. A denial at the execution stage (e.g. a policy/limit check) surfaces as a rejected state with a machine-readable code — see Error Handling & Retries.

Firm quotes

POST /v1/quotes/firm

Synchronous; responds 201 Created. The response is returned directly — it is not wrapped in a { success, data } envelope.

A firm quote is an RFQ quote whose issuing LP commits the rate and itemized fees for the quote's TTL — the LP warehouses the spread risk for that window, the same way a UniswapX exclusive quoter commits to fill at its quoted price. Firm quotes are what make guaranteed, fee-inclusive transfers possible (e.g. "the recipient receives exactly USD 100,000.00 and my total cost is locked"):

  1. Request a firm quote — the response carries a quoteId, the locked all-in rate, an itemized fees breakdown (protocol_fee, payout_fee, network_fee — the same taxonomy as the receipt, so quote and receipt reconcile per type), and validUntil.
  2. Attach the quoteId to an Intent (see Intent API → Amount bounds and firm quotes).
  3. Within the TTL, settlement enforces delivery at the quoted terms; past validUntil the quote lapses and the intent is rejected — never silently re-priced.

Quoted fees are locked charges, not cost estimates. A deducted fee item is a commitment — the amount charged at settlement is exactly the amount quoted, regardless of what the underlying cost turns out to be; the quoting side warehouses the variance (the same way a UniswapX filler commits an all-in price including gas, or a bank commits an OUR wire fee upfront). Consequently, the receipt's fees[] must match the locked quote per type — a mismatch is a contract violation, not a rounding artifact. The only exception is absorbed items (e.g. network_fee fronted by Ezys): they are informational estimates, charged to no one, and may differ on the receipt where the actual measured value is reported.

Firm Quote Request Body

json
{
  "requestId": "RFQ-FIRM-001",
  "actorId": "fsp-narae",
  "actorType": "institution",
  "side": "sell",
  "inputAsset": {
    "type": "erc20",
    "symbol": "USDC",
    "chain": "ethereum"
  },
  "outputAsset": {
    "type": "fiat",
    "symbol": "USD",
    "currency": "USD"
  },
  "amount": "10000.00",
  "amountType": "target"
}
FieldTypeRequiredDescription
requestIdstringYesCaller-supplied unique ID for the quote request
actorIdstringYesAccount or partner ID that owns the request
actorTypestringNoretail or institution. Defaults to institution
sidestringNobuy or sell. Defaults to sell
inputAssetobjectYesAsset you pay. Same AssetDescriptor shape as the Intent API
outputAssetobjectYesAsset the recipient receives. Same AssetDescriptor shape as the Intent API
amountstringYesAmount to quote. Interpreted according to amountType
amountTypestringYessource means amount is the input you will pay; target means amount is the exact recipient amount to lock

Use amountType: "target" for destination-fixed payout. In that mode the solver computes the required input by adding locked deducted fees to the target output and rounding the required input up, so the recipient is not short-paid. The returned input is the maximum amount expected for the attached intent; the intent can still set an input.limit cap when accepting the quote.

Firm Quote Response

Amounts in input, output, and fees[] follow the Money & Amounts minor-unit convention.

jsonc
{
  "requestId": "RFQ-FIRM-001",
  "quoteId": "fq_9ab32c1f",                  // ← attach this to the Intent to accept
  "pair": "USDC/USD",
  "allInRate": "1.000000",                   // locked for the TTL — LP commits
  "input":  { "amountMinor": "10035000000", "currency": "USDC", "exponent": 6 },  // 10,035.00 USDC required
  "output": { "amountMinor": "1000000",     "currency": "USD",  "exponent": 2 },  // exactly USD 10,000.00
  "fees": [                                  // itemized, separate from principal — same taxonomy as the receipt
    { "type": "protocol_fee", "amountMinor": "1000",   "currency": "USD",  "exponent": 2, "collection": "deducted" },
    { "type": "payout_fee",   "amountMinor": "2500",   "currency": "USD",  "exponent": 2, "collection": "deducted" },
    { "type": "network_fee",  "amountMinor": "150000", "currency": "USDC", "exponent": 6, "collection": "absorbed" }
  ],                                         // FX spread is NOT a fee item — it is inside allInRate
  "validUntil": "2026-07-15T12:00:30Z"
}
FieldTypeDescription
requestIdstringEchoes the submitted request ID
quoteIdstringOpaque, single-use firm quote handle. Attach it to POST /v1/intents to accept the quote
pairstringInput/output pair
allInRatestringLocked all-in rate, expressed as output per one input. FX spread is inside this rate, not a fee item
inputobjectRequired input amount, minor-unit object
outputobjectLocked output amount, minor-unit object
feesarrayLocked itemized fees, using the same taxonomy as receipts: protocol_fee, payout_fee, network_fee
validUntilstringISO 8601 expiry. The accepting intent must arrive before this time

Accepting a firm quote

Attach the returned quoteId to POST /v1/intents. A firm quote is single-use: once an intent consumes it, the same quoteId cannot be used again. Pair, amount, and TTL are checked at acceptance; mismatches are rejected rather than silently re-priced.

For destination-fixed payout, the accepting intent should use exact-output bounds:

jsonc
{
  "input":  { "limit": "10100000000" },          // cap: most you will pay
  "output": { "target": "10000.00", "limit": "10000.00" },
  "quoteId": "fq_9ab32c1f"
}

Without a quoteId, quotes are indicative and intents remain best-effort auction orders.

Notes

  • All amounts are decimal strings — no floating-point.
  • Defaults: actorTypeinstitution, quoteTypeB_institution, settlementPrefhybrid.
  • routeId is an opaque handle — do not parse it.