Theme—
Fonts—
Mount—
Ready—
SEND
DocsPricingFeaturesComparisonsHelpSign inRequest access →

SEND / Docs

Swaps API

The multi-wallet market buy and sell endpoint, plus the swap wall -- the one instruction SEND holds until a price is reached.

← DocsAPI Reference12 min readUpdated August 22, 2026

Market Swaps, and the One Thing That Rests

Nearly everything SEND executes is a market swap: an immediate buy or sell, taken at whatever the DEX gives inside the slippage tolerance you set. The primary surface is the swap batch -- one request that queues that buy or sell across several of your wallets at once. Three routes cover it.

One thing does rest. The swap wall is a standing instruction that watches a price or market-cap target and schedules swaps of its own each time the target is crossed. It lives on /api/v1/walls, it has no interface in the web app, and it is described at the end of this article. Everything between here and there is the market path.

What genuinely does not exist: no order book, no limit order, no stop-loss, no take-profit, no trailing stop, and no time-in-force. A swap batch cannot be recalled once it is accepted -- there is no cancel-swap and no open-swaps endpoint, and the two reads below are how you find out what happened to one.

  • POST /api/v1/swaps -- creates a batch. Responds 201.
  • GET /api/v1/swaps?batchId=<uuid> -- batch status. This read is the authoritative one.
  • GET /api/v1/swaps/history?limit=<n> -- your manual swap ledger, newest first. limit defaults to 100 and is clamped to between 1 and 500.

Creating a Swap Batch

The request body names the project, the direction, the mint, and one allocation per participating wallet.

A success is 201 with { batchId, actions }, where each action is { id, walletId, amountIn, status: "pending", scheduledAt }. That is an acknowledgement that the actions were queued -- not that anything executed, and not a price.
  • projectId -- required, and must be a project you own. There is no swap-by-mint endpoint; the DEX and pool are resolved from that project's state.
  • actionType -- "buy" or "sell".
  • tokenMint -- the base58 mint address.
  • allocations -- an array of { walletId, amountIn }. One entry becomes one on-chain transaction.
  • amountIn -- a raw integer: lamports on a buy, raw token units on a sell. Decimal conversion is the caller's job.
  • slippageBps -- optional, defaults to 300 (3%), and must be between 1 and 5000.
  • dex and poolAddress -- optional routing overrides. Sending poolAddress without dex is rejected.

How Execution Actually Works

The POST only enqueues. Rows are written as pending manual swap actions and a wake signal is published so the swap executor claims them within milliseconds; if that signal cannot be delivered, the executor still picks them up on its five-second poll. There is no synchronous execution path and no endpoint that blocks until a swap lands.

Validation runs before anything is queued, and each failure rejects the entire batch. An empty allocations array, a tokenMint that is not valid base58, a slippageBps outside 1 to 5000, or any amountIn of zero all return 422 VALIDATION_ERROR with a message naming the offending field. A wallet that does not exist and a wallet owned by someone else both return 404 NOT_FOUND with identical wording, so the API never reveals that another account's wallet id is real.

Polling a Batch to Completion

GET /api/v1/swaps?batchId= returns exactly one row per submitted action: the terminal ledger row once the action finishes, otherwise its in-flight row. Both arms come from a single query with an anti-join, so a row can never appear twice and the row count never exceeds the number of actions you submitted.

  • Key rows by actionId, never by id. id is null while an action is in flight -- an explicit null, not a missing key -- and is filled in once the ledger row exists.
  • status is "executing" while in flight, then "success" or "failed". Those last two are the terminal set.
  • amountIn and amountOut are null on an in-flight row, deliberately. A fabricated zero on an unproven swap poisons every P&L figure downstream, so render the absence.
  • txHash appears on an in-flight row as soon as the submission is persisted, roughly half a second to a second after submit.
  • Stop polling only when every returned row is terminal AND you hold as many rows as you submitted actions. Both halves matter: with three actions submitted and one finished, "all returned rows are terminal" is vacuously true and a naive client stops with two swaps still running.

Realtime Versus the Poll

Two WebSocket topics mirror this pipeline, and neither replaces the poll. swapSubmissions carries the signature shortly after submit and is never a terminal state. swapResults carries the outcome. Both are best-effort and at-least-once: frames can duplicate, and a client that reloaded or backgrounded its tab can miss them entirely, which is exactly why the backend re-emits terminal results.

  • Treat GET /api/v1/swaps?batchId= as the source of truth and the frames as an accelerant.
  • Dedupe frames by actionId. A repeated terminal frame is expected behaviour, not a bug.
  • swapResults uses a different status vocabulary from the REST ledger -- "completed" and "failed" on the frame versus "success" and "failed" on the row. Do not compare the two directly.
  • Display amounts on a result frame are computed server-side and are absent when the token decimals could not be resolved. Say the metadata is unavailable rather than printing a number you inferred.

