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.