Tamga Verify API
Ask a person for credentials from your website or service and receive the verified result.
- Real network
https://verify.tamga.network- Sandbox (test network)
https://verify.sandbox.tamga.network- Authentication
- Bearer (
tamga-rp+jwt) orX-Tamga-Status-Tokenheader - Definition
- OpenAPI 3.1 file · Guide
Tamga Verify is the hosted verifier of Tamga Network (ADR-0017). The browser part is @tamga-network/verifier/web; server-side verification without the hosted verifier is @tamga-network/verifier.
Flow. Your server opens a presentation for a policy (which credential and which fields) → your page shows the QR code or the "Open in your wallet" link and polls the status with the status_token → when the status is DONE, your server reads the result and, once, the approved values.
Privacy. The result and values go only to the relying party that opened the presentation. Values can be read once and are deleted at the latest 5 minutes after the result. The browser sees only the status, never values.
Outcomes. ACCEPTED, REJECTED (with the failing step) or INDETERMINATE ("could not check right now" — never treat it as invalid).
Endpoints
| Endpoint | Description |
|---|---|
POST /presentations | Open a presentation request |
GET /presentations/{id} | Get the status or the verification result |
GET /presentations/{id}/claims | Read the approved values (once) |
GET /presentations/{id}/qr.png | Get the QR code image |
GET /policies | List the policies |
GET /tamga-verifier.js | Load the page kit |
Authentication
Bearer (tamga-rp+jwt)
Authorization: Bearer <RP assertion> — a short-lived JWS (typ: tamga-rp+jwt, ES256) signed with the private key of your access certificate registered in the Tamga trust list. Header x5c carries the certificate; payload { iss: <your client_id>, aud: "https://verify.tamga.network", iat, exp ≤ iat + 60, jti }. No shared secret; jti is single-use. Helper: createRpAssertion() in @tamga-network/verifier.
X-Tamga-Status-Token header
Status-only token for the browser (also accepted as ?st=). Shows the status, never values.
Presentations
Ask for credentials and read the result.
Open a presentation request
/presentationsCreates a signed OpenID4VP request for the policy. The policy must be within your registered scope (otherwise 400 policy_exceeds_scope). Send Accept: application/json to get JSON; without it the verifier redirects to its own waiting page.
Authentication Bearer (tamga-rp+jwt)
Parameters
| Name | In | Type | Description |
|---|---|---|---|
Accept required | header | string | Ask for a JSON answer. Always application/json |
Request body
application/json
| Field | Type | Description |
|---|---|---|
policy_id required | string | A policy from GET /policies, e.g. site-signup, age-over-18-mdoc. |
dc_api_origin | string (uri) | Optional. The page origin (https://…, no path) when the presentation is answered through the browser's Digital Credentials API. The request is then bound to this origin (expected_origins). |
Responses
| Status | Description |
|---|---|
| 200 | Presentation opened. Returns StartedPresentation |
| 400 | unknown_policy, policy_exceeds_scope (the policy asks for more than your registered scope; detail lists what) or invalid_request (invalid dc_api_origin). Returns Error |
| 401 | invalid_rp_assertion — the RP assertion is expired, reused or not signed by a registered, active relying party. Returns Error |
| 429 | rate_limited — too many requests. Wait the number of seconds in Retry-After and try again. The client address is not stored or logged; the wallet-facing /vp/response and the gate /terminal/verify endpoints have the same protection. Returns Error Headers: Retry-After |
Example
curl -X POST "https://verify.tamga.network/presentations" \
-H "Authorization: Bearer $RP_ASSERTION" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"policy_id":"site-signup"}'{
"presentation_id": "prs_Q2x0dVJmNkp3",
"request_uri": "https://verify.tamga.network/vp/req/prs_Q2x0dVJmNkp3",
"qr_payload": "openid4vp://?client_id=x509_hash%3A…&request_uri=https%3A%2F%2Fverify.tamga.network%2Fvp%2Freq%2Fprs_Q2x0dVJmNkp3",
"expires_at": "2026-10-09T12:05:00Z",
"status_token": "9VdB0qXr7mYk2LwPq1sZ3A"
}Get the status or the verification result
/presentations/{id}With your RP assertion: the full verification result ({"state": "PENDING"} until the wallet answers). With only the status token (?st= or X-Tamga-Status-Token, no Authorization): the status for the browser, without values or names.
Authentication Bearer (tamga-rp+jwt) or X-Tamga-Status-Token header
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string | The presentation_id from createPresentation. |
st | query | string | Status token from createPresentation, for the browser. |
Responses
| Status | Description |
|---|---|
| 200 | Status (PENDING), the browser status, or the full result. Returns VerificationResult or BrowserStatus or Pending |
| 401 | invalid_rp_assertion. Returns Error |
| 404 | not_found — unknown presentation, or not yours (existence is not revealed). Returns Error |
Example
curl "https://verify.tamga.network/presentations/prs_Q2x0dVJmNkp3" \
-H "Authorization: Bearer $RP_ASSERTION"{
"verification_id": "vrf_Q2x0dVJmNkp3",
"outcome": "ACCEPTED",
"failed_step": "…",
"failed_reason": "…",
"indeterminate_reason": "SCHEMA_UNREACHABLE",
"spec_version": "…",
"sdk_version": "…",
"checks_performed": [
"T0",
"A1",
"A2",
"B1",
"C1",
"D1",
"E1"
],
"checks_skipped": [],
"issuer": {
"issuer_id": "…",
"state_code": "TR",
"legal_name": "…",
"category": "…",
"assurance": "…",
"class": "PUB"
},
"schema": {
"schema_id": "…",
"vct": "urn:tamga:id:IdentityAttestation:1",
"status": "…"
},
"disclosed_claims": [
"given_name",
"family_name"
],
"status": {
"value": "VALID",
"list_version": 0,
"token_age_sec": 0,
"reason": "…"
},
"freshness": {
"trust_source": "list",
"trust_version": 0,
"trust_age_sec": 0
},
"evaluated_at": "2026-10-09T12:00:00Z"
}Read the approved values (once)
/presentations/{id}/claimsReturns the values the person approved. Available once, at most 5 minutes after the result.
Authentication Bearer (tamga-rp+jwt)
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string | The presentation_id from createPresentation. |
Responses
| Status | Description |
|---|---|
| 200 | Values, or null while the presentation is pending. |
| 404 | not_found — unknown presentation, or not yours. |
| 410 | claims_gone — values already read or expired. |
Example
curl "https://verify.tamga.network/presentations/prs_Q2x0dVJmNkp3/claims" \
-H "Authorization: Bearer $RP_ASSERTION"{
"claims": {
"given_name": "Ayşe",
"family_name": "Yılmaz",
"pseudonym": "ps_7Qm…"
}
}Get the QR code image
/presentations/{id}/qr.pngA PNG of the qr_payload link (440 px), ready to put in an <img>.
Authentication None — public
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string | The presentation_id from createPresentation. |
Responses
| Status | Description |
|---|---|
| 200 | PNG image. |
| 404 | Unknown presentation. |
Example
curl "https://verify.tamga.network/presentations/prs_Q2x0dVJmNkp3/qr.png"Policies
What can be asked.
List the policies
/policiesThe policies this verifier can ask for. No personal data.
Authentication None — public
Responses
| Status | Description |
|---|---|
| 200 | Policy summaries. Returns PolicySummary |
Example
curl "https://verify.tamga.network/policies"[
{
"policy_id": "age-over-18-zk",
"purpose": "Over-18 check with a zero-knowledge proof — yes/no only, nothing else",
"purpose_localized": {
"en-US": "Over-18 check with a zero-knowledge proof — yes/no only, nothing else",
"tr-TR": "Sıfır bilgi ispatıyla 18 yaş üstü doğrulaması — yalnızca evet/hayır, başka hiçbir şey"
},
"vct_values": [
"urn:tamga:id:IdentityAttestation:1"
],
"claims": [
"age_over_18"
],
"proximity": false,
"format": "mso_mdoc_zk"
}
]Page kit
Files for the browser side.
Load the page kit
/tamga-verifier.jsThe browser bundle of @tamga-network/verifier/web (QR code, "Open in your wallet" button, status polling, Digital Credentials API). Cached for 5 minutes.
Authentication None — public
Responses
| Status | Description |
|---|---|
| 200 | JavaScript. |
Example
curl "https://verify.tamga.network/tamga-verifier.js"Objects
StartedPresentation
| Field | Type | Description |
|---|---|---|
presentation_id required | string | |
request_uri required | string (uri) | Where the wallet fetches the signed request. |
qr_payload required | string | openid4vp://… link — show as a QR code on desktop, as a button on phones. |
expires_at required | string (date-time) | |
status_token required | string | Give only this to the browser. |
dc_api_request | object | Present when dc_api_origin was sent — input for navigator.credentials.get({ digital }). |
dc_api_request.protocol | string | Always openid4vp-v1-signed |
dc_api_request.data | object | |
dc_api_request.data.request | string | Signed request object (JWS). |
Pending
The wallet has not answered yet.
| Field | Type | Description |
|---|---|---|
state | string | Always PENDING |
BrowserStatus
What the browser sees with the status token — no values, no names.
| Field | Type | Description |
|---|---|---|
state | string | One of: PENDING · DONE |
outcome | string | One of: ACCEPTED · REJECTED · INDETERMINATE |
failed_reason | string | null | |
indeterminate_reason | string | null |
VerificationResult
The full result, for the relying party that opened the presentation.
| Field | Type | Description |
|---|---|---|
verification_id | string | |
outcome | string | INDETERMINATE means "could not check right now" — never treat it as invalid. One of: ACCEPTED · REJECTED · INDETERMINATE |
failed_step | string | null | The first failing step of the verification pipeline (T0, A1…E3, P1) when REJECTED. |
failed_reason | string | null | |
indeterminate_reason | string | null | One of: SCHEMA_UNREACHABLE · STATUS_UNREACHABLE · STATUS_STALE · CHAIN_UNREACHABLE · INDEXER_STALE · SDK_VERSION_MISMATCH |
spec_version | string | Verification specification version. |
sdk_version | string | Verifier package version. |
checks_performed | array<string> | |
checks_skipped | array<string> | |
issuer | object | null | The institution that issued the credential, from the trust list. |
issuer.issuer_id | string | |
issuer.state_code | string | |
issuer.legal_name | string | |
issuer.category | string | |
issuer.assurance | string | |
issuer.class | string | One of: PUB · QUALIFIED · EAA |
schema | object | null | |
schema.schema_id | string | |
schema.vct | string | |
schema.status | string | |
disclosed_claims | array<string> | Names of the disclosed fields (values via /claims). |
status | object | Revocation status of the credential. |
status.value | string | One of: VALID · INVALID · SUSPENDED · NOT_APPLICABLE · UNKNOWN |
status.list_version | integer | null | |
status.token_age_sec | integer | null | |
status.reason | string | null | Why the status has this value, when it is not self-explanatory — e.g. NOT_APPLICABLE for a zero-knowledge presentation, which does not reveal the revocation index (ADR-0032; the credential is short-lived). The policy decides whether this is accepted; if not, the outcome is INDETERMINATE. Contains no personal data. |
freshness | object | How fresh the trust data used was. |
freshness.trust_source | string | One of: list · chain |
freshness.trust_version | integer | |
freshness.trust_age_sec | integer | |
evaluated_at | string (date-time) |
PolicySummary
| Field | Type | Description |
|---|---|---|
policy_id | string | |
purpose | string | Purpose in English. |
purpose_localized | object | Purpose by language tag (en-US, tr-TR). |
vct_values | array<string> | |
claims | array<string> | Requested fields. |
proximity | boolean | The policy also issues a gate pass. |
format | string | mso_mdoc_zk = zero-knowledge proof over an mdoc (ADR-0032). A wallet that cannot produce the proof uses the classic mso_mdoc policy instead (e.g. age-over-18-mdoc). One of: dc+sd-jwt · mso_mdoc · mso_mdoc_zk |
Error
| Field | Type | Description |
|---|---|---|
error | string | |
error_description | string | |
detail | array<string> | With policy_exceeds_scope, what goes beyond your scope. |
Import the machine-readable definition into any OpenAPI tool to generate a client or send test requests: hosted-verifier-api.openapi.yaml.