Skip to content
Logo

Quotes

Quotes price a transfer before you commit to it. Describe a source, destination, and amount, and Seismic fans out across every eligible rail and returns snapshots. Accept a snapshot to place an order.

Endpoints

Quote acceptance requirements

What accepting a quote requires is returned on each priced quote as acceptance_requirements. No additional request is needed to retrieve the evaluated requirements:

  • POST /quotes: use ?wait=1 to wait for priced quotes, and ?named=1 to receive only named options.
  • GET /quotes/:id: poll an asynchronous snapshot until its status is ready.

Read the field on the option you intend to accept in snapshot.quotes[]. Each option includes an object such as:

{
  "acceptance_requirements": {
    "files_required": true,
    "source_account_number_required": false
  }
}

files_required:

ValueBefore accepting
trueCollect at least one supporting document and include it in the accept request's files array.
falseNo supporting document is required by this quote's preflight check.
Absent on an older serverThe requirement is unknown; keep handling the typed document-required rejection.

source_account_number_required:

ValueBefore accepting
trueCollect the bank account number the payment will be funded from and send it as the accept request's source_account_number. The local network needs the originating account declared before funds are sent.
falseOmit source_account_number; accept rejects it with a 400.

Requirements depend on the priced quote, so check the selected option again when you request a new quote. These payment requirements are separate from the fields needed to register an external fiat account.

To collect documents before pricing, use GET /quotes/requirements. It returns the rule and any machine-readable condition without requiring a customer or recipient account.

Destination currencySupporting-document rule
INR, CNY, GHSRequired at every amount.
BRLRequired when the destination amount is at least 50,000 BRL, including exactly 50,000.
Other catalog currenciesNot required by the current payment policy.

For source-denominated requests, rates and fees determine the destination amount; check the priced quote before acceptance. The discovery response describes document policy, not whether a customer or corridor is eligible for payment.

See Compliance attachments for a preflight example and Document required for file constraints and the typed rejection. Additional documents may be requested during processing.

Directionality

The quote API is currency agnostic. On-ramps, off-ramps, and stablecoin swaps are all the same shape. You describe both sides of the trade and the amount to price.

Scenariosourcedestination
On-rampfiat (USD via ach / wire, …)stablecoin (USDC on base, …)
Off-rampstablecoinfiat (EUR via sepa, …)
Swapstablecoinstablecoin (often a different rail)

Not every currency/rail pair settles both ways. Several local payout rails are destination-only — INR via imps, for instance: payouts to an Indian bank account are supported, but sourcing funds from one (an India-based off-ramp) is coming soon. Quoting such a pair as the source returns CorridorUnsupported.

Read the restriction off the catalog rather than hard-coding it: each entry in GET /currencies carries a direction of source, destination, or both.

The source is one of three forms:

  • A bare virtual account ID string: "acc_1a2b…". Shorthand for an attached Account. Currency is pinned by the account row (every account has a single currency). A virtual account's rails describe how it can be funded, not how a payment drawn from its balance moves, so there's nothing to pin here: if it supports several, the server prices each and returns one quote per rail. See Narrowing an account's rails.
  • A virtual account ID with disambiguation: { account, rails?, schemas? }. Use this for fiat accounts that settle over multiple rails (e.g. a US bank reachable via both ach and wire). The fields you set must be in the account's rails[].
  • An inline source descriptor: { currency, rails?, schemas?, address? }. When there's no virtual account to debit, describe what the end customer will send instead. Accepting quotes from this form returns funding instructions.

The destination is one of two forms:

  • A bare account ID string: "acc_9c8d…". Shorthand for an attached Account. Currency is pinned by the account row (every account has a single currency). An account that settles over several rails is priced on each, one quote per rail — see Narrowing an account's rails. Note that unlike the corresponding form under source, this account ID may point to virtual or external accounts.
  • An account ID with disambiguation: { account, rails }. When you only care about a subset of the account's available rails[].

The source leg decides settlement timing. If a virtual account is provided, the account is debited immediately upon quote acceptance and the payment settles to the destination. Otherwise, funding instructions are returned upon quote acceptance and the payment only settles when funds arrive.

rails is the settlement path set and unifies fiat + crypto into one field. Always plural: each side of a quote can ask for multiple rails at once and the server fans out one quote per rail.

rails takes any rail name from GET /rails, for example ach, sepa or pix for fiat and base or ethereum for crypto. See Local rails for the full list.

schemas lets you name a group of rails: "us_bank" instead of ["ach", "wire", "swift"], "evm" instead of ["base", "ethereum", "polygon", "arbitrum"]. Server-side, the schema is expanded and unioned with rails to form the fan-out set. See GET /schemas.

Fan-out and rails

