Create a custom signal
https://api.tickerbot.io/v2/signalsPersists a named boolean predicate you can reference anywhere a built-in signal goes. A custom signal is always boolean — the expression must evaluate to true/false (a non-boolean root is rejected at compile).
Once saved, the name resolves in these contexts, expanded to its SQL before the query runs:
- Scan — inside
/v2/scanq(live andasof), mixed freely with columns:q = my_squeeze AND market_cap > 1e9. - Signal — as the signal itself on
GET /v2/signals/{name}(live andasof) andGET /v2/signals/{name}/{ticker}/history; it behaves like a built-in boolean signal (matches where the predicate is true,conditionis ignored). - Ticker — inside a
POST /v2/tickers/{ticker}/subscribecondition. - Subscribe — as the signal on
POST /v2/signals/{name}/subscribe, or referenced from a scan/ticker subscribeq/condition. - Composition — inside another custom signal's
expr; nested references are inlined, and recursion is detected.
It does not resolve on GET /v2/signals/{name}/{ticker}/events — occurrence spans are precomputed for built-in flags only, so that endpoint returns 400 not_supported_for_custom_signal and points you at /history or /scan?asof=.
Resolution is an exact match against your own custom signal names. A name can never collide with a built-in column (create returns 409 name_collision), so the two never ambiguate; any identifier that isn't one of your custom signals is left as raw SQL.
Freeze on subscribe: the subscribe endpoints expand the custom signal into the stored webhook query at creation time, so a subscription is a snapshot. Editing or deleting the custom signal later does not change existing subscriptions — re-subscribe to apply a new definition. Ad-hoc reads (scan/signal GETs) always reflect the current definition.
The expression is compiled at create time against the live column whitelist; references to other custom signals you own are inlined first. Names are scoped per-user: (user_id, name) is the key.
Pro tier for both create AND consume. Custom signals are a Pro-plan feature end to end: creating one requires Pro, and so does *referencing* one — in a scan q, a ticker condition, as a signal name, or via history/stats/subscribe. A request that references a custom signal on a lower tier returns 403 custom_signals_tier_required — including a downgraded account whose custom signals still exist (they can no longer be consumed). Free/Hobby can still list the /v2/signals catalog (metadata only).
Plan access
Pro and above. Free/Hobby can read the catalog but create/update/delete + consume return 403 custom_signals_tier_required.
Rate limit
Hobby 600/min · Pro 6,000/min · Scale 60,000/min.
Capacity
Up to 10 custom signals on Pro; unlimited on Scale and Enterprise.
Body parameters
stringrequiredSlug — `^[a-z][a-z0-9_]{0,63}$`. Must not collide with any built-in column name.
stringrequiredBoolean SQL predicate. May reference built-in columns and other custom signals you own. Must evaluate to true/false. Max 4000 chars.
stringFree-form notes. Max 500 chars.
Status codes
201{ as_of, signal: { name, kind: "expression", description, expr, created_at, updated_at } }.400compile_failed (with errors array) when the expression doesn't parse / references unknown columns. bad_request for shape failures.403custom_signals_tier_required — Hobby/Pro can read but not write custom signals.409already_exists (slug taken on this account) or name_collision (slug matches a built-in column).