EDI Analytics API
v1
Quickstart OpenAPI

EDI Analytics API

Programmatic access to the same engines that power the EDI platform: security resolution across the US, Brazil, Mexico and Peru; end-of-day global prices; standardized fundamentals with fiscal-period intelligence; cross-domain as-of matrices; and asynchronous batch execution. One request produces the same number in Excel, Python, the Quant Workstation, and a scheduled report — parity is structural, not tested-for.

Base URL https://api.edi.finance Markets US · BR · MX · PE Format JSON (camelCase) Auth session cookie OpenAPI 3 spec ↗

Quickstart

Authenticate once — the session cookie carries your entitlements on every subsequent call.

bash
# 1. Sign in (stores the session cookie)
curl -c cookies.txt -X POST https://api.edi.finance/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "you@yourfirm.com", "password": "…"}'

# 2. Resolve identifiers to canonical entity ids
curl -b cookies.txt -X POST https://api.edi.finance/api/analytics/v1/resolve \
  -H "Content-Type: application/json" \
  -d '{"identifiers": [{"value": "PETR4-BR"}, {"value": "AAPL"}]}'

# 3. A cross-market comps grid in USD, one call
curl -b cookies.txt -X POST https://api.edi.finance/api/analytics/v1/matrix \
  -H "Content-Type: application/json" \
  -d '{"ids": ["AAPL-US","BBAS3-BR","BIMBOA-MX"],
       "metrics": ["close","MARKET_CAP","SECTOR"],
       "asOf": "2026-07-30", "currency": "USD"}'

Or with the Python reference client:

python
from edi_analytics import EdiAnalyticsClient, spill_matrix

client = EdiAnalyticsClient("https://api.edi.finance")
client.login("you@yourfirm.com", "…")

env = client.matrix(ids=["AAPL-US", "BBAS3-BR"],
                    metrics=["close", "MARKET_CAP", "SECTOR"])
grid = spill_matrix(env, ids=["AAPL-US", "BBAS3-BR"],
                    metrics=["close", "MARKET_CAP", "SECTOR"])

Service authentication — API keys

For scripts, SDKs, and server-to-server integrations, create an API key (it acts as you, with your entitlements) and send it as X-API-Key or Authorization: Bearer — no cookies involved. Keys are shown once at creation, can expire, and are revocable; managing keys always requires a signed-in session, never a key.

bash
# Create a key (session-authenticated), then use it anywhere
curl -b cookies.txt -X POST https://api.edi.finance/api/analytics/v1/keys \
  -H "Content-Type: application/json" -d '{"label": "research pipeline", "expiresInDays": 365}'

curl -H "X-API-Key: edi_sk_…" -X POST https://api.edi.finance/api/analytics/v1/resolve \
  -H "Content-Type: application/json" -d '{"identifiers": [{"value": "AAPL"}]}'

Python: EdiAnalyticsClient("https://api.edi.finance", api_key="edi_sk_…") — skip login() entirely.

Core concepts

The envelope

Every data endpoint returns the same three-part envelope. Rows in data are flat and self-describing — they serialize identically to JSON, CSV, and Excel spill ranges.

json
{
  "data":   [ /* flat observation rows */ ],
  "errors": [ /* row-level failures — never a request failure */ ],
  "meta":   { "pagination": { "total": 412 }, "registryVersion": "2026.07.16.1A" }
}

Identifiers and the requestId echo

Every endpoint accepts mixed identifier types directly — market-qualified tickers (PETR4-BR, BIMBOA-MX), bare tickers, ISINs, CUSIPs, CIKs, and canonical entity ids. Every response row carries both requestId (your identifier, exactly as sent — the join key back to your spreadsheet cell or dataframe index) and entityId (our canonical key, stable across ticker changes and share classes).

Partial failure is the norm

One bad ticker must never kill a 500-cell workbook. Unresolvable identifiers, unknown metrics, and unpriceable conversions come back as entries in errors — each with a requestId, a stable code, and a human-readable detail — while every other row returns normally.

Provenance

Every response states the metric-registry version that priced it (meta.registryVersion), and rows carry the currency actually returned. Numbers are auditable across clients: the same request produces the same value everywhere, by construction.

Vocabulary

The request parameters below mean the same thing on every endpoint that accepts them.

Periodicity (fundamentals)

ValueMeaning
FYFiscal years, as reported.
QTRTrue fiscal quarters. 10-Qs stored as year-to-date are de-cumulated server-side; Q4 derives from the fiscal-year total when unreported.
LTMRolling sum of the last four quarters at each quarter-end (flows). Point-in-time items return their value as of that quarter-end.
YTDCumulative within each fiscal year.
SEMIStaged — awaiting semi-annual normalization.

basis selects the restatement axis: original (default) or restated (staged — awaits vintage modeling).

Frequency (prices & series)

ValueObservations kept
D / ADEvery observed trading day.
W · M · CQ · CSA · CYLast trading day of each ISO week / calendar month / quarter / half / year.
AM · AQ · ASA · AYAnchored: stepping from your startDate (a June 16 start yields Jun 16, Jul 16, …), taking the latest observation at or before each anchor.

Price adjustment

ValueMeaning
SPLITSplit-adjusted (default; matches stored venue closes). US listings only — MX/BR/PE store the adjusted chain separately and return a row error naming DIV_SPIN_SPLITS.
DIV_SPIN_SPLITSDividend-, spinoff- and split-adjusted.
UNSPLIT · SPLIT_SPINOFFStaged — need adjustment-factor history.

Currency

currency defaults to LOCAL: values return in their native quote or reporting currency, stated per row — a Brazilian workbook never silently receives USD. Any ISO code (USD, BRL, MXN, EUR, …) converts server-side; see currency conversion for which rate applies where.

Security resolution

POST/api/analytics/v1/resolve

The identity layer under every other call. Send up to 2,000 identifiers of mixed types; get back canonical entities with company-vs-listing structure. Rows sharing an entityId are share classes / listings of the same issuer, enumerated under listings.

