View as markdown
Endpoints · Signals

Create a custom signal

POSThttps://api.tickerbot.io/v2/signals

Persists 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/scan q (live and asof), mixed freely with columns: q = my_squeeze AND market_cap > 1e9.
  • Signal — as the signal itself on GET /v2/signals/{name} (live and asof) and GET /v2/signals/{name}/{ticker}/history; it behaves like a built-in boolean signal (matches where the predicate is true, condition is ignored).
  • Ticker — inside a POST /v2/tickers/{ticker}/subscribe condition.
  • Subscribe — as the signal on POST /v2/signals/{name}/subscribe, or referenced from a scan/ticker subscribe q/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.

stringrequired

Slug — `^[a-z][a-z0-9_]{0,63}$`. Must not collide with any built-in column name.

stringrequired

Boolean SQL predicate. May reference built-in columns and other custom signals you own. Must evaluate to true/false. Max 4000 chars.

string

Free-form notes. Max 500 chars.

201
{ as_of, signal: { name, kind: "expression", description, expr, created_at, updated_at } }.
400
compile_failed (with errors array) when the expression doesn't parse / references unknown columns. bad_request for shape failures.
403
custom_signals_tier_required — Hobby/Pro can read but not write custom signals.
409
already_exists (slug taken on this account) or name_collision (slug matches a built-in column).