Cardano chain data REST API (FastAPI) for db-sync
Find a file
2026-06-28 21:13:33 -07:00
.forgejo/workflows docs: rewrite README for public release + add MIT LICENSE 2026-06-28 12:51:08 -07:00
.gitignore Initial commit: Cardano chain data REST API 2026-03-18 11:43:46 -07:00
Dockerfile fix: Enforce strict tier-based access control for node endpoints 2026-03-21 09:15:40 -07:00
LICENSE license: relicense to AGPL-3.0-or-later 2026-06-28 21:13:33 -07:00
main.py docs: tidy inline comments and rewrite README 2026-05-27 11:15:02 -07:00
README.md license: relicense to AGPL-3.0-or-later 2026-06-28 21:13:33 -07:00
requirements.txt chore(deps): update dependency pydantic to v2.13.4 2026-06-27 21:10:22 +00:00

cardano-api

A REST API over cardano-db-sync and cardano-node, built with FastAPI, asyncpg, and Redis.

Read queries — address balances, native tokens, transaction history, assets, stake pools, and blocks — are served directly from the db-sync Postgres database. Live UTxO lookups, current protocol parameters, and transaction submission shell out to cardano-cli against a local node socket. Access is gated by API keys, and keys can optionally be issued self-service by proving ownership of a wallet that holds a configurable native token (a CIP-8 signature challenge).

Features

  • Address balances, native-token holdings, and transaction history from db-sync
  • Block, transaction, asset, and stake-pool lookups
  • Live UTxO and protocol-parameter queries via the node
  • Transaction submission to the network
  • Redis response caching with per-endpoint TTLs
  • Tiered API keys with per-tier rate limits
  • Optional token-gated, self-service key issuance via CIP-8 wallet signatures
  • Strict input validation (bech32 addresses, hex tx hashes / policy IDs) and a body-size cap on transaction submission

Requirements

  • A synced cardano-db-sync Postgres database (the cexplorer schema)
  • A running cardano-node with a reachable IPC socket — required only for the node-backed endpoints (live UTxOs, protocol params, tx submit)
  • Redis — rate limiting, response cache, and API-key storage
  • cardano-cli on PATH — the provided Docker image installs it for you

Quick start

The container image bundles cardano-cli; you provide the db-sync database, a node socket, and Redis. A minimal compose.yml:

services:
  cardano-api:
    build: .
    ports:
      - "8765:8765"
    environment:
      DB_HOST: postgres-dbsync
      DB_NAME: cexplorer
      DB_USER: dbsync
      DB_PASS: ${DB_PASS}
      REDIS_HOST: redis
      API_MASTER_KEY: ${API_MASTER_KEY}
      CARDANO_NODE_SOCKET_PATH: /node-ipc/node.socket
      CARDANO_NETWORK: mainnet
    volumes:
      - /path/to/node-ipc:/node-ipc
  redis:
    image: redis:7
docker compose up -d --build

The API listens on port 8765. Put it behind your own reverse proxy / TLS terminator as needed.

To run without Docker:

pip install -r requirements.txt
uvicorn main:app --host 0.0.0.0 --port 8765

(Install cardano-cli separately if you need the node-backed endpoints.)

Configuration

All configuration is via environment variables:

Variable Default Description
DB_HOST postgres-dbsync db-sync Postgres host
DB_PORT 5432 db-sync Postgres port
DB_NAME cexplorer db-sync database name
DB_USER dbsync db-sync database user
DB_PASS (empty) db-sync database password
REDIS_HOST redis-api Redis host
REDIS_PORT 6379 Redis port
API_MASTER_KEY (empty) Unrestricted master key for /admin/*
CARDANO_NODE_SOCKET_PATH /node-ipc/node.socket Node IPC socket path
CARDANO_NETWORK mainnet Cardano network

TRUSTED_PROXIES in main.py controls which client IPs are allowed to set the X-Forwarded-For header. It defaults to loopback plus the Docker bridge gateways; override it to match your own proxy setup.

Authentication & tiers

Pass an API key in the X-API-Key header (preferred) or as an ?api_key= query parameter. Requests with no key are treated as anonymous.

Tier Token balance Rate (req/min) tx submit (/min) Node read Node submit
anonymous 0 20 0 no no
standard ≥ 50 100 2 yes no
elevated ≥ 500 1000 10 yes yes
master n/a unlimited unlimited yes yes

Anonymous requests are rate-limited per source IP; authenticated tiers are rate-limited per key. The master key is supplied out-of-band via API_MASTER_KEY and can mint non-expiring keys at POST /admin/keys.

Token-gated keys (optional, self-service)

A wallet that holds enough of a configured native token can mint its own API key by signing a challenge nonce with CIP-8. The gating token's policy ID and the tier thresholds are constants near the top of main.py — point them at your own token to use this flow. Token-gated keys expire 48 hours after issue and can be re-authenticated (which also re-tiers them) via POST /v1/auth/refresh. A background task re-checks balances every 10 minutes and re-tiers keys in place.

POST /v1/auth/challenge  { "address": "addr1..." }
  -> { "nonce": "...", "expires_at": "..." }

# sign the nonce with the wallet via CIP-8

POST /v1/auth/verify     { "address", "nonce", "signature", "key" }
  -> { "api_key": "capi_...", "tier", "trp_balance" }

POST /v1/auth/refresh    (X-API-Key header — self-service only)
  -> { "tier", "trp_balance", "expires_at", ... }

Endpoints

GET  /health
GET  /v1/sync/status

GET  /v1/block/latest
GET  /v1/block/{block_no}

GET  /v1/address/{address}/balance
GET  /v1/address/{address}/tokens?page=&limit=
GET  /v1/address/{address}/transactions?page=&limit=&order=
GET  /v1/address/{address}/utxos                            (standard+)

GET  /v1/tx/{tx_hash}
POST /v1/tx/submit                                          (elevated+)

GET  /v1/asset/{policy_id}/info?page=&limit=
GET  /v1/asset/{policy_id}/{asset_name}/holders?limit=

GET  /v1/pool/{pool_id}/info

GET  /v1/protocol-params                                    (standard+)

POST /v1/auth/challenge
POST /v1/auth/verify
POST /v1/auth/refresh

POST   /admin/keys                                          (master)
DELETE /admin/keys/{key}                                    (master)
GET    /admin/keys                                          (master)
POST   /admin/refresh-tiers                                 (master)
GET    /admin/stats                                         (master)

Caching

Responses are cached in Redis with per-endpoint TTLs (seconds):

balance         60      asset_info     120
tokens          60      pool_info      120
transactions    30      sync_status      5
block_latest    10      protocol_params 300
tx_details     300      utxos           10

Security notes

  • API keys are stored as sha256(key). The raw key is returned exactly once, at creation; lookups, listing, and revocation all operate on the hash.
  • Every path parameter is validated against a strict regex (bech32 mainnet/testnet addresses, 64-hex tx hashes, 56-hex policy IDs) before any query runs.
  • POST /v1/tx/submit bodies are capped at 64 KB. The middleware reads the actual request stream, so chunked transfer or a missing Content-Length can't bypass the limit.
  • CIP-8 verification rejects non-EdDSA algorithms, wrong key length, empty or mismatched payloads, and any key that doesn't hash to the claimed address.

Contributing

Issues and pull requests are welcome. Please keep changes focused, run the app against a local db-sync + node before submitting, and don't commit secrets — a gitleaks workflow scans every push and pull request.

License

Released under the GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later). See LICENSE.