json
// POST /api/analytics/v1/resolve
{ "identifiers": [ {"value": "PETR4-BR"}, {"value": "US0378331005"}, {"value": "037833100", "type": "CUSIP"} ] }

// 200 — one row per candidate
{ "data": [
    { "requestId": "PETR4-BR", "entityId": "BR-9512", "ticker": "PETR4",
      "market": "BR", "matchedBy": "ticker", "candidateCount": 1,
      "listings": [ {"ticker": "PETR3", "market": "BR"}, {"ticker": "PETR4", "market": "BR"} ] } ] }

Metric catalog

GET/api/analytics/v1/catalog/metrics

The authoritative registry of every metric the engines can price — the same registry the platform's screener, quant panels, and formula workbench execute against, including your organization's custom formula metrics. Autocomplete from here and a request can never name a metric the engines don't know.

FilterMeaning
datasetfinancials_standardized · prices · valuation · derived · returns · entity_attributes · …
searchMatches code, name, and aliases (?search=revenue).
valueTypecurrency · percent · ratio · price · shares · text · …
entityTypecompany · etf · fund · bond · manager
includeDeprecatedDeprecated metrics carry a replacementMetricCode.
kindline (a statement line as filed or standardized) · amount (a computed currency amount) · ratio · market · attribute · holdings.
industryProfileindustrial · bank · insurance · reit · fund: only the metrics that exist for that issuer type. Loans / Deposits is a bank metric; the current ratio is not.
marketUS · MX · BR · PE · CL: cross-market metrics plus the ones filed in that market (as-reported lines, institutional holdings, securities lending).
limit / offsetPagination; meta.pagination.total is the filtered count.

Each row states its dataset, engine, value type, unit, supported entity types, time types (as-of / time-series), period modes, and status, and its place in the metric taxonomy: folderPath (statement lines under Financial statements, indicators under Ratios and indicators, one folder per ratio family, as-reported lines by regulator), kind, basis (standardized · as_reported · derived · observed), statement, market, industryProfiles (empty means every issuer type) and canonicalDataset, set on a copy of a code that another dataset owns. Names carry Spanish and Portuguese aliases, so ?search=deuda%20neta finds NET_DEBT.

Global prices

POST/api/content/global-prices/v1/prices

End-of-day OHLCV across all covered markets, with cross-listing containment built in: a São Paulo line sharing a ticker with a US issue can never splice into the US series. Duplicate observations are deduplicated to one canonical row per trading day.

json
// POST /api/content/global-prices/v1/prices
{ "ids": ["BBAS3-BR"], "startDate": "2026-01-01", "endDate": "2026-07-30",
  "frequency": "M", "fields": ["price", "volume"] }

// 200 — month-end observations, native currency stated per row
{ "data": [
    { "requestId": "BBAS3-BR", "entityId": "BR-1023", "date": "2026-06-30",
      "currency": "BRL", "price": 19.91, "volume": 31200400 }, … ] }

Fundamentals

POST/api/content/fundamentals/v1/fundamentals

Standardized fiscal data with the platform's period intelligence. The server owns period semantics — year-to-date storage, fiscal-year-end inference, Q4 derivation, LTM windows — so a formula never has to.

json
// POST /api/content/fundamentals/v1/fundamentals
{ "ids": ["AAPL-US"], "metrics": ["REVENUE"], "periodicity": "LTM",
  "fiscalPeriod": { "start": "2025-01-01", "end": "2026-07-30" } }

// 200 — rolling last-twelve-months revenue at each fiscal quarter-end
{ "data": [
    { "requestId": "AAPL-US", "metric": "REVENUE", "periodicity": "LTM",
      "fiscalYear": 2026, "fiscalPeriod": "Q2", "fiscalEndDate": "2026-03-28",
      "currency": "USD", "value": 451442000000 }, … ] }

Statement taxonomies, whole statements, and fiscal periods

Three discovery-and-retrieval companions to the fundamentals endpoint:

EndpointReturns
GET /taxonomies?market=BR&template=banksThe chart of accounts actually reported by that market and format — market: US·BR·MX·PE, template: industrials·banks·insurance, optional statement. Each row carries the account code, label, statement, and how many companies report it — so you know the available accounts before querying data. A BR Bancos balance sheet (Interbank assets, Loans…) is not a US Banks one.
POST /statementsThe entire statement (IS, BS, CF, or FULL) for each id in one call, at the period implied by asOf — mode: "annual" (latest fiscal year, default) or "latest" (latest filed period, values as filed). Currency conversion is statement-aware: balance sheet at period-end spot, flows at period-average.
POST /segmentsBusiness, product, and geographic revenue breakdowns for US filers, from structured filings: the issuer's own segment names, values as filed, eliminations excluded. dimension: business·product·geographic; any catalog metric.
POST /periodsEach company's reported fiscal periods (year, period, dates, account count) — coverage before you fetch.
json
// POST /api/content/fundamentals/v1/statements — one call, whole statement
{ "ids": ["BBAS3-BR"], "statement": "FULL", "asOf": "2026-07-30", "currency": "USD" }

Ratios

POST /ratios — the ratio catalog as a series: one tab of it (group), or named ratios from any tab, with every flow leg read at one period basis. This is the web application's Ratios window over the wire — the same engine call, so a number here and a number there cannot disagree. The matrix still serves any ratio at a single as-of date; this endpoint is for the history.

json
// POST /api/content/fundamentals/v1/ratios
{ "ids": ["AAPL-US", "BIMBOA-MX"], "ratios": ["GROSS_MARGIN", "ROE", "PB"],
  "periodBasis": "ltm", "scale": "quarterly" }