On a descriptor, rails and schemas are both optional:

  • Specify rails: ["ach"] to pin a single settlement path. Multiple entries (rails: ["ach", "wire"]) fan out across exactly those rails.
  • Specify schemas: ["us_bank"] to ask for every rail in that group. Schemas and rails get unioned: rails: ["base"] + schemas: ["us_bank"] would price [base, ach, wire, swift] (assuming us_bank → [ach, wire, swift]).
  • Omit both to compare every rail that corridor supports. The server returns one quote per rail.

When the leg is an account, the account's rails are used directly (no fan-out on that side), unless the account supports multiple rails, in which case omitting rails / schemas fans out across all of them. To pin a subset, pass either field with the rails / schemas you want.

Narrowing an account's rails

A single account can support multiple rails (a US bank account that accepts both ach and wire). Referencing it by bare id is never an error on either leg — the server prices every rail the account supports and returns one quote per rail. Currency is unambiguous from the account side too: every account has a single currency today.

Narrow the fan-out by replacing the bare-string form with { account, currency?, rails?, schemas? }:

{
  "amount": "200",
  "source": {
    "account": "acc_evm_wallet_1a2b",
    "currency": "USDC",
    "rails": ["base"]
  },
  "destination": "acc_usd_ach_9c8d"
}

You can also omit rails and let the server fan out across the account's rails[] for that currency, e.g. { account, currency: "USDC" } will return one quote per supported chain. Or pin a subset via schemas: ["evm"] to get every EVM chain at once.

On the response, every quote's in / out has rail populated (singular, each quote is for exactly one rail). The caller never has to disambiguate. For a crypto leg, out also carries the destination address once known.

amount is denominated in the currency of the leg named by amount_leg: the source currency unless you say otherwise. See Fixing an amount.

Fixing an amount

amount_leg picks which side of the trade amount fixes. The other side is what gets priced:

  • "source" (the default), "exact-in": you fix what the customer pays, and each quote's out.amount tells you what the destination receives. Quotes rank highest-payout-first.
  • "destination", "exact-out": you fix what the recipient receives, and each quote's in.amount tells you what the customer must pay to deliver exactly that. Quotes rank cheapest-pay-in-first.

Exact-out is the natural fit for payroll and invoicing flows, where the payout is the contract: the recipient must land exactly amount, and the pay-in absorbs the fees and rate.

{
  "amount": "50000",
  "amount_leg": "destination",
  "source": "acc_usdc_base_1a2b",
  "destination": "acc_inr_imps_9c8d"
}

Amounts and fees

  • in.amount: what the user pays. Fee-inclusive.
  • out.amount: what lands at the destination. Fees are already priced in. Nothing is added on top of in.amount to get here.
  • fees.items[]: a breakdown of the difference between in.amount × rate and out.amount, surfaced for transparency.

These invariants hold in both amount modes. amount_leg only decides which of the two amounts you fixed and which one Seismic priced.

Fee breakdown

Each fees block has:

FieldTypeMeaning
items[]Array of tagged fee lines (flat, percentage, network, other)One entry per distinct charge. When no itemised breakdown is available, a single flat line equal to total is emitted.
totalStringSum across items, denominated in currency.
currencyStringFee currency: typically the source currency (what the caller pays with).

items[] is a tagged discriminated union:

  • flat: { type, amount, currency, description? }. Fixed charge.
  • percentage: { type, bps, amount, currency, description? }. Basis points off the source amount minus any flat fees. Note that flat fees are deducted before percentage fees are applied.
  • network: { type, amount, currency, description? }. On-chain gas / settlement cost for crypto legs.
  • other: { type, amount, currency, description }. description is required so the caller can at least render a label.

Types

QuoteRequest

The body shape for POST /quotes.

FieldTypeRequiredNotes
end_customer_idstring (UUID)yesThe customer this quote is for. The caller is membership-checked against the customer's owning org.
end_customer_type"individual" | "business" | "org"no, but pass itDiscriminator for end_customer_id, see Customer types. When omitted, Seismic resolves the id automatically. Passing it catches a mistyped id immediately.
amountstringyesDenominated in the currency of the leg named by amount_leg: the source currency by default.
amount_leg"source" | "destination"noWhich leg amount fixes, see Fixing an amount. Defaults to "source" (exact-in). Pass "destination" to guarantee the payout amount (exact-out).
sourceSourceyesAccount-id string, or inline descriptor.
destinationDestinationyesAccount-id string, or { account, rails }, always a registered account.
developer_fee_bpsinteger (bps)noYour own markup on top of Seismic's pricing, in basis points (100 = 1%, max 10000). It is collected and sent to you. Surfaces as a Developer fee line on the quote's fees.items[]. See Add your own fee.

Customer types

end_customer_type tells Seismic which kind of record end_customer_id refers to:

  • individual: a person onboarded as a Customer with type: "individual".
  • business: a company onboarded as a Customer with type: "business".
  • org: your own organization. The value remains on the wire for compatibility, but payments can only be created for end customers: a quote or accept that names your org — via end_customer_type: "org", your org id as end_customer_id, or a leg funded through an organization-owned account — is rejected with OrgSelfTransactingDisabled (403). Create the payment for an end customer instead. Existing organization-owned accounts stay readable (details, balances, payment history).

