View as markdown
Endpoints

Custom signals

A custom signal is a named boolean SQL predicate. Once you create one, it behaves like a built-in column: reference it by name anywhere a WHERE clause is accepted, or use it as a signal name across the read and subscribe endpoints.

Where you can reference one

A custom signal is a filter, not a returnable column — you match with it, you don’t select it.

SurfaceWorks?How
Scan q (POST /v2/scan)YesName it in the WHERE clause; it expands to its stored SQL at request time.
Signal name — /v2/signals/{signal} match, /history, /eventsYesPass the custom’s name as the signal. It’s indistinguishable from a built-in.
Ticker subscribe conditionYesReferenced in the condition, then frozen (see below).
Webhook triggers (scan / ticker / signal)YesExpanded and frozen into the stored trigger at creation.
Signal catalog (GET /v2/signals?kind=custom)YesListed alongside built-ins, with its expr returned.
Scan select / fields / orderNoThese return or sort columns; a custom is a boolean predicate — filter with it in q instead.
Events grammar (/v2/events q / event_q)NoThat grammar is a fixed four-column namespace (ticker, ts, kind, payload); custom names aren’t resolved there.

Signals can reference other signals

Compose small predicates into bigger ones — up to five levels deep.

A custom signal’s expression may reference other custom signals by name. References expand recursively at request time, up to five levels of nesting; a deeper chain — or a circular reference — returns 400 invalid_query.

Two guardrails are enforced when you create a signal, so a reference is always an unambiguous lookup: a signal can’t reference itself (rejected at create), and its name can’t collide with a built-in column name (returns 409 name_collision).

Subscriptions freeze the definition

A webhook captures the SQL as it was the moment you created it.

When a custom signal is used in a webhook trigger, its SQL is expanded and stored on the subscription at creation time. Editing the signal later does not retroactively change existing webhooks — they keep firing on the definition they were created with. Re-create the subscription to pick up a new definition.

Keep them single-ticker for cross-endpoint use

Cross-ticker references work in scan, but not on the signal endpoints.

Scan rewrites cross-ticker references (e.g. spy.close) as it expands your query, so a custom signal that compares one ticker against another works in a /v2/scan q. The /v2/signals family (match, history, events, subscribe) does not perform that rewrite, so a custom that uses cross-ticker references will return a 400 there. Keep a signal single-ticker if you want to use it everywhere.