API Guide

Reference for the StocksFast HTTP API. Every route is documented under Routes with its permission, parameters, response and errors.

Base URL and request headers

https://c-staging.stocksfast.io/api/v1

These are the headers you send. For a header that came back on a response, see Response headers.

Header When Value
Authorization always Bearer sfu_.... Any other scheme answers 401.
Content-Type any route taking a body application/json; a body sent without it answers 400 invalid_body. The TradingView import takes multipart/form-data instead.
Idempotency-Key optional, the two execute routes Your own unique string, 1-255 chars of [A-Za-z0-9._-], to make a timed-out retry safe. See Idempotency.
If-None-Match optional, any route answering an ETag A validator, a comma-separated list, or *. A match answers 304.
X-Request-Id optional, always Your own correlation id, 1-128 chars of [A-Za-z0-9._-], echoed back unchanged. Anything else is replaced by a fresh one.

Keys

  • Minted at Account › API keys; the plaintext is shown once, only its hash is stored.
  • Minting needs Premium, an active trial, or scan credits; listing and revoking need none of those.
  • A key is personal to your account; team or organizational use needs a separate license from support@stocksfast.io.

Rotation:

  1. Mint the replacement key with the permissions your client needs.
  2. Move the client over to the new secret.
  3. Revoke the old key.

Quick start

1. Mint a key with read_market and execute_scan at Account › API keys, then export it:

export SF_KEY=sfu_9f2c1a04a7d34e1c8b5f...

2. Read the vocabulary — static reference data, fetch once and cache it:

curl -H "Authorization: Bearer $SF_KEY" \
     https://c-staging.stocksfast.io/api/v1/catalog

3. Check an expression — costs no credits, reads the same body fields execute does:

curl -H "Authorization: Bearer $SF_KEY" \
     -H "Content-Type: application/json" \
     -d '{"expression": "close > sma(close, 50)", "timeframe": "1D"}' \
     https://c-staging.stocksfast.io/api/v1/scans/validate

4. Run it:

curl -H "Authorization: Bearer $SF_KEY" \
     -H "Content-Type: application/json" \
     -d '{"expression": "close > sma(close, 50) and volume > 1000000",
          "timeframe": "1D", "limit": 2}' \
     https://c-staging.stocksfast.io/api/v1/scans/execute

Conventions

Envelope

{"data": ...}
{"data": [ ... ], "page": {"limit": 50, "offset": 0, "total": 812}}
{"error": {"code": "compile_error", "message": "unknown function 'smaa'"}}

One route answers outside the envelope: GET /watchlists/<id>/export-tradingview.

Paging

limit is how many results come back in one response. offset is how many to skip before the first one, so the second page of 50 is limit=50, offset=50.

FieldTypeDefaultBounds
limitint50clamped to [1, 200]
offsetint0floored at 0, no upper bound; loop while offset < total

Two routes take limit/offset in the request body, with a higher ceiling:

RouteMax limit
/scans/execute2000
/saved-scans/<id>/execute2000

Timestamps

One spelling across this surface: Unix epoch seconds (UTC, no fractional part), as a JSON int or null. "created_at": 1741944413 is Mar 14, 2025 09:26:53 UTC.

Execution model

Scans run synchronously: the response carries the results. Size your client-side timeout to the expression you send.

Permissions

PermissionCarries
stocksfast.read_marketThe function catalog, the universe, symbol search and detail, and expression checks.
stocksfast.execute_scanRunning a scan, spending your scan credits.
stocksfast.read_scansListing and reading your saved scans.
stocksfast.write_scansCreating, editing and deleting saved scans.
stocksfast.read_tagsListing and reading your tags.
stocksfast.write_tagsCreating, editing and deleting tags.
stocksfast.read_watchlistsListing and reading your watchlists, and the TradingView export.
stocksfast.write_watchlistsCreating, editing and deleting watchlists, adding and removing symbols, and the TradingView import.

Rate limits

BucketRoutesPer minute
execute/scans/execute, /saved-scans/<id>/execute20
validate/scans/validate30
read/catalog, /universe, /symbols, /symbols/<symbol>120
CRUD readlist and retrieve across saved-scans, tags and watchlists, plus export-tradingview90
CRUD writecreate, update and delete across saved-scans, tags and watchlists, plus add-symbols, remove-symbols and import-tradingview40

Every response that spends a bucket carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (see Response headers), served and refused alike, describing the bucket that request actually spent; absence means no budget was spent, not that it is exhausted.

Scan credits

A scan run debits one credit, once its results are built. Premium accounts are not metered, and a replayed response (see Idempotency below) debits nothing. Nothing else on this surface costs credits.

Idempotency

A scan that settles is billed whether or not you are still connected to read it. Send an Idempotency-Key header on either execute route and the retry is safe.

  • Pick your own key, 1-255 chars of [A-Za-z0-9._-]. It is scoped to your credential.
  • A repeat with the same key inside 1 hour replays the stored response, marked Idempotency-Replayed: true, and debits nothing.
  • Under load a key can be dropped before that 1 hour is up.
  • It is a retry safety net, not a result cache. Do not use it to re-fetch results.
SituationAnswer
No headerNothing is stored.
First use of a keyRuns, debits, and stores the response for the retention window.
Same key, same request, inside the windowThe stored response, Idempotency-Replayed: true, no debit.
Same key, a different request409 idempotency_key_reused — a client bug, not a replay.
Same key while the first request is still running409 idempotency_in_flight with Retry-After.
Malformed or oversized key400 invalid_value, before anything runs or debits.
A request that debited nothing (any refusal)The key is not stored; a retry with the same key runs as a first use.
Key no longer held, by age or under loadTreated as a first use, the same as a key never sent before.

Stability

This surface is Beta. The marker is the X-API-Stability: beta response header, on every response, success and error alike. It drops out at 1.0, with no path change.

Commits toWithholds
The route set and its paths inside the prefixNew codes and fields, which ship unannounced
Response body shape and timestamp spellingsThe rate-limit numbers
The sfu_ Bearer credential and the eight permission codes
The status for each condition this page names, and the code token for each documented error
Owner scoping: another account's row is absent, never forbidden

No deprecation window, sunset header, changelog feed or migration guarantee ships with the X-API-Stability header. A client that pins a withheld shape today carries that risk until the surface graduates.

Read the RateLimit-* headers (see Rate limits) on each response rather than caching the numbers from this page: they describe the request in front of you, and the published numbers can move.

Routes

Each block carries its permission, parameters, response and reachable errors, split Request against Response. Paths are relative to the base URL at the top of this page.

Account

GET /meno permission code · no plan · read, 120/min · no credits
GET https://c-staging.stocksfast.io/api/v1/me

