Skip to content

Zaps

Zaps are the simplest integration option. They facilitate market and limit orders across nearly every digital asset with a few API calls. Trades terminate at the end user's registered destination — either a deposit address at a partner custodial platform or a self-custody address.

There is no quote/accept cycle. Market makers compete in an auction and the best is auto-selected. Set min_amount_out_atoms or min_amount_out_ui as a price floor. Set the floor to 0 for pure market behavior. If no market maker can meet your configured floor, the zap fails and funds remain as an open credit.

You may optionally provide expires_at_ms to bound how long the zap remains eligible for execution. If omitted, the zap has no API-level expiry and remains active until it reaches a terminal state (settled or failed). In this default mode, the zap can still be funded later (including hours later) and will be evaluated against then-current market conditions plus your configured floor.

The flow:

  1. Register a destination for the output asset
  2. Create a zap — Routes returns a deposit address
  3. Send funds to the deposit address (or provide a credit_id from an existing deposit)
  4. Routes executes the swap and delivers the output to the registered destination

Deposit-funded matching behavior (credit_id omitted)

For zaps created without credit_id, Routes returns a deposit address and executes only after a deposit is verified.

  • If the deposited asset matches from_asset_key/from_symbol, the zap executes using the observed deposited amount (not necessarily the originally requested amount).
  • If the deposited amount is smaller or larger than requested, output adjusts to market pricing and fees for the observed input amount.
  • If the observed amount cannot be executed (for example outside current executable size bounds or below your floor), the zap fails with existing zap failure codes.
  • If the deposited asset does not match from_asset_key/from_symbol, the zap fails with failure_code=from_asset_mismatch. The deposit is still processed normally for the deposited asset and a standard deposit credit is created.
  • Zap deposit addresses are single-use. In your end-user UX and communications, instruct customers to send only one deposit per zap address. If additional deposits do arrive, Routes makes a best effort to auto-convert and auto-forward, but outcome is not guaranteed under the original zap terms.

For reconciliation:

  • amount_in_atoms / amount_in_ui on create request is the requested amount.
  • Execution uses the observed deposited amount once deposit verification completes.
  • On deposit-funded zaps, response payloads include requested_amount_in_atoms and (after verification) observed_amount_in_atoms.

Public recovery integration (optional)

For integrations that cannot surface arbitrary assets in product UX, Routes supports a public recovery claim flow using email OTP.

  • When recovery is enabled for your organization, failed zaps that leave an open credit include recovery_id and credit_id on the failed zap payload.
  • Integrator activates recovery using POST /recoveries/{recovery_id}/activate. Routes sends an OTP to the end user's email.
  • The end user claims the credit using POST /public/recover-funds with the OTP and a destination address.
  • Requires an email on the account — set via PATCH /accounts/{account_id}.
  • If no email is configured, the integrator can use POST /cancels to move the credit on the user's behalf.

Discoverability:

  • If eligible failed zaps never include recovery_id + credit_id, recovery is not enabled for your organization.

POST /zaps

Use this to create a zap.

Auth:

  • Bearer token required
  • Idempotency-Key (required)

Parameters

Parameter Type Required Description
account_id string Yes Account identifier
from_asset_key string One of from_asset_key or from_symbol required Source asset by key
from_symbol string One of from_asset_key or from_symbol required Source asset by partner symbol
to_asset_key string One of to_asset_key or to_symbol required Destination asset by key
to_symbol string One of to_asset_key or to_symbol required Destination asset by partner symbol
amount_in_atoms string One of amount_in_atoms or amount_in_ui required Input amount in atomic units
amount_in_ui string One of amount_in_atoms or amount_in_ui required Input amount in UI units. Enforce decimal precision from asset decimals value
min_amount_out_atoms string One of min_amount_out_atoms or min_amount_out_ui required Minimum acceptable output in atoms. Set "0" for no floor (pure market behavior).
min_amount_out_ui string One of min_amount_out_atoms or min_amount_out_ui required Minimum acceptable output in UI units. Set "0" for no floor (pure market behavior).
destination_id string Yes Pre-registered destination for the output asset
credit_id string No Credit to source funds from. If omitted, the zap enters awaiting_deposit and returns a deposit address.
expires_at_ms integer No Optional zap expiry timestamp (milliseconds since epoch). If reached before execution begins, zap fails with failure_code=zap_expired.
client_zap_id string No Client-provided zap identifier for correlation
integrator_fee_bps string No Integrator markup in basis points (supports decimals, e.g. "2.5"), added on top of platform fee. Overrides organization default. See Fees.

