← Back to Gemini docs

Gemini — Tool Reference

Query Gemini prediction market odds, orderbooks, contract volume, and price history

11 tools · auto-generated from the deployed toolkit

Your AI assistant discovers and invokes these tools through the MCP Server's meta tools — it calls execute_tool with the tool name and arguments below.

# get_event_details read

Read one Gemini prediction-market event and all its contracts by event ticker.

Full description
Get a single Gemini event with its full contract list.

Keyed on the EVENT TICKER (e.g. 'FED260917') from search_markets. Gemini
returns 404 for an event id, slug, contract ticker, or instrumentSymbol —
this tool reports that as NOT_FOUND with the same explanation rather than
guessing an alternate lookup.

Works for events in any status: active, approved, under_review, and
settled all return successfully.

Unlike search_markets, `live_only` defaults to FALSE so you see settled and
closed contracts alongside live ones. Every contract carries `is_live` —
true only when event status is active, contract status is active,
marketState is open, and expiryDate is in the future — plus
`not_live_reason` naming the first failing check. The event's own
`is_in_play` is Gemini's in-play marker, NOT a rollup of those contract
verdicts — an event can report is_in_play false with every contract live;
`live_contract_count` is the rollup. Prices are quote/last-trade
data as YES probabilities from 0.00 to 1.00; a contract that has never
traded reports last_trade_price = null and is never given a bid/ask midpoint
in its place.

A resolved contract reports `result` (observed only as 'yes' or 'no', but
any other side Gemini sends is passed through lowercased, so treat an
unrecognized value as unknown rather than as a no), `resolved_at`, and —
only where the outcome is numeric — `settlement_value`, the reference value
the outcome was decided against in the underlying's own units (e.g. a BTC
index price), never a probability. Since every quote field is null once a
contract settles, `result` is the only outcome signal; a settled sports
contract has a result and no settlement_value. The event carries its own
`settlement_value` once the whole event settles numerically. Answering
"which outcome won?" needs live_only=false, the default here.

`volume` and `volume_24h` are EXECUTED CONTRACT QUANTITY, never USD
notional, and may be fractional — contracts whose quantityIncrement is 0.01
trade in hundredths, so a volume of 30.59 is ordinary rather than a unit
error. `volume_delta_24h_pct` is a percentage and carries no units.

`best_bid` / `best_ask` / `spread` / `midpoint` are live YES-side quote data
in probability terms, 0.0-1.0. `spread` and `midpoint` are null whenever
either side of the book is missing — never synthesized from one side or from
the last trade. Because all three venues quote in the same 0-1 probability
terms, these fields ARE directly comparable across the kalshi, polymarket
and gemini toolkits — unlike the volume fields, whose units differ by venue.

Gemini-specific caveat: a null `best_bid` / `best_ask` here means the bulk
event summary did not publish that side as a usable number — NOT that the
direct order book is empty — and a null `spread` / `midpoint` likewise
reports an incomplete SUMMARY quote rather than a one-sided book. The
`buy_*` / `sell_*` fields are raw upstream indicative values that can simply
mirror the last trade. When an executable quote matters, confirm with
get_orderbook — the direct book, which is authoritative but costs one
request per contract.

Pass any contract's `instrument_symbol` to get_market_snapshot,
get_market_history, get_market_changes, get_orderbook, or get_recent_trades.
Parameter Type Required Description
event_ticker string Yes Gemini EVENT ticker from search_markets, e.g. 'FED260917'. Case-insensitive. Event ids, slugs, contract tickers and instrumentSymbols are all rejected with NOT_FOUND — this endpoint keys on the event ticker only.
live_only boolean No When true, return only contracts passing the four-condition liveness check. Defaults to FALSE here so you can see settled and closed siblings; every contract carries is_live and not_live_reason either way. Default: false
max_contracts integer | null No Optional cap on contracts returned. Null (the default) returns all of them.
Parameter schema (JSON)
{
  "description": "Parameters for get_event_details tool.",
  "properties": {
    "event_ticker": {
      "description": "Gemini EVENT ticker from search_markets, e.g. 'FED260917'. Case-insensitive. Event ids, slugs, contract tickers and instrumentSymbols are all rejected with NOT_FOUND — this endpoint keys on the event ticker only.",
      "minLength": 1,
      "title": "Event Ticker",
      "type": "string"
    },
    "live_only": {
      "default": false,
      "description": "When true, return only contracts passing the four-condition liveness check. Defaults to FALSE here so you can see settled and closed siblings; every contract carries is_live and not_live_reason either way.",
      "title": "Live Only",
      "type": "boolean"
    },
    "max_contracts": {
      "anyOf": [
        {
          "maximum": 100,
          "minimum": 1,
          "type": "integer"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Optional cap on contracts returned. Null (the default) returns all of them.",
      "title": "Max Contracts"
    }
  },
  "required": [
    "event_ticker"
  ],
  "title": "GetEventDetailsParams",
  "type": "object",
  "additionalProperties": false
}
Result schema (JSON)
{
  "$defs": {
    "GeminiContractSummary": {
      "description": "One tradable contract (outcome) inside a Gemini event.",
      "properties": {
        "instrument_symbol": {
          "description": "Gemini instrumentSymbol, e.g. 'GEMI-FED260917-MAINTAIN'. This is the market_id used by get_market_snapshot, get_market_history, get_market_changes, get_orderbook and get_recent_trades. Case-sensitive when passed to the analytics tools.",
          "title": "Instrument Symbol",
          "type": "string"
        },
        "contract_id": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini internal contract id, e.g. '168090-404860'. Not accepted by any other tool in this toolkit.",
          "title": "Contract Id"
        },
        "label": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Human-readable outcome label, e.g. 'Fed maintains rate'",
          "title": "Label"
        },
        "ticker": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Short contract ticker within the event, e.g. 'MAINTAIN'",
          "title": "Ticker"
        },
        "status": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini contract status, e.g. 'active' or 'settled'",
          "title": "Status"
        },
        "market_state": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini marketState: 'open', 'post_only', or 'closed'. 'post_only' contracts are not tradable even when status is 'active'.",
          "title": "Market State"
        },
        "expiry_date": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "ISO 8601 contract expiry (UTC)",
          "title": "Expiry Date"
        },
        "effective_date": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "ISO 8601 date the contract became effective (UTC)",
          "title": "Effective Date"
        },
        "is_live": {
          "description": "True only when event status is 'active' AND contract status is 'active' AND market_state is 'open' AND expiry_date is in the future. A missing or unparseable expiry_date makes this False. Computed here from those four upstream signals — Gemini publishes no per-contract liveness flag of its own. This is the ONLY field the live_only filter reads. Do not confuse it with GeminiEventSummary.is_in_play, Gemini's event-level in-play flag, which is an unrelated concept and is never consulted by this check.",
          "title": "Is Live",
          "type": "boolean"
        },
        "not_live_reason": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "When is_live is False, the first failing check: 'event_status', 'contract_status', 'market_state', or 'expiry'. None when is_live is True.",
          "title": "Not Live Reason"
        },
        "is_combo": {
          "anyOf": [
            {
              "type": "boolean"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Whether this is a combination contract",
          "title": "Is Combo"
        },
        "result": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Settlement side, lowercased from Gemini's resolutionSide. The only values observed are 'yes' and 'no' (measured over 1000 settled and 200 active events), but this is NOT a closed set: any other side Gemini sends — a void or cancellation, say — is passed through lowercased rather than dropped. So do not branch `== 'yes'` with an else that means no; test for 'yes' and 'no' explicitly and treat any other value as unknown rather than as a loss. Null means Gemini supplied no side, which is usually an unresolved contract but is not proof of one — `resolved_at` is read independently and can be set while this is null. Settled contracts have been observed carrying no usable price fields at all, leaving this the only outcome signal — that is Gemini's observed habit rather than an invariant this toolkit enforces, so read the price fields for what they are instead of inferring settlement from them. Lowercase matches the kalshi toolkit's `result`; the polymarket toolkit capitalizes its own ('Yes'/'No'), so never compare the two case-sensitively. A contract can resolve while its event is still 'active', so a non-null result does not imply the event has settled.",
          "title": "Result"
        },
        "resolved_at": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "ISO 8601 timestamp (UTC) at which Gemini resolved this contract; null when Gemini supplied no timestamp. Read independently of `result`, so neither implies the other: a contract can carry this with a null result (Gemini omitted the side) or a result with no timestamp. A contract that resolves early inside a still-active event carries both as soon as Gemini publishes them.",
          "title": "Resolved At"
        },
        "settlement_value": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Reference value the outcome was settled against, expressed in the UNDERLYING's own units — e.g. 63306.96 for a BTC price market. This is NOT a probability in 0.0-1.0 like the price fields below, and NOT a contract quantity like the volume fields. Null unless Gemini published one: always null while the contract is unresolved, and also null on resolved contracts whose outcome is not numeric — a settled sports contract has a result and no settlement_value. Gemini sends it as a string; a value that will not parse as a number is reported as null.",
          "title": "Settlement Value"
        },
        "best_bid": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Best bid as a YES probability 0.0-1.0. Quote data, not an executed price. Null means Gemini's bulk event summary published no usable bid — the field was absent, or its value would not parse as a number — and it does NOT prove the direct order book has no bids: contracts have been observed (2026-08-14) with both sides null here while get_orderbook returned resting orders. The direct book from get_orderbook is authoritative.",
          "title": "Best Bid"
        },
        "best_ask": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Best ask as a YES probability 0.0-1.0. Quote data, not an executed price. Null means Gemini's bulk event summary published no usable ask — the field was absent, or its value would not parse as a number — and it does NOT prove the direct order book has no asks: contracts have been observed (2026-08-14) with both sides null here while get_orderbook returned resting orders. The direct book from get_orderbook is authoritative.",
          "title": "Best Ask"
        },
        "spread": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "best_ask - best_bid, rounded to 4 decimals. Null unless Gemini's bulk event summary published BOTH sides as usable numbers — never derived from one side, from a last trade, or from the buy_* / sell_* indicative values. A null therefore says the summary carried an incomplete quote; it does NOT prove the direct order book is one-sided or empty, since contracts have been observed (2026-08-14) with both summary sides null while get_orderbook returned resting orders.",
          "title": "Spread"
        },
        "midpoint": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "(best_bid + best_ask) / 2, rounded to 4 decimals. Null unless Gemini's bulk event summary published BOTH sides as usable numbers — never derived from one side, from a last trade, or from the buy_* / sell_* indicative values. A null therefore says the summary carried an incomplete quote; it does NOT prove the direct order book is one-sided or empty, since contracts have been observed (2026-08-14) with both summary sides null while get_orderbook returned resting orders.",
          "title": "Midpoint"
        },
        "last_trade_price": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Last executed YES probability 0.0-1.0, as carried by Gemini's bulk event summary. Null when the summary carried none, or carried a value that would not parse as a number — usually a never-traded contract, but the field reports what the summary published, not trading history. Never replaced by a bid/ask midpoint.",
          "title": "Last Trade Price"
        },
        "buy_yes": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini's raw indicative price to buy YES, 0.0-1.0, passed through unchanged. NOT a resting quote and not guaranteed executable: on contracts whose bulk summary omits bestBid/bestAsk, Gemini has been observed (2026-08-14) publishing this equal to last_trade_price. Never used to derive spread or midpoint. For an executable price, read the direct book via get_orderbook.",
          "title": "Buy Yes"
        },
        "buy_no": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini's raw indicative price to buy NO, 0.0-1.0, passed through unchanged. NOT a resting quote and not guaranteed executable: on contracts whose bulk summary omits bestBid/bestAsk, Gemini has been observed (2026-08-14) publishing this equal to last_trade_price. Never used to derive spread or midpoint. For an executable price, read the direct book via get_orderbook.",
          "title": "Buy No"
        },
        "sell_yes": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini's raw indicative price to sell YES, 0.0-1.0, passed through unchanged. NOT a resting quote and not guaranteed executable: on contracts whose bulk summary omits bestBid/bestAsk, Gemini has been observed (2026-08-14) publishing this equal to last_trade_price. Never used to derive spread or midpoint. For an executable price, read the direct book via get_orderbook.",
          "title": "Sell Yes"
        },
        "sell_no": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini's raw indicative price to sell NO, 0.0-1.0, passed through unchanged. NOT a resting quote and not guaranteed executable: on contracts whose bulk summary omits bestBid/bestAsk, Gemini has been observed (2026-08-14) publishing this equal to last_trade_price. Never used to derive spread or midpoint. For an executable price, read the direct book via get_orderbook.",
          "title": "Sell No"
        }
      },
      "required": [
        "instrument_symbol",
        "is_live"
      ],
      "title": "GeminiContractSummary",
      "type": "object"
    },
    "GeminiEventSummary": {
      "description": "A Gemini prediction-market event and its contracts.",
      "properties": {
        "event_ticker": {
          "description": "Event ticker, e.g. 'FED260917'. This is the ONLY identifier get_event_details accepts.",
          "title": "Event Ticker",
          "type": "string"
        },
        "event_id": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini internal event id. Not accepted by any tool in this toolkit.",
          "title": "Event Id"
        },
        "title": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Event title, e.g. 'Fed decision in September?'",
          "title": "Title"
        },
        "slug": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "URL slug. Not accepted by any tool in this toolkit.",
          "title": "Slug"
        },
        "event_type": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "'binary' (two outcomes) or 'categorical' (many outcomes)",
          "title": "Event Type"
        },
        "category": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini-native category, e.g. 'Economics'. Use list_categories for the accepted values. These are NOT the same vocabulary as list_mover_categories.",
          "title": "Category"
        },
        "subcategory": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini subcategory name, e.g. 'Fed'",
          "title": "Subcategory"
        },
        "subcategory_path": {
          "description": "Subcategory breadcrumb, e.g. ['Economics', 'Fed']",
          "items": {
            "type": "string"
          },
          "title": "Subcategory Path",
          "type": "array"
        },
        "status": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini event status: 'active', 'approved', 'under_review', or 'settled'",
          "title": "Status"
        },
        "is_in_play": {
          "anyOf": [
            {
              "type": "boolean"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini's own IN-PLAY flag for the event, passed through from raw `isLive` unchanged and never computed here. It marks short-interval and currently-playing markets — an in-progress ball game, a next-hour crypto strike. It is NOT a tradability signal, NOT the contract lifecycle check, and no filter in this toolkit reads it: an event with is_in_play false routinely holds live, tradable contracts and is returned by live_only=true. For tradability read live_contract_count on this event, or GeminiContractSummary.is_live on each contract. Null when Gemini omits the flag. (Named is_live before 2026-08; that name collided with the per-contract field and is no longer emitted.)",
          "title": "Is In Play"
        },
        "settlement_value": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Event-level settlement reference value, from Gemini's settlement.value, in the UNDERLYING's own units (e.g. a BTC index price) — not a probability and not a contract quantity. Null until the whole event settles, and null even then when the outcome is not numeric: Gemini sends an empty settlement block for sports events. Per-contract outcomes are on GeminiContractSummary.result / .settlement_value, which appear as soon as a contract resolves rather than waiting for the event.",
          "title": "Settlement Value"
        },
        "description": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Event description as plain text",
          "title": "Description"
        },
        "tags": {
          "description": "Free-form Gemini event tags. Not usable as a filter by any tool here.",
          "items": {
            "type": "string"
          },
          "title": "Tags",
          "type": "array"
        },
        "volume": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini-reported total event volume in EXECUTED CONTRACT QUANTITY (contract quantity, which MAY BE FRACTIONAL — contracts whose quantityIncrement is 0.01 trade in hundredths), never USD notional. Reported by Gemini across all contracts in the event; it is collected independently of the per-contract volume returned by get_market_snapshot and the two are not reconciled.",
          "title": "Volume"
        },
        "volume_24h": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini-reported trailing 24h event volume in EXECUTED CONTRACT QUANTITY, which may be fractional, never USD notional",
          "title": "Volume 24H"
        },
        "volume_delta_24h_pct": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini-reported 24h percentage change in event contract volume",
          "title": "Volume Delta 24H Pct"
        },
        "expiry_date": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "ISO 8601 event expiry (UTC)",
          "title": "Expiry Date"
        },
        "effective_date": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "ISO 8601 date the event became effective (UTC)",
          "title": "Effective Date"
        },
        "created_at": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "ISO 8601 creation timestamp (UTC)",
          "title": "Created At"
        },
        "terms_link": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "URL to the event's terms",
          "title": "Terms Link"
        },
        "contracts": {
          "description": "Contracts returned for this event, in Gemini's own order (never re-sorted). May be filtered by live_only and capped by max_contracts_per_event.",
          "items": {
            "$ref": "#/$defs/GeminiContractSummary"
          },
          "title": "Contracts",
          "type": "array"
        },
        "contract_count": {
          "default": 0,
          "description": "Total contracts Gemini returned for this event, BEFORE any live_only filter or max_contracts_per_event cap",
          "title": "Contract Count",
          "type": "integer"
        },
        "live_contract_count": {
          "default": 0,
          "description": "How many of those contracts pass the four-condition liveness check, BEFORE the max_contracts_per_event cap. This — not is_in_play — is the event-level answer to 'is anything here tradable right now?', and it is what live_only filters on.",
          "title": "Live Contract Count",
          "type": "integer"
        },
        "contracts_truncated": {
          "default": false,
          "description": "True when max_contracts_per_event dropped contracts that would otherwise have been returned",
          "title": "Contracts Truncated",
          "type": "boolean"
        }
      },
      "required": [
        "event_ticker"
      ],
      "title": "GeminiEventSummary",
      "type": "object"
    }
  },
  "description": "Result of get_event_details tool.",
  "properties": {
    "success": {
      "title": "Success",
      "type": "boolean"
    },
    "event": {
      "anyOf": [
        {
          "$ref": "#/$defs/GeminiEventSummary"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "The event and its contracts; null on failure"
    },
    "error": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Error message if operation failed",
      "title": "Error"
    }
  },
  "required": [
    "success"
  ],
  "title": "GetEventDetailsResult",
  "type": "object"
}