Excluded from the conditional-GET scheme carried by catalog, universe, symbols and symbols/<symbol>: no ETag, no If-None-Match handling.

200 — Cache-Control: no-store
{
  "data": {
    "user_id": 7,
    "is_premium": false,
    "credit_balance": 42,
    "permissions": ["stocksfast.read_market", "stocksfast.execute_scan"],
    "key": {"id": 41, "label": "mcp-laptop", "prefix": "9f2c1a04",
            "created_at": 1755683642, "expires_at": 1763453642,
            "last_used_at": 1755690118},
    "limits": {
      "execute": {"limit": 20, "window_seconds": 60},
      "validate": {"limit": 30, "window_seconds": 60},
      "read": {"limit": 120, "window_seconds": 60},
      "crud_read": {"limit": 90, "window_seconds": 60},
      "crud_write": {"limit": 40, "window_seconds": 60}
    }
  }
}
Fields
FieldTypeNote
user_idint—
is_premiumbool—
credit_balanceintreads the ledger; spends nothing
permissions[]arraythe effective set (frozen INTERSECT the owner's live grants), not the frozen grant the key was minted with
keyobject|nullid, label, prefix, created_at, expires_at, last_used_at; null for a non-key principal
key.expires_at, key.last_used_atint|nullnull on no TTL / no prior use
limitsobjectevery rate-limit bucket's configured ceiling, keyed by name; {} for a non-key principal, since credential_id 0 would name a bucket shared by every credential-less caller, not this one
Errors
StatusCodes
401unauthorized
429rate_limited
500internal
curl -H "Authorization: Bearer $SF_KEY" \
  https://c-staging.stocksfast.io/api/v1/me

→ 200
{"data": {"user_id": 7, "is_premium": false, "credit_balance": 42, "permissions": [...], "key": {...}, "limits": {...}}}

Schema

GET /openapi.jsonno permission code · no plan · read, 120/min · no credits

The machine-readable description of this whole prefix: an OpenAPI 3.0 document generated from the same route-registration metadata every other block on this page is checked against, so a route added, removed or renamed here without its metadata following fails a test rather than shipping a document that lies.

GET https://c-staging.stocksfast.io/api/v1/openapi.json
200
{
  "openapi": "3.0.3",
  "info": { … },
  "paths": { … },
  "components": { … }
}

The body is the document itself — openapi/info/paths/components at the top level, not the {"data": …} envelope every other route on this page answers with. Compiled-in reference data, fixed for the life of the server process. Every 200 carries ETag and Cache-Control: public, max-age=3600; send the ETag back as If-None-Match and a match answers 304 with no body.

Errors
StatusCodes
401unauthorized
429rate_limited
500internal
curl -H "Authorization: Bearer $SF_KEY" \
  https://c-staging.stocksfast.io/api/v1/openapi.json

→ 200
{"openapi": "3.0.3", "info": {...}, "paths": {...}, "components": {...}}

Market data

GET /catalogread_market · no plan · read, 120/min · no credits
GET https://c-staging.stocksfast.io/api/v1/catalog
200
{
  "data": {
    "functions": [
      {
        "name": "sma",
        "signature": "sma(source, length)",
        "summary": "Simple moving average over the last `length` bars.",
        "args": [{"name": "source", "type": "field", "required": true}],
        "kwargs": [],
        "examples": ["close > sma(close, 50);"]
      }
    ],
    "operators": [
      {
        "operator": ">",
        "description": "Greater than",
        "category": "comparison",
        "example": "close > 50",
        "function_equiv": "gt(a, b)"
      }
    ],
    "fields": [
      {"name": "close", "category": "Price & Volume"}
    ],
    "timeframes": [
      {"code": "1D", "label": "Daily"}
    ]
  }
}

Compiled-in reference data, fixed for the life of the server process. Every 200 carries ETag and Cache-Control: public, max-age=3600; send the ETag back as If-None-Match and a match answers 304 with no body.

Fields
FieldTypeNote
functions[]arrayname, summary, signature, return_type, return_nullable, declared_formula, range, notes, see_also, references, related_functions, args, kwargs, examples
operators[]arrayfunction_equiv is null on an operator with no function spelling
fields[]arrayname, category
timeframes[]arraycode, label
Errors
StatusCodes
401unauthorized
403forbidden
429rate_limited
500internal
curl -H "Authorization: Bearer $SF_KEY" \
  https://c-staging.stocksfast.io/api/v1/catalog

→ 200
{"data": {"functions": [...], "operators": [...], "fields": [...], "timeframes": [...]}}
GET /universeread_market · no plan · read, 120/min · no credits
GET https://c-staging.stocksfast.io/api/v1/universe
200
{
  "data": {
    "universe_size": 8412,
    "last_available_date": "2026-08-19",
    "timeframes": [{"code": "1D", "label": "Daily"}]
  }
}

A strong ETag derived from the publish generation (universe_size, last_available_date) and the compiled-in timeframes table, rather than the rendered bytes, and Cache-Control: public, max-age=60; send the ETag back as If-None-Match and a match answers 304 with no body. Neither header appears on the 503 or the 500.

Fields
FieldTypeNote
universe_sizeint—
last_available_datestring|nullnull on an empty corpus
timeframes[]arraycode, label
Errors
StatusCodes
401unauthorized
403forbidden
429rate_limited
503store_unavailable
500internal
curl -H "Authorization: Bearer $SF_KEY" \
  https://c-staging.stocksfast.io/api/v1/universe

→ 200
{"data": {"universe_size": 8412, "last_available_date": "2026-08-19",
          "timeframes": [{"code": "1D", "label": "Daily"}]}}
GET /symbolsread_market · no plan · read, 120/min · no credits
GET https://c-staging.stocksfast.io/api/v1/symbols
Query parameters
FieldTypeRequiredDefaultBounds
qstringnononecase-insensitive ticker substring; absent or empty reads nothing
limitintno50clamped to [1, 200]
offsetintno0floored at 0
200
{
  "data": [
    {"symbol": "AAPL", "name": "Apple Inc.",
     "asset_type": "stock"}
  ],
  "page": {"limit": 2, "offset": 0, "total": 1}
}

A strong ETag that is a digest of the rendered response bytes, so it moves whenever a row's own content changes even with q/limit/offset held fixed, and Cache-Control: public, max-age=60; send the ETag back as If-None-Match and a match answers 304 with no body. Neither header appears on the 500.

Fields
FieldTypeNote
symbolstringticker; rows ordered ascending by this field, so a window is stable across requests
namestring|nullnull where the row carries no value
asset_typestring|null"stock" or "etf"; null where the row matches neither
Errors
StatusCodes
401unauthorized
403forbidden
429rate_limited
500internal
curl -H "Authorization: Bearer $SF_KEY" \
  "https://c-staging.stocksfast.io/api/v1/symbols?q=aap&limit=2"

→ 200
{"data": [{"symbol": "AAPL", "name": "Apple Inc.", "asset_type": "stock"}],
 "page": {"limit": 2, "offset": 0, "total": 1}}
GET /symbols/<symbol>read_market · no plan · read, 120/min · no credits
GET https://c-staging.stocksfast.io/api/v1/symbols/<symbol>
Path parameters
FieldTypeRequiredDefaultNotes
symbolstringyes—non-empty, matched case-insensitively
200
{
  "data": {"symbol": "AAPL", "name": "Apple Inc.",
           "asset_type": "stock"}
}

A strong ETag that is a digest of the rendered response bytes, so it moves whenever the row's own content changes even with the symbol held fixed, and Cache-Control: public, max-age=60; send the ETag back as If-None-Match and a match answers 304 with no body. Neither header appears on the 404 or the 500.

Fields
FieldTypeNote
symbolstring—
namestring|nullnull where the row carries no value
asset_typestring|null"stock" or "etf"; null where the row matches neither
Errors
StatusCodes
401unauthorized
403forbidden
429rate_limited
404not_found
500internal
curl -H "Authorization: Bearer $SF_KEY" \
  https://c-staging.stocksfast.io/api/v1/symbols/AAPL

→ 200
{"data": {"symbol": "AAPL", "name": "Apple Inc.", "asset_type": "stock"}}

Scans

POST /scans/validateread_market · no plan · validate, 30/min · no credits
POST https://c-staging.stocksfast.io/api/v1/scans/validate
Body
FieldTypeRequiredDefaultBounds
expressionstringyes—non-empty
timeframestringno1Done of the codes GET /catalog returns
scan_datestringnomost recent data dateYYYY-MM-DD; malformed → 400 compile_error; absent against an empty store → 503 store_unavailable
200
{
  "data": {
    "expression": "close > sma(close, 50)",
    "timeframe": "1D",
    "scan_date": "2026-09-03"
  },
  "warnings": []
}
Fields
FieldTypeNote
expression, timeframe, scan_datestringechoed/resolved; scan_date is the corpus default or the caller's own, snapped to the period start on a coarser timeframe
warningsarrayroot sibling of data; always present, empty when the compile raised none
Errors
StatusCodes
400invalid_body · missing_field · compile_error
401unauthorized
403forbidden
429rate_limited
503store_unavailable
500internal · internal_compiler_error
curl -H "Authorization: Bearer $SF_KEY" \
  -H "Content-Type: application/json" \
  -d '{"expression": "close > sma(close, 50)", "timeframe": "1D"}' \
  https://c-staging.stocksfast.io/api/v1/scans/validate

→ 200
{"data": {"expression": "close > sma(close, 50)", "timeframe": "1D",
          "scan_date": "2026-09-03"},
 "warnings": []}
POST /scans/executeexecute_scan · no plan · execute, 20/min · 1 credit
POST https://c-staging.stocksfast.io/api/v1/scans/execute
Body
FieldTypeRequiredDefaultBounds
expressionstringyes—non-empty
timeframestringno1Done of the codes GET /catalog returns
scan_datestringnomost recent data dateYYYY-MM-DD; malformed → 400 compile_error; absent against an empty store → 503 store_unavailable
symbolsarray of stringnono filterat most 20000 entries, each non-empty; refused, not truncated, over cap
watchlist_idsarray of intnono filterat most 100 entries, each one of your own watchlists
limitintno50clamped to [1, 2000]; wins over the query string
offsetintno0floored at 0
200
{
  "data": [
    {
      "symbol": "AAPL",
      "close": 231.40,
      "volume": 54210033,
      "dollar_volume": 12544400637.2
    }
  ],
  "page": {"limit": 2, "offset": 0, "total": 137},
  "warnings": [
    {"code": "W5001", "message": "Standalone nullable function 'pivothigh(...)' filters to non-NULL values. Use '== null' or '!= null' for explicit comparison.", "line": 1, "column": 25}
  ]
}
Fields
FieldTypeNote
data[] columnsvariesthe fixed identity/OHLCV columns plus one per indicator call your expression names; every other engine column is dropped. open/high/low/close and a numeric indicator are numbers, volume is an integer, a boolean indicator (e.g. crossover(...)) is true/false, and symbol/name stay strings. A cell with no value is null regardless of the column's type
dollar_volumefloatderived; result set ordered by this, descending
page.totalintthe full match count; a set past 2000 rows is truncated to the first 2000 at limit: 2000 (fewer at a lower limit) — narrow the scan rather than paging past it with offset
warningsarray of objectroot sibling of data and page; always present, empty when the expression compiled clean; each element is {code, message, line?, column?}, with line/column omitted when the diagnostic carries no position; never absent or null, so a caller may branch on it unconditionally
Notes
CodeMeaning
invalid_valuea filter resolving to no symbols — an unowned or unknown watchlist id, an empty watchlist, or symbols and watchlist_ids sharing no ticker
invalid_valuea malformed or oversized Idempotency-Key (see Idempotency)

No server-side cutoff bounds how long this call can take — see Execution model for the measured worst case and what a timeout does and does not protect against.

Errors
StatusCodes
400invalid_body · missing_field · invalid_value · compile_error
401unauthorized
403forbidden · insufficient_credits
409idempotency_key_reused · idempotency_in_flight
429rate_limited
500engine_error · credit_error · internal · internal_compiler_error
503store_unavailable
curl -H "Authorization: Bearer $SF_KEY" \
  -H "Content-Type: application/json" \
  -d '{"expression": "close > sma(close, 50); pivothigh(high, 5, 5);",
       "timeframe": "1D", "limit": 2}' \
  https://c-staging.stocksfast.io/api/v1/scans/execute

