# GET /v2/series

**State over time**

Any signals for any tickers on one shared time grid — up to 50 tickers by 25 columns per call.

## Query / path parameters

| Name | In | Type | Required | Description |
|------|----|----|----------|-------------|
| `tickers` | query | string[] | no | Comma-separated symbols, up to 50 (POST accepts a JSON array). Exactly one of `tickers` or `ticker` is required; when both are passed, `ticker` wins — so sending both silently narrows the request to one symbol. Example: `AAPL,MSFT`. |
| `ticker` | query | string | no | Single-symbol form — `/v2/series?ticker=AAPL` is ticker history in its canonical spelling. Exactly one of `ticker` or `tickers` is required. Example: `AAPL`. |
| `columns` | query | string[] | no | Up to 25 columns (POST accepts an array): OHLCV names, signals, and your custom signals, freely mixed. Omitted → the ticker-history default set (price, change_1d_pct, relative_volume, market_cap), intersected with what the interval carries. At `1q`, `columns` is required and quarterly-only. `fields` accepted as an alias. Example: `close,rsi_14,above_sma_200`. |
| `interval` | query | string | no | Grid granularity. `1w` resamples the daily tier weekly (Monday-keyed); `1q` is the fiscal-quarter grid. Enum: `1m`, `1h`, `1d`, `1w`, `1q`. Default: `1d`. |
| `from` | query | string | no | Earliest timestamp (inclusive), `YYYY-MM-DD` or ISO. Intraday requests default to a recent window (`1m`: 7 days, `1h`: 60 days) — the cursor keeps walking further back window-by-window, or pass `from` to widen it up front. Example: `2026-02-18`. |
| `to` | query | string | no | Latest timestamp (inclusive), `YYYY-MM-DD` or ISO. Example: `2026-06-28`. |
| `asof` | query | string | no | Point-in-time read: ONE row per ticker — the state at that instant — rather than a range. `YYYY-MM-DD` or a full ISO timestamp, the same meaning `asof` carries on [`/v2/tickers`](/docs/endpoints/tickers/state#as-of), [`/v2/scan`](/docs/endpoints/scan/state#as-of) and [`/v2/signals`](/docs/endpoints/signals/state#as-of). Cannot be combined with `from`/`to` or `cursor` (400) — a point and a window are contradictory, and `limit` has no meaning under it. It also resolves WHICH COMPANY held the symbol at that instant: a ticker that changed hands returns the row of whoever traded it then, so `tickers=SHLD&asof=2010-06-30` returns Sears Holdings' price and `asof=2026-01-01` returns the Global X defence ETF. Returns the most recent row at or before the instant, so a date inside a trading gap gives the last row before it. At `interval=1q` the anchor is the date the quarter was REPORTED (earnings release / filing), not fiscal period end — you get the latest quarter that was public knowledge at the instant, with restatements after it excluded. Example: `2010-06-30`. |
| `limit` | query | integer | no | Grid steps per page (shared across tickers). Max 1000 — an over-cap `limit` is clamped to 1000 (house convention, `limit=10000` means "max"). Separately, tickers × limit may not exceed 25,000 rows per page — over THAT cap is an explicit 400. Default: `252`. Example: `3`. |
| `cursor` | query | string | no | Opaque cursor from the previous response — every ticker pages backward in lockstep on the shared grid, no per-ticker gaps or duplicates. |
| `transitions_only` | query | boolean | no | Only rows where a boolean signal changed state. Accepted spellings: `true`/`1`/`yes` and `false`/`0`/`no` (case-insensitive) — anything else is a 400, never silently off. Requires at least one boolean signal (built-in boolean or custom signal); each returned row carries `transitions: {column: "enter"\|"exit"}`, and `_meta` lists the driving columns. Strict truth: only literal `true` is "on", so `null → true` is an enter and `true → null` an exit (a backfill boundary reads as an edge). Edges need a prior observation — on the oldest page of a walk the first row has no predecessor and yields no edge. A flip is dated by the state table and does not move with the column list: one recorded on a non-trading carry row keeps that date, with any bar columns `null` on that row (no bar exists there). Example: `true`. |

## Returns

- `as_of` (string) — Server time this response was assembled (ISO 8601).
- `interval` (string) — The grid granularity served.
- `tickers` (array) — Symbols in the response, echoed.
- `columns` (array) — Columns in the response, echoed.
- `count` (number) — Rows per ticker in this page.
- `next_cursor` (string) — Opaque token for the next page; `null` on the last page. Pass it back as `cursor`.
- `_meta` (object) — Per-column `sources` (`bars`, `state`, or `custom` for your own signals; `earnings`/`statements` at 1q) and per-ticker `coverage`, plus `non_trading_days_dropped` / `transitions_only` / `from_defaulted` when they apply. `deprecated_columns` lists any column you named under its pre-2026-09-07 spelling that was served under its current name (`requested`, `use`, `note`).
- `series` (object) — Keyed by ticker: an array of flat rows, chronological, each keyed `t` plus the columns you asked for.

## Status codes

- **200** — Success — the response shape is documented under Returns above.
- **400** — Bad interval/bounds, unknown signal (`invalid_query`), daily-only signal at an intraday interval, quarterly/daily signal mix, over the tickers × limit cap, or `transitions_only` without a boolean signal.
- **401** — Missing or invalid API key.

## Sample response

```json
{
  "as_of": "2026-08-11T22:43:53.859Z",
  "interval": "1d",
  "tickers": ["AAPL", "MSFT"],
  "columns": ["close", "rsi_14"],
  "count": 6,
  "next_cursor": "eyJiZWZvcmVfdHMiOiIyMDI2LTA4LTA2In0",
  "_meta": {
    "sources": { "close": "bars", "rsi_14": "state" },
    "coverage": { "AAPL": { "rows": 3, "missing_bar_cells": 0 }, "MSFT": { "rows": 3, "missing_bar_cells": 0 } }
  },
  "series": {
    "AAPL": [
      { "t": "2026-08-06", "close": 312.41, "rsi_14": 46.9107 },
      { "t": "2026-08-07", "close": 313.33, "rsi_14": 47.4869 },
      { "t": "2026-08-10", "close": 308.26, "rsi_14": 43.6139 }
    ],
    "MSFT": [
      { "t": "2026-08-06", "close": 499.86, "rsi_14": 77.5244 },
      { "t": "2026-08-07", "close": 499.99, "rsi_14": 77.8171 },
      { "t": "2026-08-10", "close": 506.06, "rsi_14": 78.5733 }
    ]
  }
}
```

## More examples

### Flips only: when did AAPL cross its 200-day, with price at the flip

Request:

```shell
curl "https://api.tickerbot.io/v2/series?ticker=AAPL&columns=above_sma_200,close&transitions_only=true&from=2026-02-18" \
  -H "Authorization: Bearer YOUR_KEY"
```

Response (`200`):

```json
{
  "as_of": "2026-08-11T23:23:33.317Z",
  "interval": "1d",
  "tickers": ["AAPL"],
  "columns": ["above_sma_200", "close"],
  "count": 2,
  "next_cursor": null,
  "_meta": {
    "sources": { "above_sma_200": "state", "close": "bars" },
    "coverage": { "AAPL": { "rows": 2, "missing_bar_cells": 0 } },
    "transitions_only": true,
    "transition_drivers": ["above_sma_200"]
  },
  "series": {
    "AAPL": [
      { "t": "2026-03-30", "above_sma_200": false, "close": 246.63, "transitions": { "above_sma_200": "exit" } },
      { "t": "2026-03-31", "above_sma_200": true,  "close": 253.79, "transitions": { "above_sma_200": "enter" } }
    ]
  }
}
```

### Quarterly fundamentals on the fiscal grid

Request:

```shell
curl "https://api.tickerbot.io/v2/series?ticker=AAPL&columns=eps,revenue&interval=1q&limit=2" \
  -H "Authorization: Bearer YOUR_KEY"
```

Response (`200`):

```json
{
  "as_of": "2026-08-11T23:23:33.317Z",
  "interval": "1q",
  "tickers": ["AAPL"],
  "columns": ["eps", "revenue"],
  "count": 2,
  "next_cursor": "eyJiZWZvcmVfdHMiOiIyMDI2LVExIn0",
  "_meta": {
    "sources": { "eps": "earnings", "revenue": "statements" },
    "coverage": { "AAPL": { "rows": 2 } }
  },
  "series": {
    "AAPL": [
      { "t": "2026-Q1", "fiscal_period": "2026-03-31", "reported": "2026-04-30", "eps": 2.01, "revenue": 111184000000 },
      { "t": "2026-Q2", "fiscal_period": "2026-06-30", "reported": "2026-07-30", "eps": 1.91, "revenue": null }
    ]
  }
}
```

## Notes

- The join across the OHLCV bar store and the indicator and boolean state tables that would otherwise take N history calls and a hand-rolled merge. Those two stores genuinely disagree — state has rows on non-trading days, intraday bars can lag behind state — so the endpoint owns the alignment policy: a missing cell is `null`, never a silently dropped row, and `_meta` reports per-column sources plus per-ticker coverage so a gap is always visible.
- Column names resolve by source, invisibly at the call site: `open`/`high`/`low`/`close`/`volume`/`vwap`/`trades` (or `o`/`h`/`l`/`c`/`v`/`vw`/`n`) read licensed OHLCV bars; every signal on the [schema page](/docs/schema) reads the state tables; your own [custom signals](/docs/endpoints/signals/custom) compile per-row and behave exactly like built-ins.
- With no `columns` the default set matches the deprecated [Ticker history](/docs/endpoints/tickers/history-series) — a bare `/v2/series?ticker=AAPL` IS ticker history in its canonical spelling.
- `1m` / `1h` / `1d` are stored tiers. `1w` is a real weekly resample: each signal samples the week's last observation, bars aggregate (open of first bar, max high, min low, close of last, summed volume and trades, volume-weighted `vwap` — `null` for a zero-volume week), rows keyed by the week's Monday.
- `1q` serves quarterly fundamentals (`eps`, `eps_estimate`, `eps_surprise`, `eps_surprise_pct`, `revenue`, `gross_profit`, `free_cash_flow`) on the fiscal grid — rows keyed by calendar quarter (`2026-Q2`) with each ticker's exact `fiscal_period` and `reported`, the date the quarter was announced (`null` when unknown). `reported` is the axis a point-in-time backtest filters on: the quarter's numbers were not knowable before it. Quarterly columns cannot mix with daily ones in one request — the grids are not joinable.
- `transitions_only=true` filters rows down to the state-change edges of the boolean signals — computed by the same shared rule that fires webhooks, so the two cannot disagree — with non-boolean signals riding along as context (the price at the flip). Numeric-threshold flips ("when did rsi_14 cross 70") are an [as-of](/docs/asof) question, not a transitions one.
- For charting, [Bars](/docs/endpoints/bars) is its own family on purpose: sub-hour intervals (`1s`–`30m`), fetch-on-miss coverage, and the compact array shape live there. Series is the analytical read; bars is the chart read.
- Both `GET` and `POST` are supported — `POST` takes the same parameters in a JSON body and accepts real arrays for `tickers`/`columns`.
- All-time — series history is never windowed.
- At `1q`, the two sources land at different times: `eps` arrives with the earnings announcement while statement signals (`revenue`, `gross_profit`, `free_cash_flow`) arrive with the filing. So the most recent quarter routinely carries an `eps` with `revenue: null` — a normal reporting lag, not a coverage gap. `reported` dates the announcement.
- `ticker` (singular) and `tickers` are both accepted; when both are present, `ticker` wins.
- Caps: 50 tickers, 25 columns, `limit` ≤ 1000, tickers × limit ≤ 25,000 rows per page. Over-cap `tickers`, `columns`, and tickers × limit are explicit 400s; an over-cap `limit` alone is clamped to 1000 (house convention — `limit=10000` means "max").
- On a mixed bars+state daily request, spine rows on non-trading days (weekends/holidays) are dropped and counted in `_meta.non_trading_days_dropped` — they could never carry OHLCV and would read as gaps. `transitions_only` requests are exempt: their output is already sparse, so carry rows are never dropped and a flip recorded on one keeps its original date no matter which columns ride along (bar columns are `null` on such a row and count in `_meta.coverage.*.missing_bar_cells`). Before 2026-08-10, adding a bar column re-dated such flips to the next trading day.
- [Ticker history](/docs/endpoints/tickers/history-series) and [Signal history](/docs/endpoints/signals/history) return the same data for one ticker / one signal in their original envelopes — kept stable for existing integrations. New integrations should read series.

---

Interactive sandbox + parameter editor: https://tickerbot.io/docs/endpoints/series/state