# get_market_changes read

Compute price and contract-volume change over 1h/24h/7d/30d for one Gemini market.

Full description
Get price and volume change over named windows for one Gemini contract.

Takes the instrumentSymbol directly as the market id, URL-encoded into the
request path. `windows` is a comma-separated subset of 1h, 24h, 7d, 30d,
sent as a single parameter — the upstream API keeps only the last value of
a repeated parameter, so a list form would silently lose windows.

open and close are EXECUTED YES probabilities from 0.00 to 1.00.
volume_24h_abs and volume_24h_pct describe trailing EXECUTED CONTRACT
QUANTITY, never USD notional. That quantity may be fractional, so
volume_24h_abs is routinely non-integer on markets Gemini quotes with a
quantityIncrement of 0.01.

A window with samples = 0 and null price fields is a VALID result, not an
error: it means the market has no sufficient, gap-free retained history for
that horizon. Gemini ingestion is comparatively young, so 7d and 30d
windows are frequently null for markets whose 1h and 24h windows are
populated.

The upstream endpoint answers an unknown symbol with all-null windows
rather than a 404, so an all-null result does not distinguish "no data yet"
from "wrong symbol". When every requested window returns 0 samples, `note`
says exactly that; call get_market_snapshot, which does return NOT_FOUND,
to tell the two apart.
Parameter Type Required Description
market_id string Yes Gemini instrumentSymbol, e.g. 'GEMI-FED260917-MAINTAIN'. Case-sensitive.
windows string No Comma-separated subset of: 1h, 24h, 7d, 30d. A single comma-separated string, not a list. Default: "1h,24h,7d,30d"
Parameter schema (JSON)
{
  "description": "Parameters for get_market_changes tool.",
  "properties": {
    "market_id": {
      "description": "Gemini instrumentSymbol, e.g. 'GEMI-FED260917-MAINTAIN'. Case-sensitive.",
      "minLength": 1,
      "title": "Market Id",
      "type": "string"
    },
    "windows": {
      "default": "1h,24h,7d,30d",
      "description": "Comma-separated subset of: 1h, 24h, 7d, 30d. A single comma-separated string, not a list.",
      "title": "Windows",
      "type": "string"
    }
  },
  "required": [
    "market_id"
  ],
  "title": "GetMarketChangesParams",
  "type": "object",
  "additionalProperties": false
}
Result schema (JSON)
{
  "$defs": {
    "WindowChange": {
      "description": "Price and volume change over one named window.",
      "properties": {
        "window": {
          "description": "One of: 1h, 24h, 7d, 30d",
          "title": "Window",
          "type": "string"
        },
        "samples": {
          "description": "Number of retained observation buckets in the window. 0 means no usable data for this window.",
          "title": "Samples",
          "type": "integer"
        },
        "open": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Executed YES probability at the window start, 0.0-1.0",
          "title": "Open"
        },
        "close": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Executed YES probability at the window end, 0.0-1.0",
          "title": "Close"
        },
        "price_abs": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "close - open, in probability points",
          "title": "Price Abs"
        },
        "price_pct": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "100 * (close - open) / open",
          "title": "Price Pct"
        },
        "volume_24h_abs": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Change in trailing 24h EXECUTED CONTRACT QUANTITY over the window, which may be fractional, never USD notional",
          "title": "Volume 24H Abs"
        },
        "volume_24h_pct": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Percentage change in trailing 24h EXECUTED CONTRACT QUANTITY over the window",
          "title": "Volume 24H Pct"
        }
      },
      "required": [
        "window",
        "samples"
      ],
      "title": "WindowChange",
      "type": "object"
    }
  },
  "description": "Result of get_market_changes tool.",
  "properties": {
    "success": {
      "title": "Success",
      "type": "boolean"
    },
    "market_id": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Market Id"
    },
    "windows": {
      "items": {
        "$ref": "#/$defs/WindowChange"
      },
      "title": "Windows",
      "type": "array"
    },
    "volume_units": {
      "default": "contracts",
      "description": "Units for the volume_24h fields. Always 'contracts' — a divisible quantity, not a whole-contract count.",
      "title": "Volume Units",
      "type": "string"
    },
    "note": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Set when every requested window came back with 0 samples",
      "title": "Note"
    },
    "error": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Error"
    }
  },
  "required": [
    "success"
  ],
  "title": "GetMarketChangesResult",
  "type": "object"
}

# get_market_history read

Read stored OHLC price history and per-bucket contract volume for one Gemini market.

Full description
Get stored OHLC history for one Gemini contract.

Takes the instrumentSymbol directly as the market id, URL-encoded into the
request path. Points come back ascending by timestamp in 5m, 1h, or 1d
buckets — the only grains stored.

open / high / low / close are EXECUTED YES probabilities from 0.00 to 1.00,
built from real trades and Gemini's canonical candles. `volume` is the
EXECUTED CONTRACT QUANTITY traded in that bucket and `volume_24h` is the
trailing 24-hour contract quantity; neither is USD notional, and this tool
never derives one by multiplying price by quantity. Both may be fractional
— Gemini quotes a quantityIncrement of 0.01 on many markets — so a bucket
volume of 30.59 is a real size, not a units mismatch.

The first returned point routinely has volume = null, because per-bucket
volume is a difference against the previous cumulative observation and
there is none before the window starts. That is expected data, not an error.

This makes exactly ONE request per call for at most `limit` points, whose
ceiling is 2000 — it does not loop or auto-paginate.
When older points exist before the first one returned, `truncated` is true
and `continue_from` holds that first point's timestamp; re-invoke with
`end_ts = continue_from` to walk backwards. That boundary bucket appears in
both pages.

