API Documentation

Everything you need to integrate Synapse Discovery financial data into your applications.

Base URL

https://synapsediscovery.io/api/v1

133,000+ Securities

Stocks, ETFs, mutual funds, ADRs — including delisted (survivorship-free)

60+ Years History

Daily equity prices back to 1962

AI-Native

Built for agents and LLMs

Authentication

REST API requests require an API key (the MCP server also accepts a keyless anonymous trial). Include your key in the X-API-Key header.

Request Header
X-API-Key: sd_live_your_api_key_here
cURL Example
curl -H "X-API-Key: sd_live_xxx" \
  https://synapsediscovery.io/api/v1/entity/AAPL
⚠️

Keep your API key secure

Never expose your API key in client-side code or public repositories.

Quick Start

Python
import requests

# Set your API key
API_KEY = "sd_live_your_key"
BASE_URL = "https://synapsediscovery.io/api/v1"

headers = {"X-API-Key": API_KEY}

# Get Apple stock info
response = requests.get(f"{BASE_URL}/entity/AAPL", headers=headers)
data = response.json()
print(f"Company: {data['data']['name']}")

# Get historical prices
prices = requests.get(
    f"{BASE_URL}/prices/AAPL",
    headers=headers,
    params={"start": "2024-01-01", "limit": 30}
).json()

for price in prices['data'][:5]:
    print(f"{price['date']}: ${price['close']}")
JavaScript / Node.js
const API_KEY = 'sd_live_your_key';
const BASE_URL = 'https://synapsediscovery.io/api/v1';

async function getStockData(ticker) {
  const response = await fetch(`${BASE_URL}/entity/${ticker}`, {
    headers: { 'X-API-Key': API_KEY }
  });
  return response.json();
}

// Get Apple data
const apple = await getStockData('AAPL');
console.log(apple.data.name); // "Apple Inc"

Entities

Entities represent securities in our universe. Each entity has a unique synapse_id that remains constant even when tickers change.

GET /entity/{identifier}

Get entity by ticker symbol, CIK, or synapse_id.

Parameters

Name Type Description
identifier path Ticker (AAPL), CIK (0000320193), or synapse_id (UUID)
Response
{
  "data": {
    "synapse_id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Apple Inc",
    "current_ticker": "AAPL",
    "cik": "0000320193",
    "security_type": "common_stock",
    "status": "active",
    "sector": "Technology",
    "industry": "Consumer Electronics",
    "exchange": "NASDAQ"
  },
  "provenance": {...},
  "quality": {...},
  "cost": {...}
}
GET /entities

List entities with optional filtering.

Query Parameters

Name Type Description
security_type string common_stock, etf, mutual_fund, adr, reit, spac
status string active, delisted
sector string Technology, Healthcare, etc.
search string Search by name or ticker
page integer Page number (default: 1)
limit integer Results per page (max: 1000)

Prices

Historical OHLCV price data with multiple adjustment types.

GET /prices/{identifier}

Get historical prices for an entity.

Query Parameters

Name Type Description
start date Start date (YYYY-MM-DD)
end date End date (YYYY-MM-DD)
adjustment string raw, split_adjusted (default), total_return
limit integer Max results (default: 10000, max: 100000; 0 = full history). With start, bounds the EARLIEST N in-range rows; without it, the most-recent N. Truncated responses carry meta.next_start/next_end cursors.
Response
{
  "data": [
    {
      "date": "2026-01-24",
      "open": 195.50,
      "high": 198.75,
      "low": 194.20,
      "close": 198.42,
      "volume": 45892100
    }
  ],
  "provenance": {
    "source": "multi_source",
    "adjustment_model": "split_adjusted"
  }
}

Adjustment Types:
raw - Unadjusted prices
split_adjusted - Adjusted for stock splits only
total_return - Adjusted for splits and reinvested dividends

Fundamentals

Standardized financial statements parsed from SEC filings (10-K, 10-Q).

GET /fundamentals/{identifier}

Query Parameters

Name Type Description
periods integer Number of periods (default: 8)
period_type string Q (quarterly) or FY (annual)
as_of date Point-in-time query date

Available Fields

