On this page
API Documentation
Everything you need to integrate Synapse Discovery financial data into your applications.
Base URL
https://synapsediscovery.io/api/v1
58,000+ listings with prices
Stocks, ETFs, mutual funds, ADRs — delisted securities included
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.
X-API-Key: sd_live_your_api_key_here
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
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']}")
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.
/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) |
{
"data": {
"synapse_id": "65b316ed-2573-440f-a1ea-5e1ec5252c7b",
"name": "Apple Inc.",
"current_ticker": "AAPL",
"cik": "0000320193",
"security_type": "common_stock",
"status": "active",
"sector": "Information Technology",
"industry": "Industrial Machinery & Equipment",
"exchange": "NASDAQ"
},
"provenance": {...},
"quality": {...},
"cost": {...}
}
/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.
/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. |
{
"data": [
{ "date": "2008-09-12", …, "close": 3.65, …, "adjustment": "raw" },
{ "date": "2008-09-15", …, "close": 0.21, …, "adjustment": "raw" }
],
"meta": { "ticker": "LEH", "count": 2, … }
}
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).
/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
revenuenet_incomegross_profitoperating_incomeebitdaeps_basiceps_dilutedtotal_assetstotal_liabilitiestotal_equityfree_cash_flowoperating_cash_flowEarnings & Estimates
Two estimate series with two different floors. Read this section once so you never mistake one for the other.
/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. row_source says where a row comes from: earnings_feed, or sec_filing — a delisted line's own first-filed SEC diluted EPS for a quarter the feed holds none for (eps_restatement sec_as_filed, report_date = the filing date, no consensus: eps_estimate is null with eps_estimate_unavailable_reason).
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.
/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 there is no earlier history to backfill — that floor is real, 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
/corporate-actions/{identifier}
Stock splits, dividends, and other corporate actions.
Query Parameters
action_type |
split (every split), reverse_split (served is_reverse true only), forward_split (served is_reverse false only), dividend |
start |
Start date filter |
end |
End date filter |
Split factor convention
factor is NEW shares per OLD share for that one event: 2.0 is a 2-for-1 forward split, 0.2 is a 1-for-5 reverse split. ratio is the same event written new:old (1:5). To put a price from BEFORE the event on the post-event basis, multiply it by 1/factor — SFY on 2024-10-02: factor 0.2, pre-event close 20.515 × 5 = 102.575 against the 102.84 print. This per-event factor is not the cumulative split_factor column on /prices, which already multiplies every later event together.
is_reverse is served from the factor and the raw price tape, never from a stored column: true exactly when factor < 1 and the raw price tape does not contradict it. direction_witness says what the raw price did across the date (tape_agrees, no_tape, below_witness_threshold, tape_contradicts, tape_silent); on the last two is_reverse is null with is_reverse_unavailable_reason, and likewise (factor_not_a_share_ratio) for a factor below 1 that is not a share ratio (a spin-off price adjustment) unless the tape itself agrees. ratio is null with ratio_unavailable_reason when the factor is not a simple share ratio.
Fund Holdings
/holdings/{identifier}
Get ETF or mutual fund holdings.
{
"data": [
{
"name": "Tesla Inc", …,
"shares": 2521820.0,
"market_value": 978567032.8,
"percentage": 10.1478
}, …
],
"meta": {
"fund_ticker": "ARKK",
"report_date": "2020-10-30",
"total_holdings": 49,
"source": "sec_nport",
"point_in_time": true, …
}
}
On this endpoint percentage is a percent (10.1478 = 10.1478% of NAV). The point-in-time endpoint /funds/{identifier}/holdings returns the same weight as pct_of_nav, a fraction (0.101478) — matching expense ratios and the bulk exports.
Compare Entities
/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). pe_ratio — the as-traded close over trailing-twelve-month diluted EPS from filings public on that date, never negative, a null always with pe_ratio_unavailable_reason — is served only when named here. |
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.
/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 to include closed and delisted funds), limit (default 50, max 200), offset.
Expense ratios are fractions — max_expense_ratio=0.001 means 0.10%, and every response echoes meta.expense_ratio_units. The filter compares exactly the expense_ratio each row shows — the gross (total annual operating) expense ratio, or the net where the filing carried no gross — for ETFs and mutual funds alike, so no returned row is above your cap; net_expense_ratio and net_expense_ratio_basis ride beside it, and meta.expense_ratio_basis names the rule. 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.
A fund we do not hold says why. Every week we compare the SEC's list of registered fund share classes with the funds our price source lists. A fund symbol that does not resolve on /funds/<ticker> (and on the MCP fund tools, /funds/nav/batch and /resolve) carries universe_exclusion_reason and a plain sentence where that comparison has one — for example a share class the SEC lists under a new symbol, with held_as naming the symbol we hold it under. The bulk files' population rule (no minimum history) is stated in /export/schema.
{
"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.
/funds/{identifier}
Returns family, category, expense ratios (gross, net, and net-of-acquired-fund-fees), yield, inception, AUM from SEC filings (where total_assets_scope is present it is the fund SERIES' net assets, shown on every share class of the series — never sum it across classes), 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.
GET /funds/{identifier}/classification portfolio weight breakdowns
GET /funds/{identifier}/distributions dividends + capital gains
GET /funds/categories the category vocabulary + counts
Holdings — point in time
The full position list 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.
/funds/{identifier}/holdings
as_of=YYYY-MM-DD returns the newest report we hold that was public on or before that date — cut on filing date, not report date, so a backtest is not handed 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. Each position also carries issuer_synapse_id — our own id for the issuer, decided once per security and report date against the filer's own implied price — or null with issuer_unresolved_reason when we refuse to guess, and the ticker valid on the report date with its ticker_basis: journal (dated evidence), filer_reported (the fund's own filing), or current_carried_back (today's symbol carried back: nothing we hold dates it, and nothing shows it was assigned later). Where our journal or the issuer's own price tape shows today's symbol was assigned after the report date, ticker is null with ticker_unavailable_reason — never today's symbol. The first page also carries meta.sector_allocation, whole-portfolio weights, and meta.issuer_resolution (unresolved equity rows, their share of NAV and the reasons). 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.101478 = 10.1478%). The legacy /holdings/{identifier} returns the same quantity as percentage, multiplied by 100 (10.1478).
{
"data": [{"name": "Tesla Inc", …, "issuer_unresolved_reason": null,
"shares_or_par": 2521820.0, "market_value": 978567032.8,
"pct_of_nav": 0.101478, …},
{"name": "Invitae Corp", "issuer_synapse_id": null,
"issuer_unresolved_reason": "no_ingest_link", "pct_of_nav": 0.071785, …}, …],
"meta": {"report_date": "2020-10-30", "filing_date": "2020-12-28",
"point_in_time": true, "total_holdings": 49,
"sector_allocation": […], "issuer_resolution": {…}, …}
}
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.
/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), each one checked against the fund's own NAV series before it is served.
/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.
/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.
/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.
/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, nature of ownership): someone holding common stock directly, through a trust and through an LLC appears as three rows, and every row carries that insider's total for the issuer in insider_total_shares_owned (the sum of their rows on today's share basis). A row the rule supersedes — the same holding re-titled or re-described in a later filing — is not summed and is listed only with include_superseded=true. 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.
/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.
/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, so a past date returns the members it had then, including names since removed. 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. Every row also says what
its dates rest on (added_date_basis,
removed_date_basis: public record, a vendor
snapshot, a corporate event = the security's last trading session, or our weekly
reconciliation's observation) and serves the index event dates we stand behind as
entry_date / exit_date
(null with a reason otherwise). Once our S&P 500 date reconciliation is applied, membership
from 2019-09-30 on is checked against the SEC N-PORT holdings of its three largest
tracking funds; the response's membership_dating_note
says whether it is applied yet.
/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 every index price series we hold — US headline benchmarks and their total-return twins (for example SP500TR, the S&P 500 Total Return), equal-weight, S&P 500 and Composite 1500 sectors, strategy, volatility, commodity, bond, rates, currency and non-US equity indices — each with its family, the return basis we serve (index_kind and how it was decided), its total-return counterpart and depth, first date and rows. Filter with family, index_kind, q; fetch a series from the prices endpoint under its caret symbol (^SP500TR: the caret names the index); MCP agents call list_indexes. Its meta.membership (or ?view=membership) 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; its membership is computed by our published rules from our own data, not taken from any third-party index.
{
"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 (primarily US Treasury / Fed / BLS / BEA / EIA)
Access economic data sourced primarily from the public publishers — US Treasury (yield curves), Federal Reserve releases (rates), BLS (inflation, employment), BEA (GDP) and EIA (energy). GET /macro/series lists every series we serve.
/macro/series/{series_id}
Get data for an economic series.
Popular Series
fed_funds - Fed Funds Ratetreasury_10y - 10Y Treasurycpi - Consumer Price Indexunemployment - Unemployment Rategdp - GDPsp500 - S&P 500/macro/series
List all available economic series with optional category filter.
Yield Curve
/macro/yield-curve
Treasury yield curve with the 2-year/10-year spread and an inversion flag. Yields and spread_2_10 are in percentage points.
{
"data": {
"date": "2020-03-16",
"yields": {
"3M": 0.24, "6M": 0.29, "1Y": 0.29,
"2Y": 0.36, "5Y": 0.49,
"10Y": 0.73, "30Y": 1.34
},
"spread_2_10": 0.37,
"inverted": false,
"pit_basis": "market_observed",
"pit_bases": ["market_observed"],
"pit_basis_note": "Treasury constant-maturity yields are market-observed and published the same day; they carry no revision vintages."
},
"meta": { "source": "economic_observations", "cached": false }
}
A date inside our stored daily curves (recent dates, or no date) is answered from the stored row instead: data is a one-element list, the flag is is_inverted, the 3-month/10-year spread rides beside it as spread_3m_10y, and pit_basis is captured.
Market Context
/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.
/query
{
"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
Ask for data as it had been published by a historical date: statements and fund holdings are cut at their filing date, macro vintages at the day each became public, built to keep look-ahead out of a backtest.
# What fundamentals were available on Jan 1, 2020?
GET /fundamentals/AAPL?as_of=2020-01-01&periods=4
# Returns only statements filed on or before 2020-01-01:
# no quarter ended 2019-12-28 (its 10-Q was filed 2020-01-29)
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.
Macro vintages: the grain, stated
GET /macro/series/<code>/point-in-time?as_of= filters on the date each
snapshot became public, never on the publisher's period label for it.
Vintages come from the Philadelphia Fed Real-Time Data Set: most series are
quarterly snapshots, taken on the 15th of the middle month of each
quarter, and the monthly-vintage series (payrolls, industrial production) are bounded
at their month end because the publisher does not fix a day.
So on a given as_of the newest knowable observation can legitimately be
up to a quarter old — the response states the grain, that snapshot's
vintage_available_from and the vintage_next_available_from
when the following snapshot landed. A vintage whose availability we cannot justify
is withheld, never dated by guess.
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.
import duckdb, requests
BASE = "https://synapsediscovery.io/api/v1"
H = {"X-API-Key": "sd_live_..."}
# 1. Read the manifest (re-read it every run — generation-versioned
# datasets change their paths on each nightly build).
m = requests.get(f"{BASE}/export/manifest", params={"universe": "sp500"}, headers=H).json()
paths = [f["path"] for f in m["data"]["datasets"]["fundamentals"]["file_list"]]
# 2. Exchange a path for a time-limited signed URL, then download it.
u = requests.get(f"{BASE}/export/signed-url", params={"path": paths[0]}, headers=H).json()
open("part.parquet", "wb").write(requests.get(u["data"]["url"]).content)
# 3. Query it locally — no further API calls.
print(duckdb.sql("SELECT period_end_date, revenue FROM 'part.parquet' ORDER BY 1 DESC LIMIT 4"))
POST /export/batch-signed-urls to sign many paths in one call, and GET /sync/changes to pull only what moved since your last snapshot.
AI-First Design
Every response includes metadata for AI agents: provenance, quality metrics, and cost tracking.
{
"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. /meta, /schema, /examples and /docs/ai need no key; /resolve takes your API key like every data endpoint.
/meta
Platform capabilities—datasets, coverage, update schedules.
/schema
Field definitions, types, and JSON schema for all datasets.
/resolve?q=apple
Fuzzy search to resolve names/tickers to synapse_id with confidence scores. Requires an API key.
/examples
Runnable example requests and expected responses.
/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. One-line setup for Claude Code, Cursor, VS Code and a claude.ai custom connector — keyless, or with your API key — is on the setup page.
{
"mcpServers": {
"synapse-discovery": {
"url": "https://synapsediscovery.io/mcp/jsonrpc",
"headers": {
"X-API-Key": "sd_live_your_key"
}
}
}
}
MCP Tools
A selection of the 30 typed tools — the full, always-current list is served at /mcp/tools.
get_financial_datacompare_companiesget_price_historyget_fund_holdingsget_economic_seriesget_yield_curvesearch_entitiesError 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. On /funds/<id>* routes, NOT_A_FUND means the identifier resolved to an entity our registry does not classify as a fund and for which we hold no fund record (the body names its security_type and entity_url) |
409 |
FUND_TICKER_AMBIGUOUS — the ticker names more than one fund; pass a synapse_id from fund_alternates |
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 | Included |
| Enterprise | By agreement | 1000/min | Included |
Rate Limit Headers
X-RateLimit-Limit: 10000
X-RateLimit-Remaining: 9850
X-RateLimit-Reset: 1706140800
Client code
There is no package to install: the REST API and the MCP server are the product, and both of the snippets below work today, as written.
Python (requests)
Standard library plus requests — nothing of ours to install.
import requests
BASE = "https://synapsediscovery.io/api/v1"
H = {"X-API-Key": "sd_live_..."}
aapl = requests.get(f"{BASE}/entity/AAPL", headers=H).json()["data"]
px = requests.get(f"{BASE}/prices/AAPL",
params={"start": "2024-01-01"},
headers=H).json()["data"]
MCP (Claude, Cursor, your own agent)
One line to connect; the JSON block above configures Claude Desktop and Cursor.
claude mcp add --transport http \
synapse \
https://synapsediscovery.io/mcp/jsonrpc
# add your plan's limits:
# --header "X-API-Key: sd_live_..."
Need Help?
Questions? Use the contact form; email works too.