// 200 — one row per (id, ratio, period); a flow ratio carries the basis it was read at
{ "data": [
    { "requestId": "AAPL-US", "ratio": "GROSS_MARGIN", "group": "profitability",
      "period": "FY2026 Q3", "periodEnd": "2026-06-27", "value": 0.4865,
      "valueType": "percent", "periodBasis": "ltm", "scale": "quarterly" },
    { "requestId": "AAPL-US", "ratio": "PB", "group": "valuation",
      "period": "2026-08-31", "periodEnd": "2026-08-31", "value": 43.05,
      "valueType": "multiple", "periodBasis": null, "scale": "monthly",
      "scaleClampReason": "tab_grid" }, … ],
  "errors": [] }
ParameterMeaning
groupvaluation · profitability · returns · leverage · liquidity · efficiency · cash_flow · per_share · growth. Omit it when ratios names the codes: a ratio's tab is a property of the ratio, and a request mixing tabs is answered tab by tab.
periodBasisltm (default) · ytd · 3m · fy — how every flow leg (revenue, EBITDA, cash flow) is read. Balance-sheet ratios are point-in-time and do not move with it; their rows carry periodBasis: null so a reader knows why the row sat still.
scalequarterly (default) · annual for the fundamental tabs; daily · weekly · monthly for valuation, whose numerator moves every trading day and has no fiscal period. Growth is annual only. The scale is clamped to what the tab and the issuer can honour, and every row says so in scaleClampReason: tab_grid, or issuer_reports_annually for the issuers that file no interim statements.
asOf, startReference date (default today) and how far back to reach; without start the span is the scale's own — ten years of quarters, fifteen of fiscal years, two of days.
currencyConverts the currency-magnitude rows only (Total debt, Market cap, per-share amounts); a ratio is never converted.

Point-in-time

POST /point-in-time — fundamentals as they were known on a date, for backtesting without lookahead bias (US filers). Two guarantees: a fiscal period is invisible until its filing's publication date (Apple's December quarter does not exist before January 30), and when a figure is later restated, the value that was current at pitDate is served — rows carry firstPublished, lastPublished (amendments), and valueVintage (as_known when a captured vintage applies; restatement capture accrues from 2026-08-02 forward). pitDataItems=true on the catalog lists PIT-capable metrics. FY and QTR; compose LTM client-side from PIT quarters.

Matrix

POST/api/analytics/v1/matrix

Many securities × many metrics at one as-of date, in one request — the comps-grid primitive. Metrics can mix datasets freely: prices, fundamentals, valuation ratios, derived measures, and text attributes all return through the same envelope.

json
// POST /api/analytics/v1/matrix
{ "ids": ["AAPL-US", "BBAS3-BR", "BIMBOA-MX"],
  "metrics": ["close", "MARKET_CAP", "PE_LTM", "SECTOR"],
  "asOf": "2026-07-30", "currency": "USD" }

// 200 — one cell per (id, metric); as-of = latest observation on or before
{ "data": [
    { "requestId": "AAPL-US",   "metric": "MARKET_CAP", "dataset": "derived",
      "valueType": "currency", "value": 4925847973597 },
    { "requestId": "BBAS3-BR",  "metric": "close",  "dataset": "prices", "value": 4.11 },
    { "requestId": "BIMBOA-MX", "metric": "SECTOR", "dataset": "entity_attributes",
      "valueType": "text", "value": "Food & Beverage" } ] }

Screener

POST/api/content/screener/v1/search

Run screens on the same engine as the platform screener — conditions on financial metrics, price metrics, valuation ratios, and your organization's custom formula metrics, with sector, exchange, country, and universe filters. Companion endpoints: POST /count returns the match count without materializing rows (wire it to a live "N companies match" indicator), and GET /fields lists every screenable field.

json
// POST /api/content/screener/v1/search
{ "conditions": [ { "metric": "MARKET_CAP", "operator": "gt", "value": 100000000000 },
                  { "metric": "ROE", "operator": "gte", "value": 0.15 } ],
  "metrics": ["REVENUE", "PE"], "sortBy": "MARKET_CAP", "currency": "USD", "limit": 50 }

// 200 — one row per matching security; meta.pagination.total = full match count
{ "data": [
    { "ticker": "AAPL", "entityId": "0000320193", "sector": "Technology",
      "nativeCurrency": "USD", "marketCap": 4925847973597,
      "metrics": { "REVENUE": 451442000000, "PE": 43.9 } }, … ] }

Point-in-time screening

POST /api/content/screener/v1/point-in-time — run a screen as it would have run on a past date. The live screener is present-tense; running it with old thresholds tells you which of today's survivors would pass today.

Three properties, each a separate way to be quietly wrong. A fiscal period is invisible until the filing carrying it was published, so a 2019 screen cannot see figures filed in 2020. The universe is whoever was trading on the date, taken from the prices themselves, so companies that later delisted are in it. And a metric that cannot be resolved point-in-time comes back as a row error naming it — never the current value wearing an old date.
json
// POST /api/content/screener/v1/point-in-time
{ "asOf": "2019-06-28",
  "conditions": [ { "metric": "REVENUE", "operator": "gt", "value": 20000000000 },
                   { "metric": "ROE", "operator": "gt", "value": 0.25 } ],
  "metrics": [ "REVENUE", "ROE" ], "sortBy": "REVENUE" }

// 200 — each row states the filing it was judged on
{ "data": [
  { "ticker": "ORCL", "fiscalYear": 2019,
    "fundamentalsPublished": "2019-06-21", "availabilityBasis": "sec_filed",
    "reportingCurrency": "USD", "metrics": { "REVENUE": 39506000000, "ROE": 0.51 } } ] }
Price-dependent metrics are refused. MARKET_CAP, PE, PB, PS, EV and the rest return a row error rather than a number. Forming a historical market cap needs the price and the share count on the same split basis, and the stored price history is not on one: part is as-traded and part is adjusted as of a per-ticker refresh date. The result would be correct for some issuers and wrong by the split ratio for others, with nothing in the output to say which. Fundamentals, margins, growth, returns on capital and leverage are unaffected.

Funds

POST/api/content/funds/v1/…

Local-market fund data across Brazil (65,000+ registered funds), Mexico, and Peru, with the identifier conventions each market actually uses: BR funds resolve by CNPJ in any format (00.000.684/0001-21 or 00000684000121), MX and PE funds by ticker or entity id. Four endpoints share one contract:

