Two things are genuinely real-time, and one is not — and it is worth knowing which is which before you design around them. Quotes stream over server-sent events as each provider answers. The cross-provider rate feed regenerates every few seconds. Order status is polled: there are no webhooks yet, so the push layer your users see is one you run.
/v1/partner/quotes-sse— first offer in 200–400 ms, stream ends by itself./v1/export/rates.xml— the best executable rate per direction, keyless, always fresh.- Orders: poll every 5–10 seconds, or ask for one synchronous refresh.
- Price alerts are built on the rate feed; status alerts on the poll loop.
Three sources, three cadences
| Source | Cadence | Key | Good for |
|---|---|---|---|
GET /v1/partner/quotes-sse | Push, per request. Events: offer, private_route, provider_error, ping, done. Ends at timeout_ms — default and maximum 5000. | yes | A live calculator, a checkout, anything where a user is waiting. |
GET /v1/export/rates.xml | Regenerated every few seconds and cached. | no | Rate boards, monitors, price alerts, "1 BTC = …" widgets. |
GET /v1/partner/orders/{id} | Poll every 5–10 s; our own poller refreshes each open order every 10 s. | yes | Status timelines, notifications, reconciliation. |
POST /v1/partner/orders/{id}/refresh | Synchronous, at most once every 5 s. | yes | "I've sent it" — one immediate answer, not a faster loop. |
The rate feed is an executable rate, not an index. Each
<item> is the best cross-provider rate for a direction, with
minamount, maxamount and whether it is
floating or fixed. An alert built on it fires on a
number the user can actually trade at — which is not what a market-data
ticker gives you.
Consuming the stream
const res = await fetch(sseURL, {
headers: { "X-API-Key": key, Accept: "text/event-stream" },
});
const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
let buf = "";
for (;;) {
const { value, done } = await reader.read();
if (done) break;
buf += value;
let i;
while ((i = buf.indexOf("\n\n")) !== -1) { // one SSE frame
const frame = buf.slice(0, i); buf = buf.slice(i + 2);
const event = /^event:\s*(.+)$/m.exec(frame)?.[1];
const data = JSON.parse(/^data:\s*(.+)$/m.exec(frame)?.[1] ?? "null");
if (event === "offer") render(data); // draw it the moment it lands
if (event === "provider_error") log(data); // log, never show
if (event === "done") rerank(data.deviations); // final ranking of what you drew
}
}
Building price alerts
-
Poll the feed, not the quote endpoint
/v1/export/rates.xmlis keyless, cached and covers every direction at once — one request serves every alert you hold. Quoting each user's pair separately is what gets a partner throttled by providers. -
Compare against the direction, with its limits
An alert on BTC → USDTTRC20 is only meaningful within
minamountandmaxamount. Store both with the threshold so you never notify someone about a rate they cannot take. -
Debounce before you notify
Rates wobble. Require the threshold to hold for two consecutive reads, and re-arm an alert only after the rate has moved back by a margin — otherwise a flat market sends a hundred notifications.
-
Turn the alert into an offer
The moment the user acts, quote the pair properly with
/quotesor/quotes-sse: the feed says what was available a few seconds ago, and only an offer is executable — for five minutes.
Status alerts work the same way in reverse: one poll loop per open order, your own fan-out to whatever your users listen on — a websocket, a webhook of your own, a Telegram message.
What is not real-time
| Not available | What to do instead |
|---|---|
| Webhooks for order status | Poll every 5–10 seconds until terminal, and push to your users yourself. If webhooks would change your integration, say so — it is prioritised by demand. |
| A persistent websocket | The streaming endpoints are SSE and scoped to one quote request; they end by themselves within five seconds. |
| Historical rates or candles | The feed is the current best rate. Keep your own history if you need charts. |
| Price alerts as a hosted service | The thresholds, the storage and the notifications are yours; the API supplies the number they are compared against. |
| Per-provider live rate streams | Offers arrive per provider inside a quote stream, but there is no standing per-provider feed. |
Who runs on this
- Monitors and aggregators reading
rates.xmlin the format they already parse. - Alert bots — Telegram, Discord, email — firing on an executable rate rather than an exchange ticker.
- B2B partners with dashboards, streaming their own customers' order statuses out of one poll loop.
- Wallets and calculators where the number on screen has to move while the user thinks.
Questions
Does SwapZilla send webhooks when an order changes status?
Not yet. Order status is read by polling GET /v1/partner/orders/{id} every 5–10 seconds until a terminal status, and our own poller already refreshes each open order every ten seconds. Webhooks are prioritised by partner demand, so an integration that needs them is worth telling us about.
Is the streaming endpoint a websocket?
No, it is server-sent events over an ordinary HTTP request, and it is scoped to a single quote: offers arrive as providers answer, a ping keeps proxies awake, and the stream closes itself at timeout_ms — 5000 ms by default and by maximum.
How often does the rate feed change?
/v1/export/rates.xml is regenerated every few seconds and cached, so polling it faster than that gains nothing. It publishes the best cross-provider rate per direction with the minimum and maximum amounts and the rate type, in a BestChange-style format.
Can I build price alerts without an API key?
The feed itself is public — /v1/export/rates.xml needs no key, and neither do /v1/assets or /v1/validate-address. A key is needed the moment an alert turns into an actual quote or order.
Why not just quote every user's pair on a timer?
Because providers rate-limit repeated quoting long before SwapZilla would. The feed exists precisely so that watching prices costs one request for everybody, and quoting is reserved for the moment a user is about to trade.
How current is a status I just read?
At most about ten seconds old, since the background poller refreshes every open order at that interval. When a user has just said "I've sent it", POST /v1/partner/orders/{id}/refresh goes to the provider synchronously and returns the updated order — call it once, not in a loop.
What else the same key builds
- Crypto payment links and invoicesPay-by-link and invoicing for freelancers and contractors — quote by the amount due, settle straight to the payee's wallet.
- Crypto payment processing and acquiringCheckout for shops and SaaS: accept 100+ assets, settle in one, reconcile by your own order id.
- Crypto exchange API, SDK and swap widgetEmbed swaps in a wallet, bot, aggregator or website — streaming quotes, one order call, your own UI.
- Crypto payroll for contractors and remote teamsRecurring payouts to a contractor roster: each person's own asset and network, exact net amounts, per-run reconciliation.
- Multi-send: batch payouts to many addressesOne-off fan-out to hundreds of addresses: cross-chain, per-recipient status, safe retries.
- Your own MCP server for AI agentsThe published swapzilla-mcp server in one line — or your own, on your partner key, with allow-lists and spending caps.
Getting a key
Register in the partner cabinet and your partner id and the api_key that goes in X-API-Key are issued at once. The key starts working once it is activated: write to @swapzilla_support_bot with a sentence about what you are building. Every endpoint, field and status is in the partner API reference.