/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. 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 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
| Name | In | Required | Default | Allowed | Description | Example |
|---|---|---|---|---|---|---|
| tickers | body | optional | — | — | JSON 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"] |
| ciks | body | optional | — | — | JSON 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"] |
| 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?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).