Quote
POST /api/external/intents/quoteCreates an intent session from the user’s selection and returns a routed,
economically normalized plan. The request body is the user’s intent; there is
no intentSessionId before this call — Sweep creates it in the response.
All identifiers and amounts in the examples are synthetic except documented chain and token identifiers.
Request
A user swept 10 USDC on Base into USDC on Arbitrum:
{
"walletAddress": "0x1111111111111111111111111111111111111111",
"recipient": "0x1111111111111111111111111111111111111111",
"sources": [
{
"chainId": 8453,
"address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
"amount": "10000000",
"symbol": "USDC",
"decimals": 6,
"usdValue": "10.00"
}
],
"target": {
"chainId": 42161,
"address": "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
"targetToken": {
"address": "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
"symbol": "USDC",
"decimals": 6,
"priceUSD": "1"
}
},
"slippageBps": 50,
"allowPartial": false
}| Field | Required | Meaning |
|---|---|---|
walletAddress | Yes | EVM account that signs and executes the prepared source-chain actions |
recipient | Yes | Wallet that receives the target token |
sources[] | Yes | 1–50 selected assets; see field rules below |
target | Yes | Destination chain and token; target.address and target.targetToken.address must match |
slippageBps | No | Integer 1–10,000; omission means 50. 0 is rejected — omit the field instead |
allowPartial | No | Whether Sweep may return a plan covering fewer than all selected sources |
allowedProviders | No | Per-request provider subset; omit the field to allow all providers. See Providers |
maxWaitMs | No | Quote time budget, 1,000–18,000 ms |
feeBps | No | Integer integrator markup; must not exceed the partner-configured cap (300 bps default) |
callbackUrl | No | HTTP(S) webhook URL; must equal the partner-configured callback URL |
Source field rules:
amountis a positive base-10 raw-unit string. Convert display amounts without floating-point arithmetic:0.1USDC with 6 decimals is"100000".decimalsis required for every source.- Token identity is the canonical pair
(chainId, address); duplicates are rejected. Use the zero address for a native asset. usdValueis optional for a single-target intent, required positive for multi-target intents.- Exactly one of
targetortargets[]is present.
Providers
allowedProviders accepts the names relay, across, mayan, symbiosis
and fly. There is no all value: omitting the field means all providers
enabled for the partner.
- Only
relay,acrossandmayancan currently produce an external route:symbiosisis excluded from external traffic andflyis not evaluated by external route selection. []is valid and intentionally produces no provider candidates; an allowlist containing only excluded names behaves like[].- The partner-level provider policy configured during onboarding still applies on top of any per-request subset.
Response
{
"apiVersion": "1",
"quoteStatus": "ready",
"intentSessionId": "intent_00000000000000000001",
"expiresAt": "2030-01-01T00:00:30.000Z",
"coverageMode": "full",
"selectedPlan": {
"isFullPlan": true,
"coveredSliceCount": 1,
"totalSliceCount": 1,
"providerSet": ["relay"],
"legs": [
{
"outputTargetId": "output-1",
"routeId": "relay",
"provider": "relay",
"coveredSliceCount": 1,
"totalSliceCount": 1,
"requiredQuoteCount": 1,
"noopSliceCount": 0,
"isFullRoute": true,
"summary": {
"expectedOutputRaw": "9950000",
"minimumOutputRaw": "9900000",
"expectedOutputAmount": "9.95",
"minimumOutputAmount": "9.90",
"output": {
"chainId": 42161,
"tokenAddress": "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
"symbol": "USDC",
"decimals": 6
},
"totalOutputUsd": 9.95,
"totalFeeUsd": 0.03,
"totalLossUsd": 0.05,
"timeEstimate": 2
},
"failedSources": []
}
],
"summary": {
"totalOutputUsd": 9.95,
"totalFeeUsd": 0.03,
"totalLossUsd": 0.05,
"timeEstimate": 2
}
},
"failedSources": []
}Responses can contain additional informational fields. Tolerate unknown fields and do not build logic on undocumented provider data.
Quote states
quoteStatus | Meaning | Integrator behavior |
|---|---|---|
ready | A full executable plan is selected | Show preview; allow confirmation before expiresAt |
partial_only | Only part of the requested sources can be covered | Show failedSources; require an explicit new partial quote if the product allows it |
unavailable | No acceptable plan exists | Do not call prepare; show failedSources |
expired | Replay only: the stored quote’s expiresAt has passed | Do not call prepare; start a new quote with a new idempotency key |
coverageMode is full, partial or none.
Rendering economics
Render only Sweep-normalized fields from selectedPlan:
- expected receive:
legs[].summary.expectedOutputAmount - minimum receive:
legs[].summary.minimumOutputAmount - output value:
summary.totalOutputUsd - reported fees:
summary.totalFeeUsd - total value loss:
summary.totalLossUsd - ETA seconds:
summary.timeEstimate
expectedOutputAmount is already the expected net receive — never subtract fee
or loss from it again. Fee and loss are different concepts; do not add them
together. Do not read provider-specific raw fields from routeEvaluations or
rebuild provider economics.
Expiry and replay
expiresAt is the deadline for calling prepare. Do not prepare in the
background while the user is still reviewing.
Retrying an ambiguous quote failure with the same idempotency key does not
re-run routing: Sweep replays the stored session with its current status.
A replay landing after expiresAt returns quoteStatus: "expired" with the
original intentSessionId. Treat it as dead: prepare would fail with
409 intent_session_expired; build a new intent with a new idempotency
key — reusing the expired key replays the same dead session forever.
Code
import crypto from 'node:crypto';
import { SweepExternalClient } from 'sweep-external-sdk/server';
const client = new SweepExternalClient({
sweepApiUrl: process.env.SWEEP_API_URL,
partnerId: process.env.SWEEP_PARTNER_ID,
secret: process.env.SWEEP_PARTNER_SECRET,
});
const idempotencyKey = `quote-${crypto.randomUUID()}`;
// Persist { idempotencyKey, body } BEFORE transmission.
const result = await client.quote(quoteRequestBody, idempotencyKey);
if (result.reachable && result.ok && result.response.quoteStatus === 'ready') {
// Persist intentSessionId and expiresAt; render selectedPlan.
}Validation and route failure codes: Errors.