API Documentation

One endpoint, one promise: ask what was knowable about a company on a date, and that's exactly — and only — what you get back.

Machine-readable: OpenAPI 3.1 spec — /openapi.json · live check: /status

Authentication

Send your API key with every request, either way:

x-api-key: tvd_your_key_here
# or
Authorization: Bearer tvd_your_key_here

Free keys are issued by email from the pricing page; paid keys on subscription, deactivated on cancellation. One key per person — don't share it. Limits depend on the plan (free: 250 requests/day; paid: 5,000 requests + 2,500 distinct tickers/day) — see Limits.

GET/v1/fundamentals

Returns, for each concept, the most recent fiscal period whose 10-K had been filed on or before as_of — the point-in-time view. Note the boundary is inclusive: a 10-K filed on as_of itself counts as knowable that day (filings often land after market close — use the prior day if you need strict before-the-open semantics).

Query parameters

tickerrequired
US ticker symbol, e.g. AAPL. 1–12 chars (A–Z, 0–9, dot, dash). Unknown tickers return 404.
Share classes and other aliases resolve to the SEC filer they belong to — GOOGGOOGL, BRK-ABRK-B, and roughly 1,500 more (preferred-share tickers like BAC-PKBAC, renamed issuers, OTC duplicates of a listed filer). When that happens the response echoes the ticker you sent and adds resolved_ticker plus a resolved_note explaining that fundamentals are reported at the filer level. Both fields are absent when no alias was involved.
as_ofrequired
The knowledge date, YYYY-MM-DD. Must be a real calendar date.
concept
Filter to a single concept (case-insensitive). One of: Revenue, NetIncome, Assets, StockholdersEquity, OperatingCashFlow, EPSDiluted, DilutedShares. Omit for all seven.

Response fields

concept
Which fundamental this row is.
fiscal_year
Derived from period_end: the calendar year the fiscal period ends in, except 52/53-week closes falling Jan 1–7, which belong to the prior year. We deliberately do not use the filing's own fiscal-year label — in XBRL that label is the year of the filing, so a prior-year comparative carries the newer year and collides with the real row. When a company changes its fiscal year end, one calendar year can legitimately contain two annual closes; period_end is the join key of record, not fiscal_year.
period_end
The date the fiscal period ended.
filed
When this value first became public — the point-in-time stamp.
value
The first-reported value — what was actually knowable at filed. Use this for backtests.
latest_value
The most recent revision of the value. May post-date your as_of — not point-in-time safe. Provided for reference only.
lag_days
Days from period_end to filed — the size of the lookahead gap this row closes.
data_through
Top-level. The newest filing date in the dataset. We serve a snapshot, so anything filed after this date is not reflected; if your as_of is later, the response also carries a coverage_warning.
restated
true if a later filing revised the value by more than 0.5% — a materiality threshold. latest_value may differ slightly from value without tripping the flag (sub-0.5% revisions are shown but not flagged). Revisions are tracked within the same XBRL tag across filings.
qa_status
"clean", or "FLAG:" plus semicolon-separated reasons: lag_out_of_range (filing lag outside 0–120 days), revenue_magnitude, ambiguous_tag (another XBRL element in the same filing reported materially more for this period, so which one is the true total is uncertain), implausible_value (a scale/unit-corrupted figure was suppressed — the value is null), tag_switch_discontinuity (the XBRL element behind this concept differs from the adjacent fiscal year — Broadcom alternates ProfitLoss and NetIncomeLoss, for instance. The value is usually correct; the flag marks a scope change to check before you difference year over year, not an error), and stale_for_as_of (the newest period we hold for this concept ends more than ~18 months before your as_of). We flag; we never quietly fix.

Example

curl "https://tradevodata.com/v1/fundamentals?ticker=AAPL&as_of=2024-06-30&concept=Revenue" \
  -H "x-api-key: tvd_your_key_here"
{
  "ticker": "AAPL",
  "as_of": "2024-06-30",
  "data_through": "2026-07-24",
  "count": 1,
  "fundamentals": [{
    "concept": "Revenue",
    "fiscal_year": 2023,
    "period_end": "2023-09-30",
    "filed": "2023-11-03",
    "lag_days": 34,
    "value": 383285000000,
    "latest_value": 383285000000,
    "restated": false,
    "qa_status": "clean"
  }]
}

Note the answer is FY2023, not FY2024 — on 2024-06-30 the FY2024 10-K had not been filed yet. That is the whole product.

The data_through value above is illustrative — it advances with every data load. Read the live one from any response, or check /status.

Python

The official client. as_of is a required argument on every query — there is no way to ask it for “Apple's revenue”, only for what was knowable on a given date, which is how lookahead bias stops being something you can write by accident. Zero dependencies; pandas is optional.

pip install tradevodata
import tradevodata as tv