revenue
net_income
gross_profit
operating_income
ebitda
eps_basic
eps_diluted
total_assets
total_liabilities
total_equity
free_cash_flow
operating_cash_flow

Earnings & Estimates

Two estimate series with two different floors. Read this section once so you never mistake one for the other.

GET /entity/{identifier}/earnings-history QUANT

One row per reported fiscal quarter: eps_actual, eps_estimate (the consensus as it stood at the report), eps_difference, surprise_percent, report_date, timing. Quarterly and deep — to roughly 2004 for the deep names.

Share basis. Per-share levels are served as reported. The upstream feed restates history onto today's share basis (dividing past quarters by future splits — a look-ahead), so each row is decided against the as-filed SEC eps_diluted; eps_restatement says how (none_needed · verified · verified_as_reported · unverified · conflict), and a conflict row serves null levels rather than an undecidable basis. surprise_percent is a ratio and is never adjusted.

Point-in-time. as_of keeps only quarters whose report_date is on or before that date — the actual/estimate pair is knowable at the report. start/end bound period_end. Oldest-first, with the standard window cursors.

GET /entity/{identifier}/estimates QUANT

kind=snapshot (default) — the daily-captured consensus: EPS and revenue avg/low/high, analyst counts, growth, the EPS trend 7/30/60/90 days ago and revision counts. One row per target fiscal period (estimate_date) per capture day (captured_at, the honest as-of). Snapshots exist from 2026-02-19 only. Daily capture began then, and no licensed vendor sells historical consensus snapshots — that floor is a real ceiling, not a backfill gap.

kind=pre_report — the consensus at each report, as an as-of-dated observation series: as_of = report date, target_period = the reported quarter, eps_estimate beside eps_actual. Quarterly, one observation per report, history to roughly 2004. It is not a forward-year snapshot and not a daily series.

as_of, start and end bound the observation date (captured_at for snapshots, the report date for pre-report); target_period keeps one target fiscal period. Every response's meta states both floors and which series you are holding. Pages never split an observation date, so the date cursors are safe to walk.

Corporate Actions

GET /corporate-actions/{identifier}

Stock splits, dividends, and other corporate actions.

Query Parameters

action_type stock_split, cash_dividend, stock_dividend, spin_off
start Start date filter
end End date filter

Fund Holdings

GET /holdings/{identifier}

Get ETF or mutual fund holdings.

Example: GET /holdings/SPY
{
  "data": [
    {
      "name": "Apple Inc",
      "ticker": "AAPL",
      "shares": 169547850,
      "market_value": 33659841250,
      "percentage": 7.12
    }
  ],
  "meta": {
    "fund_ticker": "SPY",
    "report_date": "2026-01-15"
  }
}

On this endpoint percentage is a percent (7.12 = 7.12% of NAV). The point-in-time endpoint /funds/{identifier}/holdings returns the same weight as pct_of_nav, a fraction (0.0712) — matching expense ratios and the bulk exports.

Compare Entities

GET /compare?tickers=AAPL,MSFT,GOOGL

Compare multiple entities on key metrics.

Parameters

tickers Comma-separated list (max 20)
metrics Comma-separated metrics (default: revenue,net_income,eps_diluted)

Fund Universe

Search and screen the full US ETF and mutual-fund universe, including dead funds. Start here, then use the returned ticker or synapse_id with the per-fund endpoints below.

GET /funds

Filters (all optional): fund_type (etf | mutual_fund), category, fund_family, asset_class, max_expense_ratio, q (name or ticker), status (active | dead | all — default active; pass dead or all for the survivorship-free universe), limit (default 50, max 200), offset.

Expense ratios are fractionsmax_expense_ratio=0.001 means 0.10%, and every response echoes meta.expense_ratio_units. The filter caps the ETF net_expense_ratio and the mutual-fund expense_ratio, while the expense_ratio shown on each row is the gross figure — so a fund with acquired-fund fees can display a value above your cap. Unknown parameters are never silently ignored: they come back in meta.ignored_params.

For a full walk of the universe pass order=id and follow meta.next_cursor — stable keyset paging that will not skip or repeat rows.

