Developers

Request API key

Build swaps and cross-chain transfers into your wallet or app with the Cleave API.

Overview

The Cleave API gives your server a quote and a ready transaction. Your user signs and sends it from their own wallet.

What you can do:

  • Same-chain swaps on 6 chains: Ethereum, Optimism, BNB Chain, Polygon, Base and Arbitrum.
  • Wrap and unwrap the native coin on each chain.
  • Cross-chain transfers between any two of these chains: USDC to USDC, token to USDC, and token to token.
  • Your own referral fee on each quote, paid on-chain to your address.
  • USD estimates on every quote, and token prices on request.

The flow is quote → execute:

  1. GET /partner/v1/quote with the tokens, amount, recipient and slippage.
  2. Read the amounts, fees and notices. Show them to your user.
  3. If needed, the user approves the token (execution.approval).
  4. The user signs and sends tx as returned.
  5. Cross-chain: track the transfer with rails[].tracking.

Only fast routes are served. If no fast route exists for a transfer, the API answers no_fast_route.

Base URL

https://api.cleave.fi

All endpoints live under /partner/v1/. Every answer is JSON and carries apiVersion: "1".

Auth and keys

Get a key

Click Request API key and tell us about your integration. We issue the key to you directly. Keep it on your servers only.

Send the key

Send it on every request:

Authorization: Bearer cq_live_<your key>
  • Keys look like cq_live_ followed by 43 characters (A-Z a-z 0-9 _ -).
  • Bearer is case-insensitive. There is no other auth method.
  • Several keys of one partner share one rate limit.

Server-to-server only

  • Requests that carry an Origin header are refused (403 forbidden_origin). Browsers send it on cross-origin requests, so a key can't be used from a web page.
  • If your web app calls your backend, strip Origin before forwarding.
  • No CORS headers are sent. OPTIONS answers 405.

Optional IP allowlist

We can lock a key to up to 16 IPv4/IPv6 addresses or CIDR ranges. Other addresses get 403 forbidden_ip. Skip it if your egress IP changes (serverless, shared proxies).

Keep the key safe

  • Store it in a secret store. Never ship it in an app or a repo.
  • Do not log full request headers.
  • Lost or leaked: ask us to revoke it and issue a new one.

Endpoints

MethodPathPurpose
GET, HEAD/partner/v1/quoteSwap, wrap/unwrap and cross-chain quotes with a ready tx
GET, HEAD/partner/v1/metaChains, contracts, lanes, fee caps, your key's limits
GET, HEAD/partner/v1/tokensDefault token list of a chain
GET, HEAD/partner/v1/tokens/searchSearch the full token list
GET, HEAD/partner/v1/tokens/lookupOne token: list entry and on-chain details
GET, HEAD/partner/v1/pricesUSD prices of up to 4 tokens
GET, HEAD/partner/v1/healthService health per chain
POST/partner/v1/reportReport a problem with a quote or transaction
  • HEAD returns the headers of the GET answer, without a body.
  • Every answer has X-Request-Id; JSON bodies repeat it as requestId. Log it and quote it when you contact us.
  • Unknown or repeated query parameters are a 400.
  • URLs are limited to 2,048 bytes.

Conventions

  • Amounts are decimal strings in base units (wei, or 1e-6 for most USDC). Parse them with a big-integer type.
  • Addresses: 0x + 40 hex, any case in requests; checksummed in answers.
  • Native coin (ETH, BNB, POL): the sentinel 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE.
  • "USDC" is the chain's USDC from meta chains[].usdc. It has 18 decimals on BNB Chain and 6 elsewhere: read chains[].usdcDecimals.
  • bps: 1 bps = 0.01 %.
  • Identifiers are opaque strings: rail ids (rails[].rail, meta lanes[].rails[].rail), tracking.kind, notice codes, report types, venue names and token sources/flags. Read them from meta or the quote; never hard-code them.
  • Ignore fields you do not know; new ones may be added.

GET /partner/v1/quote

Parameters