A market with no stored history returns success with an empty `points` list
and a note. The stored API answers with an empty series rather than a 404
for an unknown symbol, so an empty result does not by itself prove the
symbol is wrong — use get_market_snapshot, which does return NOT_FOUND, to
check that.

Gemini's own public history reaches back only seven days, so buckets older
than Toolforest's retained coverage for this symbol will not exist.
Parameter Type Required Description
market_id string Yes Gemini instrumentSymbol, e.g. 'GEMI-FED260917-MAINTAIN'. Case-sensitive.
bucket "5m" | "1h" | "1d" No Bucket size. These three are the only grains stored; there is no finer resolution. Default: "1h"
start_ts integer | string | null No Window start. Unix seconds or ISO 8601, e.g. 1786400000, '2026-08-04' or '2026-08-04T00:00:00Z'. Defaults by bucket: 24h back for 5m, 7 days for 1h, 90 days for 1d.
end_ts integer | string | null No Window end. Unix seconds or ISO 8601. Defaults to now. Use continue_from from a truncated response to page backwards.
limit integer No Maximum points to return in one call, and also this tool's own per-call ceiling, which is 2000. The stored API would accept 5000, but a 5000-point response does not fit this tool's size budget, so 2000 is the largest value it will take rather than a value it would quietly reduce. Use truncated / continue_from to page back further. Default: 2000
Parameter schema (JSON)
{
  "description": "Parameters for get_market_history tool.",
  "properties": {
    "market_id": {
      "description": "Gemini instrumentSymbol, e.g. 'GEMI-FED260917-MAINTAIN'. Case-sensitive.",
      "minLength": 1,
      "title": "Market Id",
      "type": "string"
    },
    "bucket": {
      "default": "1h",
      "description": "Bucket size. These three are the only grains stored; there is no finer resolution.",
      "enum": [
        "5m",
        "1h",
        "1d"
      ],
      "title": "Bucket",
      "type": "string"
    },
    "start_ts": {
      "anyOf": [
        {
          "type": "integer"
        },
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Window start. Unix seconds or ISO 8601, e.g. 1786400000, '2026-08-04' or '2026-08-04T00:00:00Z'. Defaults by bucket: 24h back for 5m, 7 days for 1h, 90 days for 1d.",
      "title": "Start Ts"
    },
    "end_ts": {
      "anyOf": [
        {
          "type": "integer"
        },
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Window end. Unix seconds or ISO 8601. Defaults to now. Use continue_from from a truncated response to page backwards.",
      "title": "End Ts"
    },
    "limit": {
      "default": 2000,
      "description": "Maximum points to return in one call, and also this tool's own per-call ceiling, which is 2000. The stored API would accept 5000, but a 5000-point response does not fit this tool's size budget, so 2000 is the largest value it will take rather than a value it would quietly reduce. Use truncated / continue_from to page back further.",
      "maximum": 2000,
      "minimum": 1,
      "title": "Limit",
      "type": "integer"
    }
  },
  "required": [
    "market_id"
  ],
  "title": "GetMarketHistoryParams",
  "type": "object",
  "additionalProperties": false
}
Result schema (JSON)
{
  "$defs": {
    "HistoryPoint": {
      "description": "One stored OHLC bucket.",
      "properties": {
        "ts": {
          "description": "ISO 8601 bucket start (UTC)",
          "title": "Ts",
          "type": "string"
        },
        "open": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "First executed YES probability in the bucket, 0.0-1.0",
          "title": "Open"
        },
        "high": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Highest executed YES probability in the bucket, 0.0-1.0",
          "title": "High"
        },
        "low": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Lowest executed YES probability in the bucket, 0.0-1.0",
          "title": "Low"
        },
        "close": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Last executed YES probability in the bucket, 0.0-1.0",
          "title": "Close"
        },
        "volume": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "EXECUTED CONTRACT QUANTITY traded during this bucket, which may be fractional, never USD notional. Null on the FIRST returned point when no prior cumulative observation is available to difference against — this is expected, not an error.",
          "title": "Volume"
        },
        "volume_24h": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Trailing 24h EXECUTED CONTRACT QUANTITY as of the latest execution in this bucket, which may be fractional, never USD notional",
          "title": "Volume 24H"
        }
      },
      "required": [
        "ts"
      ],
      "title": "HistoryPoint",
      "type": "object"
    }
  },
  "description": "Result of get_market_history tool.",
  "properties": {
    "success": {
      "title": "Success",
      "type": "boolean"
    },
    "market_id": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Market Id"
    },
    "bucket": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Bucket"
    },
    "since": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "ISO 8601 window start actually requested",
      "title": "Since"
    },
    "until": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "ISO 8601 window end actually requested",
      "title": "Until"
    },
    "points": {
      "description": "Buckets ascending by ts",
      "items": {
        "$ref": "#/$defs/HistoryPoint"
      },
      "title": "Points",
      "type": "array"
    },
    "count": {
      "default": 0,
      "title": "Count",
      "type": "integer"
    },
    "volume_units": {
      "default": "contracts",
      "description": "Units for volume and volume_24h. Always 'contracts' — a divisible quantity, not a whole-contract count.",
      "title": "Volume Units",
      "type": "string"
    },
    "truncated": {
      "default": false,
      "description": "True when older points exist before the first returned point",
      "title": "Truncated",
      "type": "boolean"
    },
    "continue_from": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "When truncated, the ts of the FIRST (oldest) returned point. Re-invoke with end_ts = continue_from to fetch older data; that boundary bucket will be returned again in the next page.",
      "title": "Continue From"
    },
    "note": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Note"
    },
    "error": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Error"
    }
  },
  "required": [
    "success"
  ],
  "title": "GetMarketHistoryResult",
  "type": "object"
}

# get_market_snapshot read

Read the latest stored price and contract volume for one Gemini market.

Full description
Get the latest stored Toolforest snapshot for one Gemini contract.

Takes the instrumentSymbol from search_markets or get_event_details and
uses it directly as the market id — Gemini needs no token or id
translation. The symbol is case-sensitive here and is URL-encoded into the
request path.

`price` is the latest EXECUTED YES probability from 0.00 to 1.00, sourced
from real trades and canonical candles. `volume` and `volume_24h` are
EXECUTED CONTRACT QUANTITY — the quantity of contracts traded, which may be
fractional — never USD notional, and they are per CONTRACT, not per event,
so they will be smaller than the event-level volume search_markets reports.
Gemini quotes a quantityIncrement of 0.01 on many markets, so values like
30.59 are genuine sizes rather than a units mismatch; never round them.

A market with no stored observation returns price, volume, volume_24h AND
ts all null — null meaning "nothing observed", not "observed as zero". Do
not read a null volume as no trading: it means this market has no stored
row, which is a different claim. That is valid data, not an error, and the
missing price is never replaced by a bid/ask midpoint. A symbol that has
never been stored at all returns NOT_FOUND.

(Before 2026-08-13 this path returned volume 0.0 and a fabricated ts;
market-data #83 changed both to null, which is the honest answer.)
Parameter Type Required Description
market_id string Yes Gemini instrumentSymbol, e.g. 'GEMI-FED260917-MAINTAIN'. Case-SENSITIVE: a lowercased symbol returns NOT_FOUND.
Parameter schema (JSON)
{
  "description": "Parameters for get_market_snapshot tool.",
  "properties": {
    "market_id": {
      "description": "Gemini instrumentSymbol, e.g. 'GEMI-FED260917-MAINTAIN'. Case-SENSITIVE: a lowercased symbol returns NOT_FOUND.",
      "minLength": 1,
      "title": "Market Id",
      "type": "string"
    }
  },
  "required": [
    "market_id"
  ],
  "title": "GetMarketSnapshotParams",
  "type": "object",
  "additionalProperties": false
}
Result schema (JSON)
{
  "description": "Result of get_market_snapshot tool.",
  "properties": {
    "success": {
      "title": "Success",
      "type": "boolean"
    },
    "venue": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Always 'gemini' on success",
      "title": "Venue"
    },
    "market_id": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Market Id"
    },
    "title": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Stored title, composed as '<event title>: <contract label>'",
      "title": "Title"
    },
    "category": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Toolforest canonical category, which may differ from Gemini's native category name",
      "title": "Category"
    },
    "status": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Stored lifecycle status; 'active' only when all four Gemini lifecycle conditions held at ingest",
      "title": "Status"
    },
    "ts": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "ISO 8601 timestamp of the stored observation (UTC)",
      "title": "Ts"
    },
    "price": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Latest executed YES probability 0.0-1.0. Null when the stored observation carries no usable price — a market that has never traded, a market with no stored observation at all (which reports ts, volume and volume_24h null too), or a value that would not parse as a number. Never a bid/ask midpoint.",
      "title": "Price"
    },
    "volume": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Cumulative EXECUTED CONTRACT QUANTITY for this contract, never USD notional. Per contract, not per event. May be fractional — a quantityIncrement of 0.01 makes hundredths tradable.",
      "title": "Volume"
    },
    "volume_24h": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Trailing 24h EXECUTED CONTRACT QUANTITY for this contract, which may be fractional, never USD notional",
      "title": "Volume 24H"
    },
    "volume_units": {
      "default": "contracts",
      "description": "Units for volume and volume_24h. Always 'contracts' — a divisible quantity, not a whole-contract count.",
      "title": "Volume Units",
      "type": "string"
    },
    "error": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Error"
    }
  },
  "required": [
    "success"
  ],
  "title": "GetMarketSnapshotResult",
  "type": "object"
}

# get_movers read

List Gemini markets with the largest price moves over 1h, 24h, 7d, or 30d.

Full description
Rank Gemini prediction markets by price change over a window.

Prices are EXECUTED YES probabilities from 0.00 to 1.00, so price_abs is in
probability points and price_pct is a percentage of the opening
probability. Windows are 1h, 24h, 7d, and 30d. For relative-VOLUME ranking
use get_volume_movers instead; this tool returns no volume columns because
the price ranking endpoint does not produce them.

`min_volume_24h` is in EXECUTED CONTRACT QUANTITY, not dollars. Gemini
publishes no USD notional for prediction markets. Contract quantity is
divisible — many Gemini contracts quote a quantityIncrement of 0.01 — so
the threshold and the volumes it filters on are floats, not counts.

Defaults to active markets only — pass status="" to include settled and
closed ones. Sports is excluded by default because it is roughly 3256 of
Gemini's 4400 active stored markets; pass exclude_categories=[] to include
it. Naming category or categories without also setting exclude_categories
clears that default, so an explicit include filter is never cancelled by it.
Use list_mover_categories to discover valid names — they are Toolforest's
canonical categories and differ from Gemini's native ones (Gemini's
'Weather' is 'Climate and Weather' here, 'Tech' is 'Science and
Technology'). Names are matched case-insensitively, and any value in
category, categories, or exclude_categories outside the toolkit's PINNED
vocabulary fails the call with INVALID_REQUEST listing the valid set,
rather than quietly returning nothing. Validation is offline — it never
reads the categories endpoint — so a category Gemini has newly started
carrying is rejected here until the toolkit is redeployed, even though
list_mover_categories will show it. That is deliberate: a live re-check on
the request path could consume the invocation deadline on every typo.

