SDK docs · sdk/07

Substrate reads

Two public reads, substrate status and attestation standing, answer from proven chain state with no presentation and without taking the issuer’s word.

Two reads that need no presentation

Two reads tell you whether the substrate is healthy and what standing one issuer-signed record has, from proven chain state, with no presentation, no proof engine and no verifier registration: 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, so 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 single-node production chain, in production since 20 September 2026, and its first issuer and verifier are operated by the CONECTIA group; the CONECTIA group and ZadQ, the first product built on them (on the classical seller rail), are related parties of MintID, and 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.

substrateMeansCommits you to
okthe node produces blocks and every watched issuer has a fresh root on an operational, bonded registry recordonly “ask now” — standing can still be unknown for a record no root holds yet
degradedat least one of those failed; detail names whichan operational alarm; standing reads are answering unknown meanwhile
unknownthe node itself could not be readnothing — 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.

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_codeWhat happened
state_unavailablea chain read could not be proven
issuer_unknownno registry record for this issuer
issuer_not_activethe issuer is not operational
status_root_staleno current root, or its validity window has passed
height_stalethe root was finalized outside the height-lag window
witness_unavailable · witness_malformedno witness served for this reference; or it did not parse, or names another reference
witness_root_mismatch · witness_invalidthe witness claims a different root or epoch; or it did not recompute to the proven root
bond_insufficientthe 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.

Freshness, degradation, and the arithmetic you need

Roots publish every heartbeat_seconds and are accepted up to max_root_age_seconds: 30 s and 45 s at the reference values, 30 s and 180 s on the single-node production chain. 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, which is the expected first answer, and polling faster than a minute buys nothing. max_root_age_seconds is the fail-closed bound and promises no delivery time; the gap above the heartbeat lets a routine deploy of the issuer’s service pass without turning every read unknown, and a revocation still lands in the next root. For presentations, landing is not yet refusal everywhere: a proof built against the superseded root stays acceptable while that root is inside its own validity window, so every verifier refuses an issuer revocation within max_root_age_seconds plus a few seconds of clock tolerance (under a minute at the reference values, 185 s on the single-node production chain), and at once after an emergency 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.

Limits of these reads

Both reads are classical checks of signed records: standing describes a record an issuer signed and placed in a root, and nothing about whoever holds a credential, and no zero-knowledge claim attaches to either. substrate: ok describes one chain and its watched issuers at a single point in time; your own checks still decide, and 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 — how the records behind a root come to exist; and the reason codes — the separate, frozen vocabulary of the presentation decision.