df = tv.sample()                    # free 40-company sample — no API key needed

client = tv.Client(api_key="tvd_...")           # or set TRADEVODATA_API_KEY
client.fundamentals("AAPL", as_of="2024-06-30") # -> FY2023
client.snapshot(as_of="2024-06-30", concept="Revenue", to_pandas=True)

Source: github.com/christianpichichero-max/tradevodata-py · PyPI

Note: as of June 2024, the newest knowable Apple annual revenue is FY2023 — FY2024 wasn't filed until 2024-11-01. That's the product.

Python (plain requests)

import requests

r = requests.get(
    "https://tradevodata.com/v1/fundamentals",
    params={"ticker": "AAPL", "as_of": "2024-06-30"},
    headers={"x-api-key": "tvd_your_key_here"},
)
for f in r.json()["fundamentals"]:
    print(f["concept"], f["value"], "known since", f["filed"])

R

library(httr)

r <- GET(
  "https://tradevodata.com/v1/fundamentals",
  query = list(ticker = "AAPL", as_of = "2024-06-30"),
  add_headers(`x-api-key` = "tvd_your_key_here")
)
for (f in content(r)$fundamentals) {
  cat(f$concept, f$value, "known since", f$filed, "\n")
}

Errors

400
Missing/invalid ticker, as_of, or concept. The body explains which.
401
Missing, invalid, or deactivated API key.
403
Free key on a paid-plan endpoint (/v1/download, /v1/snapshot). Nothing is metered. Upgrade on the pricing page.
404
Ticker not in the universe (5,189 US companies with 10-K data).
429 · requests
Daily request quota (250/day free, 5,000/day paid) exceeded. Resets 00:00 UTC — the Retry-After header says how many seconds until then.
429 · tickers
Daily distinct-ticker limit (2,500/day) reached. Same reset, same Retry-After header. The error body says which limit you hit.
500
Our fault. If it persists, email support.

An empty result with count: 0 and a note means the ticker exists but nothing had been filed on or before your as_of.

GET/v1/health

Public liveness check (no key needed): returns { ok, rows, published_rows, stale_build, db_ms }.

ok
true when the database answered. On failure the endpoint returns 503 with { ok: false, error }.
rows
Live row count from the database — count(*) on the fundamentals table, not a build-time constant.
published_rows
The row count this deploy of the site advertises (312,751 at the moment).
stale_build
true when rows and published_rows disagree — the data moved and the site deploy has not caught up yet. The API itself is serving the live rows either way.
db_ms
Milliseconds the database round-trip took.

Responses are edge-cached for 60 seconds, so the count is at most a minute old.

Limits

Daily limits per key, by plan. All reset at 00:00 UTC, and every 429 carries a Retry-After header with the seconds until reset.

250 requests · free
Total requests per day on a free key. Backtest a watchlist, not the universe.
5,000 requests · paid
Total requests per day. Sized so a normal research workflow never notices it.
2,500 tickers · paid
Distinct tickers per day. Repeat calls for the same ticker are free — only the first request for each new ticker counts.

The honest why: the dataset is the product. A real backtest touches a few hundred names; a full-universe scrape is all 5,189. The ticker cap sits between those two on purpose — it's an anti-bulk-extraction wall, not a revenue lever. For whole-universe work, don't fight the cap — use the bulk endpoints below (paid plan).

Bulk & cross-section

For whole-universe work — cross-sectional screens, factor backtests — don't loop the per-ticker endpoint. Both are paid-plan endpoints ($29/mo); a free key gets a 403 here, un-metered.

GET /v1/download
The entire point-in-time dataset as one gzipped CSV (312,751 rows). Costs 1 request, capped at 3 downloads/day. Verify integrity against the x-dataset-sha256 response header.
GET /v1/snapshot?as_of=YYYY-MM-DD
Every ticker's latest value that was public on or before as_of — the whole-universe cross-section for one rebalance date. Optional &concept=. Costs 25 requests (the workload of thousands of per-ticker calls).
curl -H "x-api-key: tvd_..." https://tradevodata.com/v1/download -o tradevodata.csv.gz
curl -H "x-api-key: tvd_..." "https://tradevodata.com/v1/snapshot?as_of=2024-06-30&concept=Revenue"

Coverage & semantics

  • 5,189 US companies · 312,751 point-in-time rows · 7 concepts · up to 12 fiscal years.
  • Annual data from 10-K and 10-K/A filings (SEC EDGAR XBRL). Quarterly is on the roadmap.
  • filed is the earliest filing that reported the value — across synonym XBRL tags, so re-tagged values keep their true first-public date.
  • Support: email us. Manage billing at /account.

Try it before you need a key.

The free sample covers 40 large caps with the same fields — no signup.

Or run the 3-minute Colab — nothing to install.