PATINA docs

Endpoint reference

Every endpoint in the indexer contract. Read API conventions first for types, cursors, and sync state.

What you will know after this page
  • Which call to make first, and the two fields on it that decide whether any later answer means anything.
  • What GET /window reports before, during, and after the founding commit window, and why both countdowns are block counts.
  • How to read an artifact record, including the two fields that are null at Elder and the one that is null for a relic.
  • What POST /safety/outpoints classifies an outpoint as, and what a wallet owes the user for each answer.
  • Which reads the app backend proxies, and what its two build helpers never do.
Every value in the examples is a placeholder

These pages publish no statistics. Counters in the examples are zero and heights are round numbers, so that nothing here can be mistaken for a measurement. To see real numbers, point the prober below at an indexer, or call one yourself.

Try it against your own indexer

Nothing has been requested. Enter an indexer you trust and the raw responses will appear here.

The request goes from your browser straight to the host you typed. This site has no backend, stores nothing, and has no default indexer. Opening the page from disk may cause the browser to block the request, which the panel will tell you plainly.

Status and health

GET /status

curl -s https://indexer.example.org/patina/status
{
  "network": "signet",
  "protocol_id": "PTNA",
  "spec_sha256": "0000000000000000000000000000000000000000000000000000000000000000",
  "tip_height": 0,
  "indexed_height": 0,
  "synced": false,
  "parser_version": "",
  "indexer_version": "",
  "counters": {
    "artifacts_alive": 0,
    "artifacts_relic": 0,
    "founding_total": 0,
    "rings_total": 0,
    "deepest_live_depth": 0,
    "endowment_total_sats": "0"
  }
}

The first call any client should make. Compare indexed_height with tip_height before trusting anything else, and log spec_sha256 with anything you store.

GET /health, GET /ready, GET /metrics

Three endpoints an operator wires into probes. The one that carries weight is /ready. An indexer that reports ready while it is replaying a reorg will be handed traffic it cannot answer honestly, and the wrong answers will look exactly like right ones.

Operational endpoints.
EndpointAnswersUse it for
/healthIs the process aliveLiveness probes. It does not mean the data is current.
/readyIs it safe to send trafficReadiness probes. Should fail while replaying a reorg or during initial sync.
/metricsOperational counters for scrapingDashboards and alerts. See Verify and monitor.

The founding window

GET /window

curl -s https://indexer.example.org/patina/window
{
  "state": "PENDING",
  "h_open": 0,
  "h_close": 0,
  "grace_end": 0,
  "tip_height": 0,
  "blocks_until_open": 0,
  "blocks_remaining": 0,
  "founding_total": 0
}
state
Where the chain is relative to the window. Before h_open, between the two heights, or past h_close with or without grace remaining.
blocks_until_open
Counts down before the window opens. Zero once it has opened.
blocks_remaining
Blocks left to commit while the window is open.
founding_total
How many founding artifacts exist so far. This is a real count from indexed chain data, which is why it can only be read from an indexer and never from this page.

Both countdowns are block counts, not clocks. Convert to time only as a labelled estimate. The window is 4032 blocks long and the reveal grace after it is another 4032, and what each of those two stretches is for is drawn on Firstlight Seals.

Artifacts

GET /artifacts

curl -s 'https://indexer.example.org/patina/artifacts?founding=true&status=ALIVE&limit=50'
Query parameters.
NameValuesEffect
cursoropaque stringContinue a previous page
limitintegerRequested page size. The server may return fewer.
foundingtrue, falseRestrict to the founding cohort, or exclude it
statusALIVE, RELICRestrict by state
addressaddress stringOnly artifacts whose carrier pays that address
{
  "items": [],
  "next_cursor": null
}

Each item is an artifact record. Repeat every filter when you send a cursor.

GET /artifacts/:id

curl -s https://indexer.example.org/patina/artifacts/d3b8d3013c23dd3df76882034df80935ba55aa75772007f0b142b63adce5eff0
{
  "artifact_id": "d3b8d3013c23dd3df76882034df80935ba55aa75772007f0b142b63adce5eff0",
  "birth_txid": "1d29e5ba13cecf357a9218518914617f872eeaa83a33bf6be6fd78dfdd1ce9c6",
  "birth_height": 880000,
  "birth_vout": 1,
  "endowment_sats": "100000",
  "founding": true,
  "status": "ALIVE",
  "carrier": {
    "txid": "1d29e5ba13cecf357a9218518914617f872eeaa83a33bf6be6fd78dfdd1ce9c6",
    "vout": 1,
    "height": 880000,
    "value": "100000"
  },
  "depth": 13000,
  "tier": 3,
  "tier_name": "Verdigris",
  "next_tier": 4,
  "blocks_to_next_tier": 13280,
  "rings": []
}