Example: GET /funds?fund_type=etf&max_expense_ratio=0.001&limit=2
{
  "data": {"funds": [
    {"ticker": "SPY", "name": "SPDR S&P 500 ETF Trust", "type": "etf",
     "category": "Large Blend", "expense_ratio": 0.000945,
     "total_assets": 624318000000.0}
  ]},
  "meta": {"count": 1, "has_more": true,
           "expense_ratio_units": "fraction (0.001 = 0.1%)"}
}

Fund Profile

Identity, fees, and our computed classifications for one fund. meta.coverage flags which fields are populated, so a client can render “—” honestly instead of guessing.

GET /funds/{identifier}

Returns family, category, expense ratios (gross, net, and net-of-acquired-fund-fees), yield, inception, AUM from SEC N-PORT, plus style_box, synapse_category and N-CEN structural_flags (money-market, target-date, leveraged/inverse, fund-of-funds).

These classifications are our own. synapse_category, style_box and synapse_star_rating are computed in-house from SEC filings and our own prices. They are first-party — ours, computed from our own inputs, and not interchangeable with any industry-standard classification.

Related
GET /funds/{identifier}/classification   portfolio weight breakdowns
GET /funds/{identifier}/distributions   dividends + capital gains
GET /funds/categories                   the category vocabulary + counts

NAV History

Daily or month-end NAV, in raw, vendor-adjusted, or self-computed point-in-time total-return form.

GET /funds/{identifier}/nav

Parameters

adjustment: raw (NAV only) · adjusted (default, vendor total return) · total_return / pit (self-computed, no look-ahead). frequency=monthly resamples to month-end server-side. start, end, limit (default 10000, max 100000; 0 = all rows within your tier's historical window — Free 1 year, Developer 5 years, Quant full history), offset, order. meta.tier_clipped and meta.tier_floor_date report when that window truncated your result.

Windowing

The window anchors to the end you pinned. With start, limit bounds the earliest N rows in range and you page forward with meta.next_start. Without start, you get the most-recent N and page backward with meta.next_end. order only re-arranges the page — it never changes which rows are selected.

Exception: adjustment=total_return|pit withholds both cursors. The PIT index re-bases at the filtered start, so following a cursor would splice two different bases; page that mode with offset instead.

Response
{
  "data": [{"date": "2024-01-02", "nav": 100.0, "nav_raw": 100.0}],
  "meta": {"ordering": "date_asc", "window_anchor": "start",
           "next_start": "2024-02-15", "next_end": null,
           "has_more": true, "frequency": "daily"}
}

Holdings — point in time

The complete position-level portfolio as filed on SEC N-PORT, not a top-ten snapshot. Coverage begins 2019-Q4, the first quarter of the SEC's public N-PORT structured data. Earlier holdings were disclosed on Forms N-Q and N-CSR, which we do not currently ingest.

GET /funds/{identifier}/holdings

as_of=YYYY-MM-DD returns the latest portfolio that was public on or before that date — cut on filing date, not report date, so a backtest never sees a portfolio before it was filed. limit default 500 (max 5000), offset.

Each position carries the issuer ticker, country, shares/par, market value, pct_of_nav, and sector — the issuer's synapse_sector (first-party, derived from the issuer's SEC SIC code) for equities, a deterministic bond segment for fixed income and cash, and null where we cannot classify the position honestly. The first page also carries meta.sector_allocation, whole-portfolio weights. Fixed income and cash resolve to their own segments (Treasury, Agency, Corporate Bond, Municipal, Sovereign, Bank Loan, Cash) from the position's own N-PORT fields, so a bond fund gets a meaningful breakdown rather than one blob; only positions we genuinely cannot classify roll into Unclassified, keeping the weights a true fraction of NAV.

Units differ between the two holdings endpoints — do not copy values between them. Here pct_of_nav is a fraction (0.0712 = 7.12%). The legacy /holdings/{identifier} returns the same quantity as percentage, multiplied by 100 (7.12).

Response
{
  "data": [{"name": "Apple Inc", "ticker": "AAPL", "sector": "Information Technology",
            "shares_or_par": 178000000, "market_value": 33900000000.0,
            "pct_of_nav": 0.0712}],
  "meta": {"report_date": "2021-06-30", "filing_date": "2021-08-27",
           "point_in_time": true, "total_holdings": 503,
           "sector_allocation": [{"sector": "Information Technology", "weight": 0.2841}]}
}

