{"name":"io.github.tickerbot/mcp-server","slug":"tickerbot-mcp-server","title":null,"description":"The stock market, in SQL — scan, replay, or subscribe across ~12k US tickers and top 100 cryptos.","url":"https://mcp.market/server/tickerbot-mcp-server","rating":null,"grade":"A","score":90,"certified":false,"status":"active","category":"data","tags":["data","finance","security"],"presence":{"score":33,"stars":0,"forks":0,"downloads_week":80,"last_push_at":"2026-09-17T23:56:24.000Z","license":"MIT"},"uptime":{"percent":96,"checks":28,"ok":27,"last_checked_at":"2026-09-27T12:31:17.328Z","last_ok_at":"2026-09-27T12:31:17.328Z","latency_ms":367},"claimed":false,"transport":"mixed","callable_via_gateway":true,"default_price_micros":0,"repository":"https://github.com/tickerbot/tickerbot-mcp","website":"https://tickerbot.io/mcp-server","version":"0.1.3","remotes":[{"type":"streamable-http","url":"https://api.tickerbot.io/mcp","headers":[{"description":"Bearer <TICKERBOT_API_KEY>","isRequired":true,"isSecret":true,"name":"Authorization"}]}],"packages":[{"registryType":"npm","registryBaseUrl":"https://registry.npmjs.org","identifier":"@tickerbot/mcp-server","version":"0.1.3","transport":{"type":"stdio"},"environmentVariables":[{"description":"Tickerbot API key (tb_live_… or tb_test_…). Get one at https://tickerbot.io/dashboard.","isRequired":true,"isSecret":true,"name":"TICKERBOT_API_KEY"}]}],"tools":[{"name":"tickerbot_create_custom_signal","description":"A named boolean predicate you can reference anywhere a built-in signal goes.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"name":{"type":"string","description":"Slug — `^[a-z][a-z0-9_]{0,63}$`. Must not collide with any built-in signal name, and 15 names are reserved outright: `columns`, plus the `/v2/series` OHLCV aliases `open`/`high`/`low`/`close`/`volume`/`vwap`/`trades` and `o`/`h`/`l`/`c`/`v`/`vw`/`n` (those resolve to bars before custom lookup). This is the signal's API handle: it's what you reference in `q` and in the CRUD path."},"expr":{"type":"string","description":"Boolean SQL predicate. May reference built-in signals and other custom signals you own. Must evaluate to true/false. Max 4000 chars. Stricter grammar than scan `q`: comparisons, `AND`/`OR`/`NOT`, `IN`, `BETWEEN`, `IS [NOT] NULL`, arithmetic, and the functions `abs`/`coalesce`/`round`/`least`/`greatest` only — no `LIKE`/`ILIKE`, no `CASE`, no `::` casts, no other functions. An expression that scans fine can still be rejected here with `compile_failed`. What you send is what you read back: responses echo your expression as stored, not its expansion. A signal referencing another custom of yours returns the reference as you typed it — the inlined SQL exists only internally, and is what a subscribe endpoint freezes into a webhook. The one rewrite: a column named under its pre-2026-09-07 spelling (`bollinger_pct_b`) is stored under its current name (`bollinger_b`) and reported in `_meta.deprecated_columns`."},"description":{"type":"string","description":"Free-form notes. Max 500 chars. Absent or empty comes back as `\"\"` rather than null."}},"required":["name","expr"]}},{"name":"tickerbot_create_universe","description":"Create a named ticker list owned by your account.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"name":{"type":"string","description":"Human-readable label, up to 80 characters. Display-only — never used to reference the universe."},"tickers":{"type":"array","items":{"type":"string"},"description":"Ticker symbols, up to 10,000. Validated against the active universe. `[]` is accepted — a shell universe you can fill later via PATCH."},"id":{"type":"string","description":"Optional slug — becomes the universe's permanent handle everywhere (`?universe=`, subscribe `universe`, CRUD path). Pattern `^[a-z][a-z0-9_]{0,62}$` — starts with a lowercase letter, then lowercase letters/digits/underscore, 63 chars max; the value is trimmed and lowercased before validation. `top_10` and `top_100` are reserved for system universes and rejected with 400. Must be unique within your account. Generated (`u_…`) if omitted."},"description":{"type":"string","description":"Free-form notes, up to 500 characters. Stored as `\"\"` when omitted."}},"required":["name","tickers"]}},{"name":"tickerbot_create_webhook","description":"Canonical create: a webhook is a trigger plus a delivery. Trigger shapes: scan {type:\"scan\", q, universe?}; ticker {type:\"ticker\", ticker, condition}; signal {type:\"signal\", signal, ticker?, universe?, condition?}; event {type:\"event\", kinds, tickers?, universe?, event_q?}. The subscribe tools are flat sugar over exactly this. Webhooks need a paid plan (Free has no webhook slots).","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"trigger":{"type":"object","description":"What fires the webhook. A discriminated object — `trigger.type` picks the shape, and the fields below belong inside it. Each shape is also available as a flat-params shortcut: `POST /v2/scan/subscribe`, `/v2/tickers/{t}/subscribe`, `/v2/signals/{s}/subscribe`, `/v2/events/subscribe`.","properties":{"type":{"type":"string","description":"Which trigger shape the rest of the object uses.","enum":["scan","ticker","signal","event"]},"q":{"type":"string","description":"scan: required — the SQL WHERE any ticker must match to fire. ticker: required — WHERE fragment evaluated for that ticker (auto-scoped; don't add `ticker = …` yourself; `trigger.condition` accepted as an alias). event: optional row-STATE filter on the event's ticker at fire time (`market_cap > 1e10`)."},"signal":{"type":"string","description":"signal: required — a built-in signal name (e.g. `rsi_14`) or one of your custom signals (custom SQL is expanded and frozen at creation)."},"condition":{"type":"string","description":"signal: required for every non-boolean signal — a single bound typed like the signal: `>70` (numeric), `<YYYY-MM-DDTHH:MM:SSZ` (timestamp), `>=YYYY-MM-DD` (date), `=ETF` (string); sending one with a boolean or custom signal returns 400 (it does not apply). ticker: accepted as the original alias of `trigger.q`."},"ticker":{"type":"string","description":"ticker: required — the symbol to watch (e.g. `NVDA`). signal: optional — restrict the signal to one symbol (omit to watch the whole universe)."},"tickers":{"type":"string","description":"event: optional symbol list, max 50 (e.g. `AAPL,NVDA`). Mutually exclusive with `trigger.universe`."},"kinds":{"type":"string","description":"event: required — event kinds to fire on, array or comma list (e.g. `split,analyst`)."},"event_q":{"type":"string","description":"event: optional event-CONTENT filter in the `/v2/events` grammar over `(ticker, ts, kind, payload)` — e.g. `payload->>'firm' = 'Goldman Sachs'`. Composes with `trigger.q`."},"universe":{"type":"string","description":"scan / signal / event: optional universe slug (`top_10`, `top_100`, or one of yours) scoping which tickers can fire. Mutually exclusive with `trigger.tickers` on event."}},"required":["type"]},"target_url":{"type":"string","description":"HTTPS delivery URL (the `webhook` channel), max 1024 characters. Omit for in-app delivery, or use `channel` + `discord_url`/`device_id` for other channels."},"channel":{"type":"string","description":"Delivery channel. See Delivery channels.","enum":["webhook","discord","in_app","mobile_push"]},"discord_url":{"type":"string","description":"Discord webhook URL (channel `discord`)."},"device_id":{"type":"string","description":"Registered device id (channel `mobile_push`, see /v2/devices)."},"cadence":{"type":"string","description":"Evaluation cadence — a user preference — never gated. Event triggers deliver on ingest — only `realtime` is accepted on them (400 otherwise).","enum":["realtime","hourly","nyse_open"],"default":"realtime"},"name":{"type":"string","description":"Display name, max 80 characters. Defaults to an auto-generated one from the trigger."},"columns":{"type":"string","description":"Extra columns echoed in fired payloads' match rows (`fields` accepted as an alias). Not accepted on `event` triggers (400) — event deliveries carry the event payload, not state rows."},"order":{"type":"string","description":"Signal the fired payload's match lists are sorted by before the 100-row cap is applied — so a truncated list is the deterministic top 100, not an arbitrary sample. Same contract as `POST /v2/scan`. Not accepted on `event` triggers (they deliver one event at a time).","default":"market_cap"},"dir":{"type":"string","description":"Sort direction for `order`. Not accepted on `event` triggers (400).","enum":["asc","desc"],"default":"desc"}},"required":["trigger"]}},{"name":"tickerbot_delete_custom_signal","description":"Delete one of your custom signals. Refused by default if another of your signals references it.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"signal":{"type":"string","description":"Custom signal slug (the signal name). A built-in name answers 404 — built-ins are read-only."},"force":{"type":"boolean","description":"When `true`, skip the reference check and delete. References will break on next recompile.","default":false}},"required":["signal"]}},{"name":"tickerbot_delete_universe","description":"Permanently delete one of your universes. Webhooks that reference the deleted universe fail on their next eval — clean those up first.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"id":{"type":"string","description":"Universe slug."},"force":{"type":"boolean","description":"A universe still referenced by live webhooks refuses to delete with `409 universe_referenced`. Pass `force=true` to delete anyway — those webhooks will match nothing until re-pointed or deleted.","default":false}},"required":["id"]}},{"name":"tickerbot_delete_webhook","description":"Delete a webhook subscription, and its delivery history with it.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"id":{"type":"string","description":"Webhook id."}},"required":["id"]}},{"name":"tickerbot_enable_webhook","description":"Re-enable a disabled webhook and start it clean. Clears match-state, so the next eval treats every currently-matching ticker as new.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"id":{"type":"string","description":"Webhook id."}},"required":["id"]}},{"name":"tickerbot_get_bars","description":"OHLCV bars from 1-second through monthly. The prices underneath the table.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"ticker":{"type":"string","description":"Ticker symbol, or a comma-separated list (up to 50) for a bulk response keyed by symbol."},"interval":{"type":"string","description":"Bar interval. `2h`/`4h` roll up hourly bars; `1w`/`1mo` roll up daily bars into calendar weeks (Monday start) and months — the bar's `t` is the bucket start (UTC), and with `asof` the last bucket is the week/month to date.","enum":["1s","1m","5m","15m","30m","1h","2h","4h","1d","1w","1mo"]},"from":{"type":"string","description":"Window start (inclusive): `YYYY-MM-DD`, ISO timestamp, or epoch-ms. Combines with `to` for an explicit window; page within it using `cursor`. Mutually exclusive with `asof` and `before` (400)."},"to":{"type":"string","description":"Window end (inclusive): a bare `YYYY-MM-DD` means through the end of that day, same as series. Mutually exclusive with `asof` and `before` (400)."},"asof":{"type":"string","description":"Point-in-time: the most recent bar whose period had closed at or before that moment. A bare `YYYY-MM-DD` means that day's close. A full timestamp means the last FINISHED bar — at 10:00 ET on a Wednesday the day's close has not happened, so `1d` returns Tuesday's bar. Returns one bar unless you also pass `limit`, which gives the last `limit` closed bars. Mutually exclusive with `before`/`cursor` (400). Unlimited depth."},"adjusted":{"type":"boolean","description":"Default `true`: prices are split-adjusted — restated after each later split, as the tape is, so a series is continuous across a split. `false` returns the price as it printed that day (a name that later did a 1:10 reverse split reads `21.4` adjusted and `2.14` on the tape), which is what a broker fill or a chart from that time shows. Volume scales the other way. Un-adjusted on read from the splits table; the store is untouched."},"session":{"type":"string","description":"Sub-hour intervals only. `all` (default) includes pre- and post-market bars. `regular` keeps bars whose start is in 09:30–16:00 ET (DST-aware). Why you might want it: the vendor buckets trades by SIP report time, and late-reported off-exchange (Form T) prints on thin names can land 20 min to hours late in a pre-market minute — a `$1.70` print at 08:13 ET on a `$3.85` stock. Daily high/low are untouched by those. `limit` counts after the filter; paging still works.","enum":["all","regular"],"default":"all"},"limit":{"type":"integer","description":"Most-recent N bars. Max 1000 — an over-cap value is clamped, not an error.","default":100},"before":{"type":"string","description":"Return the N bars ending strictly before this date/timestamp — back-paging. Mutually exclusive with `cursor` (they are the same control — a 400 when both are sent)."},"cursor":{"type":"string","description":"Continuation token from a prior response's `next_cursor`; sugar for `before` (sending both is a 400; a blank `cursor=` counts as absent), and the way to page inside a `from`/`to` window."}},"required":["ticker","interval"]}},{"name":"tickerbot_get_etf_holdings","description":"Returns an ETF's constituents and their weights, heaviest first. When the ticker is not an ETF, `is_etf` is false and `holdings` is empty; `is_etf: true` with zero holdings means a real ETF whose holdings are not ingested yet. The reverse lookup (\"which ETFs hold NVDA\") is a scan filter on `etf_holders`, not this tool.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"ticker":{"type":"string","description":"ETF symbol. Case-insensitive."},"limit":{"type":"integer","description":"Max holdings returned. Max 5000. When the cap cuts the list, the response sets `truncated: true` and `total` (the ETF's full holding count) — raise `limit` to at least `total` to get the full set, possible whenever `total` is within the 5000 cap (an over-cap `limit` is clamped to 5000, not an error). No `truncated` in the response means the list is complete.","default":500}},"required":["ticker"]}},{"name":"tickerbot_get_etf_sectors","description":"Returns an ETF's sector weights, heaviest first. Always complete, since sector breakdowns are small. When the ticker is not an ETF, `is_etf` is false and `sectors` is empty.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"ticker":{"type":"string","description":"ETF symbol. Case-insensitive."}},"required":["ticker"]}},{"name":"tickerbot_get_series","description":"Any signals for any tickers on one shared time grid — up to 50 tickers by 25 columns per call. One flat row per ticker per interval step, cursor-paged backward. `transitions_only: true` with boolean signals returns only the rows where a boolean flipped.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"tickers":{"type":"string","description":"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."},"ticker":{"type":"string","description":"Single-symbol form — `/v2/series?ticker=AAPL` is ticker history in its canonical spelling. Exactly one of `ticker` or `tickers` is required."},"columns":{"type":"string","description":"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."},"interval":{"type":"string","description":"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":{"type":"string","description":"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."},"to":{"type":"string","description":"Latest timestamp (inclusive), `YYYY-MM-DD` or ISO."},"asof":{"type":"string","description":"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`, `/v2/scan` and `/v2/signals`. 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."},"limit":{"type":"integer","description":"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},"cursor":{"type":"string","description":"Opaque cursor from the previous response — every ticker pages backward in lockstep on the shared grid, no per-ticker gaps or duplicates."},"transitions_only":{"type":"boolean","description":"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)."}}}},{"name":"tickerbot_get_signal","description":"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. Booleans need no `condition`; every other type needs one, typed like the signal: \">70\" (numeric), \"<YYYY-MM-DDTHH:MM:SSZ\" (timestamp), \">=YYYY-MM-DD\" (date), \"=ETF\" (string). Sorted by signal value desc for non-booleans.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"signal":{"type":"string","description":"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`."},"asof":{"type":"string","description":"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."},"interval":{"type":"string","description":"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.","enum":["1m","1h","1d","auto"],"default":"auto"},"condition":{"type":"string","description":"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)."},"universe":{"type":"string","description":"Optional. Scope to a system or caller-owned universe slug."},"limit":{"type":"integer","description":"Page size. Max 200.","default":50},"cursor":{"type":"string","description":"Opaque cursor from the previous response."},"sort_by":{"type":"string","description":"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":{"type":"boolean","description":"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}},"required":["signal"]}},{"name":"tickerbot_get_ticker","description":"The full ticker row, every signal on the schema page, for one symbol or a comma list of up to 50. Right now, or with `asof`, as of any past date. Pass a comma list of up to 50 symbols for a batch (`data` keyed by symbol plus `not_found`). Crypto is the X-prefixed pair (X:BTCUSD) — bare BTC/ETH are US-listed ETFs.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"ticker":{"type":"string","description":"One symbol, or a comma-separated list of up to 50 for a batch response keyed by symbol. Case-insensitive. Equities are bare symbols (`AAPL`); every other class carries a prefix — rates (`R:SOFR`), crypto (`X:BTCUSD`), fx (`X:EURUSD`). Bare `BTC`/`ETH` are US-listed ETFs, not spot crypto. See Tickers."},"asof":{"type":"string","description":"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."},"interval":{"type":"string","description":"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.","enum":["1m","1h","1d","auto"],"default":"auto"}},"required":["ticker"]}},{"name":"tickerbot_get_ticker_coverage","description":"For one ticker, what we hold and how far back — so an empty result is never ambiguous. Ask this before treating a gap in bars or series as an outage. `minute_tier.included: false` with `on_demand: true` is not a gap — sub-hour bars fetch from the provider on first request.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"ticker":{"type":"string","description":"Case-insensitive. Equities are bare symbols (`AAPL`); every other class carries a prefix — rates (`R:SOFR`), crypto (`X:BTCUSD`), fx (`X:EURUSD`). Bare `BTC`/`ETH` are US-listed ETFs, not spot crypto. See Tickers."}},"required":["ticker"]}},{"name":"tickerbot_get_universe","description":"Returns the universe doc. Use `top_10`/`top_100` to fetch a system universe; any other slug must be one your account owns.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"id":{"type":"string","description":"Universe slug."}},"required":["id"]}},{"name":"tickerbot_get_webhook","description":"The current state of one webhook subscription.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"id":{"type":"string","description":"Webhook id returned by a subscribe endpoint (`POST /v2/tickers/{T}/subscribe`, etc.)."}},"required":["id"]}},{"name":"tickerbot_list_events","description":"One timeline across every ticker: earnings, dividends, splits, insider filings, analyst actions, plus opt-in signal firings and news. Requires at least one bound: a ticker scope (ticker/tickers/universe), a time window (from/to), or firm/action — `q` alone is not a bound. `firm`/`action` match case-insensitively; a `q` payload match is case-sensitive. `join: state` attaches the ticker state as of each event.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"kind":{"type":"string","description":"Comma list of kinds. Omitted → the five corporate kinds; `signal` and `news` join only when named here.","enum":["dividend","split","insider","analyst","earnings","signal","news"]},"ticker":{"type":"string","description":"Single-ticker filter. When both `ticker` and `tickers` are passed, `ticker` wins."},"tickers":{"type":"string","description":"Comma list of tickers (max 50). Mutually exclusive with `universe`."},"universe":{"type":"string","description":"Universe slug (`top_10`, `top_100`, or one of yours) to scope the stream. Mutually exclusive with `tickers`."},"firm":{"type":"string","description":"Analyst-only structured filter — requires `kind=analyst` alone (`400` otherwise). Exact firm-name match on the ratings feed."},"action":{"type":"string","description":"Analyst-only structured filter — requires `kind=analyst` alone. Same `action` vocabulary as Analyst actions.","enum":["upgrades","downgrades","initiates_coverage_on","maintains","reiterates","assumes","reinstates","suspends","terminates_coverage_on"]},"signal":{"type":"string","description":"Signal-only filter — requires `kind=signal` alone (`400` otherwise). One built-in boolean signal; REQUIRED with `q` or `join=state` on that kind. See Signal firings."},"transition":{"type":"string","description":"Signal-only filter — requires `kind=signal` alone. `enter` (false→true) or `exit` (true→false).","enum":["enter","exit"]},"from":{"type":"string","description":"Events at/after this instant — strict ISO: `YYYY-MM-DD` or `YYYY-MM-DDTHH:MM[:SS]Z`. A bare `YYYY-MM-DD` means from the start of that day. (`since` accepted as an alias.)"},"to":{"type":"string","description":"Window end — same strict ISO subset. A bare `YYYY-MM-DD` means through the end of that day, matching bars/series/spans; a timestamp is exclusive (events strictly before it). (`until` accepted as an alias.)"},"q":{"type":"string","description":"SQL WHERE over the projection — `ticker`, `ts`, `kind`, `payload` (plus ticker-state signals when `join=state`). When exactly ONE `kind` is named, that kind's payload fields are additionally first-class typed columns (`amount > 1`, `firm = 'Goldman Sachs'` — see each kind page for its list); multi-kind requests use `payload->>'…'`. Max 4000 chars. ANDs with the filter params."},"join":{"type":"string","description":"Set to `state` to allow ticker-state signals in `q`/`select`/`group_by`/`having`, evaluated as of each event's timestamp (daily resolution).","enum":["state"]},"interval":{"type":"string","description":"Grain the per-event state is reconstructed at, when `join=state`: `1m`, `1h`, `1d`, or `auto` (default). `auto` resolves to `1d` — the event set's tickers are not known before the query runs, and `1d` is the only tier covering the whole universe, so it is the only grain guaranteed to satisfy every event. An explicit `1m`/`1h` trades coverage for precision: events on tickers absent from that tier join to `null`. A referenced column the grain does not store is a `400`. Reported back as `_meta.state_interval`.","enum":["1m","1h","1d","auto"],"default":"auto"},"select":{"type":"string","description":"Aggregate-mode output columns (requires `group_by`). Default: group keys + `COUNT(*) AS events`. Same naming rule as `group_by` — alias with `AS`, or take the name derived for you."},"group_by":{"type":"string","description":"Comma list of rollup keys — switches the response to aggregate rows. Columns (`kind`, `ticker`), payload fields (`firm`, or the explicit `payload->>'firm'`), and expressions over them all roll up. Name a key with `AS` to choose its JSON key: `payload->>'firm' AS firm`. Un-named keys are named for you — a payload read takes its key (`payload->>'firm'` → `firm`), a function keeps the function's name (`lower(ticker)` → `lower`), and anything else falls back to `group_1`, `group_2`."},"having":{"type":"string","description":"Post-aggregation filter. Requires `group_by`."},"order":{"type":"string","description":"Aggregate-mode sort — a bare column name or an output name only (put expressions in `select` and sort by their alias). A group key's name works too, whether you aliased it or it was named for you: `group_by=payload->>'firm' AS firm&order=firm`. Default: `events`. (Row mode is always newest-first.)"},"dir":{"type":"string","description":"Aggregate-mode sort direction.","enum":["asc","desc"],"default":"desc"},"limit":{"type":"integer","description":"Page size (row modes) / max rollup rows (aggregate mode). Max 1000.","default":50},"cursor":{"type":"string","description":"Opaque cursor from the previous response — carries the original filters (and `q` when short), so pass it alone. Not valid with `group_by`."}}}},{"name":"tickerbot_list_signals","description":"Every signal you can name in a query — the built-in signals and your own custom signals, in one catalog. Use to discover the signal names and `q` vocabulary before composing a scan; custom signals appear with `kind: custom`.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"kind":{"type":"string","description":"Filter by kind (`expression` accepted as a legacy alias for `custom`). Omit to return both.","enum":["builtin","custom"]},"limit":{"type":"integer","description":"Page size for the custom-signal slice. Max 200.","default":50},"cursor":{"type":"string","description":"Opaque cursor from a prior response."}}}},{"name":"tickerbot_list_tickers","description":"Every symbol we track, active or delisted, as one identity row each. Use `/v2/tickers/{ticker}` for the full row.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"search":{"type":"string","description":"Case-insensitive match on `ticker` or `name`, max 64 characters (longer is a 400). Results are ranked: an exact ticker match first, then symbols that start with the term, then name matches — alphabetical within each rank. The cursor carries the rank, so paging a search never repeats or skips."},"asset_class":{"type":"string","description":"Filter by asset class — `stocks`, `rates`, `crypto`, `fx`, or a comma-separated list (the live classes today; validated for shape, not against a fixed list, so a well-formed class we don't track simply matches nothing — same contract as scan). Omit for every class. This is the class of INSTRUMENT, distinct from `asset_type` below (the instrument type within equities). Every row carries its `asset_class`, so a non-equity row identifies itself."},"asset_type":{"type":"string","description":"Filter by instrument type WITHIN equities — the stored `asset_type` value (`CS`, `ETF`, `ADRC`, `PFD`, `FUND`, `UNIT`, `SP`, `ETS`, `WARRANT`, `RIGHT`, `ETN`, `ETV`), matched case-insensitively. `equity` is a convenience value expanding to the equity-like set. This is NOT an asset class: `asset_type=crypto` is rejected — use `asset_class=crypto`.","enum":["CS","ETF","ADRC","PFD","FUND","UNIT","SP","ETS","WARRANT","RIGHT","ETN","ETV","equity"]},"exchange":{"type":"string","description":"Filter by exchange name — the value rows carry in their `exchange` field. MIC codes (`XNAS`, `XNYS`, `BATS`) are also accepted and match `exchange_mic`. A malformed value (non-letters, over 16 chars) is a 400."},"limit":{"type":"integer","description":"Page size. Max 1000.","default":50},"cursor":{"type":"string","description":"Opaque cursor from the previous response's `next_cursor` field. Continues the walk from after that page. A cursor minted under `search` only resumes the same search."}}}},{"name":"tickerbot_list_universes","description":"Every universe you can reference: your own named ticker lists and the built-in ones. `owner: system` lists the built-in universes (top_10, top_100); `all` lists both. Use a slug as `universe` on scan, signal, and subscribe tools.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"owner":{"type":"string","description":"Which universes to list: `me` (your own), `system` (built-ins), or `all` (both).","enum":["me","system","all"],"default":"me"},"limit":{"type":"integer","description":"Page size (applies to your own). Max 100.","default":50},"cursor":{"type":"string","description":"Opaque cursor from the previous response."}}}},{"name":"tickerbot_list_webhook_deliveries","description":"Recent deliveries for one webhook — what was sent, and what came back.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"id":{"type":"string","description":"Webhook id."},"status":{"type":"string","description":"Filter by delivery status.","enum":["pending","delivered","permanent_failure"]},"from":{"type":"string","description":"Only deliveries created at/after this moment — epoch seconds, epoch milliseconds (13+ digits), or an ISO datetime (`since` is accepted as an alias). Delivery history is retained for 90 days; deleting a webhook deletes its delivery history with it."},"to":{"type":"string","description":"Only deliveries created at/before this moment — same value grammar as `from`. A date-only value means through the end of that UTC day. `from` after `to` is a 400."},"limit":{"type":"integer","description":"Page size. Max 100.","default":50},"cursor":{"type":"string","description":"Opaque cursor."}},"required":["id"]}},{"name":"tickerbot_list_webhooks","description":"Every webhook subscription on this account, newest first.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"status":{"type":"string","description":"Filter by status: `active` or `disabled` — the only two states a webhook has (`disabled` covers both a user pause and the automatic disable after repeated delivery failures; `consecutive_failures`/`last_error` on each record say which). Omit for all.","enum":["active","disabled"]},"limit":{"type":"integer","description":"Page size. Max 100.","default":50},"cursor":{"type":"string","description":"Opaque cursor from the previous response."}}}},{"name":"tickerbot_scan","description":"Every ticker matching a SQL WHERE clause. Right now, or with `asof`, as of any past date. The `q` grammar is a flat SQL WHERE over signal names: AND/OR/NOT, comparisons, numeric and string literals, custom signals by name. No JOIN or subqueries. With `group_by` the result is rollup rows, not tickers. Example: `gap_up AND market_cap < 2000000000 AND NOT earnings_this_week`.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"q":{"type":"string","description":"SQL WHERE expression. Max 4000 chars; semicolons, comments and write keywords are rejected. Your custom signals are valid here — each expands to its SQL at run time."},"asof":{"type":"string","description":"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."},"interval":{"type":"string","description":"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.","enum":["1m","1h","1d","auto"],"default":"auto"},"order":{"type":"string","description":"Signal to sort by. In aggregate mode the default is the count alias `tickers` — or, with a custom `select`, the last item's alias — sorted NULLS LAST with the group keys as tiebreak.","default":"change_1d_pct"},"dir":{"type":"string","description":"Sort direction.","enum":["asc","desc"],"default":"desc"},"limit":{"type":"integer","description":"Page size. Max 100. Aggregate mode does not paginate — it sets `truncated: true` when groups were cut, so sort with `order` to keep the ones you want.","default":50},"cursor":{"type":"string","description":"Opaque cursor from the previous response's `next_cursor`. Row mode only."},"columns":{"type":"string","description":"Extra signals per row, ADDITIVE — the defaults are always present (ticker, name, asset_class, asset_type, price, change_1d_pct, gap_pct, relative_volume, market_cap). `fields` accepted as an alias."},"full":{"type":"boolean","description":"Return every signal instead of the default set. Mutually exclusive with `columns` — passing both is a 400.","default":false},"universe":{"type":"string","description":"Slug of a system universe (`top_10`, `top_100`) or one of your own. Omitted, the scan runs across all ~21,082 tracked tickers."},"asset_class":{"type":"string","description":"One or more asset classes — slug or comma-separated list (`stocks`, `rates`, `crypto`, `fx`). Validated for shape, not against a fixed list, so a well-formed class we don't track simply matches nothing. Echoed in `query`."},"group_by":{"type":"string","description":"AGGREGATE MODE: 1–6 group keys (signals, expressions, or one of your custom signals as a boolean key). Results become rollup rows. Name a key with `AS` to choose its JSON key (`market_cap > 1e11 AS mega`); an un-named expression is named for you rather than returned as `?column?`. Incompatible with `columns`/`full`/`cursor`; works with `asof`."},"select":{"type":"string","description":"Aggregate output items (requires `group_by`). Default: the group keys + `COUNT(*) AS tickers`. Supports count/avg/sum/min/max/stddev/string_agg/bool_and/bool_or plus `FILTER (WHERE …)`, and your custom signals inside expressions. Alias with `AS`; a last item without one is a 400."},"having":{"type":"string","description":"Filter the aggregate rows (requires `group_by`). Custom signals are valid here too."}},"required":["q"]}},{"name":"tickerbot_search_news","description":"SQL query over the news archive. Article rows, or rollups when you group them. Filter to a ticker with the `ticker` param, or in `q` via the auto-unnest alias `tk = 'NVDA'`. `search` is full-text over title and summary. Rollups with group_by/having return `truncated: true` instead of paging.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"ticker":{"type":"string","description":"Articles mentioning this symbol (ANDed with `q`)."},"tickers":{"type":"string","description":"Comma list, up to 50 — articles mentioning ANY of them. Not combinable with `ticker` or `universe`."},"universe":{"type":"string","description":"Universe slug — articles mentioning any member. Not combinable with `ticker`/`tickers`."},"from":{"type":"string","description":"Earliest `time_published` (inclusive) — strict ISO: `YYYY-MM-DD` or `YYYY-MM-DDTHH:MM[:SS]Z`. (`since` accepted as an alias.)"},"to":{"type":"string","description":"Articles strictly before this instant — same strict ISO subset, matching `/v2/events`. (`until` accepted as an alias.)"},"search":{"type":"string","description":"Full-text search over `title` + `summary` — websearch grammar: `apple earnings` (all words), `\"price target\"` (phrase), `chips OR semiconductors`, `-crypto` (negation). Max 200 chars. ANDs with `q` and the scoping params. Language-stemmed English."},"q":{"type":"string","description":"WHERE clause over the news_article table. Max 4000 chars. Required UNLESS `search` or a scoping param (`ticker`/`tickers`/`universe`/`from`/`to`) is present — the simplest call needs no SQL. Queryable columns: `time_published`, `title`, `summary`, `source`, `source_domain`, `category`, `authors`, `topics`, `overall_sentiment_score`, `overall_sentiment_label`, `tickers`, `ticker_data`, `banner_image`, `url`, `id`, `created_at` — plus `tk`, the per-ticker UNNEST alias. Signal/state columns are not joinable here."},"order":{"type":"string","description":"Sort — a bare column name or SELECT alias only (put expressions in `select` and order by their alias). Defaults to `time_published` (article rows) or `volume` (aggregate rows)."},"dir":{"type":"string","description":"Sort direction.","enum":["asc","desc"],"default":"desc"},"limit":{"type":"integer","description":"Page size. Max 1000.","default":50},"cursor":{"type":"string","description":"Opaque pagination cursor from a prior response's `next_cursor`."},"group_by":{"type":"string","description":"AGGREGATE MODE: comma-separated group keys, 1-6 (max 1000 chars). Switches the response to rollup rows. Use `tk` to roll up per ticker without writing the UNNEST. Name a key with `AS` to choose its JSON key; an un-named expression is named for you rather than returned as `?column?`."},"select":{"type":"string","description":"Columns/expressions to return (max 2000 chars). Defaults to article columns (no `group_by`) or `<group_by cols>, COUNT(*) AS volume` (with `group_by`)."},"having":{"type":"string","description":"HAVING clause on the aggregate (max 1000 chars). Requires `group_by`."}}}},{"name":"tickerbot_subscribe_events","description":"Push new events: we POST your endpoint when events of the kinds you chose land in the archives. Webhooks need a paid plan (Free has no webhook slots). `q` filters the ticker STATE; `event_q` filters the EVENT payload in the /v2/events grammar. Latency is the ingest cadence (analyst ≤1h, corporate kinds daily), not sub-minute.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"kinds":{"type":"string","description":"Event kinds to fire on — array or comma list.","enum":["dividend","split","insider","analyst","earnings"]},"tickers":{"type":"string","description":"Scope to specific tickers (max 50). Mutually exclusive with `universe` — and with the singular alias `ticker` (sending both is a 400). Omit both for all tickers."},"ticker":{"type":"string","description":"Single-symbol shorthand for `tickers`."},"universe":{"type":"string","description":"Scope to a universe slug (`top_10`, `top_100`, or one of yours). `universe_id` accepted as an alias."},"q":{"type":"string","description":"Optional row-STATE filter evaluated against the event's ticker at fire time. Same grammar as scan `q`; custom signals are expanded and frozen at creation."},"event_q":{"type":"string","description":"Optional event-CONTENT filter in the `/v2/events` grammar — only `ticker`, `ts`, `kind`, `payload` may appear. Composes with `q`."},"target_url":{"type":"string","description":"HTTPS delivery URL; or use `channel` + `discord_url`/`device_id`. Omit for in-app."},"channel":{"type":"string","description":"Delivery channel. `slack` is reserved and returns `501`.","enum":["webhook","discord","in_app","mobile_push"]},"discord_url":{"type":"string","description":"Discord incoming-webhook URL. Required when `channel` is `discord`. Stored as a posting credential: the create response echoes it back under `channel_config`, but every later read (list, get, deliveries) strips it and sets `channel_config_present: true` instead."},"device_id":{"type":"string","description":"Device to notify, from `POST /v2/devices/register`. Required when `channel` is `mobile_push`; unknown ids are a 404 `device_not_found`."},"name":{"type":"string","description":"Display name. Defaults to `events: <kinds> · <scope>`."}},"required":["kinds"]}},{"name":"tickerbot_subscribe_scan","description":"Push a whole query: we POST your endpoint every time the match set changes. Webhooks need a paid plan (Free has no webhook slots). Use for \"alert me when this happens\" requests.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"q":{"type":"string","description":"WHERE-clause expression using signal names — the same grammar and the same 4000-char cap as `POST /v2/scan`, so anything scannable is subscribable. Custom signals are expanded and frozen in at creation."},"universe":{"type":"string","description":"System or user-owned universe to scope the scan. `universe_id` accepted as an alias. Unknown universes are a 404 `universe_not_found`."},"target_url":{"type":"string","description":"https:// URL to POST when the match set changes. Omit for in-app delivery."},"channel":{"type":"string","description":"Delivery channel. `webhook` (POST to `target_url`), `discord` (embed to `discord_url`), `in_app` (dashboard only), or `mobile_push` (requires a `device_id` from `POST /v2/devices/register`). Inferred when omitted: `webhook` if `target_url` is set, `discord` if `discord_url` is set, else `in_app`. `slack` is reserved and returns `501`.","enum":["webhook","discord","in_app","mobile_push"]},"discord_url":{"type":"string","description":"Discord incoming-webhook URL. Required when `channel` is `discord`. Stored as a posting credential: the create response echoes it back under `channel_config`, but every later read (list, get, deliveries) strips it and sets `channel_config_present: true` instead."},"device_id":{"type":"string","description":"Device to notify, from `POST /v2/devices/register`. Required when `channel` is `mobile_push`; unknown ids are a 404 `device_not_found`."},"cadence":{"type":"string","description":"`realtime` (the default) is evaluated on every data refresh (~1×/min); `hourly` and `nyse_open` throttle to a batch schedule. `1m` is a deprecated alias for `realtime`.","enum":["realtime","hourly","nyse_open"]},"name":{"type":"string","description":"Human-readable label (up to 80 chars). Defaults to `scan: <q>`."},"columns":{"type":"string","description":"Extra signals per fired payload match row, beyond the standard set (`ticker`, `name`, `asset_type`, `price`, `change_1d_pct`, `market_cap`). Each must be a real signal; unknown ones are rejected at creation. `fields` accepted as an alias — and the RESPONSE reports them under `fields`, as an array."},"order":{"type":"string","description":"Signal the payload's match lists are sorted by before the 100-row cap applies, so a truncated list is the deterministic top 100 rather than an arbitrary sample. Must be a real signal (validated at creation).","default":"market_cap"},"dir":{"type":"string","description":"Sort direction for `order`.","enum":["asc","desc"],"default":"desc"}},"required":["q"]}},{"name":"tickerbot_subscribe_signal","description":"Push one signal: we POST your endpoint whenever any ticker starts matching it. Webhooks need a paid plan (Free has no webhook slots). Omit `ticker` to watch the whole universe.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"signal":{"type":"string","description":"Signal name from the schema (case-insensitive)."},"condition":{"type":"string","description":"Required for every non-boolean signal; the shape follows the signal's `type`. Numeric: `\">70\"`, `\"<30\"`, `\">=100\"`. Timestamp: `\"<YYYY-MM-DDTHH:MM:SSZ\"` (or a bare date). Date: `\">=YYYY-MM-DD\"`. String: `\"=ETF\"` or `\"!=ETF\"`. Sending one with a boolean or custom signal returns 400 (it does not apply)."},"ticker":{"type":"string","description":"Restrict to a single ticker. Default: any ticker."},"universe":{"type":"string","description":"Restrict to a system or user-owned universe (e.g. `top_100`). `universe_id` accepted as an alias. Unknown universes are a 404 `universe_not_found`."},"target_url":{"type":"string","description":"https:// URL to POST when fired. Omit for in-app delivery."},"channel":{"type":"string","description":"Delivery channel. `webhook` (POST to `target_url`), `discord` (post an embed to `discord_url`), `in_app` (dashboard only), or `mobile_push` (notify a phone signed in to the Tickerbot mobile app; requires a `device_id` from `POST /v2/devices/register`). Inferred when omitted: `webhook` if `target_url` is set, `discord` if `discord_url` is set, else `in_app`. `slack` is reserved and returns `501`. See the Delivery channels guide.","enum":["webhook","discord","in_app","mobile_push"]},"discord_url":{"type":"string","description":"Discord incoming-webhook URL (`https://discord.com/api/webhooks/…`). Required when `channel` is `discord`. Stored as a posting credential: the create response echoes it back under `channel_config`, but every later read (list, get, deliveries) strips it and sets `channel_config_present: true` instead."},"device_id":{"type":"string","description":"Device to notify, from `POST /v2/devices/register`. Required when `channel` is `mobile_push`; unknown ids are a 404 `device_not_found`."},"cadence":{"type":"string","description":"`realtime` (the default) is evaluated on every data refresh (~1×/min); `hourly` and `nyse_open` throttle to a batch schedule. `1m` is a deprecated alias for `realtime`.","enum":["realtime","hourly","nyse_open"]},"name":{"type":"string","description":"Human-readable label (up to 80 chars). Defaults to the predicate — `at_52w_high` for a boolean, `rsi_14 > 70` for a numeric, prefixed with `<TICKER>: ` when `ticker` scopes it."},"columns":{"type":"string","description":"Comma-separated extra signals to include in each fired payload match row, beyond the standard set (`ticker`, `name`, `asset_type`, `price`, `change_1d_pct`, `market_cap`). Each must be a real signal; an unknown signal is rejected at creation. `fields` accepted as an alias — and note the RESPONSE reports them under `fields`, as an array."},"order":{"type":"string","description":"Signal the fired payload's match lists are sorted by before the 100-row cap is applied, so a truncated list is the deterministic top 100 rather than an arbitrary sample. Must be a real signal (validated at creation).","default":"market_cap"},"dir":{"type":"string","description":"Sort direction for `order`.","enum":["asc","desc"],"default":"desc"}},"required":["signal"]}},{"name":"tickerbot_subscribe_ticker","description":"Push one ticker: we POST your endpoint whenever it matches the condition you give. Webhooks need a paid plan (Free has no webhook slots). Omit `target_url` for in-app delivery.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"ticker":{"type":"string","description":"Case-insensitive. Equities are bare symbols (`AAPL`); every other class carries a prefix — rates (`R:SOFR`), crypto (`X:BTCUSD`), fx (`X:EURUSD`). Bare `BTC`/`ETH` are US-listed ETFs, not spot crypto. See Tickers."},"q":{"type":"string","description":"WHERE-clause fragment using signal names from the schema — the same grammar as /v2/scan. (`condition` accepted as an alias.)"},"condition":{"type":"string","description":"Original name for `q` — accepted as well. The same WHERE-clause fragment; send either spelling."},"target_url":{"type":"string","description":"https:// URL to POST when the condition fires. Omit for in-app delivery (visible in the dashboard)."},"channel":{"type":"string","description":"Delivery channel. `webhook` (POST to `target_url`), `discord` (post an embed to `discord_url`), `in_app` (dashboard only), or `mobile_push` (notify a phone signed in to the Tickerbot mobile app; requires a `device_id` from `POST /v2/devices/register`). Inferred when omitted: `webhook` if `target_url` is set, `discord` if `discord_url` is set, else `in_app`. `slack` is reserved and returns `501`. See the Delivery channels guide.","enum":["webhook","discord","in_app","mobile_push"]},"discord_url":{"type":"string","description":"Discord incoming-webhook URL (`https://discord.com/api/webhooks/…`). Required when `channel` is `discord`. Stored as a posting credential: the create response echoes it back under `channel_config`, but every later read (list, get, deliveries) strips it and sets `channel_config_present: true` instead."},"device_id":{"type":"string","description":"Device to notify, from `POST /v2/devices/register`. Required when `channel` is `mobile_push`; unknown ids are a 404 `device_not_found`."},"cadence":{"type":"string","description":"How often to evaluate. `realtime` (the default) is evaluated on every data refresh (~1×/min); `hourly` and `nyse_open` throttle to a batch schedule. `1m` is a deprecated alias for `realtime`.","enum":["realtime","hourly","nyse_open"]},"name":{"type":"string","description":"Human-readable label (up to 80 chars). Defaults to `<TICKER>: <query>`."},"columns":{"type":"string","description":"Comma-separated extra signals to include in each fired payload match row, beyond the standard set (`ticker`, `name`, `asset_type`, `price`, `change_1d_pct`, `market_cap`). Each must be a real signal; an unknown signal is rejected at creation. `fields` accepted as an alias — and note the RESPONSE reports them under `fields`, as an array."},"order":{"type":"string","description":"Signal the fired payload's match lists are sorted by before the 100-row cap is applied, so a truncated list is the deterministic top 100 rather than an arbitrary sample. Must be a real signal (validated at creation).","default":"market_cap"},"dir":{"type":"string","description":"Sort direction for `order`.","enum":["asc","desc"],"default":"desc"}},"required":["ticker","q"]}},{"name":"tickerbot_test_webhook","description":"Send a real-shape test POST to your endpoint, instantly. One-shot: a failed test never retries and never auto-disables the webhook. 400 when the webhook has no target_url.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"id":{"type":"string","description":"Webhook id."}},"required":["id"]}},{"name":"tickerbot_update_custom_signal","description":"Update a custom signal you own — its expression, description, or name.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"signal":{"type":"string","description":"Custom signal slug (the signal name). A built-in name answers 404 — built-ins are read-only."},"expr":{"type":"string","description":"New SQL expression. Re-validated and re-inlined against your other custom signals. Same strict grammar as create — no `LIKE`/`ILIKE`, `CASE`, `::` casts, or functions beyond `abs`/`coalesce`/`round`/`least`/`greatest`. The response echoes your expression as stored, not its expansion (a pre-2026-09-07 column spelling is stored under its current name and reported in `_meta.deprecated_columns`)."},"description":{"type":"string","description":"New description. Not derived from `expr` — change both if the prose describes a threshold you are moving."},"new_name":{"type":"string","description":"New slug — renames the signal and changes its API handle everywhere (same validation as create). Refused while other custom signals reference the current name. `name` is accepted as an alias (new_name wins when both are sent), but new_name is the unambiguous spelling since the URL already carries the current name."}},"required":["signal"]}},{"name":"tickerbot_update_universe","description":"Update one of your universes: its name, description, or members. `tickers` replaces the whole list; `add`/`remove` adjust it. System universes cannot be edited.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"id":{"type":"string","description":"Universe slug."},"name":{"type":"string","description":"New label. Non-empty, max 80 characters."},"description":{"type":"string","description":"New notes. Max 500 characters."},"tickers":{"type":"array","items":{"type":"string"},"description":"Replace the full ticker list (up to 10,000; validated against the active universe). Does not combine with `add`/`remove` (400)."},"add":{"type":"array","items":{"type":"string"},"description":"Add these tickers (deduplicated)."},"remove":{"type":"array","items":{"type":"string"},"description":"Remove these tickers."}},"required":["id"]}},{"name":"tickerbot_update_webhook","description":"Edit a webhook in place — send only the fields you want to change. The trigger and channel are immutable — delete and re-create to change what fires or where it delivers. Unknown fields are a 400.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"id":{"type":"string","description":"Webhook id."},"name":{"type":"string","description":"New display name. Non-empty, max 80 characters."},"cadence":{"type":"string","description":"Evaluation cadence. A user preference — never gated. Event triggers deliver on ingest — only `realtime` is accepted on them (400 otherwise).","enum":["realtime","hourly","nyse_open"]},"target_url":{"type":"string","description":"New https:// delivery URL (webhook channel only — a Discord/mobile subscription 400s here). `null` or empty switches to in-app delivery; `status` is untouched — a disabled webhook stays disabled until `POST /v2/webhooks/{id}/enable` (the only path that re-checks your account's webhook cap)."},"enabled":{"type":"boolean","description":"`false` disables the webhook (status → `disabled`). `true` is a no-op unless disabled, in which case use `POST /v2/webhooks/{id}/enable` instead."}},"required":["id"]}}],"scan":{"score":90,"grade":"A","scanned_at":"2026-09-27T05:30:30.055Z","report":{"scannerVersion":"0.1.9","scannedAt":"2026-09-27T05:30:30.004Z","components":{"code":{"score":25,"max":25,"notes":["8 source files scanned"]},"reliability":{"score":20,"max":20,"notes":["remote reachable in 348ms"]},"poisoning":{"score":15,"max":15,"notes":["32 tool descriptions checked"]},"auth":{"score":8,"max":15,"notes":["API key sent as a header"]},"maintenance":{"score":15,"max":15,"notes":["last push 9 days ago"]},"identity":{"score":7,"max":10,"notes":["registry namespace matches repository owner"]}},"findings":[],"inputs":{"probes":[{"url":"https://api.tickerbot.io/mcp","reachable":true,"authRequired":false,"latencyMs":348,"serverInfo":{"name":"@tickerbot/mcp-server","version":"0.2.0"}}],"packages":[{"registryType":"npm","identifier":"@tickerbot/mcp-server","version":"0.1.3","found":true,"license":"MIT","hasInstallScripts":false,"dependencyCount":1,"publishedAt":"2026-06-11T15:44:33.500Z","repositoryUrl":"git+https://github.com/tickerbot/tickerbot-mcp.git","weeklyDownloads":80}],"repo":{"found":true,"owner":"tickerbot","repo":"tickerbot-mcp","archived":false,"pushedAt":"2026-09-17T23:56:24Z","stars":0,"forks":0,"openIssues":0,"ownerType":"Organization","ownerAvatarUrl":"https://avatars.githubusercontent.com/u/292812611?v=4","ownerCreatedAt":"2026-06-11T14:13:24Z"},"icon":{"url":"https://tickerbot.io/favicon.svg?v=2","source":"site"},"presence":{"stars":0,"forks":0,"downloadsWeek":80,"license":"MIT","lastPushAt":"2026-09-17T23:56:24.000Z","score":33}}}},"grade_history":[],"reviews":[]}