ParamRequiredFormatMeaning
chainIdyesintegerChain where tokenIn is spent
tokenInyesaddress or native sentinelToken sold
tokenOutyesaddress or native sentinelToken received, on toChainId
amountInyesbase units, ^[1-9][0-9]{0,77}$Exact input amount
toChainIdnointegerDestination chain. Default chainId. Another value makes it cross-chain
slippageBpsno*integer 0–9999Sets minOut and every floor
recipientno*addressReceiver of the output
deadlineno*unix secondsSwap expiry
referralBpsnointeger 0–9999Your fee, in bps of the output
referralRecipientwith referralBps > 0addressWhere your fee is paid

* A ready tx needs recipient. When a swap runs on the source chain (same-chain swaps, token-in cross-chain), it also needs slippageBps and deadline.

Rules:

  • Token to token cross-chain needs slippageBps of at least 1.
  • deadline is refused when the input is USDC on a cross-chain quote.
  • recipient must be the user's wallet. Zero, Cleave contracts (meta chains[].forbiddenRecipients) and, cross-chain, the destination's wrapped native token and route contracts are refused (400 invalid_param).
  • Wrap and unwrap take no recipient or deadline.
  • Cross-chain native input is refused: wrap it first.
  • Token to token cross-chain, to receive the native coin: request the native sentinel as tokenOut, not the wrapped token.
  • The quote is valid for exactly amountIn.

What you get back

Requestkindflow
native → wrapped native, same chainwrap–
wrapped native → native, same chainunwrap–
any other pair, same chainswap–
USDC → USDC, cross-chaincrossbridge
USDC → other token, cross-chaincrossbridge (steps: 2)
token → USDC, cross-chaincrossswap_bridge
token → token, cross-chaincrossswap_bridge_swap

Fields in every quote

FieldMeaning
statusok, or no_route (HTTP 200, not an error)
amountInUsd, amountOutUsdUSD estimates for display, or null (see USD estimates)
quotedAt, ageMsWhen chain data was read (unix ms), and its age at answer time
block{number, hash} the quote was computed at
ttlSecRe-quote when the quote is older than this
cachemiss, collapsed, hit (shared cache, ttlSec long) or null
noticesReady-to-show user texts (see Notices)

Same-chain swap fields

FieldMeaning
amountOutGrossRoute output before fees
fees.protocol{bps, amount, block}: Cleave's fee, read on-chain
fees.referral{bps, amount}: your fee
amountOutNetGross minus both fees: what the recipient gets at this block
minOutfloor(amountOutNet × (10000 − slippageBps) / 10000); enforced on-chain
preferredExecutionclassic: send tx. measured: send txMeasured
executionCall parts: target, function, value, params, approval, permit2
tx{to, data, value}, ready to sign; null without the required params or with a blocking notice
executionMeasured, txMeasuredSame, for tokens that take a fee on transfer
routePools and venues used (venue names are display labels)
partialPart of amountIn the route refuses; it goes back to the sender in the same tx

Cross-chain fields

FieldMeaning
rails[]Routes you can execute, best first. Each has its own tx
rails[].railOpaque route id. Display name: meta lanes[].rails[].displayName
rails[].amountOutWhat arrives, in base units of amountOutToken (destination chain)
rails[].amountOutUsdUSD estimate of that route's amountOut, or null
rails[].amountOutKindexact, exact_or_refund, floor or swap_or_usdc
rails[].minimumGuaranteed floor, in the same units as amountOut
rails[].feesprotocol, referral, bridge {amount, bps}, native
rails[].eta{label, seconds, conditional}
rails[].limitsmin, max, maxUnit, plus minUnit/maxUnitInfo with decimals
rails[].execution, rails[].txCall parts and ready tx
rails[].trackingHow to follow the transfer: kind, idSource, url (see Status tracking)
rankedByamountOut or minimum: how rails[] is sorted
unavailableRails[]Routes not served for this quote, with reason
sourceSource-chain swap details (swap flows), else null
destinationDestination swap details (token to token), else null; see Destination swaps
steps, secondStepsteps: 2: bridge USDC, then quote a swap on arrival

Units on cross-chain quotes:

  • amountOut and minimum are in destination-chain units of amountOutToken.
  • amountIn, fees.*.amount and limits are in source-chain units.
  • USDC decimals differ on BNB Chain (18), so convert with usdcDecimals.