An empty result is VALID DATA, not a failure — a window can simply be
quiet. Non-Sports category filters DO return movers: the earlier behaviour
where every category except Sports came back empty was a market-data
service defect (toolforest-market-data#81), fixed 2026-08-13, and is no
longer expected sparseness. Long windows can still be thin, because Gemini's
retained series only go back about a week.

A mis-namespaced CATEGORY is no longer a cause either — unknown category
names are rejected before the movers request is made. But nothing else here
is vocabulary-checked, and `query` in particular is free text, so a typo
there still surfaces as an empty result.
Parameter Type Required Description
window "1h" | "24h" | "7d" | "30d" No Window to rank price moves over. Use 24h for one day. Default: "1h"
direction "up" | "down" | "both" No Rank rising prices ('up'), falling prices ('down'), or by absolute percentage move ('both'). Default: "up"
category string | null No Single canonical market-data category filter. Case-insensitive. Prefer categories for multiple values. Use list_mover_categories for valid names — these differ from Gemini's native names in list_categories. An unrecognized name fails the call with INVALID_REQUEST listing the valid set; it is not silently ignored. Commas split this into several names, so 'Politics,Crypto' is two filters and each is validated on its own.
categories array<string> | null No Canonical market-data categories to include, e.g. ['Politics', 'Economics']. Merged with category. Empty means no include filter. Case-insensitive, and an unrecognized name fails the call with INVALID_REQUEST listing the valid set.
exclude_categories array<string> | null No Categories to exclude. Defaults to ['Sports'] because Sports is 3256 of Gemini's roughly 4400 active stored markets and would otherwise dominate every broad scan. Pass [] to include Sports. Passing category or categories without also setting this clears the default so your include filter is not cancelled. Case-insensitive, and an unrecognized name fails the call with INVALID_REQUEST listing the valid set. One name per entry — each entry is sent whole, so 'Sports,Crypto' is rejected as a single unknown name.
query string | null No Keyword filter applied before ranking, matched against market title or description, e.g. 'Fed' or 'election'.
min_samples integer No Minimum retained observation buckets a market must have in the window. Defaults to 2 so a reported move spans two genuinely distinct observations — Gemini's stored series are young and a single-sample window produces a meaningless 100% move. Default: 2
min_volume_24h number No Minimum trailing 24h volume in EXECUTED CONTRACT QUANTITY, NOT dollars. Contract quantity may be fractional, so this is a float threshold, not a whole-contract count; a fractional value is accepted and sent to the ranking endpoint unchanged. Default 100 contracts; live active non-crypto Gemini contracts run roughly 180-900 contracts per 24h, so this suppresses dead markets without cutting real ones. Pass 0 to disable. Default: 100
limit integer No Maximum movers to return (1..200). Default: 20
status string | null No Filter to a single stored market status. Defaults to 'active', which excludes settled and closed markets. Pass an empty string to include all statuses. Default: "active"
Parameter schema (JSON)
{
  "description": "Parameters for get_movers tool.",
  "properties": {
    "window": {
      "default": "1h",
      "description": "Window to rank price moves over. Use 24h for one day.",
      "enum": [
        "1h",
        "24h",
        "7d",
        "30d"
      ],
      "title": "Window",
      "type": "string"
    },
    "direction": {
      "default": "up",
      "description": "Rank rising prices ('up'), falling prices ('down'), or by absolute percentage move ('both').",
      "enum": [
        "up",
        "down",
        "both"
      ],
      "title": "Direction",
      "type": "string"
    },
    "category": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Single canonical market-data category filter. Case-insensitive. Prefer categories for multiple values. Use list_mover_categories for valid names — these differ from Gemini's native names in list_categories. An unrecognized name fails the call with INVALID_REQUEST listing the valid set; it is not silently ignored. Commas split this into several names, so 'Politics,Crypto' is two filters and each is validated on its own.",
      "title": "Category"
    },
    "categories": {
      "anyOf": [
        {
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Canonical market-data categories to include, e.g. ['Politics', 'Economics']. Merged with category. Empty means no include filter. Case-insensitive, and an unrecognized name fails the call with INVALID_REQUEST listing the valid set.",
      "title": "Categories"
    },
    "exclude_categories": {
      "anyOf": [
        {
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Categories to exclude. Defaults to ['Sports'] because Sports is 3256 of Gemini's roughly 4400 active stored markets and would otherwise dominate every broad scan. Pass [] to include Sports. Passing category or categories without also setting this clears the default so your include filter is not cancelled. Case-insensitive, and an unrecognized name fails the call with INVALID_REQUEST listing the valid set. One name per entry — each entry is sent whole, so 'Sports,Crypto' is rejected as a single unknown name.",
      "title": "Exclude Categories"
    },
    "query": {
      "anyOf": [
        {
          "maxLength": 200,
          "minLength": 1,
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Keyword filter applied before ranking, matched against market title or description, e.g. 'Fed' or 'election'.",
      "title": "Query"
    },
    "min_samples": {
      "default": 2,
      "description": "Minimum retained observation buckets a market must have in the window. Defaults to 2 so a reported move spans two genuinely distinct observations — Gemini's stored series are young and a single-sample window produces a meaningless 100% move.",
      "maximum": 1000,
      "minimum": 1,
      "title": "Min Samples",
      "type": "integer"
    },
    "min_volume_24h": {
      "default": 100,
      "description": "Minimum trailing 24h volume in EXECUTED CONTRACT QUANTITY, NOT dollars. Contract quantity may be fractional, so this is a float threshold, not a whole-contract count; a fractional value is accepted and sent to the ranking endpoint unchanged. Default 100 contracts; live active non-crypto Gemini contracts run roughly 180-900 contracts per 24h, so this suppresses dead markets without cutting real ones. Pass 0 to disable.",
      "minimum": 0,
      "title": "Min Volume 24H",
      "type": "number"
    },
    "limit": {
      "default": 20,
      "description": "Maximum movers to return (1..200).",
      "maximum": 200,
      "minimum": 1,
      "title": "Limit",
      "type": "integer"
    },
    "status": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": "active",
      "description": "Filter to a single stored market status. Defaults to 'active', which excludes settled and closed markets. Pass an empty string to include all statuses.",
      "title": "Status"
    }
  },
  "title": "GetMoversParams",
  "type": "object",
  "additionalProperties": false
}
Result schema (JSON)
{
  "$defs": {
    "PriceMoverInfo": {
      "description": "One market's price change over the requested window.",
      "properties": {
        "venue": {
          "description": "Always 'gemini'",
          "title": "Venue",
          "type": "string"
        },
        "market_id": {
          "description": "Gemini instrumentSymbol — pass to get_market_snapshot, get_market_history, get_market_changes, get_orderbook, or get_recent_trades",
          "title": "Market Id",
          "type": "string"
        },
        "title": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Stored title, '<event title>: <contract label>'",
          "title": "Title"
        },
        "category": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Toolforest canonical category",
          "title": "Category"
        },
        "status": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Status"
        },
        "samples": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Retained observation buckets used for this window",
          "title": "Samples"
        },
        "open": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Executed YES probability at window start, 0.0-1.0",
          "title": "Open"
        },
        "close": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Executed YES probability at window end, 0.0-1.0",
          "title": "Close"
        },
        "price_abs": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "close - open, in probability points",
          "title": "Price Abs"
        },
        "price_pct": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "100 * (close - open) / open",
          "title": "Price Pct"
        }
      },
      "required": [
        "venue",
        "market_id"
      ],
      "title": "PriceMoverInfo",
      "type": "object"
    }
  },
  "description": "Result of get_movers tool.",
  "properties": {
    "success": {
      "title": "Success",
      "type": "boolean"
    },
    "window": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Window"
    },
    "direction": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Direction"
    },
    "limit": {
      "anyOf": [
        {
          "type": "integer"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Limit"
    },
    "min_volume_24h": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "The contract-quantity threshold actually applied. Contract quantity may be fractional, so this is a float, not a count.",
      "title": "Min Volume 24H"
    },
    "movers": {
      "items": {
        "$ref": "#/$defs/PriceMoverInfo"
      },
      "title": "Movers",
      "type": "array"
    },
    "count": {
      "default": 0,
      "title": "Count",
      "type": "integer"
    },
    "error": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Error"
    }
  },
  "required": [
    "success"
  ],
  "title": "GetMoversResult",
  "type": "object"
}

# get_orderbook read

Read the live Gemini order book for one contract: bid/ask levels with resting contract quantity.

Full description
Get the current Gemini order book for one prediction-market contract.

Takes an instrumentSymbol from search_markets or get_event_details. Prices
are YES probabilities from 0.00 to 1.00; sizes are resting CONTRACT
QUANTITY, never USD notional. This tool never multiplies price by quantity.
Sizes may be fractional — a contract whose quantityIncrement is 0.01 rests
in hundredths, so treat every quantity as a real number.

This is QUOTE data. It is a different thing from the executed-trade prices
and volumes returned by get_market_snapshot, get_market_history, and
get_market_changes, and the two must not be blended: a missing executed
price is never filled in with a bid/ask midpoint.

Books are typically shallow — three to six levels a side — and one-sided
books are a real, normal state, so an empty `bids` or `asks` list is valid
data rather than an error. `total_bid_quantity` and `total_ask_quantity`
sum only the levels actually returned, which `depth` may have capped;
bids_truncated / asks_truncated flag that. An empty side sums to 0.0, not
null, so the totals are always safe to add up; best_bid, best_ask, spread
and midpoint do stay null on an empty side, because a best price with no
levels is undefined rather than zero.

An unrecognized symbol is reported as INVALID_SYMBOL — Gemini rejects it
with a 400 rather than a 404.
Parameter Type Required Description
market_id string Yes Gemini instrumentSymbol from search_markets or get_event_details, e.g. 'GEMI-FED260917-MAINTAIN'
depth integer No Maximum price levels to return per side. Must be at least 1 — Gemini reads a limit of 0 as 'unlimited', so this tool never sends 0. Default: 10
Parameter schema (JSON)
{
  "description": "Parameters for get_orderbook tool.",
  "properties": {
    "market_id": {
      "description": "Gemini instrumentSymbol from search_markets or get_event_details, e.g. 'GEMI-FED260917-MAINTAIN'",
      "minLength": 1,
      "title": "Market Id",
      "type": "string"
    },
    "depth": {
      "default": 10,
      "description": "Maximum price levels to return per side. Must be at least 1 — Gemini reads a limit of 0 as 'unlimited', so this tool never sends 0.",
      "maximum": 100,
      "minimum": 1,
      "title": "Depth",
      "type": "integer"
    }
  },
  "required": [
    "market_id"
  ],
  "title": "GetOrderbookParams",
  "type": "object",
  "additionalProperties": false
}
Result schema (JSON)
{
  "$defs": {
    "OrderbookLevel": {
      "description": "One price level in the order book.",
      "properties": {
        "price": {
          "description": "YES probability 0.0-1.0 at this level",
          "title": "Price",
          "type": "number"
        },
        "quantity": {
          "description": "Resting size at this level, in CONTRACT QUANTITY (not USD). May be fractional: contracts whose quantityIncrement is 0.01 rest in hundredths, so this is a float, not a count of whole contracts.",
          "title": "Quantity",
          "type": "number"
        },
        "timestamp": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Unix seconds as reported by Gemini for this level. Null when Gemini omitted it or sent an unparseable value — never substituted with 0.",
          "title": "Timestamp"
        }
      },
      "required": [
        "price",
        "quantity"
      ],
      "title": "OrderbookLevel",
      "type": "object"
    }
  },
  "description": "Result of get_orderbook tool.",
  "properties": {
    "success": {
      "title": "Success",
      "type": "boolean"
    },
    "market_id": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Market Id"
    },
    "bids": {
      "description": "Bid levels, highest price first, capped at depth. An empty list is a real state — one-sided books occur.",
      "items": {
        "$ref": "#/$defs/OrderbookLevel"
      },
      "title": "Bids",
      "type": "array"
    },
    "asks": {
      "description": "Ask levels, lowest price first, capped at depth. An empty list is a real state.",
      "items": {
        "$ref": "#/$defs/OrderbookLevel"
      },
      "title": "Asks",
      "type": "array"
    },
    "best_bid": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Highest bid price, or null when there are no bids",
      "title": "Best Bid"
    },
    "best_ask": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Lowest ask price, or null when there are no asks",
      "title": "Best Ask"
    },
    "spread": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "best_ask - best_bid, rounded to 4 decimals to clear float-subtraction noise (Gemini quotes to 2). Null when either side is empty.",
      "title": "Spread"
    },
    "midpoint": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "(best_bid + best_ask) / 2, rounded to 4 decimals. Null when either side is empty — never derived from one side or from a last trade.",
      "title": "Midpoint"
    },
    "total_bid_quantity": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Sum of quantity across the RETURNED bid levels only, in contract quantity, which may be fractional. Capped by depth, so this is not the full book's resting size. An empty bid side sums to 0.0, not null — null appears only on a failed call (success=false).",
      "title": "Total Bid Quantity"
    },
    "total_ask_quantity": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Sum of quantity across the RETURNED ask levels only, in contract quantity, which may be fractional. Capped by depth. An empty ask side sums to 0.0, not null — null appears only on a failed call (success=false).",
      "title": "Total Ask Quantity"
    },
    "quantity_units": {
      "default": "contracts",
      "description": "Units for quantity, total_bid_quantity and total_ask_quantity. Always 'contracts' — Gemini publishes no USD notional for prediction markets. Contracts are divisible: those quoting a quantityIncrement of 0.01 carry fractional size.",
      "title": "Quantity Units",
      "type": "string"
    },
    "bids_truncated": {
      "default": false,
      "description": "True when Gemini returned more bid levels than depth",
      "title": "Bids Truncated",
      "type": "boolean"
    },
    "asks_truncated": {
      "default": false,
      "description": "True when Gemini returned more ask levels than depth",
      "title": "Asks Truncated",
      "type": "boolean"
    },
    "error": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Error"
    }
  },
  "required": [
    "success"
  ],
  "title": "GetOrderbookResult",
  "type": "object"
}

