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

> mintid-verifier-sdk: a frozen public surface, one decision entrypoint, five infrastructure ports — and a proof engine pinned inside the wheel.

index — 12 documents

1.  [overviewsdk/00](/en/sdk)
2.  [getting startedsdk/01](/en/sdk/getting-started)
3.  [python sdksdk/02](/en/sdk/python)
    *   [the public surface](#public-surface)
    *   [the entrypoint](#entrypoint)
    *   [issuing challenges](#issuing-challenges)
    *   [chain access](#chain-access)
    *   [retention](#retention)
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.  [adr-0001 · kyc trust modeladr/0001](/en/sdk/adr-0001-kyc-trust-model)
10.  [adr-0002 · packagingadr/0002](/en/sdk/adr-0002-packaging)
11.  [adr-0003 · multi-languageadr/0003](/en/sdk/adr-0003-multilanguage)
12.  [bip-0001 · contract freezebip/0001](/en/sdk/bip-0001-verifier-service-contract)

SDK docs · sdk/02

# Python SDK (embedded mode)

mintid-verifier-sdk: a frozen public surface, one decision entrypoint, five infrastructure ports — and a proof engine pinned inside the wheel.

## The public surface

Package **`mintid-verifier-sdk`**, import name **`verifier_core`**. Python ≥ 3.12. The Rust proof engine ships compiled inside the wheel at a build-time pin — a supported installation cannot end up with a substituted or mismatched engine.

One decision entrypoint, five ports. Everything importable is listed in the block on the right; everything else under `verifier_core` is internal and may change without notice.

This surface is **frozen and test-enforced**: a CI test imports exactly this list and fails when it grows or drifts. Changes follow semver — surface or semantics change is a major version.

pythonsdk/02 · verbatim

```
from verifier_core.presentation import (
    validate_presentation, PresentationDecision, ChainReader,
    DEFAULT_MAX_HEIGHT_LAG,  # and the REASON_* constants
)
from verifier_core.openid4vp import (
    PresentationRequest, IssuerPolicy, ClaimPredicate,
    PRESENTATION_LIFETIME_SECONDS,  # = 10. A constant, deliberately.
    RequestSigner, parse_request,
)
from verifier_core.proof_verifier import (
    PresentationEnvelope, VerifiedPresentation, BindingContext,
    ProofVerifier, binding_context_for,
)
from verifier_core.chain_client import (
    ChainClient, StateQueryPort, TrustedHeaderSource,
    StateQueryResult, ProofOp, verify_membership,
)
from verifier_core.nonce_store import NonceStore, SqliteNonceStore, NONCE_SIZE
from verifier_core.audit import (
    DecisionLog, DecisionRecord, JsonlDecisionLog, InMemoryDecisionLog,
    DECISION_RECORD_FIELDS,
)
from verifier_core import errors, records
```

## The entrypoint

You supply **infrastructure, never policy**:

Port

Production implementation

Your freedom

`ChainReader`

`ChainClient` over your RPC node(s) + trust-anchor header source

which nodes — never whether reads are proven

`NonceStore`

bundled `SqliteNonceStore` (crash-durable, `synchronous=FULL`)

swap the backend if it keeps atomic exactly-once `consume`

`ProofVerifier`

the wheel's pinned engine

none — implementations must reject any proof not bound to the exact challenge

`DecisionLog`

`JsonlDecisionLog` (or your store over `DecisionRecord`)

where records go — never what they contain

clock

you pass `now_unix`

a skewed clock only makes you _more_ rejecting

pythonsdk/02 · verbatim

```
decision = validate_presentation(
    request,            # PresentationRequest — the signed challenge you issued
    envelope,           # PresentationEnvelope — the holder's response
    chain=chain,        # ChainReader        — proven on-chain reads
    nonces=nonces,      # NonceStore         — durable, exactly-once consumption
    proofs=proofs,      # ProofVerifier      — the anonymous-proof engine port
    decisions=log,      # DecisionLog        — the minimal decision-record sink
    now_unix=now,       # int                — your disciplined clock
    max_height_lag=20,  # local staleness policy (blocks); tighten freely
)
decision.accepted      # bool
decision.reason_code   # closed vocabulary
decision.verified      # VerifiedPresentation | None
```

## Issuing challenges

The policy digest commits to the whole canonical body — tampering with any part of the challenge breaks the proof binding (condition 5).

pythonsdk/02 · verbatim

```
import secrets
from verifier_core.openid4vp import (
    PresentationRequest, IssuerPolicy, ClaimPredicate,
    PRESENTATION_LIFETIME_SECONDS,
)

request = PresentationRequest(
    session_id=secrets.token_bytes(16).hex(),
    nonce=secrets.token_bytes(32),
    verifier_id=MY_VERIFIER_ID,          # your on-chain registration
    audience="https://checkout.example", # one of your registered exact origins
    request_key_id=MY_REQUEST_KEY_ID,    # registered for that origin
    expires_at_unix=now + PRESENTATION_LIFETIME_SECONDS,
    finalized_chain_height=chain.latest_height(),
    issuer_policy=IssuerPolicy(),        # or restrict accepted issuers / state minimum
    claim_policy=(ClaimPredicate(claim="age_over", value=18, operator=">="),),
)
request.validate_shape(now_unix=now)
signed_body = request.to_json()          # canonical; sign with your registered key
```

## Chain access

`ChainClient` splits trust into two ports on purpose:

*   `StateQueryPort` — **any** RPC node, assumed malicious. Every answer carries an ics23 proof.
*   `TrustedHeaderSource` — your trust anchor. The bundled `TrustAnchorHeaderSource` reads an **operator-designated node** (your own node): suitable when you run the node you point it at. Backing this port with a full light client against untrusted peers is on the roadmap; never point the anchor at an arbitrary public RPC.

Absence is never trusted: if a node claims a record doesn't exist, the read fails closed (`UnprovenAbsenceError` → `state_unproven`).

## Retention (what you keep, all of it)

Exactly one `DecisionRecord` per decision, accept or reject: `session_id`, `accepted`, `reason_code`, `bound_context_digest` (32 bytes), `decided_at_unix`. Serialization emits exactly these keys; tests assert field-set equality. Presentations, proof bytes and claim values are never persisted — treat any schema extension as a privacy review.

[previous← getting started](/en/sdk/getting-started)[nextservice mode →](/en/sdk/service-mode)

---
Source: https://mintid.net/en/sdk/python · Python SDK (embedded mode) — MintID verifier SDK docs
