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.
Quickstart
Authenticate once — the session cookie carries your entitlements on every subsequent call.
# 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:
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.
# 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.
{
"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)
| Value | Meaning |
|---|---|
FY | Fiscal years, as reported. |
QTR | True fiscal quarters. 10-Qs stored as year-to-date are de-cumulated server-side; Q4 derives from the fiscal-year total when unreported. |
LTM | Rolling sum of the last four quarters at each quarter-end (flows). Point-in-time items return their value as of that quarter-end. |
YTD | Cumulative within each fiscal year. |
SEMI | Staged — awaiting semi-annual normalization. |
basis selects the restatement axis: original (default) or
restated (staged — awaits vintage modeling).
Frequency (prices & series)
| Value | Observations kept |
|---|---|
D / AD | Every observed trading day. |
W · M · CQ · CSA · CY | Last trading day of each ISO week / calendar month / quarter / half / year. |
AM · AQ · ASA · AY | Anchored: stepping from your startDate (a June 16 start yields Jun 16, Jul 16, …), taking the latest observation at or before each anchor. |
Price adjustment
| Value | Meaning |
|---|---|
SPLIT | Split-adjusted (default; matches stored venue closes). |
DIV_SPIN_SPLITS | Dividend-, spinoff- and split-adjusted. |
UNSPLIT · SPLIT_SPINOFF | Staged — 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
/api/analytics/v1/resolveThe 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.
// 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"} ] } ] }
- Bare tickers resolve against your default market, else US. Qualify with a suffix (
-BR,-MX,-PE,-US) to be explicit; hyphenated tickers likeBRK-Bare left intact. candidateCount > 1marks an ambiguous identifier — data endpoints report these as row errors rather than guessing.- Identifier shape is auto-classified; pass
type(TICKER·ISIN·CUSIP·CIK·ENTITY_ID) to override.
Metric catalog
/api/analytics/v1/catalog/metricsThe 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.
| Filter | Meaning |
|---|---|
dataset | financials_standardized · prices · valuation · derived · returns · entity_attributes · … |
search | Matches code, name, and aliases (?search=revenue). |
valueType | currency · percent · ratio · price · shares · text · … |
entityType | company · etf · fund · bond · manager |
includeDeprecated | Deprecated metrics carry a replacementMetricCode. |
limit / offset | Pagination; 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.
Global prices
/api/content/global-prices/v1/pricesEnd-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.
// 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 }, … ] }
fields:price·priceOpen·priceHigh·priceLow·volume.adjustselects the price basis.- All eleven frequency values are supported, calendar-bucketed and start-date-anchored.
- With a
currency, each observation converts at that date's spot rate; volume never converts.
Fundamentals
/api/content/fundamentals/v1/fundamentalsStandardized 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.
// 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 }, … ] }
fiscalPeriod.start/endare calendar dates; the server resolves them to completed fiscal periods. Default window: five years back.- Rows report the issuer's reporting currency, or your requested
currencyafter conversion. - Unknown metrics are per-metric row errors; the rest of the request still returns.
Matrix
/api/analytics/v1/matrixMany 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.
// 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" } ] }
- Metric names colliding across datasets (reported vs. consensus
REVENUE) resolve by a documented precedence — reported financials win. Pass{"metric": "REVENUE", "dataset": "estimates"}to override explicitly. - Financials metrics accept
periodMode: "fy"for latest-fiscal-year values; use the fundamentals endpoint for QTR/LTM/YTD series, or precomputed valuation metrics (PE_LTM,EV_EBITDA_LTM, …). - Cells that compute to null are absent, not errors — a spreadsheet fills
#N/A.
Screener
/api/content/screener/v1/searchRun 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.
// 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 } }, … ] }
- Operators:
gt·gte·lt·lte·eq·neq. A company missing a conditioned metric fails the screen. currencydefaults to USD here (not LOCAL): screening thresholds compare across issuers, which requires one common currency. Rows still state theirnativeCurrency;meta.fxRateDatediscloses the rate date used.tickersrestricts the candidate universe;batch:"Y"queues large screens through batch execution.
Funds
/api/content/funds/v1/…Local-market fund data across Brazil (65,000+ CVM-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:
| Endpoint | Returns |
|---|---|
POST /summary | Registry attributes: name, classification, operator/administrator, benchmark, status, native currency. |
POST /prices | NAV series (fields: ["nav", "netAssets"]) with the full frequency vocabulary. |
POST /returns | Periodic returns computed from NAV at the sampled frequency. Fund NAVs are accumulation-style, so these are total returns. |
POST /flows | Daily inflows / outflows / net flow — BR only (CVM daily reporting); other markets return a row error, never silence. |
// POST /api/content/funds/v1/prices { "ids": ["00000684000121", "+TASAD1F1"], "fields": ["nav", "netAssets"], "startDate": "2026-01-01", "endDate": "2026-07-30", "frequency": "M", "currency": "USD" }
- BR series are the fund's main share class; PE funds carry per-issue currency (PEN or USD), stated per row.
currencyconverts monetary fields (NAV, net assets, flows) at each observation's spot rate, exactly as in global prices.
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.
| Step | Call | Response |
|---|---|---|
| 1. Submit | POST any data endpoint with "batch":"Y" | 202 + Location header; body carries the job id |
| 2. Poll | GET /api/analytics/v1/batch-status?id=… | 202 while running · 201 when done (+Location) · 200 with status:"error" on failure |
| 3. Fetch | GET /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.
Currency conversion
Conversion is server-side and semantics-aware — the right rate kind applies to each value:
| Value kind | Rate applied |
|---|---|
| Price observations | Spot 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, text | Never 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.
Error taxonomy
| Code | Meaning | Excel mapping |
|---|---|---|
UNRESOLVED_IDENTIFIER | No match for the identifier (in the requested market, if given), or no data in the window. | #N/A |
AMBIGUOUS_IDENTIFIER | Multiple candidates; qualify with a market suffix or use an entityId. | #SPILL! |
INVALID_IDENTIFIER | Empty or malformed identifier. | #VALUE! |
UNSUPPORTED_MARKET | Market code outside US · BR · MX · PE · CL · CO. | #N/A |
UNSUPPORTED_METRIC | Unknown metric, or a metric/period combination this endpoint doesn't serve. | #NAME? |
FX_UNAVAILABLE | Requested currency cannot be priced for some rows. | #N/A |
EXECUTION_ERROR | A batch job or handler failed; detail explains. | #VALUE! |
Request-level failures use standard HTTP statuses: 400 malformed request ·
401 unauthenticated · 404 unknown/expired batch id.
Request limits
| Endpoint | Synchronous | Batch (batch:"Y") |
|---|---|---|
| /resolve | 2,000 identifiers | — |
| /prices — single day | 1,000 ids | 2,000 ids |
| /prices — multi-day | 100 ids | 1,000 ids |
| /fundamentals | 250 ids × 50 metrics | 5,000 ids × 50 metrics |
| /matrix | 500 ids × 50 metrics | 2,000 ids × 50 metrics |
| /screener/search | 1,000 rows per page | same, 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_matrix — each returning a 2-D grid ready for a dataframe or a dynamic array.
Excel. The add-in registers formulas over exactly this API:
=EDI("AAPL-US", "MARKET_CAP") → 4,925,847,973,597 =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:
basis: "restated"— restated financials, pending vintage modeling.periodicity: "SEMI"— semi-annual reporters, pending normalization.adjust: "UNSPLIT"/"SPLIT_SPINOFF"— pending adjustment-factor history.- Point-in-time endpoints (
/point-in-time,/periods) — reserved for lookahead-bias-free backtesting. - Funds and screener content domains.