Field by field explanation on Reading your artifact. At Elder, next_tier and blocks_to_next_tier are null. For a relic, carrier is null. tier is the index and tier_name is the name for it; all eight, with the block threshold each one starts at, are set out on The tier journey.

GET /artifacts/:id/card

curl -s https://indexer.example.org/patina/artifacts/d3b8d3013c23dd3df76882034df80935ba55aa75772007f0b142b63adce5eff0/card

Returns the share card payload: the identifying fields, the current tier and depth, the ring count, and the height the numbers were taken at. A card without that height cannot be checked by whoever sees it, so renderers must print it. See Rules that keep renders honest.

Lookups

GET /addresses/:address/holdings

curl -s https://indexer.example.org/patina/addresses/tb1p.../holdings

Artifacts whose current carrier pays that address. This is a lookup by output script, not a claim about ownership or identity. One person may use many addresses, and one address may be used by many people.

GET /carriers/:txid/:vout

curl -s https://indexer.example.org/patina/carriers/1d29e5ba13cecf357a9218518914617f872eeaa83a33bf6be6fd78dfdd1ce9c6/1

The carrier record for one outpoint and every artifact riding it. This is how you discover a bundle: more than one artifact on the same carrier.

History and views

Eight read only endpoints, every one of them derived from indexed blocks rather than stored separately. The split to hold on to while you read the table: some of these are settled history that can never change, and some are a picture of the tip that changes whenever somebody spends a carrier.

Endpoints that summarise state. All of them are derived from indexed chain data.
EndpointReturnsOrdering
GET /census/currentThe survival table for the current 2016-block epochDeterministic per epoch
GET /census/:epochThe same table for a past epochFixed once the epoch is complete
GET /museumThe longest completed ringsDeepest first
GET /leaderboard?scope=allDeepest live stretchesDeepest first
GET /leaderboard?scope=foundingSame, restricted to the founding cohortDeepest first
GET /shatterRecently closed ringsNewest first
GET /invalid-eventsRecent invalid events with reason codesNewest first
GET /statsCounters and distribution healthSingle object

The census is per epoch because 2016 blocks is the difficulty period, which is the natural unit for a table that says how many stretches survived. The museum is history and cannot shrink. The leaderboard is the present and changes every time somebody moves.

Concentration lives in stats

GET /stats is where distribution health is published, including during the founding window. It is deliberately a first class endpoint rather than an internal metric, because farming the founding cohort is a known limit and hiding the numbers would be the dishonest response to it.

Safety check

POST /safety/outpoints

curl -s -X POST https://indexer.example.org/patina/safety/outpoints \
  -H 'content-type: application/json' \
  -d '{"outpoints":["1d29e5ba13cecf357a9218518914617f872eeaa83a33bf6be6fd78dfdd1ce9c6:1"]}'
{
  "protected": true,
  "kind": "carrier",
  "artifact_ids": ["d3b8d3013c23dd3df76882034df80935ba55aa75772007f0b142b63adce5eff0"]
}

Three classifications, and the wallet behaviour each one asks for. Treating carrier the same as none is how a user loses a stretch to coin selection without ever being asked.

Classification values.
kindMeansWhat a wallet should do
carrierSpending this ends a stretch and resets depthWarn loudly, name the artifacts, require confirmation
commitAn unspent commit output waiting to be revealedWarn that spending it destroys a pending claim
noneNot known to the protocolNothing special

The endpoint stores nothing that you send it. It is a POST because a coin selection can be long, not because it writes anything.

The app backend proxy

The Bitcoin Universe backend re-exposes a read only subset under /patina, with fail closed behaviour when the indexer is unavailable, plus two build helpers.

/patina/status            /patina/window
/patina/artifacts         /patina/artifacts/:id
/patina/census            /patina/museum
/patina/leaderboard       /patina/shatter
/patina/stats             /patina/addresses/:address/holdings
POST /patina/safety/outpoints
POST /patina/plan/commit    unsigned commit plan and a review summary
POST /patina/plan/seed      unsigned reveal plan and a review summary

The planners never hold key material, never take custody, and never broadcast anything you have not signed. Fail closed means an unavailable indexer produces an error, not a stale or invented answer.