Skip to main content
Glama

Ticker: Live Event Ticket Market

Create Saved Event Filter (View)

create_view

Creates a View: a saved predicate over events that records a Match each time an event enters it and sends alerts by email or to an attached alert destination, with an optional Digest. A new View starts sending alerts at once unless enabled is false. Use it when the person asks to be alerted about every event that meets market conditions. Test the predicate with screen_events first; to change a View that exists use update_view; for alerts on one event use watch_event.

Tool call template

Replace each placeholder with a value matching the input schema. Only required parameters are included; add optional parameters as needed.

{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "arguments": {
      "name": "<name>",
      "channels": "<channels>"
    },
    "name": "create_view"
  }
}

Input Schema

NameRequiredDescriptionDefault
nameYesThe View's name, up to 120 characters.
tagsNoFree-text organization labels for this View (max 5, deduplicated). Purely for grouping/filtering in the app; tags never change what the View matches or where alerts deliver.
colorNoAccent color the app shows on this View's pill/dot. One of: red, orange, amber, green, teal, blue, violet, pink. Cosmetic only.
digestNoThe View's Digest: each Issue is the View's events with Movers, New and Gone against the previous Issue. Only the fields you pass change. Turning it on past the plan's Digest count is refused with the plan that carries more.
enabledNoDefault true: the View starts recording Matches and sending alerts at once. false saves it paused.
parkingNoParking events: a parking pass that is its own event (sub_category_id 75 or 'Parking' in the name). `hide` (the default) leaves them out; `only` returns parking alone; `include` returns parking beside every other event. Set `only` when the user asks for parking, `include` when they want both; leave it at `hide` otherwise. The View keeps this choice.hide
channelsYesDelivery channels, at least one. "email" mails the Matches to the account's address. The other value routes them to the destinations attached to this View instead, which is how a Slack or Discord channel receives them: add one in the Ticker web app under Settings, then pass its id as delivery_target_id here, or attach it later with update_view's attach_destination. A View may carry both.
predicateNo{} = always-true, combined with max_firings_per_eval to get a "top-N of the universe" alert. Predicate DSL (recursive JSON): a leaf is {col, op, val}; composites are {all:[...]} (AND) or {any:[...]} (OR). Columns are prefixed: "ea." = event_analytics, "e." = events, "pa." = performer_analytics, "p." = performers (p.name, the act the row is about), "v." = venues (v.venue_name, the venue's name; v.city, v.state, v.country_code, v.capacity). Ops: =, !=, >, >=, <, <=, between (val=[lo,hi]), in (val=[...]), contains (case-insensitive substring on a TEXT column; val is a plain string), not_contains (removes the rows whose TEXT column holds val as a case-insensitive substring; a row with no value stays; % and _ in val match themselves, while contains reads them as wildcards). Example: {"all":[{"col":"ea.price_self_z_7d_xs_z_subcat_tte","op":">=","val":2},{"col":"ea.days_to_event","op":"between","val":[7,60]}]}. NAMED ACTS / TEAMS / PERFORMERS, use p.name. When the user names the act the events are BY (a musician like 'Taylor Swift', a team like 'Lakers', a comedian, a touring show), filter the performer column: {col:'p.name', op:'contains', val:'Taylor Swift'}. p.name is the act the row is KEYED on. e.event_name is the TITLE, which reads 'A at B' for a sports event and so also returns the opponent's home games, and for a concert also returns parking and support billings. A performer or team ask NEVER goes to e.event_name. Keep writing 'contains' for an act name. When what was typed IS a whole act name in the catalog, the server rewrites that leaf to op '=' and says so; anything else stays a substring. REMOVING ROWS BY A WORD. 'NOT Little', 'not the Lakers', 'without Taylor', 'excluding Hamilton' remove every row whose title contains the word: {col:'e.event_name', op:'not_contains', val:'Little'}. Never write '!=' for a word: '!=' compares the WHOLE value, so e.event_name != 'Manilow' removes nothing. A MISSPELLED superlative is still a superlative. 'htotest', 'hotest', 'bigest movers', 'chepest', 'lowset price' read as the word they intend and take the ordering that word takes. A ranking word is NEVER a name: never put it in a 'contains' leaf on e.event_name or p.name, and never answer with sort null because the spelling was odd. ECONOMIC FLOOR. The server already ANDs ea.listings_current >= 25 AND ea.median_price_current >= 40 into every screen. Do NOT add a listings-depth floor of your own. A column whose name ends in d1 or 1d is a ONE-DAY CHANGE, not yesterday's value. 'Inventory dropping' or 'listings falling' is a SEVEN-DAY change or an absorption column, never a one-day ratio. A stale row, or an event that is not active, is NEVER a move: its numbers describe the last day it was priced. Views and alerts do not gate freshness: when a View is about movement, add a relative-date leg on ea.last_price_snapshot_date (days_ago 1). The economic floor CAN be turned off at the call site (apply_floor=false, or apply_economic_floor=false on a rule), only when you explicitly want the raw universe. Views and alerts do not gate freshness: a View about movement adds the leg {"col":"ea.last_price_snapshot_date","op":">=","val":{"days_ago":1}}, which means "priced within the last day" and compiles to CURRENT_DATE - 1, re-evaluated every tick, where a literal date string silently rots. The {"days_ago": N} value form is valid on any date/timestamp column. A weekend is {"col":"e.local_date","op":"between","val":{"weekend":"this","tz":"America/New_York"}} ("next" for the one after): the coming Friday to Sunday in tz, from today on a Friday to Sunday, resolved on every run. e.event_id, e.venue_id and e.performer_id take the ids rows carry (ev..., vn..., pf...). Authoritative validation runs in SQL; an invalid predicate returns a clear error.
listing_triggerNoAlert as soon as we see a listing in one section of one event with at least min_quantity tickets at or under max_price on the required price_basis (including or excluding fees). Missing prices never match. Send it without predicate. One alert per listing, and again only at a lower price. Max and Ultra.
notification_modeNoPer-alert EMAIL delivery mode: 'instant' (email each match), 'batched' (the Matches wait and go out together in one email, at the pace notification_cadence sets: `hourly` or `daily`; with `off` it is saved as 'muted'), or 'muted' (Matches recorded, never emailed). 'instant' and 'muted' override notification_cadence. 'digest' is the old name of 'batched', accepted until 2026-11-30 and stored as 'batched'. Alert destinations are unaffected: they always deliver instantly.
delivery_target_idNoOptionally attach an alert destination (an id from list_destinations; new destinations are added in the Ticker web app under Settings) so this alert's Matches are delivered there the instant they fire, in addition to email. Omit for email-only.
apply_economic_floorNoWhether the rule's predicate is AND-ed with the economic floor (listings_current >= 25 AND median_price_current >= 40). Default true.
max_firings_per_evalNoCap on events this rule can match per evaluation tick (top-N by event_id (deterministic cap, not magnitude-prioritized)). Bounds alert volume since percentile predicates can't self-limit. Default 25.
notification_cadenceNoHow often this alert's Matches may leave as EMAIL: `immediate` (each match), `hourly`, `daily` (batched), or `off` (Matches recorded but never emailed; a 'batched' alert with `off` is saved as 'muted'). Only paces EMAIL; an attached alert destination always delivers the instant a Match fires, regardless of this.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed2 schema fields changed
    • changedInput schema / properties / notification_cadence / description
      Previous value: -"How often this alert's Matches may leave as EMAIL: `immediate` (each match), `hourly`, `daily` (batched), or `off` (Matches recorded but never emailed). Only paces EMAIL; an attached alert destination always delivers the instant a Match fires, regardless of this."New value: +"How often this alert's Matches may leave as EMAIL: `immediate` (each match), `hourly`, `daily` (batched), or `off` (Matches recorded but never emailed; a 'batched' alert with `off` is saved as 'muted'). Only paces EMAIL; an attached alert destination always delivers the instant a Match fires, regardless of this."
    • changedInput schema / properties / notification_mode / description
      Previous value: -"Per-alert EMAIL delivery mode: 'instant' (email each match), 'batched' (the Matches wait and go out together in one email, at the pace notification_cadence sets: `hourly` or `daily`), or 'muted' (Matches recorded, never emailed). 'instant' and 'muted' override notification_cadence. 'digest' is the old name of 'batched', accepted until 2026-11-30 and stored as 'batched'. Alert destinations are unaffected: they always deliver instantly."New value: +"Per-alert EMAIL delivery mode: 'instant' (email each match), 'batched' (the Matches wait and go out together in one email, at the pace notification_cadence sets: `hourly` or `daily`; with `off` it is saved as 'muted'), or 'muted' (Matches recorded, never emailed). 'instant' and 'muted' override notification_cadence. 'digest' is the old name of 'batched', accepted until 2026-11-30 and stored as 'batched'. Alert destinations are unaffected: they always deliver instantly."
  2. Changed1 schema field changed
    • addedInput schema / properties / listing_trigger
      Added value: +{
      +  "additionalProperties": false,
      +  "description": "Alert as soon as we see a listing in one section of one event with at least min_quantity tickets at or under max_price on the required price_basis (including or excluding fees). Missing prices never match. Send it without predicate. One alert per listing, and again only at a lower price. Max and Ultra.",
      +  "properties": {
      +    "event_id": {
      +      "description": "The event, by its public id or link.",
      +      "minLength": 1,
      +      "type": "string"
      +    },
      +    "markets": {
      +      "description": "Markets to watch. Omit for VS, SH, SG, GT and TM Resale.",
      +      "items": {
      +        "enum": [
      +          "vs",
      +          "sh",
      +          "sg",
      +          "gt",
      +          "tmr"
      +        ],
      +        "type": "string"
      +      },
      +      "minItems": 1,
      +      "type": "array"
      +    },
      +    "max_price": {
      +      "description": "The highest price per ticket in dollars, on the specified price_basis.",
      +      "maximum": 1000000,
      +      "minimum": 0.01,
      +      "type": "number"
      +    },
      +    "min_quantity": {
      +      "description": "The fewest tickets the listing holds.",
      +      "maximum": 100,
      +      "minimum": 1,
      +      "type": "integer"
      +    },
      +    "price_basis": {
      +      "description": "Required: all_in includes fees; pre_fee excludes fees. Ask the user which basis if unspecified. A listing without that price never matches.",
      +      "enum": [
      +        "all_in",
      +        "pre_fee"
      +      ],
      +      "type": "string"
      +    },
      +    "row": {
      +      "anyOf": [
      +        {
      +          "maxLength": 40,
      +          "minLength": 1,
      +          "type": "string"
      +        },
      +        {
      +          "type": "null"
      +        }
      +      ],
      +      "description": "One row. Omit for any row."
      +    },
      +    "section": {
      +      "description": "The section, as get_event_sections or any market names it.",
      +      "maxLength": 120,
      +      "minLength": 1,
      +      "type": "string"
      +    }
      +  },
      +  "required": [
      +    "event_id",
      +    "section",
      +    "min_quantity",
      +    "max_price",
      +    "price_basis"
      +  ],
      +  "type": "object"
      +}
  3. Changed1 schema field changed
    • addedInput schema / properties / parking
      Added value: +{
      +  "default": "hide",
      +  "description": "Parking events: a parking pass that is its own event (sub_category_id 75 or 'Parking' in the name). `hide` (the default) leaves them out; `only` returns parking alone; `include` returns parking beside every other event. Set `only` when the user asks for parking, `include` when they want both; leave it at `hide` otherwise. The View keeps this choice.",
      +  "enum": [
      +    "hide",
      +    "include",
      +    "only"
      +  ],
      +  "type": "string"
      +}
  4. Changed4 schema fields changed
    • changedInput schema / properties / channels / description
      Previous value: -"Delivery channels, at least one. \"email\" mails the Matches to the account's address. The other value routes them to the destinations attached to this View instead, which is how a Slack or Discord channel receives them: add one in the Ticker web app under Settings, then attach it with set_view_target. A View may carry both."New value: +"Delivery channels, at least one. \"email\" mails the Matches to the account's address. The other value routes them to the destinations attached to this View instead, which is how a Slack or Discord channel receives them: add one in the Ticker web app under Settings, then pass its id as delivery_target_id here, or attach it later with update_view's attach_destination. A View may carry both."
    • addedInput schema / properties / enabled / description
      Added value: +"Default true: the View starts recording Matches and sending alerts at once. false saves it paused."
    • addedInput schema / properties / name / description
      Added value: +"The View's name, up to 120 characters."
    • changedInput schema / properties / predicate / description
      Previous value: -"{} = always-true, combined with max_firings_per_eval to get a \"top-N of the universe\" alert. Predicate DSL (recursive JSON): a leaf is {col, op, val}; composites are {all:[...]} (AND) or {any:[...]} (OR). Columns are prefixed: \"ea.\" = event_analytics, \"e.\" = events, \"pa.\" = performer_analytics, \"p.\" = performers (p.name, the act the row is about), \"v.\" = venues (v.venue_name, the venue's name; v.city, v.state, v.country_code, v.capacity). Ops: =, !=, >, >=, <, <=, between (val=[lo,hi]), in (val=[...]), contains (case-insensitive substring on a TEXT column; val is a plain string), not_contains (removes the rows whose TEXT column holds val as a case-insensitive substring; a row with no value stays; % and _ in val match themselves, while contains reads them as wildcards). Example: {\"all\":[{\"col\":\"ea.price_self_z_7d_xs_z_subcat_tte\",\"op\":\">=\",\"val\":2},{\"col\":\"ea.days_to_event\",\"op\":\"between\",\"val\":[7,60]}]}. NAMED ACTS / TEAMS / PERFORMERS, use p.name. When the user names the act the events are BY (a musician like 'Taylor Swift', a team like 'Lakers', a comedian, a touring show), filter the performer column: {col:'p.name', op:'contains', val:'Taylor Swift'}. p.name is the act the row is KEYED on. e.event_name is the TITLE, which reads 'A at B' for a sports event and so also returns the opponent's home games, and for a concert also returns parking and support billings. A performer or team ask NEVER goes to e.event_name. Keep writing 'contains' for an act name. When what was typed IS a whole act name in the catalog, the server rewrites that leaf to op '=' and says so; anything else stays a substring. REMOVING ROWS BY A WORD. 'NOT Little', 'not the Lakers', 'without Taylor', 'excluding Hamilton' remove every row whose title contains the word: {col:'e.event_name', op:'not_contains', val:'Little'}. Never write '!=' for a word: '!=' compares the WHOLE value, so e.event_name != 'Manilow' removes nothing. A MISSPELLED superlative is still a superlative. 'htotest', 'hotest', 'bigest movers', 'chepest', 'lowset price' read as the word they intend and take the ordering that word takes. A ranking word is NEVER a name: never put it in a 'contains' leaf on e.event_name or p.name, and never answer with sort null because the spelling was odd. ECONOMIC FLOOR. The server already ANDs ea.listings_current >= 25 AND ea.median_price_current >= 40 into every screen. Do NOT add a listings-depth floor of your own. A column whose name ends in d1 or 1d is a ONE-DAY CHANGE, not yesterday's value. 'Inventory dropping' or 'listings falling' is a SEVEN-DAY change or an absorption column, never a one-day ratio. A stale row, or an event that is not active, is NEVER a move: its numbers describe the last day it was priced. Views and alerts do not gate freshness: when a View is about movement, add a relative-date leg on ea.last_price_snapshot_date (days_ago 1). The economic floor CAN be turned off at the call site (apply_floor=false, or apply_economic_floor=false on a rule), only when you explicitly want the raw universe. Views and alerts do not gate freshness: a View about movement adds the leg {\"col\":\"ea.last_price_snapshot_date\",\"op\":\">=\",\"val\":{\"days_ago\":1}} means \"priced within the last day\" and compiles to CURRENT_DATE - 1, re-evaluated every tick, where a literal date string silently rots. The {\"days_ago\": N} value form is valid on any date/timestamp column. A weekend is {\"col\":\"e.local_date\",\"op\":\"between\",\"val\":{\"weekend\":\"this\",\"tz\":\"America/New_York\"}} (\"next\" for the one after): the coming Friday to Sunday in tz, from today on a Friday to Sunday, resolved on every run. e.event_id, e.venue_id and e.performer_id take the ids rows carry (ev..., vn..., pf...). Authoritative validation runs in SQL; an invalid predicate returns a clear error."New value: +"{} = always-true, combined with max_firings_per_eval to get a \"top-N of the universe\" alert. Predicate DSL (recursive JSON): a leaf is {col, op, val}; composites are {all:[...]} (AND) or {any:[...]} (OR). Columns are prefixed: \"ea.\" = event_analytics, \"e.\" = events, \"pa.\" = performer_analytics, \"p.\" = performers (p.name, the act the row is about), \"v.\" = venues (v.venue_name, the venue's name; v.city, v.state, v.country_code, v.capacity). Ops: =, !=, >, >=, <, <=, between (val=[lo,hi]), in (val=[...]), contains (case-insensitive substring on a TEXT column; val is a plain string), not_contains (removes the rows whose TEXT column holds val as a case-insensitive substring; a row with no value stays; % and _ in val match themselves, while contains reads them as wildcards). Example: {\"all\":[{\"col\":\"ea.price_self_z_7d_xs_z_subcat_tte\",\"op\":\">=\",\"val\":2},{\"col\":\"ea.days_to_event\",\"op\":\"between\",\"val\":[7,60]}]}. NAMED ACTS / TEAMS / PERFORMERS, use p.name. When the user names the act the events are BY (a musician like 'Taylor Swift', a team like 'Lakers', a comedian, a touring show), filter the performer column: {col:'p.name', op:'contains', val:'Taylor Swift'}. p.name is the act the row is KEYED on. e.event_name is the TITLE, which reads 'A at B' for a sports event and so also returns the opponent's home games, and for a concert also returns parking and support billings. A performer or team ask NEVER goes to e.event_name. Keep writing 'contains' for an act name. When what was typed IS a whole act name in the catalog, the server rewrites that leaf to op '=' and says so; anything else stays a substring. REMOVING ROWS BY A WORD. 'NOT Little', 'not the Lakers', 'without Taylor', 'excluding Hamilton' remove every row whose title contains the word: {col:'e.event_name', op:'not_contains', val:'Little'}. Never write '!=' for a word: '!=' compares the WHOLE value, so e.event_name != 'Manilow' removes nothing. A MISSPELLED superlative is still a superlative. 'htotest', 'hotest', 'bigest movers', 'chepest', 'lowset price' read as the word they intend and take the ordering that word takes. A ranking word is NEVER a name: never put it in a 'contains' leaf on e.event_name or p.name, and never answer with sort null because the spelling was odd. ECONOMIC FLOOR. The server already ANDs ea.listings_current >= 25 AND ea.median_price_current >= 40 into every screen. Do NOT add a listings-depth floor of your own. A column whose name ends in d1 or 1d is a ONE-DAY CHANGE, not yesterday's value. 'Inventory dropping' or 'listings falling' is a SEVEN-DAY change or an absorption column, never a one-day ratio. A stale row, or an event that is not active, is NEVER a move: its numbers describe the last day it was priced. Views and alerts do not gate freshness: when a View is about movement, add a relative-date leg on ea.last_price_snapshot_date (days_ago 1). The economic floor CAN be turned off at the call site (apply_floor=false, or apply_economic_floor=false on a rule), only when you explicitly want the raw universe. Views and alerts do not gate freshness: a View about movement adds the leg {\"col\":\"ea.last_price_snapshot_date\",\"op\":\">=\",\"val\":{\"days_ago\":1}}, which means \"priced within the last day\" and compiles to CURRENT_DATE - 1, re-evaluated every tick, where a literal date string silently rots. The {\"days_ago\": N} value form is valid on any date/timestamp column. A weekend is {\"col\":\"e.local_date\",\"op\":\"between\",\"val\":{\"weekend\":\"this\",\"tz\":\"America/New_York\"}} (\"next\" for the one after): the coming Friday to Sunday in tz, from today on a Friday to Sunday, resolved on every run. e.event_id, e.venue_id and e.performer_id take the ids rows carry (ev..., vn..., pf...). Authoritative validation runs in SQL; an invalid predicate returns a clear error."
  5. Changed2 schema fields changed
    • changedInput schema / properties / digest / properties / triggers / properties / absorption / description
      Previous value: -"Tickets leaving fast: A large share of the listed tickets leaves the marketplace. Off by default. Measures listings that disappear, which is not the same as confirmed sales. params: threshold 0.3 to 0.9 (default 0.5)."New value: +"Tickets leaving fast: A large share of the listed tickets leaves the marketplace. Off by default. Counts the tickets that leave the marketplace, bought, withdrawn or expired. params: threshold 0.3 to 0.9 (default 0.5)."
    • changedInput schema / properties / digest / properties / triggers / properties / demand / description
      Previous value: -"Demand spike: Fires when Interest in this event, the most people viewing in an hour, rises by this many people versus the day before. Off by default. A change in Interest from one date to the next, not a z-score. Coverage is still filling in, so quiet events may never trigger it. params: threshold 1 to 50 (default 5)."New value: +"Demand spike: Fires when Interest in this event, the most people viewing in an hour, rises by this many people versus the day before. Off by default. A change in Interest from one date to the next, not a z-score. params: threshold 1 to 50 (default 5)."
  6. Changed1 schema field changed
    • changedInput schema / properties / digest / properties / triggers / properties / demand / description
      Previous value: -"Demand spike: Fires when Interest in this event, people viewing per hour, rises by this much versus the day before. Off by default. A change in Interest per hour from one date to the next, not a z-score. Coverage is still filling in, so quiet events may never trigger it. params: threshold 0.1 to 5 (default 0.2)."New value: +"Demand spike: Fires when Interest in this event, the most people viewing in an hour, rises by this many people versus the day before. Off by default. A change in Interest from one date to the next, not a z-score. Coverage is still filling in, so quiet events may never trigger it. params: threshold 1 to 50 (default 5)."
  7. Changed1 schema field changed
    • changedInput schema / properties / predicate / description
      Previous value: -"{} = always-true, combined with max_firings_per_eval to get a \"top-N of the universe\" alert. Predicate DSL (recursive JSON): a leaf is {col, op, val}; composites are {all:[...]} (AND) or {any:[...]} (OR). Columns are prefixed: \"ea.\" = event_analytics, \"e.\" = events, \"pa.\" = performer_analytics, \"p.\" = performers (p.name, the act the row is about), \"v.\" = venues (v.venue_name, the venue's name; v.city, v.state, v.country_code, v.capacity). Ops: =, !=, >, >=, <, <=, between (val=[lo,hi]), in (val=[...]), contains (case-insensitive substring on a TEXT column; val is a plain string), not_contains (removes the rows whose TEXT column holds val as a case-insensitive substring; a row with no value stays; % and _ in val match themselves, while contains reads them as wildcards). Example: {\"all\":[{\"col\":\"ea.price_self_z_7d_xs_z_subcat_tte\",\"op\":\">=\",\"val\":2},{\"col\":\"ea.days_to_event\",\"op\":\"between\",\"val\":[7,60]}]}. NAMED ACTS / TEAMS / PERFORMERS, use p.name. When the user names the act the events are BY (a musician like 'Taylor Swift', a team like 'Lakers', a comedian, a touring show), filter the performer column: {col:'p.name', op:'contains', val:'Taylor Swift'}. p.name is the act the row is KEYED on. e.event_name is the TITLE, which reads 'A at B' for a sports event and so also returns the opponent's home games, and for a concert also returns parking and support billings. A performer or team ask NEVER goes to e.event_name. Keep writing 'contains' for an act name. When what was typed IS a whole act name in the catalog, the server rewrites that leaf to op '=' and says so; anything else stays a substring. REMOVING ROWS BY A WORD. 'NOT Little', 'not the Lakers', 'without Taylor', 'excluding Hamilton' remove every row whose title contains the word: {col:'e.event_name', op:'not_contains', val:'Little'}. Never write '!=' for a word: '!=' compares the WHOLE value, so e.event_name != 'Manilow' removes nothing. A MISSPELLED superlative is still a superlative. 'htotest', 'hotest', 'bigest movers', 'chepest', 'lowset price' read as the word they intend and take the ordering that word takes. A ranking word is NEVER a name: never put it in a 'contains' leaf on e.event_name or p.name, and never answer with sort null because the spelling was odd. ECONOMIC FLOOR. The server already ANDs ea.listings_current >= 25 AND ea.median_price_current >= 40 into every screen. Do NOT add a listings-depth floor of your own. A column whose name ends in d1 or 1d is a ONE-DAY CHANGE, not yesterday's value. 'Inventory dropping' or 'listings falling' is a SEVEN-DAY change or an absorption column, never a one-day ratio. A stale row, or an event that is not active, is NEVER a move: its numbers describe the last day it was priced. Gate a movement ask on ea.last_price_snapshot_date with a relative date. The economic floor CAN be turned off at the call site (apply_floor=false, or apply_economic_floor=false on a rule), only when you explicitly want the raw universe. A freshness gate is NOT applied by default, so supply it yourself: {\"col\":\"ea.last_price_snapshot_date\",\"op\":\">=\",\"val\":{\"days_ago\":1}} means \"priced within the last day\" and compiles to CURRENT_DATE - 1, re-evaluated every tick, where a literal date string silently rots. The {\"days_ago\": N} value form is valid on any date/timestamp column. A weekend is {\"col\":\"e.local_date\",\"op\":\"between\",\"val\":{\"weekend\":\"this\",\"tz\":\"America/New_York\"}} (\"next\" for the one after): the coming Friday to Sunday in tz, from today on a Friday to Sunday, resolved on every run. e.event_id, e.venue_id and e.performer_id take the ids rows carry (ev..., vn..., pf...). Authoritative validation runs in SQL; an invalid predicate returns a clear error."New value: +"{} = always-true, combined with max_firings_per_eval to get a \"top-N of the universe\" alert. Predicate DSL (recursive JSON): a leaf is {col, op, val}; composites are {all:[...]} (AND) or {any:[...]} (OR). Columns are prefixed: \"ea.\" = event_analytics, \"e.\" = events, \"pa.\" = performer_analytics, \"p.\" = performers (p.name, the act the row is about), \"v.\" = venues (v.venue_name, the venue's name; v.city, v.state, v.country_code, v.capacity). Ops: =, !=, >, >=, <, <=, between (val=[lo,hi]), in (val=[...]), contains (case-insensitive substring on a TEXT column; val is a plain string), not_contains (removes the rows whose TEXT column holds val as a case-insensitive substring; a row with no value stays; % and _ in val match themselves, while contains reads them as wildcards). Example: {\"all\":[{\"col\":\"ea.price_self_z_7d_xs_z_subcat_tte\",\"op\":\">=\",\"val\":2},{\"col\":\"ea.days_to_event\",\"op\":\"between\",\"val\":[7,60]}]}. NAMED ACTS / TEAMS / PERFORMERS, use p.name. When the user names the act the events are BY (a musician like 'Taylor Swift', a team like 'Lakers', a comedian, a touring show), filter the performer column: {col:'p.name', op:'contains', val:'Taylor Swift'}. p.name is the act the row is KEYED on. e.event_name is the TITLE, which reads 'A at B' for a sports event and so also returns the opponent's home games, and for a concert also returns parking and support billings. A performer or team ask NEVER goes to e.event_name. Keep writing 'contains' for an act name. When what was typed IS a whole act name in the catalog, the server rewrites that leaf to op '=' and says so; anything else stays a substring. REMOVING ROWS BY A WORD. 'NOT Little', 'not the Lakers', 'without Taylor', 'excluding Hamilton' remove every row whose title contains the word: {col:'e.event_name', op:'not_contains', val:'Little'}. Never write '!=' for a word: '!=' compares the WHOLE value, so e.event_name != 'Manilow' removes nothing. A MISSPELLED superlative is still a superlative. 'htotest', 'hotest', 'bigest movers', 'chepest', 'lowset price' read as the word they intend and take the ordering that word takes. A ranking word is NEVER a name: never put it in a 'contains' leaf on e.event_name or p.name, and never answer with sort null because the spelling was odd. ECONOMIC FLOOR. The server already ANDs ea.listings_current >= 25 AND ea.median_price_current >= 40 into every screen. Do NOT add a listings-depth floor of your own. A column whose name ends in d1 or 1d is a ONE-DAY CHANGE, not yesterday's value. 'Inventory dropping' or 'listings falling' is a SEVEN-DAY change or an absorption column, never a one-day ratio. A stale row, or an event that is not active, is NEVER a move: its numbers describe the last day it was priced. Views and alerts do not gate freshness: when a View is about movement, add a relative-date leg on ea.last_price_snapshot_date (days_ago 1). The economic floor CAN be turned off at the call site (apply_floor=false, or apply_economic_floor=false on a rule), only when you explicitly want the raw universe. Views and alerts do not gate freshness: a View about movement adds the leg {\"col\":\"ea.last_price_snapshot_date\",\"op\":\">=\",\"val\":{\"days_ago\":1}} means \"priced within the last day\" and compiles to CURRENT_DATE - 1, re-evaluated every tick, where a literal date string silently rots. The {\"days_ago\": N} value form is valid on any date/timestamp column. A weekend is {\"col\":\"e.local_date\",\"op\":\"between\",\"val\":{\"weekend\":\"this\",\"tz\":\"America/New_York\"}} (\"next\" for the one after): the coming Friday to Sunday in tz, from today on a Friday to Sunday, resolved on every run. e.event_id, e.venue_id and e.performer_id take the ids rows carry (ev..., vn..., pf...). Authoritative validation runs in SQL; an invalid predicate returns a clear error."
  8. Changed1 schema field changed
    • changedInput schema / properties / digest / properties / triggers / properties / demand / description
      Previous value: -"Demand spike: Interest in this event's listings rises versus the day before. Off by default. A change in Interest from one date to the next, not a z-score. Coverage is still filling in, so quiet events may never trigger it. params: threshold 1 to 50 (default 5)."New value: +"Demand spike: Fires when Interest in this event, people viewing per hour, rises by this much versus the day before. Off by default. A change in Interest per hour from one date to the next, not a z-score. Coverage is still filling in, so quiet events may never trigger it. params: threshold 0.1 to 5 (default 0.2)."
  9. Changed3 schema fields changed
    • changedInput schema / properties / digest / properties / sections / description
      Previous value: -"Sections each Issue shows: movers, new, gone (default all three). The rest of the table always shows."New value: +"Sections each Issue shows: movers, new, gone (default all three). The rest of the table always shows. When the Digest reads triggers (its triggers are not null), only new and gone are kept here and movers turns the pct_move trigger on."
    • removedInput schema / properties / digest / properties / sections / minItems
      Removed value: -1
    • addedInput schema / properties / digest / properties / triggers
      Added value: +{
      +  "additionalProperties": false,
      +  "description": "The Digest's trigger sections: each trigger on is a section of the events where it fired since the previous Issue, the same conditions a starred event alerts on, e.g. {\"weather\":{\"enabled\":true},\"pct_move\":{\"enabled\":true,\"params\":{\"threshold\":20}}}. Only the triggers you pass change. Refused while the Digest's triggers read null (trigger sections not available).",
      +  "properties": {
      +    "absorption": {
      +      "additionalProperties": false,
      +      "description": "Tickets leaving fast: A large share of the listed tickets leaves the marketplace. Off by default. Measures listings that disappear, which is not the same as confirmed sales. params: threshold 0.3 to 0.9 (default 0.5).",
      +      "properties": {
      +        "enabled": {
      +          "type": "boolean"
      +        },
      +        "params": {
      +          "additionalProperties": {
      +            "type": [
      +              "number",
      +              "string"
      +            ]
      +          },
      +          "type": "object"
      +        }
      +      },
      +      "type": "object"
      +    },
      +    "demand": {
      +      "additionalProperties": false,
      +      "description": "Demand spike: Interest in this event's listings rises versus the day before. Off by default. A change in Interest from one date to the next, not a z-score. Coverage is still filling in, so quiet events may never trigger it. params: threshold 1 to 50 (default 5).",
      +      "properties": {
      +        "enabled": {
      +          "type": "boolean"
      +        },
      +        "params": {
      +          "additionalProperties": {
      +            "type": [
      +              "number",
      +              "string"
      +            ]
      +          },
      +          "type": "object"
      +        }
      +      },
      +      "type": "object"
      +    },
      +    "lifecycle": {
      +      "additionalProperties": false,
      +      "description": "On-sale, presale, event week, low inventory: Fires when an on-sale or presale is announced, event week begins, or listed inventory runs low. On by default. params: event_week_days 1 to 30  days out (default 7); low_inventory_percent 1 to 50 % (default 10).",
      +      "properties": {
      +        "enabled": {
      +          "type": "boolean"
      +        },
      +        "params": {
      +          "additionalProperties": {
      +            "type": [
      +              "number",
      +              "string"
      +            ]
      +          },
      +          "type": "object"
      +        }
      +      },
      +      "type": "object"
      +    },
      +    "pct_move": {
      +      "additionalProperties": false,
      +      "description": "Price % move: The price changes by more than this percentage. Off by default. params: metric lowest | median (default lowest); window 1d | 7d (default 1d); threshold 5 to 100 % (default 15).",
      +      "properties": {
      +        "enabled": {
      +          "type": "boolean"
      +        },
      +        "params": {
      +          "additionalProperties": {
      +            "type": [
      +              "number",
      +              "string"
      +            ]
      +          },
      +          "type": "object"
      +        }
      +      },
      +      "type": "object"
      +    },
      +    "price_z": {
      +      "additionalProperties": false,
      +      "description": "Unusual price move: Fires when the 7-day price move is at least 2× this event's own normal swing, in either direction. On by default. params: threshold 1 to 5 (default 2).",
      +      "properties": {
      +        "enabled": {
      +          "type": "boolean"
      +        },
      +        "params": {
      +          "additionalProperties": {
      +            "type": [
      +              "number",
      +              "string"
      +            ]
      +          },
      +          "type": "object"
      +        }
      +      },
      +      "type": "object"
      +    },
      +    "weather": {
      +      "additionalProperties": false,
      +      "description": "Weather: Fires when the forecast for the event turns bad, and again when it clears. On by default.",
      +      "properties": {
      +        "enabled": {
      +          "type": "boolean"
      +        },
      +        "params": {
      +          "additionalProperties": {
      +            "type": [
      +              "number",
      +              "string"
      +            ]
      +          },
      +          "type": "object"
      +        }
      +      },
      +      "type": "object"
      +    }
      +  },
      +  "type": "object"
      +}
  10. Changed1 schema field changed
    • changedInput schema / properties / predicate / description
      Previous value: -"{} = always-true, combined with max_firings_per_eval to get a \"top-N of the universe\" alert. Predicate DSL (recursive JSON): a leaf is {col, op, val}; composites are {all:[...]} (AND) or {any:[...]} (OR). Columns are prefixed: \"ea.\" = event_analytics, \"e.\" = events, \"pa.\" = performer_analytics, \"p.\" = performers (p.name, the act the row is about), \"v.\" = venues (v.venue_name, the venue's name; v.city, v.state, v.country_code, v.capacity). Ops: =, !=, >, >=, <, <=, between (val=[lo,hi]), in (val=[...]), contains (case-insensitive substring on a TEXT column; val is a plain string), not_contains (removes the rows whose TEXT column holds val as a case-insensitive substring; a row with no value stays; % and _ in val match themselves, while contains reads them as wildcards). Example: {\"all\":[{\"col\":\"ea.price_self_z_7d_xs_z_subcat_tte\",\"op\":\">=\",\"val\":2},{\"col\":\"ea.days_to_event\",\"op\":\"between\",\"val\":[7,60]}]}. NAMED ACTS / TEAMS / PERFORMERS, use p.name. When the user names the act the events are BY (a musician like 'Taylor Swift', a team like 'Lakers', a comedian, a touring show), filter the performer column: {col:'p.name', op:'contains', val:'Taylor Swift'}. p.name is the act the row is KEYED on. e.event_name is the TITLE, which reads 'A at B' for a sports event and so also returns the opponent's home games, and for a concert also returns parking and support billings. A performer or team ask NEVER goes to e.event_name. Keep writing 'contains' for an act name. When what was typed IS a whole act name in the catalog, the server rewrites that leaf to op '=' and says so; anything else stays a substring. REMOVING ROWS BY A WORD. 'NOT Little', 'not the Lakers', 'without Taylor', 'excluding Hamilton' remove every row whose title contains the word: {col:'e.event_name', op:'not_contains', val:'Little'}. Never write '!=' for a word: '!=' compares the WHOLE value, so e.event_name != 'Manilow' removes nothing. A MISSPELLED superlative is still a superlative. 'htotest', 'hotest', 'bigest movers', 'chepest', 'lowset price' read as the word they intend and take the ordering that word takes. A ranking word is NEVER a name: never put it in a 'contains' leaf on e.event_name or p.name, and never answer with sort null because the spelling was odd. ECONOMIC FLOOR. The server already ANDs ea.listings_current >= 25 AND ea.median_price_current >= 40 into every screen. Do NOT add a listings-depth floor of your own. A column whose name ends in d1 or 1d is a ONE-DAY CHANGE, not yesterday's value. 'Inventory dropping' or 'listings falling' is a SEVEN-DAY change or an absorption column, never a one-day ratio. A stale row, or an event that is not active, is NEVER a move: its numbers describe the last day it was priced. Gate a movement ask on ea.last_price_snapshot_date with a relative date. The economic floor CAN be turned off at the call site (apply_floor=false, or apply_economic_floor=false on a rule), only when you explicitly want the raw universe. A freshness gate is NOT applied by default, so supply it yourself: {\"col\":\"ea.last_price_snapshot_date\",\"op\":\">=\",\"val\":{\"days_ago\":1}} means \"priced within the last day\" and compiles to CURRENT_DATE - 1, re-evaluated every tick, where a literal date string silently rots. The {\"days_ago\": N} value form is valid on any date/timestamp column. e.event_id, e.venue_id and e.performer_id take the ids rows carry (ev..., vn..., pf...). Authoritative validation runs in SQL; an invalid predicate returns a clear error."New value: +"{} = always-true, combined with max_firings_per_eval to get a \"top-N of the universe\" alert. Predicate DSL (recursive JSON): a leaf is {col, op, val}; composites are {all:[...]} (AND) or {any:[...]} (OR). Columns are prefixed: \"ea.\" = event_analytics, \"e.\" = events, \"pa.\" = performer_analytics, \"p.\" = performers (p.name, the act the row is about), \"v.\" = venues (v.venue_name, the venue's name; v.city, v.state, v.country_code, v.capacity). Ops: =, !=, >, >=, <, <=, between (val=[lo,hi]), in (val=[...]), contains (case-insensitive substring on a TEXT column; val is a plain string), not_contains (removes the rows whose TEXT column holds val as a case-insensitive substring; a row with no value stays; % and _ in val match themselves, while contains reads them as wildcards). Example: {\"all\":[{\"col\":\"ea.price_self_z_7d_xs_z_subcat_tte\",\"op\":\">=\",\"val\":2},{\"col\":\"ea.days_to_event\",\"op\":\"between\",\"val\":[7,60]}]}. NAMED ACTS / TEAMS / PERFORMERS, use p.name. When the user names the act the events are BY (a musician like 'Taylor Swift', a team like 'Lakers', a comedian, a touring show), filter the performer column: {col:'p.name', op:'contains', val:'Taylor Swift'}. p.name is the act the row is KEYED on. e.event_name is the TITLE, which reads 'A at B' for a sports event and so also returns the opponent's home games, and for a concert also returns parking and support billings. A performer or team ask NEVER goes to e.event_name. Keep writing 'contains' for an act name. When what was typed IS a whole act name in the catalog, the server rewrites that leaf to op '=' and says so; anything else stays a substring. REMOVING ROWS BY A WORD. 'NOT Little', 'not the Lakers', 'without Taylor', 'excluding Hamilton' remove every row whose title contains the word: {col:'e.event_name', op:'not_contains', val:'Little'}. Never write '!=' for a word: '!=' compares the WHOLE value, so e.event_name != 'Manilow' removes nothing. A MISSPELLED superlative is still a superlative. 'htotest', 'hotest', 'bigest movers', 'chepest', 'lowset price' read as the word they intend and take the ordering that word takes. A ranking word is NEVER a name: never put it in a 'contains' leaf on e.event_name or p.name, and never answer with sort null because the spelling was odd. ECONOMIC FLOOR. The server already ANDs ea.listings_current >= 25 AND ea.median_price_current >= 40 into every screen. Do NOT add a listings-depth floor of your own. A column whose name ends in d1 or 1d is a ONE-DAY CHANGE, not yesterday's value. 'Inventory dropping' or 'listings falling' is a SEVEN-DAY change or an absorption column, never a one-day ratio. A stale row, or an event that is not active, is NEVER a move: its numbers describe the last day it was priced. Gate a movement ask on ea.last_price_snapshot_date with a relative date. The economic floor CAN be turned off at the call site (apply_floor=false, or apply_economic_floor=false on a rule), only when you explicitly want the raw universe. A freshness gate is NOT applied by default, so supply it yourself: {\"col\":\"ea.last_price_snapshot_date\",\"op\":\">=\",\"val\":{\"days_ago\":1}} means \"priced within the last day\" and compiles to CURRENT_DATE - 1, re-evaluated every tick, where a literal date string silently rots. The {\"days_ago\": N} value form is valid on any date/timestamp column. A weekend is {\"col\":\"e.local_date\",\"op\":\"between\",\"val\":{\"weekend\":\"this\",\"tz\":\"America/New_York\"}} (\"next\" for the one after): the coming Friday to Sunday in tz, from today on a Friday to Sunday, resolved on every run. e.event_id, e.venue_id and e.performer_id take the ids rows carry (ev..., vn..., pf...). Authoritative validation runs in SQL; an invalid predicate returns a clear error."
  11. Changed1 schema field changed
    • changedInput schema / properties / predicate / description
      Previous value: -"Predicate DSL object (see tool description). {} = always-true, combined with max_firings_per_eval to get a \"top-N of the universe\" alert."New value: +"{} = always-true, combined with max_firings_per_eval to get a \"top-N of the universe\" alert. Predicate DSL (recursive JSON): a leaf is {col, op, val}; composites are {all:[...]} (AND) or {any:[...]} (OR). Columns are prefixed: \"ea.\" = event_analytics, \"e.\" = events, \"pa.\" = performer_analytics, \"p.\" = performers (p.name, the act the row is about), \"v.\" = venues (v.venue_name, the venue's name; v.city, v.state, v.country_code, v.capacity). Ops: =, !=, >, >=, <, <=, between (val=[lo,hi]), in (val=[...]), contains (case-insensitive substring on a TEXT column; val is a plain string), not_contains (removes the rows whose TEXT column holds val as a case-insensitive substring; a row with no value stays; % and _ in val match themselves, while contains reads them as wildcards). Example: {\"all\":[{\"col\":\"ea.price_self_z_7d_xs_z_subcat_tte\",\"op\":\">=\",\"val\":2},{\"col\":\"ea.days_to_event\",\"op\":\"between\",\"val\":[7,60]}]}. NAMED ACTS / TEAMS / PERFORMERS, use p.name. When the user names the act the events are BY (a musician like 'Taylor Swift', a team like 'Lakers', a comedian, a touring show), filter the performer column: {col:'p.name', op:'contains', val:'Taylor Swift'}. p.name is the act the row is KEYED on. e.event_name is the TITLE, which reads 'A at B' for a sports event and so also returns the opponent's home games, and for a concert also returns parking and support billings. A performer or team ask NEVER goes to e.event_name. Keep writing 'contains' for an act name. When what was typed IS a whole act name in the catalog, the server rewrites that leaf to op '=' and says so; anything else stays a substring. REMOVING ROWS BY A WORD. 'NOT Little', 'not the Lakers', 'without Taylor', 'excluding Hamilton' remove every row whose title contains the word: {col:'e.event_name', op:'not_contains', val:'Little'}. Never write '!=' for a word: '!=' compares the WHOLE value, so e.event_name != 'Manilow' removes nothing. A MISSPELLED superlative is still a superlative. 'htotest', 'hotest', 'bigest movers', 'chepest', 'lowset price' read as the word they intend and take the ordering that word takes. A ranking word is NEVER a name: never put it in a 'contains' leaf on e.event_name or p.name, and never answer with sort null because the spelling was odd. ECONOMIC FLOOR. The server already ANDs ea.listings_current >= 25 AND ea.median_price_current >= 40 into every screen. Do NOT add a listings-depth floor of your own. A column whose name ends in d1 or 1d is a ONE-DAY CHANGE, not yesterday's value. 'Inventory dropping' or 'listings falling' is a SEVEN-DAY change or an absorption column, never a one-day ratio. A stale row, or an event that is not active, is NEVER a move: its numbers describe the last day it was priced. Gate a movement ask on ea.last_price_snapshot_date with a relative date. The economic floor CAN be turned off at the call site (apply_floor=false, or apply_economic_floor=false on a rule), only when you explicitly want the raw universe. A freshness gate is NOT applied by default, so supply it yourself: {\"col\":\"ea.last_price_snapshot_date\",\"op\":\">=\",\"val\":{\"days_ago\":1}} means \"priced within the last day\" and compiles to CURRENT_DATE - 1, re-evaluated every tick, where a literal date string silently rots. The {\"days_ago\": N} value form is valid on any date/timestamp column. e.event_id, e.venue_id and e.performer_id take the ids rows carry (ev..., vn..., pf...). Authoritative validation runs in SQL; an invalid predicate returns a clear error."
  12. Changed2 schema fields changed
    • changedInput schema / properties / channels / description
      Previous value: -"Delivery channels, at least one. \"email\" mails the Matches to the account's address. The other value routes them to the destinations attached to this View instead, which is how a Slack or Discord channel receives them: create one with create_destination, then attach it with set_view_target. A View may carry both."New value: +"Delivery channels, at least one. \"email\" mails the Matches to the account's address. The other value routes them to the destinations attached to this View instead, which is how a Slack or Discord channel receives them: add one in the Ticker web app under Settings, then attach it with set_view_target. A View may carry both."
    • changedInput schema / properties / delivery_target_id / description
      Previous value: -"Optionally attach an alert destination (an id from list_destinations or create_destination) so this alert's Matches are delivered there the instant they fire, in addition to email. Omit for email-only."New value: +"Optionally attach an alert destination (an id from list_destinations; new destinations are added in the Ticker web app under Settings) so this alert's Matches are delivered there the instant they fire, in addition to email. Omit for email-only."
  13. Changed1 schema field changed
    • addedInput schema / properties / digest
      Added value: +{
      +  "additionalProperties": false,
      +  "description": "The View's Digest: each Issue is the View's events with Movers, New and Gone against the previous Issue. Only the fields you pass change. Turning it on past the plan's Digest count is refused with the plan that carries more.",
      +  "properties": {
      +    "columns": {
      +      "description": "Which get-in the Digest page opens on: primary or secondary.",
      +      "enum": [
      +        "primary",
      +        "secondary"
      +      ],
      +      "type": "string"
      +    },
      +    "destination_ids": {
      +      "description": "Destinations (ids from list_destinations) that also receive each Issue. [] clears them.",
      +      "items": {
      +        "format": "uuid",
      +        "type": "string"
      +      },
      +      "maxItems": 50,
      +      "type": "array"
      +    },
      +    "enabled": {
      +      "description": "Turn the Digest on or off.",
      +      "type": "boolean"
      +    },
      +    "extra_columns": {
      +      "description": "Extra columns: absorption adds Absorbed 7d to the Issue page, email and files. [] removes it.",
      +      "items": {
      +        "enum": [
      +          "absorption"
      +        ],
      +        "type": "string"
      +      },
      +      "type": "array"
      +    },
      +    "formats": {
      +      "description": "The files each Issue links: csv, xlsx, pdf, md.",
      +      "items": {
      +        "enum": [
      +          "csv",
      +          "xlsx",
      +          "pdf",
      +          "md"
      +        ],
      +        "type": "string"
      +      },
      +      "minItems": 1,
      +      "type": "array"
      +    },
      +    "hour": {
      +      "description": "The hour (0 to 23) in the account's timezone when the Issue is built.",
      +      "maximum": 23,
      +      "minimum": 0,
      +      "type": "integer"
      +    },
      +    "sections": {
      +      "description": "Sections each Issue shows: movers, new, gone (default all three). The rest of the table always shows.",
      +      "items": {
      +        "enum": [
      +          "movers",
      +          "new",
      +          "gone"
      +        ],
      +        "type": "string"
      +      },
      +      "minItems": 1,
      +      "type": "array"
      +    },
      +    "table_columns": {
      +      "description": "Screener columns each Issue's table shows, in order: column names from list_screen_columns (e.g. ea.lowest_aip_price_current). Each must be a column the plan unlocks. [] restores the default table.",
      +      "items": {
      +        "maxLength": 120,
      +        "minLength": 1,
      +        "type": "string"
      +      },
      +      "maxItems": 255,
      +      "type": "array"
      +    }
      +  },
      +  "type": "object"
      +}
  14. Changed2 schema fields changed
    • changedInput schema / properties / notification_mode / anyOf
      Previous value: -[
      -  {
      -    "enum": [
      -      "instant",
      -      "digest",
      -      "muted"
      -    ],
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]New value: +[
      +  {
      +    "enum": [
      +      "instant",
      +      "batched",
      +      "muted",
      +      "digest"
      +    ],
      +    "type": "string"
      +  },
      +  {
      +    "type": "null"
      +  }
      +]
    • changedInput schema / properties / notification_mode / description
      Previous value: -"Per-alert EMAIL delivery mode: 'instant' (email each match), 'digest' (the Matches wait and go out together in one email, at the pace notification_cadence sets: `hourly` or `daily`), or 'muted' (Matches recorded, never emailed). 'instant' and 'muted' override notification_cadence. Alert destinations are unaffected: they always deliver instantly."New value: +"Per-alert EMAIL delivery mode: 'instant' (email each match), 'batched' (the Matches wait and go out together in one email, at the pace notification_cadence sets: `hourly` or `daily`), or 'muted' (Matches recorded, never emailed). 'instant' and 'muted' override notification_cadence. 'digest' is the old name of 'batched', accepted until 2026-11-30 and stored as 'batched'. Alert destinations are unaffected: they always deliver instantly."
  15. Changed2 schema fields changed
    • changedInput schema / properties / notification_cadence / description
      Previous value: -"How often this alert's Matches may leave as EMAIL: 'immediate' (each match), 'hourly', 'daily' (batched), or 'off' (Matches recorded but never emailed). Only paces EMAIL; an attached alert destination always delivers the instant a Match fires, regardless of this."New value: +"How often this alert's Matches may leave as EMAIL: `immediate` (each match), `hourly`, `daily` (batched), or `off` (Matches recorded but never emailed). Only paces EMAIL; an attached alert destination always delivers the instant a Match fires, regardless of this."
    • changedInput schema / properties / notification_mode / description
      Previous value: -"Per-alert EMAIL delivery mode: 'instant' (email each match), 'digest' (the Matches wait and go out together in an hourly email or a daily email, at the pace notification_cadence sets), or 'muted' (Matches recorded, never emailed). 'instant' and 'muted' override notification_cadence. Alert destinations are unaffected: they always deliver instantly."New value: +"Per-alert EMAIL delivery mode: 'instant' (email each match), 'digest' (the Matches wait and go out together in one email, at the pace notification_cadence sets: `hourly` or `daily`), or 'muted' (Matches recorded, never emailed). 'instant' and 'muted' override notification_cadence. Alert destinations are unaffected: they always deliver instantly."
  16. Changed3 schema fields changed
    • changedInput schema / properties / notification_cadence / description
      Previous value: -"How often this alert's Matches may leave as EMAIL: 'immediate' (each match), 'hourly', 'daily' (batched), or 'off' (Matches recorded but never emailed). Only paces EMAIL — an attached alert destination always delivers the instant a Match fires, regardless of this."New value: +"How often this alert's Matches may leave as EMAIL: 'immediate' (each match), 'hourly', 'daily' (batched), or 'off' (Matches recorded but never emailed). Only paces EMAIL; an attached alert destination always delivers the instant a Match fires, regardless of this."
    • changedInput schema / properties / notification_mode / description
      Previous value: -"Per-alert EMAIL delivery mode: 'instant' (email each match), 'digest' (the Matches wait and go out together in an hourly email or a daily email, at the pace notification_cadence sets), or 'muted' (Matches recorded, never emailed). 'instant' and 'muted' override notification_cadence. Alert destinations are unaffected — they always deliver instantly."New value: +"Per-alert EMAIL delivery mode: 'instant' (email each match), 'digest' (the Matches wait and go out together in an hourly email or a daily email, at the pace notification_cadence sets), or 'muted' (Matches recorded, never emailed). 'instant' and 'muted' override notification_cadence. Alert destinations are unaffected: they always deliver instantly."
    • changedInput schema / properties / tags / description
      Previous value: -"Free-text organization labels for this View (max 5, deduplicated). Purely for grouping/filtering in the app — tags never change what the View matches or where alerts deliver."New value: +"Free-text organization labels for this View (max 5, deduplicated). Purely for grouping/filtering in the app; tags never change what the View matches or where alerts deliver."
  17. First observed

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only tell the agent this is non-read-only, non-destructive and open-world; the description adds the crucial side effect that a new View begins recording Matches and firing alerts immediately unless enabled is false, and that a Digest is optional. It does not state plan/quota limits or that nothing is destroyed on failure, but the immediate-alert disclosure is the highest-value behavioral fact here.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Five short sentences, front-loaded with the definition of the resource, then the side effect, then routing to alternatives. Every sentence carries a distinct fact and none restates the title or schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 14-parameter tool with nested predicate, digest and listing_trigger objects and no output schema, the description covers the purpose, the immediate-alert behavior and the sibling routing that the schema cannot express. The heavy predicate DSL is fully documented in the schema, so the remaining gap is primarily what a successful call returns (the created View's id), which the agent must infer.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds cross-field meaning the schema states only in isolation: the channel choice routes either to email or to an attached destination, and enabled defaults to a live alert rather than a saved draft. It still does not explain predicate authoring in the description (that lives in the schema), so it stops short of a 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

It states a specific verb and resource ("Creates a View") and then defines the resource in operational terms: a saved predicate over events that records a Match on entry and sends alerts by email or to an alert destination. That definition lets an agent distinguish it from watch_event (one event) and screen_events (ad-hoc screening) without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives a positive trigger ("when the person asks to be alerted about every event that meets market conditions") and three named alternatives with the condition that selects each: screen_events to test the predicate, update_view to modify an existing View, watch_event for single-event alerts. Nothing is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources