Skip to main content

Deprecation policy

When we remove or materially change a public endpoint or field:
  1. Announce the replacement, what to use instead, with a migration example.
  2. List affected endpoints, every route, field, or WS channel touched.
  3. Set a dated removal window, at least 90 days from announcement for breaking REST changes, unless the change is a security fix.
  4. Log reversals, if we roll back a deprecation, it is recorded here with the reason.
Non-breaking additions (new optional fields, new endpoints, new error codes) ship without a deprecation window. Closed-schema endpoints reject unknown fields, do not send fields you have not seen documented. Subscribe via the page RSS feed (/changelog/rss.xml when published).
RESTWebSocketMarkets
Nine series go live: gold and WTI crude join BTC

New guide

Series, assets, and categories — the nine series, the category and feed id tables, how to list every live market across assets, and the market hours for gold and crude.

What is new

Nine series trade in parallel. Three assets × three cadences. category 1–3 is BTC, 4–6 gold (XAU), 7–9 WTI crude, at 60s / 5m / 15m respectively. GET /v1/markets?status=trading now returns up to nine live slots rather than one.GET /v1/oracle/price takes an optional feed. ?feed=0 is BTC, 1 gold, 2 WTI crude. Omitting it still means feed 0, so existing clients are unaffected. The response body does not echo the feed. A feed with no valid tick answers the usual zero sentinel (price: "0") rather than an error — that is what a closed commodity session looks like.The oracle WebSocket channel now carries feed on every tick. The channel is global, so a single subscription receives BTC, gold and crude interleaved; branch on feed before using price. The field is additive and a frame without it would read as feed 0.The rounds WebSocket channel carries category (alongside round_number and generation) on every frame, so the global round stream can be scoped to one series client-side.GET /v1/rounds?category= filters to one series. category: 0 means unclassified, not “all” — omit the parameter to ask for everything.

What to check in your client

  • Anywhere you assumed “the live round” was unique, or that GET /v1/rounds?limit=1 was it. With nine series the top row is whichever series opened last. Use GET /v1/markets?status=trading.
  • Anywhere you assumed an oracle tick was BTC. Filter on feed.
  • Anywhere you cached market_id → asset. Slots are recycled and can return in a different series; re-read the round.
  • Anywhere you assumed fresh_source_count >= 3. Gold and crude come from a single upstream source and report 1.
  • Anywhere you assumed 24/7. Gold and crude follow their underlying sessions.

Not changing

market_id, round_number, pip ticks, the order surface, fees, scopes and rate limits are untouched. The market object still carries no category — the round is where the series lives.
RESTFees
Trading fees documented, and a 100x under-charge corrected

New guide

Fees — the per-fill formula, why it is quadratic in price, how a buy reserves its fee before the fill, why sells never reserve, and the full list of what is never charged.

Fee correction

The engine computed the per-fill fee as ceil(rate × qty × Q(p) / 10^10). With money in pips the divisor is 10^8: the pip migration scaled Q(p) by 10^4 and the fee’s own unit by 10^2, and only one of the two was carried into the constant. Every trading fee since then was 1/100th of the configured rate.The live schedule is unchanged — maker 0, taker 250 — and 250 has always been documented as 2.5% Kalshi-style. It now bills that:Affected: anything that modelled realised fees from observed fills rather than from the published rate — backtests calibrated on live fills since the pip migration will have understated costs by 100×. Maker rebates and referral shares are a percentage of fees collected, so both scale with the correction. Orders already resting keep the rate pinned at their admission.
REST
Volume-scaled write rate and a separate cancel bucket

Three buckets

Read, write, and cancel are independent token buckets. Cancels (single, batch, mass-cancel) no longer draw the write bucket. Cancel rate is always 2 × the effective write rate.Cost stays 1 token per HTTP request. A 20-order batch costs 1 token, not 20.

Volume-scaled write rate

vol_30d is that account’s trailing 30 UTC-day fill-share sum (SUM(qty), maker and taker, $1/share). A new account gets 20/s. The cap is 400/s.

GET /v1/account/limits

Additive fields on the existing endpoint: cancel, trailing_volume_30d, write_floor, usd_per_extra_rps. Ignore unknown response fields.Guide: Rate limits.Affected: bots that assumed cancels shared the write bucket, or that hard-coded 50/s write. Read the live grant from GET /v1/account/limits.
RESTWebSocketBreaking
Pip-era ticks, subcent wings, Unix-minute settlement

Breaking — ticks are pips, not cents

Contract prices, book levels, and tick / worst_tick / tick_yes are integer pips (1–9999). 1 pip = 0.01¢. $1 = 10_000 pips. 4700 is 47.00¢. Sending the old 1–99 range is bad_price_tick — it is never scaled up.Cash, max_cost, balances, fees, deposits, and withdrawals are pips (decimal strings). 10000 is $1.00. Market-buy worst_tick defaults to 9999 (was 99).YES ask = 10000 − NO bid (was 100 − t). Winning contracts pay 10,000 pips.

Rest grid (tick_size)

Market and orderbook snapshots (REST and WS) include tick_size:
Live policy is narrow: whole cents on [400, 9600], 0.10¢ on the wings. That grid is always live — do not wait for mode: "subcent" to rest 9610.Guide: Ticks, pips, and subcents.

Settlement cadence

Rounds open in the first seconds of a Unix minute and freeze when the wall clock enters the next minute (not open_ts + 60s). winner is "yes" iff the freeze TWAP is strictly above strike.Find the live slot with GET /v1/markets?status=trading, not GET /v1/rounds?limit=1.Affected: every client that sends ticks, max_cost, or assumes 100 − t reciprocity.
RESTWebSocketBreaking
Cancel batch, introspection, dead-man, WS update

Breaking, cancel batch verb

Batch cancel moved off a separate POST path.Body unchanged: { "order_ids": ["…"] } (≤ 20 decimal-string ids). Response is per-item, index-aligned; partial success is normal.POST /v1/portfolio/orders/batch remains batch placement only.Affected: any client still calling …/cancel_batch. Migrate before relying on production traffic, the old path is gone (no dual-write window; the route never had external consumers).

Breaking, cancel-all empty body

DELETE /v1/portfolio/orders must have an empty body. A non-empty body returns 400 unexpected_body instead of cancelling. This prevents a misrouted batch-cancel (missing /batch) from wiping the book.Affected: DELETE /v1/portfolio/orders only.

Round status vocabulary

Round rows use: trading, frozen, proposed, settled, void. There is no created status on round objects.

WebSocket channel inventory

Live: orderbook_snapshot, orderbook_delta, trades, user, oracle, rounds. ticker is not implemented (subscribe → wscode 4).

exchange_active honesty

GET /v1/exchange/status always returns exchange_active: true in v1. Per-round freeze/settle is the operational pause, see Pauses and rounds.

New introspection endpoints

Dead-man switch

POST /v1/portfolio/deadman with { "timeout_ms": N } (1000–60000) arms a gateway-side timer. Refresh by POSTing again. On fire: one user-wide cancel-all, then disarm. DELETE /v1/portfolio/deadman disarms without cancelling.This is not Kalshi-style order-group fill-velocity limiting, that remains deferred.

WebSocket update_subscription

Actions: get_snapshot, add_markets, delete_markets.

429 shape

Rate limits answer with details.retry_after_ms in the error body. There is no Retry-After HTTP header.