/api/v1/xbrl/timeseries/batchStandardized 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 or the company's own earnings-per-share element, read from the filing's own presentation. Structural fallback rejects the opposite US-GAAP or IFRS earnings-per-share type.
Why use this
Common use case
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 us-gaap:EarningsPerShareBasicAndDiluted fact, or the company's own earnings-per-share element, can supply either or both metrics; each retains its source concept and its reported per-share value. Its meaning comes from the filing's own presentation: the line as printed in the filed document where the page capture binds it, read under the heading printed above it when the line names only its result ("Net earnings"); otherwise the line label of the filing's own stored statement, accepted only when the filing's own net income over its weighted-average shares for the same period reproduces the value (where a diluted weighted-average count is filed separately, the diluted quotient too, unless income from continuing operations is a loss). For this check a figure the company marked exact is read at the precision it was printed, and income that includes the noncontrolling interest's share is not used where the filing reports that share; the value served is always the company's own figure. A standard basic or diluted line printed under a heading that names both supplies both. A line or heading that names two or more classes of common stock together ("per Class A and Class B common stock"), or a parent's share with its operating partnership's unit ("per common share or unit"), reads as the company-wide result; a single class, units alone, a partnership unit or a preferred series does not. 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. With provenance=full, a row whose identity rests on a filed line carries an eps_identity_evidence entry in relationship_provenance.source_lineage.transformations: the binding (captured_inline_fact or filed_presentation_label), the line labels, any header_labels read, and for a statement line its label_provenance and own_facts proof (status, case, input fact numbers, quotients and control number); rows derived from it carry the entry too. 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
| Name | In | Required | Default | Allowed | Description | Example |
|---|---|---|---|---|---|---|
| tickers | body | optional | — | — | JSON array of ticker selectors, or null. The API converts each entry to text, trims whitespace and removes blank entries, then requires 1-50 remaining selectors. The limit applies after blank removal, not to raw array length. Prefer ticker strings. A non-null tickers array takes precedence over ciks; otherwise provide ciks. Each unknown selector becomes a per-entity error. Send an array, not JSON encoded inside a string. | ["AAPL","MSFT","NVDA"] |
| ciks | body | optional | — | — | JSON array of issuer CIK selectors, or null, used when tickers is omitted or null. Zero-padded strings, bare strings and numeric CIKs are accepted; entries are converted to text and trimmed. Blank entries are removed before the 1-50 selector limit is checked. A non-numeric CIK becomes a per-entity error. At least one non-null selector array is required. Send an array, not JSON encoded inside a string. | ["320193","789019"] |
| metrics | body | optional | — | — | Same 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"] |
| period | body | optional | A | — | `A` (annual, default), `Q` (quarterly), or `TTM` — same meaning as the GET route. Applies to every entity. | TTM |
| years | body | optional | all available periods | — | Years 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 |
| provenance | body | optional | none | — | `none` | `summary` | `full` — same per-cell provenance shaping as the GET route. Applies to every entity. | summary |
| basis | body | optional | latest | — | `latest` | `original` — same amendment-aware vs as-originally-filed semantics as the GET route. | latest |
| as_of | body | optional | — | — | Point-in-time replay (`YYYY-MM-DD`), applied to every entity — same as the GET route. | 2024-12-31 |
| ratios | body | optional | false | — | `true` adds the computed-ratio catalog to every entity's series — same as the GET route. | false |
| adjusted | body | optional | false | — | `true` split-adjusts the eight per-share metrics for every entity — same as the GET route. | false |
| calendar | body | optional | false | — | `true` adds the calendar-alignment block to each row of every entity — same as the GET route. | false |
| template | body | optional | false | — | `true` assembles each entity's rows into the full footing statement templates — same as the GET route. | false |
| limit | body | optional | 5000 | — | Per-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
| Field | Type | Nullable | Description |
|---|---|---|---|
| data.results[] | array | no | One 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[].data | object | yes | Present 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[].error | object | yes | Present 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.requested | number | no | How many selectors were in the request list (after dropping blank entries). |
| meta.resolved | number | no | How 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.errors | number | no | How many selectors came back as `error` entries. |
| meta.batch_max | number | no | The maximum selectors accepted per batch call (50). A longer list is rejected with 400 INVALID_PARAM before any work. |
| meta.tokens_note | string | no | Billing 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
| Status | Label | Description |
|---|---|---|
| 200 | OK | Request succeeded. |
| 400 | Bad Request | Invalid query, body, or path parameter. |
| 401 | Unauthorized | Missing or invalid Authorization header / api_Token. |
| 402 | Payment Required | Insufficient token balance for this call. Top up |
| 429 | Too Many Requests | Rate limit exceeded for your tier (see /pricing for tier limits). Tier limits |
| 500 | Server Error | Unexpected server-side failure. Retry with backoff; report if persistent. |
Code samples
curl -X POST "https://api.finradar.ai/api/v1/xbrl/timeseries/batch" \
-H "X-API-Key: YOUR_API_KEY" \
-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).