EndpointReturns
POST /summaryRegistry attributes: name, classification, operator/administrator, benchmark, status, native currency.
POST /pricesNAV series (fields: ["nav", "netAssets"]) with the full frequency vocabulary.
POST /returnsPeriodic returns computed from NAV at the sampled frequency. Fund NAVs are accumulation-style, so these are total returns.
POST /flowsDaily inflows / outflows / net flow — BR only, where the market reports daily; other markets return a row error, never silence.
POST /holdingsPortfolio holdings for BR/MX/PE funds and US ETFs at the latest report on or before asOf: name, identifiers, asset class, quantity, market value, weight. top caps rows (default 50).

Institutional equity holdings live at /api/content/ownership/v1: POST /holders returns who holds a security (per manager: latest filing for the target quarter wins — amendments supersede), and POST /manager-holdings returns a manager's portfolio by filer CIK. The full institutional filer universe (~9,000 managers) is covered for every fully-due quarter; because these disclosures are due 45 days after quarter end, holder lists default to the latest fully-due quarter — pass includePartial: true during a filing window to see the in-progress quarter. Values are USD; weights are percent of reported value.

json
// POST /api/content/funds/v1/prices
{ "ids": ["00000684000121", "+TASAD1F1"], "fields": ["nav", "netAssets"],
  "startDate": "2026-01-01", "endDate": "2026-07-30", "frequency": "M", "currency": "USD" }

Reference data

Point-in-time identity and the filing record behind it. Both answer the same question in different registers: what did this identifier mean then — the join key any historical dataset outside our universe needs.

Identifier history

POST /api/content/reference/v1/identifier-history — every ticker, ISIN, CUSIP, CIK, exchange and MIC a security has carried, with validity intervals, back to 1926. Rows carry the corporate-action code behind each change, so a rename can be told apart from a merger.

Why it matters. A 2019 trade blotter references symbols that belong to different companies today. Joining it against current identifiers mismatches silently — nothing errors, the backtest is simply wrong. asOf returns the intervals open on that date.
json
// POST /api/content/reference/v1/identifier-history
{ "ids": [ "AAPL" ], "asOf": "2019-06-28" }

// 200 — what each identifier was on that date
{ "data": [
  { "requestId": "AAPL", "identifierType": "ISIN",
    "identifierValue": "US0378331005",
    "validFrom": null, "validTo": null, "isCurrent": true } ] }

Filings index

POST /api/content/reference/v1/filings — 2.2M US regulatory filings across 20,205 registrants since 1993: form type, filing date, period end, accession number, amendment type and the document URL. The event clock for announcement-driven work, and the audit trail behind every fundamental we serve.

formTypes matches by prefix, so 10-K includes 10-K/A — an amendment is the same event refiled, and it is usually the restatement you came for.

Corporate-action adjustment factors

/corporate-actions now returns priceFactor, shareFactor, cashComponent, affectsSplitAdjusted, affectsTotalReturn and factorStatus alongside the event terms, so you can rebuild your own adjusted series and reconcile it against ours.

The terms do not determine the factor. NVIDIA's 2024 split is recorded as a bonus issue with ratioOld: 1, ratioNew: 9 — and the price factor is 0.1, a 10-for-1. Deriving the multiplier from the ratio gives 9×. Where a factor could not be computed, the fields are null and factorStatus says why rather than the row going missing.

Trading calendars

POST /api/content/reference/v1/calendar — the sessions each market actually trades, for US, BR, MX and PE, from the 1990s through 2029. Non-trading days carry the reason; pass includeNonTrading for a full date spine.

Why this is not a weekday filter. Maundy Thursday closes Mexico and Peru and not the US or Brazil. Mexico's Constitution Day and Benito Juárez's birthday moved to designated Mondays in 2006. Brazil closed for São Paulo's city and state holidays until 2021 and has traded through them since, and shuts the last business day of the year rather than the 31st. Every one of those is a wrong answer that a five-day-week assumption produces silently.
json
// POST /api/content/reference/v1/calendar
{ "markets": [ "US", "MX" ], "startDate": "2026-04-01", "endDate": "2026-04-06" }

// 200 — 2 April is a session in the US and a holiday in Mexico
{ "data": [
  { "market": "US", "date": "2026-04-02", "isTradingDay": true, "origin": "scheduled" },
  { "market": "MX", "date": "2026-04-01", "isTradingDay": true, "origin": "scheduled" } ] }

Trading-day arithmetic

POST /api/content/reference/v1/calendar/offset — move N trading days from an anchor date. T+2 settlement, rebalance scheduling and event windows are the same question, and all silently wrong when answered with calendar days.

json
// T+2 from 1 April 2026, across Easter
{ "markets": [ "US", "MX" ], "dates": [ "2026-04-01" ], "offset": 2 }

// 200 — same anchor, different answers: Mexico also closes Maundy Thursday
{ "data": [
  { "market": "US", "resultDate": "2026-04-06" },
  { "market": "MX", "resultDate": "2026-04-07" } ] }

Insider transactions

POST /api/content/ownership/v1/insider-transactions — 1.75M insider transaction rows since 2004, with the reporter's relationship to the issuer (director, officer, 10% owner) joined in.

Two distinctions the raw filings do not make, and that a caller would otherwise have to rebuild:

Fixed income

POST /api/content/fixed-income/v1/search · /reference · /prices — Mexican paper, Peruvian corporates and Brazilian debentures. 2.2M price observations; a bond universe is discovered rather than known by ticker, so start with search.

The three markets do not quote the same quantities. Mexico and Peru publish a yield to maturity. Brazil does not — a debenture trades against the DI rate, so indexBasis reads "DI + 1,6%" and spreadOverIndex carries the indicative spread over it. yieldToMaturity is null on a Brazilian row, because 0.74 sitting in that column next to Mexico's 6.52 says Brazilian credit yields a seventh of Mexican rather than DI plus 0.74.