# get_recent_trades read

List recent executed trades for one Gemini contract. Display only — never a volume source.

Full description
List recent executions for one Gemini prediction-market contract.

Takes an instrumentSymbol from search_markets or get_event_details. Prices
are executed probabilities from 0.00 to 1.00; sizes are CONTRACT QUANTITY,
never USD notional, and are often fractional — Gemini fills in increments
of 0.01 contracts on many markets, so 26.22 is a real size, not a bug.

DO NOT SUM `quantity` INTO A VOLUME FIGURE. Gemini's generic trades feed
emitted each prediction-market fill twice for a period that ended around
2026-08-08 — once in YES space and once in the complementary NO space, at
the same millisecond and the same size — which double-counts the traded
quantity. `paired_record_groups` counts how many such pairs are present in
this response. This tool reports them; it does not remove them, and it does
not guess which leg is the YES leg, because the cutover happened at
different times for different symbols and the legs are not otherwise
distinguishable. For any volume question use get_market_snapshot,
get_market_history, or get_volume_movers, which are built on Gemini's
canonical candles.

Quiet markets can have all of their most recent trades on the affected side
of that cutover, so a non-zero paired_record_groups is not unusual.

Trades are returned newest first, in Gemini's own order, and `limit` always
selects the NEWEST ones. That needs care on the `since_ts` path: Gemini
truncates a timestamp-filtered window from the NEWEST end, returning the
OLDEST limit_trades records of it, so this tool asks for Gemini's full
500-record cap and keeps the newest `limit` itself. When the window holds
more than 500 trades even that is not enough — Gemini never sends the
latest ones — and `newer_trades_omitted` plus `note` say so. Pass a later
`since_ts`, or omit it entirely, to see the most recent activity.

An empty list is valid data: the contract may simply never have traded.
Parameter Type Required Description
market_id string Yes Gemini instrumentSymbol from search_markets or get_event_details, e.g. 'GEMI-FED260917-MAINTAIN'
limit integer No Maximum trades to return. Always the NEWEST ones in scope, including when since_ts is set. Gemini's hard cap is 500. Default: 50
since_ts integer | string | null No Return only trades at or after this time. Unix seconds or ISO 8601, e.g. 1786400000 or '2026-08-10T00:00:00Z'. Gemini caps a since_ts-filtered window at 500 records and drops the NEWEST ones to do it, so a window with more than 500 trades cannot show its latest trades at all; newer_trades_omitted flags that.
Parameter schema (JSON)
{
  "description": "Parameters for get_recent_trades tool.",
  "properties": {
    "market_id": {
      "description": "Gemini instrumentSymbol from search_markets or get_event_details, e.g. 'GEMI-FED260917-MAINTAIN'",
      "minLength": 1,
      "title": "Market Id",
      "type": "string"
    },
    "limit": {
      "default": 50,
      "description": "Maximum trades to return. Always the NEWEST ones in scope, including when since_ts is set. Gemini's hard cap is 500.",
      "maximum": 500,
      "minimum": 1,
      "title": "Limit",
      "type": "integer"
    },
    "since_ts": {
      "anyOf": [
        {
          "type": "integer"
        },
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Return only trades at or after this time. Unix seconds or ISO 8601, e.g. 1786400000 or '2026-08-10T00:00:00Z'. Gemini caps a since_ts-filtered window at 500 records and drops the NEWEST ones to do it, so a window with more than 500 trades cannot show its latest trades at all; newer_trades_omitted flags that.",
      "title": "Since Ts"
    }
  },
  "required": [
    "market_id"
  ],
  "title": "GetRecentTradesParams",
  "type": "object",
  "additionalProperties": false
}
Result schema (JSON)
{
  "$defs": {
    "TradeRecord": {
      "description": "One executed trade as reported by Gemini's public trades feed.",
      "properties": {
        "tid": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini trade id",
          "title": "Tid"
        },
        "timestamp": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Execution time, Unix seconds",
          "title": "Timestamp"
        },
        "timestamp_ms": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Execution time, Unix milliseconds",
          "title": "Timestamp Ms"
        },
        "price": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Executed price 0.0-1.0. NOTE: for older records this may be the NO-space leg of a paired emission — see paired_record_groups.",
          "title": "Price"
        },
        "quantity": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Trade size in CONTRACT QUANTITY, never USD notional. Frequently fractional — fills of 26.22 or 7.85 contracts are ordinary on the contracts Gemini quotes with a quantityIncrement of 0.01.",
          "title": "Quantity"
        },
        "side": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini's `type` value for this execution: 'buy' or 'sell'",
          "title": "Side"
        }
      },
      "title": "TradeRecord",
      "type": "object"
    }
  },
  "description": "Result of get_recent_trades tool.",
  "properties": {
    "success": {
      "title": "Success",
      "type": "boolean"
    },
    "market_id": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Market Id"
    },
    "trades": {
      "description": "Trades newest first, in Gemini's own order — nothing is deduplicated or reordered. On the since_ts path this is the newest `limit` of the window Gemini returned; see newer_trades_omitted.",
      "items": {
        "$ref": "#/$defs/TradeRecord"
      },
      "title": "Trades",
      "type": "array"
    },
    "count": {
      "default": 0,
      "title": "Count",
      "type": "integer"
    },
    "quantity_units": {
      "default": "contracts",
      "description": "Units for quantity. Always 'contracts' — a divisible quantity, not a whole-contract count.",
      "title": "Quantity Units",
      "type": "string"
    },
    "paired_record_groups": {
      "default": 0,
      "description": "Number of same-millisecond groups containing a complementary pair (equal size, prices summing to 1.00) AMONG THE RETURNED TRADES. Any non-zero value means these records double-count executions and must not be summed into a volume.",
      "title": "Paired Record Groups",
      "type": "integer"
    },
    "newer_trades_omitted": {
      "default": false,
      "description": "True when since_ts selected more trades than Gemini will return in one response (500) — Gemini truncates such a window from the NEWEST end, so trades more recent than the first one here exist and were never sent. Always false when since_ts is omitted.",
      "title": "Newer Trades Omitted",
      "type": "boolean"
    },
    "note": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Set when newer_trades_omitted is true, when paired_record_groups > 0, or both",
      "title": "Note"
    },
    "error": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Error"
    }
  },
  "required": [
    "success"
  ],
  "title": "GetRecentTradesResult",
  "type": "object"
}

# get_volume_movers read

List Gemini markets with unusually high or low relative contract volume over 1h or 24h.

Full description
Rank Gemini prediction markets by relative contract volume (RVOL).

rvol = window_vol / baseline_per_window, where a 1h window is compared
against a 24h trailing baseline and a 24h window against a 7d one. The API
labels those baselines '1d' and '7d' respectively in baseline_window. RVOL
is unitless because both terms are EXECUTED CONTRACT QUANTITY — the
quantity of contracts traded, never USD notional. This toolkit never
derives a dollar volume by multiplying price by quantity, because Gemini
publishes none. Both terms are floats: many Gemini contracts quote a
quantityIncrement of 0.01, and `baseline_per_window` is an average in any
case, so a `window_vol` of 1856.44 is a real quantity, not a units error.

Only 1h and 24h windows exist for relative volume; for longer horizons, or
for ranking by price rather than volume, use get_movers.

ALWAYS READ baseline_complete. When it is false the endpoint had less
retained history than the requested baseline and used what it had, so the
RVOL is not a full-baseline figure and must be described with that
qualifier rather than presented as one; `note` restates it and `warning`
carries any API-supplied text verbatim. Gaps are never filled with
fabricated volume.

`min_volume_24h` is in EXECUTED CONTRACT QUANTITY, not dollars, and is a
float threshold rather than a whole-contract count.

Defaults to active markets only — pass status="" to include settled and
closed ones. Sports is excluded by default because it is roughly 3256 of
Gemini's 4400 active stored markets; pass exclude_categories=[] to include
it, and note that naming category or categories without setting
exclude_categories clears that default. Use list_mover_categories for valid
names — they are Toolforest canonical names, matched case-insensitively,
and any value in category, categories, or exclude_categories outside the
toolkit's PINNED vocabulary fails the call with INVALID_REQUEST listing the
valid set, rather than quietly returning nothing. Validation is offline and
never reads the categories endpoint, so a category Gemini has newly started
carrying is rejected here until the toolkit is redeployed, even though
list_mover_categories will show it.

