# Welcome to Blerify Docs

Blerify is a digital identity platform for issuing and verifying credentials. You use it to issue verifiable credentials to end users — government IDs, professional licenses, access passes — and to verify those credentials at any point of interaction, on the web or on mobile.

This documentation is for developers building on top of Blerify: issuers, verifiers, and wallet integrators.

***

## Introduction

Understand the fundamentals before you build.

* [What is Blerify](/introduction/what-is-blerify) — the platform, the products, and who it's for
* [How It Works](/introduction/how-it-works) — the full issuance and verification flow from end to end
* [Portal Roles & Permissions](/introduction/portal-roles-and-permissions) — who on your team can do what, across the whole Portal

## Issuance

Issue verifiable credentials to your users.

* [Get Started](/introduction-to-issuance/build/get-started) — prerequisites and your first credential
* [Issue a W3C Credential](/introduction-to-issuance/build/issue-a-w3c-credential) — step-by-step guide for Portal and API issuance
* [Issue an mDoc](/introduction-to-issuance/build/issue-an-mdoc) — ISO 18013 mDoc issuance (coming soon)

## ID Wallet

Integrate the Blerify Wallet into your mobile app.

* [Get Started](/id-wallet/get-started) — what the wallet is and how holders receive credentials

## Verification

Verify credentials presented by your users.

* [Get Started](/introduction-to-verification/build/get-started) — your first working verification integration
* [Read a Verification Result](/introduction-to-verification/build/read-a-verification-result) — result schema, evidence, and error handling
* [Use Cases](/introduction-to-verification/use-cases) — age verification and customer onboarding, end to end
* [Biometric Binding](/introduction-to-verification/learn/biometric-binding) — what key attestation proves and where the limits are
* [Platform Attestation](/introduction-to-verification/learn/platform-attestation) — Android vs iOS evidence differences

## Trust Registry

The trust infrastructure behind Blerify's credential guarantees.

* [Decentralized Root of Trust](/trust-registry/decentralized-root-of-trust) — how Blerify anchors trust without a central authority
* [DID Method](/trust-registry/did-method) — the `did:lac1` method specification

## API Reference

Complete endpoint contracts and request/response schemas.