Ratings

Trailing returns, risk statistics and our 1–5 star rating, computed from our own NAV history; the expense percentile from SEC-filed expense ratios within category.

GET /funds/{identifier}/ratings

Returns, volatility and max drawdown are decimals (0.12 = 12%; max_drawdown is negative, so -0.34 is a 34% drawdown). Sharpe is a plain dimensionless ratio, not a percentage. expense_percentile is a fraction 0–1 within category, where lower is cheaper.

synapse_star_rating is our own: the within-category percentile of the Sharpe ratio from our NAV history. It is a first-party rating — ours, computed from our own data, and not interchangeable with any industry-standard score. It is honestly null for young funds and thin categories — meta.coverage tells you which, so render “—” rather than zero. A fund with no ratings row at all returns data: null and meta.available: false (with no coverage block) — check that first.

Use POST /funds/ratings/batch to fetch many funds in one call.

Fund Corporate Actions

Fund reverse and forward splits captured directly from the SEC prospectus filing family (497, 485, 424B, 486, N-14, N-8F) — including the fund splits commercial feeds miss.

GET /funds/{identifier}/corporate-actions QUANT

Parameters: action_type, limit (default 100, max 500), offset.

Splits are served only when arithmetically verified against the raw price series — a real 1-for-N reverse makes the per-share value jump about N×. Regex or model confidence alone is never enough. That is a deliberate precision floor: recall is bounded by that series' coverage, so a real split we cannot verify is withheld rather than served wrong.

Ratios are (ratio_num, ratio_den) = (new, old) shares; a reverse split is ratio_num < ratio_den. Today only splits are extracted as structured actions. Liquidations, mergers and name/ticker/fee changes exist so far as captured raw filings and are not yet in this feed.

Insider Transactions

Form 4 and 5 transactions from 2021 onward, resolved person-centrically: one insider CIK followed across every issuer they file against, not just one company's filings.

GET /insiders/transactions QUANT

Filters

Required — at least one of ticker (issuer) or insider (CIK, or a name). Optional: code · is_10b5_1 · start / end · limit (default 50, max 200) · offset.

Transaction codes

The common ones: S open-market sale · P open-market purchase · A grant or award · M option exercise · F shares withheld for tax · G gift · C conversion. Other SEC codes appear in the data and are returned unfiltered; the parameter accepts any single letter and is not validated against a whitelist.

Two things that will bite you

start/end and the ordering apply to transaction_date — the trade date, not filing_date, the date the market learned of it. For a point-in-time study, filter on the filing_date field in the returned rows.

Rows are newest-first and there is no order parameter — the opposite of /prices and /funds/{id}/nav, which are oldest-first. Sort before computing anything sequential.

is_10b5_1 is tri-state: true, false, or null. Null is honest and means the SEC checkbox did not exist for that filing — it is never silently reported as false. The flag was introduced in 2023, so it is null for essentially everything before then. It is declared at the filing level and denormalized onto every row of that form — it tells you the filing asserted Rule 10b5-1(c) reliance, not that this specific line did.

Ownership & Roster

Who the insiders are at an issuer, and what they currently hold.

GET /entity/{identifier}/insiders QUANT

The roster: every person or entity that has filed a Form 3/4/5 against this issuer, with their role (officer, director, 10% owner) and title from their latest non-superseded filing.

GET /entity/{identifier}/insider-ownership QUANT

Current beneficial ownership of non-derivative (Table I) positions only — options and RSUs are not included. One row per (insider, security class, direct/indirect), so someone holding common stock directly and also through a trust appears as two rows; sum them yourself for a person-level total. Ordered by shares_owned descending, limit default 100 (max 500).

shares_owned is a raw share count, not a percentage of the company. as_of is per-row staleness — the transaction date of the most recent non-superseded filing for that position, falling back to period-of-report then filing date. It is a trade date, not a query date. This endpoint is current-state only; it has no as_of parameter.

Form 144 & Lock-ups

The two insider surfaces that point forward rather than backward.

GET /insiders/form144 QUANT

Notices of proposed sale — an insider declaring an intent to sell restricted stock, with the broker of record and the relationship to the issuer. Electronic coverage is near-complete from April 2023 onward; 2022 is partial.