Routes served to partners:

  • Only routes expected to complete within 5 minutes are returned.
  • Routes that need a separate claim on the destination are never returned.
  • A route whose speed depends on the amount is dropped when it would be slow.
  • If only slow routes exist, the API answers 400 no_fast_route.
  • If a fast route failed only for now, the API answers a retryable 503.

Destination swaps (token to token)

When flow is swap_bridge_swap, one route carries a swap on the destination. destination describes that swap.

destination.statusWhat you get
okThe route that carries the swap has amountOutKind: swap_or_usdc. If the swap fails on arrival, USDC is delivered instead (rails[].usdcFallback)
unavailableNo destination swap right now. steps: 2: rails[] deliver USDC on the destination, and secondStep names the swap to quote after it arrives

When status is unavailable, destination.reason says why:

reasonMeaningWhat to do
retry_unavailableDestination swaps to this chain are not offered right nowTake the USDC routes, or try again later
dest_gas_highThe swap needs more gas than can be covered on the destination at current gas pricesTake the USDC routes, or try again later
no_routeNo swap route for this token and amount on the destinationTake the USDC routes
exceeds_size_capThe swap route is too large to run on arrivalTake the USDC routes
amount_too_lowThe amount is too small for a destination swapUse a larger amount, or take the USDC routes
rail_unavailable, upstream_unavailable, upstream_error, upstream_timeoutTemporaryQuote again in a moment
  • Treat any other reason like no_route.
  • The output token must have been checked for a transfer fee on the destination. If that check can't be done, there is no quote:
    • 400 unsupported_destination, reason token_unverifiable: not retryable;
    • 503 upstream_unavailable, reason delivery_unchecked: retry in a moment.

    Both carry a bridge-USDC-first plan in error.suggestion (kind: "two_step_bridge_first"): first is the cross-chain transfer to the destination USDC, then the same-chain swap on the destination.

  • compose_unsupported in unavailableRails[] means that route delivers USDC only, so it cannot carry the destination swap.

USD estimates

amountInUsd and amountOutUsd follow amountIn in every quote.

  • A decimal string in US dollars, or null when no estimate is available.
  • Display only. Never use them to build or check a transaction.
  • From $1 up: 2 decimals, rounded. Below $1: up to 4 significant digits. Zero is "0".
  • Same-chain: amountIn and amountOutNet. Cross-chain: amountIn and rails[0].amountOut; each route also has rails[].amountOutUsd.
  • Stablecoins count as $1. A quote never waits for a price.

GET /partner/v1/meta

Your discovery endpoint. Read it at start-up and offer only what it lists.

FieldMeaning
chains[]chainId, name, quotesEnabled, usdc, usdcDecimals, wrappedNative, nativeSentinel, execution, forbiddenRecipients, contracts[], cacheTtlSec
lanes[]One per chain pair: chainId, toChainId, router, rails[]
lanes[].rails[]rail, displayName, available, eta. Lists every route on the lane, including routes the partner API does not serve
venuesSwap venues per chain
feesprotocolFeeCapBps, referral caps
tokensToken and price endpoints, searchMax
reportingReport endpoint (/partner/v1/report), types, size and rate limits
partnerYour partnerId, limit {perSec, windowSec, sharedPerSec}, ipAllowlist, referral.maxBps
reportingendpoint, types[] (the report types you may send), limit
  • Supports ETag / If-None-Match (304 when unchanged).

Token endpoints

EndpointParamsAnswer
/partner/v1/tokenschainId, optional vDefault list: version, updatedAt, count, total, tokens[]
/partner/v1/tokens/searchchainId, q (1–48 chars), optional limit (1–50, default 20)count, tokens[]
/partner/v1/tokens/lookupchainId, addressonChain {symbol, name, decimals}, isToken, inList, listed, logo, flags, listStatus
  • A token entry: address, symbol, name, decimals, logo, sources, flags.
  • logo is a URL, or a path on the API host (/assets/token-logo/{chainId}/{address}). Logo paths need no key.
  • Lookup logo: the listed token's logo; for an unlisted token, a relative /assets/token-logo/{chainId}/{address} path when a logo was found; else null.
  • Quotes carry no symbols or decimals: take them from these endpoints.
  • While a list is being built: 503 upstream_unavailable, reason token_list_pending.

