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.

On this page

Start here

Create an API key on your Security settings page and send it as a bearer token. The same key works for the REST endpoints and for the MCP endpoint.

REST for scripts

Plain JSON endpoints under https://api.factiq.com/tools. Use them from curl, Python, or any HTTP client.

MCP for agents

The same tools the FactIQ plugin uses, at https://api.factiq.com/mcp. Any MCP SDK works once it sends the key as a bearer token.

Read-only data

Every data endpoint reads. SQL is limited to SELECT statements, and nothing on this page can change your account or your data.

Get an API key

Each FactIQ account holds one API key. The key is shown once, when it is created.
  1. 01

    Open Security settings

    Sign in to FactIQ and open Settings → Security.
  2. 02

    Generate the key

    Under API key, choose Generate API key. The key starts with fiq_.
  3. 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.
  4. 04

    Store it as a secret

    Put the key in an environment variable such as FACTIQ_API_KEY, or in your secret manager. The examples on this page read it from that variable.

Authenticate a request

Send the key in the Authorization header of every request. There is no sign-in call and no token exchange.
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

All endpoints live under 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.
EndpointBody or queryWhat 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&transformOne 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
  }'
Response
{
  "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

The MCP endpoint at 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"
      }
    }
  }'
Response
{
  "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

On a server, in CI, or inside a container there is no browser to finish the OAuth sign-in. Register the MCP server with the key instead.

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_KEY

Codex reads the key from the named environment variable at start-up, so the key never lands in config.toml.

Limits and errors

Both surfaces share one set of limits per account.
  • 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 429 with the reset date in detail. See pricing for plan sizes.
  • Result size. SQL and series calls return at most 5,000 rows. Search-style endpoints cap limit at 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 LIMIT and try again.
  • Validation. A malformed body gets 422 with the failing field named in detail. Note that /tools/sql needs both schema and sql.

Keep the key safe

The key is equivalent to your signed-in account. Treat it like a password.
  • 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.