GET /insiders/lockups QUANT

IPO lock-up expiry calendar extracted from SEC 424B4 final prospectuses — the dates on which insider shares become sellable. The lock-up period is model-extracted from the prospectus body and every row carries extraction_confidence; expiry = prospectus date + lock-up days. Coverage is recent IPOs with live or upcoming lock-ups.

Index Constituents

Point-in-time index membership — who was in the index on a given date. SD2000 carries real reconstitution-derived add and remove history and is genuinely survivorship-free. SPX, NDX and DJI membership is reconciled from public lists, and many of those rows carry a placeholder added_date flagged by is_synthetic_added_date — check that field before treating their history as point-in-time.

GET /index/{symbol}/constituents

as_of=YYYY-MM-DD (default today), limit (default 0 = the full book, max 5000), offset.

This route rejects unknown parameters with a 400 rather than ignoring them, which is stricter than the rest of the API. Two consequences: authenticate with the X-API-Key header (a ?api_key= query parameter is a 400 here), and ?envelope=false is rejected as unrecognized — send Accept: application/json+raw instead if you want an unenveloped response here.

weight is a fraction (0.002914 = 0.2914%) and is the weight at addition, not a live weight. It is populated for SD2000 only — SPX, NDX and DJI return weight: null and, despite meta.ordering = weight_desc, fall through to alphabetical ticker order.

GET /indexes lists which symbols actually carry membership data, with each index's provenance. SD2000 is our own rules-based US small-cap index, reconstituted annually with real add and remove history — it is not the Russell 2000 and is not derived from it.

Example: GET /index/SD2000/constituents?as_of=2022-06-30
{
  "data": [{"ticker": "ACLS", "synapse_id": "…", "weight": 0.002914,
            "added_date": "2021-06-25", "is_synthetic_added_date": false}],
  "meta": {"index_symbol": "SD2000", "asof": "2022-06-30",
           "ordering": "weight_desc"}
}

Economic Series (US Treasury / Fed / BLS / BEA)

Access economic data sourced directly from the primary public publishers — US Treasury (yield curves), Federal Reserve releases (rates), BLS (inflation, employment), BEA (GDP) and EIA (energy).

GET /macro/series/{series_id}

Get data for an economic series.

Popular Series

fed_funds - Fed Funds Rate
treasury_10y - 10Y Treasury
cpi - Consumer Price Index
unemployment - Unemployment Rate
gdp - GDP
sp500 - S&P 500
GET /macro/series

List all available economic series with optional category filter.

Yield Curve

GET /macro/yield-curve

Treasury yield curve with spreads and inversion detection.

{
  "data": {
    "date": "2026-01-24",
    "yields": {
      "1M": 5.25, "3M": 5.30, "6M": 5.20,
      "1Y": 5.00, "2Y": 4.50, "5Y": 4.20,
      "10Y": 4.10, "30Y": 4.30
    },
    "spread_2_10": -40,
    "is_inverted": true
  }
}

Market Context

GET /macro/market-context?date=2026-01-24

Aggregated market indicators for a specific date. Perfect for backtesting context.

Flexible Query (POST /query)

The "thick" endpoint for querying multiple entities with mixed data types in a single request.

POST /query
Request Body
{
  "universe": {
    "tickers": ["AAPL", "MSFT", "GOOGL"]
  },
  "fields": [
    "close_split_adj",
    "volume",
    "revenue",
    "eps_diluted"
  ],
  "range": {
    "start": "2024-01-01",
    "end": "2025-12-31"
  },
  "point_in_time": true,
  "asof": "2026-01-01T00:00:00Z"
}

Universe Options

  • tickers - List of ticker symbols
  • synapse_ids - List of synapse IDs
  • exchange - Filter by exchange (NASDAQ, NYSE)
  • security_type - Filter by type
  • sector - Filter by sector

Pro Tip: Use /query/fields (no auth required) to see all available fields and example queries.

Point-in-Time Queries

Query data as it was known at a specific historical date—essential for backtesting without look-ahead bias.

Point-in-Time Example
# What fundamentals were available on Jan 1, 2020?
GET /fundamentals/AAPL?as_of=2020-01-01&periods=4

