# YAC DB documentation yac db Back to app Getting started YACQL basics YACQL reference Datasets Clauses Filtering totals Operators & expressions Aggregate functions Scalar aggregate subqueries API Execute a query Ask in English Discover the schema Resolve players and teams Limits Reference Data source Docs Getting started YAC DB provides NFL player statistics, team results, play-by-play data, draft and combine data, and contract data. Use either of these interfaces: Ask in plain English on the home page . For example: "Who led the NFL in rushing yards in 2025?" Write YACQL directly in the editor . YACQL is the query language used for football data. English submissions display the generated YACQL, which you can edit and run. Results include the source query. You can edit and rerun it, create a chart, or copy a link to the result. YACQL basics A query begins with a dataset and grain: rushing games This uses the rushing dataset and games grain. It returns one row per player-game of rushing stats. Filter with where : rushing games where season = 2023 Pick columns with show : rushing games where season = 2023 show player, rushing_yards, team Then order and cap the result: rushing games where season = 2023 show player, rushing_yards, team rank by rushing_yards desc limit 5 That's the whole shape: , then optional where , show , rank by , and limit lines. The home page has a live editable copy of this exact example. For grouping, aggregates, and streaks, see the reference below. YACQL reference Datasets Every query opens with . For the player stat datasets, grain controls what a row means: games (one row per player-game), season (one row per player-season, summed), or career (one row per player, all-time). pbp uses plays , with one row per play. For every other dataset below, the grain word is required syntax but doesn't change the result. Use games by convention ( season for team_season and player_season , since those are already season-level data). Dataset Grain One row = player games / season / career player-game (all stat categories) passing games / season / career player-game passing stats rushing games / season / career player-game rushing stats receiving games / season / career player-game receiving stats defense games / season / career player-game defensive stats special_teams games / season / career player-game kicking/return stats pbp plays one play games games one scheduled game (score, spread, weather) teams games one franchise (reference data) team_game games team-game result team_season season team-season standings/EPA team_sos games team-season strength of schedule player_season season player-season (stats + snaps + NGS + bio) draft games one draft pick combine games one combine invite contracts games one contract snaps games player-game snap counts injuries games player-game injury report row ngs_passing games player-week Next Gen Stats (passing) ngs_receiving games player-week Next Gen Stats (receiving) ngs_rushing games player-week Next Gen Stats (rushing) The editor's schema sidebar lists every column available on each dataset. Clauses Clause Does where Filter rows before grouping. Combine with and / or / not . group by Group rows for aggregation, in addition to any grouping implied by the grain. show Choose columns and/or aggregates to return, each with an optional as alias . show distinct Return only distinct rows for the given columns. having Filter grouped results. Use it only on an aggregated query and against a show alias, not a raw aggregate. rank by [asc|desc] Order results. Defaults to ascending; add desc for descending. Multiple keys allowed. top N per Keep only the best N rows per group, per the preceding rank by . Requires a rank by . limit N Cap the number of rows returned. offset N Skip the first N rows. longest streak () Inside show : the longest run of consecutive games matching a condition. It requires the games grain and group by . See the example below. Example using group by , an aggregate, and rank by : player games where position = QB and season >= 2018 and passing_yards >= 300 group by player show count as games, avg passing_yards, sum passing_tds rank by games desc limit 20 Filtering season and career totals On aggregate grains, where filters the underlying game rows; having filters the aggregated result. Use a show alias with having for season or career thresholds. # Wrong: no single game has 300 attempts passing season where season = 2023 and attempts >= 300 # Right: filters each player's season total passing season where season = 2023 group by player show player, sum attempts as att, sum passing_tds as tds having att >= 300 rank by tds desc A bye week or season boundary does not break a streak. Streaks use consecutive games played: rushing games where season >= 2020 group by player show player, longest streak (rushing_yards >= 100) as streak rank by streak desc limit 10 Operators & expressions Comparisons: = != <> < <= > >= , plus in (v1, v2, ...) and between low and high . Logic: and , or , not , and parentheses for grouping. and binds tighter than or . Boolean columns can be used bare as a whole condition: where playoffs , where red_zone and down = 3 . String values can be quoted ( "Kansas City" ) or, for convenience, left unquoted for simple values: position = QB . Arithmetic ( + - * / ) is allowed in show over columns and numbers, e.g. show passing_yards - rushing_yards as diff . Division is safe against divide-by-zero. Aggregate functions Aggregates are prefix, not parenthesized: avg passing_yards , not avg(passing_yards) . Function Returns count / count Row count, or non-null count of a column. avg Average. sum Sum. min / max Minimum / maximum. median 50th percentile. p25 / p75 / p90 / p95 / p99 Percentile. stddev Standard deviation. Scalar aggregate subqueries Compare a row (in where ) or a grouped result (in having ) against a value computed from the whole dataset. The subquery is uncorrelated, so it cannot reference the outer row, so it's for comparing against a fixed benchmark, not "better than their own average." where rushing_yards > (avg rushing_yards where position = "RB") having total_rushing_yards > (sum rushing_yards where player = "Derrick Henry" and season = 2023) API The versioned public surface lives entirely under /api/v1/ . No account or API key is required for any endpoint below. Execute a query POST /api/v1/query compiles and executes YACQL. curl -X POST /api/v1/query \ -H "Content-Type: application/json" \ -d '{"query":"passing games\nshow player, passing_yards\nlimit 10"}' Success is 200 with columns and rows as parallel arrays. Each row is an array of values in columns order, not an array of objects: { "data": { "columns": [{ "name": "player", "type": "string" }, { "name": "passing_yards", "type": "int" }], "rows": [["Patrick Mahomes", 401], ["Josh Allen", 385]] }, "meta": { "rowCount": 2, "elapsedMs": 14, "truncated": false } } An error response has this shape; location is present for compile errors: { "error": { "code": "invalid_query", "message": "...", "requestId": "...", "location": { "line": 2, "column": 5, "start": 14, "length": 3 } } } Add ?format=csv for an RFC-4180 CSV response instead of JSON. The row count/elapsed time/truncation flag move to X-Row-Count , X-Elapsed-Ms , and X-Truncated response headers; errors are always JSON regardless of format . curl -X POST '/api/v1/query?format=csv' \ -H "Content-Type: application/json" \ -d '{"query":"passing games\nshow player, passing_yards\nlimit 10"}' -o result.csv Code Status Meaning invalid_request 400 Empty query query_too_large 413 Query text over the length limit request_too_large 413 Request body over the size limit unsupported_format 400 Unknown ?format= value invalid_query 400 Compile error (includes location ) query_timeout 504 Query exceeded the time limit service_unavailable 503 Database unavailable rate_limit_exceeded 429 Rate limit hit (includes retryAfterSeconds ) Ask in English POST /api/v1/nl translates an English question to YACQL and runs it. yacql / data / meta are present together on success; if the question is ambiguous you get a 200 with clarification set instead (that's a successful answer, not an error). curl -X POST /api/v1/nl \ -H "Content-Type: application/json" \ -d '{"text":"most passing yards 2024"}' { "yacql": "passing season\nwhere season = 2024\n...", "data": { "columns": [...], "rows": [...] }, "meta": { "rowCount": 1, "elapsedMs": 9, "truncated": false }, "clarification": null, "confidence": { "score": 0.93, "threshold": 0.8, "reasons": ["..."] } } This endpoint uses the same rate-limit budget as /api/v1/query . See Limits below. Code Status Meaning could_not_translate 422 The translator did not recognize the question. message contains guidance. translation_failed 502 Translated YACQL failed to compile (a YAC DB defect, not yours) Discover the schema GET /api/v1/schema returns every dataset, column, grain, keyword, and aggregate function available to YACQL. The response is additive over time, so it is safe to cache long term. Send If-None-Match with the last ETag to get a 304 instead of the body. GET /api/v1/meta returns data freshness: completedSeason / completedWeek is the latest played (scored) game. This is the "data through" value. scheduledSeason > completedSeason means next season's schedule has loaded but nothing's been played yet. Both endpoints are unlimited, cacheable, and served from an in-memory snapshot. Resolve players and teams YACQL filters require canonical values. Use these endpoints to resolve a search value. curl '/api/v1/players?search=jamarr' { "players": [{ "slug": "jamarr-chase-b976", "name": "Ja'Marr Chase", "playerId": "...", "position": "WR", "latestTeam": "CIN", "teams": ["CIN"], "seasons": [2021, 2022, 2023, 2024, 2025], "headshotUrl": "...", "games": 78 }] } Use name from a result as the literal value in a YACQL filter (e.g. where player = "Ja'Marr Chase" ); slug is the stable identifier for GET /api/v1/players/{slug} and yacdb.fyi/player/{slug} links. limit defaults to 10, max 50. GET /api/v1/teams returns the full 32-team list (no search needed); GET /api/v1/teams/{slug} resolves one. All four are unlimited and a missing /{slug} is a 404 not_found . Limits 10 requests per minute and 100 per rolling 24 hours per IP address, shared by POST /api/v1/query and POST /api/v1/nl 1,000 rows and 10 seconds per query 16,384 characters per query, 64 KB per request body All GET endpoints for schema, meta, players, and teams are unlimited and served from process-lifetime caches Every rate-limited response includes RateLimit-Limit , RateLimit-Remaining , RateLimit-Reset , and X-Request-ID headers. Reference OpenAPI v1 document with request and response schemas and error codes. Data source Underlying data is provided by the nflverse project, licensed CC BY 4.0 . See Terms for the full attribution notice. App · Docs · Terms · Privacy