<!-- markdown mirror of https://mintid.net/en/sdk/substrate-reads — generated at build time -->

> Two public reads — substrate status and attestation standing — that answer from proven chain state without a presentation, and establish an issuer’s standing without taking its word.

index — 12 documents

1.  [overviewsdk/00](/en/sdk)
2.  [getting startedsdk/01](/en/sdk/getting-started)
3.  [python sdksdk/02](/en/sdk/python)
4.  [service modesdk/03](/en/sdk/service-mode)
5.  [typescript clientsdk/04](/en/sdk/typescript-client)
6.  [mcp serversdk/mcp](/en/sdk/mcp-server)
7.  [conformancesdk/05](/en/sdk/conformance)
8.  [reason codessdk/06](/en/sdk/reason-codes)
9.  [substrate readssdk/07](/en/sdk/substrate-reads)
    *   [two reads](#two-reads)
    *   [substrate status](#substrate-status)
    *   [attestation standing](#attestation-standing)
    *   [without the issuer’s word](#standing-without-trust)
    *   [freshness arithmetic](#freshness)
    *   [re-initialised deployments](#reinitialised)
    *   [what they do not do](#limits)
10.  [credentials & holderssdk/08](/en/sdk/credentials-and-holders)
11.  [payment identitysdk/09](/en/sdk/payment-identity-profile)
12.  [becoming a verifiersdk/10](/en/sdk/becoming-a-verifier)
13.  [protocol statesdk/11](/en/sdk/protocol-state)
14.  [record · assurance vocabularyrecord/01](/en/sdk/kyc-trust-model)
15.  [record · packagingrecord/02](/en/sdk/packaging)
16.  [record · multi-languagerecord/03](/en/sdk/multi-language-clients)
17.  [record · service contractrecord/04](/en/sdk/verifier-service-contract)

SDK docs · sdk/07

# Substrate reads

Two public reads — substrate status and attestation standing — that answer from proven chain state without a presentation, and establish an issuer’s standing without taking its word.

## Two reads, no presentation

Two reads answer questions about the chain, and about one issuer-signed record, without a presentation, without the proof engine and without you being a registered verifier: `GET /v1/status` and `GET /v1/attestations/{ref}/standing?issuer_id=<64 hex>`. They are **additive to the frozen `verifier-service.v1` contract and versioned separately at v0** — the three v1 operations keep their semantics and their version. Both are public and cacheable for a few seconds; a route a deployment is not configured for answers `404`.

> **Related-party disclosure:** the operator of the first issuer and the first verifier, and the first product built on them, are related parties of MintID. Nothing measured there is independent adoption.

## Substrate status

One aggregate of what the verifier already holds: the node’s head, the proven registry and root state of every issuer it watches, and the chain’s published root policy — **metadata only**, no holder, attestation, witness or presentation.

`substrate`

Means

Commits you to

`ok`

the node produces blocks and every watched issuer has a fresh root on an operational, bonded registry record

only “ask now” — standing can still be unknown for a record no root holds yet

`degraded`

at least one of those failed; detail names which

an operational alarm; standing reads are answering unknown meanwhile

`unknown`

the node itself could not be read

nothing — you have no substrate signal at all

Per watched issuer: `active` (registry record operational), `bond_ok` (live bond at or above the governed minimum), `root_epoch` / `root_height` (the current root and the block that finalized it), `root_age_seconds` (now minus when the issuer generated it), `fresh` (inside its declared window and this verifier’s height-lag policy) and `reason`, from a closed set: `ok`, `issuer_unknown`, `issuer_not_active`, `bond_insufficient`, `root_missing`, `root_expired`, `height_stale`, `state_unavailable`. A last object, `verifier`, reports the deployment’s own id, origin, height-lag policy and build pins.

The **`promise` object** carries chain parameters, not the service’s own configuration. That is why it is published: a cadence read off the chain the verifier proves its reads against is a fact you can check, where the same numbers in a service-level assertion would only be a claim.

jsonsdk/07 · live answer, abridged

```
{ "substrate": "ok", "checked_unix": 1790102713, "detail": "",
  "chain": { "chain_id": "mintid-anchor", "latest_height": 89668, "block_age_seconds": 3,
             "latest_block_time_unix": 1790102710 },
  "issuers": [ { "issuer_id": "a566da63…1df1a944", "reason": "ok", "active": true, "bond_ok": true,
                 "root_epoch": 6317, "root_height": 89662, "root_age_seconds": 14, "fresh": true } ],
  "promise": { "heartbeat_seconds": 30, "max_root_age_seconds": 180,
               "note": "roots are published every heartbeat_seconds and stay fresh up to max_root_age_seconds; a revocation lands in the next root" } }
```

## Attestation standing

`ref` is a 32-byte opaque reference in hex. Besides `status` — `active`, `suspended`, `revoked` or `unknown` — you get `fresh`, `issuer_active`, `bond_ok`, the proven `root` with its epoch and height, and `latest_height`. `reason_code` is `ok` when every check passed, and `status` is then the witnessed one; every failed check degrades to `unknown`:

`reason_code`

What happened

`state_unavailable`

a chain read could not be proven

`issuer_unknown`

no registry record for this issuer

`issuer_not_active`

the issuer is not operational

`status_root_stale`

no current root, or its validity window has passed

`height_stale`

the root was finalized outside the height-lag window

`witness_unavailable · witness_malformed`

no witness served for this reference; or it did not parse, or names another reference

`witness_root_mismatch · witness_invalid`

the witness claims a different root or epoch; or it did not recompute to the proven root

`bond_insufficient`

the witnessed status is still returned, with bond\_ok: false

`bond_insufficient` is the one code that does not degrade: you get the real status, and what a short bond means is your policy.

## Standing without taking the issuer’s word

The checks run in trust order and the issuer is consulted last: (1) its registry record, read from the chain with an inclusion proof; (2) its live bond against the governed minimum, from the same proven read; (3) its current status root, that root’s validity window and the height at which it was finalized; (4) only then the witness — fetched from the issuer, recomputed locally, accepted only if it lands on the _proven_ root at the _proven_ epoch. The issuer supplies the path, never the answer: one that lies about a status serves a witness that does not recompute, one that says nothing gets `witness_unavailable`, never a default yes. Every chain read is proven against a trust anchor, and claimed absence of a record fails closed.

The witness construction is published so you can recompute it yourself. `status_byte` is `0` active, `1` suspended, `2` revoked. A witness is `{ref, status, status_epoch, root, index, path[]}`: fold the path from the leaf, taking the sibling on the left while the running index is odd and halving that index each step, then apply the root hash. The construction is off-chain and issuer-internal.

textsdk/07 · witness construction

```
leaf = SHA-256( 0x00 ‖ "mintid/issuer-status-leaf/v1" ‖ ref ‖ status_byte )
node = SHA-256( 0x01 ‖ left ‖ right )      duplicate the last node on odd levels
root = SHA-256( "mintid/issuer-status-root/v1" ‖ epoch_be64 ‖ tree_root )
```

## Freshness, degradation, and the arithmetic you need

Roots publish every `heartbeat_seconds` and are accepted up to `max_root_age_seconds`; on the first deployment, 30 s and 180 s. Nothing the issuer does is visible before its next root, so **time to first verdict is one root period plus your own polling interval** — 30 s plus a 60 s poll is up to 90 s. A newly issued reference reads `witness_unavailable` until that root: the expected first answer, not a failure, and polling faster than a minute buys nothing. `max_root_age_seconds` is a fail-closed bound, not a delivery promise; the gap above the heartbeat exists so a routine deploy of the issuer’s service does not turn every read `unknown`, and a revocation still lands in the next root.

When the substrate is behind, fail closed. **Never map `unknown` to yes**, never keep serving a cached `active` past its root’s window, and never retry until a check passes: a loop against `state_unavailable` or `status_root_stale` accepts unproven state slowly.

The failure never arrives as an HTTP error. Anything the route accepts returns `200`, with the verdict in `substrate` or in `status`/`reason_code`. `400` covers only an `issuer_id` that is not 32 bytes of hex; a `ref` that is not 64 hex characters misses the route and gets `404`. There is no 5xx for a degraded substrate — alert on the body.

## What a re-initialised deployment changes under you

A deployment can be re-initialised: the operator starts a new chain carrying the registry and the issuer’s book across, rather than migrating state in place. Hostnames, issuer id, verifier id, references and statuses survive; the **chain identifier changes**, block heights restart and `root_epoch` counts from 1 again. Witnesses regenerate at the first heartbeat and standing answers `unknown` until the new chain’s first root is finalized — the ordinary degradation path. **Persist the chain identifier next to any `root_epoch` you keep**: epochs are ordered only within one chain.

## What these reads do not do

They are not presentations and carry no zero-knowledge claim. Standing tells you about a record an issuer signed and placed in a root; it says nothing about whoever holds a credential. `substrate: ok` is a statement about one chain and its watched issuers at one instant, not a service-level commitment or a substitute for your own checks. Neither read makes you a verifier.

One gap to know about before you meet it: a status root can also carry **reserved entries** that are not attestations, used to anchor material the rest of the protocol needs. The rule for recomputing them is not published yet. If you are recomputing roots yourself rather than reading standing, ask for it rather than inferring it from a sample.

Next: [credentials and holders](/en/sdk/credentials-and-holders) — how the records behind a root come to exist; and the [reason codes](/en/sdk/reason-codes) — the separate, frozen vocabulary of the presentation decision.

[previous← reason codes](/en/sdk/reason-codes)[nextcredentials & holders →](/en/sdk/credentials-and-holders)

---
Source: https://mintid.net/en/sdk/substrate-reads · Substrate reads — MintID verifier SDK docs