* [API Documentation](https://dev.blerify.com) — authentication and core API endpoints


# How It Works

Every credential in Blerify involves three parties: an **issuer** who creates it (a government, a university, an employer), a **holder** who carries it in the wallet on their phone, and a **verifier** who checks it. What makes the model powerful is what's missing — no central database anyone has to call or trust. The credential itself carries the proof. This design follows the W3C Verifiable Credentials standard.

This page explains how that works end to end: how a credential gets issued, how it's checked, and how much you can rely on each verification.

***

## The three participants

| Participant  | What they do                                                                     | Blerify product |
| ------------ | -------------------------------------------------------------------------------- | --------------- |
| **Issuer**   | Creates and cryptographically signs a credential, then delivers it to the holder | Issuance        |
| **Holder**   | Stores the credential on their device, decides what to share and with whom       | ID Wallet       |
| **Verifier** | Asks the holder to present a credential and checks its validity                  | Verification    |

These three roles never need to communicate directly with each other. The trust between them is established by the credential's cryptographic signature and by the Blerify Trust Registry, which records which issuers are authorized to issue which credential types.

***

## End-to-end flow

### 1 — Issuance

The issuer creates a credential containing the holder's data — name, document number, qualification, or whatever the use case requires — signs it with their private key, and delivers it to the holder's wallet.

```
Issuer backend ──▶ Blerify Issuance API ──▶ Signed credential
                                                  │
                                                  ▼
                                            Holder's Wallet
```

The credential is signed once at issuance and never modified afterward. If the issuer later revokes it, they update the revocation registry. The credential in the wallet remains unchanged, but any subsequent verification will reflect the revoked status.

### 2 — Holding

The holder receives the credential into the Blerify Wallet via a QR code, a deep link, or a push notification. The wallet stores the credential on-device, encrypted at rest, and unlockable only with the holder's biometric or PIN.

The holder controls consent: when a verifier requests a credential, the wallet shows them exactly which fields will be shared before anything leaves the device.

### 3 — Presentation

The holder presents the credential to the verifier. This can happen in three ways:

**Same-device** — the holder is on a mobile web page or inside a native app. A button or deep link opens the Blerify Wallet on the same phone. They approve with their biometric, and the app or page receives the result.

**Cross-device** — the holder is at a desktop. They scan a QR code with their phone. The wallet opens, they approve, and the desktop page updates.

**App-to-app** — the holder is inside a native mobile app. A deep link opens the wallet directly. The wallet finishes and returns focus to the calling app.

In all three cases, the holder's explicit approval is required before any data leaves their device.

### 4 — Verification

The verifier's backend creates a verification session with Blerify and gets back a `transaction_id`. The verifier's frontend uses that to build a QR code or deep link for the holder. The wallet submits a signed presentation directly to Blerify. The verifier's backend polls for the result.

```mermaid
sequenceDiagram
    participant VB as Verifier Backend
    participant Blerify
    participant VF as Verifier Frontend
    participant W as Holder's Wallet

    VB->>Blerify: Create verification session
    Blerify-->>VB: transaction_id
    Note over VB,VF: Backend hands transaction_id to frontend
    VF->>W: Show QR code / deep link
    W->>Blerify: Submit signed presentation
    VB->>Blerify: Poll for result
    Blerify-->>VB: Verified data
```

The verifier's backend is the only party that calls the Blerify API. The service account token that authenticates those calls must never be exposed to a browser or mobile app.

***

## What Blerify validates

When a credential is presented, Blerify runs all of these checks automatically:

| Check                 | What it verifies                                                                  |
| --------------------- | --------------------------------------------------------------------------------- |
| **Signature**         | The credential was signed by a key belonging to a registered issuer               |
| **Trust chain**       | The issuer is in the Trust Registry and authorized to issue this credential type  |
| **Revocation**        | The issuer has not revoked this credential                                        |
| **Expiry**            | The credential is within its validity period                                      |
| **Holder binding**    | The presentation was made by the person who controls the credential's private key |
| **Replay protection** | The presentation includes a fresh nonce — it cannot be reused                     |

You don't implement any of these checks. You receive a structured result on each one and apply your own access policy on top.

***

## The Trust Registry

The Trust Registry is the source of truth for who can issue what. When Blerify validates a credential, it checks the registry to confirm:

* The issuer exists and is active
* The issuer is authorized to issue the specific credential type being presented
* The issuer's signing key matches what's recorded in the registry

The Trust Registry uses Blerify's DID method (did:lac1) to anchor issuer identities on a public ledger. This means the trust chain is verifiable by anyone — including verifiers outside the Blerify ecosystem.

See [Decentralized Root of Trust](/trust-registry/decentralized-root-of-trust) for the full technical model.

***

## Credential formats

**W3C Verifiable Credentials** are JSON documents signed as JWTs. The schema is flexible — issuers define their own claim types. Commonly used for soft credentials: licenses, badges, KYC status, employment records.

**ISO 18013 mDocs** are binary CBOR documents following the ISO 18013-5 standard for mobile driving licenses. The schema is defined by the ISO standard and uses standardized field names and namespaces. Used for government identity documents.

Both formats travel the same way, over OpenID4VP — the open protocol wallets use to present credentials to verifiers. The validation pipeline detects the format and routes to the appropriate verification path automatically.

***

## Assurance tiers

Not all verifications carry the same weight. Blerify reports an `assurance_level` — `BASIC`, `STANDARD`, or `PREMIUM` — alongside every verification result. The tier reflects what can be independently proven to you, not just what the wallet asserts.

| Tier                      | What it proves                                                                                                                                                                                                                                                   |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Basic**                 | The credential is cryptographically valid, the issuer is trusted, the credential isn't revoked, and the holder presented it in real time. Works with any wallet that follows the open presentation standard.                                                     |
| **Standard**              | Everything in Basic, plus the signing key is proven to reside in tamper-resistant hardware (TEE or Secure Enclave). The verifier receives a hardware attestation certificate chain they can validate independently against Google or Apple roots.                |
| **Premium** (coming soon) | Everything in Standard, plus the person physically holding the device is confirmed to be the credential subject. Blerify captures a live selfie from within the wallet, compares it against the photo embedded in the signed credential, and returns the result. |

The tier you receive depends on the wallet submitting the proof. Any wallet that follows the open standard achieves Basic. Standard and Premium require the Blerify Wallet, which submits hardware attestation evidence alongside the credential presentation.

You choose the tier when you configure the verification. If the wallet can't supply the evidence that tier requires, the verification fails with an explicit reason rather than quietly completing at a lower level.

See [Assurance Tiers](/introduction-to-verification/learn/assurance-tiers) for the full model, including the informative mapping to eIDAS and NIST frameworks.

***

## Privacy model

**Selective disclosure.** When a verifier requests a credential, they specify which fields they need. The wallet shows a consent screen listing exactly those fields. The holder approves or declines. Blerify only forwards what the holder approved.

**No call home.** The issuer is not notified when a holder presents a credential. Blerify does not maintain a presentation log accessible to issuers. The issuer's involvement ends at issuance.

**On-device storage.** Credentials are stored in the Blerify Wallet on the holder's device, not in a Blerify cloud database. If the holder uninstalls the wallet, the credentials are gone from Blerify's systems.

**No biometric storage.** When Premium assurance is requested, Blerify compares a live selfie against the photo embedded in the credential, then immediately deletes both. Blerify stores only the boolean result and a confidence score. There is no face database, no biometric template, and nothing to breach beyond a pass/fail flag.

***

## Next steps

* Issue your first W3C credential — [Issuance: Get Started](/introduction-to-issuance/build/get-started)
* Verify your first credential — [Verification: Get Started](/introduction-to-verification/build/get-started)
* Understand how trust anchors work — [Decentralized Root of Trust](/trust-registry/decentralized-root-of-trust)
* Learn how assurance tiers map to eIDAS and NIST — [Assurance Tiers](/introduction-to-verification/learn/assurance-tiers)


# What is Blerify

Blerify is a digital identity infrastructure platform that lets governments and enterprises issue tamper-proof credentials, lets people carry those credentials on their phone, and lets any organization verify them in seconds — without calling back to the issuer.

***

## Who it's for

**Issuers** are organizations that want to put their credentials on a user's phone: a government agency issuing national IDs, a university issuing diplomas, a financial institution issuing KYC status, a professional body issuing licenses. Blerify handles the cryptography, the trust registry, and the delivery — the issuer provides the data.

**Holders** are the people who receive credentials into the Blerify Wallet. They control what they share and with whom. No credential leaves their phone without their biometric approval.

**Verifiers** are organizations that need to confirm something about a person — age, identity, employment status, professional license — without storing the underlying document. They call a small API and get back a verified result.

***

## What you can issue and verify

Blerify supports two credential formats:

**W3C Verifiable Credentials** — the JSON-LD / JWT format defined by the W3C. Use this for any credential whose schema you define: employee badges, academic diplomas, insurance cards, professional certifications.

**ISO 18013 mDocs (mDL)** — the ISO standard for mobile driving licenses and government-issued identity documents. Use this when you need interoperability with government systems or ISO-compliant readers.

Both formats are signed with the issuer's cryptographic key, anchored in a decentralized trust registry, and verifiable offline.

***

## Real use cases in Latin America

**National digital identity.** A government agency issues citizens a digital version of their national ID card. Citizens receive it in the Blerify Wallet; banks, telecoms, and retailers verify it at onboarding without photocopying anything.

**Professional licenses.** A professional body issues digital licenses to doctors, lawyers, and engineers. Hospitals and law firms verify the license via API before granting system access — no phone calls, no registry lookups.

**Academic credentials.** A university issues diplomas and transcripts as W3C credentials. Employers verify them instantly. The university doesn't need to run a verification hotline.

**Employee digital badges.** A company issues a digital work credential to contractors and full-time employees. Physical access systems, HR portals, and benefits platforms verify the credential in the same flow as a standard API call.

**Financial KYC.** A regulated financial institution issues a KYC-passed credential to customers who've already gone through identity verification. Partner organizations can accept that credential without running their own KYC — the cryptographic proof travels with the holder.

***

## How Blerify relates to open standards

Blerify implements open W3C and ISO standards — not a proprietary protocol. Credentials issued on Blerify are interoperable with any wallet or verifier that supports:

* OpenID for Verifiable Credential Issuance (OID4VCI)
* OpenID for Verifiable Presentations (OpenID4VP)
* ISO 18013-5 for mDocs

This means holders are not locked into the Blerify Wallet and verifiers are not locked into the Blerify API. Where interoperability matters, credentials work beyond the Blerify ecosystem.

***

## The four products

Blerify ships as four independent products, each with its own portal, API, and access control:

| Product            | What it does                                                 |
| ------------------ | ------------------------------------------------------------ |
| **Issuance**       | Issue W3C credentials and ISO 18013 mDocs to holders         |
| **ID Wallet**      | The mobile app where holders receive and present credentials |
| **Verification**   | Verify credentials presented by holders                      |
| **Trust Registry** | The decentralized record of who can issue what               |

Each product is documented in its own section. You don't need all four — most integrations use two or three.

Post-Quantum Certificates is a feature within Issuance, not a standalone product — see [Post Quantum Certificates](/introduction-to-issuance/post-quantum-certificates).

***

## Next steps

* Understand the full issuance-to-verification flow in [How It Works](/introduction/how-it-works)
* Start issuing credentials in [Issuance — Get Started](/introduction-to-issuance/build/get-started)
* Start verifying credentials in [Verification — Get Started](/introduction-to-verification/build/get-started)


# Authentication

This page shows you what a service account is, how to create one in the Portal, and how to trade its credentials for a bearer token you can use on every request. Every call to the Blerify API works this way, whether you're using Issuance, Verification, or Trust Registry.

## Prerequisites

* Admin access to the [Blerify Portal](https://portal.blerify.com/)

## What is a service account

A service account isn't a person. It's an identity your backend uses to call the Blerify API. It has nothing to do with any individual login, and it never goes through a human sign-in flow. You can limit a service account to a single product (Issuance or Verification), or give it roles across several products at once. The roles you assign decide what the account can actually do. Check each product's Portal Roles and Permissions page for the full list of available roles.

Authentication works through the OAuth 2.0 `client_credentials` grant, using `private_key_jwt` as the client authentication method ([RFC 7523](https://www.rfc-editor.org/rfc/rfc7523)). There's no shared client secret. Instead, your service account's private key signs a short-lived JWT assertion, and you trade that assertion for a bearer token.

## Create a service account

1. Log in to the [Blerify Portal](https://portal.blerify.com/) with an admin account.

<figure><picture><source srcset="/files/XdBoXstyVnVcIMVeYWZt" media="(prefers-color-scheme: dark)"><img src="https://239097222-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FMvCxPFtGg0Vjq2LzXN1U%2Fuploads%2Fgit-blob-43d77bc157c0956e03a78b475534926af55faf18%2Fimage.png?alt=media" alt=""></picture><figcaption></figcaption></figure>

2. Go to **Settings → Service Accounts**.

<figure><picture><source srcset="/files/cav5PAimDgjuQ9Koug8D" media="(prefers-color-scheme: dark)"><img src="https://239097222-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FMvCxPFtGg0Vjq2LzXN1U%2Fuploads%2Fgit-blob-d5f0cc4be629a4ef9d3b6cfdf7b4e861ae9201dd%2Fimage.png?alt=media" alt=""></picture><figcaption></figcaption></figure>

3. Click **Create Service Account**.

<figure><picture><source srcset="/files/vNSnNapcAVRkY5eBre2f" media="(prefers-color-scheme: dark)"><img src="https://239097222-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FMvCxPFtGg0Vjq2LzXN1U%2Fuploads%2Fgit-blob-09d189a61ede7efb43cad20a992428d2b8793c21%2Fimage.png?alt=media" alt=""></picture><figcaption></figcaption></figure>

4. Fill in the account details:
   * **Name** — a descriptive identifier (e.g. `payments-integration`, `kyc-backend`)
   * **Description** — optional; what this account is used for
   * **Roles** — the permissions this account needs, scoped per product (e.g. `verifications.api`, `credentials.api`, `notifications.api`)
5. Click **Create**. The Portal generates a credentials JSON file and downloads it right away. **You can't get this file again**, so save it somewhere safe before you close the dialog.

<figure><picture><source srcset="/files/kowjnVfIS63x9a3lVXZN" media="(prefers-color-scheme: dark)"><img src="https://239097222-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FMvCxPFtGg0Vjq2LzXN1U%2Fuploads%2Fgit-blob-222792aad6f4294e70144a535f1d3f0d2bf47e4b%2Fimage.png?alt=media" alt=""></picture><figcaption></figcaption></figure>

## The credentials file

The downloaded JSON has everything you need to authenticate:

```json
{
  "type": "service_account",
  "organization_id": "your-organization-id",
  "client_id": "your-client-id",
  "private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----",
  "token_uri": "https://...",
  "iam_audience": "https://..."
}
```

| Field             | Used for                                                                                                                  |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `client_id`       | Identifies the service account. Sent as the `client_id` form field, and also used as `iss` and `sub` in the JWT assertion |
| `organization_id` | Sent as the `organization_id` form field                                                                                  |
| `private_key`     | Signs the JWT assertion (RS256)                                                                                           |
| `token_uri`       | The token endpoint you send the assertion to. Always read this from the file, since it can vary by account                |
| `iam_audience`    | The `aud` claim in the JWT assertion                                                                                      |

Keep the `private_key` secret. Treat this file like a password: don't commit it to source control, don't put it in client-side code, and don't log it anywhere.

## Get an access token

Build a short-lived JWT assertion signed with your private key, then send it to `token_uri`. Everything you need is already in the credentials file — copy the values as they are. The only things you generate yourself are the two timestamps and the `jti`.

**JWT assertion structure:**

```json
// header
{ "alg": "RS256", "typ": "JWT" }

// payload
{
  "iss": "<client_id from the credentials file>",
  "sub": "<client_id from the credentials file>",
  "aud": "<iam_audience from the credentials file>",
  "iat": 1700000000,
  "exp": 1700003600,
  "jti": "a-unique-uuid-v4"
}
```

The `jti` needs to be unique on every request, since it stops the token from being replayed. Keep the assertion short-lived: one hour is a good default.

**Token request:**

```bash
curl -X POST '<token_uri from the credentials file>' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'client_id=<client_id from the credentials file>' \
  -d 'organization_id=<organization_id from the credentials file>' \
  -d 'client_assertion=<the signed JWT you just built>'
```

**Response:**

```json
{
  "access_token": "eyJ...",
  "token_type": "Bearer",
  "expires_in": 600
}
```

Read `expires_in` from the response instead of hardcoding a number, since it can change.

### Use a library instead of building from scratch

The official [`blerify/auth-php-client`](https://github.com/BlerifyPlatform/auth-php-client) package handles assertion signing, token caching, and renewal from a credentials JSON file. If you work in PHP, start there.

## Use the token

Put the token in the `Authorization` header of every API request:

```
Authorization: Bearer eyJ...
```

Cache the token and refresh it before `expires_in` runs out. If you request a new token on every call, you'll add latency and hit rate limits under load.

## Next steps

[**Get Started with Issuance**](/introduction-to-issuance/build/get-started): issue your first credential using a service account with the `credentials.api` role.

See also: [Get Started with Verification](/introduction-to-verification/build/get-started) · Full endpoint reference at <https://dev.blerify.com>


# Portal Roles and Permissions

The Blerify Portal uses one role-based access control model across the whole platform — the same roles govern what a team member can do whether they work with issuance, verification, or both. This page describes the four roles, the two project-level permissions, and the rules that govern who can grant access to others.

***

## Roles

There are four roles. Think of them as access tiers — each one is strictly more powerful than the one below it.

### `org.owner`

Full access to everything in the organization: edit organization details, manage keys and certificates, create and delete projects, invite and remove users, assign any role. Only `org.owner` can delete users from the organization or edit organization-level settings.

### `org.projects`

Nearly identical to `org.owner`, with three exceptions:

* Cannot edit organization details
* Cannot delete users from the organization
* Can only assign the `project.manager` role to others — not `org.owner` or `org.projects`

### `project.manager`

Scoped to the specific projects they've been assigned to. A project manager with no project assignments can't see or do anything. What they can do within an assigned project depends on which of the two project-level permissions they hold — see [Project Manager Permissions](#project-manager-permissions).

### `tl.admin`

Manages trust lists: the directories of public keys registered on the trust registry. Within the areas covered on this page, `tl.admin` has limited read access — they can view the organization and see which projects a user belongs to, but cannot take action on projects, rules, or users.

***

## Project Manager Permissions

The `project.manager` role does nothing on its own. It requires one of two permissions, assigned per project. A project manager can hold different permissions in different projects — or no permissions at all in a project they haven't been assigned to.

### `admin_project_child` — Project Admin

Full control over the assigned project:

* Edit project settings
* View and manage project users
* Create, edit, and delete verification rules
* Accept and reject verifiers
* Upload verifiers in bulk
* Manage benefits
* View verification history

### `manage_project_pov_verifiers` — Verification Rule Manager

Day-to-day management of verifiers within a project's verification rules:

* View verification rules and their details
* Accept and reject verifiers
* Upload verifiers in bulk

This permission does not allow creating, editing, or deleting verification rules, and does not provide access to benefits, project settings, or project users.

**Example:** Ana is a `project.manager`.

* In **Project Alpha** she has `admin_project_child` → she can do everything in Alpha, including creating and editing verification rules.
* In **Project Beta** she has `manage_project_pov_verifiers` → she can accept and reject verifiers in Beta, nothing else.
* In **Project Gamma** she has no assignment → she can't see Gamma at all.

If a project manager holds both permissions on the same project, `admin_project_child` takes full effect and `manage_project_pov_verifiers` is redundant.

***

## Access Matrix

### Organization

| Action                 | org.owner | org.projects | project.manager |
| ---------------------- | :-------: | :----------: | :-------------: |
| View organization      |     ✅     |       ✅      |        ✅        |
| Edit organization      |     ✅     |       ❌      |        ❌        |
| View organization keys |     ✅     |       ✅      |        ❌        |
| Renew organization key |     ✅     |       ✅      |        ❌        |
| View certificates      |     ✅     |       ✅      |        ❌        |

### Projects

| Action                    | org.owner | org.projects | project.manager (admin) | project.manager (verif. manager) |
| ------------------------- | :-------: | :----------: | :---------------------: | :------------------------------: |
| List projects             |   ✅ all   |     ✅ all    |     ✅ assigned only     |          ✅ assigned only         |
| View project detail       |     ✅     |       ✅      |            ✅            |                 ✅                |
| Create project            |     ✅     |       ✅      |            ❌            |                 ❌                |
| Edit project              |     ✅     |       ✅      |            ✅            |                 ❌                |
| Delete project            |     ✅     |       ✅      |            ❌            |                 ❌                |
| View project users        |     ✅     |       ✅      |            ✅            |                 ❌                |
| View approver credentials |     ✅     |       ✅      |            ✅            |                 ❌                |

Adding or removing users from a project is done through the organization user management area, not from the project view directly. A project manager with `admin_project_child` can assign users to their projects from that section.

### Benefits

| Action              | org.owner | org.projects | project.manager (admin) | project.manager (verif. manager) |
| ------------------- | :-------: | :----------: | :---------------------: | :------------------------------: |
| List benefits       |     ✅     |       ✅      |            ✅            |                 ❌                |
| View benefit detail |     ✅     |       ✅      |            ✅            |                 ❌                |
| Create benefit      |     ✅     |       ✅      |            ✅            |                 ❌                |
| Edit benefit        |     ✅     |       ✅      |            ✅            |                 ❌                |
| Delete benefit      |     ✅     |       ✅      |            ✅            |                 ❌                |

### Verification Rules

| Action                        | org.owner | org.projects | project.manager (admin) | project.manager (verif. manager) |
| ----------------------------- | :-------: | :----------: | :---------------------: | :------------------------------: |
| List verification rules       |     ✅     |       ✅      |            ✅            |                 ✅                |
| View verification rule detail |     ✅     |       ✅      |            ✅            |                 ✅                |
| View verifiers on a rule      |     ✅     |       ✅      |            ✅            |                 ✅                |
| Accept verifier               |     ✅     |       ✅      |            ✅            |                 ✅                |
| Reject verifier               |     ✅     |       ✅      |            ✅            |                 ✅                |
| Upload verifiers in bulk      |     ✅     |       ✅      |            ✅            |                 ✅                |
| Create verification rule      |     ✅     |       ✅      |            ✅            |                 ❌                |
| Edit verification rule        |     ✅     |       ✅      |            ✅            |                 ❌                |
| Delete verification rule      |     ✅     |       ✅      |            ✅            |                 ❌                |
| View verification history     |     ✅     |       ✅      |            ✅            |                 ❌                |

### Organization Users

| Action                        | org.owner | org.projects | project.manager (admin) | project.manager (verif. manager) |
| ----------------------------- | :-------: | :----------: | :---------------------: | :------------------------------: |
| List organization users       |     ✅     |       ✅      |            ✅            |                 ✅                |
| View user detail              |     ✅     |       ✅      |            ✅            |                 ✅                |
| View user's projects          |     ✅     |       ✅      |            ✅            |                 ✅                |
| Edit user                     |     ✅     |       ✅      |            ❌            |                 ❌                |
| Remove user from organization |     ✅     |       ❌      |            ❌            |                 ❌                |
| Add user to organization      |     ✅     |      ✅ ¹     |           ✅ ¹           |                 ❌                |
| Assign role to user           |     ✅     |      ✅ ¹     |           ✅ ¹           |                 ❌                |
| Remove role from user         |     ✅     |      ✅ ¹     |           ✅ ¹           |                 ❌                |
| Revoke permission from user   |     ✅     |       ✅      |            ✅            |                 ❌                |

¹ Subject to privilege escalation rules — see [Privilege Escalation Rules](#privilege-escalation-rules).

***

## Privilege Escalation Rules

No role can grant more access than it holds. The system enforces this at the API level: attempts to assign a higher role or permission are rejected regardless of how the request is made.

| Role                               | Can assign                                                                | Can grant permissions                                                              |
| ---------------------------------- | ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `org.owner`                        | Any role                                                                  | Any permission                                                                     |
| `org.projects`                     | `project.manager` only                                                    | Any project permission                                                             |
| `project.manager` (admin)          | `project.manager` only, in projects where they hold `admin_project_child` | `manage_project_pov_verifiers` only — cannot grant `admin_project_child` to others |
| `project.manager` (verif. manager) | No one                                                                    | Nothing                                                                            |

**Example:** Carlos is `org.projects`. He tries to promote another user to `org.owner`. The system rejects this — `org.projects` cannot assign a role above its own level.

**Example:** Sofia is a `project.manager` with `admin_project_child` in Project Alpha. She can add another user as `project.manager` with `manage_project_pov_verifiers` in Project Alpha. She cannot grant them `admin_project_child`, and she cannot make assignments in any project where she herself has no active permission.

***

## Frequently Asked Questions

**Can a project manager see projects they haven't been assigned to?**

No. The system automatically filters the project list to show only projects where they have an active permission. There is no way to browse projects outside their assignments.

**Who can delete a project?**

Only `org.owner` and `org.projects`. No project manager can delete a project regardless of their permissions.

**Who can delete a user from the organization?**

Only `org.owner`. This action is not available to any other role.

**Can a project manager create new projects?**

No. Creating projects is restricted to `org.owner` and `org.projects`.

**What happens if a project manager has both permissions on the same project?**

`admin_project_child` takes full effect. `manage_project_pov_verifiers` is redundant in that project since admin already includes everything the verifier manager permission allows.

**Can a project manager with `admin_project_child` grant the same permission to someone else?**

No. A project manager can only grant `manage_project_pov_verifiers` to others — never `admin_project_child`, even if they hold it themselves.

**Can a project manager change the assurance tier on a verification rule?**

Yes, if they hold `admin_project_child` on that project. `manage_project_pov_verifiers` does not allow editing verification rules — only managing the verifiers attached to them. Rule configuration itself is covered in [Create a Verification Rule](/introduction-to-verification/build/create-a-verification-rule).

***

## Next steps

**Continue to** [**Authentication**](/introduction/authentication) — the machine-to-machine side of access: service accounts and how they authenticate to the API.

See also: [Issuance — Get Started](/introduction-to-issuance/build/get-started) · [Verification — Get Started](/introduction-to-verification/build/get-started)


# Introduction to Issuance

Blerify Issuance turns data your organization already holds — an employee record, a passed exam, a government ID — into a credential a stranger can trust. You sign it once with your organization's key and deliver it to the holder's wallet. From that moment, anyone can verify it without ever calling back to Blerify or to you.

Everything in this section falls into one of three parts. If you're here to ship, start with **Build**. If you want to understand what's happening underneath, read **Learn**. When you need exact contracts, roles, or scopes, go to **Reference**.

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>🛠️ Build</strong></td><td>Task-oriented guides. Issue your first credential, then manage it through its life.</td><td><a href="/introduction-to-issuance/build">Build</a></td></tr><tr><td><strong>📘 Learn</strong></td><td>The concepts underneath — the issuance flow, credential formats, and how trust is established.</td><td><a href="/introduction-to-issuance/learn">Learn</a></td></tr><tr><td><strong>📗 Reference</strong></td><td>Exact API contracts, portal roles, scopes, and release history.</td><td><a href="/introduction-to-issuance/reference">Reference</a></td></tr></tbody></table>

{% hint style="info" %}
**First time here?** Go straight to [Get Started](/introduction-to-issuance/build/get-started). It covers what you need in place and walks you from an empty project to a signed credential in a wallet.
{% endhint %}

## Next steps

[**Get Started**](/introduction-to-issuance/build/get-started) — the fastest path from nothing to your first issued credential.

See also: [How Issuance Works](/introduction-to-issuance/learn/how-issuance-works) — the model, end to end · [API Access & Scopes](/introduction-to-issuance/reference/api-access-and-scopes) — how the API authenticates and authorizes.


# Build

These are the hands-on guides. Each one is a task that starts with a verb and ends with something real — a credential created, delivered, held, or revoked. If you just want to get a credential into a wallet, this is where you live.

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Get Started</strong></td><td>What you need in place, and your first credential end to end.</td><td><a href="/introduction-to-issuance/build/get-started">Get Started</a></td></tr><tr><td><strong>Issue a W3C Credential</strong></td><td>Create, sign, and deliver — from the Portal or the API.</td><td><a href="/introduction-to-issuance/build/issue-a-w3c-credential">Issue a W3C Credential</a></td></tr><tr><td><strong>Issue an mDoc</strong></td><td>ISO 18013 mDoc issuance. Coming soon.</td><td><a href="/introduction-to-issuance/build/issue-an-mdoc">Issue an mDoc</a></td></tr><tr><td><strong>Manage a Credential</strong></td><td>Put a credential on hold, revoke it, or resend delivery.</td><td><a href="/introduction-to-issuance/build/manage-a-credential">Manage a Credential</a></td></tr><tr><td><strong>Handle Errors</strong></td><td>The failure cases you'll hit, and how to recover from each.</td><td><a href="/introduction-to-issuance/build/handle-errors">Handle Errors</a></td></tr></tbody></table>

## Next steps

[**Get Started**](/introduction-to-issuance/build/get-started) — begin here if you haven't issued a credential yet.

See also: [How Issuance Works](/introduction-to-issuance/learn/how-issuance-works) — read this first if you want the model before the mechanics.


# Get Started

This page takes you from an empty Portal to your first signed credential sitting in a holder's wallet. It assumes you haven't issued with Blerify before, so it starts with what you need in place and ends with a pointer to the full walkthrough.

***

## What you need first

* **An Issuance Portal account** for your organization. If your organization doesn't have one yet, contact your Blerify representative to provision it.
* **A project**, created in the Portal. A project groups credentials by type and by the rules that govern them — schema, validity period, revocation settings. Every credential you issue belongs to exactly one project.
* **A service account**, if you plan to use the API. This is how your backend authenticates. See [Authentication](/introduction/authentication) for how to create one and download its credentials.

If the words *project* and *service account* are new, [Core Concepts](/introduction-to-issuance/learn/core-concepts) explains how they nest together — but you don't need that detail to get going.

***

## What you're about to do

Issuing a credential is always the same three moves, whether you click through the Portal or call the API: you **create** a draft, you **sign** it, and you **deliver** it to the holder. The draft carries no weight until it's signed; signing is the point where it becomes tamper-evident and trustworthy. [How Issuance Works](/introduction-to-issuance/learn/how-issuance-works) tells that story in full — this guide just gets you through it once.

***

## Authenticating the API

Every API call authenticates as your service account. See [Authentication](/introduction/authentication) for how to create one and exchange its credentials for a bearer token.

The account also needs the right role for what you're doing:

* **`credentials.api`** — create, read, update, approve, hold, revoke, and resend credentials in a project.
* **`notifications.api`** — send push notifications to wallet holders.

Both roles are scoped to the project the service account belongs to. An account has no reach into other projects, even within the same organization.

***

## Pick your path

You issue either by hand in the **Portal** — best for setup, testing, and small batches run by your operations team — or through the **API**, for high volume, scheduled batches, and issuance triggered by your own system events. Both run the same pipeline underneath; the Portal is a UI over the same calls your backend would make.

***

## Next steps

[**Issue a W3C Credential**](/introduction-to-issuance/build/issue-a-w3c-credential) — the full walkthrough for both the Portal and the API, with a working example.

See also: [Core Concepts](/introduction-to-issuance/learn/core-concepts) — the pieces you just set up, explained · [Portal Roles & Permissions](/introduction/portal-roles-and-permissions) — decide who on your team can do what.


# Handle Errors

Most issuance failures fall into a handful of familiar shapes. None of them leave a credential in a broken half-state — Blerify's operations are atomic, so a failed call means the credential stayed exactly where it was. This page walks through the errors you're most likely to hit and how to recover from each.

***

## The data was wrong after signing

You can't edit a signed credential — the signature is what makes it trustworthy, and any change would break it. The fix is always the same: revoke the original and issue a new one with the corrected data. See [Manage a Credential](/introduction-to-issuance/build/manage-a-credential).

## The holder never received the credential

The signed credential still exists; only delivery failed. Don't reissue it. Regenerate the delivery QR or deeplink from the credential's detail in the Portal, or use the `resend` endpoint to trigger a new delivery notification.

## The access token expired mid-operation

Tokens are short-lived by design. Exchange a new one and retry the call. Because operations are atomic, the credential stays in its current status — a token expiring partway through never leaves it inconsistent. To avoid this, cache the token and refresh it before expiry rather than requesting a fresh one on every call.

## Schema validation failed

The claims you sent don't match the fields your project's schema requires. The error response tells you which fields are missing or invalid — correct them and resubmit. If you're unsure what the schema expects, check the project configuration in the Portal.

## A 403 on a token you know is valid

This is the most common surprise, and it's almost never the token. Holding the `credentials.api` role grants access to the issuance API in general, but your service account also has to be a **member of the group tied to the specific project** you're operating on. Both conditions must be true.

{% hint style="info" %}
If your token is valid but the call returns `403`, group membership is almost always the missing piece. Ask your Blerify account manager to confirm the service account belongs to the project group. The [API Access & Scopes](/introduction-to-issuance/reference/api-access-and-scopes) page explains the two-part authorization model in full.
{% endhint %}

***

## Next steps

[**API Access & Scopes**](/introduction-to-issuance/reference/api-access-and-scopes) — the authorization model behind the `403`.

See also: [Manage a Credential](/introduction-to-issuance/build/manage-a-credential) — the revoke-and-reissue path for bad data · [Authentication](/introduction/authentication) — how tokens are obtained and refreshed.


# Issue a W3C Credential

This guide covers issuing a W3C Verifiable Credential from start to finish. The credential moves through the same pipeline regardless of how you drive it — what changes is whether you click through the Portal or call the API. Pick the tab that fits how you work.

{% hint style="info" %}
**Before you start** you need a project with a defined schema — and, for the API path, a service account holding the `credentials.api` role. If that's not set up yet, walk through [Get Started](/introduction-to-issuance/build/get-started) first. For who on your team is allowed to create and approve credentials, see [Portal Roles & Permissions](/introduction/portal-roles-and-permissions).
{% endhint %}

***

## Issue the credential

{% tabs %}
{% tab title="Portal" %}
No code. Best for setup, testing, and one-off or small-batch issuance run by your operations team.

{% stepper %}
{% step %}

### Create the credential

Open the project you'll issue from — each one carries its own schema and its own list of authorized issuers, so what you can create is already scoped for you. Click **Issue Credential**, fill in the claims form (the fields come straight from your schema), and click **Create**. Blerify saves it as a draft with status `PENDING`. Nothing is signed yet, so you can still review it or discard it.
{% endstep %}

{% step %}

### Approve

Review the draft, and when the data checks out, click **Approve** — this is the moment of issuance. Blerify signs and assembles the credential and moves it to `ISSUED`. From here it's tamper-evident, so any edit would break the signature. If the data is wrong, reject it instead and start over; a rejected credential can't be re-approved.
{% endstep %}

{% step %}

### Deliver to the holder

The Portal offers three ways to hand the credential over:

* **QR code** — show or print it; the holder scans it with their wallet.
* **Deeplink** — send it by email, SMS, or your own channel; tapping it opens the wallet.
* **Push notification** — if the holder already has the Blerify Wallet installed and linked, push the credential straight to them.

The wallet validates the signature on arrival and stores the credential on the holder's device.
{% endstep %}
{% endstepper %}
{% endtab %}

{% tab title="API" %}
Full programmatic control — best for high volume, scheduled batches, or issuance triggered by your own system events. Every request carries a bearer token from your service account (see [Authentication](/introduction/authentication)), and the account needs the `credentials.api` role on the target project.

Over the API, issuance is **three chained calls**: create returns a credential `_id` and a `signingMessage`; sign turns that message into a `signature` and a `publicKey`; assemble feeds those back in to produce the finished credential.

{% stepper %}
{% step %}

### Create the credential

The request body ties together the `projectId` and `templateId` that define the credential type, the subject data under `additionalData.w3cData`, and the `organizationUser` who will receive it. Set `options.approvers` to `true` if the credential must be approved by a designated approver before it can be signed.

```bash
curl -X POST \
  "https://api.blerify.com/api/v1/organizations/$ORG/projects/$PROJECT/credentials" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "projectId": "9697f34c-6ba5-4f79-b6dc-0434379d9646",
        "templateId": "6cba8cf5-2d00-4baa-b18a-8797c8c8b550",
        "additionalData": {
          "w3cData": { "email": "ada@example.com", "name": "Ada", "lastname": "Lovelace" }
        },
        "organizationUser": { "email": "ada@example.com" },
        "options": { "approvers": false }
      }'
```

The `201 Created` response includes the draft, trimmed here to the fields that drive the flow:

```json
{
  "_id": "0x412f19b36d6c9a369798415126aec67e...",
  "status": "PENDING",
  "receiver": { "email": "ada@example.com", "repositoryType": "WALLET" },
  "signingMessage": "QS8Zs21smjaXmEFRJq7Gfsx2SmzGkg/xBEAXZpg2Rq8="
}
```

Two values carry the flow forward: `_id` is the credential identifier every later call uses, and `signingMessage` is what the sign step operates on.

Full request and response schema → [API Reference](https://dev.blerify.com/#beac6de4-d5a1-4bcf-bfbb-9cbae49e4bd1)
{% endstep %}

{% step %}

### Sign the credential

Send the `signingMessage` from the previous step in the request body, with the credential `_id` in the path. Blerify performs the cryptographic signing and returns the `signature`, the signing `algorithm`, and the `publicKey` that produced it. Carry the `signature` and `publicKey` into the assemble step.

```bash
curl -X PUT \
  "https://api.blerify.com/api/v1/organizations/$ORG/projects/$PROJECT/credentials/$CREDENTIAL_ID/sign" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "signingMessage": "QS8Zs21smjaXmEFRJq7Gfsx2SmzGkg/xBEAXZpg2Rq8=" }'
```

```json
{
  "signature": "0x1c5d69dcfb39371cb06db1388c815fb...",
  "algorithm": "ecdsa-jcs-2019",
  "publicKey": "0xd7650261601924be00f2abd2958..."
}
```

Full request and response schema → [API Reference](https://dev.blerify.com/#2b542712-0052-44d3-a3bf-1580e1d74e17)
{% endstep %}

{% step %}

### Assemble the credential

This step turns a signature into a credential. Sign proved *who* is vouching for the data; assemble packages that proof into the final W3C Verifiable Credential — the object a wallet can hold and a verifier can check. Send the `templateId` for your project together with the `signature` and `publicKey` from the previous step. Note the `keystore=keyvault` query parameter.

```bash
curl -X PUT \
  "https://api.blerify.com/api/v1/organizations/$ORG/projects/$PROJECT/credentials/$CREDENTIAL_ID/assemble?keystore=keyvault" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "templateId": "6cba8cf5-2d00-4baa-b18a-8797c8c8b550",
        "signature": "0x1c5d69dcfb39371cb06db1388c815fb...",
        "publicKey": "0xd7650261601924be00f2abd2958..."
      }'
```

The response is the complete, signed credential (trimmed here for readability):

```json
{
  "@context": [
    "https://www.w3.org/2018/credentials/v1",
    "https://vc.blerify.com/education/EducationCredential"
  ],
  "type": ["VerifiableCredential", "EducationCredential"],
  "id": "2b94c494-e402-4afd-9bd5-5ffc628efe41",
  "issuer": "did:lac1:1APgL8Z9V4bRQ...",
  "issuanceDate": "2026-02-23T16:14:54.944089Z",
  "credentialSubject": {
    "id": "did:lac1:1iT4pJXCP5YJhP...",
    "subjectName": "Ada Lovelace",
    "title": "Introduction to Cryptography",
    "date": "2026-02-23"
  },
  "proof": [
    {
      "type": "DataIntegrityProof",
      "cryptosuite": "ecdsa-jcs-2019",
      "verificationMethod": "did:lac1:1APgL8Z9V4bRQ...#2orwtrQ29...",
      "proofPurpose": "assertionMethod",
      "proofValue": "zZtk1zBQuzY5qRQWC1sAUd..."
    }
  ]
}
```

Full request and response schema → [API Reference](https://dev.blerify.com/#644f3927-2fa5-452c-8def-133c5e46281b)
{% endstep %}

{% step %}

### Deliver to the holder

Assemble hands you the finished Verifiable Credential — now get it to the holder. Deliver it as a QR code, a deeplink, or a push notification, exactly as the Portal does. The wallet validates the signature on arrival and stores the credential on the holder's device. If a delivery is ever lost, [Manage a Credential](/introduction-to-issuance/build/manage-a-credential) covers resending it.
{% endstep %}
{% endstepper %}
{% endtab %}

{% tab title="API (service account, automated)" %}
If your backend issues credentials on behalf of an organization without a human approval step — for example, an LMS issuing credentials automatically when a student completes a course — the flow is a simplified variant: create the credential, approve it directly as the service account, and poll until it is ready.

This requires a service account with the `credentials.api` role. Authentication follows the same `private_key_jwt` pattern described in [Authentication](/introduction/authentication).

{% stepper %}
{% step %}

### Create the credential

Send the template, subject data, and receiver identity. The service account creates the draft on the organization's behalf.

```bash
curl -X POST \
  "https://api.blerify.com/api/v1/organizations/$ORG/projects/$PROJECT/credentials" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "templateId": "your-template-id",
        "additionalData": {
          "w3cData": {
            "fullname": "Your Recipient",
            "metadata": { "level": 1 }
          }
        },
        "organizationUser": {
          "email": "recipient@example.com"
        },
        "options": { "omitApproval": false, "approvers": true }
      }'
```

The `201` response includes `_id` — keep it for the steps below.

Full request and response schema → [API Reference](https://dev.blerify.com/#01cf28b2-23f9-472d-a822-ab8cc0ce0114)
{% endstep %}

{% step %}

### Approve (sign) the credential

Approving as a service account records the approval and triggers issuance asynchronously. Include `keystore=keyvault` and a `lang` query parameter for the receiver's language.

```bash
curl -X PUT \
  "https://api.blerify.com/api/v1/organizations/$ORG/projects/$PROJECT/credentials/$CREDENTIAL_ID/sign?keystore=keyvault&lang=en" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "templateId": "your-template-id" }'
```

A `200` response means the approval was recorded and signing was triggered — **not** that the credential is ready. Confirm the result by polling.
{% endstep %}

{% step %}

### Poll until issued

Poll the credential until `status` is `SENT`. The `templateId` query parameter is required — access is scoped to organization, project, and template.

```bash
curl "https://api.blerify.com/api/v1/organizations/$ORG/projects/$PROJECT/credentials/$CREDENTIAL_ID/polling?templateId=$TEMPLATE_ID" \
  -H "Authorization: Bearer $TOKEN"
```

While issuing:

```json
{ "_id": "0x8f3a...c1", "status": "PENDING", "pdf": null, "thumbnail": null, "code": null }
```

When ready:

```json
{
  "_id": "0x8f3a...c1",
  "status": "SENT",
  "pdf": "https://storage.../credential.pdf?signature=...",
  "thumbnail": "https://storage.../thumbnail.png?signature=...",
  "code": "abC12XyZ..."
}
```

| Field              | Use                                                                                                     |
| ------------------ | ------------------------------------------------------------------------------------------------------- |
| `status`           | Poll until `SENT`. The credential moves to `DELIVERED` once the holder claims it.                       |
| `pdf`, `thumbnail` | Short-lived signed URLs. Fetch or render them promptly after polling, or re-poll for fresh ones.        |
| `code`             | The claim code. Use it to build the QR code the holder scans to claim the credential into their wallet. |

Poll on a sensible interval and set a timeout — if the credential hasn't reached `SENT` after a reasonable window, treat it as a failure and check for errors.
{% endstep %}
{% endstepper %}
{% endtab %}
{% endtabs %}

{% hint style="warning" %}
**A signed credential can't be edited.** If the data turns out wrong after it's `ISSUED`, revoke the original and issue a fresh one — revocation is immediate and permanent, with no undo. See [Manage a Credential](/introduction-to-issuance/build/manage-a-credential).
{% endhint %}

***

## What happens next

Once a credential is signed and delivered, it has a life of its own — it can be accepted, held, revoked, or left to expire. Those states, and how to move a credential between them, are covered separately:

* [**Credential Lifecycle**](/introduction-to-issuance/learn/credential-lifecycle) — every state a credential can hold, and why it only moves forward.
* [**Manage a Credential**](/introduction-to-issuance/build/manage-a-credential) — put a credential on hold, revoke it, or resend delivery.
* [**Handle Errors**](/introduction-to-issuance/build/handle-errors) — the failure cases you'll hit, and how to recover from each.

***

## Next steps

[**Manage a Credential**](/introduction-to-issuance/build/manage-a-credential) — the natural sequel: what to do after a credential is out in the world.

See also: [Trust & Signing](/introduction-to-issuance/learn/trust-and-signing) — why the credential you just signed is trusted without a callback · [API Reference](https://dev.blerify.com/#01cf28b2-23f9-472d-a822-ab8cc0ce0114) — the full endpoint contracts.


# Issue an mDoc

{% hint style="info" %}
**Coming soon.** This guide will walk through issuing an ISO 18013 mDoc — including the X.509 / IACA certificate hierarchy that mDocs are anchored to. It isn't published yet.
{% endhint %}

An mDoc (ISO 18013) is Blerify's format for government-grade identity documents — national IDs, driving licenses, and passports. It differs from a W3C Verifiable Credential in how it's encoded and where its trust is rooted, but it moves through the same create → sign → deliver flow.

Until this guide is ready:

* [**Credential Formats**](/introduction-to-issuance/learn/credential-formats) explains how mDocs differ from W3C credentials and when to use each.
* [**Issue a W3C Credential**](/introduction-to-issuance/build/issue-a-w3c-credential) shows the issuance flow that mDoc issuance will follow.

***

## Next steps

[**Credential Formats**](/introduction-to-issuance/learn/credential-formats) — understand the mDoc format while this guide is in progress.


# Manage a Credential

Issuing a credential isn't the end of the story. Once it's out in the world, you'll sometimes need to pause it, retire it, or get it back into a holder's hands. There are three operations for that — hold, revoke, and resend — and this guide covers each, along with the one that has no undo.

{% hint style="info" %}
All three are API calls that require a bearer token with the `credentials.api` role — see [Authentication](/introduction/authentication). The Portal exposes the same actions on a credential's detail view.
{% endhint %}

***

## Put a credential on hold

A hold is a temporary suspension. Use it when you need a credential to stop passing verification for a while — an investigation, a lapsed payment, a paused membership — without permanently ending it.

```bash
curl -X PUT \
  https://api.blerify.com/api/v1/organizations/$ORG/projects/$PROJECT/credentials/$CREDENTIAL_ID/hold \
  -H "Authorization: Bearer $TOKEN"
```

While a credential is on hold, any verification against it fails — exactly as if it were invalid — until you lift the hold. Nothing about the credential is destroyed; it simply stops passing until you decide otherwise.

***

## Revoke a credential

Revocation is the permanent version of a hold. Use it when a credential should never pass again — the holder left, the document was superseded, the data was wrong.

```bash
curl -X PUT \
  https://api.blerify.com/api/v1/organizations/$ORG/projects/$PROJECT/credentials/$CREDENTIAL_ID/revoke \
  -H "Authorization: Bearer $TOKEN"
```

Blerify updates the revocation registry immediately. The next time the credential is presented to a verifier, validation reflects the revoked status. The credential stays in the holder's wallet — you can't reach in and delete it remotely — but it will never pass verification again.

{% hint style="warning" %}
**Revocation can't be undone.** If you revoke a credential by mistake, there's no way back — you issue a new one to the holder. This is a direct consequence of the [lifecycle](/introduction-to-issuance/learn/credential-lifecycle) only ever moving forward.
{% endhint %}

***

## Resend delivery

If a holder never received a credential — a lost email, a scanned QR that expired, a phone that was offline — you don't reissue it. The signed credential already exists; you just need to hand it over again. Regenerate the delivery QR or deeplink from the credential's detail in the Portal, or trigger a fresh delivery notification with the `resend` endpoint.

***

## Next steps

[**Handle Errors**](/introduction-to-issuance/build/handle-errors) — the failure cases around these operations, and how to recover.

See also: [Credential Lifecycle](/introduction-to-issuance/learn/credential-lifecycle) — how hold, revoke, and expiry fit the full set of states · [Issue a W3C Credential](/introduction-to-issuance/build/issue-a-w3c-credential) — where every managed credential begins.


# Learn

The Build guides show you how. These pages explain why. Read them when a credential behaves in a way you didn't expect, when you're deciding which format to issue, or when a verifier asks how they're supposed to trust what you signed.

You don't need to read them in order, but this is the order that tells the whole story.

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>How Issuance Works</strong></td><td>The create → sign → deliver model, end to end.</td><td><a href="/introduction-to-issuance/learn/how-issuance-works">How Issuance Works</a></td></tr><tr><td><strong>Credential Formats</strong></td><td>W3C Verifiable Credentials vs. ISO 18013 mDocs, and when to use each.</td><td><a href="/introduction-to-issuance/learn/credential-formats">Credential Formats</a></td></tr><tr><td><strong>Core Concepts</strong></td><td>Organization, project, service account, schema, delivery.</td><td><a href="/introduction-to-issuance/learn/core-concepts">Core Concepts</a></td></tr><tr><td><strong>Credential Lifecycle</strong></td><td>Every state a credential moves through, and why it only moves forward.</td><td><a href="/introduction-to-issuance/learn/credential-lifecycle">Credential Lifecycle</a></td></tr><tr><td><strong>Trust &#x26; Signing</strong></td><td>Why a verifier trusts your credential without calling you.</td><td><a href="/introduction-to-issuance/learn/trust-and-signing">Trust &amp; Signing</a></td></tr></tbody></table>

## Next steps

[**How Issuance Works**](/introduction-to-issuance/learn/how-issuance-works) — start here for the full picture in one page.

See also: [Get Started](/introduction-to-issuance/build/get-started) — when you're ready to stop reading and start issuing.


# Core Concepts

Five words come up again and again in the Build guides: organization, project, service account, schema, and delivery method. They nest inside each other in a way that's worth seeing once, because it explains why access is scoped the way it is and why a credential can only carry the claims it does. This page walks through them from the outside in.

***

## Organization

Your **organization** is the top-level entity in Blerify — everything else lives inside it. It owns one or more projects and one or more service accounts, and it holds the root key that anchors every credential you issue in the Trust Registry. When a verifier confirms one of your credentials is genuine, the chain of trust they follow ends at your organization.

## Project

A **project** is a scoped environment for a single credential program. Each one has its own schema, its own issuance configuration, and its own revocation registry. That isolation is deliberate: you can run one project for employee badges, another for customer KYC credentials, and another for mDoc issuance, and none of them can touch the others. A credential issued in one project knows nothing about credentials in the next.

## Service account

A **service account** is a non-human identity — how your backend authenticates, as opposed to how a person logs in. It carries an asymmetric key pair. The private key signs the JWT assertions you exchange for access tokens; the public key is registered in the Trust Registry so verifiers can validate the credentials your project issues.

A service account's reach is scoped twice over. It holds roles — like `credentials.api` — that grant access to an API surface, and it belongs to specific project groups that scope that access to specific projects. Holding the role is not enough on its own; the account also has to be a member of the project's group. Both conditions have to be true, which is why a perfectly valid token can still be refused. [Authentication](/introduction/authentication) covers how to create one and sign with it.

## Credential schema

A **schema** is the set of claims a credential type carries — the fields and what they're called. For W3C Verifiable Credentials, you define the schema when you create the project, and it governs every credential that project issues. For ISO 18013 mDocs, the schema isn't yours to define; it's fixed by the ISO standard. Either way, the schema is what a `PENDING` draft is validated against before it can be signed.

## Delivery method

A **delivery method** is how the signed credential reaches the holder: a QR code they scan, a deeplink they tap, or a push notification if they already have the Blerify Wallet installed and linked. Whichever you use, the ending is the same — the wallet opens, validates the credential's signature, and stores it on the holder's device.

***

## How they fit together

```mermaid
flowchart TD
    O[Organization<br/>root key in Trust Registry] --> P1[Project A<br/>schema · config · revocation]
    O --> P2[Project B]
    O --> SA[Service account<br/>key pair · roles]
    SA -.member of group.-> P1
    P1 --> C[Credential<br/>claims defined by schema]
    C --> D[Delivery<br/>QR · deeplink · push]
```

Read it from the top: the organization owns projects and service accounts; a service account has to be a member of a project's group to act in it; a credential's claims come from its project's schema; and delivery is the last step that hands the finished credential to the holder.

***

## Next steps

[**Credential Lifecycle**](/introduction-to-issuance/learn/credential-lifecycle) — now that you know what a credential is, follow it through every state it can hold.

See also: [Trust & Signing](/introduction-to-issuance/learn/trust-and-signing) — how the organization's key turns into a verifier's trust · [Get Started](/introduction-to-issuance/build/get-started) — set up your first project and service account.


# Credential Formats

Before you issue anything, one choice shapes everything after it: which format. Blerify supports two, and they exist for different worlds. One gives you freedom to define your own data; the other gives you a government-grade standard you can't bend. This page explains what separates them so you can pick without second-guessing.

***

## The short answer

If you're issuing anything that isn't a government identity document — a license, a diploma, a KYC result, a membership — you almost certainly want a **W3C Verifiable Credential**. If you're issuing a national ID, a driving license, or a passport, you want an **ISO 18013 mDoc**.

The rest of this page is why.

***

## W3C Verifiable Credentials

A W3C Verifiable Credential is a JSON document carrying a cryptographic proof, anchored either to a decentralized identifier (DID) or an X.509 certificate. Its defining trait is flexibility: **you define the schema.** When you create a project, you decide which claims the credential carries and what they're called.

That makes W3C the right fit whenever the shape of the data is yours to design — degrees, professional licenses, employment records, membership cards, or any attribute a trusted organization can vouch for. If your use case doesn't already have an international standard dictating its fields, this is the format that gets out of your way.

***

## ISO 18013 mDocs

An ISO 18013 mDoc is a different animal. It's encoded in CBOR — a compact binary format, not JSON — and its trust is rooted in an X.509 certificate hierarchy that climbs to an Issuing Authority Certification Authority (IACA). Its defining trait is the opposite of W3C's: **the schema is fixed by the ISO standard.** You don't design the fields; the standard does, using standardized names and namespaces so that any conformant verifier, anywhere, reads them the same way.

That rigidity is the point. Government identity documents need to be recognized by systems that have never heard of your organization, and a shared standard is what makes that possible. Use mDocs for national IDs, driving licenses, and passports.

***

## Side by side

|                  | W3C Verifiable Credential           | ISO 18013 mDoc                            |
| ---------------- | ----------------------------------- | ----------------------------------------- |
| **Encoding**     | JSON with a cryptographic proof     | CBOR (binary)                             |
| **Trust anchor** | DID or X.509 certificate            | X.509 hierarchy rooted in an IACA         |
| **Schema**       | You define it, per project          | Fixed by the ISO 18013 standard           |
| **Best for**     | Licenses, degrees, KYC, memberships | National IDs, driving licenses, passports |

{% hint style="info" %}
Whichever format you choose, it moves through the same create → sign → deliver flow — see [How Issuance Works](/introduction-to-issuance/learn/how-issuance-works). The format changes the encoding and the trust anchor, not the shape of the process.
{% endhint %}

***

## Next steps

[**Core Concepts**](/introduction-to-issuance/learn/core-concepts) — how a project's schema defines the claims your W3C credentials carry.

See also: [Issue a W3C Credential](/introduction-to-issuance/build/issue-a-w3c-credential) — put the W3C format to work · [Issue an mDoc](/introduction-to-issuance/build/issue-an-mdoc) — mDoc issuance (coming soon).


# Credential Lifecycle

A credential is not a static object. From the moment you create it, it moves through a series of states — and it only ever moves forward. Understanding that one rule explains most of how issuance behaves: why you can't edit a signed credential, why a mistake means reissuing rather than fixing, and why revocation is final.

***

## The states

| Status     | What it means                                                           |
| ---------- | ----------------------------------------------------------------------- |
| `PENDING`  | Draft created, not yet signed                                           |
| `ISSUED`   | Signed and available for delivery                                       |
| `ACCEPTED` | Holder received the credential into their wallet                        |
| `REVOKED`  | Issuer revoked the credential; verifications will return revoked status |
| `EXPIRED`  | The credential's validity period has passed                             |
| `REJECTED` | Rejected before signing; cannot be re-approved                          |

***

## The story of a credential

A credential begins life as `PENDING` — a draft with no cryptographic weight. This is the only point where its data is still soft. You can review it, and you can reject it: a `REJECTED` credential is a dead end, and it can't be brought back. Create a new one instead.

When you sign a `PENDING` draft, it becomes `ISSUED`. Signing is a one-way door. The credential is now tamper-evident, which is exactly why you can't edit it — any change would break the signature that makes it trustworthy. Once the holder's wallet takes delivery, the credential moves to `ACCEPTED`.

From there, two things can end it. If its validity period runs out, it becomes `EXPIRED` on its own. If you revoke it, it becomes `REVOKED` — immediately and permanently.

```mermaid
stateDiagram-v2
    [*] --> PENDING: create
    PENDING --> REJECTED: reject
    PENDING --> ISSUED: sign
    ISSUED --> ACCEPTED: holder receives
    ISSUED --> REVOKED: revoke
    ACCEPTED --> REVOKED: revoke
    ACCEPTED --> EXPIRED: validity ends
    REJECTED --> [*]
    REVOKED --> [*]
    EXPIRED --> [*]
```

***

## Why it only moves forward

Status never moves backward, and that constraint is a feature, not a limitation. It's what lets a verifier trust a credential's current state without reconstructing its history. There's no path where a revoked credential quietly becomes valid again, or a signed credential is edited in place.

The practical consequence is simple: **if a holder's data is wrong after the credential is `ISSUED`, you don't fix it — you revoke the original and issue a new one.** The same goes for a credential revoked by mistake; there's no undo, only reissue.

{% hint style="warning" %}
Revocation is immediate and permanent. The credential stays in the holder's wallet — you can't delete it remotely — but any verification against it will return the revoked state from then on.
{% endhint %}

***

## Next steps

[**Manage a Credential**](/introduction-to-issuance/build/manage-a-credential) — the hands-on side: how to hold, revoke, and resend.

See also: [Trust & Signing](/introduction-to-issuance/learn/trust-and-signing) — why a signed credential can't be edited · [Handle Errors](/introduction-to-issuance/build/handle-errors) — recovering when something in the lifecycle goes wrong.


# How Issuance Works

Issuing a credential is three moves: you create it, Blerify signs it, and you deliver it. This page tells the story of what happens at each move — and why the result is something a verifier will trust without ever contacting you. When you're ready to run it yourself, [Issue a W3C Credential](/introduction-to-issuance/build/issue-a-w3c-credential) takes over.

{% hint style="info" %}
This is the mental model. Once it clicks, the [Build](/introduction-to-issuance/build) guides turn it into working calls.
{% endhint %}

***

## The flow, end to end

Everything starts with your backend, which holds the data, and ends on the holder's device. In between, Blerify turns that data into something anyone can check — no callback to your servers required.

```mermaid
flowchart LR
    A[Your backend] -->|holds the data| B[Create]
    B -->|unsigned draft| C[Sign]
    C -->|organization key| D[Deliver]
    D -->|QR / link / push| E[Holder's wallet]
```

### 1 — Create

You describe the claims the holder will carry, and Blerify assembles them into an unsigned credential shaped by your project's schema. Nothing is committed yet. This draft is safe to review, correct, or throw away — it carries the status `PENDING` and has no cryptographic weight until you sign it.

### 2 — Sign

You ask Blerify to sign the draft. It signs with your organization's key and registers the credential against the Trust Registry. This is the turning point in the story: from here on the credential is tamper-evident. Change a single field and the signature breaks. And because the signature and the trust registration are self-contained, the credential's authenticity no longer depends on anyone being able to reach your servers.

### 3 — Deliver

The signed credential travels to the holder as a QR code, a deeplink, or a push notification. Their wallet checks the signature the moment it arrives and stores the credential encrypted on-device. Blerify keeps no copy — once it's in the wallet, it belongs to the holder.

***

## What the verifier sees later

The point of all this is what happens *after* delivery, when the holder presents the credential somewhere. The verifier doesn't trust your database — they trust the math. They check the signature against your organization's key in the Trust Registry, confirm the credential hasn't been revoked, and read only the fields the holder agreed to share.

That's why signing is the moment that matters: everything a verifier needs to trust the credential is baked in at signing time. [Trust & Signing](/introduction-to-issuance/learn/trust-and-signing) follows this thread all the way to the verifier.

***

## Two formats travel this same path

Both credential formats Blerify issues — W3C Verifiable Credentials and ISO 18013 mDocs — move through the exact create → sign → deliver flow above. What changes is how they're encoded and where their trust is anchored. If you're choosing between them, [Credential Formats](/introduction-to-issuance/learn/credential-formats) lays out the trade-offs.

***

## Next steps

[**Core Concepts**](/introduction-to-issuance/learn/core-concepts) — the pieces this flow rests on: organization, project, service account, and schema.

See also: [Credential Lifecycle](/introduction-to-issuance/learn/credential-lifecycle) — what happens to a credential after it's issued · [Get Started](/introduction-to-issuance/build/get-started) — run the flow for real.


# Trust & Signing

The whole point of a credential is that someone who has never met you can trust it. This page follows the thread from the moment you sign a credential to the moment a verifier accepts it — and shows why, in between, nobody has to call your servers.

***

## Signing is the moment of truth

When Blerify signs a credential, it uses your organization's key and registers the credential against Blerify's decentralized trust infrastructure. Two things happen at once, and together they make the credential self-sufficient:

* The **signature** binds the credential's contents to your organization's key. Change any field and the signature no longer matches.
* The **trust registration** records, on-chain, that your organization is a legitimate issuer.

After signing, the credential carries everything a verifier needs to trust it. That's why the [lifecycle](/introduction-to-issuance/learn/credential-lifecycle) treats signing as a one-way door: the trust is baked in at that instant, and editing the credential afterward would break it.

***

## Why verifiers don't call you back

A verifier presented with one of your credentials doesn't need to reach your database, your API, or even Blerify's servers in real time to know it's genuine. They validate the signature against your organization's key as recorded in the Trust Registry, and they check the on-chain registration to confirm you're an authorized issuer. Both checks rely on public, verifiable data — not on a live conversation with you.

This is what makes the model scale and survive. Your credentials keep working even if your systems are down, and a verifier outside the Blerify ecosystem can still establish trust, because the root of trust is anchored on a public ledger rather than inside a company you have to trust on faith.

***

## Where revocation fits

If signing bakes trust in, revocation is how you take it back. You can't unsign a credential or reach into the holder's wallet to delete it — but you can update the revocation registry. From that point on, every verification against the credential reflects the revoked state. The credential still exists; it just no longer passes. See [Credential Lifecycle](/introduction-to-issuance/learn/credential-lifecycle) for how that state change propagates.

***

## Next steps

[**Decentralized Root of Trust**](/trust-registry/decentralized-root-of-trust) — the full technical model of how Blerify anchors issuer trust without a central authority.

See also: [Credential Lifecycle](/introduction-to-issuance/learn/credential-lifecycle) — how revocation and expiry end a credential's trust · [Core Concepts](/introduction-to-issuance/learn/core-concepts) — the organization key that anchors it all.


# Post Quantum Certificates

Protecting the Platform Against Future Quantum Threats

Blerify has taken a proactive approach to cybersecurity by integrating **post-quantum certificates** into its platform. This advanced feature ensures that the system remains secure against potential future threats posed by quantum computing.

#### **1. What Are Post-Quantum Certificates?**

Post-quantum certificates are cryptographic credentials designed to resist attacks from quantum computers. Traditional encryption methods, such as RSA and ECC (Elliptic Curve Cryptography), rely on mathematical problems that quantum computers could eventually solve efficiently (e.g., Shor's algorithm). Post-quantum cryptography, on the other hand, uses algorithms that are believed to be secure even against quantum computing power.

#### **2. Why Are Post-Quantum Certificates Important?**

Quantum computers, while still in development, have the potential to break widely used encryption methods. By adopting post-quantum certificates, Blerify ensures that its platform remains secure in the long term, protecting sensitive data and maintaining trust with users even as quantum computing technology advances.

#### **3. How Blerify Implements Post-Quantum Certificates**

Blerify has integrated post-quantum cryptographic algorithms into its infrastructure. These algorithms are used for:

* **Key Generation:** Creating secure cryptographic keys resistant to quantum attacks.
* **Digital Signatures:** Ensuring the authenticity and integrity of data.
* **Encryption:** Protecting data in transit and at rest from quantum decryption attempts.

#### **4. Benefits of Post-Quantum Certificates in Blerify**

* **Future-Proof Security:** Protects the platform against emerging quantum threats.
* **Compliance with Standards:** Aligns with recommendations from organizations like NIST (National Institute of Standards and Technology), which is actively standardizing post-quantum cryptographic algorithms.
* **Enhanced Trust:** Demonstrates Blerify's commitment to cutting-edge security practices.
* **Seamless Integration:** Post-quantum certificates work alongside traditional cryptographic methods, ensuring compatibility and smooth operation.

#### **5. How Blerify Ensures a Smooth Transition**

Blerify has implemented a hybrid approach, combining traditional and post-quantum cryptography. This ensures:

* **Backward Compatibility:** Existing systems and integrations continue to function without disruption.
* **Gradual Adoption:** Users and organizations can transition to post-quantum certificates at their own pace.
* **Continuous Updates:** Blerify stays ahead of the curve by monitoring advancements in quantum computing and cryptography, ensuring the platform remains secure.

#### **6. Use Cases for Post-Quantum Certificates in Blerify**

* **Verifiable Credentials (VCs):** Protecting the issuance, storage, and verification of VCs from quantum attacks.
* **Data Encryption:** Safeguarding sensitive user data and communications.
* **Digital Signatures:** Ensuring the authenticity of documents and transactions.
* **API Security:** Securing interactions between Blerify and external systems.


# Quantum-Resistant Cryptography

The rise of quantum computing poses a significant threat to modern cryptographic systems that protect online communications and sensitive data, as they rely on cryptographic algorithms that are **not quantum-resistant.** Once quantum computers become powerful enough to execute **Shor’s algorithm** at scale, widely used asymmetric cryptographic algorithms—such as RSA, (EC)DSA, and (EC)DH—will become vulnerable, as quantum computers will be able to break them in a matter of seconds.

**Post-Quantum Cryptography (PQC)** refers to a new generation of asymmetric cryptographic algorithms designed to resist quantum attacks. Unlike traditional methods, PQC does not depend on quantum mechanics for key exchange but instead leverages complex mathematical problems that cannot be efficiently solved by quantum computers.

To address this global security challenge, **NIST initiated a post-quantum cryptography standardization process in 2016**, inviting candidates for evaluation. After several selection rounds, in August 2024, **NIST finalized three post-quantum digital signature standards: CRYSTALS-Dilithium, FALCON, and SPHINCS+**, marking a crucial step toward a quantum-resistant future.

The pioneer work of Blerify’s founding team on quantum-resistant cryptography and blockchain led to the development of the first implementation of a **Quantum-Resistant EVM Blockchain**, which was published by [Nature's Scitific Reports Magazine](https://www-nature-com.translate.goog/articles/s41598-023-32701-6?error=cookies_not_supported\&code=0f0ce615-043f-4a05-b5b1-a34dd6286991&_x_tr_sl=en&_x_tr_tl=es&_x_tr_hl=es&_x_tr_pto=tc) and featured as a top 100 publication in 2023. At Blerify, we are implementing **NIST-compliant PQC algorithms** to safeguard digital identity and ensure cryptographic integrity against future quantum threats.


# Request

**Technical Diagrams for Quantum-Resistant Communication**

The communication diagram between servers has been updated.

<figure><img src="https://239097222-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FMvCxPFtGg0Vjq2LzXN1U%2Fuploads%2Fgit-blob-4d29817c0ba842d8907583b7c97a38cea8fb32aa%2Fimage.png?alt=media" alt="" width="375"><figcaption><p><strong>Diagram for Hybrid Post-Quantum Certificate Generation</strong></p></figcaption></figure>

The process of obtaining a certificate involves:

* Downloading an executable (certificate manager) that allows the generation of hybrid certificates.
* User authentication.
* Generating a CSR (Certificate Signing Request).
* Generating the certificate and registering it in a high-availability trust list with a timestamp.
* Sending the CSR to Blerify.
* Receiving the hybrid certificate issued by Blerify.


# Revocation

### Revocation of certificates

<figure><img src="https://239097222-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FMvCxPFtGg0Vjq2LzXN1U%2Fuploads%2Fgit-blob-bb43f07ffd099ec67576d3a590ee1d1570266822%2Fimage.png?alt=media" alt="" width="375"><figcaption></figcaption></figure>

From the Blerify Portal, the user can request the revocation of a certificate. Once the Blerify CA administrator authorizes the revocation, the corresponding certificate will be published in the trust list as a revoked record.


# Verify

Verifying a post-quantum certificate involves a series of steps to ensure its authenticity, integrity, and validity. Here's a detailed explanation of the process.

#### **1. Certificate Structure and Components**

A post-quantum certificate typically includes:

* **Public Key:** Generated using post-quantum cryptographic algorithms.
* **Digital Signature:** Created using a post-quantum signature scheme.
* **Metadata:** Information such as issuer details, validity period, and usage constraints.
* **Extensions:** Additional data, such as key usage policies or revocation status.

#### **2. Steps to Verify a Post-Quantum Certificate**

**a. Validate the Certificate Chain**

* Verify that the certificate is issued by a trusted Certificate Authority (CA) using post-quantum cryptographic methods.
* Check the entire certificate chain, ensuring that each intermediate and root certificate is valid and trusted.

**b. Verify the Digital Signature**

* Use the issuer's public key (also post-quantum) to verify the digital signature on the certificate.
* Ensure the signature was generated using a post-quantum algorithm (e.g., CRYSTALS-Dilithium, SPHINCS+, or Falcon).

**c. Check the Validity Period**

* Confirm that the certificate is within its validity period (not expired or not yet active).

**d. Verify Revocation Status**

* Check the certificate against a Certificate Revocation List (CRL) or use an Online Certificate Status Protocol (OCSP) to ensure it has not been revoked.
* For post-quantum systems, this step may involve querying a quantum-resistant revocation database.

**e. Validate Key Usage**

* Ensure the certificate is being used for its intended purpose (e.g., encryption, signing, or authentication) as specified in the key usage extensions.

**f. Verify the Integrity of the Certificate**

* Ensure that the certificate has not been tampered with by recalculating and comparing its hash or using other integrity-checking mechanisms.

#### **3. Tools and Protocols for Verification**

* **Post-Quantum Cryptographic Libraries:** Use libraries like Open Quantum Safe (OQS) or others that support post-quantum algorithms.
* **Trusted CA Infrastructure:** Ensure the CA issuing the certificate is using post-quantum standards.
* **Revocation Services:** Use quantum-resistant revocation mechanisms, such as CRLs or OCSP, updated for post-quantum environments.

#### **4. Example Workflow for Verification**

1. **Receive the Certificate:** Obtain the post-quantum certificate from the entity presenting it.
2. **Extract Public Key and Metadata:** Parse the certificate to retrieve the public key and other details.
3. **Verify the Signature:** Use the issuer's public key to validate the certificate's signature.
4. **Check Validity and Revocation:** Ensure the certificate is valid and not revoked.
5. **Confirm Key Usage:** Validate that the certificate is being used appropriately.
6. **Final Validation:** If all checks pass, the certificate is considered verified.

#### **5. Challenges in Post-Quantum Certificate Verification**

* **Algorithm Transition:** Ensuring compatibility between traditional and post-quantum systems during the transition phase.
* **Performance:** Post-quantum algorithms may require more computational resources, impacting verification speed.
* **Standardization:** Waiting for finalization of post-quantum standards by organizations like NIST.


# Reference

Lookup material — precise and exhaustive, with no narrative to wade through. Come here when you already know what you're doing and just need the exact role, scope, or contract.

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>API Reference</strong></td><td>The full endpoint contracts — request/response schemas, error codes, examples.</td><td><a href="https://dev.blerify.com/#01cf28b2-23f9-472d-a822-ab8cc0ce0114">https://dev.blerify.com/#01cf28b2-23f9-472d-a822-ab8cc0ce0114</a></td></tr><tr><td><strong>API Access &#x26; Scopes</strong></td><td>How a service account authenticates and gets authorized, and what each scope allows.</td><td><a href="/introduction-to-issuance/reference/api-access-and-scopes">API Access &amp; Scopes</a></td></tr><tr><td><strong>Portal Roles &#x26; Permissions</strong></td><td>The platform-wide role model — the four roles, the two project permissions, and who can grant what.</td><td><a href="/introduction/portal-roles-and-permissions">Portal Roles and Permissions</a></td></tr><tr><td><strong>Versions &#x26; Releases</strong></td><td>Changes to the Issuance product, newest first.</td><td><a href="https://github.com/BlerifyPlatform/docs-public/tree/main/issuance/reference/versions-and-releases.md">https://github.com/BlerifyPlatform/docs-public/tree/main/issuance/reference/versions-and-releases.md</a></td></tr></tbody></table>

## Next steps

[**API Access & Scopes**](/introduction-to-issuance/reference/api-access-and-scopes) — how machine-to-machine auth works and what each scope grants.

See also: [API Reference](https://dev.blerify.com/#01cf28b2-23f9-472d-a822-ab8cc0ce0114) — the full endpoint contracts · [Issue a W3C Credential](/introduction-to-issuance/build/issue-a-w3c-credential) — the guide these contracts back.


# API Access & Scopes

Before your backend can issue anything, two things have to line up: it has to authenticate as a service account, and that account has to be authorized for the specific project it's acting on. This page covers both — how access is granted, and what each scope lets you do once it is. For the endpoint-by-endpoint contracts, see the [API Reference](https://dev.blerify.com/#01cf28b2-23f9-472d-a822-ab8cc0ce0114).

***

## Prerequisites

* A Blerify organization and at least one project
* A service account provisioned by your Blerify account manager
* Your service account credentials JSON file

***

## Authenticating

The Issuance API authenticates your service account via OAuth 2.0 `client_credentials` with `private_key_jwt` -- there is no client secret. See [Authentication](/introduction/authentication) for the complete token exchange walkthrough, including the credentials file fields and the JWT assertion structure.

Send the resulting bearer token on every request:

```http
Authorization: Bearer <access_token>
```

***

## Authorization model

Authenticating gets you a valid token; it doesn't get you access. Blerify scopes service accounts in two layers, and **both have to be true** for a request to succeed:

* **Role** — holding `credentials.api` grants access to the issuance API surface in general.
* **Group membership** — belonging to the group tied to a project scopes that access to *that* project.

This is why a perfectly valid token can still be refused with a `403`. If you hold the role but haven't been added to the project's group, the API returns `403`. That's the most common cause of a `403` on a token you know is good — ask your Blerify account manager to confirm the service account belongs to the project group.

### Scopes

Once both layers line up, the `credentials.api` role grants the following scopes on the project resource:

| Scope                     | What it allows                          |
| ------------------------- | --------------------------------------- |
| `create_credential_item`  | Issue a new credential                  |
| `read_credential_item`    | Retrieve credential status and metadata |
| `update_credential_item`  | Update a credential's attributes        |
| `delete_credential_item`  | Delete a credential record              |
| `approve_credential_item` | Approve a pending issuance              |
| `hold_credential_item`    | Place a credential on hold              |
| `revoke_credential_item`  | Revoke an issued credential             |
| `resend_credential_item`  | Resend a delivery notification          |

You don't request these individually. Your service account receives all of them the moment it holds the role and belongs to the right project group — the authorization server evaluates them automatically against your token.

***

## Where the endpoints live

This page covers *who* can call the API and *what* they're allowed to do. The [**API Reference**](https://dev.blerify.com/#01cf28b2-23f9-472d-a822-ab8cc0ce0114) covers *what to call* — the full request and response schemas, error codes, and example payloads for every endpoint.

***

## Next steps

[**Portal Roles & Permissions**](/introduction/portal-roles-and-permissions) — the human counterpart to this page: who on your team can act in the Portal, as opposed to which service account can call the API.

See also: [Issue a W3C Credential](/introduction-to-issuance/build/issue-a-w3c-credential) — the calls these scopes authorize · [Handle Errors](/introduction-to-issuance/build/handle-errors) — the `403` and other failures, and how to recover.


# Programmatic Issuance — Create, Approve, and Poll

This guide walks through issuing a credential from your backend without human involvement in the Portal — create the credential, approve it as a service account, and poll until it's ready. When the credential is issued, you get a PDF, a thumbnail, and a claim code your receiver uses to collect it into their wallet.

This pattern fits any backend system that triggers issuance automatically: an LMS that issues certificates when a course is completed, an HR system that issues badges when an employee joins, or any pipeline where the Portal isn't in the loop.

***

## Prerequisites

* A provisioned service account with the `credentials.api` role and membership in the project group you're issuing under. See [API Access & Scopes](/introduction-to-issuance/reference/api-access-and-scopes) if either of those isn't set up yet.
* A **service account credentials file** (JSON) containing: `client_id`, `private_key`, `private_key_id`, `token_uri`, and `organization_id`.
* A project with a defined template and template ID.
* The base URL for the API gateway (`{GATEWAY}`) for your environment.

Keep the `private_key` secret. All requests are over TLS.

***

## The flow at a glance

Programmatic issuance is three calls after authentication: create the credential, approve it, then poll until the status reaches `SENT`.

```mermaid
sequenceDiagram
    autonumber
    participant M as Your Backend
    participant GW as Blerify Gateway

    M->>GW: 0. POST {token_uri} (client_credentials + private_key_jwt)
    GW-->>M: access_token
    M->>GW: 1. POST /credentials (templateId, w3cData, organizationUser)
    GW-->>M: 201 credential (_id, status=PENDING)
    M->>GW: 2. PUT /credentials/{cid}/sign (templateId)
    GW-->>M: 200 "success" (issuance runs asynchronously)
    loop until status = SENT
      M->>GW: 3. GET /credentials/{cid}/polling?templateId=...
      GW-->>M: 200 credential (status, pdf, thumbnail, code)
    end
```

The path segments used throughout:

* `{oid}` — your organization ID, from the credentials file (`organization_id`).
* `{pid}` — the project ID you're issuing under.
* `{tid}` — the template ID for this credential type.
* `{cid}` — the credential ID returned by the create call.

***

## Step 0 — Get an access token

Authenticate using OAuth 2.0 `client_credentials` with a `private_key_jwt` client assertion (RFC 7523) -- see [Authentication](/introduction/authentication) for how to build the JWT assertion. Then exchange it:

```
POST {token_uri}
Content-Type: application/x-www-form-urlencoded
```

| Field                   | Value                                                    |
| ----------------------- | -------------------------------------------------------- |
| `grant_type`            | `client_credentials`                                     |
| `client_assertion_type` | `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` |
| `client_assertion`      | the signed JWT described above                           |
| `scope`                 | `openid`                                                 |

**Response `200`:**

```json
{ "access_token": "eyJ...", "token_type": "Bearer", "expires_in": 300 }
```

Send `Authorization: Bearer <access_token>` on every subsequent call. When the token expires, request a new one the same way.

***

## Step 1 — Create the credential

```
POST {GATEWAY}/api/v1/organizations/{oid}/projects/{pid}/credentials
Authorization: Bearer <access_token>
Content-Type: application/json
```

```json
{
  "templateId": "008dabcc-ec8f-4dfe-905a-f1a384071123",
  "additionalData": {
    "w3cData": {
      "fullname": "Your Receiver Name",
      "metadata": { "level": 1, "membership": 123457 }
    }
  },
  "organizationUser": {
    "email": "receiver@example.com"
  },
  "options": { "omitApproval": false, "approvers": true }
}
```

| Field                    | What it does                                                                                                                                             |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `templateId`             | The credential template to issue under.                                                                                                                  |
| `additionalData.w3cData` | The data rendered into the credential. Provide the fields your template expects — the names match the template's placeholders.                           |
| `organizationUser`       | The receiver's identity. `email` is required; include `did` if the receiver already has a wallet. Blerify creates or updates the receiver automatically. |
| `options.approvers`      | Keep `true` when the template defines approvers.                                                                                                         |

**Response `201`** — the credential is created with status `PENDING`:

```json
{
  "_id": "0x8f3a...c1",
  "status": "PENDING",
  "issuer": "did:...",
  "template": { "id": "008dabcc-...", "projectId": "50bdc57f-...", "organizationId": "6611949d-..." },
  "receiver": { "id": "…", "email": "receiver@example.com" }
}
```

Hold on to `_id` — it's `{cid}` in every call that follows.

***

## Step 2 — Approve the credential

Approving as a service account records the approval and triggers issuance. You don't need to be a designated template signer — the service account's role is enough.

```
PUT {GATEWAY}/api/v1/organizations/{oid}/projects/{pid}/credentials/{cid}/sign?keystore=keyvault&lang=en
Authorization: Bearer <access_token>
Content-Type: application/json
```

```json
{ "templateId": "008dabcc-ec8f-4dfe-905a-f1a384071123" }
```

Query parameters: `keystore=keyvault` is required. `lang` sets the receiver's language (e.g. `en`, `es`).

**Response `200`:**

```json
"success"
```

{% hint style="warning" %}
`200 "success"` means the approval was recorded and signing was queued — not that the credential is ready. Always confirm the result by polling.
{% endhint %}

***

## Step 3 — Poll until issued

Poll until `status` reaches `SENT`. The `templateId` query parameter is required — access is scoped to the combination of organization, project, and template.

```
GET {GATEWAY}/api/v1/organizations/{oid}/projects/{pid}/credentials/{cid}/polling?templateId={tid}
Authorization: Bearer <access_token>
```

**While issuing:**

```json
{ "_id": "0x8f3a...c1", "status": "PENDING", "pdf": null, "thumbnail": null, "code": null }
```

**When ready:**

```json
{
  "_id": "0x8f3a...c1",
  "status": "SENT",
  "pdf": "https://storage.../credential.pdf?signature=...",
  "thumbnail": "https://storage.../thumbnail.png?signature=...",
  "code": "abC12XyZ...",
  "receiver": { "email": "receiver@example.com" },
  "template": { "id": "008dabcc-...", "name": "..." }
}
```

| Field              | How to use it                                                                                                       |
| ------------------ | ------------------------------------------------------------------------------------------------------------------- |
| `status`           | Poll until `SENT`. The credential moves to `DELIVERED` once the receiver claims it.                                 |
| `pdf`, `thumbnail` | Signed URLs — they expire quickly. Fetch or render them immediately after polling, or poll again to get fresh ones. |
| `code`             | The claim code. Use it to build the QR the receiver scans to claim the credential into their wallet.                |

A `404` response means the credential doesn't exist under that organization, project, and template combination — double-check `{cid}` and `{tid}`.

Poll on a reasonable interval (every few seconds) with a timeout to avoid polling indefinitely.

***

## Error reference

| Status           | What it means                                                 | Fix                                                                                                                                                                         |
| ---------------- | ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`            | Token missing, invalid, or expired                            | Redo Step 0 and retry                                                                                                                                                       |
| `400`            | Invalid request body or parameters                            | Check the request against the field descriptions above                                                                                                                      |
| `403`            | Valid token, wrong authorization                              | Confirm the service account holds `credentials.api` and belongs to the project group — see [API Access & Scopes](/introduction-to-issuance/reference/api-access-and-scopes) |
| `404` on polling | Wrong organization/project/template scope for that credential | Verify `{cid}` and `{tid}`                                                                                                                                                  |

***

## Next steps

[**Issue a W3C Credential**](/introduction-to-issuance/build/issue-a-w3c-credential) — the full issuance walkthrough, including the Portal path and the complete three-step API flow with signing.

See also: [API Access & Scopes](/introduction-to-issuance/reference/api-access-and-scopes) — how service account authorization works · [Authentication](/introduction/authentication) — the `private_key_jwt` grant in full detail · [Manage a Credential](/introduction-to-issuance/build/manage-a-credential) — what to do after a credential is out in the world.


# Introduction to Verification

Blerify's verification product is the **Universal Point of Verification — UPoV**: one integration through which your business obtains verified information from credential holders. It answers one question: **can I trust the credential this person is showing me?** When someone presents a digital credential on your website or in your app, UPoV checks that it's genuine, that its issuer hasn't revoked it, and that it belongs to the person presenting it — not to someone else. You get a clear answer in seconds.

For the person being verified, it feels simple: they tap a button or scan a QR code, their digital wallet shows them exactly what you're asking to see, and they approve with their fingerprint or face. No forms to fill in, no documents to photograph, no waiting for a review.

For your team, the work splits in two. Someone configures a **verification rule** in the [Blerify Portal](https://portal.blerify.com/) — a point-and-click setup where you choose which credential to accept and which details to request; no code involved. Then a developer connects your system to Blerify with two simple calls. Nobody on your side has to understand cryptography: Blerify runs the checks and reports the facts.

Everything in this section falls into one of four parts. If you want to see the product applied to a real scenario before deciding anything, read a **Use Case** — no technical background needed. Developers here to ship start with **Build**. **Learn** explains what the checks actually prove, and **Reference** is for lookup.

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>🛠️ Build</strong></td><td>Task-oriented guides: run your first verification, create a rule, read the result.</td><td><a href="/introduction-to-verification/build">Build</a></td></tr><tr><td><strong>💡 Use Cases</strong></td><td>Complete scenarios — age checks, customer onboarding — showing which decisions to make and why.</td><td><a href="/introduction-to-verification/use-cases">Use Cases</a></td></tr><tr><td><strong>📘 Learn</strong></td><td>The concepts underneath: assurance tiers, biometric binding, platform attestation, verifier trust.</td><td><a href="/introduction-to-verification/learn">Learn</a></td></tr><tr><td><strong>📗 Reference</strong></td><td>Exact contracts: the API, verification rule configuration, portal roles, and a working demo app.</td><td><a href="/introduction-to-verification/reference">Reference</a></td></tr></tbody></table>

{% hint style="info" %}
**First time here?** Start with [Create a Verification Rule](/introduction-to-verification/build/create-a-verification-rule) — everything begins with the rule you configure in the Portal. Then run your first verification against it in [Get Started](/introduction-to-verification/build/get-started).
{% endhint %}

## Next steps

[**Create a Verification Rule**](/introduction-to-verification/build/create-a-verification-rule) — the Portal setup every verification starts from.

See also: [Age Verification](/introduction-to-verification/use-cases/age-verification) — a complete scenario in five minutes of reading · [Assurance Tiers](/introduction-to-verification/learn/assurance-tiers) — how much proof each tier gives you.


# Build

These are the hands-on guides, in the order you'll actually do the work: everything starts with a verification rule in the Portal, because its UUID is what your backend references on every call. The integration itself is small — two backend calls and a QR code — so there are only three pages here.

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Create a Verification Rule</strong></td><td>The one-time Portal setup: which credential to accept, which fields to request, which assurance level to require.</td><td><a href="/introduction-to-verification/build/create-a-verification-rule">Create a Verification Rule</a></td></tr><tr><td><strong>Get Started</strong></td><td>Your first verification end to end: authenticate, start a session, show the wallet link, poll the result.</td><td><a href="/introduction-to-verification/build/get-started">Get Started</a></td></tr><tr><td><strong>Read a Verification Result</strong></td><td>The full result schema, the evidence at each tier, and what to do with every negative outcome.</td><td><a href="/introduction-to-verification/build/read-a-verification-result">Read a Verification Result</a></td></tr></tbody></table>

## Next steps

[**Create a Verification Rule**](/introduction-to-verification/build/create-a-verification-rule) — begin here; you need the rule's UUID before you can make any API call.

See also: [Use Cases](/introduction-to-verification/use-cases) — see the same integration applied to a real scenario before you write code.


# Create a Verification Rule

This page walks you through creating a verification rule in the Blerify Portal, from logging in to publishing the rule so it's ready to use.

A verification rule is a reusable configuration you create once: it defines which credential to accept, which attributes to request and which verifications to run, and how your app will talk to Blerify at runtime.

## Prerequisites

* Access to the Blerify Portal at [portal.blerify.com](https://portal.blerify.com/)
* Admin or project-level access to create rules in your organization

***

## Step 1 — Log in to the Portal

Go to [portal.blerify.com](https://portal.blerify.com/) and log in with your username and password.

<br>

<figure><img src="https://239097222-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FMvCxPFtGg0Vjq2LzXN1U%2Fuploads%2Fgit-blob-9490b62894e92b41d27ed12aeb584e10cf2c5c26%2F01-login.png?alt=media" alt=""><figcaption></figcaption></figure>

\ <br>

## Step 2 — Choose "Verify credentials"

From the Portal's home screen, pick **Verify digital credentials or identity documents** and click **Continue**.

<br>

<figure><img src="https://239097222-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FMvCxPFtGg0Vjq2LzXN1U%2Fuploads%2Fgit-blob-f5c1c10a490fb1f496acfbe12b2ca492d755d347%2F03-empezar-verificar.png?alt=media" alt=""><figcaption></figcaption></figure>

\ <br>

## Step 3 — Choose the verification channel

Pick how the credential will be presented:

* **Online verification**: the person presents their credential from their phone, through a universal link or QR code, on your website or app.
* **In-person verification**: an operator checks the document in person with the verification mobile app, with no connection between servers required.

<br>

<figure><img src="https://239097222-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FMvCxPFtGg0Vjq2LzXN1U%2Fuploads%2Fgit-blob-97251a7bf7668e38c4631d2cfd2b586fe9b35d0a%2F04-canal.png?alt=media" alt=""><figcaption></figcaption></figure>

\ <br>

## Step 4 — Choose the assurance level

Every verification rule needs an assurance level. This walkthrough uses **Basic**, the entry-level tier: it confirms that the credential is valid and was issued by a trusted issuer, checking the credential's cryptographic signature and its timestamp. It doesn't ask the holder to prove anything beyond presenting the credential itself.

**Premium is coming soon** — configure Basic or Standard for now.

**The assurance level can't be changed once the rule is created.** If you need a different one later, you'll have to delete the rule and create a new one.

<br>

<figure><img src="https://239097222-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FMvCxPFtGg0Vjq2LzXN1U%2Fuploads%2Fgit-blob-0cdf65b5a57da8d6096f2f6da6b05740b98624a2%2F05-nivel.png?alt=media" alt=""><figcaption></figcaption></figure>

\ <br>

## Step 5 — Choose which credentials to accept

Select which credentials your users will be able to present. The catalog is filtered by the assurance level you picked in the previous step.

<br>

<figure><img src="https://239097222-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FMvCxPFtGg0Vjq2LzXN1U%2Fuploads%2Fgit-blob-c9557191bd14c61ff1d38b05c38355189a94753a%2F06-credenciales.png?alt=media" alt=""><figcaption></figcaption></figure>

\ <br>

## Step 6 — Fill in the rule's basic information

Give the rule a name and description, and pick what type of rule it is (Login, Enrollment, Digital signature, Access control, or Process validation). This tells Blerify what the rule will be used for.

<br>

<figure><img src="https://239097222-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FMvCxPFtGg0Vjq2LzXN1U%2Fuploads%2Fgit-blob-cbeac21c84390b11fd0fd597652da61bae203822%2F07-informacion.png?alt=media" alt=""><figcaption></figcaption></figure>

\ <br>

## Step 7 — Choose which attributes to request

Pick which fields you need from the credential (name, date of birth, document number, and so on). Some fields require a higher assurance level than the one you picked. Those show up locked, with a note telling you which level unlocks them.

<br>

<figure><img src="https://239097222-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FMvCxPFtGg0Vjq2LzXN1U%2Fuploads%2Fgit-blob-a98d8a3905f3dd6e420761b6f0ef726afe54c714%2F08-atributos.png?alt=media" alt=""><figcaption></figcaption></figure>

\ <br>

## Step 8 — Review the verifications included

This step shows you everything Blerify checks automatically at your chosen level: document validity, revocation, issuer signature, cryptographic integrity, and holder signature. These are pre-configured and can't be turned off at this level.

<br>

<figure><img src="https://239097222-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FMvCxPFtGg0Vjq2LzXN1U%2Fuploads%2Fgit-blob-56056bbc36b0b9d99fefaff5b9f7934c21fd2c5e%2F09-verificaciones.png?alt=media" alt=""><figcaption></figcaption></figure>

\ <br>

Further down the same screen, you can see the three possible outcomes of a verification: approved automatically, requires manual review, or rejected automatically.

<br>

<figure><img src="https://239097222-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FMvCxPFtGg0Vjq2LzXN1U%2Fuploads%2Fgit-blob-789e81a90fa6e0744305ed27d9751f0a093da978%2F10-resultados-posibles.png?alt=media" alt=""><figcaption></figcaption></figure>

\ <br>

## Step 9 — Set up the technical integration

This is where you connect the rule to your own systems. Choose between:

* **API integration**: you control the whole experience. Your backend starts the verification and your frontend shows the flow.
* **SDK integration** (coming soon): Blerify will handle the plumbing for you. Choose API integration for now.

You also choose how the wallet validates the request. Today that's through a Universal Link and QR code on your own site. A redirect-based option — Blerify hosts the verification page and sends the user back to you with an authorization code — is coming soon; choose the Universal Link and QR code for now.

<br>

<figure><img src="https://239097222-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FMvCxPFtGg0Vjq2LzXN1U%2Fuploads%2Fgit-blob-3e74a57b6388d057efcc4bca0328361a0b26a233%2F11-integracion-antes.png?alt=media" alt=""><figcaption></figcaption></figure>

\ <br>

Click **Generate ID** to save the rule as a draft. This creates a [service account](/introduction/authentication#what-is-a-service-account) for the project automatically and generates the rule's ID. You can switch to a different existing service account afterward if you need to.

<figure><img src="https://239097222-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FMvCxPFtGg0Vjq2LzXN1U%2Fuploads%2Fgit-blob-0bb4875011ba1f418d8f5d64ce9c5fa1d534d966%2F16-integracion-resultado.png?alt=media" alt=""><figcaption></figcaption></figure>

<br>

Once you've generated the ID, the rule shows the service account that was created for it. From here you get the exact code for each of the four integration steps:

**Integration step 1 — Authenticate.** Your backend signs a `private_key_jwt` assertion and exchanges it for an access token.

**Integration step 2 — Start the verification.** Your backend calls Blerify with the access token to start the session.

**Integration step 3 — Show it to the user.** Build the QR code or deep link that opens the wallet, either on your website or from a native mobile app.

**Integration step 4 — Get the result.** Poll the result endpoint until you get a final status.

\ <br>

## Step 10 — Review the summary and publish

The last step shows you the complete configuration: general information, documents and attributes, verifications, sanctions lists, technical integration, and the estimated price per verification.

\ <br>

<br>

<figure><img src="https://239097222-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FMvCxPFtGg0Vjq2LzXN1U%2Fuploads%2Fgit-blob-3eaccdfcdbf8e33afd335301bdce9f57b93315ad%2F17-resumen.png?alt=media" alt=""><figcaption></figcaption></figure>

Once you're happy with everything, publish the rule. You'll see a confirmation, and the rule now appears as **Active** in your list of verification rules, ready for your verifiers to use.

\ <br>

<br>

<figure><img src="https://239097222-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FMvCxPFtGg0Vjq2LzXN1U%2Fuploads%2Fgit-blob-3ade6951bbb9c15ed2fe9713fefe2e699bc93803%2F18-publicada.png?alt=media" alt=""><figcaption></figcaption></figure>

## How your rule behaves at runtime

A few things worth knowing about the configuration you just published.

**Request only what you need.** The user sees exactly which fields you're requesting on the wallet's consent screen and has to approve them explicitly. Requesting more than necessary adds friction without improving your result. An age gate needs one boolean — not a name and a birth date.

**Issuer trust is checked on every presentation.** When a user presents a credential, Blerify verifies that its signing certificate traces back to the issuing entity your rule accepts. If the chain doesn't match, the credential is rejected even if the signature itself is valid. This is separate from revocation: trust chain validation answers "is this issuer authorized to issue this credential?", revocation answers "has this specific credential been invalidated?". Both run automatically. When an issuing authority rotates its signing certificates, Blerify updates its trust registry — your rule keeps working without changes on your end.

**If you chose Premium, the portrait is included automatically.** Premium performs a facial liveness comparison against the portrait in the credential, so the Portal adds the portrait to your data selection if you didn't, and shows a notice.

**Not every wallet reaches every tier.** The Blerify wallet supports all three tiers. Wallets from other providers — apps that follow the same open credential-exchange standard but don't include Blerify's security extensions — can only complete Basic verifications. If a wallet can't provide evidence your rule requires, the verification doesn't quietly succeed at a lower level: it fails with an explicit reason (`required_fields_missing`), and you decide what to offer the user next — see [Read a Verification Result](/introduction-to-verification/build/read-a-verification-result#when-evidence-is-missing).

## Next steps

**Continue to** [**Get Started with a Verification**](/introduction-to-verification/build/get-started): use the rule you just created to run a real verification end to end, from your backend.

See also: [Assurance Tiers](/introduction-to-verification/learn/assurance-tiers) · [Read a Verification Result](/introduction-to-verification/build/read-a-verification-result)


# Get Started

This page walks you through your first working verification, from authenticating your service account to reading the result. By the end, you'll have made a real API call and seen a credential get verified.

## Prerequisites

* A verification rule published in the Blerify Portal — the one-time setup that defines which credential to ask for, which issuer to trust, and what assurance level to require. If you don't have one yet, follow [Create a Verification Rule](/introduction-to-verification/build/create-a-verification-rule) first. The Portal gives you the rule's UUID; the examples below use `{your-template-uuid}` as its placeholder
* A service account. You already have one: the Portal created it automatically when you generated your rule's ID (or you picked an existing one at that step). It carries the `verifications.api` role your backend authenticates with — see [Authentication](/introduction/authentication) for how its credentials work

## How it works

A verification has four steps:

1. Your backend starts a session by calling the Blerify API with your template UUID
2. Blerify gives you back what your frontend needs to open the wallet: a button, a link, or a QR code
3. The user opens their wallet, sees what you're asking for, and approves with their biometric
4. Your backend gets the result

Blerify takes care of everything between steps 2 and 4: the credential protocol, the cryptographic checks, the trust chain check, and the revocation lookup. You never see the raw credential go by. You get back a clean result with the fields your template asked for and a verdict for each credential.

Your backend talks to Blerify. The wallet talks to Blerify directly on the user's behalf. You never have to parse a credential, verify a signature, or check a trust chain yourself.

## Step 1 — Authenticate your service account

You don't need to create a service account here — the Portal already set one up when you generated your rule's ID in [Create a Verification Rule](/introduction-to-verification/build/create-a-verification-rule), or attached the existing one you selected at that step.

Your backend authenticates as that account using the OAuth 2.0 `client_credentials` grant, with `private_key_jwt` as the client authentication method (RFC 7523). See [Authentication](/introduction/authentication) for how the account's credentials work and how to build the signed JWT. Once you have it:

```bash
curl -X POST '<token_uri from the credentials file>' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'client_id=<client_id from the credentials file>&organization_id=<organization_id from the credentials file>&client_assertion=<the signed JWT you built>'
```

```json
{
  "access_token": "<YOUR_SERVICE_ACCOUNT_TOKEN>",
  "token_type": "Bearer",
  "expires_in": 36000
}
```

Use this token in the `Authorization` header on every request that follows. Cache it and refresh it before it expires instead of asking for a new one on every call — or let a client library do this housekeeping: if you work in PHP, the official [`auth-php-client`](/introduction/authentication#use-a-library-instead-of-building-from-scratch) handles the assertion signing, caching, and renewal from your credentials file.

## Step 2 — Start a verification session

Your backend calls Blerify with your template UUID to create a session. Every session needs a fresh, random `nonce` that your backend generates. It ties the presentation to this session and stops replay attacks:

```bash
curl -X POST 'https://api.blerify.com/client/api/v2/openid4vp/organizations/{your-org-id}/projects/{your-project-id}/verifications/{your-template-uuid}' \
  -H 'Authorization: Bearer <YOUR_SERVICE_ACCOUNT_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{
    "nonce": "<A_FRESH_RANDOM_UNGUESSABLE_STRING>"
  }'
```

```json
{
  "request_id": "4c4a1e3a-9f2b-4d7e-a1c8-2b5e8f6a1e3a",
  "transaction_id": "8f6a1e3a-c9b5-4b1c-8e3c-4c4a1e3a7b1d",
  "request_uri_method": "get",
  "client_id_scheme": "x509_san_dns",
  "client_id": "<returned by Blerify — copy it verbatim>"
}
```

Keep `transaction_id`. It's your polling key for this session. The other four fields (`request_id`, `request_uri_method`, `client_id_scheme`, `client_id`) are what you'll use to build the wallet link in the next step.

You never construct or look up any of these values yourself — Blerify returns them, and you copy them exactly as they come. Their values depend on your rule's credential format: an ISO 18013 mDoc rule — the most common case — returns `request_uri_method: "get"` and `client_id_scheme: "x509_san_dns"` as shown above, while a W3C credential rule returns `"post"` and `"did"` with your organization's identifier as `client_id`. Your code shouldn't care either way — treat all four as opaque and pass them through.

The only other field the endpoint accepts is `expirationTime` (optional, in seconds) — the session lifetime. It defaults to 300 (5 minutes), or to the value configured on your rule in the Portal.

Most rules use Blerify-managed signing and the flow above is complete. If your rule was set up for external signing — signing the verification request with your own verifier certificate — the response also includes a `signing_payload` your backend signs and submits back before the wallet step; the Blerify team configures this with you when the rule is created.

## Step 3 — Build the wallet link and show the user a prompt

The wallet opens through a single Universal Link. The same link works whether you show it as a button, a QR code, or open it from a native app:

```
https://wallet.blerify.com/openid4vp
  ?request_uri=<URL-encoded request URL>
  &client_id=<client_id from the session response>
  &client_id_scheme=<client_id_scheme from the session response>
  &request_uri_method=<request_uri_method from the session response>
```

Copy `client_id`, `client_id_scheme`, and `request_uri_method` exactly as they came in the Step 2 response. `request_uri` works differently: you build that URL yourself from `request_id`. It points at the wallet-facing endpoint for this specific session, and you need to URL-encode it:

```
https://api.blerify.com/public/api/v1/openid4vp/organizations/{your-org-id}/projects/{your-project-id}/verifications/{your-template-uuid}/request/{request_id}
```

All four query parameters are required. If you drop or rename even one, the wallet won't open the right flow.

Build a fresh link for every session — `request_id` is single-use and short-lived.

**Same-device (user is on their phone):** render the link as a button. When the user taps it, the OS sends it straight to the Blerify Wallet app.

```html
<a href="https://wallet.blerify.com/openid4vp?request_uri=<URL-ENCODED-REQUEST-URL>&client_id=<client_id>&client_id_scheme=x509_san_dns&request_uri_method=get">
  Verify with Blerify Wallet
</a>
```

**Cross-device (user is on a desktop):** turn the same Universal Link into a QR code with any standard library. The user scans it from their wallet app while your backend keeps polling from the desktop side.

That's the whole frontend. There's no SDK to install and no wallet logic to write — one link, rendered as a button or a QR code.

## Step 4 — Poll for the result

Start polling as soon as your frontend shows the link or QR code. While you poll, the user opens their wallet, sees a consent screen with exactly what you're asking for, and approves with their biometric — the wallet sends the signed presentation to Blerify directly, so your code has nothing to do but keep checking.

Poll every 1–2 seconds until you get a final status. One thing to watch: the query parameter in the URL is `transaction-id` (hyphen), while the field in the response body is `transaction_id` (underscore).

```bash
curl 'https://api.blerify.com/client/api/v2/openid4vp/organizations/{your-org-id}/projects/{your-project-id}/verifications/{your-template-uuid}/response?transaction-id=8f6a1e3a...' \
  -H 'Authorization: Bearer <YOUR_SERVICE_ACCOUNT_TOKEN>'
```

While the user is still in the flow:

```json
{ "status": "PENDING", "transaction_id": "8f6a1e3a-c9b5-4b1c-8e3c-4c4a1e3a7b1d" }
```

You might also see `CREATED`, `SUBMITTED`, or `VALIDATING` while the session is running. Treat all of them the same way and keep polling. Stop once you get `COMPLETED` or `FAILED`. A verification typically takes 5–15 seconds end to end at Basic and Standard, 15–30 at Premium — the liveness capture adds time.

When the user finishes:

```json
{
  "status": "COMPLETED",
  "assurance_level": "BASIC",
  "effective_tier": "BASIC",
  "credentials": [
    {
      "type": "<the credential type your rule requests>",
      "present": true,
      "issuer": "<the issuing authority>",
      "result": {
        "credential_valid": true,
        "signature_valid": true,
        "issuer_trusted": true,
        "revoked": false,
        "expired": false,
        "holder_binding": "VERIFIED"
      },
      "data": {
        "format": "mso_mdoc",
        "namespaces": {
          "org.iso.18013.5.1": {
            "given_name": "Jane",
            "family_name": "Smith",
            "document_number": "00000000-0",
            "birth_date": "1990-01-15"
          }
        }
      }
    }
  ],
  "evidence": {
    "timestamp": "2026-04-21T12:00:03Z"
  },
  "signed_evidence": "eyJhbGciOiJFUzI1NiIs..."
}
```

That's the whole flow: authenticate, start a session, show the link, poll. What the result means — the schema, the evidence at each tier, and what to do with every negative outcome — is the next page's job.

## Next steps

**Continue to** [**Read a Verification Result**](/introduction-to-verification/build/read-a-verification-result): the complete result schema, the evidence at each tier, and what to do with every negative outcome.

See also: [Assurance Tiers](/introduction-to-verification/learn/assurance-tiers) · [Authentication](/introduction/authentication) · [API Reference](https://dev.blerify.com)


# Read a Verification Result

This page covers everything that comes back from a verification: the complete result at each assurance tier, what every evidence field means, and how to handle every negative outcome. It assumes you can already start a session and poll for a result. If you haven't done that yet, start with [Get Started with a Verification](/introduction-to-verification/build/get-started).

## Prerequisites

* Completed [Get Started with a Verification](/introduction-to-verification/build/get-started)
* A verification rule set up with the assurance tier you want to use. See [Portal Roles and Permissions](/introduction/portal-roles-and-permissions) for who can create rules

## The two parts of every credential entry

[Get Started](/introduction-to-verification/build/get-started#step-4--poll-for-the-result) shows a complete Basic-tier result. Each entry in its `credentials[]` array has two parts, and everything on this page hangs off them:

* **`result`**: the facts about that specific credential. Was the signature valid, is the issuer in your trust list, has the credential been revoked or expired, and does the person presenting it actually hold the signing key (`holder_binding`). The top-level `credential_valid` is only `true` when every one of those checks passes.
* **`data`**: the fields the user agreed to share, grouped by namespace. The `namespaces` structure follows the ISO 18013-5 mobile document format. Most identity fields live under `org.iso.18013.5.1`.

One principle governs everything on this page: **`COMPLETED` means the verification ran, not that the credential passed your bar.** A revoked credential still comes back `COMPLETED` with `revoked: true` inside `result` — the policy call is yours, and [Negative outcomes](#negative-outcomes) below covers what to do with each flag. `FAILED` is reserved for verifications that couldn't reach a trustworthy verdict at all — see [Process errors](#process-errors).

### `assurance_level` and `effective_tier`

The response includes both `assurance_level` (what your rule asked for) and `effective_tier` (kept for backward compatibility). On a `COMPLETED` result they always match — if evidence your rule requires had been missing, the session would have finalized as `FAILED` with `reason: "required_fields_missing"` instead of completing at a lower tier. Read either field on success; there is no downgraded state to detect.

### Standard-tier evidence

Standard adds several fields inside `evidence`. Of these, **`document_render` is the item your rule requires** — the others are included when the wallet can provide them, and arrive as `null` with a `_reason` otherwise.

**Network signals:**

* **`wallet_ip`**: the IP Blerify saw when the wallet connected.
* **`geolocation`**: an approximate location derived from IP. Treat it as one input into your risk decision, not as proof of where someone actually is.
* **`client_reported_ip`**, **`ip_match`**, and the `client_reported` half of `geolocation`: populated only when a session IP was reported at session creation. The session request currently accepts only `nonce` and `expirationTime`, so expect these to be absent. When both sides are present, `ip_match` says whether the IPs agree — a mismatch isn't necessarily fraud (NAT, VPNs, and mobile carriers route traffic through different IPs), but it's a useful signal.

**Device metadata:**

* **`device_metadata`**: the phone model and OS version. On Android, these values come from the hardware attestation certificate and are signed by the hardware itself (`attested: true`). On iOS, they're self-reported by the wallet, because Apple's attestation format doesn't expose device metadata to third-party apps (`attested: false`, `attestation_source: "wallet_self_reported"`).

**Key attestation:**

* **`key_attestation`**: proof that the credential's signing key lives in the phone's secure hardware (TEE or StrongBox on Android, Secure Enclave on iOS), and that a biometric was required to use it. The nested `key_biometric_binding` object gives you the details on that binding.

On Android, these properties are signed by the hardware itself: `security_level_attested: true` and `key_biometric_binding.attested: true`. On iOS, Apple's attestation format doesn't expose key access-control flags to third-party apps, so those fields are self-reported instead: `security_level_attested: false` and `key_biometric_binding.attested: false`. The Secure Enclave still enforces the biometric requirement locally. It just can't be proven remotely. See [Platform Attestation](/introduction-to-verification/learn/platform-attestation) for the full comparison and how to handle that difference in your risk policy.

**Document render:**

* **`document_render`**: images of the credential, generated by the wallet. Disclosed fields are legible, undisclosed fields are blurred. One image per side of the document. Use this for a human to review, for dispute resolution, or for customer service. It's a helpful complement to the machine-readable `credentials[].data`, not a replacement for it.

**Signed evidence:**

* **`signed_evidence`**: a JWT signed by Blerify with the verification outcome and evidence inside. It ships on every terminal response — `COMPLETED` at any tier, and `FAILED` — not just at Standard. Store it as your tamper-proof audit record: Blerify doesn't keep it by default after the session expires. Check the signature against Blerify's public key at the JWKS endpoint described in the [API reference](https://dev.blerify.com).

```json
{
  "status": "COMPLETED",
  "assurance_level": "STANDARD",
  "effective_tier": "STANDARD",
  "credentials": ["..."],
  "evidence": {
    "timestamp": "2026-04-21T12:00:03Z",
    "client_reported_ip": "203.0.113.42",
    "client_session_id": "sess_b48c2",
    "wallet_ip": "198.51.100.17",
    "ip_match": false,
    "geolocation": {
      "wallet": {
        "country": "SV", "region": "San Salvador", "city": "San Salvador", "accuracy": "city"
      },
      "client_reported": {
        "country": "SV", "region": "La Libertad", "city": "Santa Tecla", "accuracy": "city"
      },
      "country_match": true,
      "source": "ip_geolocation"
    },
    "device_metadata": {
      "device_model": "Pixel 8",
      "os_version": "Android 15 (patch 2026-03-05)",
      "attested": true,
      "attestation_source": "android_key_attestation"
    },
    "key_attestation": {
      "verified": true,
      "hardware_backed": true,
      "security_level": "STRONG_BOX",
      "security_level_attested": true,
      "platform": "android",
      "format": "android_key_attestation",
      "key_biometric_binding": {
        "biometric_required": true,
        "auth_type": ["FINGERPRINT", "FACE"],
        "auth_timeout_seconds": 0,
        "invalidated_on_enrollment_change": true,
        "attested": true
      }
    },
    "key_attestation_reason": null,
    "document_render": {
      "format": "image/jpeg",
      "redaction": "blur",
      "images": [
        { "side": "front", "data": "<base64 JPEG>" },
        { "side": "back", "data": "<base64 JPEG>" }
      ]
    },
    "document_render_reason": null,
    "device_attestation": null,
    "device_attestation_reason": "not_requested",
    "liveness_verification": null,
    "liveness_verification_reason": "not_requested",
    "coercion_check": null,
    "coercion_check_reason": "not_requested"
  },
  "signed_evidence": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImJsZXJpZnkta2V5LTIwMjYtMDQifQ..."
}
```

> **`device_attestation_reason` is `"not_requested"` here, not `"platform_not_available"`**. Standard never asks for device attestation on either platform, so this value just reflects your template setup, not a platform limitation. `"platform_not_available"` only shows up at Premium on iOS, where device attestation is requested but Apple doesn't give third-party apps an equivalent API.

### Standard-tier evidence — iOS

On iOS, three fields look different from an Android Standard result:

* `device_metadata.attested` is `false`, because Apple App Attest doesn't include device model or OS version
* `key_attestation.security_level_attested` is `false`, because Apple doesn't give third-party apps a way to cryptographically prove Secure Enclave key residency
* `key_biometric_binding.attested` is `false`, because App Attest doesn't carry key access-control flags at all

None of this means something failed or got downgraded. It just reflects a real difference in what Apple exposes to third-party developers. Everything else, `key_attestation.verified`, `hardware_backed`, `key_biometric_binding.biometric_required`, behaves the same as on Android. See [Platform Attestation](/introduction-to-verification/learn/platform-attestation) for the full breakdown and what policy to apply.

### Premium-tier evidence (coming soon)

Premium is coming soon. This section documents the three evidence fields a Premium rule returns once it's available, so you can design your policy ahead of it.

**Device attestation:**

* **`device_attestation`**: proof that the wallet app is genuine and the device hasn't been modified. On Android this uses Play Integrity: `device_integrity: true` means the bootloader is locked, the OS is certified, and it's not an emulator. On iOS this field is always `null`, with `device_attestation_reason: "platform_not_available"`, because Apple doesn't offer an equivalent for third-party apps. An iOS Premium result still includes liveness and the coercion check. The Premium boost on iOS comes entirely from the biometric layer.

**Liveness verification:**

* **`liveness_verification`**: the wallet captures a liveness challenge during the session, and Blerify compares it against the portrait in the credential, server-side. You never see the raw biometric data. Blerify processes the captured frames and the credential's portrait briefly and then deletes both right after comparing them. You only get back the boolean result and a confidence score.

**Coercion check:**

* **`coercion_check`**: a silent check of gaze patterns and micro-expressions during the liveness frames, looking for signs of distress (`alert: true/false`). It runs on the same camera frames already captured for liveness, so neither the user nor anyone coercing them sees anything different from the standard liveness challenge. Blerify never blocks a flow based on this signal. It just reports the fact, and you decide how to respond according to your own protocols.

```json
{
  "device_attestation": {
    "verified": true,
    "platform": "android",
    "format": "play_integrity",
    "device_integrity": true,
    "app_integrity": true,
    "nonce_match": true
  },
  "device_attestation_reason": null,
  "liveness_verification": {
    "verified": true,
    "match_confidence": 0.97,
    "spoof_detected": false,
    "challenge_completed": true,
    "provider": "facetec"
  },
  "liveness_verification_reason": null,
  "coercion_check": {
    "status": "CLEAR",
    "method": "facial",
    "alert": false
  },
  "coercion_check_reason": null
}
```

On iOS at Premium, `device_attestation` is always `null`:

```json
{
  "device_attestation": null,
  "device_attestation_reason": "platform_not_available"
}
```

## Interpreting `_reason` fields

Every evidence field that can be missing has a matching `_reason` field. When the main field has a value, `_reason` is `null`. When the main field is `null`, `_reason` tells you why.

One thing follows from [When evidence is missing](#when-evidence-is-missing), and it simplifies your handling: **a `_reason` only ever appears on a `COMPLETED` result.** If something your rule strictly requires never arrives, the session fails with `required_fields_missing` — you never have to hunt through the evidence to find out whether the verification is trustworthy. So these values explain gaps the verification was allowed to complete with: items your rule treats as optional, and evidence the platform structurally can't produce (like device attestation on iOS).

| Value                         | What it means                                                                                             | How to treat it                                                                 |
| ----------------------------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| `null`                        | The field has a value, nothing to explain                                                                 | N/A                                                                             |
| `"not_requested"`             | Your rule didn't ask for this tier or feature                                                             | Expected, not a problem                                                         |
| `"platform_not_available"`    | The platform has no API for this (for example, iOS has no Play Integrity equivalent for third-party apps) | A structural limit. Accept it, or apply a platform-specific policy              |
| `"wallet_not_supported"`      | The wallet doesn't support Blerify's extensions                                                           | Decide whether the missing optional signal matters for this operation           |
| `"user_declined"`             | The user denied a permission behind an optional item (the camera, the biometric prompt)                   | An active refusal, a different kind of risk signal than a platform limit        |
| `"failed"`                    | The check ran but rejected the data (an invalid token, a detected spoof)                                  | Treat as a strong fraud signal                                                  |
| `"attestation_not_supported"` | An older Android device where the key is hardware-backed, but cryptographic attestation isn't available   | Usually just an older, legitimate device. Your policy decides whether to accept |

The difference between `"platform_not_available"` and `"user_declined"` matters a lot. A device that structurally can't produce a signal is a different kind of risk than a user who actively chose not to give one. Handle them separately in your policy.

## Negative outcomes

A credential with a problem still comes back as `COMPLETED`, with the issue flagged, not as `FAILED`. Here's what each flag means and what to typically do about it:

| Flag                                         | What it means                                                                                                | Typical policy                                                               |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- |
| `revoked: true`                              | The issuer revoked this credential (also sets `credential_valid: false`). `data` is still there.             | Hard reject                                                                  |
| `expired: true`                              | The credential's validity period is over (also sets `credential_valid: false`). `data` is still there.       | Hard reject; prompt the user to renew                                        |
| `issuer_trusted: false`                      | The issuer isn't in your template's trust list (also sets `credential_valid: false`). `data` is still there. | Reject; check whether you should add the issuer in your Portal configuration |
| `signature_valid: false`                     | The credential's signature doesn't check out, so it was tampered with or forged. `data` is left out.         | Hard reject; consider flagging it for review                                 |
| `holder_binding: "FAILED"`                   | The person presenting it couldn't prove they hold the signing key. `data` is left out.                       | Hard reject                                                                  |
| `liveness_verification.spoof_detected: true` | Someone presented a photo, video, or mask instead of a live face                                             | Hard reject; log it for review                                               |
| `coercion_check.alert: true`                 | Gaze or micro-expression analysis picked up possible signs of distress                                       | Apply your internal protocols. Don't show this signal to the user            |

You make the call here. Blerify reports facts, and your code applies the policy.

**When `data` is left out.** If `signature_valid` is `false` or `holder_binding` is `"FAILED"`, that credential's `data` field comes back as `null`. Blerify leaves out field values it can't guarantee haven't been tampered with. A revoked or expired credential still returns `data`, since the content itself is genuine even if the credential's status isn't.

## Process errors <a href="#process-errors" id="process-errors"></a>

`FAILED` means Blerify couldn't reach a trustworthy verdict. It's never used for issues with the credential itself (a revoked or expired credential still comes back as `COMPLETED`, with the fact flagged).

**A `FAILED` response arrives with HTTP `200`.** The poll call itself succeeded — it's the verification that failed. Don't branch on the HTTP status to detect it; read `status` in the body, which carries a top-level `reason` and a human-readable `message`:

```json
{
  "status": "FAILED",
  "transaction_id": "A6Qmx2Ivtp-Mx6MbsRngib7MnCjVNUou",
  "reason": "required_fields_missing",
  "message": "The user did not submit the required fields: document_render"
}
```

* **`required_fields_missing`**: a piece of evidence your rule requires never showed up. The wallet may have submitted the credential but skipped something the rule needed — for example, a third-party wallet that doesn't implement Blerify's extension protocol. The `message` lists what was missing.
* **`share_timeout`**: the required data never arrived within the session window (`"Timed out waiting for the user to submit the required data"`). This usually means the wallet crashed mid-flow or the user abandoned after the consent screen. You can't retry the same `transaction_id`; start a new session.

Transport-level problems don't produce a `FAILED` body — they surface as HTTP errors on the call itself:

| Status | What it means                                                                               | What to do                      |
| ------ | ------------------------------------------------------------------------------------------- | ------------------------------- |
| `400`  | A parameter was missing or malformed, or the session isn't in a state that allows this call | Fix the request and try again   |
| `401`  | Your service account token is missing or expired                                            | Renew the token and try again   |
| `403`  | Your account can't access this verification rule or session                                 | Check your Portal configuration |
| `404`  | The rule or `transaction_id` doesn't exist                                                  | Check the IDs for typos         |
| `410`  | The session's time limit passed                                                             | Start a fresh session           |

## When evidence is missing

At Standard and Premium, the wallet sends attestation data alongside the credential presentation through a Blerify-specific extension. Your rule classifies each evidence item as required or optional, and that classification decides the outcome when something doesn't arrive:

* A **required** item that never shows up — including when the user is on a third-party wallet that follows the open credential-exchange standard but doesn't implement Blerify's extensions at all — finalizes the session as `FAILED` with `reason: "required_fields_missing"`. The `message` field lists exactly what was missing. The verification is never silently completed at a lower tier.
* An **optional** item that doesn't arrive fails nothing: the session completes at your configured tier, the corresponding evidence field comes back `null`, and its `_reason` field says why — `"wallet_not_supported"`, for example.

When a required item fails a verification, handle it like any other `FAILED` outcome: ask the user to retry with a compatible wallet, or route them to a fallback process of your own.

## Integration checklist

**Portal (one-time setup — everything else depends on it)**

* [ ] Create a verification rule: pick which credential fields to ask for, which issuers to trust, and which assurance tier to require. See [Create a Verification Rule](/introduction-to-verification/build/create-a-verification-rule)
* [ ] Copy the rule's UUID. That's what you pass when you create sessions

**Backend**

* [ ] Get and securely store the service account token; refresh it before it expires
* [ ] Write a function that starts a session, calling Blerify with a fresh `nonce`
* [ ] Write a polling function that checks every 1–2 seconds, with a timeout (2 minutes for same-device, 5 minutes for cross-device)
* [ ] Write a result handler that goes through `credentials[]`, checks each `result` against your policy, and pulls `data` into your data model
* [ ] Decide your fallback for `FAILED` with `reason: "required_fields_missing"` — retry with a compatible wallet, or a manual process of your own
* [ ] Store `signed_evidence` from every terminal response if you need an audit record
* [ ] Log `transaction_id` at every step, so you can trace it later

**Frontend**

* [ ] Decide how you'll show the wallet link: a button on mobile pages, a QR code on desktop pages, or both. See [Get Started](/introduction-to-verification/build/get-started#step-3--build-the-wallet-link-and-show-the-user-a-prompt)
* [ ] Save `transaction_id` before launching the wallet
* [ ] Show a loading state while polling
* [ ] Handle success, credential failure, and session timeout with clear UX
* [ ] Give the user a "try again" option that starts a fresh session after a timeout or if they abandon the flow

**Operations**

* [ ] Keep your service account token in a secrets manager, not in your source code
* [ ] Set up alerts for a spike in `FAILED` results or repeated `401`/`403` responses
* [ ] Decide how long you'll keep `signed_evidence` based on your compliance needs. Blerify doesn't keep it by default after the session expires

## Next steps

**Continue to** [**Biometric Binding**](/introduction-to-verification/learn/biometric-binding): how evidence gets captured and tied to the person holding the credential, what each tier guarantees about that, and where the limits are.

See also: [Assurance Tiers](/introduction-to-verification/learn/assurance-tiers) · [Platform Attestation](/introduction-to-verification/learn/platform-attestation) · [API Reference](https://dev.blerify.com)


# Use Cases

Complete scenarios, told from the point of view of a team shipping them. Each one shows the decisions that matter — which fields to request, which assurance tier to require, what to do with the result — and links to the [Build guides](/introduction-to-verification/build) for the mechanics. The API calls are the same in every scenario; what changes is the configuration and the policy you apply to the result.

[**Age Verification**](/introduction-to-verification/use-cases/age-verification) — prove a customer is over 18 without collecting a single piece of personal data.

[**Customer Onboarding**](/introduction-to-verification/use-cases/customer-onboarding) — open an account with verified identity data and an audit trail, instead of a photo of an ID card.

If your scenario isn't here, the building blocks almost certainly are: pick your fields and tier in [Create a Verification Rule](/introduction-to-verification/build/create-a-verification-rule), then run it with [Get Started](/introduction-to-verification/build/get-started).


# Age Verification

Prove a customer is over 18 without collecting their name, their birth date, or anything else about them. This scenario walks through the decisions; the mechanics are two backend calls and a QR code, covered in [Get Started](/introduction-to-verification/build/get-started).

## The scenario

GateWise sells tickets online for concerts and festivals. Some events are 18+, and until now the age check happened at the venue door: security looks at an ID card under a flashlight, holds up the queue, and turns people away after they've already paid. Checking age at purchase time would fix that, but the obvious web solutions are all bad. A "confirm you are 18" checkbox proves nothing. Asking for an ID upload turns a ticket purchase into a document-review operation — GateWise would be storing photos of identity documents just to sell a concert ticket, and most customers abandon the flow anyway.

The credential version of this check asks the customer's wallet one question: *is this person over 18?* The wallet answers with a cryptographically signed boolean derived from a government-issued credential. GateWise never sees the birth date it was derived from. There's nothing to store, nothing to review, and nothing worth stealing.

## The decisions

**Data to request: one field.** The customer's credential carries an age-over-18 attribute; the rule requests that boolean and nothing else. This matters beyond politeness — the consent screen shows the user exactly what's being shared, and "Age over 18: yes" is a request people accept without hesitating. A rule that asks for name and birth date to answer an 18+ question will lose customers at the consent screen, and it deserves to.

**Assurance tier: Basic.** Think about the failure you're defending against: a teenager using an older sibling's credential. Basic already verifies the credential is genuine, issued by the authority you trust, not revoked, and presented by someone holding its signing key — from a wallet that was unlocked with the holder's biometric. That's a much higher bar than the flashlight at the door. Standard and Premium exist for stolen-identity and coerced-transaction risks; a ticket sale has neither, and paying the extra friction buys nothing here.

**Rule type: Access control.** One rule, reused across every 18+ event. When the drinking-age policy for an event category changes, the rule changes in the Portal — the checkout code doesn't.

## The flow

At checkout for an 18+ event, GateWise's backend starts a verification session and the page shows the result of it:

1. On desktop, the checkout page renders a QR code; the customer scans it with their wallet. On mobile, the page shows a **Verify with Blerify Wallet** button instead — same link, no QR.
2. The wallet shows what's being requested — the single age attribute — and the customer approves with their biometric.
3. The backend, polling for the result, receives `COMPLETED` with the boolean inside, and checkout continues. The whole exchange typically takes a few seconds.

The session expires if the customer walks away (5 minutes by default), so the checkout page keeps its own shorter timer and offers a "try again" button that starts a fresh session. [Get Started](/introduction-to-verification/build/get-started) has the exact calls; [Read a Verification Result](/introduction-to-verification/build/read-a-verification-result) covers every field in the response.

## The policy

The result-handling code is one branch. If `credential_valid` is `true` and the age attribute is `true`, sell the ticket. Anything else — revoked credential, untrusted issuer, attribute `false` — falls through to the same place: no ticket, with a message pointing the customer to the venue's door-check option as a fallback.

One subtlety worth getting right: a revoked or expired credential comes back as `COMPLETED`, not as an error — the flags live inside the result and your code decides. GateWise treats them all as "not verified" because there's no reason to distinguish; a bank might not.

## What GateWise never has to do

No document uploads. No review queue. No stored birth dates or ID photos, which also means no personal-data breach surface for a company whose business is selling tickets. The age check became a boolean in a checkout flow.

## Next steps

[**Get Started**](/introduction-to-verification/build/get-started) — run this exact flow against your own rule in four steps.

See also: [Create a Verification Rule](/introduction-to-verification/build/create-a-verification-rule) · [Assurance Tiers](/introduction-to-verification/learn/assurance-tiers)


# Customer Onboarding

Open an account with verified identity data and a tamper-proof audit record, instead of a photo of an ID card and a manual review queue. This scenario covers the decisions; the calls themselves are in [Get Started](/introduction-to-verification/build/get-started).

## The scenario

NovaBanc is a digital bank. Its account-opening flow used to be the industry standard: the applicant photographs the front and back of their ID, takes a selfie, and waits. Behind the scenes, an OCR vendor extracts the fields (with errors), a human compares the selfie to the document photo (inconsistently), and compliance stores copies of identity documents it now has to protect. Onboarding took anywhere from minutes to a day, and the fraud that mattered — stolen identities assembled from leaked document photos — got through anyway, because a photo of a real document is exactly what a fraudster has.

Verifying a credential instead changes what the applicant presents: not a picture of a document, but the document's data signed by the issuing authority, presented from a wallet that requires the holder's biometric to approve the presentation. The fields arrive as data, not as OCR guesses. And the question "is this a real document?" is answered cryptographically instead of by a reviewer squinting at a JPEG.

## The decisions

**Data to request: the account-opening set.** Family name, given name, date of birth, document number, and the portrait. That's what the bank's KYC policy actually requires — the consent screen shows each field to the applicant, and each one is defensible. The portrait deserves a note: it arrives as credential data signed by the issuer, which makes it a reference image the bank can actually trust, unlike a selfie.

**Assurance tier: Standard.** The threat model for account opening is impersonation — someone opening an account with another person's identity. Standard raises exactly that bar: the rule requires the credential images (`document_render`) for audit and manual review, and the evidence carries what the wallet can additionally provide — hardware key attestation (the credential's signing key lives in the phone's secure hardware and required a biometric to use) and network signals — plus `signed_evidence`, a JWT signed by Blerify that the bank stores as its audit record for the regulator. Basic would verify the credential but leave the "is the right person holding this phone?" question weaker than a KYC file should be. Premium — live face matching and device integrity, coming soon — is more than an account opening needs anyway; when it ships, NovaBanc plans to use it for loan applications, where the cost of impersonation is not an empty account but real money out the door. Same integration, different rule.

**Rule type: Enrollment.** One rule for account opening, a separate Premium rule for credit products. The backend picks which rule to reference per operation; nothing else in the integration changes.

## The flow

The applicant reaches the identity step of onboarding — on NovaBanc's mobile web page, since account openings overwhelmingly happen on phones. The backend starts a session, the page shows the wallet button, the applicant approves the request in their wallet with their biometric, and the backend's polling returns the result. A Standard verification typically completes in five to fifteen seconds — worth remembering when you design the waiting state, because it replaces a review step that used to take hours.

## The policy

NovaBanc's result handler works through the response in order:

* **`credential_valid` false** — revoked, expired, or untrusted issuer. The application stops, and the specific flag decides the message: an expired document gets "please renew your ID", a revoked one gets a manual-review ticket.
* **`FAILED` with `reason: "required_fields_missing"`** — the applicant's wallet couldn't provide the attestation evidence the rule requires, usually a third-party wallet without Blerify's extensions. Nothing was silently accepted at a lower level. NovaBanc routes these applicants to the old manual flow instead of losing them: a real customer with the wrong wallet is still a customer.
* **An adverse network signal in the evidence** — the wallet's IP geolocation far from where the applicant claims to live, for instance. Not a rejection on its own; carrier networks and VPNs produce odd routes for legitimate users. It feeds the bank's existing risk scoring as one signal among several.
* **Everything clean** — the account opens with the verified fields written straight into the customer record, and `signed_evidence` archived next to it. No OCR corrections, no document images to protect.

The general principle behind all four branches: Blerify reports facts and the bank applies policy. The API never decides "good enough for KYC" — that call, and the responsibility for it, stays with the bank. [Read a Verification Result](/introduction-to-verification/build/read-a-verification-result) documents every field and every negative outcome.

## Next steps

[**Create a Verification Rule**](/introduction-to-verification/build/create-a-verification-rule) — build the Enrollment rule this scenario describes, field by field.

See also: [Get Started](/introduction-to-verification/build/get-started) · [Assurance Tiers](/introduction-to-verification/learn/assurance-tiers) · [Biometric Binding](/introduction-to-verification/learn/biometric-binding)


# Learn

The Build guides show you how; these pages explain what you're actually getting. Read them when you're deciding which assurance tier to require, when a result contains a field you didn't expect, or when your compliance team asks what "verified" means here.

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Assurance Tiers</strong></td><td>What Basic, Standard, and Premium each prove, and how to choose. Start here.</td><td><a href="/introduction-to-verification/learn/assurance-tiers">Assurance Tiers</a></td></tr><tr><td><strong>Biometric Binding</strong></td><td>The chain of evidence connecting the person in front of you to the person the credential was issued to.</td><td><a href="/introduction-to-verification/learn/biometric-binding">Biometric Binding</a></td></tr><tr><td><strong>Platform Attestation</strong></td><td>Why Android and iOS give you different evidence, and how to write a policy that handles both.</td><td><a href="/introduction-to-verification/learn/platform-attestation">Platform Attestation</a></td></tr><tr><td><strong>Verifier Trust</strong></td><td>How the user's wallet knows your verification request is really from you.</td><td><a href="/introduction-to-verification/learn/verifier-trust">Verifier Trust</a></td></tr></tbody></table>

## Next steps

[**Assurance Tiers**](/introduction-to-verification/learn/assurance-tiers) — the concept every other page in this section leans on.

See also: [Get Started](/introduction-to-verification/build/get-started) — when you're ready to stop reading and run one.


# Assurance Tiers

Blerify evaluates every credential presentation against one of three assurance tiers — Basic, Standard, or Premium. This page explains what each tier proves, how the evidence is collected, and how platform differences affect what's achievable on Android versus iOS.

***

## What the tiers are

Blerify's tiers are Blerify-specific definitions. They are not claims of formal equivalence with eIDAS levels or NIST Authenticator Assurance Levels, though the [mapping to those frameworks](#relationship-to-eidas-and-nist) is informative. The names Basic, Standard, and Premium were chosen deliberately to avoid implying certifications that consumer smartphones don't currently hold end-to-end.

Each tier represents what a verifier can **independently confirm** from the evidence included in the presentation — not what the wallet does internally. A third-party wallet may use hardware-backed key storage and enforce biometric authentication, but if it doesn't include the attestation evidence, the verifier has no way to confirm it. The tier reflects the provable guarantee, not the internal security posture.

***

## The three tiers

### Basic

Basic validates that the credential is cryptographically intact, currently valid, and was presented by the device that holds the bound key.

What Basic proves:

| Property                       | How it's proven                                                                      |
| ------------------------------ | ------------------------------------------------------------------------------------ |
| Credential not tampered        | Issuer signature over the credential data                                            |
| Credential not revoked         | Revocation check against the trust registry                                          |
| Session is fresh               | Nonce binding — the response is tied to this specific request                        |
| Presenter holds the device key | Device authentication — the wallet signs a challenge with the credential's bound key |

What Basic does not prove: whether the device key lives in tamper-resistant hardware, whether the device running the wallet is genuine, or whether the person presenting the credential is the person it was issued to.

Any wallet that implements OpenID4VP — the open standard wallets and verifiers use to exchange credential presentations — can achieve Basic. Third-party wallets that don't include Blerify's attestation extensions are classified as Basic regardless of their internal security architecture.

**Typical use cases:** low-risk credential checks, age verification, access control, third-party wallet integrations.

***

### Standard

Standard builds on Basic by adding cryptographic proof that the credential's signing key was created inside tamper-resistant hardware — a Trusted Execution Environment (TEE) or StrongBox on Android, or the Secure Enclave on iOS — and by collecting a signed audit evidence package.

Additional properties Standard proves:

| Property                                       | How it's proven                                                                                                |
| ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| Key resides in tamper-resistant hardware       | Key attestation included in the presentation; the verifier validates the certificate chain independently       |
| Biometric authentication required to sign      | Attested in the key attestation certificate chain (Android); self-reported by the wallet (iOS)                 |
| Key invalidated if new biometrics are enrolled | Attested in the key attestation certificate chain (Android); self-reported by the wallet (iOS)                 |
| Audit trail with session correlation           | `signed_evidence` JWT signed by Blerify, containing IPs, geolocation, device metadata, and all evidence fields |

On **Android**, key attestation is a hardware-signed X.509 certificate chain. The chain records where the key lives (`TRUSTED_ENVIRONMENT` or `STRONG_BOX`), whether biometric authentication is required to use it (`auth_timeout_seconds: 0` means biometrics are required on every signing operation with no reuse window), whether the key is destroyed if new biometrics are enrolled on the device (`invalidated_on_enrollment_change: true`), and which authentication types are accepted. This chain is generated at key-creation time by the hardware and is static — the key's location and access controls can't change after the key is created.

On **iOS**, the equivalent is an Apple App Attest attestation. App Attest proves that a genuine, unmodified instance of the registered wallet app on real Apple hardware possesses the signing key. It does not cryptographically prove Secure Enclave residency or biometric binding the same way Android's certificate chain does — those properties are self-reported by the wallet. See [Platform Attestation](/introduction-to-verification/learn/platform-attestation) for the full comparison.

Standard also includes a telemetry package automatically assembled by Blerify: the wallet's IP address captured when it submits attestation data, the verifier's session IP if provided when initiating the session, an IP correlation flag, server-side geolocation of both IPs with a country-match flag, device model and OS version, and a document render — an image of the credential with only the disclosed fields legible and everything else blurred, for audit and manual review. All of this travels in `signed_evidence`, a JWT signed by Blerify that any third party can verify against Blerify's public key.

None of this adds friction for the user. The wallet submits attestation data on a background channel; Blerify captures IPs and geolocation automatically; the signed JWT is emitted when the session completes. The user sees the same consent screen and biometric prompt as in Basic.

Standard is only achievable with a Blerify wallet that implements the attestation extensions.

**Typical use cases:** identity verification, KYC, account opening, standard banking operations, government service integrations.

***

### Premium (coming soon)

Premium builds on Standard by adding device integrity verification and server-side biometric verification. It answers a question the lower tiers cannot: is the person presenting this credential the same person the credential was issued to? Premium is coming soon — the properties below describe what it proves once available.

Additional properties Premium proves:

| Property                            | How it's proven                                                                                                    |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| Device and app are genuine          | Device attestation (Play Integrity on Android)                                                                     |
| Presenter is the credential subject | Server-side liveness — anti-spoofing analysis plus face comparison against the portrait embedded in the credential |
| Anti-spoofing                       | Passive and active attack detection: photo, video replay, deepfake, 3D mask                                        |

The liveness check runs on Blerify's infrastructure, not on the device. This produces independent evidence — the result comes from a party the verifier can audit, not from the user's own device. It also means server-side anti-spoofing catches sophisticated attacks that on-device checks miss.

The liveness data is processed and immediately deleted. Blerify retains only the boolean result and a match confidence score. No biometric templates are stored.

Premium also optionally provides an anti-coercion signal derived from the same camera frames captured during the liveness challenge. Gaze tracking and micro-expression analysis run silently alongside the liveness check, invisible to both the user and any potential coercer. If anomalies are detected, they're reported in the result — the flow is never interrupted and no error is shown to the user. Your backend decides how to act on the signal.

**iOS and device attestation:** iOS has no equivalent of Android's Play Integrity API for third-party apps. On iOS, `device_attestation` is always `null` with `device_attestation_reason: "platform_not_available"`. This is a structural platform limitation, not a failure. The Premium delta on iOS comes entirely from the liveness layer, which works identically on both platforms.

Premium requires a Blerify wallet.

**Typical use cases:** high-value banking operations (loan applications, contract signing), legal proceedings, regulated processes that require independent identity re-proofing.

***

## Choosing a tier

The right tier depends on the consequence of being wrong.

**Use Basic when** you need to confirm a credential is valid and the presenter holds it, and the downside of a false positive is limited. Any standards-compatible wallet works.

**Use Standard when** you need confidence that the credential is bound to real hardware and authorized by its holder's biometric. Appropriate for KYC, account opening, and most government and financial service integrations. The additional evidence is collected transparently — no extra friction for the user.

**Use Premium when** you need to independently confirm the person physically present is the credential subject (coming soon). Suited to high-value operations where the cost of identity fraud is significant: loans, contract signing, legal proceedings. Adds a liveness step of a few seconds.

You can run different tiers for different operations within the same product. A bank might verify at Standard for standard transfers and require Premium for loan applications.

**Decision shorthand.** Ask what the consequence of being wrong is:

* *A teenager bypassing your age gate with an older sibling's credential* → Basic is enough.
* *An attacker opens an account in a stolen identity's name* → Standard raises the bar to needing physical access to the real person's phone and their biometrics.
* *An attacker signs a high-value contract as someone else* → Premium adds liveness, making this essentially impossible without the real person physically present.

***

## Evidence fields

The verification response includes an `evidence` object alongside the credential claims. Each field tells you what was checked and what was confirmed. For the full field-level schema and request/response examples, see the [API reference](https://dev.blerify.com).

### Basic

| Field       | Source  | Description                               |
| ----------- | ------- | ----------------------------------------- |
| `timestamp` | Blerify | Exact time the presentation was processed |

### Standard (includes Basic)

| Field                      | Source                                   | Description                                                                                                                                                                                                                  |
| -------------------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `wallet_ip`                | Blerify                                  | IP address of the wallet's connection to Blerify when submitting attestation data                                                                                                                                            |
| `client_reported_ip`       | Verifier                                 | IP of the user's web session, when one was reported at session creation; absent otherwise                                                                                                                                    |
| `ip_match`                 | Blerify                                  | Whether `wallet_ip` and `client_reported_ip` match, when both are present. A mismatch isn't necessarily suspicious (NAT, VPN, mobile carrier) but is a useful audit signal                                                   |
| `geolocation`              | Blerify                                  | Server-side IP geolocation for both IPs: country, region, and city per IP, plus a country-match flag. A risk signal, not location proof — defeatable with a VPN                                                              |
| `device_model`             | Key attestation (Android) / wallet (iOS) | Device model. Attested by hardware on Android; self-reported on iOS                                                                                                                                                          |
| `os_version`               | Key attestation (Android) / wallet (iOS) | OS version and patch level. Attested by hardware on Android; self-reported on iOS                                                                                                                                            |
| `device_metadata.attested` | Blerify                                  | `true` when device model and OS version come from the hardware-attested certificate chain; `false` on iOS                                                                                                                    |
| `key_attestation`          | Wallet + Blerify                         | Whether the signing key resides in tamper-resistant hardware and how it's protected. See [Key attestation fields](#key-attestation-fields)                                                                                   |
| `document_render`          | Wallet                                   | Image of the credential rendered by the wallet with disclosed fields legible and all others blurred. Intended for audit and manual review                                                                                    |
| `signed_evidence`          | Blerify                                  | JWT signed by Blerify containing the full result and all evidence fields. Verifiable by any third party against Blerify's public key. Blerify does not retain this by default beyond the session TTL — you are the custodian |

Of these, **`document_render` is the item a Standard rule requires** — its absence fails the verification with `required_fields_missing`. The other fields are collected when the wallet can provide them and arrive as `null` with a `_reason` otherwise.

### Premium (includes Basic and Standard) — coming soon

| Field                   | Source                                         | Description                                                                                                                                                                                                                                                |
| ----------------------- | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `device_attestation`    | Play Integrity (Android) / not available (iOS) | Device and app integrity. Confirms the device runs a genuine manufacturer image, has a locked bootloader, runs the legitimate app binary, and has recent security patches. Always `null` on iOS with `device_attestation_reason: "platform_not_available"` |
| `liveness_verification` | Blerify                                        | Server-side liveness result: `verified` (boolean), `match_confidence` (0–1), `spoof_detected` (boolean)                                                                                                                                                    |
| `coercion_check`        | Wallet + Blerify                               | Anti-coercion analysis from the liveness session: `status` (`CLEAR`, `GAZE_ANOMALY`, or `EXPRESSION_ANOMALY`), `method` (`"facial"`), `alert` (boolean convenience flag). Produced silently; never interrupts the flow                                     |

### Key attestation fields

The `key_attestation` object and its `key_biometric_binding` sub-object describe the hardware-level key guarantees:

| Field                                                    | Android                                                                                | iOS                                           |
| -------------------------------------------------------- | -------------------------------------------------------------------------------------- | --------------------------------------------- |
| `security_level`                                         | `STRONG_BOX` or `TRUSTED_ENVIRONMENT`, extracted from the hardware-signed certificate  | `SECURE_ENCLAVE`, self-reported by the wallet |
| `security_level_attested`                                | `true`                                                                                 | `false`                                       |
| `key_biometric_binding.biometric_required`               | Attested — extracted from the key attestation certificate                              | Self-reported by the wallet                   |
| `key_biometric_binding.auth_type`                        | Attested — e.g. `FINGERPRINT`, `FACE`, or `DEVICE_CREDENTIAL`                          | Self-reported by the wallet                   |
| `key_biometric_binding.auth_timeout_seconds`             | Attested — `0` means biometrics required on every operation, no reuse window           | Self-reported by the wallet                   |
| `key_biometric_binding.invalidated_on_enrollment_change` | Attested — `true` means the key is destroyed if new biometrics are added to the device | Self-reported by the wallet                   |
| `key_biometric_binding.attested`                         | `true`                                                                                 | `false` (always)                              |

The `auth_type` and `biometric_required` fields reflect the authentication factor bound to the key at creation time. On devices without a strong biometric enrolled, the key is bound to the device passcode or PIN — `auth_type` will be `["DEVICE_CREDENTIAL"]` and `biometric_required` will be `false`. This is accurate, not a downgrade: the key is still in hardware and requires user authentication on every signing operation.

### StrongBox versus TEE on Android

Android devices report either `STRONG_BOX` or `TRUSTED_ENVIRONMENT` as the `security_level`. Both are accepted at Standard and Premium.

A TEE (Trusted Execution Environment) is a hardware-isolated execution environment on the same processor, present on virtually all Android devices since 2016. A StrongBox is a dedicated tamper-resistant security processor with its own CPU and storage — present on many current flagships but not universally available on mid-range hardware. StrongBox offers stronger isolation guarantees than a TEE.

Both security levels appear in `evidence.key_attestation.security_level` so you can apply your own acceptance policy if a specific use case demands StrongBox. For most integrations, accepting both is the right default.

### Absence reasons

When an optional evidence field is absent, a companion `_reason` field explains why. The reason field is always present — it's `null` when the evidence field itself is populated.

| Value                         | Meaning                                                                                                                                                 |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `null`                        | The evidence field is populated — no absence to explain                                                                                                 |
| `"platform_not_available"`    | The platform has no API for this capability (e.g., `device_attestation` on iOS)                                                                         |
| `"wallet_not_supported"`      | The wallet doesn't implement the Blerify extension                                                                                                      |
| `"not_requested"`             | The verifier didn't configure this tier or feature                                                                                                      |
| `"user_declined"`             | The user denied a required permission (e.g., camera for liveness)                                                                                       |
| `"failed"`                    | The check was attempted but rejected (e.g., spoof detected, integrity check failed)                                                                     |
| `"attestation_not_supported"` | The device has a hardware key store but predates the Key Attestation API — the key is likely hardware-backed but this can't be cryptographically proven |

The distinction between `"platform_not_available"` and `"user_declined"` matters for policy decisions. A user who refuses liveness is a different risk signal than a platform that structurally can't provide device attestation. Your verification policy can treat them differently.

***

## Platform attestation asymmetry

Android and iOS provide fundamentally different attestation capabilities. They are not equivalent mechanisms with minor differences — the underlying architectures differ.

### Key attestation

On **Android**, the key attestation certificate chain is generated by the hardware at key-creation time and signed by the TEE or StrongBox. Key properties including biometric requirements, authentication timeout, and invalidation behavior are all embedded in the signed certificate.

On **iOS**, there is no Apple API that produces an equivalent certificate for Secure Enclave keys in third-party apps. App Attest proves that a genuine, unmodified instance of the registered wallet app on real Apple hardware created and holds the key. Key properties like biometric binding are self-reported by the wallet. The `key_biometric_binding.attested` field is always `false` on iOS.

This does not mean iOS keys are less secure. The Secure Enclave enforces the access controls the wallet configured — including requiring Face ID or Touch ID for every signing operation and invalidating the key when biometric enrollment changes. The limitation is that these controls cannot be independently verified remotely. Apple has not provided a public API for this in third-party apps.

### Device attestation

Android provides Play Integrity, which independently verifies that the device uses a certified manufacturer image, has a locked bootloader, runs the legitimate app binary, and has recent security patches applied.

iOS has no equivalent API for third-party apps. `device_attestation` is always `null` on iOS with `_reason: "platform_not_available"`. This is a structural platform limitation, not a failure.

### Summary by tier and platform

| Evidence                            | Basic | Standard Android | Standard iOS | Premium Android | Premium iOS     |
| ----------------------------------- | ----- | ---------------- | ------------ | --------------- | --------------- |
| Credential valid                    | ✅     | ✅                | ✅            | ✅               | ✅               |
| Holder binding                      | ✅     | ✅                | ✅            | ✅               | ✅               |
| Key in hardware (attested)          | —     | ✅                | —            | ✅               | —               |
| Key in hardware (self-reported)     | —     | —                | ✅            | —               | ✅               |
| Biometric binding (attested)        | —     | ✅                | —            | ✅               | —               |
| Biometric binding (self-reported)   | —     | —                | ✅            | —               | ✅               |
| Audit telemetry + `signed_evidence` | —     | ✅                | ✅            | ✅               | ✅               |
| Device integrity (Play Integrity)   | —     | —                | —            | ✅               | ❌ not available |
| Liveness + face match               | —     | —                | —            | ✅               | ✅               |

### Recommended policy for iOS Standard

Accept Standard on iOS the same way you accept it on Android. An iOS device with `security_level_attested: false` and `key_biometric_binding.attested: false` is not a downgrade or a fraud signal — it reflects Apple's architectural choice not to expose an equivalent hardware attestation API to third-party developers. The Secure Enclave still enforces biometric requirements on every signing operation.

If your use case requires hardware-attested key properties regardless of platform, apply a platform-neutral control rather than treating these fields as a failure condition. For use cases where the absence of hardware attestation on iOS is unacceptable, Premium closes the gap — App Attest confirms the wallet app and device are genuine, and liveness independently re-proves the presenter's identity.

***

## `effective_tier` versus `assurance_level`

Your verification rule specifies an `assurance_level` — the tier you're requesting. The response also carries `effective_tier`, kept for backward compatibility: **on a `COMPLETED` result the two always match.** There is no silent downgrade — if evidence your rule requires is missing, the verification fails with an explicit reason instead of completing at a lower tier.

The tier measures which classes of evidence arrived and were validated, not whether the content of that evidence is favorable. A Standard result can still contain `ip_match: false` or `geolocation.country_match: false` — those are content signals you evaluate against your own risk policy; they don't change the tier.

What happens when evidence is missing depends on how your rule classifies each item:

* **Required** — if the item never arrives (including when the user is on a third-party wallet that doesn't implement Blerify's extensions at all), the session finalizes as `FAILED` with `reason: "required_fields_missing"`, and the `message` field lists what was missing.
* **Optional** — the session completes at your configured tier; the missing evidence field comes back `null` and its `_reason` field tells you why.

***

## Non-repudiation at Standard

Standard establishes a chain of evidence that makes it difficult for a credential holder to credibly deny having authorized a presentation. Each element addresses a specific argument:

| Argument                                         | Why it doesn't hold                                                                                                                                    |
| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| "Someone copied my key"                          | The key resides in tamper-resistant hardware and cannot be extracted or duplicated. Key attestation proves this cryptographically                      |
| "Someone used my key without my consent"         | Every signing operation requires the holder's biometric with no reuse window (`auth_timeout_seconds: 0`)                                               |
| "Someone enrolled their biometrics on my device" | The key is automatically destroyed if new biometrics are added (`invalidated_on_enrollment_change: true`)                                              |
| "The evidence was fabricated"                    | The `signed_evidence` JWT is signed by Blerify and verifiable by any third party against Blerify's public key — it cannot be altered without detection |

On Android, each element in this chain is hardware-attested — the properties are signed by the TEE or StrongBox, not asserted by the wallet. On iOS, the properties are self-reported but corroborated at Premium by App Attest confirming the wallet app and device are genuine.

***

## Zero biometric storage

Blerify's liveness model at Premium is structurally different from traditional identity verification services.

Traditional providers store a face image or a biometric embedding. They become custodians of biometric data subject to strict obligations under GDPR and equivalent laws. If their infrastructure is breached, biometric data is permanently exposed — unlike passwords, faces cannot be changed.

Blerify eliminates this risk by design. The credential issued by an authority already contains the holder's portrait, signed by the issuer. Blerify does not store a reference photo. When Premium verification runs, the selfie captured during liveness and the portrait extracted from the credential are compared and immediately deleted. The only artifact retained is the boolean result and a confidence score. Your backend never receives a face image, a template, or any biometric data.

For the full technical detail on how this works, see [Biometric Binding](/introduction-to-verification/learn/biometric-binding).

***

## Relationship to eIDAS and NIST

This section is informative. Blerify's tiers are not claims of formal regulatory equivalence.

| Blerify tier | Closest eIDAS level | Closest NIST AAL | PSD2 SCA                  |
| ------------ | ------------------- | ---------------- | ------------------------- |
| Basic        | \~Low               | \~AAL1           | Below SCA (single factor) |
| Standard     | \~Substantial       | \~AAL2           | Satisfies SCA             |
| Premium      | Exceeds scope       | Exceeds scope    | Exceeds SCA               |

**Standard and \~AAL2 / \~eIDAS Substantial.** The device biometric that activates the hardware-bound signing key qualifies as multi-factor authentication under both frameworks — something you have (the device with the key in hardware) plus something you are (the biometric that activates it). The mapping is informative because consumer phones meet the security bar functionally but don't hold the end-device certifications required for a formal compliance claim.

**Why Premium doesn't map to eIDAS High or NIST AAL3.** Premium exceeds the authentication requirements of both — it adds server-side liveness and independent face matching, neither of which those frameworks mandate for authentication. However, both require hardware certifications at the highest tiers that consumer smartphones meet only partially at the end-device level. This is an industry-wide limitation for consumer phone-based wallets, not specific to Blerify.

Blerify uses its own tier names to avoid implying certifications that don't yet exist end-to-end for consumer devices.

***

## Configuring tier requirements

You set the required assurance tier in your verification rule in the Portal. A verification either completes at that tier or fails with an explicit reason — there is no in-between outcome.

For the full evidence response schema, request parameters, and verification configuration details, see the [API reference](https://dev.blerify.com).

***

## Next steps

**Continue to** [**Biometric Binding**](/introduction-to-verification/learn/biometric-binding) — how the credential's signing key is bound to the user's biometrics, what each tier guarantees about that binding, and where the limits are.

See also: [Platform Attestation](/introduction-to-verification/learn/platform-attestation) · [Read a Verification Result](/introduction-to-verification/build/read-a-verification-result) · [API Reference](https://dev.blerify.com)


# Biometric Binding

Biometric binding is the chain of evidence connecting the person physically present during a verification to the person who was identity-proofed when the credential was issued. This page explains what Blerify verifies at each assurance tier, what secure hardware guarantees, where the limits are, and how server-side liveness closes the gap that hardware alone cannot close.

***

## What biometric binding means in practice

When someone presents a digital credential, three questions matter:

1. **Is the credential authentic?** Was it issued by a trusted authority, is the cryptographic signature valid, and has it not been revoked?
2. **Does the presenter control the credential?** Is the private key bound to this credential on the device being used right now?
3. **Is the presenter the credential subject?** Is the person in front of you the same person the credential was issued to?

{% hint style="info" %}
A trusted authority — the credential's issuer — is the entity that identity-proofed the holder and signed the credential: typically a government agency, civil registry, or national ID authority. This is independent from the country where you, the verifier, operate — a bank in one country can accept a credential issued by an authority in another, as long as that issuer is on your trust list.
{% endhint %}

Basic verification answers question 1 and confirms question 2 via a holder binding proof. Standard adds hardware-attested evidence about how the private key is protected. Premium answers all three — including question 3 — through server-side liveness and face matching.

***

## How the device key works

Every credential in the Blerify Wallet is bound to a private key stored in the phone's secure hardware: the Secure Enclave on iOS, StrongBox or a Trusted Execution Environment (TEE) on Android. The key never leaves that hardware. The wallet never holds it in software.

The key is configured with two constraints at the moment of creation.

**Authentication required on every use.** The key can only activate after the user authenticates with a biometric or device credential (PIN, pattern, or password). No app can trigger a signing operation silently in the background — every presentation requires an active unlock, with no caching window.

**Invalidation on biometric enrollment change.** If someone registers a new biometric on the device after the key was created, the key is automatically destroyed. A person who adds their own fingerprint to a stolen phone cannot use the credential: the key is already gone, and the legitimate holder would need to go through full re-enrollment — including identity proofing — to get a new one.

The wallet selects the authentication factor based on what the device supports. On devices with a strong biometric sensor enrolled, the key is biometric-gated and configured to invalidate if new biometrics are added. On devices without a strong biometric, the key falls back to the device credential, enforced at the hardware level. Either way, the signing key is always hardware-backed. The wallet refuses enrollment on any device that lacks hardware-backed secure storage or has no screen lock configured.

When a wallet presents a credential, it performs a signing operation using this key. The resulting holder binding proof demonstrates two things: the presenter holds the specific device the credential was bound to at issuance, and they were able to unlock its secure hardware.

***

## What each assurance tier verifies

### Basic

Blerify validates the credential's cryptographic signature, checks it against the issuer's revocation registry, verifies the issuer is on your configured trust list, and confirms the presenter completed a device authentication challenge proving they hold the credential's signing key.

A passing Basic result tells you the credential is valid and the presenter controls a device key bound to it. It tells you nothing about how that key is stored or what is required to unlock it.

Basic is also the effective tier for presentations from third-party wallets. Those wallets may internally operate at a higher security level, but without hardware-attested evidence in the presentation, that cannot be confirmed. The assurance Blerify reports reflects what the verifier can independently verify, not what the wallet may do internally.

Basic is appropriate for operations where the cost of a wrong answer is low: age checks, access control, low-value identity lookups.

### Standard

Standard extends Basic with key attestation. The wallet submits a certificate chain — signed by Google's or Apple's hardware root — alongside the presentation. Blerify validates that chain to confirm:

* The signing key lives in tamper-resistant hardware (StrongBox, TEE, or Secure Enclave), not in software.
* The key requires user authentication for every signing operation, with no caching window.
* The key is configured to be destroyed if someone adds a new biometric to the device after issuance.

This information appears in the `key_attestation` and `key_biometric_binding` fields of the verification result. On Android, these fields are signed directly by the TEE or StrongBox and are verifiable against Google's hardware attestation certificate roots — the wallet app cannot forge them. On iOS, Apple's attestation API confirms the app and device are genuine but does not carry the key's access-control flags, so `key_biometric_binding.attested` will be `false` in iOS results. The Secure Enclave still enforces the biometric requirement locally; it is simply not remotely attestable through Apple's current API. [Platform Attestation](/introduction-to-verification/learn/platform-attestation) covers these differences in detail.

Standard also includes signed audit evidence: IP addresses observed at presentation time, approximate geolocation derived server-side, device model and OS version, and a `signed_evidence` JWT you can retain for your own compliance records. That JWT is signed by Blerify and verifiable by any third party against Blerify's public key — without contacting Blerify — using the JWKS endpoint described in the [API reference](https://dev.blerify.com).

Standard does not prove that the person presenting is the credential subject. It proves that whoever holds the phone and can unlock its biometric sensor is presenting. That distinction matters for high-value operations.

Standard is appropriate for account opening, KYC, routine banking, and government service access.

### Premium

Premium adds server-side liveness verification on top of everything Standard provides. The wallet captures a short camera session during the presentation. Blerify runs that session through:

* An anti-spoofing check that detects photos, videos, 3D masks, and deepfake imagery.
* A face match against the portrait embedded in the credential — the photo the issuing authority placed in the credential at the time of issuance.

A passing Premium result means the person in front of the camera was a live human whose face matches the credential portrait with high confidence. Combined with Standard's key attestation, this closes the gap Standard cannot address on its own: you have independent, server-produced evidence that the presenter is the credential subject.

Premium also includes device attestation. On Android, Play Integrity provides explicit verdicts on whether the device has been rooted, the bootloader tampered with, or the app modified. On iOS, there is no equivalent API available to third-party apps — `device_attestation` will be `null` with `_reason: "platform_not_available"` in iOS Premium results. Liveness and face matching run identically on both platforms.

The selfie captured for liveness is never stored. Blerify processes it for the comparison and deletes it immediately. Only the boolean result and a confidence score are retained in the verification record. Your backend never receives raw biometric data.

Premium is appropriate for loan applications, high-value contracts, legal proceedings, and any operation where confirming physical presence justifies the additional step for the user.

***

## The biometric binding gap — and why it exists

Standard tier proves the signing key is in hardware and configured to require biometric authentication. It does not prove that the biometrics enrolled on the device belong to the credential holder.

If someone else's biometric was registered on the device before the credential was issued, that person can unlock the key. The invalidation-on-enrollment-change mechanism does not cover this case — it only catches biometrics added *after* key creation.

This is not a Blerify-specific limitation. iOS and Android deliberately do not expose biometric template data to applications. There is no platform API that lets a wallet verify whose biometrics are enrolled on a device. Any wallet that delegates biometric authentication to the OS faces the same constraint — passkeys, mobile banking apps, and device login all share this design.

For Basic and Standard, this gap is accepted as consistent with how device-based authentication works across the industry. For operations where physical presence matters, use Premium: server-side face matching against the credential portrait closes the gap at verification time, independent of what is enrolled on the device.

***

## Why server-side liveness rather than on-device face matching

An on-device face match would add complexity without providing what Premium is designed to deliver: independent, auditable evidence from a server outside the holder's control.

A result produced fully on the user's device — even on a certified device with hardware-attested code — is a statement from the holder's own equipment. When a transaction is disputed, evidence from the holder's phone carries less weight than evidence from an independent server. Financial institutions that use face matching for high-risk operations today — loan applications, new device enrollment, high-value contract signing — route users through a server-side verification step for exactly this reason.

Server-side liveness also provides stronger anti-spoofing. On-device passive checks detect casual attacks: photo printouts, simple video replay. Server-side processing using 3D face mapping and temporal video analysis catches sophisticated attacks: deepfakes, 3D-printed masks, and high-fidelity video replay. Device attestation confirms the app is genuine and unmodified, but cannot confirm that what the camera sees is a real person. That confirmation requires server-side analysis.

The combined result — device attestation confirming the environment, plus server-side liveness confirming physical presence — gives you independently auditable evidence that does not require sending biometric data to a centralized database.

***

## Zero biometric storage

Blerify's liveness model is structurally different from traditional identity verification services.

Traditional providers typically store a face image or a mathematical embedding of it. They become custodians of biometric data subject to strict obligations under GDPR and equivalent laws. If their infrastructure is breached, the biometric data of every user they have ever verified is exposed — and unlike passwords, faces cannot be changed.

Blerify's model eliminates this risk by design. The credential issued by a government authority already contains the holder's portrait, signed by the issuer. Blerify does not store a reference photo. When Premium verification runs:

1. The portrait is extracted from the signed credential during validation. Because it travels inside the signed credential, any tampering would have invalidated the issuer's signature before this step.
2. The wallet captures a short liveness session and sends it to Blerify.
3. Blerify compares the session against the portrait and deletes both immediately after the comparison.
4. The only artifact retained is the boolean result and a confidence score.

Your backend receives the result. It never receives a face image, a template, or any biometric data. Blerify never builds a face database.

The practical consequence is that a breach of Blerify's verification infrastructure would expose boolean results ("credential verified at time Y with confidence 0.97") but zero biometric data — because there is none to find.

***

## What credentials need for Premium

Premium requires a portrait field in the credential. Without it, Blerify has no reference image to match against and cannot perform liveness verification.

**ISO 18013 mDoc credentials** always contain a `portrait` field in the standard namespace. This is a required element of the ISO 18013-5 specification.

**W3C Credentials and SD-JWT credentials** may or may not include a photo, depending on the issuer. If the photo claim is selectively disclosed and the holder does not disclose it, liveness cannot proceed.

When Premium is requested but the credential contains no usable portrait, Blerify caps the result at Standard and includes `"liveness_not_possible": "credential_has_no_photo_claim"` in the response. You decide in your own logic whether Standard is acceptable for that operation.

Photo quality also matters. The face comparison engine needs the face to be recognizable at a reasonable resolution. The issuer controls portrait quality at issuance time — by the time a presentation reaches Blerify, whatever the issuer encoded is what the comparison runs against. Heavily compressed portraits, very small face regions, or images with significant glare or occlusion will reduce match confidence.

***

## Non-repudiation and audit evidence

Standard tier produces a non-repudiation chain. If a holder later disputes having authorized an action, the hardware attestation evidence addresses each common argument:

| Holder's argument                               | Why it doesn't hold                                                                                                                                            |
| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| "Someone copied my key"                         | The key lives in tamper-resistant hardware and cannot be extracted by software. Key attestation proves this cryptographically.                                 |
| "Someone used my key without my consent"        | Every signing operation requires hardware-enforced authentication with no reuse window.                                                                        |
| "Someone enrolled their biometric on my device" | If a new biometric was added after key creation, the key was automatically destroyed. The presentation could not have occurred.                                |
| "The audit evidence was fabricated"             | The `signed_evidence` JWT is signed by Blerify and independently verifiable against Blerify's public key by any auditor. It cannot be modified after issuance. |

For Premium, the liveness record adds that the person physically present matched the credential portrait, strengthening the chain further for operations that require evidence of physical presence.

Blerify signs the `signed_evidence` JWT but does not retain it after the session expires. You are the custodian of the audit record — store it according to your own retention requirements.

***

## Coercion detection

Premium includes an additional signal: analysis of gaze patterns and facial micro-expressions captured during the liveness session. This runs silently alongside the face match — the user sees only the standard liveness challenge, and a potential coercer sees the same thing.

If the analysis flags an anomaly — sustained gaze toward a fixed off-camera point, or involuntary stress expressions — the `coercion_check` field in the verification result will carry a non-clear status. The verification flow is never interrupted on a coercion signal. Blerify reports the signal; you decide how to respond based on your own risk policies. Interrupting the flow on a coercion detection would reveal to the coercer that a signal was triggered, potentially escalating the danger.

Coercion detection is Premium-only. The platform biometric APIs on both iOS and Android deliberately do not expose which biometric template was matched. This makes a duress-finger signal impossible to implement at the Standard tier. The camera-based liveness session at Premium enables gaze and expression analysis that the Standard flow structurally cannot support.

***

## Regulatory context

Blerify's tier names — Basic, Standard, Premium — are Blerify-specific definitions. They are not formal certifications of eIDAS or NIST equivalence.

**Basic** is below eIDAS Substantial and NIST AAL2. It establishes single-factor possession of the credential's signing key with no attestation of how that key is protected.

**Standard** aligns informally with eIDAS Substantial and NIST AAL2: two factors from different categories (possession and inherence), with the signing key in tamper-resistant hardware. Individual hardware security components in consumer phones often hold relevant security certifications, but no consumer end-device currently holds the certifications required by a strict reading of eIDAS High or NIST AAL3 at the whole-device level. This is an industry-wide constraint for mobile wallet deployments, not specific to Blerify.

**Premium** exceeds what eIDAS, NIST, PSD2, or banking regulators in most jurisdictions require for per-transaction authentication. Server-side liveness and face matching are controls designed for operations where identity re-proofing at a specific friction point is warranted — account opening, loan applications, high-risk device enrollment.

Neither eIDAS nor NIST requires that biometrics enrolled on a device belong to the credential holder. Both frameworks assume the device owner enrolled their own biometrics. The EU Digital Identity Wallet Architecture Reference Framework (EUDIW ARF) goes further and requires that biometric enrollment be linked to identity proofing. Blerify's roadmap is aligned with this direction.

For the detailed informative mapping between Blerify tiers and eIDAS/NIST levels — including the specific certification gaps that prevent formal equivalence — see [Assurance Tiers](/introduction-to-verification/learn/assurance-tiers).

***

## Next steps

**Continue to** [**Platform Attestation**](/introduction-to-verification/learn/platform-attestation) — how Android and iOS produce different evidence shapes in the verification result, and how to write a risk policy that accounts for the iOS attestation asymmetry.

See also: [Assurance Tiers](/introduction-to-verification/learn/assurance-tiers) · [Read a Verification Result](/introduction-to-verification/build/read-a-verification-result) · [API Reference](https://dev.blerify.com)


# Platform Attestation

Android and iOS expose fundamentally different attestation capabilities to third-party wallet apps. This page explains what each platform can and cannot prove, how that asymmetry shapes the evidence Blerify returns to your verification endpoint, and how to write a policy that accounts for it.

***

## Prerequisites

You should be familiar with Blerify's assurance tiers (Basic, Standard, Premium) and the general shape of the verification evidence response. If you haven't read [Assurance Tiers](/introduction-to-verification/learn/assurance-tiers) yet, start there.

***

## Why the platforms differ

At Standard and Premium tiers, Blerify asks the wallet to submit attestation data alongside the credential presentation. That data supports two claims: the signing key lives in secure hardware, and the user authenticated with their biometric before every signing operation.

On Android, a single API — Android Key Attestation — lets the wallet produce an X.509 certificate chain signed up to a Google-managed root. That chain encodes the key's properties directly: where it lives (TEE or StrongBox), whether biometric is required to use it, and whether the key is destroyed when a new biometric is enrolled. Every property is hardware-signed and cannot be forged by software, including on a rooted device.

On iOS, the equivalent API is Apple App Attest. It proves that an unmodified copy of the registered app, running on genuine Apple hardware, possesses the signing key. What it cannot prove is where inside the device the key lives, whether biometric is required for each signature, or whether the key will be invalidated on enrollment change. Apple provides no API that attests Secure Enclave key properties to third-party apps — the wallet reports those properties itself.

This is an architectural decision by Apple, not a limitation of any wallet implementation. The gap has been present since App Attest launched in 2021 and affects every third-party wallet on iOS.

***

## What this means in the evidence response

The asymmetry surfaces in two places: fields whose values differ by platform, and `_reason` fields that explain why a field is absent.

### `security_level_attested`

On Android, `key_attestation.security_level` — either `STRONG_BOX` or `TRUSTED_ENVIRONMENT` — is extracted directly from the hardware-signed certificate chain. Blerify sets `security_level_attested: true`.

On iOS, the wallet self-reports `SECURE_ENCLAVE`. Blerify sets `security_level_attested: false`.

```json
// Android — Standard tier
"key_attestation": {
  "security_level": "STRONG_BOX",
  "security_level_attested": true,
  "platform": "android",
  "format": "android_key_attestation",
  "key_biometric_binding": {
    "biometric_required": true,
    "auth_type": ["FINGERPRINT", "FACE"],
    "auth_timeout_seconds": 0,
    "invalidated_on_enrollment_change": true,
    "attested": true
  }
}
```

```json
// iOS — Standard tier
"key_attestation": {
  "security_level": "SECURE_ENCLAVE",
  "security_level_attested": false,
  "platform": "ios",
  "format": "app_attest",
  "key_biometric_binding": {
    "biometric_required": true,
    "auth_type": ["FACE"],
    "auth_timeout_seconds": 0,
    "invalidated_on_enrollment_change": true,
    "attested": false
  }
}
```

`attested: false` on iOS means the `key_biometric_binding` properties are self-reported by the wallet. On a genuine device running an unmodified app, they accurately reflect how the Secure Enclave key was configured. On a jailbroken device, they can be forged. Android Key Attestation cannot be forged because the certificate chain is signed by hardware that the operating system cannot reach.

### `key_biometric_binding` fields

These five fields describe the access-control policy that governs the signing key.

| Field                              | What it means                                                                                                                      | Android           | iOS            |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ----------------- | -------------- |
| `biometric_required`               | Biometric authentication is required before every signing operation                                                                | Hardware-attested | Self-reported  |
| `auth_type`                        | Which authenticator the key accepts: `FINGERPRINT`, `FACE`, or `DEVICE_CREDENTIAL`                                                 | Hardware-attested | Self-reported  |
| `auth_timeout_seconds`             | Seconds of reuse allowed after authentication. `0` means every signing operation requires fresh authentication — no caching window | Hardware-attested | Self-reported  |
| `invalidated_on_enrollment_change` | The key is destroyed if any new biometric is added to the device                                                                   | Hardware-attested | Self-reported  |
| `attested`                         | Whether the four fields above are hardware-attested (`true`) or self-reported (`false`)                                            | Always `true`     | Always `false` |

`auth_timeout_seconds: 0` combined with `invalidated_on_enrollment_change: true` is the strongest configuration. The key cannot be used without the holder's biometric at the moment of signing, and enrolling any new biometric on the device permanently destroys the key.

### Strong biometric vs. device credential

Both platforms support two key-binding paths: the **strong biometric path** (fingerprint or face recognition) and the **device credential path** (PIN, pattern, or passcode). The path chosen at key-creation time determines what `auth_type` reports.

On the strong biometric path, `auth_type` is `["FINGERPRINT"]`, `["FACE"]`, or both, and `invalidated_on_enrollment_change` is `true`. On the device credential path, `auth_type` is `["DEVICE_CREDENTIAL"]` and `invalidated_on_enrollment_change` is `false` — a PIN change does not destroy the key. This distinction matters for non-repudiation claims: a key on the device credential path cannot be made inaccessible to an attacker who knows the PIN.

### `device_attestation` at Premium on iOS

At Premium, an Android wallet submits a Play Integrity token that produces a device-integrity verdict: locked bootloader, certified OS image, legitimate app install source, and recent security patches. These are independent signals that key attestation does not cover.

iOS has no equivalent API for third-party apps. App Attest, which is already fully consumed at Standard tier, verifies app identity against Apple's servers but does not produce a device-integrity verdict, cannot detect jailbreaks, and does not inspect the OS image.

At Premium on iOS, `device_attestation` is always `null` with `device_attestation_reason: "platform_not_available"`.

```json
// iOS — Premium tier
"device_attestation": null,
"device_attestation_reason": "platform_not_available"
```

This is not a failure — it is a structural platform constraint. The practical consequence is that the Premium upgrade on iOS comes entirely from the biometric layer. `liveness_verification` and `coercion_check` work identically on both platforms.

### `device_metadata` attestation

Android Key Attestation embeds the device model and OS version in the hardware-signed certificate chain, so `device_metadata.attested` is `true` on Android. App Attest proves device genuineness but does not include model or OS version in the attestation object, so those values are self-reported by the wallet on iOS and `device_metadata.attested` is `false`.

```json
// Android
"device_metadata": {
  "device_model": "Pixel 8",
  "os_version": "Android 15 (patch 2026-03-05)",
  "attested": true,
  "attestation_source": "android_key_attestation"
}

// iOS
"device_metadata": {
  "device_model": "iPhone 15 Pro",
  "os_version": "iOS 18.3",
  "attested": false,
  "attestation_source": "wallet_self_reported"
}
```

***

## The `_reason` field

Every optional evidence field has a companion `_reason` field. When the main field is populated, `_reason` is `null`. When the main field is `null`, `_reason` explains why.

| Value                       | Meaning                                                                                                | What to do                                                                                              |
| --------------------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------- |
| `null`                      | The field is populated                                                                                 | N/A                                                                                                     |
| `platform_not_available`    | The platform has no API for this capability — for example, iOS `device_attestation` at Premium         | Structural limit — accept, or apply a platform-specific policy rule                                     |
| `wallet_not_supported`      | The wallet does not implement Blerify extensions                                                       | Capability gap — downgrade assurance or direct users to a compatible wallet                             |
| `not_requested`             | Your verification configuration did not request this tier or feature                                   | Expected absence                                                                                        |
| `user_declined`             | The user denied a required permission (camera access, biometric prompt)                                | Active refusal — route to manual review rather than treating identically to a structural platform limit |
| `failed`                    | Validation was attempted but the data was rejected — invalid token, spoof detected, assertion mismatch | Treat as a strong integrity signal — investigate                                                        |
| `attestation_not_supported` | Older Android device where the key is in hardware but cryptographic attestation is unavailable         | Hardware-backed but unprovable — accept or downgrade per your policy                                    |

The `_reason` field is always present in the response schema — `null` when the main field is populated. You can rely on a consistent shape and never need to infer absence semantics from a missing field.

The distinction between `platform_not_available` and `user_declined` matters for policy. A device that structurally cannot produce a signal is different from a user who actively chose not to provide one. Consider routing `user_declined` to a separate review queue rather than treating it the same as a platform limit.

Similarly, `attestation_not_supported` on an older Android device is different from `failed`. The former is a legacy hardware limitation affecting less than 1% of the active Android fleet; the latter is an active integrity failure.

***

## Platform comparison

| Evidence field                            | Android                                                            | iOS                                                   |
| ----------------------------------------- | ------------------------------------------------------------------ | ----------------------------------------------------- |
| `key_attestation.security_level`          | Hardware-attested (`STRONG_BOX` or `TRUSTED_ENVIRONMENT`)          | Self-reported (`SECURE_ENCLAVE`)                      |
| `key_attestation.security_level_attested` | `true`                                                             | `false`                                               |
| `key_biometric_binding.*`                 | All five fields hardware-attested                                  | All five fields self-reported                         |
| `key_biometric_binding.attested`          | `true`                                                             | `false` (always)                                      |
| `device_metadata.attested`                | `true`                                                             | `false`                                               |
| `device_metadata.attestation_source`      | `android_key_attestation`                                          | `wallet_self_reported`                                |
| `device_attestation` at Premium           | Play Integrity — device integrity, OS certification, app integrity | `null`, `_reason: "platform_not_available"`           |
| `liveness_verification` at Premium        | Full liveness — platform-neutral                                   | Full liveness — platform-neutral                      |
| `coercion_check` at Premium               | Facial analysis on liveness frames — platform-neutral              | Facial analysis on liveness frames — platform-neutral |

***

## Key invalidation on enrollment change

### Android

When a new biometric is enrolled after the signing key was created — for example, if someone adds their own fingerprint to a stolen phone — the key is destroyed automatically on the strong biometric path. The `invalidated_on_enrollment_change: true` field tells you this protection is active, and `attested: true` tells you it is hardware-enforced.

On the device credential path (`auth_type: ["DEVICE_CREDENTIAL"]`), the key is PIN-gated and a PIN change does not destroy it. Check `auth_type` to understand which path is in use before making non-repudiation claims.

### iOS

The equivalent protection comes from an access-control flag set on the Secure Enclave key at creation time. A key created with this flag becomes inaccessible when any new biometric is enrolled — the Secure Enclave enforces this locally. The wallet self-reports this via `invalidated_on_enrollment_change: true`, and `attested: false` tells you it is locally enforced rather than remotely verifiable.

On the device credential path, enrollment changes do not invalidate the key, matching Android's device credential behavior.

***

## TEE vs. StrongBox on Android

Android Key Attestation distinguishes two hardware security levels.

**TEE (Trusted Execution Environment)** is a hardware-isolated execution environment present on virtually all Android devices since Android 7.0. It runs in a secure partition of the same processor, enforcing memory separation between the normal Android OS and the secure world where keys are managed. TEE has a broader attack surface than discrete secure hardware — known CVEs exist for specific chipset implementations, and exploitation typically requires an attacker to first gain code execution on the device.

**StrongBox** is a dedicated, tamper-resistant secure processor with its own CPU, storage, and random number generator. It is not present on all devices — it is common on modern flagship phones but absent from many mid-range and budget devices. StrongBox is significantly harder to attack than TEE because it is physically separate hardware.

Both are accepted at Standard and Premium tiers. Blerify reports `security_level` so you can apply your own acceptance policy. Most regulated identity verification use cases accept TEE — exploiting it to bypass biometric-gated key signing requires chaining multiple chipset-specific vulnerabilities. If your requirements call for StrongBox-only, evaluate `security_level` in the evidence and enforce your policy accordingly.

***

## Older Android devices and `attestation_not_supported`

A small fraction of Android devices — less than 1% of the active fleet — launched before Android 8.0 with hardware security modules that predate Key Attestation. On these devices, the signing key is still in hardware and the biometric gates still work, but Blerify cannot produce the X.509 certificate chain that proves hardware residency.

In this case, `key_attestation_reason` is `"attestation_not_supported"`. Whether to accept a hardware-backed but unprovable key is a policy decision. Most verifiers treat it the same as a proven hardware-backed key given the low prevalence and the fact that the user still authenticated with their biometric to authorize the presentation.

***

## Writing a policy that accounts for iOS

**Accept iOS without device attestation.** Most regulated use cases accept that iOS does not expose a Play Integrity equivalent for third-party apps. The combination of App Attest at Standard — proving genuine Apple hardware and an unmodified app — plus liveness at Premium covers the majority of identity verification and account-opening flows. Document the acceptance explicitly in your risk assessment.

**Differentiate `security_level_attested: false` by platform.** On iOS, this value always reflects a platform constraint, not a degraded or suspicious result. Pair it with a check on the `platform` field to confirm you are seeing the expected iOS self-reported case rather than an anomaly.

**Treat `attested: false` differently from `failed`.** An iOS key with `attested: false` and a valid App Attest assertion is meaningfully different from a key whose attestation failed validation. The former is a known platform limitation; the latter is an active integrity failure worth investigating. Your policy logic should handle them separately.

**Use `user_declined` as an escalation signal.** A user who denies the biometric prompt or camera permission has actively interrupted the flow. This is a different risk category from an iOS device that structurally cannot produce device attestation. Route `user_declined` to a review queue rather than treating it as equivalent to a structural absence.

**Require liveness for high-value iOS flows.** Since device attestation is unavailable on iOS, `liveness_verification` at Premium is the primary compensating control for transactions that warrant it. If you need the highest available assurance on iOS, make liveness a hard requirement in your verification configuration.

**Do not block on `"platform_not_available"`.** Blocking iOS users because the platform cannot produce a Play Integrity equivalent would exclude a large share of legitimate users without a corresponding security benefit. Note the limitation, compensate with liveness where the risk warrants it, and document the accepted residual gap.

***

## What the layered iOS evidence adds up to

At Premium on iOS, the combination of evidence across all layers supports a defensible assurance level even without hardware-attested `key_biometric_binding` and without device attestation.

1. **App Attest** proves the request came from a genuine Apple device running an unmodified instance of the registered app. A modified or unauthorized app fails App Attest before any signing occurs.
2. **Secure Enclave key** is configured by the wallet with enrollment-change invalidation. The key is tied to the biometric enrollment set at key-creation time and becomes inaccessible if that set changes. The Secure Enclave enforces this locally — Apple does not provide a remote attestation of it, but the wallet has no mechanism to bypass it on a genuine, unmodified device.
3. **Liveness verification** confirms that the person physically present is the credential holder by comparing a live facial capture against the portrait embedded in the credential presentation.

The gap versus Android is that the Secure Enclave configuration is enforced by Apple's hardware rather than proven by a verifiable certificate chain. For most regulated use cases, that is an acceptable residual risk.

***

## Non-repudiation and the biometric binding chain

At Standard tier, the combination of key attestation and biometric binding supports a non-repudiation argument: the credential was presented by someone who held the device, authenticated with the enrolled biometric, and whose signing key would have been destroyed had anyone modified the biometric enrollment set.

The argument is strongest on Android, where each property in `key_biometric_binding` is hardware-attested. On iOS, it relies on Apple's local enforcement of the Secure Enclave configuration rather than on a verifiable proof. In most jurisdictions this is sufficient to shift the burden of proof to the credential holder to demonstrate they did not authorize the presentation.

At Premium on iOS, `liveness_verification` strengthens the chain by confirming that the person who authorized the presentation was physically present and matched the credential's portrait.

Whether this evidence constitutes legally sufficient non-repudiation depends on the jurisdiction and applicable regulatory framework. Your legal team should evaluate the evidence chain against your specific requirements.

***

## Next steps

**Continue to** [**Assurance Tiers**](/introduction-to-verification/learn/assurance-tiers) — how Basic, Standard, and Premium differ, what each proves per platform, and how to choose the right tier for your use case.

See also: [Biometric Binding](/introduction-to-verification/learn/biometric-binding) · [Read a Verification Result](/introduction-to-verification/build/read-a-verification-result) · [API Reference](https://dev.blerify.com)


# Verifier Trust

When a user's wallet receives a credential request, it needs to answer two questions before presenting anything: *is this request really from the organization it claims to be?* and *has Blerify vetted that organization as a legitimate verifier?*

Verifier trust is how Blerify answers both questions. Every verifier organization gets a signing certificate that authenticates its requests. The Blerify wallet learns which certificates to trust from a signed trust list it syncs from Blerify — so adding a new verifier never requires an app update, and revoking one takes effect within minutes.

This page explains how the system works from the perspective of someone building on or integrating with the Blerify ecosystem.

***

## How it works

Two independent mechanisms work together.

**Request authentication** answers *"is this request really from that organization?"* Each organization has a signing certificate. Every credential request carries that certificate and is signed with the corresponding private key. The wallet verifies the signature before presenting anything.

**Trust distribution** answers *"which organizations does Blerify vouch for?"* Blerify maintains a signed trust list describing every vetted verifier — its certificate anchor, its display name and logo, and what it's authorized to request. The wallet syncs this list from Blerify and uses it at request time.

The two mechanisms are linked: the trust list tells the wallet which certificates to accept, and the request authentication uses those certificates. A request from an organization whose certificate isn't in the trust list is rejected, even if the signature is cryptographically valid.

***

## Verifier identity and key custody

Every verifier organization gets exactly one signing identity — regardless of how many verification templates it creates. That identity is provisioned automatically the first time an organization publishes a verification template, with no manual configuration required.

The practical design question is where the signing key lives. Blerify supports a spectrum of custody models:

| Custody model      | Key lives                                                            | When to use                                                                  |
| ------------------ | -------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| **Managed**        | Blerify's key management infrastructure                              | Default — no infrastructure to operate                                       |
| **Issued**         | Your own backend (Blerify-issued certificate)                        | You want to hold your own key but use a Blerify-issued certificate           |
| **Sovereign-lite** | Your own backend (self-signed certificate, pinned in the trust list) | Long-lived key, minimal rotation; the certificate itself is the trust anchor |
| **Sovereign**      | Your own certificate authority                                       | Full PKI control, own CA hierarchy                                           |

The custody model doesn't affect what the user sees. In every case, the wallet renders the organization's vetted display name and logo from the trust list — not from the certificate. Blerify controls what appears on the user's consent screen, preventing one organization from impersonating another's brand.

Leaf certificate rotation — short-validity certificates renewing on a schedule — is handled automatically in Managed and Issued tiers. In Sovereign-lite, the certificate *is* the trust anchor, so rotation requires a trust list update rather than a certificate-chain operation.

***

## How the wallet learns who to trust

The wallet ships with two provisioned inputs: a pinned Blerify root key and a federation base URL. Everything else — organization names, logos, certificates, authorization policies — arrives signed and synced from Blerify. No app update is required when a new verifier is onboarded or an existing one is revoked.

On first sync and periodically thereafter, the wallet:

1. Fetches the Trust Anchor Entity Configuration from the federation base URL and verifies it against the pinned root key
2. Enumerates all vetted verifier organizations
3. Fetches and verifies a signed statement for each organization, extracting its certificate anchor, display metadata, and what it's authorized to request
4. Fetches the revocation list and verifies it against the same root
5. Caches logos locally after verifying each image against a signed hash embedded in the statement — a swapped CDN image fails the check

The result is a local trust store the wallet consults at request time, entirely without network access. Syncing is a background operation decoupled from any individual verification session.

***

## What the trust list contains

For each vetted organization, the trust list carries:

* **Certificate anchor** — the CA certificate (for Managed/Issued/Sovereign organizations) or the pinned certificate thumbprint (for Sovereign-lite). This is what the wallet validates incoming request certificates against.
* **Display metadata** — the organization's vetted name and logo, curated by Blerify. The wallet never renders identity from the certificate's subject field.
* **Authorization policy** — what credential types and claims the organization is permitted to request. The wallet enforces this at presentation time: a request that asks for more than the organization is authorized to receive is refused before the user sees a consent screen.
* **Status** — whether the organization is active or revoked.

Display metadata is controlled by Blerify and updated only through an explicit vetting process. An organization cannot change what the wallet shows on its consent screen by editing its own profile — the change only reaches the trust list after Blerify re-curates the entry.

***

## How a request is validated

When a credential request arrives, the wallet establishes who sent it before considering what it's asking for:

1. **Find the anchor.** Walk the certificate chain in the request to find the first certificate that matches an entry in the local trust list.
2. **Validate the chain.** For CA-anchored entries, perform standard X.509 path validation from the leaf up to the trusted CA. For pinned-leaf entries, verify the presented certificate's thumbprint matches the registered pin exactly — no chain is walked.
3. **Check revocation.** Verify the anchor against the signed revocation list. Revocation is always positive evidence — an organization absent from the revocation list is not considered revoked.
4. **Check freshness.** Confirm revocation data is recent enough for the assurance level being requested.
5. **Verify the signature.** Validate the request signature against the leaf key that just passed chain validation.
6. **Enforce authorization.** Compare what the request is asking for against what the trust list says the organization is permitted to request. Requests that exceed the authorized set are refused.
7. **Render the consent screen.** Show the organization's vetted name and hash-verified logo from the trust list — never from the certificate subject.

A request fails at the first step that doesn't pass. The wallet never falls back to trusting a certificate just because it's cryptographically valid.

***

## Revocation

Revoking a verifier takes effect at the organization level. When an organization's trust anchor is revoked:

* The revocation list is updated immediately and distributed with a shorter cache lifetime than the trust list itself
* The wallet picks up the revocation on its next sync
* All subsequent requests from that organization are refused, regardless of whether their signing certificates are individually valid

Revocation uses positive evidence — a signed entry saying "this anchor was revoked at this time, for this reason." The wallet never infers revocation from an organization's absence in the trust list. This design choice means a network outage or a stale mirror can't silently re-trust a revoked verifier.

Two revocation artifacts serve different consumers. The signed revocation list is what wallets check for organization-level revocation. An X.509 CRL serves the proximity verification flow — where an in-person verifier presents a reader certificate to the wallet over Bluetooth or NFC — because that flow uses standard X.509 reader authentication rather than the federation protocol. Both artifacts are derived from the same source.

***

## Display identity and brand protection

A key property of the system is that the organization's display identity — the name and logo the user sees — is controlled by Blerify, not by the organization presenting the request.

This works because:

* Display metadata lives in the signed trust list, not in the certificate
* Blerify curates display metadata through an explicit vetting process
* The wallet verifies logo images against a hash embedded in the signed statement before rendering them
* Changes to an organization's display metadata don't reach the trust list until Blerify re-curates the entry

As a result, an organization cannot impersonate another's brand by modifying its certificate subject or self-asserting a different name in its request. The user always sees the identity Blerify has vetted.

***

## How this relates to assurance tiers

Verifier trust is independent of assurance tiers. It determines *who* can request a presentation. Assurance tiers determine *what* evidence is collected during the presentation itself.

The authorization policy in the trust list does constrain what a verifier can *ask for* — credential types and claim fields — but that's a permission control, not an assurance level. A verifier might be authorized to request a driving license and configured with a Standard tier requirement. Those are separate settings.

For the full assurance model, see [Assurance Tiers](/introduction-to-verification/learn/assurance-tiers).

***

## Next steps

**Continue to** [**Assurance Tiers**](/introduction-to-verification/learn/assurance-tiers) — what Basic, Standard, and Premium each prove, and how to choose the right tier for your use case.

See also: [Platform Attestation](/introduction-to-verification/learn/platform-attestation) · [Get Started with a Verification](/introduction-to-verification/build/get-started) · [API Reference](https://dev.blerify.com)


# Reference

Lookup material — precise, no narrative. Come here when you already know what you're doing and need the exact contract, role, or configuration rule.

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>API Reference</strong></td><td>The full endpoint contracts — request/response schemas, error codes, live examples.</td><td><a href="https://dev.blerify.com">https://dev.blerify.com</a></td></tr><tr><td><strong>Portal Roles and Permissions</strong></td><td>The platform-wide role model — who on your team can create and manage rules.</td><td><a href="/introduction/portal-roles-and-permissions">Portal Roles and Permissions</a></td></tr><tr><td><strong>Reference Implementation</strong></td><td>A deployable Next.js app demonstrating the complete integration pattern.</td><td><a href="/introduction-to-verification/reference/reference-implementation">Reference Implementation</a></td></tr></tbody></table>

## Next steps

[**API Reference**](https://dev.blerify.com) — the endpoint contracts behind every Build guide.

See also: [Create a Verification Rule](/introduction-to-verification/build/create-a-verification-rule) — what each rule setting controls, and how it behaves at runtime.


# Reference Implementation

The `blerify-verification-demo` is a deployable reference implementation of the Blerify verification integration pattern. It's a Next.js server-side application that demonstrates the complete flow: authenticating a service account, starting a verification session, showing the user a QR code or wallet button, and polling for the result. No secrets leave the server.

Use it as a working example to understand the integration before building your own, or fork it as a starting point for a real deployment.

The source is at [github.com/BlerifyPlatform/blerify-verification-demo](https://github.com/BlerifyPlatform/blerify-verification-demo). Build instructions, local development setup, and the full source live in that repository's README — this page covers the portal prerequisites, configuration, and deployment targets.

***

## Prerequisites

* A Blerify account with owner or organization admin access
* A verification project in the Portal
* The two portal artifacts described below: a service account and a published verification template

***

## How it works

The demo is a Backend For Frontend (BFF). Your browser never holds credentials or calls Blerify directly.

```
Browser  ──POST /api/start──▶  BFF  ──start session──▶  Blerify
  │   shows QR or wallet button  ◀──  { transaction_id, ... }
  │
Wallet  ── scans QR or opens deep link, presents credential ──▶  Blerify
  │
  └─  GET /api/status?transactionId  ──▶  BFF  ──poll result──▶  Blerify
                                     ◀──  { status, credential claims }
```

The BFF authenticates to Blerify using a service account with `private_key_jwt` (RFC 7523). The wallet talks to Blerify directly — the BFF is never in the credential transport path. The result comes back by polling, keyed on `transaction_id`.

On mobile, the demo detects the user agent and shows a wallet button instead of a QR code. A toggle lets users switch to QR if their wallet is on a different device.

For the full protocol detail — endpoints, request/response schemas, session states — see the [API Reference](https://dev.blerify.com).

***

## Step 1 — Create a service account

The service account is how the BFF authenticates to Blerify machine-to-machine. You create it at the organization level and need owner or organization admin access.

1. In the Portal, go to **Organization → Service Accounts** and click **Create service account**.
2. Fill in a name and description.
3. Under **Signing algorithm**, select `RS256`.
4. Under **Roles**, select **Verifications** — this grants the account permission to start and poll verification sessions.
5. Under **Project**, choose the verification project where your template lives (or will live).
6. Click **Create**. The Portal generates a key pair and prompts you to download a JSON credentials file.
7. **Download the file now.** The private key is shown once and cannot be retrieved again. The download button stays active until you close the dialog — close it only after saving the file.

The downloaded JSON contains everything the demo needs to authenticate:

| JSON field        | Environment variable            |
| ----------------- | ------------------------------- |
| `client_id`       | `SA_CLIENT_ID`                  |
| `private_key`     | `SA_PRIVATE_KEY`                |
| `iam_audience`    | `SA_IAM_AUDIENCE`               |
| `token_uri`       | `SA_TOKEN_URI` (optional)       |
| `organization_id` | `SA_ORGANIZATION_ID` (optional) |

***

## Step 2 — Create and publish a verification template

A verification template defines what credential to request, which issuer to trust, and what assurance level to require. It lives inside a verification project.

1. Open the verification project and go to **Verification Rules → New rule**.
2. Choose an assurance level and the credential types to accept. This opens a five-step wizard:
   * **Information** — name, internal code, description, type
   * **Attributes and proofs** — which claims to request from each credential
   * **Verifications** — validation settings and transport (OpenID4VP)
   * **Sanction lists** — optional control lists
   * **Technical integration** — select the service account you created in Step 1, then generate the rule ID
3. In the **Technical integration** step, click **Generate ID** to save the rule as a draft, then click **Publish rule** to activate it. Only a published rule can be used at runtime.

**Finding your IDs.** The **Technical integration** step shows a code snippet with the full endpoint path. That path contains all three IDs you need:

```
.../organizations/{ORG_ID}/projects/{PROJECT_ID}/verifications/{RULE_ID}
```

Copy them from there. `RULE_ID` is the UUID labeled **Rule ID** in the integration step — not the `ver_…` identifier shown on the rule detail screen.

***

## Step 3 — Configure the demo

All configuration is runtime — nothing is baked into the build artifact. Copy `.env.example` from the repository and set the values for your environment:

| Variable             | Description                                                                |
| -------------------- | -------------------------------------------------------------------------- |
| `BLERIFY_API_URL`    | Blerify API base URL for your environment                                  |
| `ORG_ID`             | Your organization ID                                                       |
| `PROJECT_ID`         | Your verification project ID                                               |
| `RULE_ID`            | The UUID of your published verification template                           |
| `WALLET_BASE_URL`    | Base URL of the Blerify Wallet                                             |
| `SA_CLIENT_ID`       | From the service account JSON                                              |
| `SA_PRIVATE_KEY`     | From the service account JSON (PEM, PKCS#8, RSA-2048)                      |
| `SA_IAM_AUDIENCE`    | From the service account JSON                                              |
| `SA_TOKEN_URI`       | Optional — defaults to the standard token endpoint                         |
| `SA_ORGANIZATION_ID` | Optional — defaults to `ORG_ID`                                            |
| `DEMO_MOCK`          | Set to `true` to run the full UI without a real backend or service account |

`DEMO_MOCK=true` lets you explore the UI and simulate the complete flow without configuring any Blerify credentials. Use it to evaluate the interface before setting up the Portal prerequisites.

***

## Deploying

The same codebase deploys to three targets. In every case, set environment variables in the platform — never commit them to the repository.

### Netlify

Connect the repository in the Netlify dashboard. The `netlify.toml` in the repo declares the build command and configures the Next.js adapter, which serves the API routes as serverless functions. Set environment variables under **Site settings → Environment variables**.

### Vercel

Import the repository in the Vercel dashboard. Vercel auto-detects it as a Next.js project using `vercel.json`. Set environment variables in the project settings.

### Docker

```bash
docker build -t blerify-verification-demo .
docker run --env-file .env -p 8080:8080 blerify-verification-demo
```

The image is generic — it reads all configuration from environment variables at startup. Use the same image across environments by changing the env file, not the image.

The repository's CI pipeline validates every push: type-checking, build, and Docker image construction. It does not deploy and requires no secrets.

***

## Next steps

**Continue to** [**Get Started**](/introduction-to-verification/build/get-started) — build the same flow this demo implements, step by step.

See also: [Read a Verification Result](/introduction-to-verification/build/read-a-verification-result) · [Assurance Tiers](/introduction-to-verification/learn/assurance-tiers) · [API Reference](https://dev.blerify.com)


# Get Started with the Blerify Wallet

The Blerify Wallet is a mobile app where you receive, store, and present digital credentials. This page explains what the wallet is, what you can do with it, and how to receive your first credential.

***

## What is the Blerify Wallet

The Blerify Wallet is an identity wallet that stores W3C Verifiable Credentials and ISO 18013 mDocs on your device. Credentials are encrypted at rest and unlockable only with your biometric (fingerprint or face) or PIN.

When an organization asks you to present a credential — at an online service, at a kiosk, inside an app — the wallet shows you exactly which fields they're requesting. You approve or decline. Nothing leaves your device without your consent.

Credentials are stored locally. Blerify does not hold a copy of your credentials in a cloud database.

***

## Install the wallet

The Blerify Wallet is available on:

* **iOS** — App Store
* **Android** — Google Play

Download and install it before following the steps below.

***

## Receive a credential

Credentials are delivered to your wallet by an issuer — a government agency, employer, university, or other organization that has issued you a digital credential. You receive a credential in one of three ways:

### Option A — Scan a QR code

The issuer shows you a QR code (on screen, on paper, or in an email). Open the Blerify Wallet, tap **Scan**, and point your camera at the QR code. The wallet downloads the credential, validates its signature, and asks you to confirm that you want to add it.

Tap **Accept**. The credential is stored on your device.

### Option B — Tap a deeplink

The issuer sends you a link — via email, SMS, or their own app. Tap the link on your phone. If the Blerify Wallet is installed, the OS routes it directly to the wallet. The wallet shows you the credential and asks you to confirm.

Tap **Accept**. The credential is stored on your device.

### Option C — Push notification

If the issuer has your wallet account linked, you may receive a push notification. Tap the notification. The wallet opens on the credential confirmation screen.

Tap **Accept**. The credential is stored on your device.

***

## Present a credential

When a service or organization asks you to present a credential, the process is the same regardless of whether they're a website, kiosk, or mobile app.

1. The verifier shows you a QR code or button.
2. You open the Blerify Wallet (or, on a mobile page, tap the button to open it directly).
3. The wallet shows a consent screen: the verifier's name, what they're asking for, and which fields from your credential will be shared.
4. You approve with your biometric or PIN.
5. The wallet sends the cryptographic proof to the verifier. The verifier's system receives the result. Your screen shows a confirmation.

The verifier only receives the fields you approved. Your full credential is never transmitted.

***

## What the wallet holds

The wallet can store both W3C Verifiable Credentials and ISO 18013 mDocs. You can have multiple credentials of the same type — for example, multiple employee credentials from different organizations, or both a national ID and a professional license.

Credentials are grouped in the wallet's main screen. Tap any credential to see its fields, its issuer, its validity period, and its status (active, expired, revoked).

***

## Authenticate with your wallet

Beyond presenting credentials to verifiers, the wallet supports logging in to services by presenting a credential directly. Instead of a username and password, you prove your identity by presenting a verifiable credential — an ISO mDL or a W3C Verifiable Credential — which the service verifies cryptographically. The service issues you a bounded, revocable session in return.

The credential is bound to a single-use challenge issued by the service, so nothing is replayable. If your session expires, the wallet can refresh it silently; if the refresh token itself expires or is revoked, you present your credential again to start a new session.

This login flow is used internally by the wallet app to authenticate with the Blerify platform. If you're building a service that accepts this kind of credential-based login, see the developer documentation at <https://dev.blerify.com>.

***

## Revoked or expired credentials

If an issuer revokes a credential, the wallet flags it on the next background sync. The credential stays visible in your wallet with a `REVOKED` badge — you won't be able to present it to verifiers, and any attempt will fail verification. Contact the issuer to understand why it was revoked and whether a replacement can be issued.

An expired credential is similar — it stays in your wallet for reference but won't pass verification. Issuers typically notify you before expiry if a renewal process is available.

***

## Privacy and data

* Your credentials are stored on your device, not on Blerify servers.
* Blerify does not track when or how often you present your credentials.
* The issuer is not notified when you present a credential.
* Uninstalling the wallet removes the credentials from your device. They cannot be recovered from Blerify. Contact the issuer to re-issue them.

***

## Next steps

[**Issuance: Issue a W3C Credential**](/introduction-to-issuance/build/issue-a-w3c-credential) — if you're building a service that delivers credentials to wallets, this is where to start.

See also: [Verification: Get Started](/introduction-to-verification/build/get-started) — how verifiers request and validate credential presentations from the wallet.


# Blerify's Decentralized Root of Trust

Blerify's Trust Registry anchors issuer identity and credential status on-chain, so any verifier can confirm that a credential came from a trusted issuer — without calling Blerify at verification time.

This page explains the two on-chain registries, how they work together, and how the Trust Registry fits into the verification pipeline.

***

## Two registries, two questions

Every credential verification answers two independent questions:

| Question                                   | Registry                                      |
| ------------------------------------------ | --------------------------------------------- |
| Is the issuer trusted?                     | **ChainOfTrust** (issuer trust)               |
| Has this specific credential been revoked? | **Verification Registry** (credential status) |

They're separate contracts with separate concerns. You can use one without the other.

***

## ChainOfTrust — issuer trust

The ChainOfTrust contract is a hierarchical on-chain registry of issuer signing keys. Each entry associates a public key with an entity, a parent entity, and an expiration window.

When a verifier checks a credential, Blerify walks the chain from the credential's signing key up to the root, verifying that every level is active and unexpired. If any ancestor is expired or revoked, the entire chain fails — the credential is untrusted regardless of its cryptographic signature.

**Key properties:**

* **Hierarchical**: issuers are organized in a tree. A root authority endorses intermediaries; intermediaries endorse issuers. Each level carries its own expiration.
* **Single traversal**: trust and certificate-level revocation are resolved in one on-chain query — no separate CRL or OCSP call needed for the issuer's signing key.
* **Format-agnostic**: works identically for W3C Credentials (DID-based or X.509-based) and ISO 18013 mDocs.
* **EVM-compatible**: deployed on any EVM-based blockchain; Blerify operates nodes on supported chains.

**What endorsing trust means:**

When an entity is endorsed at a given level in the chain, it must acknowledge the endorsement before trust is established. The endorser retains the ability to revoke or update the endorsement — but endorsing an entity does not give the endorser control over that entity's own identity or its descendants.

***

## Verification Registry — credential status

The Verification Registry tracks the lifecycle of individual credentials by their digest (a hash of the credential content), keyed by issuer.

Each credential can be in one of three states:

| State     | Meaning                            |
| --------- | ---------------------------------- |
| `ISSUED`  | Valid                              |
| `ON_HOLD` | Temporarily suspended — reversible |
| `REVOKED` | Permanently revoked — irreversible |

A verifier queries `getDetails(issuer, digest)` and receives the credential's current state, its expiration, and whether it's on hold.

**What this is not:** the Verification Registry answers *"has this credential been revoked?"* — it does not answer *"is the issuer trusted?"*. Both checks run independently in the verification pipeline.

***

## How the two registries interact

During a verification, the pipeline runs both checks in sequence:

```
Credential received
       │
       ▼
Issuer trust check ──── ChainOfTrust ──── trusted? ──── continue
       │ no                                               │
       ▼                                                  ▼
  fail: UNTRUSTED_ISSUER                    Credential status check
                                                   │
                                            Verification Registry
                                                   │
                                    ISSUED? ── continue to assurance checks
                                    ON_HOLD / REVOKED? ── fail: REVOKED
```

A credential can fail either check independently. A valid signature from a trusted issuer doesn't help if that specific credential has been revoked; and an unrevoked credential from an untrusted issuer fails the first gate.

***

## Decentralized Public Key Directories (DPKDs)

The ChainOfTrust implements the DPKD pattern: an on-chain registry where issuer identity and public keys are publicly recorded and resolvable without trusting any single operator.

DPKDs support multiple credential data formats including W3C Verifiable Credentials, ISO 18013 digital credentials, and EBSI Legal Entity formats. Entries carry immutable timestamps that record when an entity was registered and when it ceased to be valid, enabling time-bound verification — a verifier can confirm that an issuer was trusted at the moment a credential was issued, even if that issuer has since been removed.

***

## Decentralized Trusted Lists (DTLs)

DTLs extend the DPKD model with reputational trust endorsements. Where DPKDs record *identity and keys*, DTLs record *who vouches for whom, and for what purpose*.

A DTL is a multi-level smart contract where entities endorse trust to other entities for general or specific purposes. Like DPKDs, DTLs use immutable timestamps — if an entity's reputational endorsement is removed, any credential issued by that entity after the removal timestamp can be rejected.

DTLs are designed to be self-managed by the entities that issue and receive endorsements, with no central operator required. They interoperate with DPKDs: a DID resolved via a DPKD can be cross-referenced against a DTL to apply additional trust filtering.

***

## Access recovery

In the DRoT model, entities always retain a path to recover access. The entity that originally endorsed trust can update or restore the endorsed entity's access when needed. This doesn't mean the endorser controls the endorsed entity — it means the trust relationship can be repaired if keys are rotated or lost.

***

## Credential-embedded registry pointers (mDocs)

For ISO 18013 mDoc credentials, issuers can embed a pointer to the Verification Registry directly in the credential. The pointer is a base58-encoded reference in a custom namespace within the credential document.

When a verifier encounters this pointer, the revocation check resolves directly to the referenced on-chain registry — no external configuration or lookup required. This is the preferred approach for mDoc issuers: the credential carries its own verification path, independent of how the verifier is configured.

For credentials without an embedded pointer, the verification pipeline falls back to issuer-configured revocation methods.

***

## Next steps

[**DID Method did:lac1**](/trust-registry/did-method/did-method-did-lac1) — Blerify's DID method builds on top of the DRoT infrastructure: resolving issuer DIDs traces back to keys registered in the ChainOfTrust.

See also: [DID Controller](/trust-registry/did-method/did-controller) — how smart contracts manage DID operations on supported registries.


# DID Method


# DID Controller

Blerify DID Controller is a set of contracts designed to administer [DIDs](https://www.w3.org/TR/did-core/) whose [DID Registry](https://www.w3.org/TR/did-core/#dfn-verifiable-data-registry) is built on top of a blockchain network (e.g. [lac](https://github.com/lacchain/lacchain-did-registry/tree/master?tab=readme-ov-file#lacchain-did-method),[lac1](https://github.com/lacchain/LACChain-identity-contracts/blob/master/DidSpecs.md) and [ethr](https://github.com/decentralized-identity/ethr-did-resolver/blob/master/doc/did-method-spec.md) DID methods). Setting a contract instance of the DID Controller as the controller of a specific DID will allow to manage the [services](https://www.w3.org/TR/did-core/#services) and [verifications methods](https://www.w3.org/TR/did-core/#verification-methods) associated to such DID.

### **Terminology:**

* Admin
* Manager
* Assignor

### **Capability:**

Refers to a privilege inherently owned or granted. Such Capabilities are:

* Assertion
* Authentication
* Key Agreement
* Capability Invocation
* Capability Delegation
* Services

### **Base Considerations:**

There are three main levels of control:

* Admin level: Any actor with this privilege has full control over a particular identity instance living on a specified DID Registry, can do any action
* Capability Manager level: Any actor with this privilege can only assign pre defined roles for [capabilities](https://github.com/BlerifyPlatform/did-controller/blob/main/docs/functional/DIDController.md#capability) or custom ones to "assignors"
* Assignor level: Actor assigned with this privilege can just call the DID Registry through the DID Controller contract and just add a [verification relationship](https://www.w3.org/TR/did-core/#verification-relationships) or a [service](https://www.w3.org/TR/did-core/#services) to a user

### **Smart Contract Considerations:**

* Due to the roles feature that DID Controller has it allows multiple agents controlling a particular DID Registry at the same time.
* Due to gas limitations, full verification of the payload to be relayed is not made. The contract just resolves the type of property to be added to the DID Document (verification method,service, controller management action) and determines whether the agent calling the contract is authorized to perform such action.


# DID Method did:lac1

The verifiability of digital credentials depends on the cryptographic signatures of **issuers** and **subjects**. Historically, **X.509 certificates** have been used for authentication, primarily in server and application security. However, these certificates lack the **scalability and flexibility** needed for digital credentials. A more scalable approach requires **decentralized identifiers (DIDs)**, which allow entities to manage multiple cryptographic key pairs while supporting **key rotation, revocation, and multiple endpoints**. This makes DIDs an ideal solution for modern digital credential ecosystems.

In compliance with the [W3C DID Core specifications](https://www.w3.org/TR/did-core/), we have proposed a new **DID method did:lac1, designed for scalability, security, and interoperability.** It builds upon the [ethr DID method ](https://github.com/decentralized-identity/ethr-did-resolver/blob/master/doc/did-method-spec.md)and the [LAC DID method](https://github.com/lacchain/lacchain-did-registry/blob/master/DID_SPEC.md), introducing key enhancements for greater precision and transparency. Our method enables the **encoding of the exact resolution path within the DID itself**, ensuring seamless access to the underlying **DID registry**. Key improvements over **did:ethr, did:lac**, and other did methods include:

* **Backwards Revocation Time Support:** This feature allows a **DID controller** to revoke a key not only from the moment of revocation but also retroactively, specifying a time in the past (t₁) after which the key is considered revoked. This is particularly useful when revoking a key without invalidating all cryptographically verifiable statements signed with it—only those issued after t₁ are affected. Key benefits are:
  * **Transparency**: Since revocations are recorded on the blockchain, all key changes are fully **traceable and auditable**.
  * **Key Compromise Scenarios**: If a key associated with a DID is compromised, the controller has two options:
    1. **Full revocation**, which invalidates **all** statements signed with that key.
    2. **Selective revocation**, where the controller specifies a past date when the key became invalid (e.g., if a vulnerability was identified X days ago, only statements issued **after X days ago** are revoked, while earlier statements remain valid). To ensure a verifier can trust that a cryptographically verifiable statement was made **before X days ago**, the statement should include a **proof of time**, such as a timestamp anchored to a blockchain. This allows verifiers to confirm the document’s existence at a specific point in time.
* **Direct DID Registry Resolution:** Our method encodes the exact path to the **DID registry** within the DID itself, eliminating the need for additional lookups and ensuring seamless resolution.
* **Backward Compatibility & Upgradability**: Enhancements to the DID method are designed to be **fully backward compatible**, ensuring continued support for existing implementations while allowing for future improvements.
* **DID Migration Support**: Through the **also Known** as attribute, our method enables **smooth migration** to a different DID, ensuring identity continuity without disrupting existing verifiable interactions.

By integrating these innovations, our **DID method** enhances **trust, transparency, and flexibility**, making it a powerful solution for verifiable credentials and decentralized identity management

***


# Changelog


# Blerify APP


# APP Versión 3.19.0 (386)

### **We are excited to announce a series of updates and improvements designed to make our platform more versatile, efficient, and user-friendly.** Here's a rundown of what's new:

* Verifiable Credential of the membership type.
* Create benefit (discount / group / price).
* Associate benefit(s) with Verifiable Credentials.
* Wallet integration.
* Verifier (view Benefits).


# APP Versión: 3.27.0

### Mejoras en la Interfaz

* Nuevos Textos Descriptivos para Perfiles: Se mejoraron las descripciones en la configuración de perfiles para mayor claridad y facilidad de uso.

### QR y PoV Mobile Ajustes en la Generación de QR para PoV Mobile:

* Corrección y optimización en la visualización y escaneo de puntos de verificación mobile.


# APP Versión: 3.36.1

### Novedades y mejoras:

* Mejoras en la creación de alias/perfil: Se optimizó el método de generación de alias para ofrecer una experiencia más fluida y personalizada.
* Mayor transparencia en el punto de verificación: Se agregó un botón para visualizar fácilmente con quién se comparte la información de una credencial.

### Cambios anteriores integrados:

* Versión 3.32.0
  * Mejora en la interfaz del flujo de punto de verificación.
* Versión 3.31.0
  * Texto personalizado al compartir credenciales en redes sociales.
  * Nombres descriptivos actualizados (por ejemplo: "Perfil Verificador", "Conexiones").
  * Mejora en la experiencia del usuario en el resultado del punto de verificación.
  * Nuevo acceso directo a la sección “Información compartida”.
  * Ajustes visuales en la sección de número de versión, con acceso directo a nuestro sitio web para ver nuevas funcionalidades, correcciones y noticias.
* Versión 3.30.0
  * Se muestra el nombre del comercio y el nombre del producto en la sección "Beneficios".


# APP Versión: 3.43.0

En esta versión, incorporamos funcionalidades clave que mejoran la comunicación entre las organizaciones y los usuarios permitiendo optimizar la experiencia de usuario en nuestra Wallet:

### Recepción de notificaciones en la Wallet

* Ahora los usuarios pueden recibir notificaciones directamente dentro de la Wallet. Ahora las organizaciones estarán más cerca de sus usuarios, enviándole diferentes novedades.

### Push Notifications

* Se implementó el sistema de Push Notifications, permitiendo notificar al usuario aún si no está dentro de la Wallet en ese momento. Esto garantiza mayor alcance y una experiencia más fluida.


# APP Versión: 3.45.0

### Actualizaciones de la Wallet Blerify

* **Notificaciones mejoradas:** la app ahora permite una gestión más clara de notificaciones, incluyendo mejoras en seguridad, ajustes de interfaz y bandeja histórica para notificaciones leídas.
* **Rediseño visual:** nuevas imágenes y estilos en la interfaz para una experiencia más moderna y clara para los usuarios.
* **Mejoras funcionales:**
  * Validación de credenciales revocadas.
  * Inclusión del nombre de la organización en las notificaciones.
  * Ajustes en la UI para mejorar la visualización de mensajes y novedades.
  * Mockups de las nuevas secciones **Novedades** y **Descubre Blerify**, que próximamente estarán disponibles en la app.
* **Mejoras Seguridad:** Mejoras en la seguridad de la wallet.


# Panamá Conecta Credenciales

Updates and Improvements

<div align="left"><figure><img src="https://239097222-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FMvCxPFtGg0Vjq2LzXN1U%2Fuploads%2Fgit-blob-e2e74bc24fed0ff53197154606b3d82aa0c04a49%2Fimage.png?alt=media" alt="" width="188"><figcaption></figcaption></figure></div>

<div align="left"><figure><img src="https://239097222-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FMvCxPFtGg0Vjq2LzXN1U%2Fuploads%2Fgit-blob-2271d82180574287bc95ba854ec37eaeba1c7d6d%2FiMockup%20-%20Google%20Pixel%208%20Pro-1.png?alt=media" alt="" width="188"><figcaption></figcaption></figure></div>


# APP Versión 3.50.1

Versiones:\
\- Android: 3.50.1 (752)\
\- iOs: 3.50.1 (754)

### **Release Notes**

#### **UI / UX**

* Ajustes estéticos en la interfaz de usuario en modo oscuro para mejorar la consistencia visual.
* Corrección en la visualización del valor **“ND - APATRIADOS”** en el campo *nationality*.
* Actualización del template de verificación de correo electrónico dentro del flujo de **ID Level**.

***

#### **Backend / API**

* Seguimiento y validación de la corrección en el envío de notificaciones vía API.
* Ajuste en el manejo del campo de fecha dentro de las notificaciones (corrección aplicada por Sertracen).

***

#### **Mobile**

* Corrección para permitir que el **Reader** realice correctamente la verificación de credenciales del **Holder**.
* Actualización automática para verificar credenciales revocadas.
* Cambio de Número administrativo / Número de RUC.


# APP Versión 4.0.3

Versiones:\
\- Android: 4.0.3 (785)\
\- iOs: 4.0.3 (785)

### **Release Notes**

•⁠ ⁠Corrección URL (apuntar a producción).\
•⁠ ⁠⁠Solución al problema de recepción de MDL:

* Ahora no hace falta reiniciar la APP.
* Se ha forzado la reactivación del deeplink al acceder a la página de inicio.
* Se han optimizado temas de caché en producción.


# Web Portal


# Web Portal Release 1.5

### **Verifiable Credentials (VCs) Management** <a href="#id-0-toc-title" id="id-0-toc-title"></a>

* **Membership VC with Benefits:** You can now create and associate exclusive benefits such as discounts, special pricing, or custom groupings with membership credentials.
* **Gov ID VC:** Exclusive for government organizations.
* **VC Package Purchase Management:** You can now purchase a package, and we will assign the number of VCs you have bought.
* **Improvements in VC Creation Flow:** VCs can now only be created through the Getting Started guide.

### **Data Update and Management** <a href="#id-1-toc-title" id="id-1-toc-title"></a>

* **Smart Database Upload:** If an email already exists in the database, fields such as names will be updated automatically.
* **New Registration Form:** Add individual contacts quickly and easily.
* **Recent Recipients View:** Easily check the most recent recipients.

### **Virtual Wallet Improvements** <a href="#id-2-toc-title" id="id-2-toc-title"></a>

* **Homepage Redesign:** New interface with visual and text adjustments for a better user experience.
* **Faster VC Issuance:** Credentials will automatically appear in the wallet upon issuance.
* **Safe Rollback:** If an approved issuance fails, the system will automatically revert the process.
* **Issuer Images:** Included in the wallet and verifier for a more personalized experience.

### **File and Document Optimization** <a href="#id-3-toc-title" id="id-3-toc-title"></a>

* **HTML to PDF Conversion:** New feature to improve PDF viewing compatibility.
* **Improved PDF Upload and Viewing:** Faster and more efficient.
* **Private Template Viewing:** Easily access your custom templates.

### **Performance and Security** <a href="#id-4-toc-title" id="id-4-toc-title"></a>

* **Performance Improvement:** Reduced loading times and optimized credential issuance.
* **Optional Attributes in POV Rules:** Greater flexibility in rule configuration.
* **Signature and Approval Management:** Improved visualization and approval flow for credentials.
* **JWT Fix After DID Key Renewal:** Greater stability and security in authentications.

### **Benefits Section** <a href="#id-5-toc-title" id="id-5-toc-title"></a>

* **New Membership Credentials:** It is now possible to issue verifiable membership credentials within the wallet.
* **Benefit Management from the Portal:** Added the option to create benefits such as discounts, groupings, or special pricing directly from the administrative portal.
* **Association of Benefits to Credentials:** Benefits can be linked to specific credentials, allowing for a personalized user experience.
* **Portal-Wallet Integration:** Automatic synchronization between the portal and the wallet to ensure benefits are always up to date.
* **Verifiable Web for Benefits:** New functionality that allows users to view the benefits associated with a credential through Verifiable Web.
* **Benefit Activation from the Gateway:** Added support for activating and managing benefits through gateway integrations.

### Credentials <a href="#id-6-toc-title" id="id-6-toc-title"></a>

* **VC Issuance Limit:** It is now possible to define and view the issuance limit for verifiable credentials (VCs).
* **OpenID4VP Compatibility:** Support for authentication and presentation of verifiable credentials using OpenID4VP.
* **New VC Types:**
  * **Membership:** Creation and association of benefits with membership-type credentials.
  * **Gov ID:** Exclusive for government organizations.
* **VC Creation Flow:** All credentials must now be generated through the Getting Started section.
* **Automatic Data Update:** If a database with existing emails is uploaded, names will be updated automatically (backend integration).
* **Single Contact Registration:** Added a form to register a single contact.
* **Automatic Wallet Display:** Approved VCs will appear immediately in the Wallet.
* **Rollback on Approval Failures:** If an approved VC fails, the system will execute an automatic rollback.
* **Issuer Image:** The issuer's image is now included in the Wallet (RoT) and the verifier.
* **Recipient History:** Lists the most recently added recipients.
* **Persistent User Selection:** When changing pages, selected users will not be deselected.
* **VC Metadata:** Added created\_at and updated\_at in the frontend with creation date.

<br>

### Flow and UX Improvements <a href="#id-7-toc-title" id="id-7-toc-title"></a>

* **POV Flow:**
  * Adjustment in the instruction code to improve the user experience.
  * Support for optional attributes in POV rules.
  * Integration with the Presentation Exchange v2.0.0 standard.
* **HTML to PDF Conversion:** Added HTML to PDF conversion with Node.js.
* **PDF Loading Optimization:** Improved performance in loading and viewing PDF documents.
* **Private Template Support:** Viewing of generic private templates.
* **Email Notification on VC Issuance:** An automatic email is sent when a credential is issued to an existing DID.
* **Automatic Signature on Credentials:** Credentials can now be created with automatic signing.
* **Improved VC Reception Time in Wallet:** Optimized backend-wallet integration to reduce latency.
* **Unification of VC Creation Flows:** Integration of Getting Started with creation from an empty project.
* **JWT Integration:** Fixed JWT handling when a DID renews its expired keys.

### Backend & API Improvements <a href="#id-8-toc-title" id="id-8-toc-title"></a>

* **Approvals:**
  * New API to update and return the number of approvers required in a flow.
  * Fixed the display of VCs approved by a signer, preventing them from remaining pending for another Approver.
* **QR Generation:** Improved QR code display within generated PDFs.
* **Schema Validations in Backend:**
  * Implementation of networknt/json-schema-validator for schema validation.
  * Ability to customize formats, validations, and vocabularies.

### **Wallet Interface Improvements** <a href="#id-9-toc-title" id="id-9-toc-title"></a>

* **Home Screen Redesign:** New layout and optimized experience for users.
* **Visual and Text Adjustments:** Improved readability and consistency of texts within the app.

### **Wallet Connection Management** <a href="#id-10-toc-title" id="id-10-toc-title"></a>

* **Relocation of "Connections":** The Connections section is now located within Profile for better organization.
* **Interaction with Organizations:**
  * Clicking on an organization the user is connected to enables new management and viewing options.


# Web Portal Release 1.6

Nueva Versión Portal Web

### Gestión y seguridad de Credenciales Verificables Renovación de Keys para Organizaciones:

* Mejora en la administración de claves para mayor seguridad y estabilidad.
* Acceso Directo a Información de ProjectId y TemplateId: Ahora disponible desde el frontend para una gestión más eficiente.
* Mejora en Seguridad para el Acceso a Template IDs: Protección reforzada en la manipulación de plantillas.
* Migración del Portal Web a TypeScript: Código más robusto, seguro y escalable.
* Absorción de Autenticación en el Core: Integración optimizada para una gestión centralizada.
* Integraciones y Configuración Generación de Endpoints para Configuración de Cuentas de Servicio: Integración más fluida entre backend y frontend.
* Optimización de Archivos y Documentos Incorporación de Plantillas sin PDF Asociado: Se permite la creación y uso de plantillas sin necesidad de un archivo PDF.
* Nuevo Flujo de Creación para Plantillas sin PDF: Interfaz optimizada para facilitar su configuración.

### Mejoras de UX y diseño

* Ajustes en Responsive para Dimensiones >1200px: Se optimizó la visualización en pantallas grandes.
* Ajustes de texto en la sección de Beneficios: Mayor claridad y coherencia en la presentación de los beneficios para los usuarios.


# Web Portal Release 1.7

Nueva Versión Portal Web

### **Seguridad**

* Incorporación de Captcha en páginas públicas para evitar automatización maliciosa.
* Corrección en métodos signed del DID controller.
* Mayor seguridad en puntos de verificación web mediante refuerzo en el código de instrucción.
* Mejora en seguimiento de logs para detectar y responder más rápido ante errores.

### Roles y Permisos

* Permisos limitados para rol "Gestor de proyecto": ahora solo puede acceder a funciones específicas y no administrativas.

### Mejoras UX / UI

* Indicación visual del límite de caracteres en múltiples campos del sistema.
* Mejora en la búsqueda por tipo de proyectos y credenciales.
* Mejoras en la UI de información de proyectos.
* Mejoras en el flujo de emisión con firma automática.
* Mejora en el flujo de creación de credenciales.
* Mejora UX/UI al resolver credenciales, incluyendo componentes visuales más claros.
* Mejora UX en el template de email al reclamar credenciales.
* Mejoras UX para templates especiales: para estos casos, ahora solo se permite cargar destinatarios desde archivos base de datos.
* En el módulo sujeto, ahora hay cards personalizadas por color.
* Mejor UX en el flujo de aprobación automática de credenciales.
* Mejora en el seguimiento visual de proyectos custom creados desde organizaciones clientes.
* Mejora UX para permitir configuración de mensajes al compartir en redes sociales.
* Mejora en el flujo con botón de acción en lugar de QR, útil en mobile donde está la wallet.
* Corrección para mejorar compatibilidad con navegadores Android.

### Infraestructura / Lógica

* Mejora del flujo de emisión mediante estructura de control más robusta.
* Corrección en la actualización de la fecha de expiración de las credenciales.
* Mejora en el campo de carga de photo/portrait para credenciales.
* Incorporación de editor Wallet Rendering para credenciales tipo "Gobierno e Identidad".
* Mejora de verificabilidad en resolver: se muestra la URL del sitio de la organización emisora como parte del "root of trust".
* Actualización de campos en el schema de credenciales tipo membership.
* Incorporación de nuevo tipo de credencial "Identidad" compatible con estándar W3C.


# Web Portal Release 1.8

Nueva Versión Portal Web

En esta versión, incorporamos funcionalidades clave que mejoran la comunicación entre el portal y wallet, optimizando la experiencia de usuario en nuestra Wallet y sistemas asociados:

### Nuevo Módulo de Notificaciones

Se desarrolló e integró el módulo completo de Notificaciones, abarcando tanto el frontend como el backend. Este nuevo módulo permite:

* Crear, enviar y visualizar notificaciones en tiempo real.
* Gestionar distintos tipos de notificaciones desde el portal web.
* Registrar eventos clave del sistema asociados a usuarios y organizaciones, para tener un track de cada notificación, sabiendo si la misma fue enviada, entregada y leída.

### Mejora en el flujo de claim de credenciales

* Se reemplazó la metodología anterior basada en Mailbox por un flujo Without Mailbox, eliminando la dependencia del buzón interno. Este cambio implica:
* Reducción de fricción en el proceso de recuperación y uso de credenciales.
* Mejora en la velocidad y simplicidad del claim por parte de los usuarios.
* Mayor interoperabilidad con billeteras externas o descentralizadas.

### Además

Ahora las organizaciones no solo pueden enviar notificaciones desde el portal, sino también a través de nuestra API, lo que abre nuevas oportunidades para automatizar flujos y mejorar la comunicación con sus usuarios.


# Web Portal Release 1.9

### Actualizaciones del Portal Blerify

* **Metadata en Credenciales:** ahora las credenciales incluyen metainformación dinámica que mejora su trazabilidad y permite mayor flexibilidad en su uso.
* **Métricas optimizadas:** se añadieron nuevas visualizaciones y mejoras generales en el sistema de métricas para que las organizaciones tengan un mayor control y entendimiento sobre sus credenciales y verificaciones.
* **Autenticación en dos pasos (MFA):** se implementó el inicio de sesión con doble factor, reforzando la seguridad en el acceso al portal administrativo.
* **Flujo de Refresh Token:** se optimizó el proceso de renovación de tokens para garantizar sesiones más seguras y estables.
* **Seguridad reforzada:** se implementaron múltiples actualizaciones siguiendo buenas prácticas de OWASP, incluyendo validaciones de contraseña en recuperación, control de accesos, encriptación de datos, aislamiento de endpoints expuestos, protección contra inyección SQL, MFA en la app y ajustes generales en infraestructura y gateway.