end_customer_type may be omitted. Seismic resolves the id across the types above. Pass it anyway: it documents intent, and naming the wrong type fails immediately with the correct type in the error instead of quoting a record you didn't mean. For example, an org id sent as business returns a 400 (correcting the type won't make the quote succeed — an org as end customer is then rejected with OrgSelfTransactingDisabled):

{
  "ok": false,
  "error": {
    "code": "InvalidRequest",
    "violations": [
      {
        "field": "end_customer_type",
        "reason": "not_in_allowed_set",
        "description": "0d8a7f30-… is an org; specify \"end_customer_type\": \"org\"",
        "allowed": [
          "org"
        ]
      }
    ],
    "message": "0d8a7f30-… is an org; specify \"end_customer_type\": \"org\""
  }
}

Ids that don't exist, or that belong to a different org, return the uniform CustomerNotFound regardless of the type passed.

Source

The source-leg shape: a discriminated union, one of string, account-with-disambiguation, or inline-descriptor.

account-with-disambiguation:

FieldTypeRequiredNotes
accountstringyesAttached account UUID. Currency is pinned by the account row (single currency per account today).
railsstring[]noSubset of the account's rails[]. Omit to fan out across all of them.
schemasstring[]noNamed rail groups, see GET /schemas. Unioned with rails to form the fan-out set.

inline-descriptor:

FieldTypeRequiredNotes
currencystringyesISO-4217 ("USD") or stablecoin symbol ("USDC").
railsstring[]noPin a set of rails. Omit to fan out across every rail this corridor supports.
schemasstring[]noNamed rail groups: ["us_bank"], ["evm"], etc. Unioned with rails.
addressstringnoCrypto sources only. The sending wallet's address.

Destination

The destination-leg shape: a discriminated union, one of string or account-with-disambiguation. Unlike Source, there is no inline-descriptor form (a destination is always a registered account) and the object form takes only account and an optional rails. Any other field is rejected.

account-with-disambiguation:

FieldTypeRequiredNotes
accountstringyesAttached account UUID. Currency is read off the account row.
railsstring[]noSubset of the account's rails[]. Omit to fan out across all of them.

To pay out somewhere the customer has no account yet, register the account first via POST /customers/:id/accounts. This applies both to first and third party payments.

AsyncQuoteSnapshot

Returned by POST /quotes and GET /quotes/:id.

FieldTypeNotes
idstring (UUID)Snapshot id. Pass to /accept.
status"pending" | "ready" | "expired"pending only with ?wait=0. ready once every option has been priced.
quotesarray of QuoteRanked best-first. One entry per rail.
best_quote_idstring (optional)The server's recommended quote id. Matches quotes[0].id today.
requestobjectEcho of the original request, resolved: each leg comes back as { account_id?, currency, rails }, account_id present when the leg was account-backed (always, for destinations). named is true when the quote was created with ?named=1.
created_atstring (ISO-8601)

Quote

A single quote. Its acceptance_requirements tells you what to collect before accepting this option; it is separate from account-registration requirements.

FieldTypeNotes
idstringOpaque. Pass to /accept to pick this exact option.
inLegSource side. amount is fee-inclusive (what the user pays).
outLegDestination side. amount is what lands.
feesQuoteFeesItemized fees + total.
ratestringEffective out.amount / in.amount.
expires_atstring (ISO-8601, optional)When this specific quote stops being honored.
is_namedbooleantrue for a named payout: the payment reaches the beneficiary under the customer's own legal name. Always present, whether or not the quote was created with named.
acceptance_requirementsobjectWhat accepting this quote requires; see Quote acceptance requirements.
supported_expired_quote_behaviorsstring[]Expired-quote behaviors /accept will honor for this quote via its expired_quote_behavior field. Empty when only the default applies: the payment is refunded on expiry. A quote whose source leg is funded manually omits flexible_source_amount. See Expired-quote behavior.

Leg

The resolved-side shape: used by Quote.in / Quote.out here, and by RampOrder.source / destination on orders. The order is the settled view of the accepted quote, so the per-leg shape is canonical across both surfaces. Distinct from the request-side Source / Destination unions: by the time a quote comes back, every leg is resolved to one concrete corridor.

FieldTypeNotes
amountstringDenominated in currency. Fee-inclusive on in. What lands on out.
currencystringISO-4217 or stablecoin symbol.
railstringAlways present (singular): the one rail this option priced.
addressstring (optional)Crypto legs only. Echoed from the descriptor or from the resolved Account's wallet.
account_idstring (UUID)Always present: every leg resolves to a real account (caller-attached or server-side).

For the full account record, look up account_id via GET /accounts/:id.