An empty result is VALID DATA, not a failure: a market can simply be new,
inactive, illiquid, or missing the required baseline. A mis-namespaced
CATEGORY is no longer one of the causes — unknown category names are
rejected before the movers request is made — but nothing else here is
vocabulary-checked, and `query` in particular is free text, so a typo there
still surfaces as an empty result.
Parameter Type Required Description
window "1h" | "24h" No Numerator window for relative volume. Only 1h and 24h are supported — the upstream endpoint rejects 7d and 30d. Default: "1h"
direction "up" | "down" | "both" No 'up' returns the highest relative volume, 'down' the lowest, 'both' ranks by absolute relative volume. Default: "up"
category string | null No Single canonical market-data category filter. Case-insensitive. Use list_mover_categories for valid names. An unrecognized name fails the call with INVALID_REQUEST listing the valid set; it is not silently ignored. Commas split this into several names, each validated on its own.
categories array<string> | null No Canonical market-data categories to include. Merged with category. Case-insensitive, and an unrecognized name fails the call with INVALID_REQUEST listing the valid set.
exclude_categories array<string> | null No Categories to exclude. Defaults to ['Sports'] because Sports is 3256 of Gemini's roughly 4400 active stored markets. Pass [] to include Sports. Naming category or categories without setting this clears the default. Case-insensitive, and an unrecognized name fails the call with INVALID_REQUEST listing the valid set. One name per entry — each entry is sent whole, so 'Sports,Crypto' is rejected as a single unknown name.
query string | null No Keyword filter applied before ranking, matched against market title or description.
min_volume_24h number No Minimum trailing 24h volume in EXECUTED CONTRACT QUANTITY, NOT dollars. Contract quantity may be fractional, so this is a float threshold, not a whole-contract count; a fractional value is accepted and sent to the ranking endpoint unchanged. Default 100 contracts. Pass 0 to disable. Default: 100
limit integer No Maximum movers to return (1..200). Default: 20
status string | null No Filter to a single stored market status. Defaults to 'active'. Pass an empty string to include all statuses. Default: "active"
Parameter schema (JSON)
{
  "description": "Parameters for get_volume_movers tool.",
  "properties": {
    "window": {
      "default": "1h",
      "description": "Numerator window for relative volume. Only 1h and 24h are supported — the upstream endpoint rejects 7d and 30d.",
      "enum": [
        "1h",
        "24h"
      ],
      "title": "Window",
      "type": "string"
    },
    "direction": {
      "default": "up",
      "description": "'up' returns the highest relative volume, 'down' the lowest, 'both' ranks by absolute relative volume.",
      "enum": [
        "up",
        "down",
        "both"
      ],
      "title": "Direction",
      "type": "string"
    },
    "category": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Single canonical market-data category filter. Case-insensitive. Use list_mover_categories for valid names. An unrecognized name fails the call with INVALID_REQUEST listing the valid set; it is not silently ignored. Commas split this into several names, each validated on its own.",
      "title": "Category"
    },
    "categories": {
      "anyOf": [
        {
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Canonical market-data categories to include. Merged with category. Case-insensitive, and an unrecognized name fails the call with INVALID_REQUEST listing the valid set.",
      "title": "Categories"
    },
    "exclude_categories": {
      "anyOf": [
        {
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Categories to exclude. Defaults to ['Sports'] because Sports is 3256 of Gemini's roughly 4400 active stored markets. Pass [] to include Sports. Naming category or categories without setting this clears the default. Case-insensitive, and an unrecognized name fails the call with INVALID_REQUEST listing the valid set. One name per entry — each entry is sent whole, so 'Sports,Crypto' is rejected as a single unknown name.",
      "title": "Exclude Categories"
    },
    "query": {
      "anyOf": [
        {
          "maxLength": 200,
          "minLength": 1,
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Keyword filter applied before ranking, matched against market title or description.",
      "title": "Query"
    },
    "min_volume_24h": {
      "default": 100,
      "description": "Minimum trailing 24h volume in EXECUTED CONTRACT QUANTITY, NOT dollars. Contract quantity may be fractional, so this is a float threshold, not a whole-contract count; a fractional value is accepted and sent to the ranking endpoint unchanged. Default 100 contracts. Pass 0 to disable.",
      "minimum": 0,
      "title": "Min Volume 24H",
      "type": "number"
    },
    "limit": {
      "default": 20,
      "description": "Maximum movers to return (1..200).",
      "maximum": 200,
      "minimum": 1,
      "title": "Limit",
      "type": "integer"
    },
    "status": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": "active",
      "description": "Filter to a single stored market status. Defaults to 'active'. Pass an empty string to include all statuses.",
      "title": "Status"
    }
  },
  "title": "GetVolumeMoversParams",
  "type": "object",
  "additionalProperties": false
}
Result schema (JSON)
{
  "$defs": {
    "VolumeMoverInfo": {
      "description": "One market's relative contract volume over the requested window.",
      "properties": {
        "venue": {
          "description": "Always 'gemini'",
          "title": "Venue",
          "type": "string"
        },
        "market_id": {
          "description": "Gemini instrumentSymbol — pass to get_market_snapshot, get_market_history, get_market_changes, get_orderbook, or get_recent_trades",
          "title": "Market Id",
          "type": "string"
        },
        "title": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Title"
        },
        "category": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Toolforest canonical category",
          "title": "Category"
        },
        "status": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Status"
        },
        "window_vol": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "EXECUTED CONTRACT QUANTITY traded during the window, which may be fractional, never USD notional",
          "title": "Window Vol"
        },
        "baseline_per_window": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Average EXECUTED CONTRACT QUANTITY per equivalent window over the trailing baseline, never USD notional. An average, so fractional even where the underlying contract trades in whole units.",
          "title": "Baseline Per Window"
        },
        "rvol": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "window_vol / baseline_per_window. Unitless — both terms are contract quantity. 2.0 means twice the usual activity.",
          "title": "Rvol"
        },
        "open": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Executed YES probability at window start, 0.0-1.0",
          "title": "Open"
        },
        "close": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Executed YES probability at window end, 0.0-1.0",
          "title": "Close"
        },
        "price_abs": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "close - open, in probability points",
          "title": "Price Abs"
        },
        "price_pct": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "100 * (close - open) / open",
          "title": "Price Pct"
        }
      },
      "required": [
        "venue",
        "market_id"
      ],
      "title": "VolumeMoverInfo",
      "type": "object"
    }
  },
  "description": "Result of get_volume_movers tool.",
  "properties": {
    "success": {
      "title": "Success",
      "type": "boolean"
    },
    "window": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Window"
    },
    "baseline_window": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Baseline horizon label reported by the API: '1d' for a 1h window, '7d' for a 24h window",
      "title": "Baseline Window"
    },
    "baseline_complete": {
      "anyOf": [
        {
          "type": "boolean"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Reported by the API. False means the requested baseline was not fully covered by retained history and a shorter available history was used instead, so rvol is not a full-baseline figure. Gaps are never filled with fabricated volume.",
      "title": "Baseline Complete"
    },
    "warning": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Opaque qualifier string from the API, passed through verbatim. Usually null.",
      "title": "Warning"
    },
    "direction": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Direction"
    },
    "limit": {
      "anyOf": [
        {
          "type": "integer"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Limit"
    },
    "min_volume_24h": {
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "The contract-quantity threshold actually applied. Contract quantity may be fractional, so this is a float, not a count.",
      "title": "Min Volume 24H"
    },
    "movers": {
      "items": {
        "$ref": "#/$defs/VolumeMoverInfo"
      },
      "title": "Movers",
      "type": "array"
    },
    "count": {
      "default": 0,
      "title": "Count",
      "type": "integer"
    },
    "volume_units": {
      "default": "contracts",
      "description": "Units for window_vol and baseline_per_window. Always 'contracts' — a divisible quantity, not a whole-contract count.",
      "title": "Volume Units",
      "type": "string"
    },
    "note": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Set when baseline_complete is False, restating that qualifier",
      "title": "Note"
    },
    "error": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Error"
    }
  },
  "required": [
    "success"
  ],
  "title": "GetVolumeMoversResult",
  "type": "object"
}

# list_categories read

List the Gemini-native category values accepted by search_markets.

Full description
List Gemini's own category values for use with search_markets(category=...).

Returns names only. Gemini's categories endpoint supplies no event counts
and no volume, and this tool does not invent them — deriving counts would
mean downloading the entire multi-megabyte event catalog for a taxonomy
lookup.

Two things worth knowing about this list, both true on every call:
- Some values currently match zero events. They are still accepted by the
  filter; they simply return nothing.
- 'Live' is a VIRTUAL filter, not a real category. Filtering on it returns
  in-play markets whose actual categories are Crypto, Commodities, Sports,
  and so on. In-play status is reported per event as `is_in_play`; it is a
  different concept from contract liveness and from search_markets'
  `live_only`, which filters on per-contract liveness alone.

This is a DIFFERENT vocabulary from list_mover_categories, which returns
Toolforest's canonical market-data categories (for example Gemini's
'Weather' appears there as 'Climate and Weather', and 'Tech' as 'Science
and Technology'). Use this list for search_markets and that list for
get_movers / get_volume_movers; a name from one is not guaranteed valid in
the other.

No parameters.

Parameter schema (JSON)
{
  "description": "Parameters for list_categories tool.",
  "properties": {},
  "title": "ListCategoriesParams",
  "type": "object",
  "additionalProperties": false
}
Result schema (JSON)
{
  "$defs": {
    "GeminiCategorySummary": {
      "description": "One Gemini-native category value.",
      "properties": {
        "category": {
          "description": "Category value accepted by search_markets(category=...). Matching is case-insensitive.",
          "title": "Category",
          "type": "string"
        }
      },
      "required": [
        "category"
      ],
      "title": "GeminiCategorySummary",
      "type": "object"
    }
  },
  "description": "Result of list_categories tool.",
  "properties": {
    "success": {
      "title": "Success",
      "type": "boolean"
    },
    "categories": {
      "items": {
        "$ref": "#/$defs/GeminiCategorySummary"
      },
      "title": "Categories",
      "type": "array"
    },
    "count": {
      "default": 0,
      "title": "Count",
      "type": "integer"
    },
    "note": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Caveats about this list that apply on every successful call",
      "title": "Note"
    },
    "error": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Error message if operation failed",
      "title": "Error"
    }
  },
  "required": [
    "success"
  ],
  "title": "ListCategoriesResult",
  "type": "object"
}

# list_mover_categories read

List the canonical categories accepted by get_movers and get_volume_movers, with stored market counts.

Full description
List Toolforest canonical categories for Gemini mover filters.

Use these names with get_movers(category=...) / get_movers(categories=[...])
and the same arguments on get_volume_movers. Matching is case-insensitive.

Those two tools validate against a PINNED COPY of this vocabulary, not
against this live listing: a name outside that copy — in category,
categories, or exclude_categories — fails with INVALID_REQUEST naming the
offending value and listing the valid set, instead of returning an empty
list that looks like a quiet market.

The copy can therefore lag this endpoint. If a name appears here but a
mover call rejects it, the pinned copy is stale and needs a toolkit change;
retrying will not help. Validation is deliberately offline because a live
re-check on the request path is an unbounded upstream call that could
consume the invocation deadline on every mistyped category.
Parameter Type Required Description
status string | null No Filter to a single stored market status. Defaults to 'active'. Pass an empty string for no status filter. Only 'active' and 'inactive' actually match rows — any other non-empty value returns an empty list rather than an error. Default: "active"
Parameter schema (JSON)
{
  "description": "Parameters for list_mover_categories tool.",
  "properties": {
    "status": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": "active",
      "description": "Filter to a single stored market status. Defaults to 'active'. Pass an empty string for no status filter. Only 'active' and 'inactive' actually match rows — any other non-empty value returns an empty list rather than an error.",
      "title": "Status"
    }
  },
  "title": "ListMoverCategoriesParams",
  "type": "object",
  "additionalProperties": false
}
Result schema (JSON)
{
  "$defs": {
    "MoverCategorySummary": {
      "description": "One canonical market-data category and its stored market count.",
      "properties": {
        "category": {
          "description": "Category name to pass to get_movers(category=...) or get_volume_movers(category=...). Matching is case-insensitive, and a name outside this list is rejected there with INVALID_REQUEST.",
          "title": "Category",
          "type": "string"
        },
        "market_count": {
          "default": 0,
          "description": "Number of stored Gemini markets in this category matching the status filter. A market count, not an event count, and not a volume — so unlike every contract-quantity field in this toolkit, it is a genuine whole number and is never fractional.",
          "title": "Market Count",
          "type": "integer"
        }
      },
      "required": [
        "category"
      ],
      "title": "MoverCategorySummary",
      "type": "object"
    }
  },
  "description": "Result of list_mover_categories tool.",
  "properties": {
    "success": {
      "title": "Success",
      "type": "boolean"
    },
    "categories": {
      "items": {
        "$ref": "#/$defs/MoverCategorySummary"
      },
      "title": "Categories",
      "type": "array"
    },
    "count": {
      "default": 0,
      "title": "Count",
      "type": "integer"
    },
    "note": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Note"
    },
    "error": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Error"
    }
  },
  "required": [
    "success"
  ],
  "title": "ListMoverCategoriesResult",
  "type": "object"
}

# search_markets read

Search Gemini prediction-market events and their contracts by keyword, category, and live status.

Full description
Search Gemini prediction-market events and return their contracts.

Queries Gemini's public events API directly. `query` is matched
server-side as ONE contiguous case-insensitive substring of the event
title, description, or ticker — not slugs, contract labels, or
instrumentSymbols. Gemini does not split the query into terms and does not
AND them, so word order and adjacency matter: "Fed decision" finds the
September Fed event, while "fed september" finds nothing even though both
words are in its title. If a multi-word query returns nothing, retry with a
shorter phrase or a single distinctive word before concluding the market
does not exist. `category` and `status` are also applied server-side.

