Changelog
Notable changes to the ByteTree API’s public contract.
2026-07-28 — ByteTree MCP moves to per-customer API keys
Breaking for MCP clients. ByteTree MCP no longer accepts the shared preview key that
covered the early-access window. Connect with your own ByteTree API key instead, sent
as Authorization: Bearer <your-api-key>. The key must carry the api+mcp capability,
described on the Authentication page. A key that works against the
REST API is not on its own proof that MCP is enabled on it — a 403 means exactly that
and nothing else.
The cutover happened on 2026-07-24; a client still configured with the preview key is
already receiving 401. Endpoints and tool behaviour are unchanged — only the credential
is different.
Keys are issued per customer and revocable individually, and what a key can read through MCP is exactly what its scope allows, enforced on every request rather than only at connection time. Keys are also environment-specific: one issued against a given ByteTree environment will not authenticate against another.
MCP — Getting Started now documents the credential, and the responses worth distinguishing:
401— no key, or a key we could not validate.403— valid key, but not provisioned for MCP access. Deliberately distinct from401: nothing is wrong with the key, it just needs MCP enabled.503— we could not verify the key right now. Temporary, and not a reason to rotate credentials.405— returned onGETorDELETE. The endpoint isPOST-only.
Also corrected on the MCP pages
Three descriptions that were wrong or missing, none of them a change in behaviour:
- Tool count. ByteTree MCP exposes 14 tools. The Tool Reference
documents the 13 that carry data; the fourteenth is
ping, a health check returningpongand a timestamp, and it is now named so the count reconciles. Your client may show fewer if you have individual tools switched off in its settings. get_regime_transitionsdefaults to a 90-day window when neitherfromnortois supplied. This has been live for some time and the 2026-07-23 entry already referred to it, but the tool’s own page never said so — a date-less historical scan was silently returning 90 days.${VAR}expansion in client configs. The previous guidance said neither Claude Desktop nor Claude Code expands${VAR}. Claude Code does; Claude Desktop does not, and additionally does not read your shell profile. Getting this backwards is the single most common cause of an unexplained401.- Claude Code config location. The page named a file Claude Code does not read, so a
correctly-written stanza put there would never load — and with no request ever sent,
there was no error to diagnose it by. Use
claude mcp add, or edit.mcp.jsonin the project root or~/.claude.json. The Claude Desktop paths were already correct, and that app now carries the reminder that it reads its config only at startup.
2026-07-23 — Unknown query parameters return 400; response-shape and universe-scope guidance
Second documentation catch-up of the day, covering a convention that has been live since 16 July and two places the docs described the API less accurately than they should.
Unknown query parameters are rejected
Passing a parameter an endpoint doesn’t accept returns
400 with the accepted list
echoed and a suggestion for close misspellings, rather than being silently ignored.
This is the behaviour you want: a silently-dropped filter returns unfiltered data with a
200, which looks plausible and is wrong. It is worth checking any client that passes
parameters speculatively — a stray or misspelled parameter that used to be ignored is
now an error. format is accepted on every endpoint and never counts as unknown, and
authentication is still checked first, so an invalid key returns 401.
Response shapes described as rules
The Response Conventions section previously
named which endpoints used the paginated envelope, and that list had gone stale — several
endpoints had adopted it since. Replaced with the rule that generates it: pagination
fields appear where you can meaningfully page, and data is an array everywhere except
/api/metrics/latest, whose data is an object keyed by symbol.
macro is not guaranteed
Documentation examples use macro, but the universes a key can reach depend on the
subscription behind it, and a key scoped to a single universe is a normal configuration.
Getting Started and Universes now
say so and point at GET /api/universes for the authoritative per-key list.
Compatibility
No wire change — all three describe behaviour already live. The 400 convention is the
one worth acting on if you send parameters the documentation doesn’t list.
2026-07-23 — /api/universes response fields, a bounded default on regime transitions, and a degraded-data flag on summary
A documentation catch-up pass. Three shipped behaviours were not described on this site; all three are live and none of them changes today.
/api/universes — membership fields and the paged envelope
The Universes page described an older, smaller response. It now documents what the endpoint actually returns:
- The paged envelope
{count, total, limit, offset, data}, and thelimit/offsetparameters that drive it. label,sourceandmemberCounton every universe, plusrankMetric,rankLimitandrankDirectionon ranked universes.sourceisall(the whole active screen —macro) orrank(a top-N ranked screen —public-50,global-trends-200), matching the wording already used on the MCP surface.- The list is scope-filtered: universes outside your key’s scope are omitted from
datarather than returned as an error. - A universe can appear with an empty
dimensionsarray — this means “inherits every dimension frommacro”, not “unavailable”.public-50is in that state today.
The page previously said assets are grouped across four dimensions; it is five — Region, Sector, Industry, Type, and Country — as the Universe Groups page has always stated.
/api/metrics/regime-transitions — 90-day default window
A call that omits from now applies a 90-day look-back instead of scanning all
history, anchored on to when supplied. Every response carries
appliedRange —
{from, to, defaulted} — reporting the window actually queried. An explicit from is
honoured unchanged. This matches the equivalent MCP tool, so both surfaces bound an
unqualified query identically.
/api/summary — priceDataDegraded
When the price-data section can’t be produced for a request, the response now carries
priceDataDegraded: true with an empty priceData
array, rather than an empty array alone — so an empty section is distinguishable from a
dataset that genuinely holds no price data. Such responses are sent Cache-Control: no-store. The flag is absent on healthy responses; test for its presence, not for
false.
generatedAt on this endpoint is also documented more precisely: it timestamps the
price-data section — the stalest part of the response — and so may be older than the
request.
Compatibility
No wire change. All three behaviours were already live; this pass only documents them.
appliedRange and priceDataDegraded are additive response fields — existing clients
that ignore unknown fields are unaffected. The one behaviour worth checking is the
regime-transitions default: if you relied on a date-less call returning all history, pass
an explicit from. In practice such a call was more often failing than returning slowly —
an unbounded scan could exceed the upstream time limit and surface as an error rather than
data — so the default is closer to a repair than a restriction.
2026-07-21 — BYT-367: public-50 universe documented; source / memberCount clarified
Follow-on to the 2026-07-20 MCP vocabulary alignment. public-50 — the top 50 stocks
by market cap — is now documented as a customer universe alongside macro and
global-trends-200, and the list_grouping_configs
source / memberCount fields are described precisely.
What changed
- Concepts — Universes and
get_group_metricsnow cover three customer universes:macro,public-50, andglobal-trends-200. list_grouping_configs’sourceis documented asall(the whole active screen —macro) orrank(a top-N ranked screen —public-50top 50,global-trends-200top 200, both by market cap);memberCountis the count of assets resolved into the universe.global-trends-200’s membership is clarified as a ranked screen (top 200 by market cap), not a hand-picked list — “curated” applies only to its region/sector/industry dimension mappings.
Compatibility
Documentation clarification — no wire change; per-asset and group metrics, and all universe slugs, are unchanged.
2026-07-20 — BYT-366: macro universe slug and universe/dimension filters on the MCP surface
The seeded universe formerly exposed as default on the MCP surface is now macro,
completing the alignment of MCP tool vocabulary with the universe slugs used across
the REST API. Alongside the rename, the combined grouping slug filter is replaced
by explicit universe + dimension parameters, and list_grouping_configs now
returns a per-universe summary instead of raw GroupingConfig documents.
What changed
- Universe slug
default→macroin every tool description, filter value, and thebytetree://glossaryresource. Global Trends’ undefined dimensions (type, country) now read as falling back tomacro. - Filters on
list_assets,get_regime_transitions, andget_metric_distribution: thegroupingslug parameter (e.g."global-trends-200/region") is replaced byuniverse(e.g."global-trends-200") +dimension(e.g."region");groupis unchanged. list_grouping_configsreturns one row per universe — slug, label, source, member count, natively defineddimensions,fallbacksinherited frommacro, and a per-dimensionconfigssummary — rather than a flat list of GroupingConfig documents.get_group_metrics’suniverseinput defaults to"macro".
Compatibility
Breaking for MCP clients holding the old vocabulary: universe: "default" and
grouping parameters no longer match anything — call
list_grouping_configs to refresh, and split any
stored universe/dimension slug into the two explicit parameters. The REST surface is
unchanged — it already speaks macro and takes the universe as a path segment; see
Universes. Entries below this one predate the rename and refer to the
seeded universe by its old name.
2026-07-15 — BYT-362: adjusted basis includes dividends (wording correction)
A documentation correction, not a contract change. Since the 2026-05-05 cutover the default adjusted basis has included dividend distributions as well as splits; the pages introduced with that cutover described it as “split-adjusted”, which was imprecise. The wire is unchanged — only the description was wrong.
The precise contract
- The adjusted series is seeded from a fully split- and dividend-adjusted price history.
- It is maintained forward against the internal splits ledger: new splits rebase the series as they occur; dividend distributions after the seed are not retro-applied and flow through as ordinary price movement. A parity audit against the upstream fully-adjusted series watches for material drift.
The Data Model page now carries the full contract, including what this means for reconciliation.
What this means for consumers
If you reconcile closeUsd (or default OHLCV close) against an exchange print or a
split-only-adjusted vendor series, historical values for dividend-paying assets will
sit below both — the cumulative distributions are folded back into history, by roughly
the compounded yield. The *Raw fields and ?raw=true remain the market-printed view
and are unaffected.
Compatibility
No wire change. The endpoint pages, Data Model, and MCP concept pages now say “adjusted” / “split- and dividend-adjusted” where they previously said “split-adjusted”. (As of 2026-07-15, all stock, ETF, and index assets are on this uniform basis across their full history.)
2026-06-12 — BYT-353: Global Trends universe is global-trends-200 on the MCP surface; sector now natively defined
The Global Trends universe key in grouping data changed from globalTrends to
global-trends-200, aligning the MCP tool layer’s vocabulary with the universe slugs
used across the REST API. At the same time, Global Trends gained its own sector
dimension config, so sector no longer falls back to default.
What changed
- Grouping slugs returned by
list_grouping_configsand accepted bygroupingfilters readglobal-trends-200/<dimension>(e.g.global-trends-200/region). get_group_metricstakesuniverse: "global-trends-200".- Global Trends now defines region, sector, and industry; only type and country fall
back to
default.
Compatibility
Breaking for MCP clients holding the old key: globalTrends/* slugs and
universe: "globalTrends" no longer match any data — re-run
list_grouping_configs to refresh. On the REST
surface, which already speaks the customer universe slugs, the only visible change is
global-trends-200/sector now resolving to Global Trends’ own config rather than
returning the macro fallback (no fallback: true) — see Universes.
2026-06-12 — splits endpoints withdrawn from the public API
GET /api/splits and GET /api/splits/:symbol are no longer part of the public API
surface — they have moved to ByteTree’s internal operations layer, and their
documentation pages have been retired.
Why consumers don’t need them
All public OHLCV is split-adjusted by default — the splits ledger remains the
internal input that drives the adjustment, so charts, scores, and returns are unaffected
by corporate actions without any consumer-side handling. See Data Model
for how the adjustment works and ?raw=true on
GET /api/prices/:symbol for the forensic raw view.
(Basis wording corrected — the adjustment includes dividends as well as splits; see
the 2026-07-15 entry.)
Compatibility
Breaking for consumers of the two splits endpoints: requests to these paths now return
401. All other endpoints are unaffected.
2026-06-10 — BYT-337: API-key authentication is live
Key enforcement is now in force. Every documented endpoint
requires an API key — sent as Authorization: Bearer <key> — with one exception:
GET /api/health stays open as a liveness check. The Authentication
page documents the full model.
What changed
- Unauthenticated requests to protected endpoints return
401with the standard{ "error": "<message>" }envelope. - Every key carries a two-dimensional scope:
universes[](which universes it may read) andcapability(view|api+mcp— programmatic access requiresapi+mcp). - A valid key requesting a universe outside its scope receives
403; list endpoints return only in-scope rows. - Universe paths take the customer universe slugs (
macro,public-50, …) — see Universes for the vocabulary.
Compatibility
Breaking for keyless consumers: all reads except GET /api/health now require a key.
Obtain a key through your ByteTree.io account and send it as a bearer token — no other
request changes are needed.
2026-05-26 — BYT-327: ByteTree MCP docs refresh — naming convention, 90-day series default, 800 KB response guard
The public ByteTree MCP documentation gets a clarity pass on three already-shipped behaviours. No engine or MCP behaviour change — this is a docs-only update.
What’s clearer now
- Weekly / Daily naming convention. Customer-facing prose uses Weekly and Daily as the canonical labels for the two ByteTrend arcs; the wire-format suffixes
…200wand…30dappear as code references alongside the human labels (sobyteTrendCapr200wis the Weekly CAPR ByteTrend score,byteTrendCapr30dis the Daily CAPR ByteTrend score, and so on). See the naming-convention callout on the Concepts page. - 90-day series default.
get_metrics_seriesandget_pricesdefault to a 90-day window ending today when neitherfromnortois supplied — surfaced explicitly in the Tool Reference for both tools, in their parameter tables, and in the Overview’s response-size discipline section. - 800 KB response guard. The tool layer caps results at ~800 KB and returns an explicit error with narrowing guidance past the cap (rather than silently truncating). The Overview frames it as a server-published convention — surfaced via the MCP
instructionsfield at initialization — so connected agents adapt without needing to read this page first.
Compatibility
Documentation-only update. No engine or MCP behaviour change.
2026-05-22 — BYT-321: weekly rolling high/low window moves 30-week → 20-week
The four persisted weekly rolling high/low metrics move from a 30-week to a 20-week window and are renamed accordingly:
localMax30w → localMax20w, localMin30w → localMin20w, caprMax30w → caprMax20w, caprMin30w → caprMin20w.
What changed
- The weekly raw high/low fields are renamed
…30w→…20won Latest Metrics and Metrics Time-Series. - The old 30-week raw high/low fields are removed from the API surface.
- The
%dev-from-30-week deviation fields (devFromLocalHigh30w,devFromLocalLow30w,devFromCaprHigh30w,devFromCaprLow30w) are unchanged — they keep their 30-week basis and continue to drive trend-regime classification.
Compatibility
Breaking for consumers that read the weekly raw high/low fields by name: update …30w → …20w. The deviation fields, all daily windows (…20d / …30d), and the 200-week entries are unaffected.
2026-05-08 — BYT-285: documenting /api/fx/:symbol/prices
The canonical close-only FX price series endpoint shipped under BYT-281 is now documented. See the endpoint page.
Why now
BYT-278’s polymorphic-FX docs cutover deliberately scoped to BYT-274’s veneer surface and skipped the dedicated FX price route. This entry closes that doc gap.
Shape
GET /api/fx/:symbol/prices returns { symbol, count, data: [{ date, close }, ...] } with optional from / to ISO date filters. Close-only by design — FX is conventionally rendered as line series and the endpoint reflects that. 404 when the symbol has no FX records.
The polymorphic /api/prices/:symbol returns the same shape for FX symbols; the new dedicated route exists for FX-only consumers that want a zero-ambiguity surface within /api/fx/*.
Compatibility
Documentation-only update. No engine or API behaviour change.
2026-05-07 — BYT-299: trend-regime transition event log
A new public, no-auth endpoint GET /api/metrics/regime-transitions returning the cross-asset event log of trend-regime changes. See the endpoint page.
What’s new
- Filter by
window(200w-CAPR,200w-Local,30d-CAPR,30d-Local), targetregime,direction(into/outOf/any), date range, and the standard asset filter set (type,status,sector,q, universe group filters). - Per-row
metricsAtTransitionsnapshot, with the included keys controllable viafields(default:closeUsd, byteTrendCapr200w, byteTrendCapr30d, returnUsd1M, marketCapUsd, sharpe52w). 200w-CAPRrows carry the confirmation-lag tripletrendAgeAtTransition/confirmedTransitionDate/pendingStartDate; the three raw windows omit those fields entirely.- 5001-row short-circuit returns
400to keep the cap honest.type=fxis rejected. - Backs the public ByteTree MCP’s
get_regime_transitionstool.
Compatibility
Additive. No effect on existing endpoints.
2026-05-07 — BYT-298: cross-sectional metric distribution endpoint
A new public, no-auth endpoint GET /api/metrics/distribution returning per-timestep summary statistics (and, where applicable, histograms) of any allow-listed metric across the asset population. See the endpoint page.
What’s new
- 40-metric allow-list — the four ByteTrend score variants,
trendAge200w, plus 35 continuous metrics (returns, Sharpe, dollar volume, market cap, MA deviations, etc.). frequencyofdaily,weekly, ormonthly(defaultweekly);fromis required,todefaults to today.- Bucket-vs-percentile branching: discrete-score metrics emit a six-key
bucket-discretehistogram (0..5, zeros included);trendAge200wemits a six-keybucket-rangehistogram (lower bounds0,5,30,90,365,1825); continuous metrics emitstatsonly withmean,median,p25,p75,p90. - Standard asset filters (
type,status,sector,q, universe group filters). - Projected-rows cap at 5000;
type=fxis rejected. - Backs the public ByteTree MCP’s
get_metric_distributiontool.
Compatibility
Additive. No effect on existing endpoints.
2026-05-07 — BYT-297: dataset-overview endpoint /api/summary
A new public, no-auth endpoint that returns a single-screen dataset overview — asset counts grouped by (type, status), per-type PriceData date envelopes, and an FX-collection summary. See the endpoint page.
What’s new
GET /api/summary— purpose-built for tools that need a “what does this dataset look like?” answer in one round-trip without unwinding to the symbol level.- Backs the public ByteTree MCP’s
summarise_datasettool. - Standard
?format=json|toon|csvsupport. - 60-second public cache (
Cache-Control: public, max-age=60);generatedAtechoes the recompute moment within the window.
Compatibility
Additive. No effect on existing endpoints.
2026-05-05 — BYT-283: split-adjusted OHLCV by default, raw access via opt-in
/api/prices/:symbol now returns split-adjusted OHLCV by default — open, high, low, close, and volume are all derived from our own pure-function adjustment of RawPriceData against the internal splits ledger, idempotent and immune to upstream retroactive rewrites. The previous “raw close + sidecar metadata.rawClose” pattern is replaced by a clean toggle. (Wording corrected 2026-07-15: the adjusted basis has included dividends from the start — the series is seeded from a fully split- and dividend-adjusted history, with the splits ledger maintaining it forward. See the 2026-07-15 entry.)
What changed
GET /api/prices/:symbol— full OHLCV is split-adjusted by default. Pass?raw=truefor the unmodified market-printed values (forensic mode). See the endpoint page.GET /api/prices— list-formclose(and the rest of OHLCV) is split-adjusted. There is no list-level raw toggle; use the symbol endpoint when raw is needed./api/metrics/*closeUsd,capr,marketCapUsd— flipped from raw to split-adjusted semantics so quoted-state values match the basis used by every time-series metric.- New raw-access fields on
/api/metrics/*—closeRawUsd,caprRaw,marketCapRawUsdexpose the same trio on raw close.
Three-layer data model
A new conceptual reference page Data Model explains the three layers — RawPriceData (forensic archive, not exposed), PriceData raw fields (the provider’s evolving view, mutable), and own-computed split-adjusted OHLCV (idempotent, default) — and which view a given consumer wants.
FX
FX pairs aren’t subject to splits, so adjusted and raw views are identical. The /api/fx/* namespace and the polymorphic FX rows on /api/assets and /api/metrics therefore expose only the default values.
Compatibility
Breaking semantics for raw consumers. The fields closeUsd, capr, and marketCapUsd previously held raw values; they now hold split-adjusted values. Any consumer that needs the previous raw semantics must migrate to closeRawUsd, caprRaw, or marketCapRawUsd. There are no public consumers as of the cutover.
The OHLCV change on /api/prices/:symbol is also a semantics flip — the field names are unchanged. Consumers that explicitly want raw OHLCV must add ?raw=true.
2026-05-05 — BYT-274: polymorphic FX synthesis on /api/assets and /api/metrics
Asset and metric endpoints now resolve FX-pair symbols through a fallback path so polymorphic consumers (clients iterating across mixed asset types — stocks, crypto, FX) can use a single resolution path.
What changed
GET /api/assets?type=fxreturns FX pairs synthesised from rate data, with asset-only fields (isin,sector,industry,country,region,mktCap,mktCapUsd)nullandnamerendered in slash form (e.g."EUR/USD"). See Polymorphic FX rows.GET /api/assets/:idfalls back to a synthesised FX row when:idis a recognised FX-pair symbol with no registered Asset. MongoDB-ID lookups are not synthesised. See Polymorphic FX fallback.GET /api/metrics/:symbol/latestandGET /api/metrics/:symbol(series) fall back to FX rate data on PriceData miss, so the same client path resolves any tracked symbol.
Recommended surface for FX-only consumers
A dedicated /api/fx namespace ships alongside the polymorphic veneer as the canonical zero-ambiguity surface for FX score and metric data:
GET /api/fx— list of tracked FX pairs with latest scores and trend regimeGET /api/fx/:symbol— latest computed metrics for a single FX pairGET /api/fx/:symbol/series— daily metric time series for a single FX pair
List-form metrics endpoint
The pre-existing GET /api/metrics/latest (no :symbol) gains a ?type=fx branch that returns FX-pair records derived from rate data. The endpoint returns a symbol-keyed object as data rather than an array — see the endpoint page for the envelope.
Compatibility
Additive. Pre-existing /api/assets and /api/metrics consumers see no change in behaviour for stock, crypto, etf, index, or commodity symbols. The new fx value on ?type= and the FX-symbol fallback paths extend the resolution surface without altering existing rows.
/api/rates, /api/rates/sparklines, and /api/rates/:pair remain scoped to raw OHLC rate data — no scores, no metrics. Consumers wanting computed FX metrics should use /api/fx/*.
2026-05-02 — BYT-268: ByteTrend score bin histograms on GroupMetric
Group metric responses now carry per-variant score histograms — byteTrendBins — so consumers can render distribution shape without re-deriving from the underlying assets list. Surfaces automatically on all three /api/universes read paths.
Field
byteTrendBins is a nested object with one key per ByteTrend score variant:
| Key | Type | Description |
|---|---|---|
local200w | number[6] | Score histogram for Weekly Local — bins[i] = count of assets at score i |
local30d | number[6] | Score histogram for Daily Local |
capr200w | number[6] | Score histogram for Weekly CAPR |
capr30d | number[6] | Score histogram for Daily CAPR |
For each variant, sum(bins) ≤ assetCount — the gap is the number of group members whose score is null (e.g. asset hasn’t accumulated the long window yet).
"byteTrendBins": {
"local200w": [4, 7, 27, 3, 17, 47],
"local30d": [11, 15, 16, 17, 10, 37],
"capr200w": [23, 16, 15, 16, 13, 22],
"capr30d": [40, 10, 18, 11, 4, 23]
}
Endpoints
Visible on every read path that returns GroupMetric rows — see Universe Groups → ByteTrend Score Bins for the canonical field reference.
GET /api/universes/:universeGET /api/universes/:universe/:categoryGET /api/universes/:universe/:category/:group
Compatibility
Additive. The field is omitted (byteTrendBins: undefined) on responses that read pre-existing storage rows — callers should treat absent bins as “not yet populated”, not as zero counts.
Forward-only — backfill caveat
The daily 04:00 UTC group aggregator populates byteTrendBins going forward. GroupMetric rows aggregated before 2026-05-02 do not have the field — including the 2026-05-02 row itself, which was aggregated by the cron that ran before the engine deploy. Two paths to populate stored rows:
- Natural propagation — wait for the next daily aggregator run; that day’s row onwards carries bins.
- Manual re-aggregation — ByteTree operators can rewrite stored rows for a date range on request. Not run by default; full historical backfill is opt-in.
Time-series consumers reading multi-month responses will therefore see byteTrendBins populated only for dates from the cutover forward unless a manual backfill has been run.
2026-05-01 — pagination envelope harmonisation
Consumer-paginated list endpoints now return a richer envelope. GET /api/assets and GET /api/splits add total, limit, and offset alongside the existing count and data. This was the harmonisation pass scoped into BYT-252 that didn’t ship with the original splits cutover; closing the loose end now while the contract is still fresh. (The splits endpoints were later withdrawn from the public API — see the 2026-06-12 entry.)
Envelope
{
"count": 10,
"total": 4287,
"limit": 10,
"offset": 0,
"data": [ /* records on this page */ ]
}
count— length of the returneddataarray (records on this page). Semantics unchanged from the legacy envelope.total— full match count for the same filter, ignoringlimitandoffset. Computed viacountDocumentsin parallel with the paged query — no extra latency on the critical path.limit/offset— echoed input. Consumers paginate by advancingoffsetuntiloffset + count >= total.
See Response Conventions → Paginated list envelope for the canonical reference.
Endpoint changes
GET /api/assets—limitandoffsetquery parameters are now documented (default500, capped at1000;offsetdefaults to0). The endpoint already accepted these inputs; they are now part of the published contract and are echoed in the response.GET /api/splits— addsoffsetas a new query parameter. Previously cap-only vialimit.
Unchanged
GET /api/assets/:id— single-record read, returns{ data: ... }.GET /api/splits/:symbol— single-symbol all-records lookup, intentionally stays on{symbol, count, data}. Split histories are naturally bounded (well under 30 entries per symbol), so pagination would be empty ceremony./api/rates,/api/prices,/api/metrics,/api/universes,/api/currencies— bounded reads, remain on the legacy{count, data}envelope. They will adopt the full envelope if/when they become consumer-driven paginated surfaces.
Compatibility
Additive. Callers that read only count and data are unaffected — the new fields appear alongside.
2026-04-30 — BYT-252: public splits API
Closes the BYT-238 trilogy phase 3. Two new public endpoints expose the corporate-actions data layer that drives adjusted-close computation. (Both were later withdrawn from the public API — see the 2026-06-12 entry.)
Endpoints
GET /api/splits— universe-wide history, date-descending, defaultlimit500, capped at 1000.GET /api/splits/:symbol— per-symbol history, date-descending, no limit.
Data shape
Default projection: symbol, date, numerator, denominator, ratio, source. The ratio field denormalises numerator / denominator for query convenience — reverse splits have ratio < 1. Operations fields (firstSeenAt, confirmedAt, metadata) are hidden by default and opt-in via ?fields=.
Pipeline
A daily 02:00 UTC cron pulls the upstream stock-split calendar over a rolling ±90-day window into a new splits collection. Each ingested split is auto-reconciled against the price-anomaly review queue from BYT-251 phase 2 — a 'likely-split' anomaly within ±3 calendar days of a confirmed split is auto-acknowledged, populating splitId on the anomaly and confirmedAt on the split.
Trilogy completion
- Phase 1 (2026-04-28) — metrics moved to adjusted close.
- Phase 2 (engine-internal) — close-to-close anomaly sensor; no public-API surface.
- Phase 3 (this entry) — corporate-actions feed, sourced from the upstream split calendar.
2026-04-28 — BYT-238 phase 1: adjusted-close backfill, monthly refresh
Time-series metrics (MAs, slopes, rolling highs/lows, ByteTrend scores, deviations, drawdowns, returns, volatility, Sharpe, beta) are now computed on adjusted close instead of raw close. Closes the hidden corruption that unaccounted-for splits introduced into trend computation (the “BKNG-style” failure: a 1-for-6 split or large dividend silently steps the entire historical series, breaking MA crossings, ATH tracking, and drawdown depth).
Rationale
The prices endpoints have always surfaced adjusted close as close (raw close in metadata.rawClose). Metrics computation was reading raw close, which meant any unrecorded split corrupted every windowed metric until the split worked its way out of the longest window. Aligning metrics with the adjustment model the prices endpoints already use means scores and drawdowns survive splits cleanly.
Renamed
| Old | New |
|---|---|
allTimeHighUsd | allTimeHighAdjUsd |
The new name makes the adjustment model explicit. drawdownATH keeps its name; its semantics shift to ”% below adjusted-close all-time high”.
Semantic shift (same keys)
Every time-series metric below — keys, units, and ranges unchanged — is now computed on adjusted close:
- ByteTrend scores:
byteTrendLocal{200w,30d},byteTrendCapr{200w,30d}and all companions (*Rules,change*,trendRegime*,consec*5/0). - Moving averages and slopes: all 8 MAs + 8 slopes.
- Rolling highs/lows: all 14 fields (
*Max20d/30d/30w/200w,*Min20d/30d/30w). - Deviations:
devFrom*MaLong200w,devFrom*High*,devFrom*Low*. - Trend quality:
divergenceLocal/Capr,scoreVelocity5d200w,cumulHighs12w,volumeConfirmation30d. - Risk:
volatility30w,sharpe52w,beta52w,returnVolRatio. - Drawdown:
drawdown52w,drawdownATH,allTimeHighAdjUsd,maxDrawdown30w/52w. - Returns:
returnUsd*,returnFwdUsd*. - Relative strength:
relStrengthRaw*,relStrengthCapped*.
Backtest impact. Historical series for any asset that has split (or paid a large enough dividend to register in the adjustment) will differ from pre-cutover values. Backtests that join pre- and post-2026-04-28 metrics across a split will see a one-time step on the cutover day. Affected today: 211 stocks and 1 index. Commodities and crypto are unaffected — the engine sets adjClose = close for those types, so compute paths remain uniform but values are identical to pre-cutover.
Stayed on raw close
These three fields represent today’s quoted state, not a trend over time:
closeUsd— current USD close.marketCapUsd— current market cap.capr— current USD price ÷ MSCI World close.
Added (infrastructure)
adjClosefield on PriceData — internal compute input, nullable for pre-backfill records (compute readsadjClose ?? close). Not exposed on the public API surface.adjclose-refreshorchestrator stage — monthly cron that refetches full history for stocks/ETFs/indexes, diffs against storedadjClose, writes changes, and triggers per-asset recompute on detected diffs. Catches splits the daily ingest path missed.
Follow-ups
- Phase 2 — close-to-close sensor. Engine-side detection of unexplained close-to-close gaps that suggest an unrecorded split. Engine-internal; no public-API surface.
- Phase 3 — split-calendar cross-ref. Validation against external split-calendar data. See the BYT-252 entry for the public splits API.
2026-04-28 — BYT-241: legacy field cleanup + uniform window-suffix convention
The BYT-240 dual-write window has closed. Legacy field names emitted alongside the canonical BYT-240 surface have been removed, and a uniform window-suffix convention now applies to every window-bearing metric across the API.
Rationale
- One name per field, suffix matches the actual window. Before BYT-241,
localMaShort200wwas actually a 30-week MA (named for the pair it belonged to);localMax30dwas actually a 20-day window per its docs description; the assets endpoint surfacedbyteTrendCapr(unsuffixed) as an alias ofbyteTrendCapr200w. All three patterns are gone. Every window-bearing field now carries the actual window in its identifier —30d,200d,30w,200w— and there is exactly one canonical name per field across every endpoint. - No transitional aliases. Legacy names from BYT-240 are removed outright, not deprecated. New integrations were already advised to use the canonical names from BYT-240’s day one.
- Composite-name exception preserved. ByteTrend score families (
byteTrendLocal200wand friends) and divergence (divergenceLocal/divergenceCapr) keep their pair/architecture names — they identify a score class, not a single-window measurement.
Renamed
Underlying MAs (pair-label sweep — suffix now denotes actual window):
| Old | New |
|---|---|
localMaShort200w | localMaShort30w |
caprMaShort200w | caprMaShort30w |
localMaLong30d | localMaLong200d |
caprMaLong30d | caprMaLong200d |
MA slopes (4 weekly + 4 daily, all newly suffixed to match their underlying MA):
localMaLong200wSlope, localMaShort30wSlope, caprMaLong200wSlope, caprMaShort30wSlope, localMaLong200dSlope, localMaShort30dSlope, caprMaLong200dSlope, caprMaShort30dSlope (was localMaLongSlope, localMaShortSlope, caprMaLongSlope, caprMaShortSlope).
Rolling highs/lows:
| Old | New |
|---|---|
localMax | localMax30w |
localMin | localMin30w |
caprMax | caprMax30w |
caprMin | caprMin30w |
Trend regime / age / pending:
| Old | New |
|---|---|
confirmedRegimeCapr | confirmedRegimeCapr200w |
pendingRegimeCapr | pendingRegimeCapr200w |
pendingRegimeCount | pendingRegimeCount200w |
trendAge | trendAge200w |
Deviations (suffix the long MA they reference):
| Old | New |
|---|---|
devFromLocalMaLong | devFromLocalMaLong200w |
devFromCaprMaLong | devFromCaprMaLong200w |
Other:
| Old | New |
|---|---|
volumeConfirmation | volumeConfirmation30d |
scoreVelocity5d | scoreVelocity5d200w (now tracks byteTrendLocal200w) |
Assets endpoint denormalised fields:
| Old | New |
|---|---|
byteTrendCapr | byteTrendCapr200w |
trendRegimeCapr | trendRegimeCapr200w |
changeCapr | changeCapr200w |
GroupMetrics aggregates: legacy unsuffixed avgByteTrendFast/Local/Capr, medianByteTrendLocal, pctScore{5,0}{Local,Capr} removed; use the suffixed equivalents listed in Universe Groups.
Added
- 20-day rolling high/low set —
localMax20d,localMin20d,caprMax20d,caprMin20d. Used by Daily score’slastTouchMaxrule (recent-extremum tracking). Replaces the dropped legacyfastMax/fastMin. - 30-week CAPR rolling pair —
caprMax30w/caprMin30w(symmetric with the renamedlocalMax30w/localMin30w). - 200-week CAPR rolling high —
caprMax200w(symmetric withlocalMax200w). - Daily MA slopes —
localMaLong200dSlope,localMaShort30dSlope,caprMaLong200dSlope,caprMaShort30dSlope. Previously only weekly MA slopes were emitted. - Pending regime fields documented —
pendingRegimeCapr200wandpendingRegimeCount200wwere already emitted but not previously listed in the docs.
Removed (no replacement)
scoreVelocity1ddivergenceWeekly*(entire VWAP-sampled family — never used by consumers post-VWAP removal)- All BYT-240 transitional alias fields:
byteTrendFast/Local/Capr(unsuffixed) + their*Rulesandchange*companions;consecLocal/Capr 5/0(unsuffixed);trendRegimeLocal/Capr(unsuffixed);byteTrendWeekly{Fast,Local,Capr}+ companions;weekly{Fast,Local,Capr}{MaLong,MaShort,Max,Min};vwap1W/caprVwap1W;fastMaLong/Short,fastMax/Min;localMaLong/Short,caprMaLong/Short(unsuffixed pre-BYT-240 names).
Docs corrections
- The Rolling Highs & Lows description for
localMax30d/localMin30d/caprMax30d/caprMin30dpreviously read “20-day rolling high/low” — corrected to 30-day. The fields are genuinely 30-day (used by Daily score regime classification, matches the daily short MA window). The actual 20-day pair is the newlocalMax20d/localMin20d/caprMax20d/caprMin20d.
Preserved under existing names
byteTrendLocal200w/byteTrendLocal30d/byteTrendCapr200w/byteTrendCapr30dand all their companions (*Rules,change*,consec*5/consec*0,trendRegime*).divergenceLocal/divergenceCapr— kept under their BYT-240 names with Daily−Weekly semantics.
See the BYT-240 entry for the prior cutover.
2026-04-22 — BYT-240: ByteTrend score rename, VWAP removal
Four collision-free ByteTrend scores replace the previous six, naming window into the identifier. The legacy VWAP-weighted weekly infrastructure has been removed.
Rationale
- Collision-free naming. Window (
200wor30d) is baked into the score identifier, removing the ambiguity that produced the “CAPR (Daily)” UI label on what was actually a weekly-window score. - Real Daily CAPR.
byteTrendCapr30dis a net-new score — a true 200d/30d/20d CAPR signal where the previous surface only offered the weekly-windowbyteTrendCapr. - VWAP removal. The VWAP-based weekly overlays were a misstep — they added surface without conceptual clarity.
byteTrendWeekly{Fast,Local,Capr}, their rule/change companions, allweekly*Ma*/weekly*Max/Minoverlays, andvwap1W/caprVwap1Ware gone.
Canonical scores
| Field | UI Label | Windows |
|---|---|---|
byteTrendLocal200w | Local (Weekly) | 200w / 30w / 30w on local close |
byteTrendLocal30d | Local (Daily) | 200d / 30d / 20d on local close |
byteTrendCapr200w | CAPR (Weekly) | 200w / 30w / 30w on CAPR |
byteTrendCapr30d | CAPR (Daily) | 200d / 30d / 20d on CAPR |
Each carries *Rules, change*, trendRegime*, and consec*5/consec*0 companions on the same suffix.
Added
- Four new scores with full companions (rules, changes, regimes, 8 × consec counters)
- Moving-average aliases per window:
localMaLong{200w,30d},localMaShort{200w,30d},caprMaLong{200w,30d},caprMaShort{200w,30d} - 30-day rolling highs/lows:
localMax30d,localMin30d,caprMax30d,caprMin30d - 30-day deviations:
devFromLocalHigh30d,devFromLocalLow30d,devFromCaprHigh30d,devFromCaprLow30d - Group aggregates (16 fields):
avgByteTrend{Local,Capr}{200w,30d},medianByteTrend{Local,Capr}{200w,30d},pctScore{5,0}{Local,Capr}{200w,30d}
Removed
vwap1W,caprVwap1WbyteTrendWeeklyFast,byteTrendWeeklyLocal,byteTrendWeeklyCapr(+ their*Rulesandchange*companions)- Weekly VWAP overlays:
weeklyFastMaLong/Short,weeklyLocalMaLong/Short,weeklyCaprMaLong/Short - Weekly VWAP high/lows:
weeklyFastMax/Min,weeklyLocalMax/Min,weeklyCaprMax/Min
Semantic shift (same keys)
divergenceLocal/divergenceCapr— now Daily − Weekly (was Daily − VWAP-weekly). Keys, units, and range unchanged. Historical series are not directly comparable across the cutover — any backtest that joins pre- and post-2026-04-22 divergence values will see an artificial step.
Migration
The engine dual-wrote canonical and legacy names during the BYT-240 → BYT-241 transition window. Dual-write closed at BYT-241 (2026-04-28); only the canonical names are emitted now. See the BYT-241 entry for the full removal list.