GET /partner/v1/prices

USD prices of up to 4 tokens on one chain.

ParamMeaning
chainIdAn enabled chain
tokens1 to 4 comma-separated addresses; the native sentinel is allowed; duplicates are merged
curl -s -H "Authorization: Bearer $CLEAVE_KEY" \
  'https://api.cleave.fi/partner/v1/prices?chainId=8453&tokens=0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb,0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'
{
  "apiVersion": "1",
  "chainId": 8453,
  "prices": {
    "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb": { "usd": "1.00", "at": 1791810633 },
    "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913": { "usd": "1.00", "at": 1791810633 }
  }
}
  • Keys are checksummed addresses.
  • usd: price of one whole token, same format as the USD estimates. at: unix seconds the price was made.
  • null for a token: no price right now.
  • Prices are cached server-side for about 45 s.
  • Display only, like the USD estimates.

GET /partner/v1/health

  • 200 with status: "ok" when every chain is healthy.
  • 503 with status: "degraded" otherwise.
  • chains[] has one entry per chain.
  • It counts against your rate limit: poll it rarely.

POST /partner/v1/report

Tell us when something went wrong. Body: JSON, Content-Type: application/json or text/plain, at most 4,096 bytes.

FieldRequiredMeaning
typeyesOne of meta reporting.types
chainIdyesSource chain
clientVersionyesYour app version: 1–64 chars of letters, digits, . _ + -
toChainId, txHash, destTxHash, quoteRequestId, rail, amount, token, recipientper typeContext; the answer names a missing one
messagenoUp to 280 characters

Answer: {accepted, reportId, verified}. Reports are limited to 10 per 60 s per key.

Examples

Examples use illustrative values. Never copy them into a transaction.

Trimming: each JSON block is a valid subset of a real answer. Values in <angle brackets> are placeholders for identifiers. tx.data is cut to its 4-byte selector followed by ….

Same-chain swap with a referral

1 WETH to DAI on Base, 0.5 % slippage, 0.25 % referral:

curl -s -H "Authorization: Bearer $CLEAVE_KEY" \
  'https://api.cleave.fi/partner/v1/quote?chainId=8453&tokenIn=0x4200000000000000000000000000000000000006&tokenOut=0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb&amountIn=1000000000000000000&slippageBps=50&recipient=0x000000000000000000000000000000000000bEEF&deadline=4102444800&referralBps=25&referralRecipient=0x00000000000000000000000000000000000000AA'
{
  "apiVersion": "1",
  "requestId": "a4013f22-d6e2-4eb4-be28-0313ee57ddc5",
  "kind": "swap",
  "status": "ok",
  "chainId": 8453,
  "toChainId": 8453,
  "tokenIn": "0x4200000000000000000000000000000000000006",
  "tokenOut": "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb",
  "amountIn": "1000000000000000000",
  "amountInUsd": "2500.00",
  "amountOutUsd": "2491.25",
  "quotedAt": 1791810633561,
  "ageMs": 2,
  "cache": "miss",
  "block": {
    "number": "1000",
    "hash": "0x00000000000000000000000000000000000000000000000000000000000003e8"
  },
  "ttlSec": 5,
  "amountOutGross": "2500000000000000000000",
  "fees": {
    "protocol": { "bps": 10, "amount": "2500000000000000000", "block": "1000" },
    "referral": { "bps": 25, "amount": "6250000000000000000" }
  },
  "amountOutNet": "2491250000000000000000",
  "minOut": "2478793750000000000000",
  "preferredExecution": "classic",
  "execution": {
    "target": "0x00000000000000000000000000000000000000A5",
    "function": "swapWithBlob((address,address,uint256,uint256,uint256,address,address,uint16,bool),bytes)",
    "value": "0",
    "approval": {
      "token": "0x4200000000000000000000000000000000000006",
      "spender": "0x00000000000000000000000000000000000000A5",
      "amount": "1000000000000000000"
    }
  },
  "tx": {
    "to": "0x00000000000000000000000000000000000000A5",
    "data": "0xab33b1ec…",
    "value": "0"
  },
  "notices": []
}

