← Registry

Analytics

tigzig.com

Provides access to Tigzig V2 financial indicators and generates technical analysis reports for stock tickers.

3 endpoints8 known toolsFirst detected August 1, 2026Last detected September 16, 2026

ENDPOINT 1

https://api.tigzig.com/mcp

No auth detected

MCP server metadata

Name
Tigzig Unified Data Surface
Version
Tigzig V2 - unified data API. Read-only access to multiple data domains via a single MCP surface. Call list_categories first to see the available domains, then list_indicators_in_category to expand any one of them, then v2_get_series to pull data. find_indicator(query) is a cross-category fuzzy-search shortcut.
Capabilities
experimentaltools

Known tools 4

list_indicators_in_category

List indicators in a V2 category Returns metadata for every visible indicator in one Tigzig V2 category.

Inferred read-only
list_categories

List all V2 categories with one-liner descriptions Returns the full menu of Tigzig V2 categories with a one-line summary of each.

Inferred read-only
find_indicator

Cross-category indicator search by id substring or name Substring search over indicator IDs and display names across ALL V2 categories.

Inferred read-only
v2_get_series

Get time-series data for one or more V2 indicators (wide format) Returns wide-format time series: one row per date, one column per indicator.

Inferred read-only

CONNECT WITH APPROVAL

Client installation

Review this server and its permissions before adding it. Secret placeholders must be set locally.

Codex

~/.codex/config.toml

[mcp_servers.tigzig-unified-data-surface]
url = "https://api.tigzig.com/mcp"
enabled = true
Claude Code

.mcp.json

{
  "mcpServers": {
    "tigzig-unified-data-surface": {
      "type": "http",
      "url": "https://api.tigzig.com/mcp"
    }
  }
}
Claude Desktop

Settings → Connectors → Add custom connector

Name: tigzig-unified-data-surface
Remote MCP URL: https://api.tigzig.com/mcp

Add this remote URL as a custom connector in Claude Desktop. Availability depends on the user plan and workspace policy.

Cursor

.cursor/mcp.json

{
  "mcpServers": {
    "tigzig-unified-data-surface": {
      "url": "https://api.tigzig.com/mcp"
    }
  }
}
Visual Studio Code

.vscode/mcp.json

Add to Visual Studio Code
{
  "servers": {
    "tigzig-unified-data-surface": {
      "type": "http",
      "url": "https://api.tigzig.com/mcp"
    }
  }
}
Generic MCP

Client-specific MCP configuration

{
  "name": "tigzig-unified-data-surface",
  "transport": "streamable-http",
  "url": "https://api.tigzig.com/mcp"
}
MCP Inspector

Run the official MCP Inspector locally and enter the indexed Streamable HTTP endpoint.

ENDPOINT 2

https://ta.tigzig.com/mcp

No auth detected

MCP server metadata

Name
Technical Analysis MCP API
Version
MCP server for technical analysis endpoints. Note: Some operations may take up to 3 minutes due to data fetching and analysis requirements.
Capabilities
experimentaltools

Known tools 1

create_technical_analysis

Create Technical Analysis Generates comprehensive technical analysis reports for a specified stock ticker.

Potential side effects

CONNECT WITH APPROVAL

Client installation

Review this server and its permissions before adding it. Secret placeholders must be set locally.

Codex

~/.codex/config.toml

[mcp_servers.technical-analysis-mcp-api]
url = "https://ta.tigzig.com/mcp"
enabled = true
Claude Code

.mcp.json

{
  "mcpServers": {
    "technical-analysis-mcp-api": {
      "type": "http",
      "url": "https://ta.tigzig.com/mcp"
    }
  }
}
Claude Desktop

Settings → Connectors → Add custom connector

Name: technical-analysis-mcp-api
Remote MCP URL: https://ta.tigzig.com/mcp

Add this remote URL as a custom connector in Claude Desktop. Availability depends on the user plan and workspace policy.

Cursor

.cursor/mcp.json

{
  "mcpServers": {
    "technical-analysis-mcp-api": {
      "url": "https://ta.tigzig.com/mcp"
    }
  }
}
Visual Studio Code

.vscode/mcp.json

