Browse documentation
Programmatic access
Call FactIQ from your own scripts, notebooks, and services with an API key. No MCP client and no OAuth flow are required.
Start here
REST for scripts
https://api.factiq.com/tools. Use them from curl, Python, or any HTTP client.MCP for agents
https://api.factiq.com/mcp. Any MCP SDK works once it sends the key as a bearer token.Read-only data
Get an API key
- 01
Open Security settings
Sign in to FactIQ and open Settings → Security. - 02
Generate the key
Under API key, chooseGenerate API key. The key starts withfiq_. - 03
Copy it now
FactIQ stores only a hash of the key. After you leave the page, the full key cannot be shown again. If you lose it, regenerate it. - 04
Store it as a secret
Put the key in an environment variable such asFACTIQ_API_KEY, or in your secret manager. The examples on this page read it from that variable.
Authenticate a request
Authorization: Bearer fiq_...A request without a valid key gets 401 Unauthorized. A key that belongs to a blocked account gets 403 Forbidden. The key acts as your account, so it sees exactly the schemas and datasets your account can see.
Check that the key works
This request returns the list of schemas, the dataset index for each schema, and the shared table structure. It is the same information the FactIQ plugin loads first.
curl https://api.factiq.com/tools/context \
-H "Authorization: Bearer $FACTIQ_API_KEY"REST endpoints
https://api.factiq.com/tools. POST bodies are JSON. Responses are JSON. The exact request and response schemas are published in the interactive reference at https://api.factiq.com/docs and as an OpenAPI document at https://api.factiq.com/openapi.json.| Endpoint | Body or query | What it returns |
|---|---|---|
GET /tools/context | — | Every schema, a compact per-schema dataset index, and the shared table structure. Call it once before exploring. |
POST /tools/search_datasets | { "query", "schemas"?, "limit" } | Keyword search over dataset titles and topics across all schemas. |
GET /tools/describe_dataset/{schema}/{dataset_code} | — | Full metadata for one dataset: methodology, dimensions, example series. |
POST /tools/search | { "queries": [{ "schema", "terms": [] }], "limit" } | Substring search on series titles within a schema. |
GET /tools/series/{schema}/{series_id} | ?from_year&to_year&sample&transform | One series with its observations and metadata. |
POST /tools/sql | { "schema", "sql", "sample", "max_rows" } | Read-only SQL against one schema. SELECT statements only. |
POST /tools/market | { "asset", "data_type", "frequency", "limit" } | Price history, quotes, company and ETF profiles, symbol search. |
POST /tools/geo_data | { "dataset", "region", "start_date", "end_date", "aggregation" } | A satellite-derived indicator aggregated over a named region. |
POST /tools/company_filings | { "company", "search_target", "concept", ... } | Filing coverage, reported facts and metrics, management commentary, risk-factor changes. |
POST /tools/earnings | { "query", "search_target", "ticker", ... } | Structured earnings-call claims, Q&A pressure points, coverage. |
POST /tools/media | { "query", "search_target", "company_filter", "person", ... } | Executive media appearances and the claims made in them. |
POST /tools/news | { "query", "tickers", "topic", "start_date", "end_date", "sort", "limit" } | News search by keyword, ticker, or topic. |
Run SQL
Every schema shares the same three tables: series (the catalog), data_points (the values) and dimensions (faceted metadata). Name the schema in the body and qualify table names in the statement. This example fetches the last three months of the US unemployment rate.
curl -X POST https://api.factiq.com/tools/sql \
-H "Authorization: Bearer $FACTIQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"schema": "bls",
"sql": "SELECT series_id, time, value FROM bls.data_points WHERE series_id = '"'"'LNS14000000'"'"' ORDER BY time DESC LIMIT 3",
"sample": false
}'{
"schema_name": "bls",
"row_count": 3,
"columns": ["time", "Seasonally adjusted unemployment rate for people aged 16 years and over"],
"results": [["2026-08-01", 4.1], ["2026-07-01", 4.1], ["2026-06-01", 4.2]],
"transformed_query": "...",
"exclude_from_data_panel": false
}sample defaults to true, which returns a preview sized for an LLM context window. Set it to false to receive full rows. A single call returns at most 5,000 rows; larger results come back with truncated: true and a note asking you to narrow the query. Each statement is limited to 30 seconds.
Fetch one series
When you already know a series id, the series endpoint returns its observations with the title, units and description attached.
curl "https://api.factiq.com/tools/series/bls/LNS14000000?from_year=2026&sample=false" \
-H "Authorization: Bearer $FACTIQ_API_KEY"From Python
Any HTTP client works. This example uses the requests library.
import os, requests
headers = {"Authorization": f"Bearer {os.environ['FACTIQ_API_KEY']}"}
r = requests.post(
"https://api.factiq.com/tools/sql",
headers=headers,
json={
"schema": "bls",
"sql": "SELECT time, value FROM bls.data_points WHERE series_id = 'LNS14000000' ORDER BY time DESC LIMIT 12",
"sample": False,
},
timeout=60,
)
r.raise_for_status()
for time, value in r.json()["results"]:
print(time, value)MCP endpoint without a client
https://api.factiq.com/mcp accepts the same API key. It speaks JSON-RPC over plain HTTP POST, so you can call it without an MCP SDK when you want the exact tool names and argument shapes the FactIQ plugin uses.Every POST is independent: there is no session to open and no session id to carry between calls. Responses are plain JSON, not a stream. Send an Accept header that lists both application/json and text/event-stream; the endpoint answers 406 without it.
List the tools
curl -X POST https://api.factiq.com/mcp \
-H "Authorization: Bearer $FACTIQ_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}'The result names each tool and gives its JSON schema for arguments: get_data_catalog, search_datasets, describe_dataset, search_series, get_series, run_sql, get_market_data, get_geo_data, search_company_filings, search_earnings_transcripts, search_media_appearances, search_news, get_style_guides and send_feedback.
Call a tool
curl -X POST https://api.factiq.com/mcp \
-H "Authorization: Bearer $FACTIQ_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "run_sql",
"arguments": {
"schema": "bls",
"sql": "SELECT series_id, time, value FROM bls.data_points WHERE series_id = '"'"'LNS14000000'"'"' ORDER BY time DESC LIMIT 3"
}
}
}'{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [{ "type": "text", "text": "{\"row_count\": 3, ...}" }],
"structuredContent": {
"row_count": 3,
"columns": ["time", "Seasonally adjusted unemployment rate for people aged 16 years and over"],
"results": [["2026-08-01", 4.1], ["2026-07-01", 4.1], ["2026-06-01", 4.2]]
},
"isError": false
}
}structuredContent holds the parsed result. The content array holds the same result serialised as text for clients that cannot read structured output.
From an MCP SDK
Any MCP client library works when it sends the key as a bearer token. This example uses the Python fastmcp package.
import asyncio, os
from fastmcp import Client
from fastmcp.client.auth import BearerAuth
async def main():
auth = BearerAuth(os.environ["FACTIQ_API_KEY"])
async with Client("https://api.factiq.com/mcp", auth=auth) as client:
tools = await client.list_tools()
print([t.name for t in tools])
result = await client.call_tool(
"get_series",
{"schema": "bls", "series_id": "LNS14000000", "from_year": 2026},
)
print(result.structured_content["results"])
asyncio.run(main())Coding agents without a browser
For interactive use on your own machine, follow the Claude Code or Codex CLI guide; the browser sign-in is simpler there. Use the commands below when a sign-in prompt cannot be answered.
Claude Code
claude mcp add factiq https://api.factiq.com/mcp --transport http \
--header "Authorization: Bearer $FACTIQ_API_KEY"The header is stored in Claude Code's MCP configuration. Add --scope project to write it to a .mcp.json file in the current folder instead, and keep that file out of version control.
Codex CLI
codex mcp add factiq --url https://api.factiq.com/mcp \
--bearer-token-env-var FACTIQ_API_KEYCodex reads the key from the named environment variable at start-up, so the key never lands in config.toml.
Limits and errors
- Rate. Up to 10 requests per second per account. Faster callers get
429 Too Many Requests; wait a second and retry. - Monthly allowance. REST calls count against the monthly tool-call allowance of your plan. When it is used up the response is
429with the reset date indetail. See pricing for plan sizes. - Result size. SQL and series calls return at most 5,000 rows. Search-style endpoints cap
limitat 50. Aggregate in SQL rather than paging through raw rows. - Statement time. A SQL statement is cancelled after 30 seconds. Add a date filter or a
LIMITand try again. - Validation. A malformed body gets
422with the failing field named indetail. Note that/tools/sqlneeds bothschemaandsql.
Keep the key safe
- Send it only over HTTPS, and only to
api.factiq.com. - Never commit it to a repository or paste it into a shared prompt.
- If it leaks, open Settings → Security and regenerate it. The old key stops working at once.
- Requests made with the key are logged the same way as requests from the plugin. See the privacy policy.