→ 200
{"data": [{"symbol": "AAPL", "close": 231.40, "volume": 54210033,
           "dollar_volume": 12544400637.2}],
 "page": {"limit": 2, "offset": 0, "total": 137},
 "warnings": [{"code": "W5001",
               "message": "Standalone nullable function 'pivothigh(...)' filters to non-NULL values. Use '== null' or '!= null' for explicit comparison.",
               "line": 1, "column": 25}]}

Saved scans

POST /saved-scans/<id>/executeexecute_scan + read_scans · no plan · execute, 20/min · 1 credit
POST https://c-staging.stocksfast.io/api/v1/saved-scans/<id>/execute
Path parameters
FieldTypeRequiredDefaultNotes
idintyes—one of your own; another account's id → 404
Body (optional in full — the expression comes from the stored scan)
FieldTypeRequiredDefaultBounds
timeframestringno1Done of the codes GET /catalog returns
scan_datestringnomost recent data dateYYYY-MM-DD
symbolsarray of stringnono filterat most 20000 entries, each non-empty
watchlist_idsarray of intnono filterat most 100 entries
limitintno50clamped to [1, 2000]
offsetintno0floored at 0
Notes
CodeMeaning
invalid_valueexpression is not accepted here — the stored one always runs — and is refused like any other key outside the table above; use POST /scans/execute for a different expression
invalid_valuea malformed or oversized Idempotency-Key (see Idempotency)