Other fields omitted; ignore unknown fields.

Illustrative: the protocol fee here (10 bps) is a test value, not the live fee.

Check: referral = 2500 × 25 / 10000 = 6.25 DAI; net = 2500 − 2.5 − 6.25 = 2491.25; minOut = 2491.25 × 0.995.

Cross-chain: USDC on Base to USDC on Arbitrum

curl -s -H "Authorization: Bearer $CLEAVE_KEY" \
  'https://api.cleave.fi/partner/v1/quote?chainId=8453&toChainId=42161&tokenIn=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913&tokenOut=0xaf88d065e77c8cC2239327C5EDb3A432268e5831&amountIn=100000000&slippageBps=50&recipient=0x00000000000000000000000000000000c1ea7e01'
{
  "apiVersion": "1",
  "requestId": "bdeea5d5-e64b-4968-9320-d46ac3f50703",
  "kind": "cross",
  "flow": "bridge",
  "status": "ok",
  "chainId": 8453,
  "toChainId": 42161,
  "tokenIn": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  "tokenOut": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
  "amountIn": "100000000",
  "amountInUsd": "100.00",
  "amountOutUsd": "99.98",
  "quotedAt": 1791810633756,
  "ageMs": 3,
  "block": {
    "number": "52000000",
    "hash": "0xb1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1"
  },
  "rails": [
    {
      "rail": "<rail id>",
      "amountIn": "100000000",
      "amountOut": "99982161",
      "amountOutUsd": "99.98",
      "amountOutToken": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
      "amountOutKind": "exact_or_refund",
      "minimum": "99982161",
      "fees": {
        "protocol": { "bps": 0, "amount": "0", "block": "52000000" },
        "referral": { "bps": 0, "amount": "0" },
        "bridge": {
          "amount": "17839",
          "bps": 1.78,
          "lp": "0",
          "relayerGas": "7839",
          "relayerCapital": "10000"
        },
        "native": null
      },
      "eta": { "label": "~2 s", "seconds": 2, "conditional": false },
      "userTxs": 2,
      "limits": {
        "min": "500022",
        "max": "414501693156",
        "maxUnit": "bridged_usdc",
        "exceedsRailLimit": false
      },
      "execution": {
        "target": "0x00000000000000000000000000000000000000B5",
        "function": "bridgeUSDC(uint256,bytes32,uint256,address,uint256,uint256,address,uint16)",
        "value": "0",
        "approval": {
          "token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
          "spender": "0x00000000000000000000000000000000000000B5",
          "amount": "100000000"
        }
      },
      "tx": {
        "to": "0x00000000000000000000000000000000000000B5",
        "data": "0xea672b3e…",
        "value": "0"
      }
    }
  ],
  "rankedBy": "amountOut",
  "steps": 1,
  "secondStep": null,
  "ttlSec": 5,
  "notices": []
}

Other fields omitted; ignore unknown fields. The second route of rails[] is omitted too.

To execute: approve execution.approval if needed, then send tx with its value. Routes with a messaging fee carry it in tx.value (see fees.native).

Read your limits

curl -s -H "Authorization: Bearer $CLEAVE_KEY" https://api.cleave.fi/partner/v1/meta
{
  "chains": [
    {
      "chainId": 8453,
      "name": "Base",
      "quotesEnabled": true,
      "usdc": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "usdcDecimals": 6,
      "wrappedNative": "0x4200000000000000000000000000000000000006",
      "nativeSentinel": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"
    }
  ],
  "lanes": [
    {
      "chainId": 8453,
      "toChainId": 42161,
      "rails": [
        { "rail": "<rail id>", "displayName": "Express", "available": true },
        { "rail": "<rail id>", "displayName": "Instant", "available": true }
      ]
    }
  ],
  "partner": {
    "partnerId": "acme",
    "limit": { "perSec": 3, "windowSec": 1, "key": "partner", "sharedPerSec": 3 },
    "ipAllowlist": false
  }
}

Other fields omitted; ignore unknown fields. Only one chain and one lane are shown.

Fees in quotes

Every amount the user pays is a field in the quote.