Fund fees

POST /api/content/funds/v1/fees — expense ratios, management and performance fees, loads and turnover. US share classes from prospectus disclosures (7.58M facts over 64,931 classes), Brazilian funds from their own regulatory fund disclosures.

Everything is percent, and every row says so. US disclosures file fractions (0.0003 for VTI) and Brazilian ones file percent (2.5). Served as stored they would sit four hundred times apart while reading as eighty.

Ownership changes

POST /api/content/ownership/v1/changes — what institutions did in a security between two quarters: who entered, added, trimmed or left, ordered by what actually moved.

A manager who has not filed is not a seller. These disclosures are due 45 days after quarter end, so inside a filing window most managers are simply absent from the newer quarter and a naive difference reads every one as a total exit. Both periods default to fully-due quarters, and a departure is EXITED only when the manager actually filed for that quarter — one who filed nothing is NOT_FILED.

Research

The academic research surface: point-in-time panels over survivorship-safe historical universes, pinned by citable Research Snapshots. Every dataset answer leads with its diagnostics — universe coverage, PIT withholding, empty metrics — because a panel looks equally complete at 6% and 96% coverage without them. Methods are documented in the twelve methodology chapters shipped with the academic package (identity, PIT, universes, adjustment, event studies, snapshots, and more).

Access. Same authentication as every endpoint (API key or session). While the Academia section is in staging, research endpoints additionally require the research role on your account.

Downloads — the academic package

Self-hosted and versioned with the platform: the edifinance SDK wheel, the teaching notebooks in three languages (English, Spanish, Portuguese — Excel sample workbooks included), and the twelve methodology chapters. The SDK is not on PyPI while Academia is in staging — install it straight from here (works on Colab too).

bash
# the SDK is not on PyPI yet — install from the platform
pip install https://api.edi.finance/static/academic/edifinance-0.1.1-py3-none-any.whl

Historical universes

GET/api/research/universes

Universe definitions with snapshot counts. Resolving one as of a date yields an immutable membership snapshot: status is stored as of that date (a security delisted in 2022 is active in a 2019 snapshot), and unpriced members are flagged price_coverage: none, never dropped — dropping them is the survivorship bias this engine exists to prevent.

bash
# memberships as of a date, grid-shaped
curl -s "https://api.edi.finance/api/research/excel/universe?code=BR_ALL_EQUITY&as_of=2022-12-31" \
  -H "X-API-Key: $EDI_KEY"

Dataset builder — PIT panels

POST/api/research/dataset-build

Builds a panel from a named definition (create one via POST /api/research/dataset-definitions) and returns a capped preview plus full-panel diagnostics. In PIT mode, a fact whose availability cannot be proven is withheld and counted — never served on a guess. PIT coverage is market-honest: US and Brazil are fully supported; Mexico is partial; Peru is not supported.

python
# SDK install: see Downloads above (not on PyPI yet)
from edifinance import ResearchClient

rc = ResearchClient(api_key="…")
df = rc.panel(universe="BR_ALL_EQUITY", metrics=["REVENUE", "NET_INCOME"],
              start="2019-12-31", end="2023-12-31", point_in_time=True)
df.attrs["diagnostics"]  # coverage, withheld facts, snapshot code

Research Snapshots

GET/api/research/research-snapshots

A snapshot (EDI-RS-<year>-<token>) pins everything needed to recreate an analysis: knowledge date, the frozen universe snapshot, metric/factor registry content hashes, currency and adjustment policies, the request payload, and an order-independent dataset hash. Published snapshots are immutable by database trigger — a citation resolves forever. Provenance for one code: GET /api/research/excel/snapshot/{code} (also =EDI.SNAPSHOT(code) in Excel).

Event studies

POST/api/research/event-study

Runs a seeded study: market-model or market-adjusted abnormal returns, AAR/CAAR with a cross-sectional t-test, trading-day windows, event dates mapped forward to the next session (US filing events are timed by regulatory acceptance timestamps — two-thirds of filings land after the close and belong to the next session). Dropped events are counted by reason. The engine's standing validation is a placebo test: random dates must find nothing.

bash
curl -s -X POST https://api.edi.finance/api/research/event-study \
  -H "X-API-Key: $EDI_KEY" -H "Content-Type: application/json" \
  -d '{"definition_name": "us_earnings_announcements"}'

Batch execution

Every data endpoint accepts "batch": "Y" with the identical request body — flip one flag when a workbook grows past the synchronous limits. The batch branch runs the same code path as the synchronous one, so results are identical by construction.

StepCallResponse
1. SubmitPOST any data endpoint with "batch":"Y"202 + Location header; body carries the job id
2. PollGET /api/analytics/v1/batch-status?id=…202 while running · 201 when done (+Location) · 200 with status:"error" on failure
3. FetchGET /api/analytics/v1/batch-result?id=…200 with the standard envelope

Jobs are owned by the submitting account and expire after 24 hours. Failed jobs deliver an EXECUTION_ERROR envelope through the same contract.

Bulk export

Batch execution scales a workbook. Bulk export is for the other shape of problem: loading a dataset into your own store, then keeping it current. One job, one compressed file, and an exact cursor to resume from.

StepCallResponse
0. DiscoverGET /api/content/bulk/v1/datasetsExportable datasets, their exact column projection, and whether each supports incremental pulls
1. SubmitPOST /api/content/bulk/v1/export202 + Location; body carries the job id
2. PollGET /api/content/bulk/v1/status?id=…Job row with status, rowCount, byteCount, nextChangedSince
3. DownloadGET /api/content/bulk/v1/download?id=…The file — gzipped CSV or NDJSON
bash
# Seed: the full price history, one file
curl -b cookies.txt -X POST https://api.edi.finance/api/content/bulk/v1/export \
  -H "Content-Type: application/json" -d '{"dataset": "prices_eod"}'