side is always exact_in for zaps — it is not a parameter.

Validation rules:

  • Exactly one of from_asset_key or from_symbol must be provided.
  • Exactly one of to_asset_key or to_symbol must be provided.
  • Exactly one of amount_in_atoms or amount_in_ui must be provided.
  • Exactly one of min_amount_out_atoms or min_amount_out_ui must be provided.
  • Providing both fields in a one-of pair returns 400 invalid_parameter.

HTTP response behavior:

  • 200 zap accepted; response includes a zap resource in executing or awaiting_deposit.
  • 400 validation error (parameter_missing, invalid_parameter, invalid_enum, ui_precision_exceeded).
  • 401 missing or invalid bearer auth.
  • 403 policy block (for example forbidden_asset, destination_suspended).
  • 404 unknown resource (for example asset_not_supported).
  • 409 idempotency conflict (idempotency_key_reuse).
  • 429 rate limited.

Error cases:

Code HTTP Description
parameter_missing 400 Required field missing
invalid_parameter 400 Malformed or conflicting fields
invalid_enum 400 Enum field has unsupported value
ui_precision_exceeded 400 *_ui precision exceeds asset decimals
invalid_asset_key 400 asset_key format is invalid
invalid_symbol 400 Symbol format is invalid
forbidden_asset 403 Asset not permitted for your organization
asset_not_supported 404 Asset is not supported by Routes
destination_suspended 403 Destination is suspended by screening
idempotency_key_reuse 409 Same key reused with different payload
rate_limited 429 Request throttled

Example request:

Request
curl -X POST https://routes.srcry.xyz/v1/zaps \
  -H "Authorization: Bearer $ROUTES_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: unique-key-123" \
  -d '{
  "account_id": "acct_01J...",
  "from_asset_key": "native.btc",
  "to_asset_key": "spl.solana:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
  "amount_in_atoms": "100000",
  "min_amount_out_atoms": "5000000",
  "destination_id": "dest_01J..."
}'
Request
import httpx

response = httpx.post(
    "https://routes.srcry.xyz/v1/zaps",
    headers={
        "Authorization": f"Bearer {api_key}",
        "Idempotency-Key": "unique-key-123",
    },
    json={
        "account_id": "acct_01J...",
        "from_asset_key": "native.btc",
        "to_asset_key": "spl.solana:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
        "amount_in_atoms": "100000",
        "min_amount_out_atoms": "5000000",
        "destination_id": "dest_01J..."
    },
)
Request
const response = await fetch("https://routes.srcry.xyz/v1/zaps", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${apiKey}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "unique-key-123",
  },
  body: JSON.stringify({
    account_id: "acct_01J...",
    from_asset_key: "native.btc",
    to_asset_key: "spl.solana:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
    amount_in_atoms: "100000",
    min_amount_out_atoms: "5000000",
    destination_id: "dest_01J..."
  }),
});
Request body
{
  "account_id": "acct_01J...",
  "from_asset_key": "native.btc",
  "to_asset_key": "spl.solana:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
  "amount_in_atoms": "100000",
  "min_amount_out_atoms": "5000000",
  "destination_id": "dest_01J..."
}

Execution behavior after create:

  • If credit_id is provided (pre-deposited funds): zap begins executing immediately
  • If no credit_id: response includes a deposit address. After deposit verification:
  • matching deposit asset -> zap executes using observed deposited amount
  • non-matching deposit asset -> zap fails with from_asset_mismatch; deposit credit is still created for the deposited asset
  • If expires_at_ms is provided and reached before execution begins: zap fails with failure_code=zap_expired

Example response (with credit_id):

{
  "request_id": "req_01J...",
  "zap": {
    "zap_id": "zap_01J...",
    "status": "executing",
    "client_zap_id": null,
    "requested_amount_in_atoms": "100000",
    "poll_after_ms": 5000
  }
}

Example response (without credit_id):

{
  "request_id": "req_01J...",
  "zap": {
    "zap_id": "zap_01J...",
    "status": "awaiting_deposit",
    "requested_amount_in_atoms": "100000",
    "deposit_address": {
      "deposit_address_id": "depaddr_01J...",
      "asset_key": "native.btc",
      "address": "bc1q...",
      "memo": null,
      "tag": null
    },
    "poll_after_ms": 15000
  }
}

Zap deposit addresses are single-use. Do not reuse a deposit address from a previous zap — always use the address returned by the current POST /zaps response.

Execution and settlement timing vary by route and chain conditions. There is no fixed latency SLA. Use poll_after_ms and webhooks as the canonical timing model.

Response fields

Field Type Description
request_id string Request identifier
zap.zap_id string Zap identifier
zap.status string Current zap status
zap.client_zap_id string Client-provided identifier
zap.expires_at_ms integer Zap expiry timestamp when provided
zap.requested_amount_in_atoms string Requested zap input amount from create request. Present for all zap resources.
zap.observed_amount_in_atoms string Observed deposited amount used for execution. Present only after deposit verification for deposit-funded zaps; absent for credit-funded zaps and pre-verification awaiting_deposit states.
zap.deposit_address object Deposit address details (when awaiting deposit)
zap.failure_code string Failure reason (terminal only): min_amount_out_not_met, execution_failed, settlement_failed, settlement_timeout, settlement_orphaned, deposit_insufficient_confirmations, deposit_flagged, from_asset_mismatch, zap_expired
zap.recovery.recovery_id string Present when recovery is enabled and failed zap leaves an open credit
zap.recovery.credit_id string Present when recovery is enabled and failed zap leaves an open credit
zap.settlement.out_tx_hash string Transaction hash for account → MM leg
zap.settlement.out_confirmations integer Current confirmation count for out leg
zap.settlement.out_finalized boolean Whether out leg has reached chain finality
zap.settlement.in_tx_hash string Transaction hash for MM → account leg
zap.settlement.in_confirmations integer Current confirmation count for in leg
zap.settlement.in_finalized boolean Whether in leg has reached chain finality
zap.fees.platform_bps string Platform fee in basis points
zap.fees.integrator_bps string Integrator fee in basis points
zap.fees.total_bps string Combined fee in basis points
zap.fees.platform_atoms string Platform fee in output asset atoms
zap.fees.integrator_atoms string Integrator fee in output asset atoms
zap.fees.total_atoms string Total fee in output asset atoms
zap.poll_after_ms integer Milliseconds until next poll

GET /zaps

Use this to list zaps. Returns zaps across all accounts in your organization, ordered by creation time descending.

Auth:

  • Bearer token required

HTTP response behavior:

  • 200 list response.
  • 401 missing or invalid bearer auth.
  • 429 rate limited.

Error cases:

Code HTTP Description
invalid_enum 400 status filter is not a supported value
rate_limited 429 Request throttled

Query parameters

Parameter Type Required Description
account_id string No Filter to zaps for a specific account
status string No Filter by status: awaiting_deposit, executing, filled, pending_settle, settled, failed
limit integer No Maximum zaps to return (default 50, max 100)
cursor string No Pagination cursor from previous response

Example response:

{
  "request_id": "req_01J...",
  "zaps": [
    {
      "zap_id": "zap_01J...",
      "account_id": "acct_01J...",
      "status": "settled",
      "client_zap_id": null,
      "requested_amount_in_atoms": "100000",
      "from_asset_key": "native.btc",
      "to_asset_key": "spl.solana:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
      "created_at_ms": 1730000020000
    },
    {
      "zap_id": "zap_01J...",
      "account_id": "acct_01J...",
      "status": "awaiting_deposit",
      "client_zap_id": "myzap_789",
      "requested_amount_in_atoms": "50000",
      "from_asset_key": "native.btc",
      "to_asset_key": "erc20.evm:1_0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
      "created_at_ms": 1730000010000
    }
  ],
  "has_more": false,
  "next_cursor": null
}

GET /zaps/{zap_id}

Use this to track zap execution. The polling and webhook patterns are the same as for trades.

Auth:

  • Bearer token required