What the In-App Panel Does

The Buy/Sell panel on a project chart is one client of this API, and its defaults are its own rather than the API's. It defaults to 100 bps (1%) of slippage where the API defaults to 300, offers 50, 100 and 300 bps presets, and remembers your choice locally between sessions.

  • One execution creates one action per selected wallet, all sharing a single batchId.
  • Percent-of-balance and Max amounts stay disabled until every selected wallet has a fully-read balance, because a ceiling computed from a partial balance understates what you can actually spend.
  • POST /api/v1/swaps is on the write rate-limit tier: 60 requests per minute per user.

The Swap Wall: a Standing Price Trigger

A swap wall is a campaign rather than a single order. You give it a project, a direction, a target and a budget; a background worker re-reads it every ten seconds and, on every tick where the target is met, queues one more swap action into the same executor a manual batch uses. It is the only instruction on SEND that is held until a price is reached.

It has no interface. Nothing in the SEND web app creates, lists, starts or cancels a wall, so these routes are reachable only from your own integration -- with the same Authorization: Bearer header and the same response envelope as every other route on this API.

A wall you do not own is reported as 404 NOT_FOUND on every one of these routes, exactly as a foreign wallet is -- the API never confirms that another account's wall id is real.
  • POST /api/v1/walls -- creates a wall. Responds 202 Accepted with the stored campaign under data.campaign, at status "pending". Creating one arms nothing.
  • POST /api/v1/walls/{id}/start -- moves it to "executing", which is the only status the worker looks at. Starting an already-executing wall succeeds and changes nothing; starting a completed, partial, cancelled or failed one is 409 CONFLICT.
  • GET /api/v1/walls?page=&pageSize=&projectId=&status= -- your walls. page defaults to 1 and pageSize to 20; page must be at least 1 and pageSize between 1 and 100, and an out-of-range value is 422.
  • GET /api/v1/walls/{id} -- one wall, including its live amountExecuted and currentLadderStep.
  • POST /api/v1/walls/{id}/cancel -- stops the wall and cancels its still-pending swap actions. Cancelling an already-cancelled wall succeeds and changes nothing; a completed, partial or failed one is 409 CONFLICT.
  • GET /api/v1/walls/{id}/transactions -- the wall's own ledger, one row per swap it fired, each carrying side, amountIn, amountOut, executionPrice, ladderStep, txHash and a status of "pending", "confirmed" or "failed".

How a Wall Decides and Fires

The two directions are mirror images. A sell wall fires while the market is at or above its target; a buy wall fires while it is at or below. wallType picks one, and there is no third value.

triggerMode is "price" or "market_cap", defaulting to "price" when omitted. Price mode reads targetPrice as wSOL per token. Market-cap mode reads targetMarketCap, which is that price multiplied by the token's total supply, and omitting targetMarketCap in that mode is rejected 400 BAD_REQUEST -- note the code, because every other rejection on this route is 422 VALIDATION_ERROR.

amountMode decides the size of each fire. "fixed" walks a ladder: it starts at baseOrderSize and compounds by orderSizeMultiplier percent for each step already taken, where orderSizeMultiplier defaults to 10, and both baseOrderSize and totalAmount are required in that mode. There is no per-order ceiling, so the only thing bounding a compounding ladder is the budget left in totalAmount. "price_impact" instead sizes each swap from live pool reserves, aiming to move the price to the target in one go.

Each fire queues exactly one pending swap action, against one wallet drawn from the project's wallet set, scheduled 1 to 5 seconds ahead. The wall's spend meter, amountExecuted, advances only once that swap is proven to have landed -- a submission that reverts on chain moves nothing and leaves the budget untouched.

Four accepted fields reach nothing. walletSetId is ignored: a wall always trades from the wallet set recorded on its project, so a wall for a project with no wallet set, or with no active wallets in it, fires nothing. orderCount is validated as at least 1 and then discarded -- the real number of orders is emergent from the budget and the ladder. priorityFeeLamports and jitoTipLamports have no plumbing behind them at all. Do not build against any of the four, and read GET /api/v1/walls/{id} rather than assuming your request shaped the campaign.
←Previous
Wallets API
→Next
WebSocket Events
Was this article helpful?

SEND

Execution infrastructure for Solana. Coordinated execution across thousands of wallets, with a footprint that reads as thousands of strangers.

TwitterDiscordGitHub

Product

FeaturesPricingComparisonsChangelogDocumentation

Company

AboutAmbassador ProgramContact

Legal

PrivacyTerms

Support

Help CenterDiscordTwitter

© SEND 2026 — All rights reserved

Powered by ChainKit