Ticker Docs
Tools

screen_events

Screen Events

Reads only.

Use this to find and rank many events by analytics signals: price, demand, inventory, rank. Use search_events instead to look an event up by name, venue or date, and get_event_analytics for every column of one event you already have. Screen the live event universe with a predicate over analytics columns. Returns matching events with their joined analytics snapshot (event_analytics + event + performer_analytics), including cross-sectional percentile/MAD-z columns. A plan limit is never missing data: a reading the account's plan does not include is refused with a plan_required error (entitled false, required_tier the plan that unlocks it, a one line message), and a column or field the plan strips from an answer is named in a top level withheld list with the same three fields. null, an empty list and available false mean the data does not exist. NON-ADMISSION SKUs ARE ALWAYS EXCLUDED: parking and shuttle / no-admission passes (sub_category_id 75, or an event_name matching '%parking%') never appear in a screen, and there is no parameter that lets them in. A screen that asks for sub_category_id 75 alone is refused with an error that says so. EVERY ROW IS ALREADY LINKED: each row carries event_id (the Ticker id every tool here accepts) and event_url (its page), whatever you project. Never call search_events to find the link or the id for a row this tool returned. The row has both, and a second lookup by name can attach a DIFFERENT event. An unknown name in columns is REJECTED with the valid spelling, not dropped, so fix it and call again. A page too large for one tool result comes back as the first rows plus meta.truncated and meta.total_rows; narrow the predicate, project fewer columns, or page, rather than repeating the call. Also screenable: marketplace demand levels/changes (ea.interest*, ea.sales*; young series, gate freshness on ea.demand_asof_date), PERFORMER-grain demand (pa.interest*, pa.sales*, pa.demand_asof_date; a separate performer-resource reading, NOT a rollup of that performer's events; filtering on it selects every event of a matching performer, so use it to find hot performers and ea.demand_* to pick among their events; gate on pa.demand_asof_date), performer popularity-rank momentum (pa.popularity_rank*; positive change = climbing), and sale-timing/ops columns: e.presale1_date and e.onsale_date (timestamptz; NULL = unknown/TBD and NULL never matches a range predicate; negative days_ago means the FUTURE: 'on sale in the next 7 days' is {"all":[{"col":"e.onsale_date","op":">=","val":{"days_ago":0}},{"col":"e.onsale_date","op":"<=","val":{"days_ago":-7}}]}) and e.monitoring_priority (the priority tier P1 to P10, stored as 1 to 10; P1 is read most often). Count changes come as counts (ea.listings_delta_*, ea.tickets_delta_*) AND as percents (ea.listings_pct_1d/_7d, ea.tickets_pct_1d/_7d): 'listings down 10%' is ea.listings_pct_7d <= -10, never a count of 10. ea.median_price_7d_ago is the median of 7 days earlier, a past level. ea.tm_tickets_pct_3d is the primary book's 3-day percent change (-100 = none left after some 3 days ago), and e.tm_sells is true when TM sells the event as its own primary sale ('not on TM' is false); both are primary-market columns. ONLY EVENTS DATED TODAY OR LATER (UTC) ARE SCREENED, whatever active_only says. A screen whose e.local_date range ended two or more days ago is refused with an error that says so. active_only=false adds inactive events in that window; their rows can be FROZEN at the last computation (ea.days_to_event stops counting, often stuck at -1) or hollow (returned with partial_row=true in the snapshot: treat their nulls as unknown, not zero). Don't rank frozen rows against live ones. For a past event's per-day history use get_event_price_chart / get_event_analytics_history. EMPTY RESULTS ARE EXPLAINED, NOT GUESSED AT: when a screen returns nothing you also get a diagnostics block (see the diagnostics param): rows_evaluated (how many events the predicate was actually measured against, AFTER the active/parking/economic-floor/freshness gates), system_gates (how many survived each of those gates, so you can see a scope flag did the damage), root and a per-leg passed/failed/null breakdown. Read the warnings before answering: ALL_NULL means that column is NULL for EVERY row evaluated, so the leg can never be true and the screen is broken; that is NOT a real no-match, and the honest reply is 'this signal isn't computed for these events' (offer a different column), never 'no events match'. A real no-match has legs with non-zero passed and null counts well under rows_evaluated: the filters worked, the combination is just too tight, so loosen the thresholds. HIGH_NULL_RATE (>50% NULL) is the middle ground: the results are real but cover only part of the universe, so say so. NO_ROWS_EVALUATED means nothing reached the predicate at all: the scope flags, not the predicate, are the problem. Each leg's marginal is how many more rows you'd get by dropping that one leg (null under an any/OR group, where dropping a member is meaningless). Legs include whole any/all groups as well as single comparisons; kind tells them apart. If diagnostics.status is "unavailable" the analysis could not be run; the events themselves are still correct. ABSORPTION IS DELISTING, NOT SALES: ea.absorption_count_1d/7d, ea.absorption_rate_7d and ea.listings_added_count_* count listings that LEFT (or joined) the marketplace: a broker withdrawal, a move to another exchange, or an expiry counts the same as a sale. NEVER report them as tickets bought or as sales volume; say 'absorbed' / 'delisted'. Whenever your predicate or sort touches an absorption column, ea.absorption_data_quality, ea.absorption_grain and ea.absorption_asof_date are auto-added to the projection; quote the quality and the as-of day with any absorption number. The as-of day is yesterday (UTC) for every covered event; an event without seat block ledger coverage is NULL in every absorption column, never 0. Quality is captured days / 7 times a block identity weight, so it tops out at 1.0. A provisional exit counts at once and a later return takes it back, so a published day can go down. 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 book-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. Authoritative validation runs in SQL; an invalid predicate returns a clear error.

Parameters

NameTypeRequiredDefaultNotes
predicateobject{}Predicate DSL object (see tool description), a pure boolean FILTER, not an ordering. Omit or pass {} to screen the whole live universe. To surface the most interesting rows first, use the sort parameter; the predicate cannot express ordering.
sortobjectServer-side ranking: sorts the FULL match set by col (NULLS LAST, event_id tiebreak) so the page is the true top-N, not an event_id slice. Omit for default event_id ASC. Fast on the ranking primitives (ea.price_self_z_7d_pct_rank_subcat_tte, ea.price_self_z_7d_xs_z_subcat_tte, ea.inventory_velocity_1d_xs_z_subcat_tte, ea.pace_z_3d_vs_14d_xs_z_subcat_tte); sorting other columns over a broad predicate may time out; narrow the predicate.
columnsstring[]Snapshot columns to return, as namespaced refs (e.g. ["ea.median_price_current","e.event_name"]). A bare namespace ("ea") returns that whole bag; ["*"] returns the full ea+e+pa snapshot. Omit for a compact default (identity + headline signals) that keeps the payload small. Request more only when you need them. Names must come from list_screen_columns, spelled exactly; an unknown one is rejected by name rather than dropped. event_id and event_url are on every row already and do not need projecting.
pageinteger1At least 1.
limitinteger50From 1 to 100.
countbooleanfalseCompute the total number of matches (up to 10,000, surfaced as total; countCapped true means at least that many). Default false: counting roughly DOUBLES query cost and is usually not needed to answer with a page of events. Set true ONLY when the user's ask is about how many events match ("how many…", "count the…") or you must paginate exhaustively. When false, total, totalPages and countCapped are null.
active_onlybooleantrueRestrict to the active event universe (status='active'). Default true: inactive/closed events are stale and should not rank as live signals. Set false to include them.
apply_floorbooleantrueApply the economic floor (listings_current >= 25 AND median_price_current >= 40). Default true. Set false only when explicitly screening the raw universe.
exclude_stalebooleantrueDrop stale rows: events last priced more than 3 days behind the market frontier (last_price_snapshot_date). Default true; a 4-day-old snapshot is not a live signal. Set false only when the user explicitly wants stale rows included.
diagnosticsboolean or on_emptyon_emptyExplain the result set (see EMPTY RESULTS in the tool description). "on_empty" (the default) runs the analysis only when this page came back with zero rows, so an empty screen is never reported as a plain 'no matches' when it is really an ALL_NULL column. true runs it always (useful to check how much of the universe a signal actually covers); false never runs it. The analysis is a second aggregate query over the whole candidate universe and adds ~5s, so don't set true routinely.

Example

The strongest cross-sectional price outliers 7 to 60 days out, top 25 by percentile rank:

{
  "predicate": {
    "all": [
      { "col": "ea.price_self_z_7d_xs_z_subcat_tte", "op": ">=", "val": 2 },
      { "col": "ea.days_to_event", "op": "between", "val": [7, 60] }
    ]
  },
  "sort": { "col": "ea.price_self_z_7d_pct_rank_subcat_tte", "dir": "desc" },
  "limit": 25
}

On this page