No server-side cutoff bounds how long this call can take — see Execution model for the measured worst case and what a timeout does and does not protect against.

200
{
  "data": [
    {
      "symbol": "AAPL",
      "close": 231.40,
      "volume": 54210033,
      "dollar_volume": 12544400637.2
    }
  ],
  "page": {"limit": 2, "offset": 0, "total": 137},
  "warnings": [
    {"code": "W5001", "message": "Standalone nullable function 'pivothigh(...)' filters to non-NULL values. Use '== null' or '!= null' for explicit comparison.", "line": 1, "column": 28}
  ]
}
Fields
FieldTypeNote
data[] columnsvariesthe fixed identity/OHLCV columns plus one per indicator call the stored expression names; every other engine column is dropped. open/high/low/close and a numeric indicator are numbers, volume is an integer, a boolean indicator (e.g. crossover(...)) is true/false, and symbol/name stay strings. A cell with no value is null regardless of the column's type
dollar_volumefloatderived; result set ordered by this, descending
page.totalintthe full match count; a set past 2000 rows is truncated to the first 2000 at limit: 2000 (fewer at a lower limit) — narrow the scan rather than paging past it with offset
warningsarray of objectroot sibling of data and page; always present, empty when the expression compiled clean; each element is {code, message, line?, column?}, with line/column omitted when the diagnostic carries no position; never absent or null, so a caller may branch on it unconditionally
Side effects
FieldEffect
last_executed_atstamped to now on a run that settles; updated_at does not move with it
Errors
StatusCodes
400invalid_body · invalid_value · compile_error
401unauthorized
403forbidden · insufficient_credits
404not_found
409idempotency_key_reused · idempotency_in_flight
429rate_limited
500engine_error · credit_error · internal · internal_compiler_error
503store_unavailable
curl -X POST -H "Authorization: Bearer $SF_KEY" \
  -H "Content-Type: application/json" -d '{"limit": 2}' \
  https://c-staging.stocksfast.io/api/v1/saved-scans/41/execute

→ 200
{"data": [{"symbol": "AAPL", "close": 231.40, "volume": 54210033,
           "dollar_volume": 12544400637.2}],
 "page": {"limit": 2, "offset": 0, "total": 137},
 "warnings": [{"code": "W5001",
               "message": "Standalone nullable function 'pivothigh(...)' filters to non-NULL values. Use '== null' or '!= null' for explicit comparison.",
               "line": 1, "column": 28}]}
GET /saved-scansread_scans · no plan · CRUD read bucket, 90/60s · no credits
GET https://c-staging.stocksfast.io/api/v1/saved-scans
Query parameters
FieldTypeRequiredDefaultBounds
tagstringnononea tag slug of your own; an unmatched slug answers an empty page
limitintno50clamped to [1, 200]
offsetintno0floored at 0
200
{
  "data": [
    {"id": 41, "name": "Momentum breakout", "description": "",
           "expression": "close > sma(close, 50)", "is_favorite": false, "is_public": false,
           "created_at": 1755683642, "updated_at": 1755683642,
           "last_executed_at": 1755690118, "tags": ["momentum"]}
  ],
  "page": {"limit": 50, "offset": 0, "total": 3}
}
Fields
FieldTypeNote
idintserver-assigned
namestring—
descriptionstring—
expressionstring—
is_favorite, is_publicbool—
created_at, updated_atintepoch seconds; server-set
last_executed_atint|nullepoch seconds; null on a scan that has never run
tagsarray of stringcreated on demand in your own namespace
Errors
StatusCodes
401unauthorized
403forbidden
429rate_limited
500internal
curl -H "Authorization: Bearer $SF_KEY" \
  https://c-staging.stocksfast.io/api/v1/saved-scans

→ 200
{"data": [{"id": 41, "name": "Momentum breakout", "expression": "close > sma(close, 50)", ...}],
 "page": {"limit": 50, "offset": 0, "total": 3}}
POST /saved-scansread_scans + write_scans · Premium, or fewer than 5 saved scans · CRUD write bucket, 40/60s · no credits
POST https://c-staging.stocksfast.io/api/v1/saved-scans
Body
FieldTypeRequiredDefaultBounds
namestringyes—at most 100 characters; unique among your scans
expressionstringyes—must compile as StonQL
descriptionstringno""—
is_favoriteboolnofalse—
is_publicboolnofalse—
tagsarray of stringnono tagsat most 50 entries; each must carry a letter or digit
201
{
  "data": {"id": 41, "name": "Momentum breakout", "description": "",
           "expression": "close > sma(close, 50)", "is_favorite": false, "is_public": false,
           "created_at": 1755683642, "updated_at": 1755683642,
           "last_executed_at": null, "tags": ["momentum"]}
}
Fields
FieldTypeNote
idintserver-assigned
namestring—
descriptionstring—
expressionstring—
is_favorite, is_publicbool—
created_at, updated_atintepoch seconds; server-set
last_executed_atint|nullepoch seconds; null on a scan that has never run
tagsarray of stringcreated on demand in your own namespace
Errors
StatusCodes
400invalid_body · missing_field · invalid_value
401unauthorized
403forbidden · upgrade_required (plan gate)
429rate_limited
409conflict (a name another of your rows already carries, named in details)
500internal · internal_compiler_error
curl -X POST -H "Authorization: Bearer $SF_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Momentum breakout", "expression": "close > sma(close, 50)"}' \
  https://c-staging.stocksfast.io/api/v1/saved-scans

→ 201
{"data": {"id": 41, "name": "Momentum breakout", "expression": "close > sma(close, 50)", ...}}
GET /saved-scans/<id>read_scans · no plan · CRUD read bucket, 90/60s · no credits
GET https://c-staging.stocksfast.io/api/v1/saved-scans/<id>
Path parameters
FieldTypeRequiredDefaultNotes
idintyes—one of your own; another account's id → 404
200
{
  "data": {"id": 41, "name": "Momentum breakout", "description": "",
           "expression": "close > sma(close, 50)", "is_favorite": false, "is_public": false,
           "created_at": 1755683642, "updated_at": 1755683642,
           "last_executed_at": 1755690118, "tags": ["momentum"]}
}
Fields
FieldTypeNote
idintserver-assigned
namestring—
descriptionstring—
expressionstring—
is_favorite, is_publicbool—
created_at, updated_atintepoch seconds; server-set
last_executed_atint|nullepoch seconds; null on a scan that has never run
tagsarray of stringcreated on demand in your own namespace
Errors
StatusCodes
401unauthorized
403forbidden
429rate_limited
404not_found
500internal
curl -H "Authorization: Bearer $SF_KEY" \
  https://c-staging.stocksfast.io/api/v1/saved-scans/41