HTTP response behavior:

  • 200 resource response.
  • 401 missing or invalid bearer auth.
  • 404 zap not found (or not in your organization).
  • 429 rate limited.

Error cases:

Code HTTP Description
rate_limited 429 Request throttled

Non-terminal states:

  • awaiting_deposit — waiting for funds to arrive at deposit address
  • executing — solver/MM is executing the swap
  • filled — MM has committed to filling the order; funds credited as pending in ledger
  • pending_settle — MM has initiated on-chain settlement

Terminal states:

  • settled — settlement confirmed on-chain
  • failed — execution or settlement did not complete

On failure, the failure_code field indicates the reason. Zap failures should be rare. When a zap fails, the source credit is not consumed — retry with a new zap, fall back to an RFQ-based trade, or cancel the credit.

Recoverable — retry automatically:

  • min_amount_out_not_met — no solver could meet the price floor. Retry with a wider min_amount_out_atoms or min_amount_out_ui (including 0 for no floor), or wait for better market conditions.
  • execution_failed — swap execution did not complete. Retry the zap.
  • deposit_insufficient_confirmations — counterparty requires more confirmations for this deposit. Wait for additional confirmations and retry.
  • zap_expired — caller-provided zap expiry was reached before execution began. Create a new zap with a new expiry (or omit expiry).

Requires user/integrator action:

  • from_asset_mismatch — deposit asset does not match the zap's requested source asset. The zap does not execute. The deposit is still credited under the deposited asset. If recovery is enabled, failed payloads include recovery_id + credit_id; activate with POST /recoveries/{recovery_id}/activate (sends OTP to user's email), then end user claims via POST /public/recover-funds. If no email is set, integrator uses POST /cancels. If recovery is not enabled, create a new zap using the deposited asset or cancel that credit.

Non-recoverable — escalate for manual review:

  • settlement_failed — settlement transaction failed on-chain
  • settlement_timeout — settlement not confirmed within expected window
  • settlement_orphaned — a previously-confirmed settlement tx was dropped (reorg)
  • deposit_flagged — the deposit's source address failed transaction monitoring screening. No credit was created. See Transaction Monitoring — Flagged deposit recovery.

Zap lifecycle:

  ┌───────────────────┐
  │ awaiting_deposit  │
  └─────────┬─────────┘
            │
    ┌───────▼───────┐
    │   executing   │
    └───────┬───────┘
            │
    ┌───────▼───────┐
    │    filled     │
    └───────┬───────┘
            │
  ┌─────────▼──────────┐
  │  pending_settle    │
  └────┬────────────┬──┘
       │            │
  ┌────▼─────┐ ┌────▼────┐
  │ settled  │ │ failed  │
  └──────────┘ └─────────┘

Note: failed is reachable from any non-terminal state.

Non-terminal example:

{
  "request_id": "req_01J...",
  "zap": {
    "zap_id": "zap_01J...",
    "status": "pending_settle",
    "poll_after_ms": 15000
  }
}

Deposit-funded example (after deposit verification):

{
  "request_id": "req_01J...",
  "zap": {
    "zap_id": "zap_01J...",
    "status": "executing",
    "requested_amount_in_atoms": "100000",
    "observed_amount_in_atoms": "98500",
    "poll_after_ms": 5000
  }
}

Terminal example (settled):

{
  "request_id": "req_01J...",
  "zap": {
    "zap_id": "zap_01J...",
    "status": "settled",
    "settlement": {
      "out_tx_hash": "0xabc123...",
      "out_confirmations": 64,
      "out_finalized": true,
      "in_tx_hash": "0xdef456...",
      "in_confirmations": 42,
      "in_finalized": true
    },
    "fees": {
      "platform_bps": "5",
      "integrator_bps": "10",
      "total_bps": "15",
      "platform_atoms": "50000",
      "integrator_atoms": "100000",
      "total_atoms": "150000"
    }
  }
}

Terminal example (failed):

{
  "request_id": "req_01J...",
  "zap": {
    "zap_id": "zap_01J...",
    "status": "failed",
    "failure_code": "from_asset_mismatch",
    "recovery": {
      "recovery_id": "rcv_01J...",
      "credit_id": "cred_01J..."
    }
  }
}