`live_only` (default true) is applied here, not by Gemini. A contract is
live only when all four upstream signals agree: event status active,
contract status active, marketState open, and expiryDate in the future. A
missing or unparseable expiryDate is treated as NOT live. Gemini leaves
stale metadata marked active, so no single flag is trusted: a settled
contract with a 2027 expiry sitting inside an active event, and a
post_only contract that is status-active with a future expiry, are both
excluded only because all four checks run.

That filter reads PER-CONTRACT liveness and nothing else. The event-level
`is_in_play` field is Gemini's separate in-play marker — an in-progress
game, a next-hour crypto strike — and takes no part in it: most events
holding live contracts report is_in_play false, and live_only=true returns
them anyway. Read `live_contract_count` for the event-level tradability
count; is_in_play answers a different question.

Every returned contract carries `instrument_symbol` — pass it to
get_market_snapshot, get_market_history, get_market_changes,
get_orderbook, or get_recent_trades.

SETTLED OUTCOMES: a resolved contract reports `result` (observed only as
'yes' or 'no', but any other side Gemini sends is passed through, so treat
an unrecognized value as unknown rather than as a no),
`resolved_at`, and — only where the outcome is numeric — `settlement_value`
in the underlying's own units, never a probability. Every quote field is
null on a settled contract, so `result` is the only outcome signal. Reach
them with status="settled" AND live_only=false: the default status
"active" excludes settled events server-side, and live_only=true would drop
every contract in one anyway. `result` also appears on the occasional
contract that resolves early inside a still-active event.

`volume` and `volume_24h` are Gemini's EXECUTED CONTRACT QUANTITY, never
USD notional. Gemini publishes no USD volume for prediction markets and
this tool never derives one. That quantity is NOT an integer count: Gemini
sets a per-contract quantityIncrement, and the many contracts set to 0.01 —
crypto, sports, commodities and weather — produce genuinely fractional
volumes such as 30.59. Report them as given; do not round to whole
contracts and do not read a fraction as a unit error.
(`volume_delta_24h_pct` is a percentage, not a quantity, and carries no
contract units at all.)

PAGING: `limit` counts events surviving live_only. Because that filter is
client-side, this tool pages Gemini forward until it has `limit` survivors,
reaches its 4-page / 800-event scan cap, or runs low on invocation runtime.
`next_offset` is the exact server offset of the first event not examined,
so no event is skipped or seen twice; it is null only when the matched set
really ran out. Whenever the scan stops early — scan cap, deadline, or a
failed page — `note` says which, and `next_offset` lets you resume from
exactly where it stopped. That holds even when Gemini omits the pagination
total: `has_more` is false there because there is no total to compare
against, not because the scan finished. `has_more` reports only that more
server-MATCHED events remain — a follow-up page can legitimately return
zero live events.

`best_bid` / `best_ask` / `spread` / `midpoint` are live YES-side quote data
in probability terms, 0.0-1.0. `spread` and `midpoint` are null whenever
either side of the book is missing — never synthesized from one side or from
the last trade. Because all three venues quote in the same 0-1 probability
terms, these fields ARE directly comparable across the kalshi, polymarket
and gemini toolkits — unlike the volume fields, whose units differ by venue.

Gemini-specific caveat: a null `best_bid` / `best_ask` here means the bulk
event summary did not publish that side as a usable number — NOT that the
direct order book is empty — and a null `spread` / `midpoint` likewise
reports an incomplete SUMMARY quote rather than a one-sided book. The
`buy_*` / `sell_*` fields are raw upstream indicative values that can simply
mirror the last trade. When an executable quote matters, confirm with
get_orderbook — the direct book, which is authoritative but costs one
request per contract.

An empty `events` list is a valid result, not an error.