→ 200
{"data": {"id": 41, "name": "Momentum breakout", "expression": "close > sma(close, 50)", ...}}
PATCH /saved-scans/<id>read_scans + write_scans · Premium · CRUD write bucket, 40/60s · no credits
PATCH https://c-staging.stocksfast.io/api/v1/saved-scans/<id>
Path parameters
FieldTypeRequiredDefaultNotes
idintyes—one of your own; another account's id → 404
Body, applied as an overlay onto the stored scan; an omitted field keeps its stored value
FieldTypeRequiredDefaultBounds
namestringnounchangedat most 100 characters; unique among your scans
expressionstringnounchangedmust compile as StonQL
descriptionstringnounchanged"" clears it
is_favoriteboolnounchangedfalse clears it
is_publicboolnounchangedfalse clears it
tagsarray of stringnounchangedat most 50 entries; [] clears the set
Notes
FieldEffect
Bodyrequired — an absent or non-object body answers 400
200
{
  "data": {"id": 41, "name": "Momentum breakout", "description": "",
           "expression": "close > sma(close, 50)", "is_favorite": false, "is_public": false,
           "created_at": 1755683642, "updated_at": 1755690118,
           "last_executed_at": 1755690118, "tags": ["momentum"]}
}
Fields
FieldTypeNote
idintserver-assigned
namestring—
descriptionstring—
expressionstring—
is_favorite, is_publicbool—
created_at, updated_atintepoch seconds; server-set
last_executed_atint|nullepoch seconds; null on a scan that has never run
tagsarray of stringcreated on demand in your own namespace
Errors
StatusCodes
400invalid_body · invalid_value
401unauthorized
403forbidden · upgrade_required (plan gate)
429rate_limited
404not_found
409conflict (a name another of your rows already carries, named in details)
500internal · internal_compiler_error
curl -X PATCH -H "Authorization: Bearer $SF_KEY" \
  -H "Content-Type: application/json" -d '{"is_favorite": true}' \
  https://c-staging.stocksfast.io/api/v1/saved-scans/41

→ 200
{"data": {"id": 41, "name": "Momentum breakout", "is_favorite": true, ...}}
DELETE /saved-scans/<id>read_scans + write_scans · Premium · CRUD write bucket, 40/60s · no credits
DELETE https://c-staging.stocksfast.io/api/v1/saved-scans/<id>
Path parameters
FieldTypeRequiredDefaultNotes
idintyes—one of your own; another account's id → 404
204
Side effects
FieldEffect
—the scan's tag links go with it
Errors
StatusCodes
401unauthorized
403forbidden · upgrade_required (plan gate)
429rate_limited
404not_found
500internal
curl -X DELETE -H "Authorization: Bearer $SF_KEY" \
  https://c-staging.stocksfast.io/api/v1/saved-scans/41

→ 204

Tags

GET /tagsread_tags · no plan · CRUD read bucket, 90/60s · no credits
GET https://c-staging.stocksfast.io/api/v1/tags
Query parameters
FieldTypeRequiredDefaultBounds
limitintno50clamped to [1, 200]
offsetintno0floored at 0
200
{
  "data": [{"id": 12, "name": "Momentum", "slug": "momentum", "created_at": 1755683642}],
  "page": {"limit": 50, "offset": 0, "total": 4}
}
Fields
FieldTypeNote
idintserver-assigned
namestring—
slugstringderived from name; what makes two names the same tag; server-set
created_atintepoch seconds; server-set
Errors
StatusCodes
401unauthorized
403forbidden
429rate_limited
500internal
curl -H "Authorization: Bearer $SF_KEY" \
  https://c-staging.stocksfast.io/api/v1/tags

→ 200
{"data": [{"id": 12, "name": "Momentum", "slug": "momentum", "created_at": 1755683642}], "page": {"limit": 50, "offset": 0, "total": 4}}
POST /tagsread_tags + write_tags · no plan · CRUD write bucket, 40/60s · no credits
POST https://c-staging.stocksfast.io/api/v1/tags
Body
FieldTypeRequiredDefaultBounds
namestringyes—at most 100 characters; must carry a letter or digit
201
{
  "data": {"id": 12, "name": "Momentum", "slug": "momentum", "created_at": 1755683642}
}
Fields
FieldTypeNote
idintserver-assigned
namestring—
slugstringderived from name; what makes two names the same tag; server-set
created_atintepoch seconds; server-set
Errors
StatusCodes
400invalid_body · missing_field · invalid_value
401unauthorized
403forbidden
429rate_limited
409conflict (a name another of your rows already carries, named in details)
500internal
curl -X POST -H "Authorization: Bearer $SF_KEY" \
  -H "Content-Type: application/json" -d '{"name": "Momentum"}' \
  https://c-staging.stocksfast.io/api/v1/tags

→ 201
{"data": {"id": 12, "name": "Momentum", "slug": "momentum", "created_at": 1755683642}}
GET /tags/<id>read_tags · no plan · CRUD read bucket, 90/60s · no credits
GET https://c-staging.stocksfast.io/api/v1/tags/<id>
Path parameters
FieldTypeRequiredDefaultNotes
idintyes—one of your own; another account's id → 404
200
{
  "data": {"id": 12, "name": "Momentum", "slug": "momentum", "created_at": 1755683642}
}
Fields
FieldTypeNote
idintserver-assigned
namestring—
slugstringderived from name; what makes two names the same tag; server-set
created_atintepoch seconds; server-set
Errors
StatusCodes
401unauthorized
403forbidden
429rate_limited
404not_found
500internal
curl -H "Authorization: Bearer $SF_KEY" \
  https://c-staging.stocksfast.io/api/v1/tags/12

→ 200
{"data": {"id": 12, "name": "Momentum", "slug": "momentum", "created_at": 1755683642}}
PATCH /tags/<id>read_tags + write_tags · no plan · CRUD write bucket, 40/60s · no credits
PATCH https://c-staging.stocksfast.io/api/v1/tags/<id>
Path parameters
FieldTypeRequiredDefaultNotes
idintyes—one of your own; another account's id → 404
Body, applied as an overlay onto the stored tag
FieldTypeRequiredDefaultBounds
namestringnounchangedat most 100 characters; must carry a letter or digit
Notes
FieldEffect
Bodyrequired — an absent or non-object body answers 400
200
{
  "data": {"id": 12, "name": "Momentum breakouts", "slug": "momentum-breakouts",
           "created_at": 1755683642}
}
Fields
FieldTypeNote
idintserver-assigned
namestring—
slugstringderived from name; what makes two names the same tag; server-set
created_atintepoch seconds; server-set
slug on renamestringre-derived; a rename onto a slug you already hold → 409 conflict
Errors
StatusCodes
400invalid_body · invalid_value
401unauthorized
403forbidden
429rate_limited
404not_found
409conflict (a name another of your rows already carries, named in details)
500internal
curl -X PATCH -H "Authorization: Bearer $SF_KEY" \
  -H "Content-Type: application/json" -d '{"name": "Momentum breakouts"}' \
  https://c-staging.stocksfast.io/api/v1/tags/12

→ 200
{"data": {"id": 12, "name": "Momentum breakouts", "slug": "momentum-breakouts", "created_at": 1755683642}}
DELETE /tags/<id>read_tags + write_tags · no plan · CRUD write bucket, 40/60s · no credits
DELETE https://c-staging.stocksfast.io/api/v1/tags/<id>
Path parameters
FieldTypeRequiredDefaultNotes
idintyes—one of your own; another account's id → 404
204
Side effects
FieldEffect
—the tag's links to saved scans go with it; the scans stay
Errors
StatusCodes
401unauthorized
403forbidden
429rate_limited
404not_found
500internal
curl -X DELETE -H "Authorization: Bearer $SF_KEY" \
  https://c-staging.stocksfast.io/api/v1/tags/12

→ 204

Watchlists

GET /watchlistsread_watchlists · no plan · CRUD read bucket, 90/60s · no credits
GET https://c-staging.stocksfast.io/api/v1/watchlists
Query parameters
FieldTypeRequiredDefaultBounds
limitintno50clamped to [1, 200]
offsetintno0floored at 0
200
{
  "data": [
    {"id": 7, "name": "Swing candidates",
     "created_at": 1755683642, "updated_at": 1755683642}
  ],
  "page": {"limit": 50, "offset": 0, "total": 2}
}
Fields
FieldTypeNote
idint—
namestring—
created_at, updated_atintepoch seconds
Notes
FieldEffect
symbolsnot included here; read GET /watchlists/<id> for membership
Errors
StatusCodes
401unauthorized
403forbidden
429rate_limited
500internal
curl -H "Authorization: Bearer $SF_KEY" \
  https://c-staging.stocksfast.io/api/v1/watchlists

→ 200
{"data": [{"id": 7, "name": "Swing candidates", "created_at": 1755683642, "updated_at": 1755683642}],
 "page": {"limit": 50, "offset": 0, "total": 2}}
POST /watchlistsread_watchlists + write_watchlists · no plan · CRUD write bucket, 40/60s · no credits
POST https://c-staging.stocksfast.io/api/v1/watchlists
Body
FieldTypeRequiredDefaultBounds
namestringyes—non-blank, at most 100 characters
symbolsarray of stringnoempty membershipat most 1000 entries, each at most 10 bytes
201
{
  "data": {"id": 7, "name": "Swing candidates",
           "created_at": 1755683642, "updated_at": 1755683642,
           "symbols": ["AAPL", "MSFT"]}
}
Fields
FieldTypeNote
id, created_at, updated_atintserver-set
symbolsarray of stringpresent when the body carried it; lists membership actually written — an unknown symbol is refused and absent from the array
Errors
StatusCodes
400invalid_body · missing_field · invalid_value
401unauthorized
403forbidden
429rate_limited
409conflict (a name another of your rows already carries, named in details)
500internal
curl -X POST -H "Authorization: Bearer $SF_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Swing candidates", "symbols": ["AAPL", "MSFT"]}' \
  https://c-staging.stocksfast.io/api/v1/watchlists

→ 201
{"data": {"id": 7, "name": "Swing candidates", "symbols": ["AAPL", "MSFT"], ...}}
GET /watchlists/<id>read_watchlists · no plan · CRUD read bucket, 90/60s · no credits
GET https://c-staging.stocksfast.io/api/v1/watchlists/<id>
Path parameters
FieldTypeRequiredDefaultNotes
idintyes—one of your own; another account's id → 404
200
{
  "data": {"id": 7, "name": "Swing candidates",
           "created_at": 1755683642, "updated_at": 1755683642,
           "symbols": ["AAPL", "MSFT"]}
}
Fields
FieldTypeNote
id, created_at, updated_atint—
symbolsarray of stringalways present here, empty on a watchlist with no members
Errors
StatusCodes
401unauthorized
403forbidden
429rate_limited
404not_found
500internal
curl -H "Authorization: Bearer $SF_KEY" \
  https://c-staging.stocksfast.io/api/v1/watchlists/7

→ 200
{"data": {"id": 7, "name": "Swing candidates", "symbols": ["AAPL", "MSFT"], ...}}
PATCH /watchlists/<id>read_watchlists + write_watchlists · no plan · CRUD write bucket, 40/60s · no credits
PATCH https://c-staging.stocksfast.io/api/v1/watchlists/<id>
Path parameters
FieldTypeRequiredDefaultNotes
idintyes—one of your own; another account's id → 404
Body, applied as an overlay onto the stored watchlist
FieldTypeRequiredDefaultBounds
namestringnounchangednon-blank, at most 100 characters
symbolsarray of stringnomembership unchangedat most 1000 entries, each at most 10 bytes; a present array replaces the whole membership, [] clears it
Notes
FieldEffect
Bodyrequired — an absent or non-object body answers 400
200
{
  "data": {"id": 7, "name": "Swing candidates",
           "created_at": 1755683642, "updated_at": 1755690118,
           "symbols": ["AAPL", "NVDA"]}
}
Fields
FieldTypeNote
symbolsarray of stringpresent when the body carried it; a membership replace that fails leaves the prior membership in place and the reply omits symbols
Errors
StatusCodes
400invalid_body · invalid_value
401unauthorized
403forbidden
429rate_limited
404not_found
409conflict (a name another of your rows already carries, named in details)
500internal
curl -X PATCH -H "Authorization: Bearer $SF_KEY" \
  -H "Content-Type: application/json" -d '{"symbols": ["AAPL", "NVDA"]}' \
  https://c-staging.stocksfast.io/api/v1/watchlists/7

→ 200
{"data": {"id": 7, "name": "Swing candidates", "symbols": ["AAPL", "NVDA"], ...}}
DELETE /watchlists/<id>read_watchlists + write_watchlists · no plan · CRUD write bucket, 40/60s · no credits
DELETE https://c-staging.stocksfast.io/api/v1/watchlists/<id>
Path parameters
FieldTypeRequiredDefaultNotes
idintyes—one of your own; another account's id → 404
204
Side effects
FieldEffect
—the watchlist's symbols go with it
Errors
StatusCodes
401unauthorized
403forbidden
429rate_limited
404not_found
500internal
curl -X DELETE -H "Authorization: Bearer $SF_KEY" \
  https://c-staging.stocksfast.io/api/v1/watchlists/7