# Every night after that: only what has been written since the last pull
curl -b cookies.txt -X POST https://api.edi.finance/api/content/bulk/v1/export \
  -H "Content-Type: application/json" \
  -d '{"dataset": "prices_eod", "changedSince": "2026-08-21T23:16:18.915379Z"}'
nextChangedSince is the contract. A completed job reports the highest source write timestamp it actually included. Pass that value back verbatim and the next pull resumes exactly where this one stopped — nothing written mid-run is missed or repeated. Using your own clock, or now, silently loses rows.

Dataset status

GET /api/analytics/v1/datasets — for each dataset: the coverage window, when it last loaded, row and entity counts, which endpoints serve it, and a freshness verdict.

json
// GET /api/analytics/v1/datasets?dataset=prices_eod
{ "data": [ {
    "datasetId": "prices_eod", "cadence": "daily",
    "firstObservation": "1991-12-02", "lastObservation": "2026-08-21",
    "lastLoadedAt": "2026-08-21T23:16:18Z",
    "freshness": "FRESH", "observationLagBusinessDays": 0,
    "expectedLagBusinessDays": 2,
    "rowCount": 21768648, "rowCountIsEstimated": true,
    "computedAt": "2026-08-23T13:52:25Z" } ] }

Currency conversion

Conversion is server-side and semantics-aware — the right rate kind applies to each value:

Value kindRate applied
Price observationsSpot rate on each observation's date.
Flow fundamentals (IS/CF)Period-average rate over the fiscal window.
Stock fundamentals (BS)Spot rate at the fiscal period end.
Matrix cells (currency/price typed)Spot rate at the as-of date.
Volume, ratios, percents, textNever converted.

Cross rates compose through USD. A conversion that cannot be priced — unknown source currency, unsupported pair, no rate within the staleness window — is an FX_UNAVAILABLE row error with the affected rows dropped: you never silently receive unconverted native values.

CSV everywhere. Send Accept: text/csv on any data endpoint and the envelope's flat rows come back as a CSV file — nested maps flatten to dotted columns, row-level errors ride along as trailing # comment lines, and registryVersion is stamped at the end. The fastest path from any endpoint into a spreadsheet or dataframe.

Complete endpoint index

Every operation the service routes, 113 of them, generated from the route table itself. The sections above describe the ones most clients start with; everything here is live and callable.

