A normalized, permission-aware API for market prices, SentimenTrader indicators, paired series, snapshots, recent bulk values, intraday history, and account-owned custom indicators.
Base URL
st-tools.sentimentrader.com/data/v1
Protocol
HTTPS · GET only
Authentication
Basic · Cookie · optional OIDC Bearer
Format
JSON envelope
!
No legacy compatibility for REST. This contract does not expose former legacy route names, numeric-key maps, or double-encoded JSON. New integrations should use /data/v1 or the canonical Market Data MCP tools; see the REST Data API page for the legacy application it replaces.
i
Every endpoint below pairs a copyable request with its response payload; parameters and behavior notes sit beside the code, and the full envelope contract is documented in Response Contract.
Endpoint directory
Group
Endpoint
Purpose
Catalog
/data/v1/catalog
Search the entitled indicator catalog
Prices
/data/v1/prices/{symbol}
Bounded daily OHLCV history for one tradable symbol
Indicators
/data/v1/indicators/{indicator}/series
One indicator with its market's OHLCV
Pairs
/data/v1/pairs
Align one symbol with up to 10 indicators
Snapshots
/data/v1/snapshots
Latest or dated point-in-time values
Recent
/data/v1/recent
Bulk values for all or selected indicators over the last N trading days
Intraday
/data/v1/intraday/{indicator}
Bounded intraday history for one identifier
Custom
/data/v1/custom-indicators
List custom indicators owned by the account
Custom
/data/v1/custom-indicators/{indicator_url}/series
Read one owned custom indicator series
Authentication and authorization
HTTP calls use Basic Auth, a valid session cookie, or an OIDC Bearer access token when OAuth is enabled. Access is layered: an authenticated local account, the data_api app gate (including legacy REST Data API compatibility), then per-resource entitlement or custom-indicator ownership.
Layer
Behavior
Credentials
Basic and session credentials resolve a SentimenTrader account directly. An enabled OIDC Bearer token resolves its exact issuer/subject mapping to the same local account.
App gate
The data_api app is fail-closed. An explicit Market API row grants or denies access; when no row exists, accounts already authorized for the legacy REST Data API by a current Data plan (or the legacy RGNCM exception) are admitted automatically. An unavailable permission backend returns 503 instead of a silent pass-through.
Indicator tiers
Indicator access keeps the existing full, selected + base, and selected-only tiers on a per-indicator basis.
Entitlement read failure
When the entitlement query itself fails, the request fails closed with 502 upstream_error — a data-tier outage, deliberately distinct from the 503 authorization_backend_unavailable used for the app gate. The cause is logged server-side; the body stays generic.
Single-series requests
Endpoints that address one indicator reject an unentitled identifier with 403 before any data is queried.
Multi-result requests
Catalog and snapshot endpoints filter their output to entitled records; pair requests query only the entitled subset and report omitted identifiers in warnings.
Custom indicators
Ownership is enforced against the authenticated account. The account identity is derived from authentication and cannot be supplied by callers.
Transport
Always use HTTPS. Basic credentials are encoded, not encrypted by the header itself.
Response contract
Every successful endpoint returns one JSON object with three parts: data rows, descriptive meta, and a warnings list. Metadata is part of the contract — report it rather than recomputing it client-side.
Per-series provenance, for example stocks.eod_stocks for prices or catalog-resolved MySQL table for indicators.
fallback_used
True when the price leg fell back to the close-only index table.
data_as_of
Newest observation timestamp present in data.
retrieved_at
UTC timestamp of the server-side retrieval.
frequency / calendar / alignment / fill_policy / adjustment_policy
How the rows were produced: source-native or requested frequency, trading calendar, join/resample alignment, fill policy, and price adjustment policy.
row_count / cell_count / response_bytes
Serialized size accounting for the payload.
truncated
Whether the result was cut by limit. When it is true, narrow the date range or drop limit for the complete series.
next_cursor
Published by /data/v1/recent only, because that is the one source that can continue: it returns an opaque cursor when another page exists and null on the last page. Every other endpoint omits the field, so a truncated history response can never be paged — narrow the range instead.
truncated_to_trial_window
Present on series endpoints when a trial account received only the most recent 90 observations.
warnings
Human-readable notices: omitted unentitled indicators, indicators with no data in range, and trial-window notices.
Query budget
Bound
Contract
Row limit
Cannot exceed 5,000. Omitted, the list-shaped endpoints return 1,000 rows, while /prices, /indicators/…/series, and /pairs return the complete series. Continue /data/v1/recent with meta.next_cursor; for other truncated results, narrow the date range.
Date span
Only /data/v1/custom-indicators/{url}/series rejects a range longer than 7,305 days (20 years + 5). Every other endpoint accepts any span, so bound the range yourself.
Cell budget
When a limit is supplied, limit × columns must stay within 100,000 response cells per request; a pair request counts every indicator it carries.
Response size
Serialized payloads must stay within the 5,000,000 byte response budget on every response; oversized requests fail with 422.
Pair and snapshot
Pairs accept 1–10 indicators; /data/v1/snapshots accepts up to 50. Recent bulk takes trading_days 1–90 and 1–50 indicators.
Trial accounts
Trial accounts receive the most recent 90 observations only, with a matching warning.
Why these numbers
5,000 rows ≈ twenty years of daily data (one research window per call); 100,000 cells is that row cap across ~20 columns; the 5 MB budget leaves ~2× margin over the largest realistic payload (ten indicators × 5,000 rows ≈ 2.5 MB); 90 observations and 1–50 indicators are what the pre-generated recent store holds; 10 pair indicators is a column budget on top of the market's OHLCV.
Date and identifier formats
Input
Contract
start / end
Strict YYYY-MM-DD for catalog, prices, indicators, pairs, snapshots, and custom series.
start / end (intraday)
ISO-8601 datetimes. Timezone awareness must be consistent: both aware or both naive.
Identifiers
Trimmed, printable, length-capped (64 characters for price symbols, 255 for indicators, 2048 for custom indicator URLs). Discover canonical identifiers from the catalog first; do not manufacture them from display names.
Indicator catalog
Discover indicator identifiers before requesting history or pair data. Catalog rows are metadata; indicator (identical to url, kept for legacy callers) is the identifier used by every other endpoint.
GET/data/v1/catalog
Searches the indicator catalog, filtered to the caller's entitlement tier before pagination.
Envelope type Envelope<CatalogRecord[]>. Meta carries query, count (entries matched), catalog_size (entries searchable by this account), entitlement_tier, and suggestions (populated only when the query misses or matches an identifier alone).
Price history
Complete OHLCV history for one tradable symbol, daily by default. The whole available range is returned unless you pass limit, which returns the most recent N rows and reports meta.truncated.
GET/data/v1/prices/{symbol}
OHLCV rows ordered oldest to newest, resampled to the requested frequency; index symbols are served close-only from the index table.
Envelope type Envelope<PriceRecord[]>. Meta reports symbol, fallback_used, frequency: daily, calendar: business-day, and the explicit adjustment policy raw_close_excluding_split_rows.
Indicator series
One entitled SentimenTrader indicator over its full available range, returned with the OHLCV of the market the indicator belongs to. The indicator is forward-filled onto the daily grid and carried as {indicator}_close (and {indicator}_sentiment when requested), the same naming pair requests use. Pass limit for the most recent N rows.
GET/data/v1/indicators/{indicator}/series
Observations ordered oldest to newest, with optional sentiment readings.
Envelope type Envelope<IndicatorRecord[]>. Trial accounts receive the most recent 90 observations, reported via truncated_to_trial_window.
Paired series
Align one tradable symbol with up to 10 indicators for comparison, charting, or downstream analysis. Prices are aggregated with OHLCV semantics; each indicator becomes a {indicator}_close column. indicators is optional: omit it to receive the market's own OHLCV series. The full series is returned — pass limit only when you want the most recent N rows, and note that meta.truncated then reports true.
GET/data/v1/pairs
Price series joined with each indicator on a shared grid, then resampled to the requested frequency.
DTdate stringOpen/High/Low/Close/Volumeprice columns{indicator}_closeone per indicatormeta.indicator_market_symbolsindicator → market symbol
Envelope type Envelope<PairRecord[]>. Resample rules: business days (daily), Friday weeks (weekly), business month ends (monthly), business quarter ends (quarterly).
Snapshots
Latest or dated point-in-time values. Prefer a snapshot over fetching history and taking the last row. Multi-record results are filtered to the caller's entitled indicators.
GET/data/v1/snapshots
One row per indicator — latest stored values, or the point-in-time values for one calendar date.
Request
# Latest values (omit date), or a specific date:curl-u"you@example.com:your-password" \
"https://st-tools.sentimentrader.com/data/v1/snapshots?indicators=etf_spy&date=2026-09-10"
Envelope type Envelope<SnapshotRecord[]>. Meta reports date, count, and entitlement_filtered.
Recent bulk data
Tall-row bulk reads over the SPX trading calendar: every observation of every entitled indicator for the last N trading days. Use this instead of looping per-indicator history calls.
GET/data/v1/recent
One row per indicator observation (DT, indicator, close, sentiment) inside the trading-day window.
Request
# Last 5 trading days for all entitled indicators:curl-u"you@example.com:your-password" \
"https://st-tools.sentimentrader.com/data/v1/recent?trading_days=5&limit=1000"
Envelope type Envelope<RecentReading[]>. Meta reports trading_days, resolved_as_of_date, indicator_count, offset, and next_cursor.
Intraday history
A separate, higher-volume capability. Use it only when intraday resolution is material; daily price or indicator history answers most questions with less data.
GET/data/v1/intraday/{indicator}
Bounded intraday records for one entitled identifier, ordered oldest to newest.
Envelope type Envelope<IntradayRecord[]>. Provenance is stocks.intraday.
Custom indicators
Indicators created in the SentimenTrader web application, scoped to the authenticated account. Discover them with the list endpoint before requesting data; ownership is enforced by the server.
GET/data/v1/custom-indicators
Lists custom indicators owned by the authenticated account, ordered newest first.
Envelope type Envelope<CustomIndicator[]>. Records may also include calculation settings such as indicator_1_url, indicator_2_url, upper_extreme_value, lower_extreme_value, ma, and benchmark.
DTdate stringclosenumbersymbolstring · indicator name
Envelope type Envelope<CustomIndicatorReading[]>. Trial accounts receive the most recent 90 observations only.
MCP tools
The mounted MCP endpoint exposes intent-oriented workflows over the same application service. Its seven canonical tools aggregate related reads where useful, preserve the normalized envelope, and attribute every call to the authenticated user.
Canonical tool
Shared capability
Purpose
search_market_data
/data/v1/catalog
Indicator discovery and entitlement visibility
get_market_history
price / indicator / pair
Intent-based facade over the three history endpoints
get_market_snapshot
/data/v1/snapshots
Current/latest values or a specific date
get_recent_market_data
/data/v1/recent
Bulk recent values with cursor pagination
get_intraday_history
/data/v1/intraday/{indicator}
Intraday history for one identifier
list_custom_indicators
/data/v1/custom-indicators
Discover account-owned custom indicators
get_custom_indicator_data
/data/v1/custom-indicators/{indicator_url}/series
Read one owned custom indicator series
Deprecated aliases
Three aliases remain registered for one compatibility cycle. New clients should use get_market_history, which takes mode plus that mode's parameters directly.
Deprecated alias
Canonical replacement
get_price_history
get_market_history with mode="price"
get_indicator_history
get_market_history with mode="indicator"
get_pair_data
get_market_history with mode="pair"
Transport
Behavior
Mounted endpoint
The application-mounted MCP endpoint uses per-request Basic Auth or, when enabled, OIDC Bearer. Both reuse the same local account and app-gate checks, and every tool call is attributed to that user.
Standalone SSE
Standalone SSE omits these tools because it does not have a per-request identity to authorize against.
Error shape
Domain failures surface as {"error": {"code", "status", "message"}} with the same codes as the HTTP status table below.
Errors, access, and limits
Domain errors carry a stable status code and error code. Permission denials and backend failures are distinct: a user without entitlement sees 403, one whose entitlement cannot be read sees 502, and an unavailable app-access policy backend fails closed with 503.
Status
Code
Meaning
401
authentication_required
Authentication is missing or invalid.
403
access_denied
The app, indicator, or custom resource is not allowed for this account.
404
not_found
An allowed resource does not exist in the requested range.
422
invalid_parameters
A parameter or the query budget is invalid.
429
—
The configured app quota is exceeded.
502
upstream_error
An upstream service failed, including a data-tier read while loading entitlements.
503
authorization_backend_unavailable
The app-access policy backend is unavailable; the request fails closed.
504
upstream_timeout
An upstream service timed out.
MCP tools return every failure as a result with isError: true and the body {"error": {"code", "status", "message", "details"}}, so a client can branch on the flag and on the code. Rejected arguments report invalid_parameters with error.details.fields naming the offending field, its constraint, and the value received; an unsupported parameter name is reported rather than ignored. Pydantic-internal text (generated model names, error-type tags, documentation links) is never returned.
Trial access
Trial accounts receive the most recent 90 observations only, on every series endpoint. The response reports truncated_to_trial_window in meta and adds a warning so callers can state the limitation instead of treating the tail as full history.
Request volume
Cache catalog responses, and prefer a pair request, recent-bulk request, or snapshot over many repeated single-series calls. Pair and history series come back in full; when you do pass a limit, meta.truncated tells you a cap applied. Continue recent-bulk pages only with the returned opaque meta.next_cursor, and page other ranges by narrowing the date range.
Budgets
The query budget table above lists every request-level bound, and the failure semantics are the same for all of them: a violated bound fails the call with invalid_parameters rather than returning a partial series, while an applied default page sets meta.truncated instead of cutting the data off silently.