# Returns only data that was filed BEFORE Jan 1, 2020
# No Q4 2019 data (filed in early 2020) - prevents look-ahead bias

⚠️ Look-Ahead Bias: Without point-in-time queries, your backtest might use data that wasn't actually available at the time, leading to unrealistic results.

Data Lake Sync

Download complete datasets in Parquet format for local querying. Requires Quant tier or higher.

GET /sync/status

Check current snapshot and available data.

GET /sync/changes

Get incremental changes since last sync.

GET /export/manifest

Complete file listing with checksums.

GET /export/signed-url

Get signed URL for file download.

CLI Sync Example
# Install the sync client (package not yet on PyPI)
pip install synapse-discovery

# Initialize with your API key
synapse-discovery init --api-key sd_live_xxx

# Sync prices and fundamentals locally
synapse-discovery sync --to ./data --datasets prices,fundamentals

# Query with DuckDB
duckdb -c "SELECT * FROM './data/prices/NASDAQ/AAPL/*.parquet'"

AI-First Design

Every response includes metadata for AI agents: provenance, quality metrics, and cost tracking.

AI-Friendly Response Structure
{
  "data": {...},
  
  // Where did this data come from?
  "provenance": {
    "source": "multi_source",
    "collection_time": "2026-01-24T06:00:00Z",
    "adjustment_model": "split_adjusted"
  },
  
  // How reliable is this data?
  "quality": {
    "completeness": 0.98,
    "staleness_hours": 18
  },
  
  // Usage tracking
  "cost": {
    "units_consumed": 1,
    "cache_hit": true
  }
}

Discovery Endpoints

Self-describing endpoints for AI agents. No authentication required.

GET /meta

Platform capabilities—datasets, coverage, update schedules.

GET /schema

Field definitions, types, and JSON schema for all datasets.

GET /resolve?q=apple

Fuzzy search to resolve names/tickers to synapse_id with confidence scores.

GET /examples

Runnable example requests and expected responses.

GET /docs/ai

AI-readable documentation in structured JSON format.

MCP Protocol Integration

Native Model Context Protocol support for Cursor, Claude Desktop, and other MCP-compatible AI tools.

MCP Configuration (Claude Desktop / Cursor)
{
  "mcpServers": {
    "synapse-discovery": {
      "url": "https://synapsediscovery.io/mcp/jsonrpc",
      "headers": {
        "X-API-Key": "sd_live_your_key"
      }
    }
  }
}

MCP Tools

A selection of the 28 typed tools — the full, always-current list is served at /mcp/tools.

get_financial_data
compare_companies
get_price_history
get_fund_holdings
get_economic_series
get_yield_curve
search_entities

Error Handling

Errors include structured codes, messages, and suggestions for AI agents.

{
  "error": {
    "code": "ENTITY_NOT_FOUND",
    "message": "No entity found for identifier: APPL",
    "suggestion": "Did you mean one of these?",
    "similar": ["AAPL", "APL", "APLE"]
  }
}

HTTP Status Codes

Code Description
200 Success
400 Bad request (invalid parameters)
401 Authentication required or invalid API key
403 Forbidden (feature not available on your tier)
404 Resource not found
429 Rate limit exceeded
500 Internal server error

Rate Limits

Tier Daily Limit Burst Rate Bulk Download
Free 100 calls/day 10/min
Developer 10,000 calls/day 60/min
Quant 100,000 calls/day 300/min
Enterprise Unlimited 1000/min

Rate Limit Headers

X-RateLimit-Limit: 10000
X-RateLimit-Remaining: 9850
X-RateLimit-Reset: 1706140800

SDKs & Libraries

Python

pip install synapse-discovery

Not yet published to PyPI — use the REST API or MCP server today.

from synapse_discovery import SynapseClient

client = SynapseClient("your-key")
aapl = client.get("AAPL")
prices = client.prices("AAPL")

CLI (Data Lake)

pip install synapse-discovery

Not yet published to PyPI — use the REST API or MCP server today.

synapse-discovery init --api-key xxx
synapse-discovery sync --to ./data
synapse-discovery validate ./data

Need Help?

Can't find what you're looking for? Our team is here to help.