Add to Visual Studio Code
{
  "servers": {
    "technical-analysis-mcp-api": {
      "type": "http",
      "url": "https://ta.tigzig.com/mcp"
    }
  }
}
Generic MCP

Client-specific MCP configuration

{
  "name": "technical-analysis-mcp-api",
  "transport": "streamable-http",
  "url": "https://ta.tigzig.com/mcp"
}
MCP Inspector

Run the official MCP Inspector locally and enter the indexed Streamable HTTP endpoint.

ENDPOINT 3

https://db-mcp.tigzig.com/mcp

No auth detected

MCP server metadata

Name
Database MCP Server
Version
1.0.0
Capabilities
experimentaltools
Server instructions

Read-only SQL over two cricket databases: Postgres (men's and women's T20, ODI and Test internationals, plus the IPL) and DuckDB (men's and women's T20, ODI and Test internationals, plus the IPL). Both engines hold the SAME tables with the same rows, so the endpoint is a choice of SQL dialect and nothing else. Everything below applies to BOTH tools. ### Scope Men's and women's international cricket in all three formats, plus the IPL. Everything here is Cricsheet-derived; there is no other domestic or franchise cricket yet. ## Source and licence The ball-by-ball data comes from Cricsheet ([cricsheet.org](https://cricsheet.org)), published under the Open Data Commons Attribution License 1.0, ODC-BY ([opendatacommons.org/licenses/by/1-0/](https://opendatacommons.org/licenses/by/1-0/)). The tables here are derived from it: reshaped into two engines, split by gender into separate tables, joined to a match-level table and refreshed twice daily. If you build on this API, the same attribution carries to you. Coverage note: Cricsheet withholds matches featuring the Afghanistan men's team or played in the Afghanistan Premier League (see [cricsheet.org/withheld-matches](https://cricsheet.org/withheld-matches)). That exclusion is inherited here, so this is not a complete record of men's internationals. Provided as is. No guarantee of accuracy, completeness or availability, and no support commitment. TigZig is not affiliated with or endorsed by Cricsheet. Cricsheet is credited as the source of the underlying match data under the terms of the ODC-BY 1.0 licence. Full terms, including what ODC-BY asks of you if you pass the data on: **[/terms](https://db-mcp.tigzig.com/terms)** ### The tables `ball_by_ball` is one row per delivery and `match_info` is one row per match, and they join on `match_id`. Both hold every format and both genders, so you narrow with ordinary `WHERE` clauses: | Column | Values | |---|---| | `match_type` | `T20`, `ODI`, `TEST`, `IPL` | | `gender` | `male`, `female` | | `team_type` | `international`, `club` | *** `match_type = 'T20'` MEANS T20 INTERNATIONALS AND DOES NOT INCLUDE THE IPL. *** They are separate values, so twenty-over cricket of every kind is `match_type IN ('T20','IPL')`. This is the one thing on this page that will quietly hand you fewer rows than you expected. ### The slice views Each combination is published as a view too, if you would rather not write the `WHERE`. They are plain views over those tables and hold no separate data: `ball_by_ball_t20_men`, `ball_by_ball_t20_women`, `ball_by_ball_odi_men`, `ball_by_ball_odi_women`, `ball_by_ball_test_men`, `ball_by_ball_test_women`, `ball_by_ball_ipl`, and the seven matching `match_info_*` views. **Every ball table and view has the same columns**, so a query written against one runs against any other by changing the name. `DESCRIBE <table>` gives you the columns and types on either engine. The ball columns are explained under "Reading the ball columns" below, and the match_info result columns under "How a result is recorded". `match_info` is where who won, by how much, which competition, player of the match and the officials live - none of that is in a ball table. Read "How a result is recorded" before counting wins: a super-over match is a TIE, not a win. RETIRED 2026-08-19: `ball_by_ball` and `odi_cricket_ball_by_ball` no longer exist on either engine. A bare `ball_by_ball` could not say which format it meant once each engine held all three. A query on a retired name returns a 400 naming its replacement. ### Join example ```sql SELECT m.winner, SUM(b.runs_off_bat) FROM ball_by_ball b JOIN match_info m ON b.match_id = m.match_id GROUP BY m.winner ``` ### Before you aggregate One thing here will otherwise give you a surprising answer. `team1_icc_type` and `event` live in match_info, not in the ball table. A plain "top run scorers" over the ball table pools every level of international cricket, so Associate nations rank alongside Full Members. That is correct data, rarely the intended question. Join to match_info and filter when you mean a subset. ### One asymmetry Stated up front, because it will not be obvious from a row count: match_info and the ball-by-ball tables now cover the same match types, so every match here has deliveries behind it. Counting matches in match_info still will not agree with counting them in ONE ball table, because match_info holds every format at once - filter it by match_type to compare like with like. This is deliberate: cross-database joins are impossible, so each engine carries its own copy of match_info, and keeping every format means the full match universe stays queryable from either side. ### match_type values Uppercase: 'ODI', 'T20', 'TEST' and 'IPL'. match_type = 'Test' matches nothing. ## Field definitions live with Cricsheet **[cricsheet.org/format/csv_ashwin](https://cricsheet.org/format/csv_ashwin/)** That page is the canonical data dictionary for every column in these tables. It is their data and their definitions, and it stays right by definition. Open it before writing anything that depends on what a particular field means. What you get here instead is short per-field descriptions, the join key, worked examples and the traps that bite people. Where the two ever disagree, theirs is correct. ## What SQL you can run Read-only, and wider than most people assume. Rather than probing to find the edges, here is the shape of it. **Statements.** `SELECT`, `WITH`, `EXPLAIN`, `DESCRIBE`, plus `SHOW TABLES` and `SHOW DATABASES`. **Everything you would normally reach for inside a SELECT works.** CTEs including several chained together, subqueries and correlated subqueries, every join type, `GROUP BY`, `HAVING`, window functions, `FILTER (WHERE ...)`, `CASE`, `UNION` and the other set operations, ordering, `LIMIT` and `OFFSET`, and the usual string, date, maths and aggregate functions of whichever engine you picked. **Schema introspection works.** `information_schema`, and the `pg_catalog` views on Postgres. So you can ask the database what it holds without guessing. **Two limits worth knowing before they surprise you.** A query is capped at 3500 characters, measured on the DECODED SQL, so something over the line only because of URL encoding will fit as a POST. If you need more than that, download the dataset and query it locally with no limits. And a query is stopped if it runs too long, which is a protection for other callers rather than a judgement about yours. **If something is refused, the error says why.** The response body names the specific problem, and usually the exact character, table or function involved. Read it rather than retrying, and note that some HTTP clients hide the body by default. ## Want the whole table? Download it, do not paginate it Every table is published as files, men's and women's alike, refreshed twice daily from this same database. **`/downloads` on this service** serves that index directly, and the files themselves come from **[api.tigzig.com/cricket/v1/download](https://api.tigzig.com/cricket/v1/download)** Per table as `parquet`, `csv.gz` or `csv.zip`. All files together as `duckdb.zip`, `duckdb.gz`, `sqlite.zip` or `sqlite.gz`. Sizes and row counts are at `/cricket/v1/downloads/manifest`. **Name the file you want and that is exactly what you receive**, so `/cricket/v1/download/match_info.csv.gz` returns `match_info.csv.gz` and `curl -O` saves it under the right name with no extra flags. The all-tables file is `cricket_all_tables.duckdb.zip` and its three siblings. Names are case-sensitive and nothing is inferred, so a wrapping we do not list is a 404 rather than a guess. Pulling a few thousand rows over SQL is exactly what this API is for. Pulling millions is slower for you, costs a lot of requests, and gives you something a single file would have given you in one. The licence is the same either way - see `/terms`. ## Is this data right? The checks are published Every layer that puts this data behind the API is checked on a schedule and published, pass or fail, at **[validation.tigzig.com](https://validation.tigzig.com/)** - what cricsheet publishes against the master, the master against what is served, and what the API returns on both engines, plus every anomaly the checks find, row by row. `/validations` on this service redirects there. Read it before deciding a number is wrong. ## Reading the ball columns Every row is one delivery. Three columns tell you where it sat in the innings. | Column | What it is | |---|---| | `innings` | Which innings. 1 or 2 in a limited-overs match, up to 4 in a Test | | `over_no` | Which over, counting from 0 | | `delivery_in_over` | Which delivery inside that over, counting from 1 | An over usually has six deliveries, so `delivery_in_over` usually runs 1 to 6. When a wide or a no-ball is bowled it has to be bowled again, so the over needs extra deliveries to get six legal ones and the count simply keeps going. Overs of 8 or 10 deliveries are ordinary and longer ones exist. There is no fixed ceiling, and it moves as new matches arrive, so take the current one yourself with a `MAX(delivery_in_over)` if you need it. **So an over is not a fixed number of rows, and a count of deliveries cannot be derived from `over_no` by multiplying.** If you want legal deliveries, `actual_delivery` already counts them. Here is a real over that needed 11 deliveries because five of them were wides: | over_no | delivery_in_over | wides | |---|---|---| | 1 | 1 | | | 1 | 2 | | | 1 | 3 | 1 | | 1 | 4 | 1 | | 1 | 5 | 1 | | 1 | 6 | | | 1 | 7 | 1 | | 1 | 8 | | | 1 | 9 | 1 | | 1 | 10 | | | 1 | 11 | | To read an innings in the order it was bowled: ```sql SELECT over_no, delivery_in_over, striker, bowler, runs_off_bat FROM ball_by_ball WHERE match_id = 1208612 AND innings = 1 ORDER BY over_no, delivery_in_over ``` ### The older `ball` column There is also a `ball` column, kept because it came first and people have queries written against it. It packs the same two numbers into one decimal, so over 1 delivery 3 shows as `1.3`. It has a catch worth knowing. The tenth delivery of an over is written `1.10` at the source, and as a decimal that is the same value as `1.1`, which is the first delivery. So in a long over two different deliveries can carry the same `ball` value, and sorting by it puts the tenth delivery in the wrong place. `over_no` and `delivery_in_over` are whole numbers, so they cannot run into this. Use them whenever the order matters. `ball` is fine for everything else and is not going anywhere. ### Where a column is numbered, the source sent a list Cricsheet sometimes records more than one name in the same slot: an award shared between two players, a second TV or reserve umpire, a third and fourth on-field umpire. We split those into numbered columns and keep every entry. player_of_match1 player_of_match2 umpire1 umpire2 umpire3 umpire4 tv_umpire1 tv_umpire2 reserve_umpire1 reserve_umpire2 The `1` column is always populated where the source gave anything; the `2` is filled only when the source listed a second name, so it is usually null. **Read both if you are counting awards or officials** - reading only the first quietly drops the other half. Count them yourself if you want the scale; it is one query and it changes as new matches arrive. `player_of_match`, `tv_umpire` and `reserve_umpire` were the old single-value spellings and no longer exist. Queries using them fail rather than silently returning one of two. **What each field MEANS is Cricsheet's definition, not ours** - see the field reference above. The numbering is only how we lay their list out in columns. ### Identifying a single delivery `match_id` + `innings` + `over_no` + `delivery_in_over` identifies one delivery uniquely. Use those four together as the key when you need to join deliveries, page through them in a stable order, or check you have not double-counted. `actual_delivery` looks like it should do the same job and does not. It counts LEGAL deliveries, so a wide or a no-ball does not advance it and every extra shares its number with the legal ball that eventually counts. That is correct behaviour for what the column means, and it is why it repeats. ## How a result is recorded **Canonical field definitions live with the source: [cricsheet.org/format/csv_ashwin](https://cricsheet.org/format/csv_ashwin/)** - that page is the authority for every column below and is worth opening before you write anything that depends on one. The notes here are short on purpose. **Read this part even if you skip the rest.** A super-over win is NOT a win. The match is a tie and the tiebreak is recorded beside it, which is how the sport itself records it (ESPNcricinfo writes it Tie+W / Tie+L). So `eliminator` is a SUBSET of `outcome='tie'`, not a category next to it, and the partition that always closes is: ``` winner IS NOT NULL + outcome='draw' + outcome='tie' + outcome='no result' = every match ``` There is deliberately no merged "who really won" column. Folding super-over wins into wins would promote ties to wins, which is a judgement the sport declines to make. Three plain predicates cover every question instead: ```sql WHERE winner = 'India' -- won outright WHERE outcome = 'tie' AND eliminator = 'India' -- tied, took the tiebreak WHERE outcome = 'no result' -- abandoned ``` | Column | What it holds | |---|---| | `outcome` | `draw`, `tie` or `no result`. Empty when there is a winner | | `eliminator` | Team that won the super over. Spelled exactly as `team1` / `team2`, so it joins | | `bowl_out` | Team that won the bowl-out. Same spelling rule. Two matches, both T20 | | `winner_innings` | `1` when the win was by an innings. **Test only, empty everywhere else** | | `method` | `D/L` and similar. Set when the result came from a method rather than play | | `super_over` | Which innings numbers were super overs, e.g. `3,4`. One match ran to `3,4,5,6,7,8` | | `declared` | Which innings were declared, e.g. `1` or `1,3` | | `overs` | Scheduled overs per innings | | `target_runs` | Runs the chasing side needs **to win**. The +1 is already in it | | `target_overs` | Overs available to the chasing side, when a limit was set | | `balls_per_over` | Normally 6 | | `match_type_number`, `team_type`, `toss_uncontested` | See the Cricsheet page | **Four traps worth naming.** Each one returns a number rather than an error, which is why they are here. `target_runs` ALREADY INCLUDES THE +1. It is the runs needed **to win**, not the runs the first side made. Chasing 288 gives `target_runs` 289, so the test for a completed chase is `cumulative >= target_runs` with nothing added. Adding one yourself moves every answer by a run and nothing complains. Almost every match with a target follows the plus-one rule exactly; the handful that do not are rain-revised. `target_runs` IS NOT ONLY FOR RAIN-AFFECTED GAMES, which this page used to say. The great majority of matches carrying a target have **no `method` at all**, so it is the ordinary target on any chase. `method` is what tells you one was revised. `winner_innings` IS EMPTY ON EVERY LIMITED-OVERS MATCH. It appears on Test matches only and on **no** ODI, T20 or IPL match at all, because an innings victory cannot happen there. The name suggests a batting order and it is not that - it is a victory MARGIN flag. If you want "did the side batting second win", the test is `winner_wickets IS NOT NULL`, which is exact: `winner_runs` and `winner_wickets` never both appear on the same match. `winner_runs` alone is ambiguous. With `winner_innings = 1` a value of 359 means "by an innings and 359 runs", not "by 359 runs". Ordering by `winner_runs` without checking `winner_innings` gives a wrong ranking and no error. `target_overs` is overs-and-balls, not a decimal. `40.2` is 40 overs and 2 balls, so averaging or summing that column produces a number that means nothing. **One column is ours, not Cricsheet's.** `team1_icc_type` / `team2_icc_type` are our mapping, taken from the ICC Classification of Official Cricket with effect from 1 July 2026: [the classification document](https://images.icc-cricket.com/image/upload/prd/enzawp3xa8h4ahqonlpd.pdf), reached from the ICC's [playing conditions page](https://www.icc-cricket.com/about/cricket/rules-and-regulations/playing-conditions). The document is the source; the page carries the surrounding regulations. **Men's and women's ODI status are different lists, and we map them separately.** A team can hold one and not the other, so the value depends on the match's `gender` as well as the team. Two caveats, both ours to state. It is a **snapshot applied to every match regardless of date**, so it is not the team's status on the day - Ireland appear as a Full Member in fixtures played years before they became one, and the ICC's own document says the classification is not intended to be applied retrospectively. And it moves when the ICC republishes. Check the document above if a value matters to your result. ## SQL guardrails Read-only endpoint. ### Statements - `SELECT` and `WITH`. - `SHOW TABLES`, `DESCRIBE <name>` and `EXPLAIN`. `DESCRIBE` is the quickest way to get column types, and both work on either engine. - One statement per request. A semicolon-separated batch is rejected rather than running only the last one. ### Clauses and operators - `JOIN` with an explicit `ON`, up to 10 per SELECT - `WHERE`, `GROUP BY`, `HAVING`, `ORDER BY`, `LIMIT`, `OFFSET` - `CASE WHEN`, `DISTINCT`, `IN`, `EXISTS`, `BETWEEN`, `LIKE`, `ILIKE`, `IS NULL` - `UNION` and `UNION ALL` - Subqueries to depth 3. CTEs do not count towards that depth, so a chain of CTEs can go deeper. - Window functions: `OVER (PARTITION BY ... ORDER BY ...)`, window frames such as `ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW`, `FILTER (WHERE ...)`, and `QUALIFY` - Arithmetic and comparison operators ### Functions If a function is not on this list, it is not available. | | | |---|---| | Aggregate | `count` `sum` `min` `max` `avg` `median` `mode` `stddev` `stddev_pop` `stddev_samp` `variance` `var_pop` `var_samp` `percentile_cont` `percentile_disc` `quantile_cont` `quantile_disc` `count_if` `arg_min` `arg_max` `max_by` `any_value` `array_agg` `string_agg` `group_concat` | | Boolean aggregate | `bool_and` `bool_or` `every` | | Window | `row_number` `rank` `dense_rank` `percent_rank` `cume_dist` `ntile` `lag` `lead` `first_value` | | Numeric | `floor` `ceil` `ceiling` `round` `abs` `sign` `sqrt` `greatest` `least` | | Null handling | `coalesce` `nullif` | | Type conversion | `cast` `try_cast` | | Date | `extract` `year` `month` `day` `date_part` | | Text | `length` `lower` `upper` `split_part` | For case-insensitive matching you do not need a function at all: `ILIKE` is available and is usually the better answer. `string_agg` and `group_concat` are the same function under two names and both work, on whichever engine has them - `group_concat` is a DuckDB spelling, so on Postgres use `string_agg`. `percentile_cont` and `percentile_disc` use the ordered-set form and work on both engines: `percentile_cont(0.5) WITHIN GROUP (ORDER BY runs_off_bat)`. `quantile_cont` and `quantile_disc` are the DuckDB spellings and Postgres does not have them. Some that are not available: `concat`, `concat_ws`, `||`, `chr`, `substring`, `trim`, `reverse`, `lpad`, `repeat`, `format`, `printf`, `regexp_extract`, `regexp_replace`, `date_trunc`, `strftime`, `md5`, `hash`, `generate_series`, `unnest`, `list_aggregate`, `array_to_string`, `replace`. ### Tables - The cricket tables listed above. - `information_schema` and `pg_class`, for schema metadata. Both list the cricket tables above, so you can ask the database what it holds rather than guessing. - Not available: `pg_proc`, `pg_views`, `pg_tables`, `pg_namespace`, `pg_attribute`, `pg_indexes`, `pg_database`, `pg_roles`, `pg_settings`, `pg_stat_activity`, `pg_shadow`, `pg_authid`. ### Also blocked - Writes and DDL of any kind. - SQL comments. - `INTERSECT` and `EXCEPT`. - Joins without `ON`, comma joins, `CROSS JOIN`. - More than 10 JOINs per SELECT. - More than 10 SELECT keywords, counting the word SELECT anywhere in the query, including inside CTEs and subqueries. - `WITH RECURSIVE`. - Subqueries inside `ORDER BY`, and function calls inside `ORDER BY`. Sort on a plain column, or compute the value in a SELECT or CTE first and sort on that. - Functions that read a file or a URL, such as `read_csv` and `read_parquet`. - Functions that act on the database server rather than query it, such as `pg_terminate_backend`, `pg_cancel_backend` and `pg_sleep`. - `COPY`, in any form. - Queries longer than 3500 characters. The limit is measured on the decoded SQL, so a GET that is over only because of URL encoding will fit if sent as a POST with a JSON body. ### Row limit: every query returns at most 1000 rows - If yours would return more, you get the first 1000 and `truncated: true`. Always read that field. - The ceiling applies even when you ask for more: `LIMIT 5000` returns 1000 rows with `truncated: true`, not 5000. - A `LIMIT` or `FETCH FIRST` below 1000 is honoured exactly and comes back `truncated: false`, so you know you have the whole result. - If you set no limit at all, one is added for you. Everything else behaves normally: `ORDER BY`, `OFFSET`, CTEs and aggregates are unaffected, so `LIMIT ... OFFSET ...` pagination works. - **Set your own limit.** It is faster, and a `false` on `truncated` is your proof nothing was cut. - There is also a 1MB response ceiling. ### Response format: `json`, `csv` or `tsv` - Pass `format` in the JSON body, or as a query parameter on a GET. The default is `json`. - `json` returns `columns` plus `rows` as arrays. - `csv` and `tsv` return the rows as a plain file with a header row, ready to save or read straight into a dataframe. There is no JSON to unwrap. - Any other value is treated as `json`. On `csv` and `tsv` the row count and the truncation flag move to response headers, because they cannot sit in the file without breaking it. Read `X-Row-Count` and `X-Truncated`. A `true` there means the row cap cut your result, and the `Link` header points at the bulk download. Errors are always `json`, whatever format you asked for. So check the status code first: a 200 is your data in the format you requested, and anything else is a JSON body explaining why. `tsv` changed on 15 September 2026. It used to return JSON with the tab-separated text inside a `data` field. It now returns the file itself. ### Time limit: 30s for the query, 45s worst case for the request The extra 15s is time spent waiting for a free slot when the service is busy. If you disconnect, the query does not stop, because the database cannot tell you left, so it runs to its 30s budget. Retrying immediately stacks work rather than replacing it. ### Two engines, two dialects `/v1/query/postgres` is PostgreSQL and `/v1/query/duckdb` is DuckDB. **They hold the same tables** - `ball_by_ball`, `match_info`, `match_players`, `people` and a view for each slice - with the same rows and the same columns, so the endpoint you choose is a choice of SQL DIALECT and nothing else. Ask either one the same question and you get the same answer. The dialects are **not** interchangeable, and that is the only thing that differs. Each supports functions and syntax the other does not: `QUALIFY` works on DuckDB and fails on Postgres, and date and string functions differ. So if a query works on one endpoint and fails on the other, **it is the dialect, not the table** - the table is present on both. Standard `SELECT`, `JOIN`, `GROUP BY`, CTEs, window functions and `FILTER` work on both. ## When a query fails ### Read the SQL we quote back to you Every error repeats the SQL exactly as we received it. Compare that against what you sent. If the two differ, the problem is not your SQL, it is what happened to it on the way here, and the next section is for you. This one check settles most failures in seconds. ### The URL can change your SQL without telling you This affects GET only, where the query travels inside the URL and your client has to percent-encode it. When that encoding is incomplete, the SQL that arrives is not the SQL you wrote. Three real examples, all from live traffic: | You wrote | We received | Cause | |---|---|---| | `SUM(runs_off_bat + extras)` | `SUM(runs_off_bat extras)` | A bare `+` in a URL means space. Send `%2B` | | `... AS runs FROM ball_by_ball` | `... AS runsFROM ball_by_ball` | A line break was swallowed | | `SELECT gender, COUNT(*) FROM ...` | `SELECT gender, COUNT` | The encoder gave up at `(` | In all three cases the caller was looking at correct SQL and being told it was invalid. **The fix for all of them is the same: send the query as a POST with a JSON body.** ```bash curl -X POST "https://db-mcp.tigzig.com/v1/query/duckdb" \ -H "Content-Type: application/json" \ -d '{"sql": "SELECT COUNT(*) AS balls FROM ball_by_ball", "format": "json"}' ``` A JSON body has no percent-encoding step to get wrong, and the length limit is measured on the decoded SQL, so a query that is over the limit as a GET may be comfortably under it as a POST. GET stays fully supported and is the right choice from a browser or a no-code HTTP node. If you use it, percent-encode the whole query, including `+` as `%2B`. ### Which error you got tells you where to look - `SQL_REJECTED` means this endpoint refused the query before running it. The reason is in the message, and everything we refuse is listed under SQL guardrails above. - `SQL_ERROR` means the database itself rejected it. That is an ordinary SQL problem: a wrong column, an ambiguous reference, something missing from `GROUP BY`. The database's own message is passed through unchanged. ### One note on retrying If a query is slow, wait for it to come back rather than firing it again. Disconnecting does not stop it, because the database cannot tell you left, so a repeat stacks work on top of the first attempt instead of replacing it. ## Data semantics Each row is a single delivery (ball) in a match, in whichever ball table you queried. The shape of a match follows the FORMAT, not the endpoint: an ODI is 50 overs per innings and a T20 is 20, usually 2 innings each. Both tables are on both engines, so pick the table for the format you want. ### Ball counting The ball field (e.g. 0.1, 7.5) is an over.ball identifier, NOT a sequential count. Overs may have >6 deliveries due to wides/no-balls (e.g. 0.7). Use COUNT(*) for total balls bowled. **To get the over number, use `FLOOR(ball)`, not `CAST(ball AS INTEGER)`.** The field is stored as a floating-point number, and a cast to integer ROUNDS rather than truncates on both engines: `CAST(19.5 AS INTEGER)` is 20, while `FLOOR(19.5)` is 19. A cast therefore moves every delivery from x.5 onwards into the next over. Nothing errors when this happens - the query succeeds and the totals look plausible - and it distorts precisely the deliveries at the end of an innings. `MAX(FLOOR(ball))` gives the last over bowled and `COUNT(DISTINCT FLOOR(ball))` the number of overs. ### Runs `runs_off_bat` is runs scored by batsman. extras = additional runs (wides, no-balls, byes, legbyes, penalty). Total runs for a delivery = runs_off_bat + extras. NULL extras components should be treated as 0. ### Wickets Check both wicket_type AND other_wicket_type for dismissals. If either is non-null, that delivery has a dismissal. Common wicket_type values: bowled, caught, lbw, run out, stumped, caught and bowled, hit wicket, retired hurt. ### Player names Use the exact full name if known. If uncertain, use surname with LIKE wildcards (e.g. WHERE striker LIKE '%Kohli%'). ### Season format Can be a year (2023) or split-year (2023/24) for southern hemisphere seasons. ### match_type Scoped to the TABLE, not to the endpoint. Every row carries its own `match_type`, on BOTH engines - so filtering a ball table by match_type is always a no-op. It is only meaningful on `match_info`, which carries all three formats including TEST. ### Example query ```sql SELECT striker, SUM(runs_off_bat) as runs, COUNT(*) as balls FROM ball_by_ball WHERE season = '2023' GROUP BY striker ORDER BY runs DESC LIMIT 10 ``` Part of Tigzig: free interactive tools, open-source repos, APIs and MCP servers for analytics and live data across global and Indian markets, macro indicators and filings. Catalog: https://api.tigzig.com/.well-known/api-catalog Guide: https://www.tigzig.com/llms.txt