MethodPathSummary
analytics
GET/api/analytics/v1/batch-resultDownload a finished batch job's envelope.
GET/api/analytics/v1/batch-statusStatus codes: 202 while pending/running, 201 when the result exists (Location points at /batch-result), 200 with status=error.
GET/api/analytics/v1/catalog/equity-presetsCanonical equity presets; metric instances resolve through the catalog.
GET/api/analytics/v1/catalog/fund-presetsCanonical Fund Quant presets resolved against market source capability.
GET/api/analytics/v1/catalog/metricsThe unified metric catalog: every metric this API can serve, with its category, datasets, entity types and value type, plus its place in the Research Library taxonomy (folderPath, kind, basi
GET/api/analytics/v1/datasetsCoverage and freshness per dataset: how far it reaches, when it last moved.
GET/api/analytics/v1/healthLiveness plus a per-store check -- database, facts, prices, FX, batch workers -- so a degraded dependency is visible before it is inferred from empty responses.
POST/api/analytics/v1/inspectCell-level provenance for one (id, metric) pair: what the identifier resolved to, which dataset served the value, and which engine owns it.
GET/api/analytics/v1/keysThe caller's API keys and their status.
POST/api/analytics/v1/keysIssue an API key.
DELETE/api/analytics/v1/keys/{key_id}Revoke one API key immediately.
GET/api/analytics/v1/keys/{key_id}/usageDaily usage rollups for one of the caller's keys (session-only).
POST/api/analytics/v1/matrixCross-domain retrieval: any set of ids against any set of metrics, each cell routed to the dataset that owns it.
POST/api/analytics/v1/resolveResolve tickers, ISINs, CUSIPs, CIKs or entity ids to the platform's canonical security, with the candidates when one is ambiguous.
POST/api/research/dataset-buildRun a build and return a PREVIEW plus the full diagnostics.
GET/api/research/dataset-definitionsNamed, reusable panel definitions: universe, metrics, window, frequency, PIT mode, currency.
POST/api/research/dataset-definitionsCreate a named panel definition.
POST/api/research/dataset-exportDownload a full panel as CSV or XLSX (§67).
GET/api/research/dataset-runsRecent builds with row counts, dataset hashes, full diagnostics, and any minted snapshot codes.
GET/api/research/event-studiesSeeded event-study definitions: model, benchmark, windows, and event counts.
POST/api/research/event-studyThin transport over the event-study engine (§82 names this route; the engine predates it).
POST/api/research/excel/panelThe =EDI.DATASET() backend: derive-or-reuse a definition (the SDK's exact naming scheme, "xl_" prefixed so Excel-born definitions are recognisable), build, and answer as a grid.
GET/api/research/excel/snapshot/{snapshot_code}The =EDI.SNAPSHOT() backend -- the provenance inspector (§36) as a key/value grid: what a citation code pins, and whether it is reproducible, as EVIDENCE fields rather than a bare claim.
GET/api/research/excel/universeThe =EDI.UNIVERSE() backend.
GET/api/research/metricsMetrics that actually have facts, with their coverage.
GET/api/research/research-snapshotsResearch Snapshots: citation code, status, knowledge date, universe, registry hashes, and dataset hash.
POST/api/research/snapshots/{snapshot_code}/replayOne-call snapshot replay (§27): re-run the build a citation pins and return the comparison as evidence.
GET/api/research/universesUniverse definitions with markets, benchmark linkage, and snapshot counts.
bulk
GET/api/content/bulk/v1/datasetsWhich datasets can be exported, with their column projection.
GET/api/content/bulk/v1/downloadStream the finished export file.
POST/api/content/bulk/v1/exportQueue a whole-dataset export; poll status, then download one file.
GET/api/content/bulk/v1/statusProgress of one export, or every export this account has queued.
fixed-income
POST/api/content/fixed-income/v1/pricesDaily bond observations: price, yield, duration, spread.
POST/api/content/fixed-income/v1/referenceTerms for specific bonds: coupon, maturity, par, amount issued, ratings.
POST/api/content/fixed-income/v1/searchFind bonds across MX, PE, BR and CL by issuer, currency, maturity or ISIN.
formula
GET/api/content/formula/v1/catalog/metricsMetrics a formula may reference, with their argument shapes.
GET/api/content/formula/v1/definitionsFormulas visible to the caller: their own, plus anything shared with them.
GET/api/content/formula/v1/definitions/{formula_id}One formula's current definition.
GET/api/content/formula/v1/definitions/{formula_id}/versionsEvery saved version of a formula, newest first.
POST/api/content/formula/v1/executeEvaluate a formula for a set of securities and dates.
POST/api/content/formula/v1/validateParse and type-check an expression without running it.
fundamentals
POST/api/content/fundamentals/v1/fundamentalsStandardized fundamentals on one fiscal basis: FY, QTR, LTM or YTD.
POST/api/content/fundamentals/v1/periodsWhich fiscal periods exist for a security, and their end dates -- the calendar to ask fundamentals questions against.
POST/api/content/fundamentals/v1/point-in-timeFundamentals as they stood on a past date, resolved through the publication ladder so a restatement cannot leak backwards.
POST/api/content/fundamentals/v1/ratiosFinancial ratios as a series: one tab of the ratio catalog, or named ratios, at one period basis (LTM, YTD, 3M, FY) on the fiscal or price grid.
GET/api/content/fundamentals/v1/ratios/fieldsEvery ratio the ratios endpoint serves: tab, formula, value type, grid scales and the issuer types it exists for.
POST/api/content/fundamentals/v1/segmentsReported business and geographic segments.
POST/api/content/fundamentals/v1/statementsFull financial statements as a hierarchy of line items, standardized or as reported.
GET/api/content/fundamentals/v1/taxonomiesStatement taxonomies available per market, and the line items in each.
funds
POST/api/content/funds/v1/aumAssets under management history.
POST/api/content/funds/v1/feesWhat a fund costs to hold: expense ratios, fees and loads.
POST/api/content/funds/v1/flowsNet subscriptions and redemptions per period.
POST/api/content/funds/v1/holdingsDisclosed portfolio holdings for a fund at a reporting date.
POST/api/content/funds/v1/portfolio/analyticsRisk and exposure analytics computed over a fund's disclosed holdings.
POST/api/content/funds/v1/pricesFund NAV and market price history.
POST/api/content/funds/v1/rankingsPeer rankings within a fund category over a chosen window.
POST/api/content/funds/v1/registryBulk share-class registry for a market, including point-in-time returns.
POST/api/content/funds/v1/returnsFund returns as reported and as computed from NAV.
POST/api/content/funds/v1/summaryIdentity and headline facts for a fund or share class.
fx
POST/api/content/fx/v1/averageThe average FX rate over a period -- the right basis for translating a flow, where a spot rate is right for a balance.
POST/api/content/fx/v1/historyAn FX pair over time.
POST/api/content/fx/v1/rateA spot FX rate on a date.
POST/api/content/fx/v1/translateTranslate an amount between currencies at a stated date and basis, so the rate used is on the response rather than assumed.
global-prices
POST/api/content/global-prices/v1/corporate-actionsSplits, dividends and other corporate actions with the price and share factors derived from them.
POST/api/content/global-prices/v1/pricesEnd-of-day prices across all covered markets.
POST/api/content/global-prices/v1/returnsPeriod returns over named windows, price or total return.
POST/api/content/global-prices/v1/returns-rangeReturns between two explicit dates.
POST/api/content/global-prices/v1/returns/snapshotStandard return windows for a set of securities in one row each: the performance strip, without a request per window.
indices
POST/api/content/indices/v1/changesAdditions and deletions between two constituent dates.
POST/api/content/indices/v1/constituentsIndex constituents and weights at a date.
POST/api/content/indices/v1/historyAn index level series over time.
POST/api/content/indices/v1/levelsIndex levels.
POST/api/content/indices/v1/membershipThe index memberships a security has held, with entry and exit dates.
POST/api/content/indices/v1/searchFind indices by name, market or provider.
POST/api/content/indices/v1/weightOne security's weight in an index over time.
lending
POST/api/content/lending/v1/historySecurities-lending history.
POST/api/content/lending/v1/summaryCurrent securities-lending state for a security: balance, rate and availability where the market publishes them.
macro
POST/api/content/macro/v1/historyA macro series over time.
POST/api/content/macro/v1/observationsThe latest observation of a macro series.
POST/api/content/macro/v1/searchFind macro series by name, country or source.
market-analytics
POST/api/content/market-analytics/v1/correlationA correlation matrix across securities over a window.
POST/api/content/market-analytics/v1/performancePerformance statistics against a benchmark, including the risk-free leg where a Sharpe ratio is asked for.
POST/api/content/market-analytics/v1/regressionRegress a security against factors or a benchmark.
POST/api/content/market-analytics/v1/riskRisk statistics over a return window: volatility, drawdown, VaR, beta.
ownership
POST/api/content/ownership/v1/changesWhat institutions did in a security between two quarters.
POST/api/content/ownership/v1/holdersInstitutional holders of a security from 13F, N-PORT and BR/MX/PE local-fund disclosures.
POST/api/content/ownership/v1/insider-transactionsInsider activity, classified so the transaction codes do not have to be.
POST/api/content/ownership/v1/manager-holdingsOne manager's disclosed book: every position they reported for a quarter.
POST/api/content/ownership/v1/shareholdersRegistered and beneficial shareholders as disclosed to the local regulator, for markets that publish them.
peers
POST/api/content/peers/v1/searchCanonical peer set for one security (peer_universe.build_peer_universe).
portfolio
POST/api/content/portfolio/v1/analyticsRisk, exposure and performance analytics for a supplied portfolio of weights or positions.
POST/api/content/portfolio/v1/optimizeOptimize weights under constraints, over the same risk math the Construction workstation uses.
POST/api/content/portfolio/v1/walk-forwardOptimize at each as-of date on what was knowable then, hold the weights out of sample to the next, and chain the held-out segments -- beside 1/N over each step's eligible names and the bench
rates
POST/api/content/rates/v1/curveA yield curve: every tenor at one date.
POST/api/content/rates/v1/historyAn interest rate over time.
POST/api/content/rates/v1/rateAn interest rate on a date.
POST/api/content/rates/v1/spreadThe spread between two tenors on one curve, or across two curves.
reference
POST/api/content/reference/v1/calendarTrading sessions per market.
POST/api/content/reference/v1/calendar/offsetMove N trading days from an anchor date.
POST/api/content/reference/v1/filingsThe regulatory filing index for a security: what was filed, and when.
POST/api/content/reference/v1/identifier-historyEvery identifier a security has carried, with validity intervals.
regime
POST/api/content/regime/v1/historyThe regime classification over time.
POST/api/content/regime/v1/snapshotWhich market regime is in force now, and on what evidence.
POST/api/content/regime/v1/statsHow an asset behaved conditional on regime.
screener
POST/api/content/screener/v1/countHow many securities match a screen, without returning them.
POST/api/content/screener/v1/distinct-countDistinct values of one field across a screen -- how many sectors, how many countries -- without pulling the rows.
GET/api/content/screener/v1/fieldsFields available to screen on, with their types and permitted operators.
POST/api/content/screener/v1/point-in-timeRun a screen as it would have run on a past date.
POST/api/content/screener/v1/searchScreen the universe on any catalog metric and return the matching securities with the requested columns.
POST/api/content/screener/v1/statisticsAggregate statistics over a screen: count, mean, median, percentiles.
sustainability
GET/api/content/sustainability/v1/fieldsAvailable fields with units, definitions and current issuer coverage.
POST/api/content/sustainability/v1/historyDisclosed history for one measure, one point per reported period.
POST/api/content/sustainability/v1/summaryLatest disclosed workforce or environmental measure for an issuer.

Error taxonomy

CodeMeaningExcel mapping
UNRESOLVED_IDENTIFIERNo match for the identifier (in the requested market, if given), or no data in the window.#N/A
AMBIGUOUS_IDENTIFIERMultiple candidates; qualify with a market suffix or use an entityId.#SPILL!
INVALID_IDENTIFIEREmpty or malformed identifier.#VALUE!
UNSUPPORTED_MARKETMarket code outside US · BR · MX · PE · CL · CO.#N/A
UNSUPPORTED_METRICUnknown metric, or a metric/period combination this endpoint doesn't serve.#NAME?
FX_UNAVAILABLERequested currency cannot be priced for some rows.#N/A
EXECUTION_ERRORA batch job or handler failed; detail explains.#VALUE!
429 + Retry-AfterPer-key rate limit exceeded (default 1,200 requests/minute; per-key overrides on creation, admin approval above the default). Honour Retry-After before retrying.the add-in retries up to four times on its own; only then EDI rate limit reached

Rendering rule (clients). Every row error carries a detail string with instructions. In a context entirely about one security — a scalar =EDI() cell or a single-security spill (HISTORY, FUNDAMENTALS, RETURNS, CA) — clients render the symbol plus the detail (e.g. #N/A — no match for ticker 'ZZNOSUCH' in market US), so the fix reaches the sheet. Multi-security grid cells (MATRIX) keep the bare symbol. These are strings, not native Excel error values, so ISNA/IFERROR semantics are unaffected. Programmatic consumers should key on errors[].code, never on cell text.

Request-level failures use standard HTTP statuses: 400 malformed request · 401 unauthenticated · 404 unknown/expired batch id. The add-in surfaces 4xx detail text in the cell as well.

Request limits

EndpointSynchronousBatch (batch:"Y")
/resolve2,000 identifiers—
/prices — single day1,000 ids2,000 ids
/prices — multi-day100 ids1,000 ids
/fundamentals250 ids × 50 metrics5,000 ids × 50 metrics
/matrix500 ids × 50 metrics2,000 ids × 50 metrics
/screener/search1,000 rows per pagesame, queued

Clients & Excel

Python. The reference client (edi_analytics.py) wraps login, the envelope, and batch polling, and ships the spill helpers used by the Excel integration — spill_scalar, spill_series, spill_fundamentals, spill_ratios, spill_matrix, spill_fibonacci — each returning a 2-D grid ready for a dataframe or a dynamic array.

Excel. The add-in registers formulas over exactly this API. Start with the Excel installation guide, which covers both Microsoft 365 administrator deployment and individual installation. You can also download the production manifest or the demo workbook directly.

Company-managed Microsoft 365? Ask your administrator to deploy the manifest through Integrated Apps. Users then receive the EDI ribbon without handling manifest files or configuring trusted folders.
excel
=EDI.VALUE("AAPL-US", "MARKET_CAP")                → one value
=EDI.HISTORY("BBAS3-BR", "price", "2026-01-01", "2026-07-30", "M")   → spills 7×2
=EDI.FUNDAMENTALS("AAPL-US", "REVENUE", "LTM")   → spills fiscal periods
=EDI.MATRIX(A2:A20, B1:F1)                          → spills a comps grid

Bad inputs degrade cell-by-cell (#N/A, #NAME?) — never a failed workbook refresh. The add-in batches formula requests through batch execution automatically.

Anything else. The OpenAPI 3 document drives generated clients for other stacks.

Try it

Sign in with your EDI account and run live requests against the API — every example on this page is runnable. Requests execute with your entitlements, exactly as they would from Excel or a script.

Staged capabilities

These request axes are part of the contract but deliberately rejected (HTTP 400, with an explanatory message) until their backing machinery ships — you can code against the vocabulary today without risk of silent wrong answers: