Skip to content
/api/v1/xbrl/timeseries/batch

Standardized metric time-series for MANY companies in ONE request — the multi-security form of GET /api/v1/xbrl/timeseries.

Standardized metric time-series for MANY companies in ONE request — the multi-security form of GET /api/v1/xbrl/timeseries. Earnings-per-share metrics prefer their matching individual US-GAAP fact, then us-gaap:EarningsPerShareBasicAndDiluted. Structural fallback rejects the opposite US-GAAP or IFRS earnings-per-share type.

Why use this

Send a list of up to 50 tickers or CIKs plus the SAME options GET /api/v1/xbrl/timeseries takes, and get each company's full metric time-series back in a single call. Eliminates the network N+1 — a 100-name screen that was 100 separate calls becomes two batch calls. Partial success: an unknown or unresolvable selector comes back as an `error` entry and never fails the whole batch.

Common use case

Building a peer-comparison table or a multi-company dashboard; hydrating a watchlist's fundamentals in one round-trip; pulling a whole screen result's histories after GET /api/v1/xbrl/screen returns the matching tickers.

The multi-security form of GET /api/v1/xbrl/timeseries: POST a list of up to 50 tickers or CIKs and the same options, and receive each company's standardized metric time-series in ONE metered request — the network-efficient way to hydrate a peer group, a watchlist, or a whole screen result. The per-entity body is byte-identical to what a single GET /api/v1/xbrl/timeseries would return (same standardization ladder, same provenance/basis/as_of/ratios/template semantics, same currency handling), so a batch and a per-company call never disagree. Selectors resolve independently: an unknown or unresolvable ticker/CIK returns a status:"error" entry with a machine-readable code and never fails the rest of the batch (partial success, exactly like POST /api/v1/cusip/batch). A request-wide error (an unknown metric key, an over-limit list, an empty list, or a cursor/period_offset in the body) is a single 400 for the whole call. Entities are assembled serially inside one database session (no fan-out), so the batch is a strict efficiency win over N separate HTTP calls with no extra database load. Multi-security retrieval is table stakes for a terminal-grade fundamentals API — professional fundamentals platforms return a field matrix across a security list from one request; the request/response shape here is documented above. Billing note: the endpoint currently bills the flat batch cost (10 tokens) — the shared token meter debits one fixed cost per request and refunds that one debit on failure; meta.resolved reports the entity count so an N-per-entity multiplier can be added once the meter grows a variable-charge primitive.

Earnings per share. For eps_basic and eps_diluted, the corresponding individual US-GAAP fact takes priority. If it is unavailable, an eligible historical us-gaap:EarningsPerShareBasicAndDiluted fact can supply either or both metrics; both retain that combined source concept and its reported per-share value. Structural fallback rejects an explicitly basic-only US-GAAP or IFRS fact for diluted earnings per share, and vice versa. If that opposite kind is the only evidence, the requested metric remains missing. Existing period, currency and consolidation checks still apply; quarter and trailing-twelve-month calculation methods are unchanged. This supports already-filed combined facts, whose tag was deprecated in the 2022 taxonomy (FASB FAQ 2.14).

Parameters

NameInRequiredDefaultAllowedDescriptionExample
tickersbodyoptionalJSON array of ticker symbols (>=1, up to 50). Provide EITHER `tickers` OR `ciks`; if both are sent, `tickers` wins (same selector precedence as the GET route). Each is resolved independently — an unknown ticker becomes a per-entity `error` entry, not a whole-batch failure. An empty list -> 400 MISSING_PARAM; more than 50 -> 400 INVALID_PARAM.["AAPL","MSFT","NVDA"]
ciksbodyoptionalJSON array of issuer CIKs, zero-padded or bare (>=1, up to 50). Used only when `tickers` is absent. A non-numeric CIK becomes a per-entity `error` entry. Empty -> 400 MISSING_PARAM; over 50 -> 400 INVALID_PARAM.["320193","789019"]
metricsbodyoptionalSame canonical metric keys as GET /api/v1/xbrl/timeseries (JSON array or comma-separated string). Applies to every entity in the batch. Omit for all standardized primitives. A ratio-only key without `ratios:true` -> 400 INVALID_PARAM for the whole request (it is the same error for every entity).["revenue","net_income"]
periodbodyoptionalA`A` (annual, default), `Q` (quarterly), or `TTM` — same meaning as the GET route. Applies to every entity.TTM
yearsbodyoptionalall available periodsYears of history per entity. DEFAULT = all available periods (omit, or pass `"all"`, for each entity's full history — no fiscal-year floor); pass an integer to narrow to the last N fiscal years, clamped to [1, 25]. Same semantics as the GET route.10
provenancebodyoptionalnone`none` | `summary` | `full` — same per-cell provenance shaping as the GET route. Applies to every entity.summary
basisbodyoptionallatest`latest` | `original` — same amendment-aware vs as-originally-filed semantics as the GET route.latest
as_ofbodyoptionalPoint-in-time replay (`YYYY-MM-DD`), applied to every entity — same as the GET route.2024-12-31
ratiosbodyoptionalfalse`true` adds the computed-ratio catalog to every entity's series — same as the GET route.false
adjustedbodyoptionalfalse`true` split-adjusts the eight per-share metrics for every entity — same as the GET route.false
calendarbodyoptionalfalse`true` adds the calendar-alignment block to each row of every entity — same as the GET route.false
templatebodyoptionalfalse`true` assembles each entity's rows into the full footing statement templates — same as the GET route.false
limitbodyoptional5000Per-ENTITY page size in period-rows, clamped to [1, 5000]. Each entity's series is capped independently at this page size (its `data.meta.pagination` reports `has_more`). `cursor` is NOT supported on the batch endpoint — a single cursor cannot page N independent series; page one company at a time with GET /api/v1/xbrl/timeseries. `period_offset` and `cursor` in the body -> 400 INVALID_PARAM.5000

Response schema

FieldTypeNullableDescription
data.results[]arraynoOne entry per requested selector, in request order. Each carries `selector` (the ticker/CIK you sent) and `status`: `ok` (with `data` = the SAME body a single GET /api/v1/xbrl/timeseries returns for that entity, and `meta` = that entity's per-series meta including its own `pagination`) or `error` (with `error.{code,message}` — an unknown/unresolvable/non-numeric selector). A per-entity error never fails the batch (partial success).
data.results[].dataobjectyesPresent only on an `ok` entry: the identical `/timeseries` response body for that entity (`ticker`, `cik`, `company_name`, `period`, `basis`, `provenance`, `reporting_currency`, `ratios`, `rows`, and the flag echoes as requested).
data.results[].errorobjectyesPresent only on an `error` entry: `{code, message}` — e.g. `UNKNOWN_TICKER` (no such US filer), `NOT_COVERED` (basket wall, if enabled), or `INVALID_PARAM` (a non-numeric CIK in the list).
meta.requestednumbernoHow many selectors were in the request list (after dropping blank entries).
meta.resolvednumbernoHow many selectors resolved to a company and returned a series (status `ok`). Surfaced so a client can see the entity count that WOULD drive an N-per-entity charge once the meter supports it.
meta.errorsnumbernoHow many selectors came back as `error` entries.
meta.batch_maxnumbernoThe maximum selectors accepted per batch call (50). A longer list is rejected with 400 INVALID_PARAM before any work.
meta.tokens_notestringnoBilling transparency: this endpoint currently bills the FLAT batch cost (10 tokens), like the CUSIP batch, because the token meter debits one fixed cost per request. `meta.resolved` reports the entity count for a future N-per-entity multiplier.

Sample response

·
  • "request_id": null
  • "timestamp": "2026-09-13T00:00:00Z"
  • "status": "success"
  • "data":
    • "results":
    }
  • "meta":
    • "requested": 2
    • "resolved": 1
    • "errors": 1
    • "batch_max": 50
    • "tokens_note": "billed at the flat batch cost; N-per-entity metering pending a variable-charge primitive in the token meter"
    }
}

Errors

StatusLabelDescription
200OKRequest succeeded.
400Bad RequestInvalid query, body, or path parameter.
401UnauthorizedMissing or invalid Authorization header / api_Token.
402Payment RequiredInsufficient token balance for this call. Top up
429Too Many RequestsRate limit exceeded for your tier (see /pricing for tier limits). Tier limits
500Server ErrorUnexpected server-side failure. Retry with backoff; report if persistent.

Code samples

curl -X POST "https://api.finradar.ai/api/v1/xbrl/timeseries/batch?api_Token=YOUR_API_KEY" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "tickers": "[\"AAPL\",\"MSFT\",\"NVDA\"]",
  "ciks": "[\"320193\",\"789019\"]",
  "metrics": "[\"revenue\",\"net_income\"]",
  "period": "TTM",
  "years": "10",
  "provenance": "summary",
  "basis": "latest",
  "as_of": "2024-12-31",
  "ratios": "false",
  "adjusted": "false",
  "calendar": "false",
  "template": "false",
  "limit": "5000"
}'

Generate an API key in /account/credentials to run live queries (literal YOUR_API_KEY placeholder shown until then).