DeFiLlama Protocols
Total value locked in each DeFi protocol, tracked by DeFiLlama.
defillama_protocols — the dataset name to pass to the Obscura API.
What one row means
One DeFi protocol's Total Value Locked (TVL) snapshot as scraped from DeFiLlama's keyless `/protocols` endpoint on a given UTC calendar day — one row per (protocol name, day) among the top `top_n` (default 500) protocols ranked by TVL at scrape time.
One row per (name, captured_date).
Point-in-time availability
Every Obscura dataset carries available_date: the calendar day the publisher made the row
available, day-of, with no session rounding. It is the one column a backtest filters on, and it means the
same thing on every dataset in the catalog.
For defillama_protocols: snapshot: available_date = captured_date, a STORED GENERATED column (migration 000205). WHY THAT IS THE DAY IT BECAME AVAILABLE: /protocols has no period or as-of parameter and returns only the live current-moment TVL, so there is no publication event to lag behind — the day the collector asked is the only day this state could have been obtained. EVIDENCE captured_date is the capture day: the collector writes Utc::now().date_naive() live and BronzeObject::captured_day() on replay, so re-parsing an old bronze object reproduces that object's day. Generated rather than collector-written so no clock can reach it on a replay.
Refresh cadence
Obscura refreshes defillama_protocols daily — the most frequent scheduled job that re-collects or re-exports it. This is Obscura's own pipeline cadence, not the upstream publisher's release schedule; when a row became public is recorded per row in available_date.
Schema — 23 columns
The full public column list for defillama_protocols, with the meaning of every field. The same
schema is served unauthenticated at https://api.obscura.trade/v1/catalog/defillama_protocols.
| Column | Type | Description |
|---|---|---|
| name | text · not null | Protocol display name as returned by DeFiLlama's /protocols feed (e.g. 'Aave', 'AFI Protocol'); the natural key alongside available_date. Deduped per scrape via a BTreeMap keyed on this field (last entry wins if the feed ever repeats a name), so it is assumed unique within a single day's response. |
| captured_date | date · not null | UTC calendar day the collector CONTACTED DeFiLlama, set once per run (Utc::now().date_naive() live, the bronze object's capture day on replay) and shared by every row from that run. This is the only temporal signal in the dataset: /protocols carries no period or report-date field, so the day we asked is the day the state was obtainable. Part of the composite key with name, and the column available_date is generated from. |
| category | text | DeFiLlama's protocol category tag as returned by the API (e.g. 'Lending', 'Farm', 'Bridge', 'CEX'); nullable since some protocols omit it. Free-text taxonomy owned by DeFiLlama, not normalized/enumerated on our side. |
| chain | text | Chain(s) the protocol's TVL is attributed to, as a single string from DeFiLlama (e.g. 'Multi-Chain', 'Arbitrum', 'Ethereum'); nullable since the source can omit it. 'Multi-Chain' means DeFiLlama itself aggregates TVL across several chains for that protocol — this table has no per-chain breakdown, only the single top-line chain label the API supplies. |
| tvl_usd | double precision | Total Value Locked in USD at scrape time, taken directly from the API's `tvl` field (nullable if the source omits it). This is the ranking key: the collector sorts all returned protocols descending by tvl and truncates to the top args.top_n (default 500) before upserting, so the long tail of small protocols is never persisted. |
| change_1d | double precision | 24-hour percentage change in TVL as reported by DeFiLlama's `change_1d` field (nullable when the source doesn't supply it, e.g. newly listed protocols). Signed float — negative values indicate a TVL decline over the trailing day as computed upstream by DeFiLlama, not recomputed locally. |
| protocol_id | text | Stable numeric DeFiLlama protocol id, from the raw `id` key (returned as a string, e.g. '2269'). A durable natural key that survives display-name changes — our PK is the mutable display `name`, so this is the join-safe cross-dataset identifier. Nullable defensively though the source always supplies it. |
| symbol | text | Protocol governance/token ticker from the raw `symbol` key (e.g. 'BNB', 'OBOL', 'OKB'); nullable. Cross-dataset entity identifier for joining to price/market-cap feeds keyed on token symbol. |
| token_address | text | Token contract address from the raw `address` key (e.g. '0x75231f58…'); nullable (~half of protocols omit it). On-chain identifier — DeFiLlama may prefix a chain (e.g. 'ethereum:0x…'); stored verbatim as supplied. |
| gecko_id | text | CoinGecko coin id from the raw `gecko_id` key (e.g. 'okb', 'obol-2'); nullable. Cross-dataset join key into CoinGecko-sourced price/market-cap datasets. |
| cmc_id | text | CoinMarketCap id from the raw `cmcId` key (returned as a string, e.g. '8000'); nullable. Cross-dataset join identifier into CoinMarketCap-sourced datasets. |
| mcap_usd | double precision | Token market capitalization in USD from the raw `mcap` key; a distinct measure not derivable from TVL. NOTE: the `/protocols` list endpoint returns this as null for every protocol — the populated value lives in a per-protocol endpoint the collector does not fetch, so this column is currently always NULL but wired for when the source populates it. |
| change_1h | double precision | 1-hour percentage change in TVL from the raw `change_1h` key (nullable); signed float, computed upstream by DeFiLlama. Sibling window to change_1d. |
| change_7d | double precision | 7-day percentage change in TVL from the raw `change_7d` key (nullable); signed float, computed upstream by DeFiLlama. Sibling window to change_1d. |
| staking_usd | double precision | TVL locked in native-token staking, USD, from the raw `staking` key (nullable). A distinct measure that DeFiLlama excludes from the headline `tvl` top-line. |
| pool2_usd | double precision | TVL locked in pool2 (LP positions pairing the protocol's own token), USD, from the raw `pool2` key (nullable). A distinct measure excluded from the headline `tvl` top-line. |
| listed_at | timestamp with time zone | Moment the protocol was first listed on DeFiLlama, from the raw `listedAt` unix-seconds key, converted to a UTC timestamp (nullable). A real temporal signal — inception/age proxy — distinct from available_date (the scrape day). |
| parent_protocol | text | Parent-protocol grouping id for sub-protocols, from the raw `parentProtocol` key (e.g. 'parent#aave'); nullable (only set on sub-protocols). Entity-hierarchy link to the umbrella protocol. |
| dead_from | date | Date the protocol went dead, from the raw `deadFrom` key (supplied as a 'YYYY-MM-DD' string, parsed to a date; nullable). A status/lifecycle date. |
| rugged | boolean | Rug-pull status flag from the raw `rugged` boolean key (nullable/absent when not flagged). Canonical status concept for filtering out defunct/fraudulent protocols. |
| deprecated | boolean | Deprecated status flag from the raw `deprecated` boolean key (nullable/absent when not flagged). Live-vs-defunct filter. |
| dead_url | boolean | Dead-site status flag from the raw `deadUrl` boolean key (nullable/absent when not flagged). Signals the protocol's site/front-end is offline. |
| available_date | date | PUBLIC-availability date = captured_date. STORED GENERATED column (migration 000205), read-only; the point-in-time column to filter and join on. It is the day the collector polled DeFiLlama, because a live current-state endpoint with no historical parameter makes that the only day this TVL reading could have been obtained. |
Access defillama_protocols
Two delivery paths, one identifier. Both require an Obscura account and an active subscription; the catalog entry and the schema above are public.
import obscura
client = obscura.Client("obs_live_…")
df = client.query(
dataset="defillama_protocols",
start="2024-01-01",
)
Create a free account Browse all 95 datasets
Frequently asked questions
What is in the defillama_protocols dataset?
Total value locked in each DeFi protocol, tracked by DeFiLlama. One DeFi protocol's Total Value Locked (TVL) snapshot as scraped from DeFiLlama's keyless `/protocols` endpoint on a given UTC calendar day — one row per (protocol name, day) among the top `top_n` (default 500) protocols ranked by TVL at scrape time.
How do I avoid look-ahead bias with defillama_protocols?
Filter on defillama_protocols.available_date, the day the publisher made the row public. For this dataset that date is derived as follows — snapshot: available_date = captured_date, a STORED GENERATED column (migration 000205). WHY THAT IS THE DAY IT BECAME AVAILABLE: /protocols has no period or as-of parameter and returns only the live current-moment TVL, so there is no publication event to lag behind — the day the collector asked is the only day this state could have been obtained. EVIDENCE captured_date is the capture day: the collector writes Utc::now().date_naive() live and BronzeObject::captured_day() on replay, so re-parsing an old bronze object reproduces that object's day. Generated rather than collector-written so no clock can reach it on a replay. A query of the form WHERE available_date <= '<as-of date>' never sees a row before it existed.
In what formats can I get defillama_protocols?
As a Parquet bulk export (POST https://api.obscura.trade/v1/download) or as JSON from the typed query API (POST https://api.obscura.trade/v1/query), both with dataset="defillama_protocols". The column schema is public at https://api.obscura.trade/v1/catalog/defillama_protocols.
How often is defillama_protocols updated?
Obscura refreshes defillama_protocols on a daily schedule — that is the most frequent scheduled job that re-collects or re-exports the table. It is Obscura's own pipeline cadence, not the upstream publisher's release schedule; when the publisher makes a row available is described by the availability rule above, and is recorded per row in available_date.