Reference

Response conventions

A small set of rules every endpoint follows, so the API is predictable across the surface. None of these are negotiable inside /api/v1 — breaking changes ship under a new version path.

JSON casing

Field names are camelCase on both request and response sides. This matches every snippet on the endpoint reference.

Addresses, puzzle hashes, asset IDs

Anywhere the API takes an address, puzzle hash, coin ID, hint, or asset ID, the library normalizes the input through ChiaAddressCodec.Normalize*. All three of these forms are accepted and produce the same result:

  • bech32m address — xch1abcdef...
  • hex with 0x prefix — 0xabc...
  • bare hex — abc...

Output is always canonical lowercase hex with no 0x prefix. If you need a xch1… address back, call POST /api/v1/address/encode.

Mojos and amounts

Anything ending in Mojos is a decimal string, not a JSON number. See working with mojos for conversion snippets and the reasoning. Fields like amountMojos, spendableMojos, and totalUnspentMojos all follow this rule.

Timestamps

Timestamps come back two ways side by side so callers can pick the form they want:

  • timestamp — ISO-8601 UTC string, e.g. "2026-05-12T14:32:01Z".
  • timestampSeconds — raw Unix seconds, as the Chia node emits them.

Errors

Every failure response is RFC 7807 application/problem+json. The shape is consistent regardless of which endpoint produced it:

{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "Bad Request",
  "status": 400,
  "detail": "Address is not valid bech32m.",
  "traceId": "00-abc...-xyz...-00"
}

Status code map:

  • 400 Bad Request — input failed library validation (invalid hex, malformed offer, address outside the configured HRP). Library throws InvalidOperationException; the handler translates to 400.
  • 404 Not Found — the resource (coin, wallet, etc.) does not exist on the local node.
  • 429 Too Many Requests — rate limit exceeded for the per-IP fixed window.
  • 499 Client Closed Request — the caller cancelled before the handler finished.
  • 500 Internal Server Error — an unexpected exception inside a handler. Has a traceId; bring it to the operator.
  • 502 Bad Gateway — connection to the local Chia node or wallet RPC failed.

The traceId field is the same id you'll find in the server logs — useful when reporting an issue.

Rate limits

Two fixed-window limiters keyed by remote IP:

  • Queries (default): 60 requests / minute / IP. Covers node, coin, address, security, admin reads.
  • Offers: 10 requests / minute / IP for POST /offers/inspect and POST /offers/validate, since both round-trip to the local wallet RPC.

Hitting either limit returns 429 Too Many Requests.

Caching

The server caches a handful of read-only responses at the HTTP layer using OutputCache:

  • GET /api/v1/node/state — 5 seconds.
  • GET /api/v1/security/* — 30 seconds.

No coin or wallet read is cached; You can expect them to always be fresh.

Pagination

Coin lookups under /api/v1/coins/by-puzzle-hash and /api/v1/coins/by-hint can return large sets. The handlers wrap the underlying RPC in a flat envelope and accept limit as a query parameter:

{
  "queryType": "puzzleHash",
  "queryValue": "abc...",
  "coinRecords": [/* … */],
  "totalRecords": 17,
  "totalAmount": "12345...",
  "totalUnspentAmount": "9876...",
  "notes": "..."
}

Pagination uses sibling fields, not a wrapping envelope. limit defaults to 100 and is capped at 1000 server-side.

Cancellation

If the caller disconnects mid-request, downstream RPC calls are cancelled too. In-browser, pass an AbortController.signal to fetch and the server will stop work as soon as it detects the closed connection.

Versioning

The path is /api/v1. Additive changes — new fields on a response, new optional query parameters, new endpoints — stay in v1. Anything that would break an existing caller (rename, removal, type change) ships under /api/v2. The OpenAPI spec is the source of truth: /openapi/v1.json.

CORS

During development, the API allows any origin so tinkering from http://localhost in your editor or a sandbox just works. Before the site goes public, CORS is locked down to a configured allow-list in appsettings.Production.json.

Authentication

Right now: none. Every endpoint, including the /api/v1/admin/* group, is reachable anonymously because the host is not yet public.

Before launch: the admin group picks up an OperatorOnly policy backed by an X-Api-Key header validated against a hashed secret in config. The Tier 1 public endpoints stay anonymous. See API_PLAN.md Phase 3 for the full hardening checklist.

An unhandled error has occurred. Reload 🗙