Gotchas
Things that have caught real users. Quick read; save yourself a confused hour later.
`BTC` is not bitcoin
Symbols are namespaced by asset class. We don’t auto-resolve.
Passing BTC to a stock endpoint returns the Grayscale Bitcoin Mini Trust ETF (a real US-listed equity with that ticker), not bitcoin spot. If you want the crypto, use X:BTCUSD. Same rule for GOLD (Barrick Gold equity) vs CMD:GOLD (gold spot). See Asset symbols for the full prefix table.
Crypto has no `session_open` / `session_high` / `session_low`
Markets that never close don’t have a session.
For equities those columns reflect today's regular session. For crypto they're unset; use the rolling 24-hour columns instead (change_24h, high_24h, low_24h, etc.). Same goes for corporate-event flags: earnings_this_week and friends always come back as false for crypto.
Edge-triggered flags stay true all session
One firing, sticky until the next reset.
Flags like breakout, gap_up_3pct, and golden_cross_today are edge-triggered: they flip true the moment the condition is met and stay true for the rest of the trading session even if the underlying condition reverses. They reset at the start of the next session. If you're asking “is this happening right now,” use the continuous flavor of the flag (e.g. above_prior_high); if you're asking “did this happen today,” the edge flag is what you want.
Numeric literals are fully specified
This is an API; we don’t parse suffixes.
Write 1500000000, not 1.5B. Write 0.05 for 5%, not 5 (percentage columns are stored as decimals — see the field description on the schema page for the underlying scale).
Cursors are opaque
Don’t parse them, don’t cache them across deploys.
List endpoints return next_cursor. Pass it back as ?cursor= on the next request and that's it. The token bakes in the sort key, page size, and original filters; changing any query parameter mid-walk invalidates it. See Pagination for the full mechanics.
Daily-only signals can’t be pulled at 1m or 1h
SMAs, RSI, MACD, fundamentals: daily snapshots only.
Asking /v2/signals/rsi_14/AAPL/history/1m returns signal_not_available_at_interval. Request 1d for any slow-moving signal. The schema page labels each column with the intervals it's stored at; minute-tier columns are price, day_change_pct, gap_pct, volume, relative_volume, day_vwap, and the position-from-extremes columns.
DELETE endpoints return 204 No Content
Empty body on success.
DELETE /v2/webhooks/{id}, DELETE /v2/universes/{id}, and DELETE /v2/rules/{id} all return 204 No Content on success. There's no JSON to parse; check the status code. Errors (404) still come back with the standard JSON envelope.
`as_of` means different things on different endpoints
Live time vs requested date.
On live endpoints, as_of is the server time at which the response was assembled (typically within a minute of your call). On historical endpoints (/tickers/{t}/history, /scan?asof=), as_of is the date you asked about. Don't treat an old as_of from a historical call as “stale data” — it's by design.
Canceled subscriptions fall back to a no-feature tier
Your key keeps working; capacity goes to zero.
When a subscription is canceled, the API key keeps working at a fallback tier with no webhook / universe / rule capacity. Read endpoints continue. Any attempt to create a webhook, universe, or rule returns webhook_tier_required (or the universe/rule equivalent) until you resubscribe. Existing webhooks created before cancel are auto-disabled if they exceed the new tier's capacity; they stay in the database and re-enable on resubscribe. See errors for the codes.
System universe slugs are reserved
`top_10` and `top_100` you don’t own.
When you fetch /v2/universes/top_10 you get a system universe, even though you didn't create one. You can't create a universe with a slug that collides with a system one (slug_taken on POST). You can't delete or edit a system universe either; both come back 403.
Webhook deliveries can fire after delete
Queued deliveries capture target + secret at queue time.
If a webhook is in the middle of an evaluation cycle when you call DELETE /v2/webhooks/{id}, any deliveries already enqueued can still fire. They carry the original target URL and signing secret. To pause without this race, stop responding 2xx at your target and the subscription will flip to disabled after consecutive failures.
Plan-gated history applies to /scan?asof and signals/.../history
Two different windows, both checked on every call.
Per-ticker history depth (/v2/tickers/{t}/history and /v2/signals/.../history) and historical scan depth (/v2/scan?asof=) are both plan-gated and both enforced at request time. Hobby reaches one year back, Pro five years, Scale and Enterprise all-time. Going past the window returns history_window_exceeded with earliest_allowed_asof so your code can clamp. Helpful when building paginating backtests:
if (err.code === "history_window_exceeded") {
asof = err.earliest_allowed_asof;
// retry from clamped date
}