# GET /v2/signals/{signal}

**Signal state**

The state of a signal is the set of tickers matching it right now, or with `asof`, as of any moment. One name, the whole market, one call.

## Query / path parameters

| Name | In | Type | Required | Description |
|------|----|----|----------|-------------|
| `signal` | path | string | yes | A signal name. Booleans (e.g. `golden_cross`, `above_sma_50`) are detected automatically; every other type (numeric `rsi_14`, timestamp `price_asof`, date `earnings_date`, string `asset_class`) requires a `condition`. Example: `rsi_14`. |
| `asof` | query | string | no | Optional. Target moment as `YYYY-MM-DD` (that day's close) or an ISO timestamp (that intraday moment) — the same read as it stood then, unlimited depth. Full contract under [As of a past date](#as-of). Example: `2026-06-28`. |
| `interval` | query | string | no | Grain the past state is reconstructed at: `1m`, `1h`, `1d`, or `auto` (default). Only valid alongside `asof` — a live read with `interval` is a 400. Details under [As of a past date](#as-of). Enum: `1m`, `1h`, `1d`, `auto`. Default: `auto`. |
| `condition` | query | string | no | Required for every non-boolean signal; the shape follows the signal's `type` in the catalog. Single bound, `<op><value>`. **numeric**: `>70`, `<=200`, `!=0` (operators `>`, `>=`, `=`, `!=`, `<`, `<=`). **timestamp**: an ISO instant, `<YYYY-MM-DDTHH:MM:SSZ` or `>=YYYY-MM-DD` (a bare date is midnight UTC). **date**: `>=YYYY-MM-DD` or `=YYYY-MM-DD`. **string**: `=ETF` or `!=ETF` (`=` and `!=` only; quotes optional). A relative window ("older than 15 minutes") is a `/v2/scan` query: `price_asof < now() - interval '15 minutes'`. Sending a condition with a boolean or custom signal returns 400 (it does not apply). Example: `>70`. |
| `universe` | query | string | no | Optional. Scope to a system or caller-owned universe slug. Example: `top_10`. |
| `limit` | query | integer | no | Page size. Max 200. Default: `50`. Example: `10`. |
| `cursor` | query | string | no | Opaque cursor from the previous response. |
| `sort_by` | query | string | no | Row order: `default` (alphabetic for booleans, highest-value-first for numerics) or `market_cap` (desc NULLS LAST; adds `market_cap` to each row). Live only — with `asof` it is a 400 (the snapshot's order is fixed). Enum: `default`, `market_cap`. Default: `default`. |
| `include_active_since` | query | boolean | no | Built-in booleans only: adds `active_since` and `days_live` per row — the first day of the current true streak, from daily state (the day after the last false day; if the boolean has never been false since it first computed, the first true day). Looks back five years, so a boolean true for longer reports the window edge as a lower bound. Live only — a 400 with `asof`. Default: `false`. Example: `true`. |

## Returns

- `as_of` (string) — Server time this response was assembled (ISO 8601).
- `signal` (string) — The signal you asked for.
- `condition` (string) — The bound you passed, echoed; `null` for boolean and custom signals.
- `universe` (string) — The universe you scoped to, echoed; `null` when unscoped.
- `_meta` (object) — With `asof`: how the read was resolved — interval served and requested, blending, sources, frozen fields (see the as-of read below). On any read, `deprecated_columns` when you named the signal under its pre-2026-09-07 spelling and it was served under its current name.
- `count` (number) — Rows in this page.
- `next_cursor` (string) — Opaque token for the next page; `null` on the last page. Pass it back as `cursor`.
- `results` (array) — Matching tickers with the signal value.

## Status codes

- **200** — Success — the response shape is documented under Returns above.
- **400** — Non-boolean signal called without `condition`, a condition whose shape does not match the signal's type, invalid cursor, or `interval` on a live read — `interval` selects the grain a PAST state is reconstructed at, so it is only valid alongside `asof`.
- **404** — Signal name does not exist on the schema.

## Sample response

```json
{
  "as_of": "2026-08-11T21:33:54.670Z",
  "signal": "rsi_14",
  "condition": ">70",
  "universe": null,
  "count": 3,
  "next_cursor": "eyJhZnRlcl92YWx1ZSI6OTkuMjY0NCwiYWZ0ZXJfdGlja2VyIjoiU0dWQSJ9",
  "results": [
    { "ticker": "TOMDF", "name": "Todos Medical Ltd",                       "value": 100 },
    { "ticker": "CMBO",  "name": "Wayfinder Dynamic U.S. Interest Rate ETF", "value": 99.4075 },
    { "ticker": "SGVA",  "name": "F/m Accumulator Ultrashort Treasury Fund", "value": 99.2644 }
  ]
}
```

## More examples

### Tickers at a 52-week high (boolean)

Request:

```shell
curl "https://api.tickerbot.io/v2/signals/at_52w_high?limit=3" \
  -H "Authorization: Bearer YOUR_KEY"
```

Response (`200`):

```json
{
  "as_of": "2026-08-11T21:35:37.719Z",
  "signal": "at_52w_high",
  "condition": null,
  "universe": null,
  "count": 3,
  "next_cursor": "eyJhZnRlcl90aWNrZXIiOiJBQk5HIn0",
  "results": [
    { "ticker": "AAAP", "name": "Pacer Barings CLO Market Flex ETF",              "value": true },
    { "ticker": "ABCS", "name": "Alpha Blue Capital US Small-Mid Cap Dynamic ETF", "value": true },
    { "ticker": "ABNG", "name": "Leverage Shares 2x Long ABNB Daily ETF",          "value": true }
  ]
}
```

## Notes

- Numeric matches come back sorted by signal value descending; boolean matches alphabetically by ticker.
- For the match set as of a past date, add `?asof=` — see [Ticker matches (as-of)](/docs/endpoints/signals/state#as-of).
- Custom signals: `{signal}` may be one of your custom signals — it resolves as a boolean signal (matches where its predicate is true; `condition` is ignored). See Create a custom signal.
- For booleans, the response value is `true`/`false`. For numerics it's the signal value at the time of the request.
- Pair with `asof` to ask "who matched as of a past date" — see [Ticker matches (as-of)](/docs/endpoints/signals/state#as-of). For a per-ticker time series of one signal, see [Series](/docs/endpoints/series).
- Membership: live reads return currently-active tickers only. As-of reads use point-in-time listing instead (was the ticker listed and not yet delisted at that instant), so a since-delisted name can legitimately appear in a historical match set.

---

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


---

## GET /v2/signals/{signal}?asof=

**Ticker matches (as-of)**

The set of tickers that matched one signal as of a past date.

### Query / path parameters

| Name | In | Type | Required | Description |
|------|----|----|----------|-------------|
| `signal` | path | string | yes | A signal name. Booleans (e.g. `golden_cross`, `above_sma_50`) are detected automatically; every other type (numeric `rsi_14`, timestamp `price_asof`, date `earnings_date`, string `asset_class`) requires a `condition`. Example: `rsi_14`. |
| `asof` | query | string | yes | Target moment as `YYYY-MM-DD` (the close of that day) or full ISO timestamp (that intraday moment). Unlimited depth. Example: `2026-06-28`. |
| `interval` | query | string | no | Grain the past state is reconstructed at: `1m`, `1h`, `1d`, or `auto` (default). Same name and values as [Series](/docs/endpoints/series/state) and [Bars](/docs/endpoints/bars/get). Not every signal exists at every grain (`rsi_14`, fundamentals and valuation ratios are `1d`-only), and a cross-section must resolve to ONE grain — so without this, one `1d`-only column in the projection silently re-dated every other column in the request. With it set, that case is a `400 interval_unavailable` naming the signal and the grains that carry it. `auto` picks the finest servable grain and reports which in `_meta.interval` / `_meta.interval_reason`. Enum: `1m`, `1h`, `1d`, `auto`. Default: `auto`. |
| `condition` | query | string | no | Required for every non-boolean signal; the shape follows the signal's `type` in the catalog. Single bound, `<op><value>`. **numeric**: `>70`, `<=200`, `!=0` (operators `>`, `>=`, `=`, `!=`, `<`, `<=`). **timestamp**: an ISO instant, `<YYYY-MM-DDTHH:MM:SSZ` or `>=YYYY-MM-DD` (a bare date is midnight UTC). **date**: `>=YYYY-MM-DD` or `=YYYY-MM-DD`. **string**: `=ETF` or `!=ETF` (`=` and `!=` only; quotes optional). A relative window ("older than 15 minutes") is a `/v2/scan` query: `price_asof < now() - interval '15 minutes'`. Sending a condition with a boolean or custom signal returns 400 (it does not apply). Example: `>70`. |
| `universe` | query | string | no | Scope to a system or caller-owned universe slug. Example: `top_10`. |
| `limit` | query | integer | no | Page size. Max 200. Default: `50`. Example: `10`. |
| `cursor` | query | string | no | Opaque cursor from the previous response. |

### Returns

- `as_of` (string) — The moment you asked about — the date's close, or the timestamp you passed.
- `signal` (string) — The signal you asked for.
- `condition` (string) — The bound you passed, echoed; `null` for boolean and custom signals.
- `universe` (string) — The universe you scoped to, echoed; `null` when unscoped.
- `_meta` (object) — Grain served (`interval`), why (`interval_reason`), `frozen_fields`, and `coverage_gap` when one applies. `_meta.resolution` is a deprecated alias of `interval`. `deprecated_columns` lists any column you named under its pre-2026-09-07 spelling that was served under its current name (`requested`, `use`, `note`).
- `count` (number) — Rows in this page.
- `next_cursor` (string) — Opaque token for the next page; `null` on the last page. Pass it back as `cursor`.
- `results` (array) — Tickers that matched at that instant.

### Status codes

- **200** — Success — the response shape is documented under Returns above.
- **400** — Missing/malformed `asof`, non-boolean signal called without `condition` (or a condition whose shape does not match the signal's type), or invalid cursor.
- **404** — Signal name does not exist on the schema.

### Sample response

```json
{
  "as_of": "2024-02-23",
  "signal": "rsi_14",
  "condition": ">70",
  "universe": null,
  "_meta": {
    "interval": "1d",
    "interval_requested": "auto",
    "interval_reason": "date-only asof resolves to the daily close",
    "resolution": "daily",
    "frozen_fields": ["name", "sector", "industry", "asset_type", "exchange", "asset_class", "ticker_category"]
  },
  "count": 3,
  "next_cursor": "eyJhZnRlcl92YWx1ZSI6OTcuNTY2OTA4MDU1Mjc0ODgsImFmdGVyX3RpY2tlciI6IlI6RUZGUiJ9",
  "results": [
    { "ticker": "R:FFLOWER", "name": "Fed Funds Target Range — Lower Bound", "value": 100 },
    { "ticker": "R:FFUPPER", "name": "Fed Funds Target Range — Upper Bound", "value": 100 },
    { "ticker": "R:EFFR",    "name": "Effective Federal Funds Rate",         "value": 97.56690805527488 }
  ]
}
```

### More examples

#### Boolean: who was at a 52-week high that day

Request:

```shell
curl "https://api.tickerbot.io/v2/signals/at_52w_high?asof=2024-02-23&limit=3" \
  -H "Authorization: Bearer YOUR_KEY"
```

Response (`200`):

```json
{
  "as_of": "2024-02-23",
  "signal": "at_52w_high",
  "condition": null,
  "universe": null,
  "_meta": {
    "interval": "1d",
    "interval_requested": "auto",
    "interval_reason": "date-only asof resolves to the daily close",
    "resolution": "daily",
    "frozen_fields": ["name", "sector", "industry", "asset_type", "exchange", "asset_class", "ticker_category"]
  },
  "count": 3,
  "next_cursor": "eyJhZnRlcl90aWNrZXIiOiJBRkxHIn0",
  "results": [
    { "ticker": "ABEQ", "name": "Absolute Select Value ETF",              "value": true },
    { "ticker": "ACR",  "name": "ACRES Commercial Realty Corp.",          "value": true },
    { "ticker": "AFLG", "name": "First Trust Active Factor Large Cap ETF", "value": true }
  ]
}
```

### Notes

- This is [`GET /v2/signals/{signal}`](/docs/endpoints/signals/state) with `?asof=` — [as-of](/docs/asof) at one-signal scope, the second tense of the same page because point-in-time is its own question. The response envelope matches the live call, plus `_meta` describing the grain served and why.
- Pass `asof` as a `YYYY-MM-DD` date (that day's close) or a full ISO timestamp (that intraday moment), and pick the grain with `interval`. A match set is a cross-section, so it resolves to ONE grain — and `1m` is only servable for a scoped `universe`, since the minute tier carries ~1,100 of ~13,700 tickers.
- Custom signals: `{signal}` may be one of your custom signals — it resolves as a boolean signal. See Create a custom signal.
- A match set spans every asset class the signal is defined for, so `R:` rates and `X:` FX/crypto instruments appear alongside equities — the example above is topped by Fed funds rates, not stocks. Scope with `universe` (or filter `asset_class` via /v2/scan) when you want one class.
- Static fields aren't historized — `name`, `sector`, `industry`, `asset_type`, and `exchange` reflect their *current* values. `_meta.frozen_fields` lists exactly which ones.
- For per-ticker time series of one signal across many bars (not a point-in-time snapshot), use [Series](/docs/endpoints/series/state) — `?ticker={t}&columns={signal}`.
- Unlimited depth.
- A date-only `asof` resolves to the close of that day. A full ISO timestamp (`…T15:30:00Z`) resolves intraday at the grain you name with `interval` (`1m`/`1h`/`1d`/`auto`). `_meta.interval` reports the grain served and `_meta.interval_reason` why; tickers with no row at that grain are disclosed in `_meta.coverage_gap` with a sample. Illiquid instruments (SPAC warrants, units, rights) routinely have no intraday print, so a small `coverage_gap` at `1m`/`1h` is normal rather than a fault. If there is no state exactly at the requested moment (weekend, holiday, or a gap), the server carries forward the most-recent row within the lookback window (**14 days** at daily resolution, 5 days for intraday granularities) — a Saturday `asof` returns Friday's values, not an empty set. Beyond the window you get an empty `results` array with the echoed `asof`. (`/v2/tickers/{ticker}?asof=` applies the identical staleness rule since 2026-07-27, and 404s with `last_available` when nothing is fresh enough.)
- Membership is point-in-time (survivorship-safe): the as-of set includes tickers that were listed and not yet delisted at that instant, so a since-delisted name can legitimately appear. The live read of this same endpoint returns currently-active tickers only.

---

Interactive sandbox + parameter editor: https://tickerbot.io/docs/endpoints/signals/state#as-of