→ 204
POST /watchlists/<id>/add-symbolswrite_watchlists (write-only; no read floor) · no plan · CRUD write bucket, 40/60s · no credits
POST https://c-staging.stocksfast.io/api/v1/watchlists/<id>/add-symbols
Path parameters
FieldTypeRequiredDefaultNotes
idintyes—one of your own; another account's id → 404
Body
FieldTypeRequiredDefaultBounds
symbolsarray of stringyes—at most 1000 entries, each at most 10 bytes; [] is accepted and adds nothing
200 — the single-resource envelope
{
  "data": {
    "added_count": 2,
    "skipped_count": 1,
    "invalid_count": 0,
    "watchlist": {
      "id": 7, "user_id": 7, "name": "Swing candidates",
      "created_at": 1755683642,
      "updated_at": 1755690118,
      "symbols": ["AAPL", "MSFT", "NVDA"]
    }
  }
}
Fields
FieldTypeNote
added_countint—
skipped_countintalready on the list
invalid_countintmarket data does not carry it
watchlistobjectepoch-second timestamps, matching the collection routes for the same columns
Errors
StatusCodes
400invalid_body · invalid_value
401unauthorized
403forbidden
429rate_limited
404not_found
500internal
curl -X POST -H "Authorization: Bearer $SF_KEY" \
  -H "Content-Type: application/json" -d '{"symbols": ["AAPL", "MSFT", "NVDA"]}' \
  https://c-staging.stocksfast.io/api/v1/watchlists/7/add-symbols

→ 200
{
  "data": {
    "added_count": 2,
    "skipped_count": 1,
    "invalid_count": 0,
    "watchlist": {
      "id": 7, "user_id": 7, "name": "Swing candidates",
      "created_at": 1755683642,
      "updated_at": 1755690118,
      "symbols": ["AAPL", "MSFT", "NVDA"]
    }
  }
}
POST /watchlists/<id>/remove-symbolswrite_watchlists (write-only; no read floor) · no plan · CRUD write bucket, 40/60s · no credits
POST https://c-staging.stocksfast.io/api/v1/watchlists/<id>/remove-symbols
Path parameters
FieldTypeRequiredDefaultNotes
idintyes—one of your own; another account's id → 404
Body
FieldTypeRequiredDefaultBounds
symbolsarray of stringyes—at most 1000 entries, each at most 10 bytes; [] is accepted and removes nothing
200 — the single-resource envelope
{
  "data": {
    "removed_count": 1,
    "watchlist": {
      "id": 7, "user_id": 7, "name": "Swing candidates",
      "created_at": 1755683642,
      "updated_at": 1755697451,
      "symbols": ["AAPL", "MSFT"]
    }
  }
}
Fields
FieldTypeNote
removed_countinta symbol not on the list contributes nothing
watchlistobjectepoch-second timestamps, matching the collection routes for the same columns
Errors
StatusCodes
400invalid_body · invalid_value
401unauthorized
403forbidden
429rate_limited
404not_found
500internal
curl -X POST -H "Authorization: Bearer $SF_KEY" \
  -H "Content-Type: application/json" -d '{"symbols": ["NVDA"]}' \
  https://c-staging.stocksfast.io/api/v1/watchlists/7/remove-symbols

→ 200
{
  "data": {
    "removed_count": 1,
    "watchlist": {
      "id": 7, "user_id": 7, "name": "Swing candidates",
      "created_at": 1755683642,
      "updated_at": 1755697451,
      "symbols": ["AAPL", "MSFT"]
    }
  }
}
GET /watchlists/<id>/export-tradingviewread_watchlists · Premium · CRUD read bucket, 90/60s · no credits
GET https://c-staging.stocksfast.io/api/v1/watchlists/<id>/export-tradingview
Path parameters
FieldTypeRequiredDefaultNotes
idintyes—one of your own; another account's id → 404
200 — text/plain, outside the envelope, with Content-Disposition: attachment
NASDAQ:AAPL
NASDAQ:MSFT
NYSE:BRK.B
Format
Rule—Detail
one line per member—EXCHANGE:SYMBOL, no trailing newline; an empty watchlist answers an empty body
share-class suffix—dot form preserved
unresolved exchange—labelled NASDAQ
Errors
StatusCodes
401unauthorized
403forbidden · upgrade_required (plan gate, checked before the id is read)
429rate_limited
404not_found
500internal
curl -H "Authorization: Bearer $SF_KEY" \
  https://c-staging.stocksfast.io/api/v1/watchlists/7/export-tradingview