FeeWhereNotes
Cleave protocol feefees.protocol {bps, amount, block}Read on-chain at quote time; capped at 10 bps (meta fees.protocolFeeCapBps)
Your referral feefees.referral {bps, amount}Set by you per request
Route fee (cross-chain)rails[].fees.bridge {amount, bps}Charged by the route; already out of amountOut
Messaging fee (cross-chain)rails[].fees.native {amount, value, bufferPct}Paid in the native coin through tx.value
Destination swap feedestination.fees.protocolToken-to-token only

Same-chain math:

  • referral = floor(gross × referralBps / 10000)
  • protocol = floor(gross × protocolBps / 10000)
  • net = gross − protocol − referral
  • minOut = floor(net × (10000 − slippageBps) / 10000)

Cross-chain: gross is amountIn for USDC input, or the source swap's USDC output.

Referral caps

QuoteMax referralBps
Same-chain swap100
Cross-chain10
Destination swap (token to token, on arrival)0
Wrap, unwrap0
  • Above the cap is a 400 (invalid_param, reason referral), never a silent clamp.
  • With referralBps above 0, the instant route is not quoted: it cannot pay a referral. Send referralBps=0 to get it.
  • The same caps are in meta partner.referral.maxBps.
  • Your fee is paid on-chain to referralRecipient in the user's transaction.

Notices

notices holds ready-to-show texts: {code, level, title, text}. Cross-chain rails carry their own rails[].notices.

levelMeaning
blockingNo transaction is returned. Show the text; do not build a tx
warningShow before signing; the user may continue
infoSecondary text

Branch on level, never on the wording. code is an opaque identifier: log it, but do not hard-code its values.

Errors

Errors use one shape, with HTTP status and retryable:

{
  "apiVersion": "1",
  "requestId": "75444d74-3598-4074-a019-eb28425d8cda",
  "error": {
    "code": "unsupported_lane",
    "reason": "no_fast_route",
    "message": "No fast route serves this transfer between these networks. Try another amount or token.",
    "detail": "<developer text>",
    "retryable": false
  }
}
  • code: stable, branch on it. reason: finer cause, when present.
  • message: end-user text. detail: developer text; it may change, never parse it.
  • retryAfterSec (and the Retry-After header) when you should wait.
  • Some errors add suggestion or notices.

A fee-on-transfer token can't be sent cross-chain directly. The answer is 400 unsupported_source, reason fee_on_transfer, with a two-step plan:

{
  "suggestion": {
    "kind": "two_step",
    "first": {
      "chainId": 8453,
      "tokenIn": "0x00000000000000000000000000000000000Fee01",
      "tokenOut": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
    },
    "then": {
      "chainId": 8453,
      "toChainId": 42161,
      "tokenIn": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "tokenOut": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
    }
  }
}
  • first: a same-chain swap to USDC on the source chain.
  • then: a cross-chain transfer of that USDC. then.tokenOut is always the destination USDC, never the token you asked for.
  • To end in another token, quote a same-chain swap on the destination once the USDC arrives.
HTTPcodeRetryWhat to do
400invalid_paramnoFix the request; detail names the parameter
400unsupported_chainnoUse a chain from meta
400unsupported_lanenoNo route for this pair; no_fast_route: try another amount or token
400unsupported_sourcenoToken can't be sent cross-chain (e.g. fee-on-transfer); see suggestion
400unsupported_destinationnoToken can't be received cross-chain directly (e.g. fee_on_transfer, token_unverifiable); see suggestion
400same_tokennoPick a different output token
401unauthorizednoKey missing, malformed, unknown, suspended or revoked (reason)
403forbidden_originnoRemove the Origin header
403forbidden_ipnoCall from an allowlisted address
404not_foundnoCheck the path
405method_not_allowednoUse GET/HEAD (POST for reports)
413payload_too_largenoReport body over 4,096 bytes
414uri_too_longnoURL over 2,048 bytes
415unsupported_media_typenoReport Content-Type must be JSON or text
429rate_limitedyesWait Retry-After
500internalyesRetry once; then report with requestId
502bad_upstream_datayesRetry
503upstream_unavailableyesWait Retry-After (2 s; 60 s when reason is route_paused)
503route_check_pendingyesRetry after Retry-After
503not_configurednoNot available on this chain
504upstream_timeoutyesRetry

