Skip to main content
Base URL: https://orchestration.flashnet.xyz OpenAPI:
  • Swagger UI: GET /docs
  • OpenAPI JSON: GET /openapi.json
The OpenAPI spec does not currently include GET /v1/orchestration/estimate. This product is named Orchestra. The API path prefix is still /v1/orchestration/*. Start here:

Authentication

Authenticated endpoints require an API key:
Authorization: Bearer fn_...
Use GET /v1/orchestration/estimate for unauthenticated price previews. POST /v1/orchestration/quote is the start of a submit-able flow and should be called with Authorization. Quotes created without Authorization (if accepted) cannot be submitted. A quote is bound to your partner account and can only be submitted by an API key for the same partner. If a quote uses affiliateId, Authorization is required because affiliate profiles are partner-scoped.

Idempotency

Partner-authenticated mutating endpoints require X-Idempotency-Key. Rules:
  • Key scope is (partnerId, endpoint, key).
  • Replaying the same request returns the stored response and sets X-Idempotency-Replayed: true.
  • Reusing a key with a different JSON body returns 409 idempotency_conflict.

Errors

Errors use a single envelope:
{
  "error": {
    "code": "string",
    "message": "string"
  }
}
Common error.code values:
  • unauthorized: Missing or invalid API key
  • auth_required: Authentication is required for this request
  • missing_idempotency_key: X-Idempotency-Key missing
  • idempotency_conflict: Same idempotency key reused with a different payload
  • unsupported_route: Route is not supported
  • unsupported_amount_mode: amountMode is not supported for the selected route
  • unsupported_fee_plan: appFees or affiliateId is not supported for the selected route or quote mode
  • invalid_request: Request body failed business validation
  • invalid_query: Invalid query parameters
  • invalid_address: Recipient or refund address is invalid
  • invalid_state: Endpoint was called in the wrong order state
  • conflict: State changed concurrently, retry the request
  • quote_expired: Quote has expired
  • not_found: Quote or order not found
  • amount_too_small: Swap input is below the Flashnet pool minimum
  • amount_too_large: Amount is too large for the selected route
  • rate_limited: Too many requests
  • flashnet_error: Swap simulation or execution failed
  • price_impact_too_high: Quote exceeds configured price impact constraints
  • internal_error: Unhandled error
Some malformed JSON or schema violations currently surface as internal_error. Treat it as a request bug and retry only after fixing the payload.

Chains, Assets, Amounts

Routes are expressed as (sourceChain, sourceAsset) -> (destinationChain, destinationAsset). Supported chains:
  • base
  • solana
  • spark
  • bitcoin
  • lightning
Supported assets:
  • USDC
  • USDB
  • BTC
  • ETH (Base)
  • SOL (Solana)
Amounts are integer strings in smallest units. Do not send floats.

Orchestra API

GET /v1/orchestration/estimate

Lightweight price preview. This does not create a quote and does not write to the database. Query parameters:
ParamRequiredNotes
sourceChainYesOne of the supported chains
sourceAssetYesOne of the supported assets
destinationChainYesOne of the supported chains
destinationAssetYesOne of the supported assets
amountYesInteger string
Response:
{
  "estimatedOut": "string",
  "feeAmount": "string",
  "feeBps": 5,
  "feeAsset": "USDC",
  "route": ["USDC", "USDB", "BTC"]
}
feeAsset is USDC for most routes and USDB for BTC -> USDB.

POST /v1/orchestration/quote

Request a durable quote with deposit instructions. Quotes expire 30 minutes after creation. Exact-in example (default mode):
{
  "sourceChain": "base",
  "sourceAsset": "USDC",
  "destinationChain": "spark",
  "destinationAsset": "BTC",
  "amount": "100000000",
  "recipientAddress": "spark1...",
  "slippageBps": 50,
  "zeroconfEnabled": true
}
Exact-out payment-intent example (bitcoin:BTC -> base|solana:USDC only):
{
  "sourceChain": "bitcoin",
  "sourceAsset": "BTC",
  "destinationChain": "base",
  "destinationAsset": "USDC",
  "amount": "100000000",
  "amountMode": "exact_out",
  "recipientAddress": "0xYourBaseUsdcAddress",
  "refundAddress": "bc1qyourrefundaddress...",
  "slippageBps": 0
}
Exact-in affiliate-fee example (spark:BTC -> solana:USDC):
{
  "sourceChain": "spark",
  "sourceAsset": "BTC",
  "destinationChain": "solana",
  "destinationAsset": "USDC",
  "amount": "100000",
  "recipientAddress": "So1RecipientAddress...",
  "appFees": [
    {
      "recipient": "So1AffiliateOne...",
      "fee": 100
    },
    {
      "recipient": "So1AffiliateTwo...",
      "fee": 50
    }
  ]
}
Exact-in affiliate-registry example (spark:BTC -> solana:USDC):
{
  "sourceChain": "spark",
  "sourceAsset": "BTC",
  "destinationChain": "solana",
  "destinationAsset": "USDC",
  "amount": "100000",
  "recipientAddress": "So1RecipientAddress...",
  "affiliateId": "swapkit"
}
Exact-in source-side affiliate-fee example (solana:SOL -> spark:BTC):
{
  "sourceChain": "solana",
  "sourceAsset": "SOL",
  "destinationChain": "spark",
  "destinationAsset": "BTC",
  "amount": "100000000",
  "recipientAddress": "spark1...",
  "appFees": [
    {
      "recipient": "Dtkxt55zEUDj6NTXGRtH8uQsACsTjoSVkXfYBEF8xkkT",
      "fee": 100
    }
  ]
}
Notes:
  • amountMode defaults to exact_in.
  • amountMode=exact_out is currently supported only for bitcoin:BTC -> (base|solana):USDC.
  • refundAddress is required when amountMode=exact_out.
  • If Authorization is included, send X-Idempotency-Key.
  • appFees and affiliateId are currently supported only for amountMode=exact_in and when sourceChain or destinationChain is base|solana.
  • Fee settlement chain is sourceChain when source is base|solana, otherwise destinationChain.
  • appFees max length is 16.
  • Each appFees[i].fee is fee bps (1..10000).
  • Sum of all app fee bps must be <= 10000.
  • affiliateId must match ^[a-z0-9][a-z0-9_-]{0,63}$ after trimming and lowercasing.
  • Use either appFees or affiliateId, not both.
  • affiliateId requires authenticated quote requests.
  • slippageBps defaults to 50 when omitted.
  • zeroconfEnabled is only used when sourceChain = bitcoin and amountMode = exact_in.
    • If omitted, it defaults to true.
    • If the deployment does not have ZeroConf configured, the engine falls back to on-chain confirmations.
  • For destinationChain = lightning, recipientAddress must be a 0-amount (amountless) BOLT11 invoice.
Affiliate fee models:
  • Inline recipients:
    • appFees: [{ recipient: string, fee: number }]
    • fee is fee bps per recipient (1..10000).
  • Registry by id:
    • affiliateId: string
    • profile is managed through PUT /v1/affiliates/:affiliateId
    • quote resolves recipient + fee bps from your partner-scoped profile
  • Settlement asset is always USDC.
  • Settlement chain is selected as:
    • sourceChain when source is base|solana
    • otherwise destinationChain (must be base|solana)
  • Execution point:
    • If source is base|solana, fees are deducted on the source-side USDC leg before bridge/swap continuation.
    • If source is not base|solana, fees are deducted on the destination-side USDC payout leg.
  • For source native routes like SOL -> BTC and ETH -> BTC, fees are still deducted on the source-side USDC leg after ingress conversion (SOL|ETH -> USDC).
  • Quote math order:
    • platform fee (feeAmount)
    • optional sweep fee (sweepFeeAmount, USDC-source routes only)
    • app fee allocation (appFeeAmount, appFees)
    • net amount proceeds to route execution
  • appFees recipients are validated against the settlement-chain address format.
  • For affiliateId, the profile must have a recipient address for the quote settlement chain (base and/or solana).
Response (exact-out sample):
{
  "quoteId": "q_...",
  "depositAddress": "bc1q...",
  "amountIn": "250000",
  "estimatedOut": "100000000",
  "feeAmount": "100000",
  "totalFeeAmount": "100000",
  "feeAsset": "USDC",
  "feeBps": 5,
  "route": ["BTC", "USDB", "USDC"],
  "expiresAt": "2026-02-04T02:00:00.000Z",
  "zeroconfEnabled": false,
  "amountMode": "exact_out",
  "targetAmountOut": "100000000",
  "requiredAmountIn": "250000",
  "maxAcceptedAmountIn": "250050",
  "inputBufferBps": 2,
  "priceLockMode": "approval_required",
  "lockedMinAmountOut": "99900000"
}
Response (exact-in with appFees sample):
{
  "quoteId": "q_...",
  "depositAddress": "spark1...",
  "amountIn": "100000",
  "estimatedOut": "24825000",
  "feeAmount": "25000",
  "totalFeeAmount": "62125",
  "appFeeAmount": "37125",
  "appFees": [
    {
      "affiliateId": "swapkit",
      "recipient": "So1AffiliateOne...",
      "feeBps": 50,
      "amount": "24750"
    },
    {
      "recipient": "So1AffiliateTwo...",
      "feeBps": 50,
      "amount": "12375"
    }
  ],
  "feeAsset": "USDC",
  "feeBps": 5,
  "route": ["BTC", "USDB", "USDC"],
  "expiresAt": "2026-02-04T02:00:00.000Z"
}
Response (exact-in source-side appFees sample):
{
  "quoteId": "q_...",
  "depositAddress": "So1DepositAddress...",
  "amountIn": "100000000",
  "estimatedOut": "11643",
  "feeAmount": "7893",
  "totalFeeAmount": "86749",
  "appFeeAmount": "78856",
  "appFees": [
    {
      "recipient": "Dtkxt55zEUDj6NTXGRtH8uQsACsTjoSVkXfYBEF8xkkT",
      "feeBps": 50,
      "amount": "78856"
    }
  ],
  "feeAsset": "USDC",
  "feeBps": 5,
  "route": ["SOL", "USDC", "USDB", "BTC"],
  "expiresAt": "2026-02-11T23:55:36.296Z",
  "priceLockMode": "strict_requote",
  "lockedMinAmountOut": "11584"
}
Field notes:
  • depositAddress depends on sourceChain:
    • base|solana: chain address that receives the source asset
    • spark: Spark address that receives BTC or USDB
    • bitcoin: Bitcoin L1 address
    • lightning: BOLT11 invoice to pay
  • totalFeeAmount is returned when feeAsset is USDC and equals feeAmount + appFeeAmount + sweepFeeAmount (when present).
  • sweepFeeAmount is returned on some USDC-source routes when a sweep fee is configured, and is included in totalFeeAmount.
  • appFeeAmount and appFees are returned when appFees or affiliateId was requested.
  • appFees[*].amount is quoted in settlement-chain USDC smallest units.
  • appFees[*].affiliateId is present when the quote used affiliateId.
  • zeroconfEnabled is only present for sourceChain = bitcoin.
  • lightningReceiveRequestId is only present for sourceChain = lightning quotes.
  • feeAsset is USDB for BTC -> USDB quotes and USDC otherwise.
  • targetAmountOut, requiredAmountIn, maxAcceptedAmountIn, and inputBufferBps are present for exact-out quotes.
  • inputBufferBps is currently 2 when slippageBps=0, otherwise 0.
  • priceLockMode and lockedMinAmountOut are present for routes where a Flashnet min-out lock is enforced.

POST /v1/orchestration/submit

Create an order from a quote after you have initiated the source deposit. Headers:
Authorization: Bearer fn_...
X-Idempotency-Key: <unique key>
Request body shape depends on the quote sourceChain. Base/Solana sources (sourceChain = base|solana):
{
  "quoteId": "q_...",
  "txHash": "0x...",
  "sourceAddress": "0x..."
}
sourceAddress is optional but recommended. It is required when depositing to a shared address. When present, deposit verification requires the sender to match. Spark sources (sourceChain = spark):
{
  "quoteId": "q_...",
  "sparkTxHash": "spark_transfer_id_or_token_tx_hash",
  "sourceSparkAddress": "spark1..."
}
sourceSparkAddress is optional but recommended. It is required when depositing to a shared Spark address. When present, deposit verification requires the sender to match. Bitcoin L1 sources (sourceChain = bitcoin):
{
  "quoteId": "q_...",
  "bitcoinTxid": "txid",
  "bitcoinVout": 0
}
Lightning sources (sourceChain = lightning):
{
  "quoteId": "q_...",
  "lightningReceiveRequestId": "string"
}
lightningReceiveRequestId can be omitted. When omitted, the API uses the value embedded in the quote. Response:
{
  "orderId": "ord_...",
  "status": "processing"
}
If an order already exists for the same (sourceChain, sourceTxHash[, sourceTxVout]), submit returns the existing { orderId, status }.

POST /v1/orchestration/reprice/approve

Approve a pending repricing request and resume execution. Headers:
Authorization: Bearer fn_...
X-Idempotency-Key: <unique key>
Request:
{
  "orderId": "ord_...",
  "approvedMinAmountOut": "99500000"
}
Rules:
  • Order must be in awaiting_approval.
  • approvedMinAmountOut must be less than or equal to the previous locked minimum in order.reprice.previousLockedMinAmountOut.
Response:
{
  "orderId": "ord_...",
  "status": "swapping"
}

POST /v1/orchestration/reprice/reject

Reject repricing and fail the order. Headers:
Authorization: Bearer fn_...
X-Idempotency-Key: <unique key>
Request:
{
  "orderId": "ord_...",
  "reason": "Optional partner reason"
}
Response:
{
  "orderId": "ord_...",
  "status": "failed"
}

POST /v1/orchestration/reprice/refund

Reject repricing and request an on-chain BTC refund. Headers:
Authorization: Bearer fn_...
X-Idempotency-Key: <unique key>
Request:
{
  "orderId": "ord_...",
  "reason": "Optional partner reason",
  "refundAddress": "bc1qoptionaloverride..."
}
Rules:
  • Order must be in awaiting_approval.
  • Refund is currently supported only for Bitcoin source orders.
  • refundAddress can come from the quote or this request; one must be present.
Response:
{
  "orderId": "ord_...",
  "status": "refunding"
}
The worker executes the refund asynchronously and finalizes the order as refunded or failed.

GET /v1/orchestration/status

Check an order’s current state. Authentication is optional:
  • Without Authorization: returns a redacted order record.
  • With Authorization: returns the full order record for your partner only.
Provide exactly one query parameter:
ParamNotes
idOrder id (ord_...)
quoteIdQuote id (q_...)
txHashSource transaction identifier (see order.sourceTxHash)
txHash lookup returns the most recently created order with that sourceTxHash. For Bitcoin L1 deposits where multiple orders can share the same txid, prefer id from webhooks for exact attribution. Possible order.status values:
  • processing
  • confirming
  • bridging
  • swapping
  • awaiting_approval
  • refunding
  • delivering
  • completed
  • failed
  • refunded
Response (authenticated):
{
  "order": {
    "id": "ord_...",
    "type": "order",
    "status": "awaiting_approval",
    "quoteId": "q_...",
    "sourceChain": "bitcoin",
    "sourceAsset": "BTC",
    "sourceAddress": null,
    "sourceTxHash": "<txid>",
    "sourceTxVout": 0,
    "depositAddress": "bc1q...",
    "destinationChain": "base",
    "destinationAsset": "USDC",
    "recipientAddress": "0x...",
    "amountIn": "250000",
    "amountOut": null,
    "feeBps": 5,
    "feeAmount": "100000",
    "slippageBps": 0,
    "errorCode": "reprice_approval_required",
    "errorMessage": "Received 249900 sats, requires at least 250000 sats",
    "createdAt": "2026-02-04T01:30:00.000Z",
    "updatedAt": "2026-02-04T01:30:10.000Z",
    "completedAt": null,
    "priceLock": {
      "version": 1,
      "mode": "approval_required",
      "amountIn": "250000",
      "quotedAmountOut": "100100000",
      "lockedMinAmountOut": "100000000",
      "slippageBps": 0,
      "createdAt": "2026-02-04T01:00:00.000Z",
      "expiresAt": "2026-02-04T01:30:00.000Z"
    },
    "reprice": {
      "version": 1,
      "status": "awaiting_approval",
      "previousLockedMinAmountOut": "100000000",
      "proposedMinAmountOut": "99800000",
      "latestExpectedAmountOut": "99800000",
      "requestedAt": "2026-02-04T01:30:10.000Z",
      "resolvedAt": null,
      "reason": "insufficient_input"
    },
    "paymentIntent": {
      "version": 1,
      "amountMode": "exact_out",
      "targetAmountOut": "100000000",
      "requiredAmountIn": "250000",
      "maxAcceptedAmountIn": "250050",
      "inputBufferBps": 2,
      "actualAmountIn": "249900",
      "refundAddress": "bc1q...",
      "exactOutExecution": "strict"
    },
    "feePlan": {
      "version": 1,
      "settlementChain": "solana",
      "settlementAsset": "USDC",
      "appFees": [
        {
          "affiliateId": "swapkit",
          "recipient": "So1AffiliateOne...",
          "feeBps": 50
        },
        {
          "recipient": "So1AffiliateTwo...",
          "feeBps": 50
        }
      ]
    },
    "feePayouts": {
      "version": 1,
      "entries": [
        {
          "idempotencyKey": "order:ord_...:full:appfee:0",
          "leg": "full",
          "chain": "solana",
          "role": "app_fee",
          "affiliateId": "swapkit",
          "recipient": "So1AffiliateOne...",
          "feeBps": 50,
          "amount": "24750",
          "txHash": "5kW...",
          "recordedAt": "2026-02-04T01:35:00.000Z"
        },
        {
          "idempotencyKey": "order:ord_...:full:payout",
          "leg": "full",
          "chain": "solana",
          "role": "recipient_payout",
          "recipient": "So1RecipientAddress...",
          "feeBps": null,
          "amount": "2475000",
          "txHash": "3hN...",
          "recordedAt": "2026-02-04T01:35:02.000Z"
        }
      ]
    }
  },
  "stages": [
    {
      "name": "deposit_confirmed",
      "status": "completed",
      "completedAt": "2026-02-04T01:30:05.000Z"
    },
    {
      "name": "reprice_requested",
      "status": "completed",
      "completedAt": "2026-02-04T01:30:10.000Z"
    }
  ]
}
With Authorization, the order object is the full public operation record. Without it, some fields are omitted. Depending on route and progress, the order can include:
  • flashnetRequestId when a Flashnet swap has executed
  • sparkTxHash when a Spark transfer, withdrawal, or Lightning action is created
  • swap metadata when a Flashnet swap leg has been recorded
  • priceLock, reprice, and paymentIntent metadata for locked/approval-required flows
  • feePlan when quote-level appFees or affiliateId was requested
  • feePayouts as affiliate and recipient payout legs are executed
  • feePayouts.entries[*].role is app_fee or recipient_payout
  • feePayouts.entries[*].affiliateId is present for app-fee entries derived from affiliateId
  • feePayouts.entries[*].leg is full, instant, or holdback for routes that execute in multiple legs

GET /v1/orchestration/history

List your orders filtered by recipientAddress. Headers:
Authorization: Bearer fn_...
Query parameters:
ParamRequiredNotes
addressYesRecipient address (Spark address, Bitcoin address, Lightning invoice, or Base/Solana address depending on the route)
statusNoExact match on the order status
limitNoDefault 50, max 200
offsetNoDefault 0
Response:
{
  "orders": [{ "id": "ord_...", "status": "completed" }]
}
History entries use the same order shape as GET /v1/orchestration/status, including optional swap, priceLock, reprice, and paymentIntent metadata when present.

Affiliates

Affiliate profiles let you register a partner-scoped app-fee recipient for Base and/or Solana under a stable affiliateId, then reference that id in quote requests.

PUT /v1/affiliates/:affiliateId

Create or update an affiliate profile. Headers:
Authorization: Bearer fn_...
X-Idempotency-Key: <unique key>
Request:
{
  "recipientAddressBase": "0xYourBaseUsdcAddress",
  "recipientAddressSolana": "So1YourSolanaUsdcAddress",
  "feeBps": 50
}
Legacy fields are supported for backward compatibility:
  • settlementChain + recipientAddress
Response:
{
  "affiliate": {
    "affiliateId": "swapkit",
    "settlementChain": "base",
    "settlementAsset": "USDC",
    "recipientAddress": "0xYourBaseUsdcAddress",
    "recipientAddressBase": "0xYourBaseUsdcAddress",
    "recipientAddressSolana": "So1YourSolanaUsdcAddress",
    "feeBps": 50,
    "enabled": true,
    "createdAt": "2026-02-12T18:00:00.000Z",
    "updatedAt": "2026-02-12T18:00:00.000Z"
  }
}

GET /v1/affiliates

List affiliate profiles for your partner. Headers:
Authorization: Bearer fn_...
Query params:
  • includeDisabled (true|false, default false)
  • limit (default 200, max 1000)
  • offset (default 0)

DELETE /v1/affiliates/:affiliateId

Disable an affiliate profile. Headers:
Authorization: Bearer fn_...
X-Idempotency-Key: <unique key>
Response:
{ "ok": true }

Partner Webhooks

Webhooks deliver order status changes for your partner account.

POST /v1/webhooks

Register an endpoint. Headers:
Authorization: Bearer fn_...
X-Idempotency-Key: <unique key>
{ "url": "https://example.com/flashnet/webhooks" }
Response:
{
  "webhookId": "wh_...",
  "secret": "hex string"
}
secret is only returned at creation time. Store it.

DELETE /v1/webhooks/:id

Disable an endpoint. Headers:
Authorization: Bearer fn_...
X-Idempotency-Key: <unique key>
Response:
{ "ok": true }
See Webhooks for signature verification, payload shape, and retry behavior.

Accumulation Addresses

Accumulation addresses are reusable deposit addresses on Base and Solana. Each deposit automatically creates an order that delivers BTC or USDB on Spark.

POST /v1/accumulation-addresses

Create an accumulation address. Headers:
Authorization: Bearer fn_...
X-Idempotency-Key: <unique key>
{
  "label": "optional",
  "destinationAsset": "BTC",
  "recipientSparkAddress": "spark1...",
  "feeBps": 5,
  "slippageBps": 50
}
Response:
{
  "accumulationAddressId": "acu_...",
  "destinationAsset": "BTC",
  "recipientSparkAddress": "spark1...",
  "feeBps": 5,
  "slippageBps": 50,
  "enabled": true,
  "depositAddresses": { "base": "0x...", "solana": "So1..." },
  "createdAt": "2026-02-04T01:00:00.000Z",
  "subscriptions": []
}

POST /v1/accumulation-addresses/sync

Re-sync all enabled accumulation addresses into the configured deposit detection webhooks. Headers:
Authorization: Bearer fn_...
X-Idempotency-Key: <unique key>

GET /v1/accumulation-addresses

List accumulation addresses.

GET /v1/accumulation-addresses/:id

Get a single accumulation address, including subscription status.

DELETE /v1/accumulation-addresses/:id

Disable an accumulation address. Headers:
Authorization: Bearer fn_...
X-Idempotency-Key: <unique key>

Liquidation Addresses

Liquidation addresses are reusable Bitcoin L1 deposit addresses. Each deposit automatically creates an order that delivers USDC on Base or Solana.

POST /v1/liquidation-addresses

Create a liquidation address. Headers:
Authorization: Bearer fn_...
X-Idempotency-Key: <unique key>
{
  "label": "optional",
  "destinationChain": "base",
  "destinationAddress": "0x...",
  "zeroconfEnabled": false,
  "slippageBps": 50,
  "feeBps": 5
}
Response:
{
  "liquidationAddressId": "liq_...",
  "sparkAddress": "spark1...",
  "l1DepositAddress": "bc1...",
  "destination": { "chain": "base", "address": "0x..." },
  "zeroconfEnabled": false,
  "feeBps": 5,
  "slippageBps": 50,
  "enabled": true,
  "createdAt": "2026-02-04T01:00:00.000Z"
}

GET /v1/liquidation-addresses

List enabled liquidation addresses.

GET /v1/liquidation-addresses/:id

Get a single liquidation address (enabled or disabled).

DELETE /v1/liquidation-addresses/:id

Disable a liquidation address. Headers:
Authorization: Bearer fn_...
X-Idempotency-Key: <unique key>

POST /v1/liquidation-addresses/sync-blockdaemon

Synchronize enabled liquidation addresses into Blockdaemon Streaming watchlists. Headers:
Authorization: Bearer fn_...
X-Idempotency-Key: <unique key>

Rate Limiting

Some public endpoints are IP rate-limited when rate limiting is enabled in the deployment:
  • GET /v1/orchestration/estimate: 120 requests per minute
  • POST /v1/orchestration/quote: 60 requests per minute
  • GET /v1/orchestration/status: 120 requests per minute
When enabled, responses include X-RateLimit-* headers and the API returns 429 rate_limited on exhaustion.