Known tools 3

query_postgres

Run read-only SQL against the cricket data, PostgreSQL dialect.

Inferred read-only
query_duckdb

Run read-only SQL against the cricket data, DuckDB dialect.

Inferred read-only
health_check

Health check Returns service status, version, and connectivity to both databases.

Inferred read-only

CONNECT WITH APPROVAL

Client installation

Review this server and its permissions before adding it. Secret placeholders must be set locally.

Codex

~/.codex/config.toml

[mcp_servers.database-mcp-server]
url = "https://db-mcp.tigzig.com/mcp"
enabled = true
Claude Code

.mcp.json

{
  "mcpServers": {
    "database-mcp-server": {
      "type": "http",
      "url": "https://db-mcp.tigzig.com/mcp"
    }
  }
}
Claude Desktop

Settings → Connectors → Add custom connector

Name: database-mcp-server
Remote MCP URL: https://db-mcp.tigzig.com/mcp

Add this remote URL as a custom connector in Claude Desktop. Availability depends on the user plan and workspace policy.

Cursor

.cursor/mcp.json

{
  "mcpServers": {
    "database-mcp-server": {
      "url": "https://db-mcp.tigzig.com/mcp"
    }
  }
}
Visual Studio Code

.vscode/mcp.json

Add to Visual Studio Code
{
  "servers": {
    "database-mcp-server": {
      "type": "http",
      "url": "https://db-mcp.tigzig.com/mcp"
    }
  }
}
Generic MCP

Client-specific MCP configuration

{
  "name": "database-mcp-server",
  "transport": "streamable-http",
  "url": "https://db-mcp.tigzig.com/mcp"
}
MCP Inspector

Run the official MCP Inspector locally and enter the indexed Streamable HTTP endpoint.

TRUST AND VERIFICATION EVIDENCE

Trust Data Available

BuiltWith Trust API v2 evidence for tigzig.com was fetched 2026-08-08T22:09:38.481Z and is being refreshed.

Trust status RestrictedContent

tigzig.com is assessed as RestrictedContent: Domain runs gambling-related technology.

Indexed

Evidence is source-attributed and does not guarantee that a third-party server is safe. Risk labels are conservative metadata heuristics.