Credentials and holders
How a credential is issued over OpenID4VCI, held in a headless holder library, kept current by witness refresh, and retired when its check expires.
The lifecycle, in four movements
A presentation is only as good as the credential behind it. A credential starts from an approved identity check, is blind-signed by the issuer, lives in a holder library you embed, and becomes usable once a finalized status root commits it. Four properties carry the rest:
- The grade is the issuer’s, not the holder’s. The offer carries the grade and date of birth the approved identity check produced; a holder cannot ask for a grade.
- The issuer never sees the credential in the clear. The holder sends a blinded request bound to a one-shot nonce from the token endpoint; the issuer blind-signs, the holder unblinds locally, and the link secret stays inside the holder library.
- A credential is not usable the moment it is signed. It becomes usable when a finalized status root commits the accumulator it lives in; until then the witness endpoint says so, and a conforming holder does not present.
- Holders do not accumulate on chain. The chain carries the issuer’s periodic status root — never a credential id, serial, presentation or holder identifier.
Issuance follows OpenID4VCI; the holder library is the stable surface
There is no OpenAPI document for issuance. The operator-facing side of the issuer service has one; the holder-facing endpoints are specified by their implementation and by this page, and by nothing else. So a wallet somebody else builds has no stable issuance contract to build against today, and the intended integration is the holder library below. The shape follows OpenID4VCI with a pre-authorized code grant; the credential format and proof type are MintID’s own.
| Endpoint | Called by | What it does |
|---|---|---|
GET /.well-known/openid-credential-issuer | anyone | issuer metadata: the credential configuration, the issuer’s current definition, and where its accumulator is anchored |
POST /v1/credential-offers | the issuer’s operator | turns one approved verification session into an offer with a pre-authorized code |
POST /v1/token | the holder | redeems that code once for an access token and a nonce |
POST /v1/credential | the holder | fulfils a blinded issuance request bound to that nonce |
GET /v1/credentials/{id}/witness | the holder | the usability gate plus fresh witness material |
Operator routes are authenticated; holder routes are reached with the one-shot code and token, and one token yields at most one credential.
There is no wallet, on purpose
The holder is a headless library you embed, running in your own infrastructure next to the keys you already custody. MintID ships no consumer wallet, browser key store, hosted key or mobile app, and has none planned: a wallet front-end is outside the specification, and a dashboard that could read your key would be custody in fact. Nothing here prevents a credential from one day being presented from a wallet somebody else builds — the transports are the standard ones — but that is not a surface MintID offers you today. The library is Rust, with three bindings — Python, Go and TypeScript; the Python and Go bindings drive it as a child process over JSON lines (no cgo, no hand-written FFI).
| Operation | What it does | What it guarantees |
|---|---|---|
issue | the holder half of issuance: metadata, token, blinded request, unblinding | the issuer learns a commitment, never the link secret |
refresh_witness | adopts the issuer’s current definition, this credential’s membership witness, and the finalized root anchoring them | self-checked first: material that does not verify is refused and the holder keeps its previous state |
present | answers a verifier’s challenge with one bound, 10-second envelope | the binding is derived by the library from the challenge itself; if the credential cannot satisfy the policy the call fails and nothing is sent |
self_revoke | revokes the holder’s own credential through a privacy relay that submits the revocation and pays for it, so the holder needs no account | in production on the single-node production chain since 24 September 2026: neither who the holder is nor which credential it was is revealed; the issuer must remove it within a bounded window the chain enforces, and every service refuses it in under two minutes; its independent audit is a scheduled milestone whose findings are published |
There is no export. The one serialization is a custody encoding holding the link secret: encrypt it under your own key, keep it where your payment key lives. The holder’s view of itself reports the issuer, grade, validity instants, credential id and current root — never the secret, never the date of birth. A lost key is recoverable by nobody: you re-verify and get a fresh credential, unlinkable to the old one.
When a credential expires
A credential expires exactly when the verification behind it does: 12 months after approval for grades A1 and A2, 6 months for grade A3, 3 months for grade A4 (grades are encoded 1–4 on the wire) — however late in that period the credential was issued. There is no grace period and no extension. Once the verification has expired, every issuance step is refused, including an offer made just before it. The expiry is inside the credential and proven at every presentation; past it, the credential no longer satisfies an unexpired policy, and the way back is a new verification.
Witness refreshes, and what a stale one costs
Non-revocation is proven against the accumulator the issuer’s latest finalized status root commits, and roots move on a short heartbeat.
- Cadence: size it against how often roots are published, not how long one stays accepted. In practice: refresh right before presenting.
- Stale fails closed. A witness that no longer matches the anchored accumulator cannot build a valid presentation, and a proof built against a superseded root is rejected once that root leaves its validity window.
- The refusal names the failed check —
status_root_stale, not a generic failure. Refresh once and retry, never loop (reason codes). - Revocation lands at the next root, and landing is not yet refusal everywhere: a proof against the previous root stays acceptable while that root is inside its validity window, so every verifier refuses a credential its issuer revoked in under a minute at the reference values, or at once through the emergency key.
Where identity evidence lives
With the verification provider, and nowhere else. The boundary is narrow and provider-neutral: a driver opens a case, reports its state, and returns an authenticated verdict enumerating the checks it performed. The issuer keeps that verdict, an opaque handle to evidence in its own vault, a grade, timestamps and a status; raw evidence reaches neither MintID nor the chain. The provider does not set the grade: a driver that returned one is rejected, because grading is the issuer’s decision over enumerated checks, bounded by what its admission permits.
Assurance grades
| Grade | What the issuer asserts |
|---|---|
| A1 | basic binding of the subject to the credential |
| A2 | document verification plus a liveness / face-match step |
| A3 | A2 plus enhanced due diligence with periodic review |
| A4 | highest assurance, under stricter controls |
A grade measures the strength of the check, not its recency; freshness is a separate dimension, carried beside the grade and never folded into it. Note what follows from that: a presentation today proves a grade threshold, and there is no freshness predicate — if recency matters to your decision, it is not something a proof will tell you. The issuer publishes the band, and it is read on the classical seller rail. An issuer may use only the grades its admission permits, and a presentation proves a threshold — “at least A2” — never the exact grade or the attributes. MintID’s four grades are its own vocabulary, mapped to eIDAS and ISO/IEC 29115 without claiming conformance to either, and the reasoning is in the assurance vocabulary record.
Current status
The proof core that issues and presents these credentials is built and runs on the single-node production chain, in production since 20 September 2026 and operated by the CONECTIA group, and integrators use it under commercial terms; the CONECTIA group, the chain’s first issuer and verifier, and ZadQ, the first product built on them (on the classical seller rail), are related parties of MintID. An independent audit of its implementation is a scheduled milestone whose findings are published. Holder-initiated revocation has been in production on the single-node production chain since 24 September 2026, through the privacy relay the operations table describes; its independent audit is a scheduled milestone whose findings will be published. The verifier side of the exchange starts at the integration overview; what you owe before any code is in getting started, and roots and registry state are in substrate reads.