ByteTree API documentation

Requests & Responses

The rules every endpoint follows: the formats you can ask for, the shapes responses come back in, how paging works, and how the API tells you a request was wrong. Getting Started covers the basics; this page is the detail.

Response Format

All endpoints return JSON by default. You can request alternative formats using the format query parameter:

FormatContent-TypeDescription
jsonapplication/jsonDefault. Standard JSON.
csvtext/csvComma-separated values (RFC 4180).
toontext/toonTOON — compact, token-efficient encoding ideal for LLM consumers.
# CSV
curl "https://api.bytetree.io/api/rates?format=csv"

# TOON
curl "https://api.bytetree.io/api/rates?format=toon"

Unsupported format values return 400 Bad Request.

Where csv is available

json and toon work on every endpoint. csv is offered only where the response is one table. An endpoint whose response is several tables at once — /api/summary is the documented case — refuses csv with a 400 that names the formats it does support:

{ "error": "format=csv is not available for this endpoint; use json or toon", "formats": ["json", "toon"] }

The refusal itself is JSON, not CSV, and it is decided by the endpoint’s shape, never by its data — an empty result does not change the answer.

How CSV is shaped

Response shapeCSV rendering
Paginated list, entity seriesOne row per record in data; the envelope fields (count, total, …) are not included
Empty listAn empty document — no header row
Single-record read (e.g. /api/assets/:id)One row; nested objects flatten to dotted column names (metadata.exchange)
Keyed map (/api/metrics/latest)One row per symbol, with a leading key column carrying the symbol

Inside a row, an array of primitives is joined into one quoted cell, and an array of objects is written as JSON text in the cell.

Response Conventions

Paginated list envelope

List endpoints that support consumer-driven pagination return:

{
  "count":  10,
  "total":  4287,
  "limit":  10,
  "offset": 0,
  "data":   [ /* records on this page */ ]
}
FieldDescription
countLength of the returned data array (records on this page).
totalFull match count for the same filter, ignoring limit and offset. Computed in parallel with the paged query.
limit / offsetEchoed input. Consumers paginate by advancing offset until offset + count >= total.
dataArray of records for this page.

This is the shape for top-level list endpoints/api/assets, /api/rates, /api/prices, /api/universes, /api/fx among them.

Compatibility: the envelope is additive. Callers that read only count and data are unaffected. See the Changelog for the cutover note.

Which shape to expect

Rather than memorising a list, read it from the request. Pagination fields appear exactly where you can meaningfully page, and data is an array everywhere except one endpoint:

ShapeReturnsWhere
Paginated list{count, total, limit, offset, data[]}Top-level list endpoints — the envelope above
Entity series{symbol, count, data[]} (or {pair, …})One entity’s series, e.g. /api/prices/:symbol, /api/fx/:symbol/series. Not paginated — it’s one thing’s history, not a list
Keyed map{count, data} where data is an object keyed by symbol/api/metrics/latest only. count is the number of keys

The keyed map is the one to watch: data is an object, not an array, so iterating it as a list fails rather than returning nothing.

Single-record reads

Single-record endpoints — e.g. GET /api/assets/:id, GET /api/rates/:pair — return { data: ... } and may include the lookup key as a sibling field. They are not paginated.

Unknown query parameters are rejected

Passing a parameter an endpoint doesn’t accept returns 400, rather than being silently ignored:

{ "error": "Unknown query parameter 'typ'. Did you mean 'type'? Accepted parameters: type, region, sector, limit, offset." }

Close misspellings get a suggestion, and the accepted list is always echoed. An endpoint that takes no parameters says so explicitly.

This is deliberate. Silently ignoring an unrecognised parameter means a filter you thought you applied simply didn’t apply, and the response looks plausible — a filtered query returning unfiltered data with a 200. Failing loudly turns a wrong answer into an obvious error.

format is accepted everywhere and never counts as unknown. Authentication is checked first, so an invalid key returns 401 rather than 400, even if the request also carries a bad parameter.

Malformed limit and offset values are rejected

The same rule applies to values. On every paginated list endpoint, limit must be a positive integer and offset a non-negative integer. Anything else returns 400 naming the parameter and the value received:

{ "error": "Invalid value for 'limit': expected a positive integer, received 'abc'." }
RequestResult
?limit=abc, ?limit=1.5, ?limit=1e3, ?limit=12abc400 — not an integer
?limit=0, ?limit=-3400limit must be 1 or more
?offset=-1, ?offset=1.5, ?offset=abc400offset must be a whole number, 0 or more
?limit=5000200 — clamped to the endpoint’s maximum, which the envelope echoes in limit
limit / offset absent or empty200 — the endpoint’s default applies

Only malformed values are rejected. An over-large limit is still clamped rather than refused, and the echoed limit tells you what was applied. limit=0 is an error, not “no limit”.

Available Endpoints

Every endpoint is a read-only GET.

PathDescription
/api/healthHealth check — the one endpoint that needs no key
/api/currenciesCurrency code to name mapping
/api/ratesLatest rates for all FX pairs
/api/rates/sparklines12-month weekly closes per pair
/api/rates/:pairDaily time series for one pair
/api/assetsList assets with optional filters
/api/assets/:idSingle asset by ID or symbol
/api/pricesLatest price per symbol
/api/prices/sparklines12-month weekly closes per symbol
/api/prices/:symbolDaily price time series for one symbol
/api/fxTracked FX pairs with latest scores and trend regime
/api/fx/:symbolLatest computed metrics for one FX pair
/api/fx/:symbol/seriesDaily metric time series for one FX pair
/api/fx/:symbol/pricesClose-only price series for one FX pair
/api/metrics/latestLatest metrics across all symbols
/api/metrics/:symbol/latestLatest computed metrics snapshot
/api/metrics/:symbolComputed metrics time series
/api/metrics/distributionCross-sectional metric distributions
/api/metrics/regime-transitionsTrend-regime transition event log
/api/summarySingle-screen dataset overview
/api/universesThe universe roster with membership counts
/api/universes/:universeLatest group metrics across all dimensions for a universe’s members
/api/universes/:universe/:categoryGroups within a dimension
/api/universes/:universe/:category/:groupTime series for one group
/api/macroGroup metrics across every tracked asset (also /:dimension and /:dimension/:group)
/api/dimensionsThe five global grouping dimensions (also /:dimension)

:universe takes a universe slug — bytetrend, bytefolio and the rest of the roster on Universes. Every API key reaches every universe. macro is not a universe: the whole-registry aggregation is at /api/macro.