Tax API
Every Cosmos Hub block indexed and replayed into tax records: events, a double-entry journal, open lots, CARF and DAC8 summaries, past-date balances and validator statements, as JSON or CSV.
Quickstart
The read endpoints are public and need no key: pass any Cosmos Hub address and a date range. Every example below runs as pasted against the sample validator's operator account.
curl
# Every reward, transfer, swap and fee in October, priced in USD at receipt
curl -s "https://bryanlabs.net/api/tax/v1/events?address=cosmos18ruzecmqj9pv8ac0gvkgryuc7u004te9xr2mcr&start=2026-10-01&end=2026-10-31&format=json" | jq '.summary'
# The same rows as CSV
curl -s -o events.csv "https://bryanlabs.net/api/tax/v1/events?address=cosmos18ruzecmqj9pv8ac0gvkgryuc7u004te9xr2mcr&start=2026-10-01&end=2026-10-31&format=csv"
# One validator's commission and delegator rewards by month
curl -s "https://bryanlabs.net/api/tax/v1/validator-statement?validator=cosmosvaloper18ruzecmqj9pv8ac0gvkgryuc7u004te9rh7w5s&start=2026-10-01&end=2026-10-31" | jq '.months'JavaScript (Node 18+ or browser)
const url = new URL("https://bryanlabs.net/api/tax/v1/journal");
url.search = new URLSearchParams({
address: "cosmos18ruzecmqj9pv8ac0gvkgryuc7u004te9xr2mcr",
start: "2026-10-01",
end: "2026-10-31",
format: "json",
}).toString();
const res = await fetch(url);
if (!res.ok) {
const problem = await res.json(); // RFC 9457: type, title, status, detail, code, request_id
throw new Error(`${problem.code}: ${problem.detail} (request ${problem.request_id})`);
}
const journal = await res.json();
console.log(journal.lines.length, "journal lines; coverage:", journal.index_coverage?.complete);Python 3 (standard library)
import json, time, urllib.parse, urllib.request
from urllib.error import HTTPError
params = urllib.parse.urlencode(
{"address": "cosmos18ruzecmqj9pv8ac0gvkgryuc7u004te9xr2mcr", "start": "2026-10-01", "end": "2026-10-31", "type": "rewards"}
)
url = f"https://bryanlabs.net/api/tax/v1/events?{params}"
for attempt in range(3):
try:
with urllib.request.urlopen(url, timeout=120) as res:
events = json.load(res)
break
except HTTPError as e:
if e.code == 429:
time.sleep(int(e.headers.get("Retry-After", "60")))
continue
problem = json.load(e)
raise SystemExit(f"{problem['code']}: {problem['detail']} (request {problem['request_id']})")
print(events["summary"])Coverage and accuracy
Cosmos Hub from genesis on 2021-02-18. Every report says whether its own period is fully indexed (index_coverage in JSON, X-Index-Coverage headers on CSV), and index-coverage gives each year's percentage. Rows without a USD price carry price_missing rather than a zero. Every figure names the methodology version that produced it.
Before the Gaia v6 upgrade (2021-12-14) amounts are read from messages and module events, and a past-date balance before then is refused with 409. Commission flows answer empty until the daily trace job publishes its first traces.
Authentication and API keys
Saved reports, tags, entities and preferences belong to an account. Create a key under Account with the tax:read scope to read and tax:write to change, and send it as Authorization: Bearer blk_.... A missing or revoked key is 401; a key without the scope is 403 with code insufficient_scope.
curl
# Saved reports and account settings need an API key (Account, API keys)
curl -s -H "Authorization: Bearer $BRYANLABS_API_KEY" "https://bryanlabs.net/api/tax/reports"Rate limits and errors
60 requests per 60 seconds per IP on every /api/tax/v1 read. Each response carries RateLimit and RateLimit-Policy; over the limit is 429 with Retry-After.
Every error is an RFC 9457 problem (application/problem+json) with a stable code and the request_id also sent as X-Request-Id. A report blocked by flagged receipts is 409 asset_review_required; decide them with asset-review and pass asset_decisions. Codes are listed under conventions.
Versioning and support
The path is versioned (/v1). Additive fields ship without notice; anything breaking gets 90 days' notice with Deprecation and Sunset headers, then 410. Every change is dated in the changelog.
Machine-readable: OpenAPI 3.1 and llms.txt. Live health is on status; report a vulnerability through security.