WORKFLOW:
1. list_categories() -> valid Gemini category names
2. search_markets(query="Fed") -> events with contracts and instrument_symbol
3. get_market_snapshot(market_id) -> stored price and contract volume
4. get_market_history / get_market_changes -> stored time series
Parameter Type Required Description
query string | null No Keyword filter applied by Gemini server-side. ONE contiguous case-insensitive substring — the terms are NOT split and NOT ANDed, so word order and adjacency both matter: 'Fed decision' matches 'Fed decision in September?' but 'fed september' matches nothing. Matched against the event title, event description, and event ticker only — it does NOT match slugs, contract labels, or instrumentSymbols. Prefer a short phrase or a single word; a zero-result response means that exact substring was absent, not that no such market exists. Minimum 3 characters. Omit for an unfiltered browse.
category string | null No Single Gemini-native category filter, e.g. 'Economics'. Case-insensitive. Use list_categories for valid values; an invalid value is rejected by Gemini with an INVALID_REQUEST error. Prefer categories for multiple values.
categories array<string> | null No Gemini-native categories to include, e.g. ['Economics', 'Politics']. Merged with category. Note 'Live' is a virtual filter that returns in-play markets across real categories rather than a category of its own; in-play status is surfaced per event as is_in_play, which is unrelated to contract liveness and to live_only.
status string | null No Gemini event status filter: 'active', 'approved', 'under_review', or 'settled'. Defaults to 'active'. Pass an empty string to send no status at all — note that Gemini's unfiltered default is NOT active-only and mixes in recently settled and pending events. Default: "active"
live_only boolean No When true, return only contracts where event status is 'active' AND contract status is 'active' AND marketState is 'open' AND expiryDate is in the future, and drop events left with no such contract. When false, every contract is returned with is_live and not_live_reason set. Default: true
limit integer No Maximum events to return in this page, counted AFTER the live_only filter. Default: 20
start_offset integer No Gemini server-side offset to resume scanning from. This is an offset into Gemini's MATCHED events, not into this tool's filtered output. Pass the next_offset value from a previous call to continue. Default: 0
max_contracts_per_event integer | null No Cap on contracts returned per event; contracts_truncated flags any event that hit it. Pass null for no cap — Gemini events have had up to 69 contracts. Default: 10
Parameter schema (JSON)
{
  "description": "Parameters for search_markets tool.",
  "properties": {
    "query": {
      "anyOf": [
        {
          "maxLength": 200,
          "minLength": 3,
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Keyword filter applied by Gemini server-side. ONE contiguous case-insensitive substring — the terms are NOT split and NOT ANDed, so word order and adjacency both matter: 'Fed decision' matches 'Fed decision in September?' but 'fed september' matches nothing. Matched against the event title, event description, and event ticker only — it does NOT match slugs, contract labels, or instrumentSymbols. Prefer a short phrase or a single word; a zero-result response means that exact substring was absent, not that no such market exists. Minimum 3 characters. Omit for an unfiltered browse.",
      "title": "Query"
    },
    "category": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Single Gemini-native category filter, e.g. 'Economics'. Case-insensitive. Use list_categories for valid values; an invalid value is rejected by Gemini with an INVALID_REQUEST error. Prefer categories for multiple values.",
      "title": "Category"
    },
    "categories": {
      "anyOf": [
        {
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Gemini-native categories to include, e.g. ['Economics', 'Politics']. Merged with category. Note 'Live' is a virtual filter that returns in-play markets across real categories rather than a category of its own; in-play status is surfaced per event as is_in_play, which is unrelated to contract liveness and to live_only.",
      "title": "Categories"
    },
    "status": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": "active",
      "description": "Gemini event status filter: 'active', 'approved', 'under_review', or 'settled'. Defaults to 'active'. Pass an empty string to send no status at all — note that Gemini's unfiltered default is NOT active-only and mixes in recently settled and pending events.",
      "title": "Status"
    },
    "live_only": {
      "default": true,
      "description": "When true, return only contracts where event status is 'active' AND contract status is 'active' AND marketState is 'open' AND expiryDate is in the future, and drop events left with no such contract. When false, every contract is returned with is_live and not_live_reason set.",
      "title": "Live Only",
      "type": "boolean"
    },
    "limit": {
      "default": 20,
      "description": "Maximum events to return in this page, counted AFTER the live_only filter.",
      "maximum": 100,
      "minimum": 1,
      "title": "Limit",
      "type": "integer"
    },
    "start_offset": {
      "default": 0,
      "description": "Gemini server-side offset to resume scanning from. This is an offset into Gemini's MATCHED events, not into this tool's filtered output. Pass the next_offset value from a previous call to continue.",
      "minimum": 0,
      "title": "Start Offset",
      "type": "integer"
    },
    "max_contracts_per_event": {
      "anyOf": [
        {
          "maximum": 100,
          "minimum": 1,
          "type": "integer"
        },
        {
          "type": "null"
        }
      ],
      "default": 10,
      "description": "Cap on contracts returned per event; contracts_truncated flags any event that hit it. Pass null for no cap — Gemini events have had up to 69 contracts.",
      "title": "Max Contracts Per Event"
    }
  },
  "title": "SearchMarketsParams",
  "type": "object",
  "additionalProperties": false
}
Result schema (JSON)
{
  "$defs": {
    "GeminiContractSummary": {
      "description": "One tradable contract (outcome) inside a Gemini event.",
      "properties": {
        "instrument_symbol": {
          "description": "Gemini instrumentSymbol, e.g. 'GEMI-FED260917-MAINTAIN'. This is the market_id used by get_market_snapshot, get_market_history, get_market_changes, get_orderbook and get_recent_trades. Case-sensitive when passed to the analytics tools.",
          "title": "Instrument Symbol",
          "type": "string"
        },
        "contract_id": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini internal contract id, e.g. '168090-404860'. Not accepted by any other tool in this toolkit.",
          "title": "Contract Id"
        },
        "label": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Human-readable outcome label, e.g. 'Fed maintains rate'",
          "title": "Label"
        },
        "ticker": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Short contract ticker within the event, e.g. 'MAINTAIN'",
          "title": "Ticker"
        },
        "status": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini contract status, e.g. 'active' or 'settled'",
          "title": "Status"
        },
        "market_state": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini marketState: 'open', 'post_only', or 'closed'. 'post_only' contracts are not tradable even when status is 'active'.",
          "title": "Market State"
        },
        "expiry_date": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "ISO 8601 contract expiry (UTC)",
          "title": "Expiry Date"
        },
        "effective_date": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "ISO 8601 date the contract became effective (UTC)",
          "title": "Effective Date"
        },
        "is_live": {
          "description": "True only when event status is 'active' AND contract status is 'active' AND market_state is 'open' AND expiry_date is in the future. A missing or unparseable expiry_date makes this False. Computed here from those four upstream signals — Gemini publishes no per-contract liveness flag of its own. This is the ONLY field the live_only filter reads. Do not confuse it with GeminiEventSummary.is_in_play, Gemini's event-level in-play flag, which is an unrelated concept and is never consulted by this check.",
          "title": "Is Live",
          "type": "boolean"
        },
        "not_live_reason": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "When is_live is False, the first failing check: 'event_status', 'contract_status', 'market_state', or 'expiry'. None when is_live is True.",
          "title": "Not Live Reason"
        },
        "is_combo": {
          "anyOf": [
            {
              "type": "boolean"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Whether this is a combination contract",
          "title": "Is Combo"
        },
        "result": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Settlement side, lowercased from Gemini's resolutionSide. The only values observed are 'yes' and 'no' (measured over 1000 settled and 200 active events), but this is NOT a closed set: any other side Gemini sends — a void or cancellation, say — is passed through lowercased rather than dropped. So do not branch `== 'yes'` with an else that means no; test for 'yes' and 'no' explicitly and treat any other value as unknown rather than as a loss. Null means Gemini supplied no side, which is usually an unresolved contract but is not proof of one — `resolved_at` is read independently and can be set while this is null. Settled contracts have been observed carrying no usable price fields at all, leaving this the only outcome signal — that is Gemini's observed habit rather than an invariant this toolkit enforces, so read the price fields for what they are instead of inferring settlement from them. Lowercase matches the kalshi toolkit's `result`; the polymarket toolkit capitalizes its own ('Yes'/'No'), so never compare the two case-sensitively. A contract can resolve while its event is still 'active', so a non-null result does not imply the event has settled.",
          "title": "Result"
        },
        "resolved_at": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "ISO 8601 timestamp (UTC) at which Gemini resolved this contract; null when Gemini supplied no timestamp. Read independently of `result`, so neither implies the other: a contract can carry this with a null result (Gemini omitted the side) or a result with no timestamp. A contract that resolves early inside a still-active event carries both as soon as Gemini publishes them.",
          "title": "Resolved At"
        },
        "settlement_value": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Reference value the outcome was settled against, expressed in the UNDERLYING's own units — e.g. 63306.96 for a BTC price market. This is NOT a probability in 0.0-1.0 like the price fields below, and NOT a contract quantity like the volume fields. Null unless Gemini published one: always null while the contract is unresolved, and also null on resolved contracts whose outcome is not numeric — a settled sports contract has a result and no settlement_value. Gemini sends it as a string; a value that will not parse as a number is reported as null.",
          "title": "Settlement Value"
        },
        "best_bid": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Best bid as a YES probability 0.0-1.0. Quote data, not an executed price. Null means Gemini's bulk event summary published no usable bid — the field was absent, or its value would not parse as a number — and it does NOT prove the direct order book has no bids: contracts have been observed (2026-08-14) with both sides null here while get_orderbook returned resting orders. The direct book from get_orderbook is authoritative.",
          "title": "Best Bid"
        },
        "best_ask": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Best ask as a YES probability 0.0-1.0. Quote data, not an executed price. Null means Gemini's bulk event summary published no usable ask — the field was absent, or its value would not parse as a number — and it does NOT prove the direct order book has no asks: contracts have been observed (2026-08-14) with both sides null here while get_orderbook returned resting orders. The direct book from get_orderbook is authoritative.",
          "title": "Best Ask"
        },
        "spread": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "best_ask - best_bid, rounded to 4 decimals. Null unless Gemini's bulk event summary published BOTH sides as usable numbers — never derived from one side, from a last trade, or from the buy_* / sell_* indicative values. A null therefore says the summary carried an incomplete quote; it does NOT prove the direct order book is one-sided or empty, since contracts have been observed (2026-08-14) with both summary sides null while get_orderbook returned resting orders.",
          "title": "Spread"
        },
        "midpoint": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "(best_bid + best_ask) / 2, rounded to 4 decimals. Null unless Gemini's bulk event summary published BOTH sides as usable numbers — never derived from one side, from a last trade, or from the buy_* / sell_* indicative values. A null therefore says the summary carried an incomplete quote; it does NOT prove the direct order book is one-sided or empty, since contracts have been observed (2026-08-14) with both summary sides null while get_orderbook returned resting orders.",
          "title": "Midpoint"
        },
        "last_trade_price": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Last executed YES probability 0.0-1.0, as carried by Gemini's bulk event summary. Null when the summary carried none, or carried a value that would not parse as a number — usually a never-traded contract, but the field reports what the summary published, not trading history. Never replaced by a bid/ask midpoint.",
          "title": "Last Trade Price"
        },
        "buy_yes": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini's raw indicative price to buy YES, 0.0-1.0, passed through unchanged. NOT a resting quote and not guaranteed executable: on contracts whose bulk summary omits bestBid/bestAsk, Gemini has been observed (2026-08-14) publishing this equal to last_trade_price. Never used to derive spread or midpoint. For an executable price, read the direct book via get_orderbook.",
          "title": "Buy Yes"
        },
        "buy_no": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini's raw indicative price to buy NO, 0.0-1.0, passed through unchanged. NOT a resting quote and not guaranteed executable: on contracts whose bulk summary omits bestBid/bestAsk, Gemini has been observed (2026-08-14) publishing this equal to last_trade_price. Never used to derive spread or midpoint. For an executable price, read the direct book via get_orderbook.",
          "title": "Buy No"
        },
        "sell_yes": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini's raw indicative price to sell YES, 0.0-1.0, passed through unchanged. NOT a resting quote and not guaranteed executable: on contracts whose bulk summary omits bestBid/bestAsk, Gemini has been observed (2026-08-14) publishing this equal to last_trade_price. Never used to derive spread or midpoint. For an executable price, read the direct book via get_orderbook.",
          "title": "Sell Yes"
        },
        "sell_no": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini's raw indicative price to sell NO, 0.0-1.0, passed through unchanged. NOT a resting quote and not guaranteed executable: on contracts whose bulk summary omits bestBid/bestAsk, Gemini has been observed (2026-08-14) publishing this equal to last_trade_price. Never used to derive spread or midpoint. For an executable price, read the direct book via get_orderbook.",
          "title": "Sell No"
        }
      },
      "required": [
        "instrument_symbol",
        "is_live"
      ],
      "title": "GeminiContractSummary",
      "type": "object"
    },
    "GeminiEventSummary": {
      "description": "A Gemini prediction-market event and its contracts.",
      "properties": {
        "event_ticker": {
          "description": "Event ticker, e.g. 'FED260917'. This is the ONLY identifier get_event_details accepts.",
          "title": "Event Ticker",
          "type": "string"
        },
        "event_id": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini internal event id. Not accepted by any tool in this toolkit.",
          "title": "Event Id"
        },
        "title": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Event title, e.g. 'Fed decision in September?'",
          "title": "Title"
        },
        "slug": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "URL slug. Not accepted by any tool in this toolkit.",
          "title": "Slug"
        },
        "event_type": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "'binary' (two outcomes) or 'categorical' (many outcomes)",
          "title": "Event Type"
        },
        "category": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini-native category, e.g. 'Economics'. Use list_categories for the accepted values. These are NOT the same vocabulary as list_mover_categories.",
          "title": "Category"
        },
        "subcategory": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini subcategory name, e.g. 'Fed'",
          "title": "Subcategory"
        },
        "subcategory_path": {
          "description": "Subcategory breadcrumb, e.g. ['Economics', 'Fed']",
          "items": {
            "type": "string"
          },
          "title": "Subcategory Path",
          "type": "array"
        },
        "status": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini event status: 'active', 'approved', 'under_review', or 'settled'",
          "title": "Status"
        },
        "is_in_play": {
          "anyOf": [
            {
              "type": "boolean"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini's own IN-PLAY flag for the event, passed through from raw `isLive` unchanged and never computed here. It marks short-interval and currently-playing markets — an in-progress ball game, a next-hour crypto strike. It is NOT a tradability signal, NOT the contract lifecycle check, and no filter in this toolkit reads it: an event with is_in_play false routinely holds live, tradable contracts and is returned by live_only=true. For tradability read live_contract_count on this event, or GeminiContractSummary.is_live on each contract. Null when Gemini omits the flag. (Named is_live before 2026-08; that name collided with the per-contract field and is no longer emitted.)",
          "title": "Is In Play"
        },
        "settlement_value": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Event-level settlement reference value, from Gemini's settlement.value, in the UNDERLYING's own units (e.g. a BTC index price) — not a probability and not a contract quantity. Null until the whole event settles, and null even then when the outcome is not numeric: Gemini sends an empty settlement block for sports events. Per-contract outcomes are on GeminiContractSummary.result / .settlement_value, which appear as soon as a contract resolves rather than waiting for the event.",
          "title": "Settlement Value"
        },
        "description": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Event description as plain text",
          "title": "Description"
        },
        "tags": {
          "description": "Free-form Gemini event tags. Not usable as a filter by any tool here.",
          "items": {
            "type": "string"
          },
          "title": "Tags",
          "type": "array"
        },
        "volume": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini-reported total event volume in EXECUTED CONTRACT QUANTITY (contract quantity, which MAY BE FRACTIONAL — contracts whose quantityIncrement is 0.01 trade in hundredths), never USD notional. Reported by Gemini across all contracts in the event; it is collected independently of the per-contract volume returned by get_market_snapshot and the two are not reconciled.",
          "title": "Volume"
        },
        "volume_24h": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini-reported trailing 24h event volume in EXECUTED CONTRACT QUANTITY, which may be fractional, never USD notional",
          "title": "Volume 24H"
        },
        "volume_delta_24h_pct": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "Gemini-reported 24h percentage change in event contract volume",
          "title": "Volume Delta 24H Pct"
        },
        "expiry_date": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "ISO 8601 event expiry (UTC)",
          "title": "Expiry Date"
        },
        "effective_date": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "ISO 8601 date the event became effective (UTC)",
          "title": "Effective Date"
        },
        "created_at": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "ISO 8601 creation timestamp (UTC)",
          "title": "Created At"
        },
        "terms_link": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "description": "URL to the event's terms",
          "title": "Terms Link"
        },
        "contracts": {
          "description": "Contracts returned for this event, in Gemini's own order (never re-sorted). May be filtered by live_only and capped by max_contracts_per_event.",
          "items": {
            "$ref": "#/$defs/GeminiContractSummary"
          },
          "title": "Contracts",
          "type": "array"
        },
        "contract_count": {
          "default": 0,
          "description": "Total contracts Gemini returned for this event, BEFORE any live_only filter or max_contracts_per_event cap",
          "title": "Contract Count",
          "type": "integer"
        },
        "live_contract_count": {
          "default": 0,
          "description": "How many of those contracts pass the four-condition liveness check, BEFORE the max_contracts_per_event cap. This — not is_in_play — is the event-level answer to 'is anything here tradable right now?', and it is what live_only filters on.",
          "title": "Live Contract Count",
          "type": "integer"
        },
        "contracts_truncated": {
          "default": false,
          "description": "True when max_contracts_per_event dropped contracts that would otherwise have been returned",
          "title": "Contracts Truncated",
          "type": "boolean"
        }
      },
      "required": [
        "event_ticker"
      ],
      "title": "GeminiEventSummary",
      "type": "object"
    }
  },
  "description": "Result of search_markets tool.",
  "properties": {
    "success": {
      "title": "Success",
      "type": "boolean"
    },
    "events": {
      "items": {
        "$ref": "#/$defs/GeminiEventSummary"
      },
      "title": "Events",
      "type": "array"
    },
    "count": {
      "default": 0,
      "description": "Number of events in this page, after the live_only filter",
      "title": "Count",
      "type": "integer"
    },
    "total_matched": {
      "anyOf": [
        {
          "type": "integer"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Total events matching the server-side query/category/status filters, BEFORE the client-side live_only filter. Not a count of live events. Null when Gemini omitted the pagination block.",
      "title": "Total Matched"
    },
    "scanned": {
      "default": 0,
      "description": "How many server-side matched events this call examined",
      "title": "Scanned",
      "type": "integer"
    },
    "has_more": {
      "default": false,
      "description": "True when next_offset is still below total_matched, i.e. more MATCHED events remain to scan. It does not promise that any of them will survive live_only — a follow-up page can legitimately return zero events.",
      "title": "Has More",
      "type": "boolean"
    },
    "next_offset": {
      "anyOf": [
        {
          "type": "integer"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Server offset of the first event not examined. Pass as start_offset to continue. None ONLY when the matched set really ran out — an empty page, or an offset past total_matched. It is still set when Gemini omitted the pagination total, because the offset is exact either way; has_more is false there simply because there is no total to compare against.",
      "title": "Next Offset"
    },
    "note": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Set when the page scan stopped early — scan cap, invocation deadline, or a page that failed mid-scan. None means the scan ended on its own terms (limit reached or matched set exhausted).",
      "title": "Note"
    },
    "error": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Error message if operation failed",
      "title": "Error"
    }
  },
  "required": [
    "success"
  ],
  "title": "SearchMarketsResult",
  "type": "object"
}