PATINA docs

API conventions

Read these once and every endpoint becomes predictable.

What you will know after this page
  • Why satoshi amounts arrive as JSON strings and heights as numbers, and what a proxy breaks by swapping them.
  • How to walk a list with cursor and limit, and which filters have to be resent with every cursor.
  • Why a long page walk is not a snapshot, and which field to record before and after one.
  • The five fields on /status that identify the indexer you are talking to.
  • Which response changes can land without warning, and which ones have to go through the upgrade boundary.

Base path

https://your-indexer.example.org/patina/...

The app backend proxies a read only subset under /patina/... with the organisation's fail closed conventions, plus two build helpers that return unsigned transaction plans. It holds no keys and takes no custody.

On that path segment

The program has a rule against version labels in names, folders, and copy, and the indexer base path follows it like everything else. An earlier draft of the baseline put a numbered segment in this path, which broke the rule; the baseline was corrected before launch, and every implementation serves /patina.

Types

The table below is the whole serialisation contract. One rule explains most of it: satoshi amounts travel as strings so that a consumer holding them in a 32 bit integer cannot truncate one silently, and everything else travels as the type you would expect. Read the last column when you are tempted to normalise a field in your own layer.

How values are serialised.
KindJSON typeExampleWhy
Satoshi amountsstring"100000"Survives parsers that lose precision on large integers
Block heightsnumber893000Always small enough to be safe
Depths and countsnumber13000Same
Ids and hashesstring, lowercase hex"d3b8d301..."One fixed spelling
Txidsstring, display order"1d29e5ba..."Matches block explorers. Wire order appears only inside derivations.
Outpoints in requestsstring"txid:vout"One field instead of two
Absent valuesnull"carrier": nullFields are present with null, not omitted

There are no timestamps anywhere in the protocol surface. Heights are the clock. If you need calendar time, convert from heights yourself and label it as an estimate.

Requests

  • Everything is GET except POST /patina/safety/outpoints, which is a POST only because the request body can be long. It stores nothing.
  • Request bodies are application/json.
  • No authentication is defined by the protocol. An operator may put their own in front.
  • Unknown query parameters are ignored rather than rejected, so clients can add hints safely.

Pagination

List endpoints take cursor and limit, and return items plus next_cursor.

GET /patina/artifacts?limit=100
GET /patina/artifacts?limit=100&cursor=<next_cursor from the previous page>
Pagination rules.
RuleDetail
Cursors are opaqueTreat the string as a token. Do not parse it, do not build one, do not assume it encodes an offset.
End of listnext_cursor is null. An empty items array with a null cursor means there is nothing more.
LimitClient asks, server decides. Always read how many items you actually received rather than assuming you got limit.
OrderingStable within a page sequence, and documented per endpoint. Do not rely on ordering that is not stated.
Filters must be repeatedSend the same founding, status, or address filters with the cursor.
Pagination is not a snapshot

New blocks arrive while you page. An artifact can move between page one and page four, so a long walk can miss items or repeat them. If you need a consistent view, record indexed_height before you start, and check it is unchanged when you finish. If it moved and you need exactness, walk again.

Sync state

Every consumer should read this before trusting anything else:

{
  "tip_height": 0,
  "indexed_height": 0,
  "synced": false
}
  • tip_height is what the Bitcoin node reports.
  • indexed_height is what this indexer has processed.
  • synced is the operator's view of whether the gap is acceptable.

A large gap means the answers are correct for an older chain, not wrong. Show the height you read at, and never present data from an unsynced indexer as current.

Sync state tells you how fresh an answer is, not whether it is true. For that, check the indexer against a node: the public site walks one such check end to end on Verify it yourself.

Identity of the indexer

status also carries network, protocol_id, spec_sha256, parser_version, and indexer_version. Log all five alongside any data you keep. When two systems disagree, the first question is whether they are running the same spec on the same network.

Compatibility promise

  • New fields may appear on existing responses. Ignore fields you do not know.
  • New endpoints may appear.
  • Existing fields are not removed, renamed, or repurposed.
  • Sat values stay strings. Do not switch them to numbers in your own proxy.

Anything beyond that is a protocol change and goes through the upgrade boundary.