On 401, the reason is one of missing_key, malformed_key, unknown_key, invalid_record, suspended, revoked.

Retryable or not

Trust retryable.

  • Transient (retryable: true): for example 503 upstream_unavailable with retryAfterSec, 503 route_check_pending, 504, 429. Wait Retry-After, then send the same request again.
  • Deterministic (retryable: false): the same request fails the same way. Change the amount, token or chain instead. This covers every 400 (for example no_fast_route or a fee-on-transfer token) and 503 not_configured.
  • Stop auto-retrying after a few retryable errors in a row, and show message.

A quote with status: "no_route" (HTTP 200) is not an error.

Cross-chain, when every fast route refuses the transfer (for example the amount is too low or too high), the answer is usually 400 no_fast_route. It can also be a status: "no_route" body, where unavailableRails[].reason says why each route was refused (for example amount_too_low or exceeds_rail_limit). Either way: change the amount or token.

Rate limits

LimitValue
Per partnerDefault 3 requests per second, sliding 1 s window; all your keys share it
Maximum per partner100 per second, on request
Shared partner capacity3 per second, shared by all partners, for quotes that need fresh chain work
Reports10 per 60 s per key
Key lookups not served from cache100 per 10 s per IP
  • What counts: every request that passes the key and IP checks, including 400s and HEAD.
  • Shared capacity is taken by cross-chain quotes, same-chain swap quotes not served from cache, and reports that need on-chain checks. Meta, health, wrap/unwrap and token endpoints never take it.
  • Your current values: meta partner.limit.perSec and sharedPerSec.

Headers:

HeaderValue
X-RateLimit-LimitYour perSec
X-RateLimit-RemainingRequests left in the current window
Retry-AfterSeconds to wait (on 429 and some 503s)

429 reasons:

reasonCause
partner_limitOver your per-second limit
aggregate_limitShared partner capacity busy
auth_attemptsToo many key lookups from your IP
(none)Report limit reached
  • After partner_limit, all requests are refused until Retry-After passes.
  • If our limiter is degraded, a fixed fallback of 30 requests per 10 s applies and the X-RateLimit-* headers are absent.
  • Quote on user action, not on a timer. Re-quote just before signing if the quote is older than ttlSec.

Supported chains and tokens

ChainchainIdNative coin
Ethereum1ETH
Optimism10ETH
BNB Chain56BNB
Polygon137POL
Base8453ETH
Arbitrum42161ETH

Cross-chain lanes exist between every pair of these chains.

Discover everything at run time from GET /partner/v1/meta:

  • Chains: chains[] with quotesEnabled.
  • Chain pairs: lanes[]; a pair missing there is not offered.
  • USDC per chain: chains[].usdc and chains[].usdcDecimals.
  • Contracts to approve or call: chains[].execution, chains[].contracts[], lanes[].router.
  • Tokens: the token endpoints (lists, search, lookup).

ttlSec is 5 s on most chains and 12 s on Ethereum.

Status tracking for cross-chain transfers

Every route in rails[] carries a tracking object. Use it; do not hard-code a tracking endpoint.

{
  "tracking": {
    "kind": "<tracking kind>",
    "idSource": "source transaction hash",
    "url": "<status link with {txHash}>"
  }
}
FieldMeaning
kindOpaque identifier of the tracking method. Never hard-code it
idSourceThe id to use: the source transaction hash for every partner route
urlStatus link. Replace {txHash} with the source transaction hash and poll it
  • Expected time is in rails[].eta.
  • If an instant route is not filled within 3 hours, the input is refunded to the sender on the source chain.

Problems

If a transfer looks stuck, send POST /partner/v1/report with the delayed-transfer type from meta reporting.types, txHash and toChainId.

FAQ

My user's source transaction failed. Is it safe to try again?

Yes. A source transaction that fails reverts as a whole: no tokens move. Get a fresh quote and send the new tx.

Smart-account wallets (for example EIP-7702 delegated accounts) may set their own gas limit instead of the one your app estimates. If that limit is too low, the transaction fails this way and a retry is safe.