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

> How a credential comes to exist, who holds it and when it expires: issuance over OpenID4VCI, the headless holder library instead of a wallet, witness refresh, and where identity evidence lives.

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)
10.  [credentials & holderssdk/08](/en/sdk/credentials-and-holders)
     *   [the lifecycle](#lifecycle)
     *   [issuance surface](#issuance-surface)
     *   [no wallet, on purpose](#no-wallet)
     *   [when it expires](#validity)
     *   [witness refresh](#witness-refresh)
     *   [identity evidence](#identity-evidence)
     *   [assurance grades](#grades)
     *   [current status](#status)
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/08

# Credentials and holders

How a credential comes to exist, who holds it and when it expires: issuance over OpenID4VCI, the headless holder library instead of a wallet, witness refresh, and where identity evidence lives.

## The lifecycle, in four movements

A presentation is only as good as the credential behind it. This page is the other side of that exchange: how a credential comes to exist, who holds it, and what the holder component does and refuses to do. Four properties carry the rest of it:

1.  **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.
2.  **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.
3.  **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 an honest holder does not present.
4.  **Holders do not accumulate on chain.** The chain carries the issuer’s periodic status root — never a credential id, serial, presentation or holder identifier.

textsdk/08 · the lifecycle

```
identity check ──▶ offer ──▶ token ──▶ blinded request ──▶ credential
   a verdict reaches the issuer,                          signed blind
   never the evidence                                           │
   presentations, 10 seconds each ◀── witness refresh, per root ─┘
```

## The issuance surface has no published contract

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 third-party wallet has nothing stable to build against today, and the intended integration is the **library below**, not a wallet. 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. Two bindings exist — **Python** and **Go** (the Rust library as a child process over JSON lines: no cgo, no hand-written FFI); no TypeScript binding yet.

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`

—

not implemented. It refuses and changes nothing; holder-initiated revocation is a scheduled, audit-gated milestone

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 1 and 2, 6 months for grade 3, 3 months for grade 4 — 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; one built against a superseded root is rejected.
*   **The refusal names the failed check** — `status_root_stale`, not a generic failure. Refresh once and retry, never loop ([reason codes](/en/sdk/reason-codes)).
*   **Revocation lands at the next root.** Until it finalizes, the previously anchored state still verifies: cadence, not discretion.

## 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

Read these honestly. 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. 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 claims conformance to no external assurance framework**: these four are its own vocabulary, and the reasoning behind them is in the [assurance vocabulary record](/en/sdk/kyc-trust-model).

## Current status

The proof core that issues and presents these credentials is built and in production. Holder-initiated revocation is the part not yet implemented, as the operations table says. The verifier side of the exchange starts at the [integration overview](/en/sdk); what you owe before any code is in [getting started](/en/sdk/getting-started), and roots and registry state are in [substrate reads](/en/sdk/substrate-reads).

[previous← substrate reads](/en/sdk/substrate-reads)[nextpayment identity →](/en/sdk/payment-identity-profile)

---
Source: https://mintid.net/en/sdk/credentials-and-holders · Credentials and holders — MintID verifier SDK docs
