The whole API is one endpoint.
POST SQL, get JSON rows back. The full reference fits on this page: two endpoints, five tables, and an MCP server.
First query
- 01Sign in at app.outcome.sh. Magic-link email, no password and no credit card.
- 02Create a key under API Keys in the dashboard. Keys are shown once.
- 03POST your SQL:
curl -X POST https://api.outcome.sh/v1/execute \
-H "Authorization: Bearer $OUTCOME_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "SELECT max(created_time) AS last_trade FROM trades"}'Playground
The playground runs the same queries in the browser, with the live schema, charts, and an AI analyst. No key required.
MCP
Claude, Cursor, and other MCP clients can explore the schema and run queries through the MCP server.
API keys
Every request authenticates with an API key in the Authorization header. Keys are created and revoked in the dashboard, belong to your team, and spend the team credit balance. The same key works for the execute endpoint and the MCP server.
Authorization: Bearer $OUTCOME_API_KEY- Each key is rate limited to 60 requests per second.
- A leaked key can spend your credits. Revoke it from the API Keys page.
GET /v1/schemais public and needs no key.
Execute a query
Runs a single read-only SQL statement against the warehouse and returns the rows as a JSON array. SELECT, WITH, EXPLAIN, and SHOW are permitted; anything that writes, touches system tables, or reaches external data is rejected before it runs.
Request body
A single read-only SQL statement. JSON responses are capped at 10,000 rows and 8 MiB, with a 1 MiB limit per row. Execution is limited to 60 minutes; available credits can impose a shorter limit. SQL SETTINGS, FORMAT, and INTO clauses are rejected.
When true, applies the ClickHouse FINAL modifier so markets and trades return merged, deduplicated rows instead of occasional pre-merge duplicates. Slower and scans more; use it only when exact dedup matters.
Allow at least 61 minutes for client request timeouts so the server can return a result or timeout error after the full query window.
Example
curl -X POST https://api.outcome.sh/v1/execute \
-H "Authorization: Bearer $OUTCOME_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "SELECT ticker, yes_price FROM markets ORDER BY volume_24h DESC LIMIT 5"}'[
{ "ticker": "KXMENWORLDCUP-26-ES", "yes_price": 0.999 },
{ "ticker": "KXTECHLAYOFFS-26", "yes_price": 0.911 },
{ "ticker": "KXUSAALIENS-27", "yes_price": 0.074 }
]Usage headers
Every successful response reports what the query cost:
Credits charged for this query.
Wall-clock execution time in milliseconds.
Bytes the warehouse scanned to answer the query.
Team balance after the charge. Omitted when the query was not charged.
HTTP/2 200
X-Credits-Used: 3
X-Duration-Ms: 118
X-Bytes-Read: 204811520
X-Credits-Remaining: 49997Streaming and exports
Both endpoints accept the same query and optional final fields as /v1/execute and use the same authentication and query billing. They avoid the JSON endpoint’s 10,000-row and 8 MiB response limits. Individual rows still have a 1 MiB limit: return ordinary rows instead of collecting results into a large groupArray or JSON string. Query memory and time limits still apply.
Stream NDJSON
/v1/execute/stream sends one row record per line, followed by a completion record with the final row count, usage, and billing. Consume it incrementally. A 200 status alone does not mean the query succeeded: reject error records, missing or unsuccessful completion, row-count mismatches, and truncated transfers. Usage comes from the completion record, not response headers. Disconnecting cancels database consumption.
{"type":"row","row":{"number":0}}
{"type":"complete","success":true,"row_count":1,"bytes_read":8,"duration_ms":12,"credits_used":1,"charged":true,"credits_remaining":99}Create a downloadable export
POST /v1/exports runs the query and writes a file to private storage. Set format to csv for spreadsheets or ndjson for JSON Lines (the API default). An optional name uses your query title for the download filename, normalized with a UTC timestamp; new JSON Lines files use .jsonl. Keep the request open until it returns 201 with id, download_url, format, and completion. The file is published only after successful completion. JSON Lines includes the final completion record; CSV contains column headers and data rows, with completion and billing in the API response. Download directly from the signed URL; loading the file into a browser array or Blob defeats the memory benefit. When the playground has complete results for the current query, Download full CSV saves those results without rerunning or using more credits. Otherwise it explains the cost before running a new export, preserving any SQL LIMIT.
curl --max-time 3660 https://api.outcome.sh/v1/exports \
-H "Authorization: Bearer $OUTCOME_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query":"SELECT number FROM numbers(20001)","format":"csv"}'CSV preserves query-column order, quotes commas and newlines, represents null as an empty cell, and writes nested values as JSON text. Formula-like string cells are prefixed with an apostrophe for spreadsheets; use JSON Lines for exact original text. The table view keeps its 10,000-row cap; downloads bypass that cap.
Save an existing JSON result array using its original response bytes as the body. Set ?format=csv or ?format=ndjson and an optional name. This stores the supplied data without running SQL or charging credits, and adds the file to Exports. The response uses the same completion contract with zero credits used. Uploads are limited to 10,000 rows, 8 MiB total, 1 MiB per row, and two minutes. CSV columns follow sorted JSON keys. The playground reuses only complete results matching the exact SQL and account; older results without that metadata cannot be reused safely. An upload failure never automatically starts a new query.
List your team’s completed files, including older exports and files whose creation response was interrupted. Each page contains up to 100 exports with id, format, filename, size_bytes, created_at, and expires_at. Pass the returned cursor as ?cursor=... to continue in storage-key order. The Exports page under Workspace and in the profile menu uses this endpoint.
Save the export ID. An authenticated GET returns a fresh download_url for a completed export owned by your team without rerunning or rebilling the query. Links last up to one hour and files are retained for seven days. Signed links grant access to the file; share them only with intended recipients.
If creation fails after returning X-Export-ID, check that ID before submitting another POST: the file may already be complete and the query billed. A lost connection before headers may leave the outcome unknown. Creating another export always starts a new query. A 404 lookup means no completed export is available to your team; a 503 can mean capacity is busy or exports are not configured. Cancellation requests stop ongoing work, but cannot undo a completed query or charge.
The live schema
Returns every queryable table with its columns and exact ClickHouse types. Public, no key required. Feed it to a code generator or an agent before it writes SQL.
curl https://api.outcome.sh/v1/schema{
"tables": [
{
"name": "markets",
"columns": [
{ "name": "venue", "type": "LowCardinality(String)" },
{ "name": "ticker", "type": "String" },
{ "name": "yes_price", "type": "Float64" },
...
]
},
...
]
}The tables
Kalshi is live today, more venues are on the way. The venue column tells rows apart, so queries keep working as venues are added.
Outcome is an analytical warehouse, not a trading feed: new markets, trades, and book updates land within five minutes of the venue. Build research and dashboards on it, not HFT.
Types below are simplified. For exact ClickHouse types, use GET /v1/schema.
markets
One row per market, updated continuously while it trades: title, status, current prices, volume, liquidity, and the rules text that defines resolution.
-- What does the market believe right now?
SELECT title, round(yes_price * 100, 1) AS chance
FROM markets
WHERE status = 'open'
ORDER BY volume_24h DESC
LIMIT 5- venuetext
- tickertext
- event_tickertext
- titletext
- yes_sub_titletext
- no_sub_titletext
- statustext
- resulttext
- market_typetext
- strike_typetext
- yes_pricefloat
- no_pricefloat
- yes_bidtext
- yes_asktext
- no_bidtext
- no_asktext
- volumefloat
- volume_24hfloat
- liquiditytext
- yes_liquidityfloat
- no_liquidityfloat
- notional_valuetext
- rules_primarytext
- rules_secondarytext
- open_timetimestamp
- created_timetimestamp
- close_timetimestamp
- expiration_timetimestamp
- expected_expiration_timetimestamp
- updated_timetimestamp
- is_deleteduint8
trades
Every trade at tick level, from a market opening to its resolution. Prices are the YES and NO contract prices in dollars, count is the number of contracts.
-- Replay one market's price path
SELECT created_time, yes_price
FROM trades
WHERE ticker = 'KXMENWORLDCUP-26-ES'
ORDER BY created_time- venuetext
- trade_idtext
- tickertext
- yes_pricefloat
- no_pricefloat
- countfloat
- taker_sidetext
- taker_book_sidetext
- taker_outcome_sidetext
- created_timetimestamp
- is_deleteduint8
orderbooks
The latest order book snapshot per market: best bids and asks on both sides, spreads, available liquidity, and the full depth levels as JSON.
-- Tightest markets right now
SELECT ticker, yes_bid, yes_ask, spread_yes
FROM orderbooks
WHERE spread_yes IS NOT NULL
ORDER BY spread_yes ASC
LIMIT 10- venuetext
- tickertext
- yes_bidfloat
- yes_askfloat
- no_bidfloat
- no_askfloat
- spread_yesfloat
- spread_nofloat
- yes_liquidityfloat
- no_liquidityfloat
- yes_levelstext
- no_levelstext
- updated_timetimestamp
historical_orderbooks
Every order book update, sequenced. The same shape as orderbooks plus a sequence_number, so you can reconstruct the book at any point in time.
-- Liquidity over time for one market
SELECT updated_time, yes_liquidity, no_liquidity
FROM historical_orderbooks
WHERE ticker = 'KXMENWORLDCUP-26-ES'
ORDER BY sequence_number- venuetext
- tickertext
- yes_bidfloat
- yes_askfloat
- no_bidfloat
- no_askfloat
- spread_yesfloat
- spread_nofloat
- yes_liquidityfloat
- no_liquidityfloat
- yes_levelstext
- no_levelstext
- sequence_numberbigint
- updated_timetimestamp
resolved_markets
The final result of every settled market, one row per resolution.
-- Resolution outcomes across all settled markets
SELECT result, count() AS markets
FROM resolved_markets
GROUP BY result- venuetext
- tickertext
- resulttext
The MCP server
A remote MCP server for Claude, Cursor, and any other MCP client: the agent discovers the schema and queries the market itself. Streamable HTTP (protocol 2025-06-18), authenticated with your API key, with the same read-only rules, rate limits, and billing as the execute endpoint. Nothing to install or host.
Connect a client
claude mcp add --transport http outcome https://api.outcome.sh/v1/mcp \
--header "Authorization: Bearer YOUR_API_KEY"Tools
List every public table with its columns and types. Takes no arguments.
Return the schema of a single table by name.
Fetch a bounded sample of rows (up to 100) from one table, optionally restricted to specific columns. The server generates the SQL; no arbitrary queries.
Execute a read-only SQL query with exactly the same rules, limits, and billing as POST /v1/execute. Returns rows plus row count, bytes read, credits used, and duration.
A typical session: list_tables once, a preview or two, then run_query. Billed tools report credits used in their results.
Credits and limits
1,000 credits equal one dollar. Only successful queries are charged: one base credit, plus one credit per 100 MB scanned, plus a time cost that rises steeply past a minute:
| query duration | approx. time cost |
|---|---|
| 1 second | 1 credit · $0.001 |
| 30 seconds | 11 credits · $0.011 |
| 60 seconds | 1,000 credits · $1.00 |
| 5 minutes | 5,000 credits · $5.00 |
Set spend limits and auto reload in Billing.
Errors
Errors return a JSON body with an error message and, where a machine needs to react, a stable code:
{
"error": "team spend limit exceeded",
"code": "team_spend_limit_exceeded"
}The body is malformed, the query is empty, the SQL fails to parse, or the statement is not read-only.
No Authorization header, or the key is wrong or revoked.
The team balance cannot cover the query, or the query ran past its byte budget.
The JSON result exceeds 10,000 rows or 8 MiB. Add a LIMIT, stream NDJSON, or create an export. Individual rows are limited to 1 MiB on every path.
The key exceeded 60 requests per second, or the query would exceed the spend limit set in Billing.
Billing is unavailable or query capacity is busy. Retry with backoff; check any returned export ID before creating another export.
The query hit the 60 minute limit or a shorter limit based on available credits.
Start querying
Magic-link sign in, no credit card.
Questions or missing data? Academics and researchers get free credits. Write to support@outcome.sh.