Stable Tape · developer reference
The interfaces Stable Tape publishes
Stable Tape maintains one number — USD-pegged stablecoin supply as a share of U.S. M2 — and the dated records behind it. This page documents every interface that number is published through, what each one returns, and which of them are supported public contracts rather than research or first-party surfaces.
No key, no registration and no account is required for any public endpoint. Every endpoint on this page is read-only: none of them accepts a write, and none of them takes a body. Documented behaviour here is verified against the implementation by npm run test:developer-api-docs.
register
What is published, and on what terms
Not every interface below carries the same commitment. Three registers are used on this page, and each one is stated in words as well as by the rule beside the row, so the distinction survives print, greyscale and a cropped screenshot.
- Public. A supported read-only contract. CORS-open to any origin, documented here, and covered by the documentation test. Field names and units are not changed without changing this page.
- Research. Real and readable, but published as a research surface. The on-chain oracle is a second, independent publication of the same reading; it is not the citation surface and it carries no availability commitment.
- Internal. Reachable, but not offered as a public contract. Restricted to first-party origins, and listed here only so that an interface visible in a network log is explained rather than mistaken for something to build on.
| Route | Method | Returns | Freshness | Register |
|---|---|---|---|---|
| /api/ratio | GET | Current DDD reading: unrounded and display share, 1-in-N, supply, U.S. M2, M2 observation date, sources | Recomputed per request, behind a 5-minute shared cache | Public |
| /api/tape?window=daily | GET | One frozen canonical Daily Tape record, in full | Daily, cut at 12:00 UTC | Public |
| /api/tape?window=daily-history | GET | Newest-first page of reduced Daily Tape records, plus declared gaps | Daily, appends | Public |
| /api/issuers | GET | Issuer supply and share snapshot behind /issuers | Recomputed per request, behind a 5-minute shared cache | Public |
| /api/chains | GET | Per-chain supply and 24h change behind /chains | Recomputed per request, behind a 5-minute shared cache | Public |
| /api/currencies-supply | GET | Supply by peg currency behind /currencies | Recomputed per request, behind a 5-minute shared cache | Public |
| /widget | GET | Embeddable HTML document that reads /api/ratio | Reads on every load | Public |
| /api/v1/oracle/summary | GET | Decoded on-chain summary account, big integers as strings | Follows the on-chain write cycle | Research |
| /api/v1/oracle/snapshot | GET | Summary plus the chain, issuer and token books, with a consistent flag |
Follows the on-chain write cycle | Research |
| /api/jupiter-solana-route | GET | Read-only Jupiter quote proxy. Not an oracle read, not versioned | Per request | Internal |
/api/tape?window=daily, /api/tape?window=daily-history, or the dated /tape/YYYY-MM-DD permalink. Those return frozen canonical records. /api/ratio is a current reading, not a record, and the on-chain oracle is a research publication of the same reading. Neither is the citable surface.
what the benchmark is for
A maintained benchmark, not another raw feed
Stablecoin supply and U.S. M2 are both public, and both stay useful on their own. What DDD adds is the layer above them: one maintained number, computed from a fixed source selection with a fixed rounding and display rule, frozen into dated records, and carried forward unchanged when an upstream feed changes shape.
Stitching DeFiLlama and FRED together yourself means re-deriving that methodology and re-checking the rounding rule every time something moves upstream. The point of these endpoints is that a product, article, dashboard or agent can cite one number instead of re-deriving it. The full source selection and its caveats are on Methodology.
public · benchmark and records
Benchmark and record API
Three public read-only endpoints carry the benchmark itself. All are CORS-open to any origin, take no authentication, and return application/json.
GET/api/ratio
Current DDD reading
Recomputed on each request from the same two sources the benchmark always uses, then served behind Cache-Control: public, s-maxage=300, stale-while-revalidate=600. It takes no parameters. It is a current reading, not a frozen record, so successive calls can differ.
$ curl -sS 'https://stabletape.com/api/ratio'
// Illustrative values. Every field the endpoint returns is shown. { "dddPctRaw": 1.3308184918420052, // unrounded PERCENT, not a 0-1 ratio "dddPctDisplay": "1.33", // string, fixed 2dp "oneInN": 75, // round(100 / dddPctRaw) "stablecoinSupplyUsd": 308153683423, // whole USD "usM2Usd": 23155200000000, // whole USD "m2Date": "2026-06-01", // FRED M2SL observation date, always a month start "m2DateLabel": "JUN 2026", "updatedAt": "2026-08-06T12:04:11.318Z", "sources": { "stablecoins": "DeFiLlama", "m2": "FRED M2SL" } }
"status": "fallback-m2", sources.m2 becomes "bot/fallback.json" instead of "FRED M2SL", and updatedAt reports the stored snapshot's timestamp rather than the time of the request. Treat the presence of status as the signal that M2 is not fresh. If the stablecoin supply fetch fails instead, no reading is invented: the endpoint returns 502 with an error field.
GET/api/tape?window=daily
One frozen Daily Tape record
Returns the most recent frozen canonical Daily Tape record in full. Add &date=YYYY-MM-DD to fetch a specific date. A record carries the record date and window, supply before and after, expanded, contracted and net movement, per-row movement, the benchmark block, provenance and integrity fields, the selected headline and its evidence line under display.signal, and the record's canonical status.
$ curl -sS 'https://stabletape.com/api/tape?window=daily' $ curl -sS 'https://stabletape.com/api/tape?window=daily&date=2026-06-25'
canonical: true are the citable surface. Each dated record also has a share card at /daily-tape-card.png?date=YYYY-MM-DD, square variant at /daily-tape-card-square.png?date=YYYY-MM-DD. The canonical Daily Tape series starts 2026-06-20. Records describe snapshot-to-snapshot supply movement, not transaction-level mint or burn events.
GET/api/tape?window=daily-history
Recent frozen Daily Tape records
Returns the newest frozen Daily Tape records in newest-first order. The default response contains up to 7 records. A caller that needs a longer run of history asks for a larger bounded page with ?limit=N, up to a maximum of 90. The archive decides the rest: if fewer eligible records are stored than the number requested, fewer are returned, so limit=90 returns 90 records only once 90 records exist.
Each entry is a reduced projection of the frozen record, not the complete stored record. It carries the record date, the snapshot window, supply before and after, net, expanded and contracted movement, the largest expansion, contraction and absolute supply delta, the tracked row count, the selected headline, the benchmark block, and the record's canonical status. Full per-row movement, provenance and integrity fields stay on the dated record at /api/tape?window=daily&date=YYYY-MM-DD.
| Parameter | Default | Accepted | Behaviour |
|---|---|---|---|
| limit | 7 | 1–90 | Maximum number of records in the response. A value above 90 is clamped to 90, so the response stays bounded however long the archive grows. Any value that is not a plain positive integer, for example an empty, negative, fractional, exponential or non-numeric value, normalises to the default of 7 and still returns HTTP 200 rather than an error. A repeated limit uses the first occurrence. |
Omitting limit is the long-standing behaviour and is unchanged: an existing caller that sends no parameter keeps the same seven-record response it received before the parameter existed. Responses are CORS-open and sent with Cache-Control: public, max-age=300, stale-while-revalidate=600.
$ curl -sS 'https://stabletape.com/api/tape?window=daily-history'
$ curl -sS 'https://stabletape.com/api/tape?window=daily-history&limit=90'
// illustrative values; each record is a reduced projection, newest first { "status": "ok", "count": 41, // whole stored archive, not the returned page "records": [ { "date": "2026-07-29", "from": "2026-07-28T12:00:00.000Z", "to": "2026-07-29T12:00:00.000Z", "netIssuerMovementUsd": -250060285, "expandedUsd": 426210093, "contractedUsd": 676270378, "benchmark": { "totalSupplyUsd": 308022276470, "m2Used": 23155200000000, "m2AsOf": "2026-06-01", // FRED M2SL reference month "sharePctRaw": 1.33025098, "sharePct": 1.33, "oneInN": 75 }, "canonical": true, "recordStatus": "canonical" } ], "gaps": [ { "date": "2026-07-30", "type": "gap", "reason": "noon_anchor_unavailable" } ] }
gaps with a reason, and is never fabricated into a record. Never assume a missing day equals zero movement. count reports the whole stored archive, so it can be larger than the number of records returned; the returned page is always the newest slice of the same ordering, and a smaller limit gives a strict prefix of a larger one.
benchmark block carries the inputs to the published calculation: total stablecoin supply, the U.S. M2 figure used, the M2 reference month, the raw and display-rounded share, and the 1-in-N value. Every canonical record therefore resolves back to the division it came from. The window does not provide M2 release dates, M2 vintage history, or revision history; the M2 figure is the monthly observation held constant until the next reference month. Early records stored before the canonical series began are still returned, flagged canonical: false with a recordStatus, rather than silently dropped.
public · structure
Structure endpoints
Three further public endpoints expose the structure layer that Methodology points here for. Each one backs the corresponding Structure route, is CORS-open, and is served with Cache-Control: public, s-maxage=300, stale-while-revalidate=600. Like /api/ratio, they are current snapshots rather than frozen records, so they are not a citation surface.
| Route | Backs | Top-level keys |
|---|---|---|
| GET /api/issuers | /issuers | The issuer snapshot object, including issuers and history. Each issuer row carries its symbol, supply, share and the static metadata Stable Tape maintains for it. |
| GET /api/chains | /chains | Per-chain totals summed across every USD-pegged asset, with the previous-day figure alongside the current one so a 24h change can be derived. |
| GET /api/currencies-supply | /currencies | currencies, totals, summary, trackingStartedAt, annualAvailable, annualNote, generatedAt. Note that annualAvailable is false: there is no 1Y currency series behind this endpoint yet, and the response says so rather than implying one. |
error field rather than a stale or partial figure.
citation
Attribution guidance
Please attribute Stable Tape and link to the relevant dated Daily Tape record or to the methodology page when citing the benchmark.
- Benchmark: cite Digital Dollar Dominance (DDD) with a link to stabletape.com/methodology. DDD measures USD stablecoin supply as a share of U.S. M2. It is a supply-scale benchmark, not a complete adoption metric.
- Daily Tape records: dated, frozen records are the citable and shareable surface, and each carries a selected headline and its own dated card. Link to stabletape.com/tape and include the record date. Current readings from
/api/ratio, from the widget or from the homepage are not citable until frozen as a dated record. - Movement language: use expanded supply, contracted supply and snapshot-to-snapshot movement. Daily Tape is not a transaction-level mint or burn tracker unless on-chain event evidence is shown.
- Sources and caveats: stablecoin supply from DeFiLlama; U.S. M2 from FRED M2SL. M2 observations are revised by the Federal Reserve, and DDD uses the latest available. Data can be delayed or revised upstream. No SLA or uptime guarantee is implied.
public · embed
Embed widget
The widget is a single self-contained HTML document served at /widget. Drop it into any page as an iframe. It loads no script bundle, no web font and no stylesheet from anywhere, and it makes exactly one network request of its own: a read of /api/ratio when it loads. It is free to use and needs no key.
research · solana mainnet
The on-chain oracle
The DDD reading is also written to four accounts on Solana mainnet, so the number can be verified and consumed without trusting this site's HTTP layer. This is a research surface. It is a second publication of the same reading, not a separate measurement, and it carries no availability commitment: an on-chain read can be mid-cycle, and the accounts are updated by a single authority.
Reads are permissionless. No wallet, no transaction and no fee is involved; a single RPC getAccountInfo or getMultipleAccounts call is enough.
| Account | Seed | Role | Address |
|---|---|---|---|
| program | — | Oracle program ID | XJjnewyPHcfb2ogMN1uAZGyt25XbKN2DWnm1GfAwddd |
| summary | "oracle" | DDD value, totals, concentration and metadata | 8DAncbbsEkmsCCakHpNK5zf49XLcWQrryTBYdjgcLWxA |
| chains | "chains" | Supply breakdown by chain, up to 20 rows | 97g36T1PV3anxCwxV6ue5MmF2A9HQHMsLqevckrkqjzK |
| issuers | "issuers" | Supply breakdown by issuer, up to 25 rows | GznG6pxbLPrqeYDd5aFXJcXK5SQ4wXifBtqjep34DNe |
| tokens | "tokens" | Supply breakdown by token, up to 25 rows | 7FRjdiN489qMU2R8G2RumBPxRjanY9683gnXttKP4Fhv |
Each account is a program-derived address from its seed and the program ID, so any of them can be re-derived rather than trusted from this page.
sequence when they are written together. If a book's sequence does not equal the summary's, the read landed mid-cycle or one book write failed. Compare them and retry rather than mixing figures from two cycles.
Node.js · @snowplow1337/ddd-oracle-sdk
SDK
The SDK handles PDA derivation and account deserialisation. It has a peer dependency on @solana/web3.js and is published to GitHub Packages, so an install needs a registry entry for the @snowplow1337 scope.
// Read the DDD summary account from Solana mainnet. import('@snowplow1337/ddd-oracle-sdk').then(async ({ readDDD }) => { const { Connection } = await import('@solana/web3.js'); const conn = new Connection('https://api.mainnet-beta.solana.com'); const s = await readDDD(conn); // dddPercent is precomputed; totalStablesUsd is a bigint in whole USD. console.log('DDD = ', s.dddPercent.toFixed(4) + '%'); console.log('Stables = $' + (Number(s.totalStablesUsd) / 1e9).toFixed(2) + 'B'); });
node --input-type=module or use dynamic import() as shown above.
readDDD(connection) fetches the summary account only. readAll(connection) fetches the summary and all three books in one getMultipleAccounts call and adds a consistent flag; readAllConsistent(connection) is the same read with a built-in retry when that flag is false. readChains, readIssuers and readTokens fetch one book each.
| Field | Type | Meaning |
|---|---|---|
| admin | PublicKey | Admin key; can rotate the authority and the admin. |
| authority | PublicKey | Key that signs updates. |
| bump | number | Bump seed for the summary PDA. |
| ddd | bigint | Raw on-chain value, (stables / m2) × 106. |
| totalStablesUsd | bigint | Total US-stablecoin circulating supply, in whole USD. Divide by 1e9 for billions. |
| m2Usd | bigint | U.S. M2 money stock, in whole USD. Divide by 1e12 for trillions. |
| timestamp | bigint | Unix seconds for the underlying data point. |
| slot | bigint | Solana slot when the account was last written. |
| sequence | bigint | Monotonic write counter. Books copy it each cycle; compare to detect a mid-cycle read. |
| chainCount | number | Valid rows in the chain book. |
| issuerCount | number | Valid rows in the issuer book. |
| tokenCount | number | Valid rows in the token book. |
| issuerHhi | number | Issuer Herfindahl-Hirschman index on a 0–10,000 scale: the sum of squared market-share percentages. |
| effectiveIssuerScaled | number | Effective issuer count × 106. 2.5 issuers is 2500000. |
| topIssuerShareScaled | number | Largest issuer share as percent × 106, the same encoding book rows use. |
| dddRatio | number | Convenience: DDD as a decimal ratio, ddd / 1e6. 1.4138% is 0.014138. |
| dddPercent | number | Convenience: DDD as a percentage, dddRatio × 100. Ready to display. |
| effectiveIssuerCount | number | Convenience: effectiveIssuerScaled / 1e6. |
| topIssuerSharePercent | number | Convenience: topIssuerShareScaled / 1e6, e.g. 58.97. |
Python 3 · standard library only
Decoding without an SDK
No packages required. This queries the Solana JSON-RPC and decodes the binary account layout directly, using the offsets documented below.
import urllib.request, json, base64, struct from datetime import datetime, timezone # Base58 encoder (no deps) ALPH = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" def b58(b): n = int.from_bytes(b, 'big'); s = '' while n: n, r = divmod(n, 58); s = ALPH[r] + s return '1' * (len(b) - len(b.lstrip(b'\x00'))) + s # Fetch all 4 oracle accounts in one RPC call req = urllib.request.Request( 'https://api.mainnet-beta.solana.com', data=json.dumps({ "jsonrpc": "2.0", "id": 1, "method": "getMultipleAccounts", "params": [[ "8DAncbbsEkmsCCakHpNK5zf49XLcWQrryTBYdjgcLWxA", # summary "97g36T1PV3anxCwxV6ue5MmF2A9HQHMsLqevckrkqjzK", # chains "GznG6pxbLPrqeYDd5aFXJcXK5SQ4wXifBtqjep34DNe", # issuers "7FRjdiN489qMU2R8G2RumBPxRjanY9683gnXttKP4Fhv", # tokens ], {"encoding": "base64"}], }).encode(), headers={'Content-Type': 'application/json'}, ) r = json.loads(urllib.request.urlopen(req).read())['result']['value'] s, c, ib, t = (base64.b64decode(a['data'][0]) for a in r) ts_utc = lambda u: datetime.fromtimestamp(u, tz=timezone.utc).isoformat() # Summary account, offset 73 onward: ddd, total_stables_usd, m2_usd, timestamp, slot, sequence ddd, stb, m2, ts, slot, seq = struct.unpack_from('<QQQqQQ', s, 73) # Concentration fields follow the three u8 counts at 121/122/123. hhi, eff, top = struct.unpack_from('<III', s, 124) print(f"DDD ratio : {ddd/1e4:.4f}%") # raw / 1e4 == percent print(f"Stables : ${stb/1e9:,.4f}B") # whole USD, so /1e9 == billions print(f"U.S. M2 : ${m2/1e12:,.4f}T") print(f"Issuer HHI: {hhi} (effective {eff/1e6:.2f}, top {top/1e6:.2f}%)") print(f"Updated : {ts_utc(ts)} (slot {slot:,})") print(f"Sequence : {seq}") print(f"Admin : {b58(s[8:40])}") # Book accounts: count at byte 25, then fixed 29-byte entries from byte 26. def read_book(name, d): cnt = d[25] print(f"\n--- {name} (count={cnt}) ---") for k in range(cnt): o = 26 + k*29 nm = d[o+1:o+1+d[o]].decode('utf-8', errors='replace') sup, pct = struct.unpack_from('<QI', d, o+17) print(f" {k+1:<3} {nm:<16} ${sup/1e9:>10,.2f}B {pct/1e6:.4f}%") read_book('CHAINS', c) read_book('ISSUERS', ib) read_book('TOKENS', t)
# Static example. This page runs no browser-side RPC call of its own: # third-party RPC endpoints set their own CORS and CSP rules, so a # documentation page is the wrong place to read from one. Run the # snippet above from a server or a shell for real values. DDD ratio : 1.3500% Stables : $305.0000B U.S. M2 : $22.5900T Issuer HHI: 3120 (effective 3.21, top 61.40%) Updated : 2026-08-06T12:00:00+00:00 (slot ...) Sequence : ...
Any language · Solana JSON-RPC
Direct RPC call
No SDK? Fetch the summary account directly with a standard Solana JSON-RPC call. This works with any HTTP client in any language.
$ curl -sS https://api.mainnet-beta.solana.com \ -X POST \ -H 'Content-Type: application/json' \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "getAccountInfo", "params": [ "8DAncbbsEkmsCCakHpNK5zf49XLcWQrryTBYdjgcLWxA", { "encoding": "base64", "commitment": "confirmed" } ] }'
The response is not meant to be readable at a glance. result.value.data[0] is the base64-encoded raw account. Solana does not turn that into DDD = 1.33% for you: decode the base64, then read little-endian fields at the offsets in the layout table below. The long string that looks like noise is the expected result.
If a python3 -c "..." one-liner printed nothing, the cause is usually quoting: double quotes let the shell expand $ inside Python f-strings before Python runs. Use single-quoted -c '...' as below so stdin from curl still reaches Python. Do not use curl | python3 <<'PY'; that replaces stdin and breaks the pipe.
$ curl -sS 'https://api.mainnet-beta.solana.com' -X POST \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":1,"method":"getAccountInfo","params":["8DAncbbsEkmsCCakHpNK5zf49XLcWQrryTBYdjgcLWxA",{"encoding":"base64","commitment":"confirmed"}]}' \ | python3 -c ' import sys, json, base64, struct from datetime import datetime, timezone j = json.load(sys.stdin) raw = base64.b64decode(j["result"]["value"]["data"][0]) ddd, stb, m2, ts, slot, seq = struct.unpack_from("<QQQqQQ", raw, 73) print("DDD % ", round(ddd / 1e4, 4)) print("Stables ", "${:.2f}B".format(stb / 1e9)) print("U.S. M2 ", "${:.2f}T".format(m2 / 1e12)) print("Updated ", datetime.fromtimestamp(ts, tz=timezone.utc).strftime("%Y-%m-%d %H:%M UTC")) print("Slot ", slot) print("Sequence ", seq) '
"commitment": "confirmed" in the params object; many setups need it. If nothing comes back: try the URL without a trailing slash, check whether the provider enforces an IP allowlist, and add -v to see HTTP errors. On Windows, run the command in Git Bash or WSL, as PowerShell handles multiline single-quoted JSON differently.
Binary layout · OracleState
Summary account layout
The summary account is a packed Anchor struct of exactly 252 bytes. Every integer is little-endian and there is no padding between fields, so each offset below is the sum of the widths before it. A read that returns any other length is not this account.
| Field | Offset | Type | Meaning |
|---|---|---|---|
| discriminator | 0 | [u8; 8] | Anchor account discriminator |
| admin | 8 | [u8; 32] | Admin pubkey; base58-encode to display |
| authority | 40 | [u8; 32] | Update authority pubkey |
| bump | 72 | u8 | Bump seed for this PDA |
| ddd | 73 | u64 LE | DDD ratio × 106. Divide by 10,000 for percentage. e.g. 13900 → 1.3900% |
| total_stables_usd | 81 | u64 LE | Total US-stablecoin circulating supply, in whole USD. Not cents, and not scaled. e.g. 319000000000 → $319B |
| m2_usd | 89 | u64 LE | U.S. M2 money stock, in whole USD. e.g. 21700000000000 → $21.7T |
| timestamp | 97 | i64 LE | Unix seconds (UTC) of the underlying data point. Signed, not unsigned. |
| slot | 105 | u64 LE | Solana slot of the last write |
| sequence | 113 | u64 LE | Monotonic write counter. Books copy it; compare to detect a mid-cycle read. |
| chain_count | 121 | u8 | Valid rows in the chain book |
| issuer_count | 122 | u8 | Valid rows in the issuer book |
| token_count | 123 | u8 | Valid rows in the token book |
| issuer_hhi | 124 | u32 LE | Issuer HHI on a 0–10,000 scale: the sum of squared market-share percentages. e.g. a 56% issuer contributes 3136 |
| effective_issuer_scaled | 128 | u32 LE | Effective issuer count × 106. e.g. 2500000 → 2.5 issuers |
| top_issuer_share_scaled | 132 | u32 LE | Largest issuer share, percent × 106. e.g. 58970000 → 58.97% |
| reserved | 136 | [u8; 116] | Reserved for future fields. Do not read; do not assume it stays zero. |
ddd is the decimal ratio × 106, so percent is ddd / 10,000. The *_scaled concentration fields and every book row's pct_scaled are a percentage × 106, so percent is value / 1,000,000. Both use 106; they do not mean the same thing.
Book account layout
The three book accounts share one layout. Only the row capacity, and therefore the total size, differs: chains holds up to 20 rows (638 bytes), issuers and tokens up to 25 (783 bytes each). Rows past count are present in the account but not meaningful.
| Field | Offset | Type | Meaning |
|---|---|---|---|
| discriminator | 0 | [u8; 8] | Anchor account discriminator |
| bump | 8 | u8 | Bump seed for this PDA |
| sequence | 9 | u64 LE | Sequence stamped when this book was written. Must equal the summary's for an atomic read. |
| timestamp | 17 | i64 LE | Unix seconds of the cycle this book belongs to |
| count | 25 | u8 | Number of valid rows |
| entries[i] | 26 + 29i | 29 bytes | Fixed-width row: name_len u8, name [u8; 16], supply_usd u64 LE at +17 (whole USD), pct_scaled u32 LE at +25 (percent × 106). |
| reserved | 26 + 29·capacity | [u8; 32] | Reserved for future fields |
Research · /api/v1/oracle/*
Oracle over HTTP
If you want the on-chain values without running an RPC client, two versioned read-only endpoints return the decoded accounts as JSON. They read the same four accounts the SDK reads, so they are a convenience over the oracle, not a separate source. They carry the same research register as the oracle itself.
GET /api/v1/oracle/summary— the decoded summary account only. The cheapest read.GET /api/v1/oracle/snapshot— the summary plus the chain, issuer and token books from one multi-account read, with aconsistentboolean. Whenconsistentisfalse, anotefield explains that the read landed mid-cycle; retry shortly rather than using the books.
Both wrap their payload in apiVersion, kind, programId and cluster, and both return every 64-bit value as a string so no precision is lost in JSON. Alongside the raw fields they include the same convenience values the SDK computes: dddRatio, dddPercent, effectiveIssuerCount, topIssuerSharePercent, plus updatedAt as an ISO timestamp derived from timestamp.
Both are CORS-open, answer OPTIONS, and reject any method other than GET with 405. Each response carries an ETag keyed on the oracle sequence and Cache-Control: public, max-age=15, s-maxage=45, stale-while-revalidate=120. Send If-None-Match with the previous ETag to get 304 Not Modified while the sequence is unchanged. If the account cannot be decoded, both return 503 with error: "oracle_unavailable"; any other failure is 500. Adding ?debug=1 appends a small non-sensitive debug block with the fetch time and the RPC hostname.
GET /api/jupiter-solana-route also exists and may appear in a network log. It is a read-only proxy for a Jupiter swap quote — not an oracle read, not versioned, and not part of the benchmark. Its CORS policy admits only Stable Tape's own origins, so a cross-origin browser call from another site will fail. It is listed here so it is explained, not so it is built on.
agent workflows
Using DDD with agents
People often pick a stablecoin by habit, brand or chain loyalty. An agent can read the structure instead. The endpoints above expose the benchmark, issuer concentration and chain distribution in machine-readable form, which is the input an allocation or routing decision actually needs.
- Treasury
Check issuer concentration — HHI, effective issuer count, top issuer share — before recommending a stablecoin allocation.
- Risk monitoring
Watch for concentration rising: the same three fields, sampled over time from
/api/v1/oracle/summaryor/api/issuers. - Payment planning
Use chain distribution to decide where stablecoins should move. Velocity data is research-stage and is not exposed here.
- Research
Read the dated records directly from
/api/tapeinstead of scraping a dashboard that may re-render.
DDD decides. Execution providers execute.
limits and guarantees
What is and is not promised
- No SLA. These endpoints are published without an uptime, latency or availability guarantee. Cache what you need and degrade gracefully. Every public endpoint here is CDN-cached, so treat the cache window as the real freshness bound.
- Upstream revision. DeFiLlama restates supply and the Federal Reserve revises M2. A current reading can change under you; a frozen dated record cannot. That is the reason to cite records rather than readings.
- Update cadence. The Daily Tape record is cut at 12:00 UTC.
/api/ratioand the structure endpoints recompute per request behind a five-minute cache. The oracle follows its own write cycle and stampstimestamp,slotandsequenceso you can see exactly how old a read is. - No fees on chain. Reading Solana accounts costs nothing. You only pay lamports to write, which reading the ratio never requires.
- RPC endpoints. Every snippet here uses the public cluster
api.mainnet-beta.solana.com, which is rate-limited and not intended for production. Use your own provider. This page itself makes no RPC call: the example output above is a static illustration, not a live read. - Change policy. If a documented field name, unit or scale changes, this page changes with it, and
npm run test:developer-api-docsfails until it does. Interfaces in the research and internal registers carry no such commitment.
Questions? @stabletape
Weekly email
The weekly email.
The week’s stablecoin supply movement and the current benchmark level. One list, at most one email a week.