→ 200
NASDAQ:AAPL
NASDAQ:MSFT
NYSE:BRK.B
POST /watchlists/import-tradingviewwrite_watchlists (write-only; no read floor) · Premium · CRUD write bucket, 40/60s · no credits
POST https://c-staging.stocksfast.io/api/v1/watchlists/import-tradingview
Body — multipart/form-data
FieldTypeRequiredDefaultBounds
filefile partyes—.txt, at most 5 MB, UTF-8; at most 1000 symbols, each at most 10 bytes
watchlist_idstringnononeone of your own watchlists; present means add to that list
watchlist_namestringnouploaded filename, else Imported Watchlistused when creating a new list
200 (add-to-existing) or 201 (create-new) — the single-resource envelope
{
  "data": {
    "watchlist": {
      "id": 9, "user_id": 7, "name": "tv-export",
      "created_at": 1755683642,
      "updated_at": 1755683642,
      "symbols": ["AAPL", "MSFT"]
    },
    "added_count": 2, "skipped_count": 0, "invalid_count": 1,
    "added_symbols": ["AAPL", "MSFT"], "invalid_symbols": ["ZZZZ"],
    "unsupported_count": 1, "unsupported_symbols": ["LSE:VOD"],
    "malformed_count": 1, "malformed_symbols": ["not a line"]
  }
}
Fields
FieldTypeNote
watchlistobjectepoch-second timestamps
invalid_*int/arraysymbols the market data does not carry
unsupported_*int/arraylines on an exchange this data does not cover
malformed_*int/arraylines that never split into EXCHANGE:SYMBOL
Notes
FieldEffect
upload checksrun in order — extension, size, encoding — and all three refuse before any watchlist is written; a file parsing to nothing is still 200, with the parse errors on the response
Errors
StatusCodes
400missing_field · invalid_value (an upload check names file in details)
401unauthorized
403forbidden · upgrade_required (plan gate, runs ahead of the upload checks)
429rate_limited
404not_found
409conflict (the create-new branch's watchlist_name already held by another of your watchlists, named in details)
500internal
curl -X POST -H "Authorization: Bearer $SF_KEY" \
  -F "file=@watchlist.txt" -F "watchlist_name=tv-export" \
  https://c-staging.stocksfast.io/api/v1/watchlists/import-tradingview

→ 201
{
  "data": {
    "watchlist": {
      "id": 9, "user_id": 7, "name": "tv-export",
      "created_at": 1755683642,
      "updated_at": 1755683642,
      "symbols": ["AAPL", "MSFT"]
    },
    "added_count": 2, "skipped_count": 0, "invalid_count": 1,
    "added_symbols": ["AAPL", "MSFT"], "invalid_symbols": ["ZZZZ"],
    "unsupported_count": 1, "unsupported_symbols": ["LSE:VOD"],
    "malformed_count": 1, "malformed_symbols": ["not a line"]
  }
}

Errors

CodeStatusCause
bad_request400A malformed request refused below the routes, before any handler ran.
invalid_body400The body did not parse: absent, not application/json, unparseable, or parsed but not a JSON object.
missing_field400A required field is absent.
invalid_value400A value the route refuses: a wrongly-typed field, an over-cap or malformed array entry, a filter resolving to no symbols, a blank or over-long name, a malformed path id, an unsupported TradingView upload, or an expression that will not compile on a saved-scan write with STONQL_CAT_INPUT -- any other compile category answers internal_compiler_error below instead. The saved-scan, tag and watchlist routes name the finer reason in details for a conflicting inline tag, an over-cap/malformed tag list entry, or a saved-scan expression that fails to compile with STONQL_CAT_INPUT; every other 400 the generic dispatch built, whether from its own type check or a resource's pre_validate hook, carries no details, reason in message only.
compile_error400A scan validate/execute route's expression did not compile on the caller's own input. message is the compiler's own diagnostic.
internal_compiler_error500The compiler or this app faulted on its own compiling an expression, on a scan validate/execute route or a saved-scan write. message is a generic string; the diagnostic is logged server-side only.
unauthorized401The key is missing, malformed, unknown, revoked or expired. The answer never says which.
forbidden403The key authenticated but does not carry the route's permission. The answer never says which permission.
insufficient_credits403No scan credits and no Premium, on an execute route. The body carries balance and cost alongside code/message, naming the shortfall.
upgrade_required403The saved-scan writes and the two TradingView actions refuse a caller whose plan does not cover the action: over the free-tier saved-scan quota, saved data is read-only, or the action itself is Premium-gated.
not_found404No such row, including one belonging to another account; or an unknown symbol on the detail route.
method_not_allowed405A registered path hit with a method it neither accepts nor gets implicitly. HEAD on a path with a registered GET is not a wrong verb -- it replays the GET handler and its gates exactly, body suppressed after the fact: same status, headers and Content-Length. OPTIONS on any registered path is a fourth outcome, always 204, and never reaches a handler. Either way the response carries an Allow header naming the accepted methods plus implicit HEAD/OPTIONS.
conflict409A unique constraint refused the write: a saved-scan or watchlist name, a tag (user, slug), or the TradingView import's create-new watchlist_name, another of your rows already carries. details names the collided field.
idempotency_key_reused409An execute route's Idempotency-Key was reused with a different request. See Idempotency.
idempotency_in_flight409An execute route's Idempotency-Key is still reserved by a request in flight. Wait out Retry-After. See Idempotency.
rate_limited429The route's per-key budget is spent. Wait out Retry-After.
too_many_concurrent_executes429The execute routes share a cap on scans in flight, checked before the run starts. No rate window is involved and no credit is spent; the RateLimit-* headers still describe the execute bucket, which was already spent. Wait out Retry-After, which is short.
internal500Something broke on our side. The request was fine. On the execute routes, a response page that fails to build is refused this way, and no credit is spent. If it persists, report it with X-Request-Id from the response.
credit_error500The credit ledger write failed after the run. The built page is discarded; no results are served. If it persists, report it with X-Request-Id from the response.
engine_error500The scan query failed in the engine. If it persists, report it with X-Request-Id from the response.
client_errorany other 4xxThe class generic for a 4xx no row above names. Read the status.
server_errorany other 5xxThe class generic for a 5xx no row above names. Retry.
store_unavailable503The market data store would not open, is empty where a default scan date had to be resolved, or a read like universe reaches a store that refuses its query. Wait out Retry-After. An execute route also answers it when an Idempotency-Key reservation could not be claimed. No credit is spent on the execute routes.

Response headers

Look a header up here when you see it on a response. Nothing in this table is something you send — for that, see Base URL and request headers.

Header On Meaning
X-API-Stability every response beta, success and error alike. See Stability.
X-Request-Id every response The correlation id for this request, yours if you sent a well-formed one. Quote it when reporting internal, internal_compiler_error, engine_error or credit_error.
WWW-Authenticate 401 Bearer error="invalid_token" when a Bearer credential was presented and rejected; bare Bearer when none was presented.
Idempotency-Replayed 200 true when the response is a stored replay rather than a fresh run; absent otherwise.
Retry-After 429 Seconds left in the rate-limit window — except the execute routes' concurrency-cap refusal, a fixed short wait unrelated to any window.
Retry-After 409 An Idempotency-Key still reserved by a request in flight only; a short fixed hint.
Retry-After 503 30, a fixed hint; the condition clears on its own.
RateLimit-Limit metered response The spent bucket's ceiling. See Rate limits.
RateLimit-Remaining metered response Attempts left in that bucket after this request.
RateLimit-Reset metered response Seconds until that bucket's window rolls; matches Retry-After on a rate-limit 429, but not on the execute routes' concurrency-cap 429, whose bucket is spent ahead of that check.
ETag GET /catalog, GET /openapi.json A strong validator over the rendered body, stable for the life of the process. Present on 200 and 304 alike.
ETag GET /universe A strong validator that moves with the corpus's publish generation (last_available_date, universe_size) and the timeframes table, not the rendered bytes. Present on 200 and 304 alike; absent on every error status.
ETag GET /symbols, GET /symbols/<symbol> A strong validator that is a digest of the rendered response bytes. Present on 200 and 304 alike; absent on every error status.
Cache-Control GET /me no-store. credit_balance must never come from a shared or client cache.
Cache-Control GET /catalog, GET /openapi.json public, max-age=3600, on 200 and 304 alike.
Cache-Control GET /universe, GET /symbols, GET /symbols/<symbol> public, max-age=60, on 200 and 304 alike.
Content-Disposition 200 TradingView export only: attachment; filename="watchlist-<name>-YYYY-MM-DD.txt".
Allow 405, and 204 on OPTIONS The path's registered methods, plus implicit HEAD (when GET is registered) and implicit OPTIONS (always). See Errors.

More