{ "openapi": "3.1.0", "info": { "title": "Didit Credentials API", "version": "1.0.0", "description": "OpenID4VCI 1.0 issuance + OpenID4VP 1.0 verification (SD-JWT VC, draft-16) with a multi-tenant management plane: credentials, presentations, verifications, a trust registry (frameworks, trusted issuers, relying parties), and issuer key rotation. Management endpoints authenticate with a tenant API key (`Authorization: Bearer `); the OpenID4VCI/VP protocol + holder/verifier endpoints are public." }, "paths": { "/v1/credential-schemas": { "get": { "operationId": "v1_credential_schemas_retrieve", "tags": [ "Credentials" ], "responses": { "200": { "description": "Array of the tenant's credential schemas, ordered by creation time descending.", "content": { "application/json": { "schema": { "type": "object", "properties": { "items": { "type": "array", "description": "The credential schemas owned by this tenant. Each item is a full schema object.", "items": { "type": "object", "properties": { "uuid": { "type": "string", "description": "Unique identifier of the credential schema. Reference this when creating a credential template.", "example": "3a1f9c20-8d4e-4b1a-9f2c-7e6d5a4b3c2d", "format": "uuid" }, "name": { "type": "string", "description": "Human-readable name of the schema. Unique per tenant together with version.", "example": "Loyalty Membership" }, "version": { "type": "string", "description": "Schema version string. Unique per tenant together with name. Defaults to \"1.0\".", "example": "1.0" }, "vct": { "type": "string", "description": "Verifiable Credential Type identifier embedded in every issued credential of this schema. Wallets and verifiers match on this value to recognise the credential.", "example": "https://acme-air.didit.me/credentials/loyalty-membership" }, "format": { "type": "string", "description": "Credential wire format. `sd_jwt_vc` is the IETF SD-JWT VC format (the default); `mdoc` is ISO/IEC 18013-5 mdoc; `w3c` is W3C Verifiable Credentials 2.0.", "example": "sd_jwt_vc", "enum": [ "sd_jwt_vc", "mdoc", "w3c" ] }, "attributes": { "type": "array", "description": "Ordered list of the claim attributes that make up this credential. Each attribute declares its claim name, value type, and whether it is selectively disclosable.", "items": { "type": "object", "properties": { "name": { "type": "string", "description": "Claim name as it appears in the issued credential payload.", "example": "given_name" }, "type": { "type": "string", "description": "Logical data type of the claim value (e.g. string, number, boolean, date).", "example": "string" }, "sd": { "type": "boolean", "description": "Selective-disclosure flag. When true the claim is individually disclosable by the holder (hidden behind a salted digest in the SD-JWT) so the holder can choose whether to reveal it at presentation time. When false the claim is always disclosed. Defaults to true.", "example": true } } } }, "revocable": { "type": "boolean", "description": "Whether credentials issued under this schema can be revoked or suspended via a status list. Immutable after the schema is created.", "example": true }, "branding": { "type": "object", "description": "Optional free-form wallet branding object (e.g. background color, text color, logo URI) used by holder wallets to render the credential. Defaults to an empty object.", "example": { "background_color": "#2567FF", "text_color": "#FFFFFF", "logo_uri": "https://acme-air.didit.me/logo.png" }, "properties": {} }, "created_at": { "type": "string", "description": "ISO 8601 timestamp when the schema was created.", "example": "2026-06-30T12:00:00+00:00", "format": "date-time" } } } } } } } } } }, "summary": "List credential schemas", "description": "List every credential schema defined for the authenticated tenant, newest first. A credential schema declares the verifiable-credential type (vct), the wire format, the claim attributes (with their selective-disclosure flags), whether issued credentials can be revoked, and optional wallet branding. Use it to discover the schemas available before creating a template or issuing a credential. Scoped to the tenant derived from the API key, so schemas belonging to other tenants are never returned. Requires Authorization: Bearer .", "security": [ { "bearerAuth": [] } ] }, "post": { "operationId": "v1_credential_schemas_create", "tags": [ "Credentials" ], "responses": { "200": { "description": "No response body" }, "201": { "description": "The credential schema was created. Returns the full schema object.", "content": { "application/json": { "schema": { "type": "object", "properties": { "uuid": { "type": "string", "description": "Unique identifier of the newly created schema. Reference this when creating a credential template.", "example": "3a1f9c20-8d4e-4b1a-9f2c-7e6d5a4b3c2d", "format": "uuid" }, "name": { "type": "string", "description": "Human-readable name of the schema.", "example": "Loyalty Membership" }, "version": { "type": "string", "description": "Schema version string.", "example": "1.0" }, "vct": { "type": "string", "description": "Verifiable Credential Type identifier embedded in every issued credential of this schema.", "example": "https://acme-air.didit.me/credentials/loyalty-membership" }, "format": { "type": "string", "description": "Credential wire format.", "example": "sd_jwt_vc", "enum": [ "sd_jwt_vc", "mdoc", "w3c" ] }, "attributes": { "type": "array", "description": "The claim attributes that make up this credential.", "items": { "type": "object", "properties": { "name": { "type": "string", "description": "Claim name as it appears in the issued credential payload.", "example": "given_name" }, "type": { "type": "string", "description": "Logical data type of the claim value.", "example": "string" }, "sd": { "type": "boolean", "description": "Selective-disclosure flag for this claim.", "example": true } } } }, "revocable": { "type": "boolean", "description": "Whether credentials issued under this schema can be revoked or suspended.", "example": true }, "branding": { "type": "object", "description": "Wallet branding object used by holder wallets to render the credential.", "example": { "background_color": "#2567FF", "text_color": "#FFFFFF", "logo_uri": "https://acme-air.didit.me/logo.png" }, "properties": {} }, "created_at": { "type": "string", "description": "ISO 8601 timestamp when the schema was created.", "example": "2026-06-30T12:00:00+00:00", "format": "date-time" } } } } } } }, "summary": "Create a credential schema", "description": "Create a new credential schema for the authenticated tenant. The schema defines the verifiable-credential type, wire format, claim attributes (and which are selectively disclosable), whether issued credentials are revocable, and optional wallet branding. The combination of name + version must be unique within the tenant. Once created, the schema can be referenced by credential templates. The `revocable` flag is immutable after creation. Requires Authorization: Bearer .", "security": [ { "bearerAuth": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "name": { "type": "string", "description": "Human-readable name of the schema. Must be unique per tenant together with version.", "example": "Loyalty Membership" }, "version": { "type": "string", "description": "Schema version string. Must be unique per tenant together with name. Defaults to \"1.0\".", "example": "1.0" }, "vct": { "type": "string", "description": "Verifiable Credential Type identifier to embed in every credential issued under this schema. Wallets and verifiers match on this value.", "example": "https://acme-air.didit.me/credentials/loyalty-membership" }, "format": { "type": "string", "description": "Credential wire format. `sd_jwt_vc` (IETF SD-JWT VC) is the default; `mdoc` is ISO/IEC 18013-5; `w3c` is W3C VC 2.0. Defaults to `sd_jwt_vc`.", "example": "sd_jwt_vc", "enum": [ "sd_jwt_vc", "mdoc", "w3c" ] }, "attributes": { "type": "array", "description": "List of the claim attributes that make up this credential. Each attribute declares a claim name, its value type, and whether it is selectively disclosable. Defaults to an empty list.", "items": { "type": "object", "properties": { "name": { "type": "string", "description": "Claim name as it will appear in the issued credential payload.", "example": "given_name" }, "type": { "type": "string", "description": "Logical data type of the claim value (e.g. string, number, boolean, date).", "example": "string" }, "sd": { "type": "boolean", "description": "Selective-disclosure flag. When true (the default) the claim is individually disclosable by the holder \u2014 hidden behind a salted digest so the holder can choose whether to reveal it at presentation time. When false the claim is always disclosed.", "example": true } }, "required": [ "name", "type" ] } }, "revocable": { "type": "boolean", "description": "Whether credentials issued under this schema can be revoked or suspended through a status list. Immutable after creation. Defaults to true.", "example": true }, "branding": { "type": "object", "description": "Optional free-form wallet branding object (e.g. background color, text color, logo URI) used by holder wallets to render the credential. Defaults to an empty object.", "example": { "background_color": "#2567FF", "text_color": "#FFFFFF", "logo_uri": "https://acme-air.didit.me/logo.png" }, "properties": {} } }, "required": [ "name", "vct" ] } } } } } }, "/v1/credential-templates": { "get": { "operationId": "v1_credential_templates_retrieve", "tags": [ "Credentials" ], "responses": { "200": { "description": "Array of the tenant's credential templates, ordered by creation time descending.", "content": { "application/json": { "schema": { "type": "object", "properties": { "items": { "type": "array", "description": "The credential templates owned by this tenant. Each item is a full template object.", "items": { "type": "object", "properties": { "uuid": { "type": "string", "description": "Unique identifier of the credential template. Pass this as `template_id` when issuing a credential.", "example": "b2c3d4e5-6f7a-4b8c-9d0e-1f2a3b4c5d6e", "format": "uuid" }, "schema": { "type": "string", "description": "UUID of the credential schema this template is bound to. The schema determines the vct, format, and claim attributes of issued credentials.", "example": "3a1f9c20-8d4e-4b1a-9f2c-7e6d5a4b3c2d", "format": "uuid" }, "name": { "type": "string", "description": "Human-readable name of the template.", "example": "Gold-tier membership (1 year)" }, "validity_seconds": { "type": "integer", "description": "Lifetime, in seconds, applied to credentials issued from this template; it sets each credential's expires_at to issuance time plus this many seconds. Defaults to 31536000 (365 days).", "example": 31536000 }, "created_at": { "type": "string", "description": "ISO 8601 timestamp when the template was created.", "example": "2026-06-30T12:00:00+00:00", "format": "date-time" } } } } } } } } } }, "summary": "List credential templates", "description": "List every credential template defined for the authenticated tenant, newest first. A credential template binds a credential schema to a reusable issuance profile: a name and a validity window. Issuing a credential references a template by its uuid. Scoped to the tenant derived from the API key. Requires Authorization: Bearer .", "security": [ { "bearerAuth": [] } ] }, "post": { "operationId": "v1_credential_templates_create", "tags": [ "Credentials" ], "responses": { "200": { "description": "No response body" }, "201": { "description": "The credential template was created. Returns the full template object.", "content": { "application/json": { "schema": { "type": "object", "properties": { "uuid": { "type": "string", "description": "Unique identifier of the newly created template. Pass this as `template_id` when issuing a credential.", "example": "b2c3d4e5-6f7a-4b8c-9d0e-1f2a3b4c5d6e", "format": "uuid" }, "schema": { "type": "string", "description": "UUID of the credential schema this template is bound to.", "example": "3a1f9c20-8d4e-4b1a-9f2c-7e6d5a4b3c2d", "format": "uuid" }, "name": { "type": "string", "description": "Human-readable name of the template.", "example": "Gold-tier membership (1 year)" }, "validity_seconds": { "type": "integer", "description": "Lifetime in seconds applied to credentials issued from this template.", "example": 31536000 }, "created_at": { "type": "string", "description": "ISO 8601 timestamp when the template was created.", "example": "2026-06-30T12:00:00+00:00", "format": "date-time" } } } } } } }, "summary": "Create a credential template", "description": "Create a credential template for the authenticated tenant. A template binds an existing credential schema to a reusable issuance profile \u2014 a name and a validity window (in seconds) \u2014 that issuance references by uuid. The referenced schema must belong to the same tenant, otherwise the request returns 404. Requires Authorization: Bearer .", "security": [ { "bearerAuth": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "schema": { "type": "string", "description": "UUID of the credential schema this template binds to. Must reference a schema owned by the authenticated tenant; otherwise the request returns 404.", "example": "3a1f9c20-8d4e-4b1a-9f2c-7e6d5a4b3c2d", "format": "uuid" }, "name": { "type": "string", "description": "Human-readable name of the template.", "example": "Gold-tier membership (1 year)" }, "validity_seconds": { "type": "integer", "description": "Lifetime, in seconds, to apply to credentials issued from this template (sets expires_at = issuance time + validity_seconds). Defaults to 31536000 (365 days).", "example": 31536000 } }, "required": [ "schema", "name" ] } } } } } }, "/v1/credentials/issue": { "post": { "operationId": "v1_credentials_issue_create", "tags": [ "Credentials" ], "responses": { "200": { "description": "No response body" }, "201": { "description": "The credential was issued and signed. Returns the credential identifier, its type, the serialized SD-JWT, and its index in the tenant's status list.", "content": { "application/json": { "schema": { "type": "object", "properties": { "credential_id": { "type": "string", "description": "UUID of the newly issued credential record. Use it with the retrieval and lifecycle (revoke/suspend/reactivate) endpoints.", "example": "c1d2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f", "format": "uuid" }, "vct": { "type": "string", "description": "Verifiable Credential Type of the issued credential, copied from the template's schema.", "example": "https://acme-air.didit.me/credentials/loyalty-membership" }, "sd_jwt": { "type": "string", "description": "The serialized credential as a `dc+sd-jwt` string: the issuer-signed SD-JWT followed by the `~`-separated selective-disclosure parts (and key-binding placeholder). This is the value delivered to the holder wallet.", "example": "eyJhbGciOiJFUzI1NiIsInR5cCI6ImRjK3NkLWp3dCJ9.eyJ2Y3QiOiJ...~WyJzYWx0IiwiZ2l2ZW5fbmFtZSIsIkFkYSJd~WyJzYWx0IiwiZmFtaWx5X25hbWUiLCJMb3ZlbGFjZSJd~" }, "status_idx": { "type": [ "integer", "null" ], "description": "Index assigned to this credential in the tenant's IETF Token Status List. Lifecycle changes (revoke/suspend/reactivate) flip the bit at this index. Null when the credential's schema is not revocable.", "example": 4213 } } } } } } }, "summary": "Issue a credential directly (management)", "description": "Issue a verifiable credential to a holder from a credential template. Provide the template id, the holder's public JWK (used as the credential's key-binding confirmation key so only that holder can present it), and the claim values keyed by claim name as declared on the template's schema. Optionally pass a source_session_id to bridge a Didit verification session to the issued credential (the capture-to-issue link). The credential is signed with the tenant's active signing key and returned as a `dc+sd-jwt` string. The referenced template must belong to the authenticated tenant, otherwise the request returns 404. Requires Authorization: Bearer .", "security": [ { "bearerAuth": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "template_id": { "type": "string", "description": "UUID of the credential template to issue from. Must reference a template owned by the authenticated tenant; otherwise the request returns 404. The template's schema determines the vct, format, and which claims are selectively disclosable.", "example": "b2c3d4e5-6f7a-4b8c-9d0e-1f2a3b4c5d6e", "format": "uuid" }, "holder_jwk": { "type": "object", "description": "The holder's public key as a JSON Web Key (JWK). It is bound into the credential as the key-binding confirmation (cnf) key, so only the wallet holding the matching private key can later present the credential.", "example": { "kty": "EC", "crv": "P-256", "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0" }, "properties": {} }, "claims": { "type": "object", "description": "The credential claim values, keyed by claim name. Each key should match an attribute name declared on the template's schema; the corresponding value populates that claim in the issued credential.", "example": { "given_name": "Ada", "family_name": "Lovelace", "membership_tier": "gold" }, "properties": {} }, "source_session_id": { "type": "string", "description": "Optional identifier of the Didit verification session whose captured data backs this credential (the capture-to-issue bridge). Stored on the issued credential for traceability. Defaults to an empty string.", "example": "11111111-2222-3333-4444-555555555555" } }, "required": [ "template_id", "holder_jwk", "claims" ] } } } } } }, "/v1/credentials": { "get": { "operationId": "v1_credentials_retrieve", "tags": [ "Credentials" ], "responses": { "200": { "description": "Array of the tenant's issued credentials, ordered by issuance time descending.", "content": { "application/json": { "schema": { "type": "object", "properties": { "items": { "type": "array", "description": "The issued-credential management records owned by this tenant.", "items": { "type": "object", "properties": { "uuid": { "type": "string", "description": "Unique identifier of the issued credential (the credential_id returned at issuance). Use it with the detail and lifecycle endpoints.", "example": "c1d2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f", "format": "uuid" }, "vct": { "type": "string", "description": "Verifiable Credential Type of the issued credential.", "example": "https://acme-air.didit.me/credentials/loyalty-membership" }, "iss": { "type": "string", "description": "Issuer identifier (the https iss URL) of the tenant that issued the credential.", "example": "https://acme-air.didit.me" }, "status": { "type": "string", "description": "Lifecycle status of the credential. `issued` is valid and verifiable; `suspended` is temporarily disabled (reversible via reactivate); `revoked` is permanently invalidated; `expired` is past its expires_at.", "example": "issued", "enum": [ "issued", "suspended", "revoked", "expired" ] }, "status_idx": { "type": [ "integer", "null" ], "description": "Index of this credential in the tenant's IETF Token Status List, where its revocation/suspension bit lives. Null for non-revocable credentials.", "example": 4213 }, "issued_at": { "type": "string", "description": "ISO 8601 timestamp when the credential was issued.", "example": "2026-06-30T12:00:00+00:00", "format": "date-time" }, "expires_at": { "type": [ "string", "null" ], "description": "ISO 8601 timestamp when the credential expires (issuance time plus the template's validity_seconds). Null if the credential has no expiry.", "example": "2027-06-30T12:00:00+00:00", "format": "date-time" }, "holder_cnf": { "type": "object", "description": "The holder key-binding confirmation (cnf) object \u2014 typically the holder's public JWK under a `jwk` key \u2014 that binds the credential to its holder, derived from the holder_jwk supplied at issuance.", "example": { "jwk": { "kty": "EC", "crv": "P-256", "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0" } }, "properties": {} }, "source_session_id": { "type": "string", "description": "Identifier of the Didit verification session this credential was bridged from, or an empty string if none was supplied at issuance.", "example": "11111111-2222-3333-4444-555555555555" } } } } } } } } } }, "summary": "List issued credentials", "description": "List every credential issued by the authenticated tenant, newest first. Each item is a management-view of an issued credential \u2014 its type, issuer, lifecycle status, status-list index, validity timestamps, holder key-binding, and the source session it was bridged from. The raw SD-JWT is not included (it is delivered only once at issuance). Scoped to the tenant derived from the API key. Requires Authorization: Bearer .", "security": [ { "bearerAuth": [] } ] } }, "/v1/credentials/{uuid}": { "get": { "operationId": "v1_credentials_retrieve_2", "parameters": [ { "name": "uuid", "in": "path", "required": true, "description": "UUID of the issued credential to retrieve. Must belong to the authenticated tenant; otherwise the request returns 404.", "schema": { "type": "string", "format": "uuid" } } ], "tags": [ "Credentials" ], "responses": { "200": { "description": "The issued-credential management record.", "content": { "application/json": { "schema": { "type": "object", "properties": { "uuid": { "type": "string", "description": "Unique identifier of the issued credential.", "example": "c1d2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f", "format": "uuid" }, "vct": { "type": "string", "description": "Verifiable Credential Type of the issued credential.", "example": "https://acme-air.didit.me/credentials/loyalty-membership" }, "iss": { "type": "string", "description": "Issuer identifier (the https iss URL) of the tenant that issued the credential.", "example": "https://acme-air.didit.me" }, "status": { "type": "string", "description": "Lifecycle status of the credential. `issued` is valid and verifiable; `suspended` is temporarily disabled (reversible); `revoked` is permanently invalidated; `expired` is past its expires_at.", "example": "issued", "enum": [ "issued", "suspended", "revoked", "expired" ] }, "status_idx": { "type": [ "integer", "null" ], "description": "Index of this credential in the tenant's IETF Token Status List. Null for non-revocable credentials.", "example": 4213 }, "issued_at": { "type": "string", "description": "ISO 8601 timestamp when the credential was issued.", "example": "2026-06-30T12:00:00+00:00", "format": "date-time" }, "expires_at": { "type": [ "string", "null" ], "description": "ISO 8601 timestamp when the credential expires. Null if the credential has no expiry.", "example": "2027-06-30T12:00:00+00:00", "format": "date-time" }, "holder_cnf": { "type": "object", "description": "The holder key-binding confirmation (cnf) object that binds the credential to its holder.", "example": { "jwk": { "kty": "EC", "crv": "P-256", "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0" } }, "properties": {} }, "source_session_id": { "type": "string", "description": "Identifier of the Didit verification session this credential was bridged from, or an empty string if none.", "example": "11111111-2222-3333-4444-555555555555" } } } } } } }, "summary": "Get an issued credential", "description": "Retrieve a single issued credential by its uuid, in the same management-view shape returned by the list endpoint. Use it to check a credential's current lifecycle status or its validity window. The lookup is scoped to the authenticated tenant; a credential belonging to a different tenant returns 404 (the cross-tenant access guard). Requires Authorization: Bearer .", "security": [ { "bearerAuth": [] } ] } }, "/v1/credentials/{uuid}/revoke": { "post": { "operationId": "v1_credentials_revoke_create", "parameters": [ { "name": "uuid", "in": "path", "required": true, "description": "UUID of the issued credential to revoke. Must belong to the authenticated tenant; otherwise the request returns 404.", "schema": { "type": "string", "format": "uuid" } } ], "tags": [ "Credentials" ], "responses": { "200": { "description": "The credential was revoked. Returns the updated issued-credential record with status `revoked`.", "content": { "application/json": { "schema": { "type": "object", "properties": { "uuid": { "type": "string", "description": "Unique identifier of the issued credential.", "example": "c1d2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f", "format": "uuid" }, "vct": { "type": "string", "description": "Verifiable Credential Type of the credential.", "example": "https://acme-air.didit.me/credentials/loyalty-membership" }, "iss": { "type": "string", "description": "Issuer identifier (the https iss URL) of the tenant that issued the credential.", "example": "https://acme-air.didit.me" }, "status": { "type": "string", "description": "Lifecycle status after the call; `revoked` on success.", "example": "revoked", "enum": [ "issued", "suspended", "revoked", "expired" ] }, "status_idx": { "type": [ "integer", "null" ], "description": "Index of this credential in the tenant's status list, where the revocation bit was just set. Null for non-revocable credentials.", "example": 4213 }, "issued_at": { "type": "string", "description": "ISO 8601 timestamp when the credential was issued.", "example": "2026-06-30T12:00:00+00:00", "format": "date-time" }, "expires_at": { "type": [ "string", "null" ], "description": "ISO 8601 timestamp when the credential expires. Null if no expiry.", "example": "2027-06-30T12:00:00+00:00", "format": "date-time" }, "holder_cnf": { "type": "object", "description": "The holder key-binding confirmation (cnf) object that binds the credential to its holder.", "example": { "jwk": { "kty": "EC", "crv": "P-256", "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0" } }, "properties": {} }, "source_session_id": { "type": "string", "description": "Identifier of the Didit verification session this credential was bridged from, or an empty string if none.", "example": "11111111-2222-3333-4444-555555555555" } } } } } } }, "summary": "Revoke a credential", "description": "Permanently revoke an issued credential. Sets its status to `revoked` and flips its bit in the tenant's IETF Token Status List, so any verifier that fetches the status list will reject it from this point on. Revocation is final \u2014 unlike suspension it cannot be reversed by reactivation. Takes no request body. The credential must belong to the authenticated tenant; otherwise the request returns 404. Requires Authorization: Bearer .", "security": [ { "bearerAuth": [] } ] } }, "/v1/credentials/{uuid}/suspend": { "post": { "operationId": "v1_credentials_suspend_create", "parameters": [ { "name": "uuid", "in": "path", "required": true, "description": "UUID of the issued credential to suspend. Must belong to the authenticated tenant; otherwise the request returns 404.", "schema": { "type": "string", "format": "uuid" } } ], "tags": [ "Credentials" ], "responses": { "200": { "description": "The credential was suspended. Returns the updated issued-credential record with status `suspended`.", "content": { "application/json": { "schema": { "type": "object", "properties": { "uuid": { "type": "string", "description": "Unique identifier of the issued credential.", "example": "c1d2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f", "format": "uuid" }, "vct": { "type": "string", "description": "Verifiable Credential Type of the credential.", "example": "https://acme-air.didit.me/credentials/loyalty-membership" }, "iss": { "type": "string", "description": "Issuer identifier (the https iss URL) of the tenant that issued the credential.", "example": "https://acme-air.didit.me" }, "status": { "type": "string", "description": "Lifecycle status after the call; `suspended` on success.", "example": "suspended", "enum": [ "issued", "suspended", "revoked", "expired" ] }, "status_idx": { "type": [ "integer", "null" ], "description": "Index of this credential in the tenant's status list, where the suspension bit was just set. Null for non-revocable credentials.", "example": 4213 }, "issued_at": { "type": "string", "description": "ISO 8601 timestamp when the credential was issued.", "example": "2026-06-30T12:00:00+00:00", "format": "date-time" }, "expires_at": { "type": [ "string", "null" ], "description": "ISO 8601 timestamp when the credential expires. Null if no expiry.", "example": "2027-06-30T12:00:00+00:00", "format": "date-time" }, "holder_cnf": { "type": "object", "description": "The holder key-binding confirmation (cnf) object that binds the credential to its holder.", "example": { "jwk": { "kty": "EC", "crv": "P-256", "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0" } }, "properties": {} }, "source_session_id": { "type": "string", "description": "Identifier of the Didit verification session this credential was bridged from, or an empty string if none.", "example": "11111111-2222-3333-4444-555555555555" } } } } } } }, "summary": "Suspend a credential", "description": "Temporarily suspend an issued credential. Sets its status to `suspended` and flips its bit in the tenant's IETF Token Status List so verifiers reject it while suspended. Unlike revocation, suspension is reversible \u2014 call the reactivate endpoint to restore the credential. Takes no request body. The credential must belong to the authenticated tenant; otherwise the request returns 404. Requires Authorization: Bearer .", "security": [ { "bearerAuth": [] } ] } }, "/v1/credentials/{uuid}/reactivate": { "post": { "operationId": "v1_credentials_reactivate_create", "parameters": [ { "name": "uuid", "in": "path", "required": true, "description": "UUID of the issued credential to reactivate. Must belong to the authenticated tenant; otherwise the request returns 404.", "schema": { "type": "string", "format": "uuid" } } ], "tags": [ "Credentials" ], "responses": { "200": { "description": "The credential was reactivated. Returns the updated issued-credential record with status `issued`.", "content": { "application/json": { "schema": { "type": "object", "properties": { "uuid": { "type": "string", "description": "Unique identifier of the issued credential.", "example": "c1d2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f", "format": "uuid" }, "vct": { "type": "string", "description": "Verifiable Credential Type of the credential.", "example": "https://acme-air.didit.me/credentials/loyalty-membership" }, "iss": { "type": "string", "description": "Issuer identifier (the https iss URL) of the tenant that issued the credential.", "example": "https://acme-air.didit.me" }, "status": { "type": "string", "description": "Lifecycle status after the call; `issued` on success.", "example": "issued", "enum": [ "issued", "suspended", "revoked", "expired" ] }, "status_idx": { "type": [ "integer", "null" ], "description": "Index of this credential in the tenant's status list, where the bit was just cleared. Null for non-revocable credentials.", "example": 4213 }, "issued_at": { "type": "string", "description": "ISO 8601 timestamp when the credential was issued.", "example": "2026-06-30T12:00:00+00:00", "format": "date-time" }, "expires_at": { "type": [ "string", "null" ], "description": "ISO 8601 timestamp when the credential expires. Null if no expiry.", "example": "2027-06-30T12:00:00+00:00", "format": "date-time" }, "holder_cnf": { "type": "object", "description": "The holder key-binding confirmation (cnf) object that binds the credential to its holder.", "example": { "jwk": { "kty": "EC", "crv": "P-256", "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0" } }, "properties": {} }, "source_session_id": { "type": "string", "description": "Identifier of the Didit verification session this credential was bridged from, or an empty string if none.", "example": "11111111-2222-3333-4444-555555555555" } } } } } } }, "summary": "Reactivate a credential", "description": "Reactivate a previously suspended credential. Sets its status back to `issued` and clears its bit in the tenant's IETF Token Status List, so verifiers accept it again. Use it to reverse a suspension; it has no effect path for revoked credentials (revocation is permanent). Takes no request body. The credential must belong to the authenticated tenant; otherwise the request returns 404. Requires Authorization: Bearer .", "security": [ { "bearerAuth": [] } ] } }, "/v1/presentations/request": { "post": { "operationId": "v1_presentations_request_create", "tags": [ "OpenID4VP Verification" ], "responses": { "200": { "description": "No response body" }, "201": { "description": "The presentation request was created. Returns the request object the holder/wallet uses to build a presentation, including the DCQL query and the single-use nonce.", "content": { "application/json": { "schema": { "type": "object", "properties": { "uuid": { "type": "string", "description": "Unique identifier of the presentation request. This is the public capability the holder polls (GET /v1/presentations/{uuid}) and answers (POST /v1/presentations/{uuid}/response or /demo-present).", "example": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d", "format": "uuid" }, "transaction_id": { "type": "string", "description": "Transaction identifier that binds the holder's presentation to this specific request. Generated server-side and indexed; used to correlate the request with the resulting verification.", "example": "f0e1d2c3-b4a5-6978-8c9d-0a1b2c3d4e5f", "format": "uuid" }, "dcql": { "type": "object", "description": "The OpenID4VP DCQL (Digital Credentials Query Language) query the wallet evaluates to find a matching credential and build the presentation. Contains a `credentials` array; each entry declares the format (`dc+sd-jwt`), the accepted `vct_values`, and the requested claim paths.", "example": { "credentials": [ { "id": "cred1", "format": "dc+sd-jwt", "meta": { "vct_values": [ "https://credentials.didit.me/vct/identity-card" ] }, "claims": [ { "path": [ "given_name" ] }, { "path": [ "family_name" ] }, { "path": [ "birth_date" ] } ] } ] }, "properties": {} }, "requested_vct": { "type": "string", "description": "Echoes the Verifiable Credential Type required by this request.", "example": "https://credentials.didit.me/vct/identity-card" }, "requested_claims": { "type": "array", "description": "Echoes the claim names the verifier asked the holder to disclose.", "example": [ "given_name", "family_name", "birth_date" ], "items": { "type": "string" } }, "aud": { "type": "string", "description": "Echoes the verifier audience (client_id / origin) the holder must bind into the KB-JWT.", "example": "https://checkin.acme-air.com" }, "nonce": { "type": "string", "description": "Single-use, short-TTL (300s) cryptographic nonce the holder must sign into the Key-Binding JWT. Consumed atomically on first verification; a replayed nonce is rejected.", "example": "Xy7Zq9-Ab3kQ9z1Xy7Zq9Ab3kQ9z1Xy7Zq9Ab3kQ9" }, "status": { "type": "string", "description": "Lifecycle state of the presentation request. `pending` immediately after creation; transitions to `verified` once a valid presentation is accepted, or `failed` if verification rejects it. (`presented` is reserved for the intermediate received-but-not-yet-verified state.)", "example": "pending", "enum": [ "pending", "presented", "verified", "failed" ] }, "created_at": { "type": "string", "description": "ISO 8601 timestamp when the presentation request was created.", "example": "2026-06-30T12:00:00+00:00", "format": "date-time" } } } } } } }, "summary": "Create an OpenID4VP presentation request (DCQL)", "description": "Create an OpenID4VP presentation request: the verifier (relying party) declares the credential type and the specific claims it wants disclosed, and the service returns a request object containing a DCQL (Digital Credentials Query Language) query, a single-use nonce, and a transaction id to bind the holder's presentation to this exact request. The holder wallet (or the demo present/response flow) presents an SD-JWT VC against this request; the returned `uuid` is the public capability the holder polls via GET /v1/presentations/{uuid} and answers via POST /v1/presentations/{uuid}/response. The nonce has a 300-second TTL and is consumed atomically on first verification (replay is rejected). Authenticated with the tenant API key (Bearer).", "security": [ { "bearerAuth": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "requested_vct": { "type": "string", "description": "The Verifiable Credential Type (`vct`) the verifier requires the holder to present. Must match the `vct` of a credential schema the holder holds; it is embedded into the DCQL query's `meta.vct_values` so wallets filter to matching credentials.", "example": "https://credentials.didit.me/vct/identity-card" }, "requested_claims": { "type": "array", "description": "The list of claim names the verifier asks the holder to selectively disclose (each becomes a DCQL claim path). Only these claims are returned in `disclosed_claims` after a successful presentation; everything else stays hidden under selective disclosure. Defaults to an empty list (no specific claims requested).", "example": [ "given_name", "family_name", "birth_date" ], "items": { "type": "string" } }, "aud": { "type": "string", "description": "The verifier's audience identifier \u2014 its OpenID4VP client_id or web origin. It is bound into the request and must match the `aud` the holder signs into the Key-Binding JWT (KB-JWT) of the presentation; a mismatch fails the `aud` check. Also surfaced as the relying-party name on the public poll endpoint.", "example": "https://checkin.acme-air.com" }, "trusted_iss": { "type": "string", "description": "Optional. The issuer identifier (`iss`) the verifier is willing to trust for this presentation. When omitted or blank, it defaults to the tenant's own issuer identifier (`tenant.issuer_iss`). At verification time the presented credential's `iss` is checked against the tenant's trusted-issuer registry; an untrusted issuer fails verification.", "example": "https://credentials.didit.me/issuers/acme-air" } }, "required": [ "requested_vct", "aud" ] } } } } } }, "/v1/presentations/{uuid}": { "get": { "operationId": "v1_presentations_retrieve", "description": "Public, unauthenticated poll endpoint for a presentation request \u2014 the request `uuid` is itself the capability, so any holder/wallet that received the request can read it without an API key. Returns the request management fields (DCQL, nonce, status) enriched with a holder-facing view: who is asking (`requester`), the human-labelled `claims` being requested with their selective-disclosure flags, and \u2014 once a presentation has been verified \u2014 a `result` object with the verdict and disclosed claims. Holders poll this to render the consent screen and to observe the verification outcome.", "parameters": [ { "name": "uuid", "in": "path", "required": true, "description": "UUID of the presentation request to fetch. An unknown or soft-deleted id returns 404.", "schema": { "type": "string", "format": "uuid" } } ], "tags": [ "OpenID4VP Verification" ], "responses": { "200": { "description": "The presentation request in its holder-facing shape: the request fields plus `requester`, `claims`, and (once verified) `result`.", "content": { "application/json": { "schema": { "type": "object", "properties": { "uuid": { "type": "string", "description": "Unique identifier of the presentation request.", "example": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d", "format": "uuid" }, "transaction_id": { "type": "string", "description": "Transaction identifier binding the holder's presentation to this request.", "example": "f0e1d2c3-b4a5-6978-8c9d-0a1b2c3d4e5f", "format": "uuid" }, "dcql": { "type": "object", "description": "The OpenID4VP DCQL query the wallet evaluates to build the presentation (same structure returned at creation).", "example": { "credentials": [ { "id": "cred1", "format": "dc+sd-jwt", "meta": { "vct_values": [ "https://credentials.didit.me/vct/identity-card" ] }, "claims": [ { "path": [ "given_name" ] }, { "path": [ "birth_date" ] } ] } ] }, "properties": {} }, "requested_vct": { "type": "string", "description": "The Verifiable Credential Type required by this request.", "example": "https://credentials.didit.me/vct/identity-card" }, "requested_claims": { "type": "array", "description": "Raw claim names the verifier requested.", "example": [ "given_name", "birth_date" ], "items": { "type": "string" } }, "aud": { "type": "string", "description": "The verifier audience (client_id / origin) bound into the request.", "example": "https://checkin.acme-air.com" }, "nonce": { "type": "string", "description": "Single-use nonce the holder signs into the KB-JWT.", "example": "Xy7Zq9-Ab3kQ9z1Xy7Zq9Ab3kQ9z1Xy7Zq9Ab3kQ9" }, "status": { "type": "string", "description": "Lifecycle state of the presentation request.", "example": "pending", "enum": [ "pending", "presented", "verified", "failed" ] }, "created_at": { "type": "string", "description": "ISO 8601 timestamp when the request was created.", "example": "2026-06-30T12:00:00+00:00", "format": "date-time" }, "requester": { "type": "object", "description": "Holder-facing description of the relying party asking for the presentation, used to render the wallet consent screen.", "properties": { "id": { "type": "string", "description": "Identifier of the requester. In this slice always the literal `relying-party`.", "example": "relying-party" }, "name": { "type": "string", "description": "Display name of the relying party \u2014 the request's `aud` value, or `Relying party` when `aud` is blank.", "example": "https://checkin.acme-air.com" }, "did": { "type": "string", "description": "The relying party's decentralized identifier, derived from the tenant slug as `did:web:{slug}.didit.test`.", "example": "did:web:acme-air.didit.test" }, "purpose": { "type": "string", "description": "Human-readable reason the credential is being requested, shown to the holder on the consent screen.", "example": "Confirm your identity for this verification." } } }, "claims": { "type": "array", "description": "Holder-facing list of the requested claims, one entry per requested claim name, with a human label and selective-disclosure metadata for the consent UI.", "items": { "type": "object", "properties": { "name": { "type": "string", "description": "The raw claim name (the disclosure key in the SD-JWT VC).", "example": "given_name" }, "label": { "type": "string", "description": "Human-readable label derived from the claim name (underscores replaced with spaces, title-cased) for display in the wallet.", "example": "Given Name" }, "requested": { "type": "boolean", "description": "Whether this claim is being requested. Always true for claims listed here.", "example": true }, "selective_disclosure": { "type": "boolean", "description": "Whether the claim is disclosed selectively (the holder reveals it on a per-claim basis rather than the whole credential). Always true on this endpoint.", "example": true } } } }, "result": { "type": [ "object", "null" ], "description": "The verification result. Present only once a presentation has been submitted and verified (a one-to-one record per request); absent while the request is still pending.", "properties": { "uuid": { "type": "string", "description": "Unique identifier of the verification result record.", "example": "b2c3d4e5-6f7a-8b9c-0d1e-2f3a4b5c6d7e", "format": "uuid" }, "verdict": { "type": "string", "description": "Outcome of verifying the presented vp_token: `verified` if every cryptographic and policy check passed, `failed` otherwise.", "example": "verified", "enum": [ "verified", "failed" ] }, "disclosed_claims": { "type": "object", "description": "Map of the claims the holder actually disclosed (claim name to value). Only the requested, consented claims appear; reserved SD-JWT registered claims (iss, vct, iat, exp, cnf, status, _sd, _sd_alg) are stripped out. Empty when the verdict is `failed`.", "example": { "given_name": "Ada", "family_name": "Lovelace", "birth_date": "1815-12-10" }, "properties": {} }, "checks": { "type": "object", "description": "Per-check boolean results of verification. Fully populated on a `verified` result; an empty object on a `failed` result (the failing check is captured in `error`).", "properties": { "signature": { "type": "boolean", "description": "The issuer's signature over the SD-JWT is valid, verified with the issuer key resolved by `kid` (supports key rotation).", "example": true }, "key_binding": { "type": "boolean", "description": "The Key-Binding JWT (KB-JWT) is present and signed by the holder key bound in the credential's `cnf` claim \u2014 proving holder possession.", "example": true }, "aud": { "type": "boolean", "description": "The audience in the KB-JWT matches the verifier audience (`aud`) on the presentation request.", "example": true }, "nonce": { "type": "boolean", "description": "The nonce in the KB-JWT matches the single-use nonce issued with the request, and the nonce had not been consumed or expired (anti-replay).", "example": true }, "alg_allowlist": { "type": "boolean", "description": "Every signing algorithm used in the presentation is on the allow-list (ES256), enforced with a defense-in-depth re-assertion after parsing.", "example": true }, "not_expired": { "type": "boolean", "description": "The credential's `exp` claim is in the future (the library does not enforce expiry, so it is checked explicitly).", "example": true }, "not_revoked": { "type": "boolean", "description": "The credential is not marked revoked or suspended in its IETF Token Status List (checked when the credential carries a `status` claim).", "example": true } } }, "error": { "type": "string", "description": "Failure reason as `ExceptionType: message` when the verdict is `failed`; empty string on success.", "example": "" } } } } } } } } }, "summary": "Poll a presentation request (holder + status)", "security": [] } }, "/v1/presentations/{uuid}/response": { "post": { "operationId": "v1_presentations_response_create", "parameters": [ { "name": "uuid", "in": "path", "required": true, "description": "UUID of the presentation request being answered. Scoped to the authenticated tenant; an unknown, soft-deleted, or sibling-tenant id returns 404.", "schema": { "type": "string", "format": "uuid" } } ], "tags": [ "OpenID4VP Verification" ], "responses": { "200": { "description": "Verification succeeded (verdict `verified`). Returns the verification result with the disclosed claims and the per-check booleans.", "content": { "application/json": { "schema": { "type": "object", "properties": { "uuid": { "type": "string", "description": "Unique identifier of the verification result record.", "example": "b2c3d4e5-6f7a-8b9c-0d1e-2f3a4b5c6d7e", "format": "uuid" }, "verdict": { "type": "string", "description": "Outcome of verification; `verified` on a 200 response.", "example": "verified", "enum": [ "verified", "failed" ] }, "disclosed_claims": { "type": "object", "description": "Map of claim name to value for the claims the holder disclosed. Reserved SD-JWT registered claims (iss, vct, iat, exp, cnf, status, _sd, _sd_alg) are excluded.", "example": { "given_name": "Ada", "family_name": "Lovelace", "birth_date": "1815-12-10" }, "properties": {} }, "checks": { "type": "object", "description": "Per-check boolean results of verification (all true on a verified result).", "properties": { "signature": { "type": "boolean", "description": "The issuer's signature over the SD-JWT is valid (issuer key resolved by `kid`, supporting rotation).", "example": true }, "key_binding": { "type": "boolean", "description": "The KB-JWT is present and signed by the holder key bound in the credential's `cnf` claim (holder possession proof).", "example": true }, "aud": { "type": "boolean", "description": "The KB-JWT audience matches the request's verifier audience (`aud`).", "example": true }, "nonce": { "type": "boolean", "description": "The KB-JWT nonce matches the request's single-use nonce, which had not been consumed or expired (anti-replay).", "example": true }, "alg_allowlist": { "type": "boolean", "description": "Every algorithm in the presentation is on the ES256 allow-list.", "example": true }, "not_expired": { "type": "boolean", "description": "The credential's `exp` is in the future.", "example": true }, "not_revoked": { "type": "boolean", "description": "The credential is not revoked or suspended per its Token Status List.", "example": true } } }, "error": { "type": "string", "description": "Empty string on a verified result.", "example": "" } } } } } }, "422": { "description": "Verification failed (verdict `failed`), or the request was already used (replay). Returns the failed result; `checks` is an empty object and `error` carries the failing reason. On replay, the body is a minimal failure shape with `error` set to the replay message.", "content": { "application/json": { "schema": { "type": "object", "properties": { "uuid": { "type": [ "string", "null" ], "description": "Unique identifier of the failed verification result record. Absent on the replay-rejection shape.", "example": "b2c3d4e5-6f7a-8b9c-0d1e-2f3a4b5c6d7e", "format": "uuid" }, "verdict": { "type": "string", "description": "Always `failed` on a 422 response.", "example": "failed", "enum": [ "failed" ] }, "disclosed_claims": { "type": [ "object", "null" ], "description": "Empty object on a failed verification; absent on the replay-rejection shape.", "example": {}, "properties": {} }, "checks": { "type": "object", "description": "Empty object on a failed result (no individual check passed cleanly enough to record; the failing reason is in `error`).", "example": {}, "properties": {} }, "error": { "type": "string", "description": "Failure reason. On a verification failure, formatted as `ExceptionType: message` (e.g. signature, key-binding, nonce, expiry, revocation, or untrusted-issuer errors). On replay, the literal message that the request was already used.", "example": "SignatureError: untrusted issuer" } } } } } } }, "summary": "Submit a vp_token for verification", "description": "Submit a holder's presentation (the SD-JWT VC verifiable presentation, including the Key-Binding JWT) against a presentation request and verify it. The service consumes the request's single-use nonce, verifies the issuer signature (resolving the key by `kid`), the holder key binding, the `aud` and `nonce` bindings, the algorithm allow-list, expiry, and revocation via the Token Status List, then checks the credential's issuer against the tenant's trusted-issuer registry. A presentation request is single-use: a second response (replay) is rejected with 422. Authenticated with the tenant API key.", "security": [], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "vp_token": { "type": "string", "description": "The OpenID4VP verifiable presentation token: the SD-JWT VC with its selected disclosures and the appended Key-Binding JWT (KB-JWT). The KB-JWT must be signed by the holder key bound in the credential's `cnf` claim and carry the request's `aud` and `nonce`.", "example": "eyJhbGciOiJFUzI1NiIsInR5cCI6ImRjK3NkLWp3dCJ9.eyJpc3MiOiJ...~WyJzYWx0IiwiZ2l2ZW5fbmFtZSIsIkFkYSJd~eyJhbGciOiJFUzI1NiIsInR5cCI6ImtiK2p3dCJ9.eyJub25jZSI6Ii4uLiJ9.sig" } }, "required": [ "vp_token" ] } } } } } }, "/v1/presentations/{uuid}/demo-present": { "post": { "operationId": "v1_presentations_demo_present_create", "description": "Demo / verifier-simulation endpoint (public, no API key). Server-side it issues a credential matching the request's `vct`, builds a real SD-JWT VC verifiable presentation with a fresh holder key (real KB-JWT, real nonce/aud binding), and verifies it through the full production crypto pipeline \u2014 without needing a second device or a real wallet. Used by the verifier console and the holder fallback to produce a genuine verified result. The request must have a credential template for its `vct`, and is single-use (a request that already has a result is rejected with 422). The request body is empty.", "parameters": [ { "name": "uuid", "in": "path", "required": true, "description": "UUID of the presentation request to present-and-verify. An unknown or soft-deleted id returns 404.", "schema": { "type": "string", "format": "uuid" } } ], "tags": [ "OpenID4VP Verification" ], "responses": { "200": { "description": "The demo presentation verified successfully (verdict `verified`). Returns the verification payload.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "description": "Unique identifier of the verification result record.", "example": "b2c3d4e5-6f7a-8b9c-0d1e-2f3a4b5c6d7e", "format": "uuid" }, "request_id": { "type": "string", "description": "UUID of the presentation request this verification answers.", "example": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d", "format": "uuid" }, "verdict": { "type": "string", "description": "Outcome of verification; `verified` on a 200 response.", "example": "verified", "enum": [ "verified", "failed" ] }, "vct": { "type": "string", "description": "The Verifiable Credential Type that was requested and presented.", "example": "https://credentials.didit.me/vct/identity-card" }, "verifier": { "type": "string", "description": "The verifier audience (the request's `aud` \u2014 client_id / origin) the presentation was bound to.", "example": "https://checkin.acme-air.com" }, "requested_claims": { "type": "array", "description": "The claim names the request asked to be disclosed.", "example": [ "given_name", "family_name", "birth_date" ], "items": { "type": "string" } }, "disclosed_claims": { "type": "object", "description": "Map of claim name to value for the claims disclosed in the presentation. In demo mode the values are synthesized as `demo-{claim_name}` for every requested claim.", "example": { "given_name": "demo-given_name", "family_name": "demo-family_name", "birth_date": "demo-birth_date" }, "properties": {} }, "checks": { "type": "object", "description": "Per-check boolean results of verification (all true on a verified result).", "properties": { "signature": { "type": "boolean", "description": "The issuer's signature over the SD-JWT is valid (issuer key resolved by `kid`).", "example": true }, "key_binding": { "type": "boolean", "description": "The KB-JWT is present and signed by the holder key bound in `cnf` (holder possession proof).", "example": true }, "aud": { "type": "boolean", "description": "The KB-JWT audience matches the request's verifier audience.", "example": true }, "nonce": { "type": "boolean", "description": "The KB-JWT nonce matches the request's single-use nonce (anti-replay).", "example": true }, "alg_allowlist": { "type": "boolean", "description": "Every algorithm in the presentation is on the ES256 allow-list.", "example": true }, "not_expired": { "type": "boolean", "description": "The credential's `exp` is in the future.", "example": true }, "not_revoked": { "type": "boolean", "description": "The credential is not revoked or suspended per its Token Status List.", "example": true } } }, "error": { "type": "string", "description": "Empty string on a verified result.", "example": "" }, "created_at": { "type": "string", "description": "ISO 8601 timestamp when the verification result was recorded.", "example": "2026-06-30T12:00:00+00:00", "format": "date-time" } } } } } }, "422": { "description": "The demo presentation failed verification, the request was already used, or no credential template exists for the request's `vct`. On a recorded failure the body is the verification payload with verdict `failed`; on the no-template / already-used short-circuit it is a minimal failure shape (`verdict`, `error`, empty `checks`).", "content": { "application/json": { "schema": { "type": "object", "properties": { "verdict": { "type": "string", "description": "Always `failed` on a 422 response.", "example": "failed", "enum": [ "failed" ] }, "error": { "type": "string", "description": "Failure reason \u2014 e.g. a verification error, `request already used`, or `no template for vct {vct}` when the tenant has no credential template for the requested type.", "example": "no template for vct https://credentials.didit.me/vct/identity-card" }, "checks": { "type": "object", "description": "Empty object on the short-circuit failure shape; populated only when a full verification ran and failed.", "example": {}, "properties": {} } } } } } } }, "summary": "Demo: real server-side present + verify", "security": [], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": {} } } } } } }, "/v1/verifications": { "get": { "operationId": "v1_verifications_retrieve", "tags": [ "OpenID4VP Verification" ], "responses": { "200": { "description": "Array of verification payloads for the tenant, ordered by creation time descending.", "content": { "application/json": { "schema": { "type": "object", "properties": { "_root": { "type": "array", "description": "The response body is a JSON array of verification result objects (no envelope).", "items": { "type": "object", "properties": { "id": { "type": "string", "description": "Unique identifier of the verification result record.", "example": "b2c3d4e5-6f7a-8b9c-0d1e-2f3a4b5c6d7e", "format": "uuid" }, "request_id": { "type": "string", "description": "UUID of the presentation request this verification answers.", "example": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d", "format": "uuid" }, "verdict": { "type": "string", "description": "Outcome of verification.", "example": "verified", "enum": [ "verified", "failed" ] }, "vct": { "type": "string", "description": "The Verifiable Credential Type that was requested and presented.", "example": "https://credentials.didit.me/vct/identity-card" }, "verifier": { "type": "string", "description": "The verifier audience (the request's `aud` \u2014 client_id / origin).", "example": "https://checkin.acme-air.com" }, "requested_claims": { "type": "array", "description": "The claim names the request asked to be disclosed.", "example": [ "given_name", "birth_date" ], "items": { "type": "string" } }, "disclosed_claims": { "type": "object", "description": "Map of claim name to value for the claims disclosed. Empty when the verdict is `failed`.", "example": { "given_name": "Ada", "birth_date": "1815-12-10" }, "properties": {} }, "checks": { "type": "object", "description": "Per-check boolean results of verification. Fully populated on a verified result; empty on a failed result.", "properties": { "signature": { "type": "boolean", "description": "The issuer's signature over the SD-JWT is valid (issuer key resolved by `kid`).", "example": true }, "key_binding": { "type": "boolean", "description": "The KB-JWT is present and signed by the holder key bound in `cnf` (holder possession proof).", "example": true }, "aud": { "type": "boolean", "description": "The KB-JWT audience matches the request's verifier audience.", "example": true }, "nonce": { "type": "boolean", "description": "The KB-JWT nonce matches the request's single-use nonce (anti-replay).", "example": true }, "alg_allowlist": { "type": "boolean", "description": "Every algorithm in the presentation is on the ES256 allow-list.", "example": true }, "not_expired": { "type": "boolean", "description": "The credential's `exp` is in the future.", "example": true }, "not_revoked": { "type": "boolean", "description": "The credential is not revoked or suspended per its Token Status List.", "example": true } } }, "error": { "type": "string", "description": "Failure reason (`ExceptionType: message`) when the verdict is `failed`; empty string on success.", "example": "" }, "created_at": { "type": "string", "description": "ISO 8601 timestamp when the verification result was recorded.", "example": "2026-06-30T12:00:00+00:00", "format": "date-time" } } } } } } } } } }, "summary": "List verifications (log)", "description": "List all verification results for the authenticated tenant, newest first. Each entry is a verification payload (the same shape returned by demo-present): verdict, requested and disclosed claims, the per-check booleans, and the verifier audience. Scoped to the tenant via the API key, so only this tenant's verifications are returned. Authenticated with the tenant API key.", "security": [ { "bearerAuth": [] } ] } }, "/v1/verifications/{uuid}": { "get": { "operationId": "v1_verifications_retrieve_2", "parameters": [ { "name": "uuid", "in": "path", "required": true, "description": "UUID of the verification result record to fetch. Scoped to the authenticated tenant; a sibling-tenant, unknown, or soft-deleted id returns 404.", "schema": { "type": "string", "format": "uuid" } } ], "tags": [ "OpenID4VP Verification" ], "responses": { "200": { "description": "The verification payload for the requested result.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "description": "Unique identifier of the verification result record.", "example": "b2c3d4e5-6f7a-8b9c-0d1e-2f3a4b5c6d7e", "format": "uuid" }, "request_id": { "type": "string", "description": "UUID of the presentation request this verification answers.", "example": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d", "format": "uuid" }, "verdict": { "type": "string", "description": "Outcome of verification.", "example": "verified", "enum": [ "verified", "failed" ] }, "vct": { "type": "string", "description": "The Verifiable Credential Type that was requested and presented.", "example": "https://credentials.didit.me/vct/identity-card" }, "verifier": { "type": "string", "description": "The verifier audience (the request's `aud` \u2014 client_id / origin).", "example": "https://checkin.acme-air.com" }, "requested_claims": { "type": "array", "description": "The claim names the request asked to be disclosed.", "example": [ "given_name", "family_name", "birth_date" ], "items": { "type": "string" } }, "disclosed_claims": { "type": "object", "description": "Map of claim name to value for the claims disclosed. Empty when the verdict is `failed`.", "example": { "given_name": "Ada", "family_name": "Lovelace", "birth_date": "1815-12-10" }, "properties": {} }, "checks": { "type": "object", "description": "Per-check boolean results of verification. Fully populated on a verified result; empty on a failed result.", "properties": { "signature": { "type": "boolean", "description": "The issuer's signature over the SD-JWT is valid (issuer key resolved by `kid`).", "example": true }, "key_binding": { "type": "boolean", "description": "The KB-JWT is present and signed by the holder key bound in `cnf` (holder possession proof).", "example": true }, "aud": { "type": "boolean", "description": "The KB-JWT audience matches the request's verifier audience.", "example": true }, "nonce": { "type": "boolean", "description": "The KB-JWT nonce matches the request's single-use nonce (anti-replay).", "example": true }, "alg_allowlist": { "type": "boolean", "description": "Every algorithm in the presentation is on the ES256 allow-list.", "example": true }, "not_expired": { "type": "boolean", "description": "The credential's `exp` is in the future.", "example": true }, "not_revoked": { "type": "boolean", "description": "The credential is not revoked or suspended per its Token Status List.", "example": true } } }, "error": { "type": "string", "description": "Failure reason (`ExceptionType: message`) when the verdict is `failed`; empty string on success.", "example": "" }, "created_at": { "type": "string", "description": "ISO 8601 timestamp when the verification result was recorded.", "example": "2026-06-30T12:00:00+00:00", "format": "date-time" } } } } } } }, "summary": "Get a verification result", "description": "Fetch a single verification result by id for the authenticated tenant. Returns the verification payload \u2014 verdict, requested and disclosed claims, the per-check booleans, and the verifier audience. Scoped to the tenant via the API key; a verification belonging to another tenant (or an unknown/soft-deleted id) returns 404. Authenticated with the tenant API key.", "security": [ { "bearerAuth": [] } ] } }, "/v1/status-lists/{slug}/{uuid}": { "get": { "operationId": "v1_status_lists_retrieve", "description": "Public, unauthenticated fetch of an entire IETF Token Status List (compressed bitstring) used for SD-JWT VC revocation and suspension. Verifiers fetch the whole list (never a per-credential entry) and read the bit at a credential's status index; serving the full list rather than a single entry is a deliberate correlation defense. Call it when verifying a credential whose `status` claim points at this list.", "parameters": [ { "name": "slug", "in": "path", "required": true, "description": "The issuer tenant's URL slug. Scopes the lookup to that tenant; a mismatched slug returns 404.", "schema": { "type": "string" } }, { "name": "uuid", "in": "path", "required": true, "description": "UUID of the specific status list to fetch. Must belong to the tenant identified by `slug`, or the request returns 404.", "schema": { "type": "string", "format": "uuid" } } ], "tags": [ "Status Lists" ], "responses": { "200": { "description": "The full status list as a base64url-encoded bitstring plus its self-referential URI and size.", "content": { "application/json": { "schema": { "type": "object", "properties": { "uri": { "type": "string", "description": "Canonical absolute URI of this status list, of the form {issuer_iss}/v1/status-lists/{slug}/{uuid}. This is the value referenced from each credential's status claim.", "example": "https://acme-air.didit.me/v1/status-lists/acme-air/9f0a1b2c-3d4e-5f6a-7b8c-9d0e1f2a3b4c", "format": "uri" }, "size": { "type": "integer", "description": "Number of bits in the status list (the herd size, deliberately far larger than the credential count to resist correlation). Defaults to 131072.", "example": 131072 }, "bits_b64": { "type": "string", "description": "The status-list bitstring encoded as a base64url string (no padding stripping; standard urlsafe base64 of the raw bytes). Decode it and read the bit at a credential's status index: 1 means revoked/suspended, 0 means valid.", "example": "AAAAAAAAAAAAAAAAAAAAAA", "format": "byte" } } } } } } }, "summary": "Fetch a Token Status List (whole list)", "security": [] } }, "/v1/trust-frameworks": { "get": { "operationId": "v1_trust_frameworks_retrieve", "tags": [ "Trust Registry" ], "responses": { "200": { "description": "A bare JSON array of trust framework objects, ordered alphabetically by name.", "content": { "application/json": { "schema": { "type": "object", "properties": { "items": { "type": "array", "description": "Array of trust framework reference objects.", "items": { "type": "string" } } } } } } } }, "summary": "List trust frameworks", "description": "List the global reference trust frameworks Didit ID recognizes (eIDAS 2.0 / EUDI, ISO/IEC 18013-5 mDL, Swiss e-ID, US mDL / AAMVA). These are read-only reference rows seeded on first access \u2014 they are not tenant-scoped \u2014 and are used to tag a trusted issuer with the framework it operates under. The response is a bare JSON array of framework objects ordered by name.", "security": [ { "bearerAuth": [] } ] } }, "/v1/trusted-issuers": { "get": { "operationId": "v1_trusted_issuers_retrieve", "tags": [ "Trust Registry" ], "responses": { "200": { "description": "A bare JSON array of trusted issuer objects for the caller's tenant, newest first.", "content": { "application/json": { "schema": { "type": "object", "properties": { "items": { "type": "array", "description": "Array of trusted issuer objects.", "items": { "type": "string" } } } } } } } }, "summary": "List trusted issuers", "description": "List the issuers this tenant trusts when verifying presented credentials. During verification, Didit ID rejects any credential whose `iss` is not an active trusted issuer (the supply-chain-of-trust gate). The tenant's own self-trust row is guaranteed to exist so the registry is never empty. Scoped to the caller's tenant via the API key. Returns a bare JSON array ordered by most-recently-created first.", "security": [ { "bearerAuth": [] } ] }, "post": { "operationId": "v1_trusted_issuers_create", "tags": [ "Trust Registry" ], "responses": { "200": { "description": "No response body" }, "201": { "description": "The trusted issuer was registered. Returns the full trusted issuer object.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "description": "UUID of the newly created trusted issuer registry entry.", "example": "0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9", "format": "uuid" }, "name": { "type": "string", "description": "Display name of the trusted issuer.", "example": "Gov PID" }, "iss": { "type": "string", "description": "The HTTPS `iss` identifier of the issuer.", "example": "https://pid.gov.example" }, "trust_anchor": { "type": "string", "description": "How the issuer's signing key is anchored and resolved.", "example": "https-jwks", "enum": [ "https-jwks", "did-web", "x509" ] }, "jwks_uri": { "type": "string", "description": "JWKS endpoint for the issuer's public keys; empty string when not set.", "example": "https://pid.gov.example/.well-known/jwks.json" }, "did": { "type": "string", "description": "Issuer DID; empty string when not set.", "example": "" }, "status": { "type": "string", "description": "Trust status of the issuer. Newly created issuers are always `active`.", "example": "active", "enum": [ "active", "revoked" ] }, "added_by": { "type": "string", "description": "Identifier of the API principal that registered this trusted issuer.", "example": "tenant:airline-acme" }, "framework": { "type": "string", "description": "Slug of the associated trust framework, or null if none.", "example": "eidas2" }, "created_at": { "type": "string", "description": "ISO 8601 timestamp when the trusted issuer was registered.", "example": "2026-06-29T12:34:56.789012+00:00", "format": "date-time" } } } } } } }, "summary": "Add a trusted issuer", "description": "Register an issuer this tenant will trust when verifying credentials. After registration, credentials whose `iss` matches an active trusted issuer pass the verification trust gate. The new entry is always created with status `active` and `added_by` set to the calling API principal. Scoped to the caller's tenant via the API key.", "security": [ { "bearerAuth": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "name": { "type": "string", "description": "Display name for the trusted issuer.", "example": "Gov PID" }, "iss": { "type": "string", "description": "The HTTPS `iss` identifier of the issuer to trust. Must be unique within the tenant.", "example": "https://pid.gov.example" }, "trust_anchor": { "type": "string", "description": "How the issuer's signing key is anchored. Defaults to `https-jwks` when omitted.", "example": "https-jwks", "enum": [ "https-jwks", "did-web", "x509" ] }, "jwks_uri": { "type": "string", "description": "JWKS endpoint used to fetch the issuer's public keys. Provide when `trust_anchor` is `https-jwks`.", "example": "https://pid.gov.example/.well-known/jwks.json" }, "did": { "type": "string", "description": "Decentralized identifier of the issuer. Provide when `trust_anchor` is `did-web`.", "example": "did:web:pid.gov.example" }, "framework": { "type": "string", "description": "Trust framework slug (from GET /v1/trust-frameworks) to associate with this issuer (e.g. eidas2). Resolved to a framework if it exists; ignored if no match is found.", "example": "eidas2" } }, "required": [ "name", "iss" ] } } } } } }, "/v1/trusted-issuers/{uuid}": { "delete": { "operationId": "v1_trusted_issuers_destroy", "parameters": [ { "name": "uuid", "in": "path", "required": true, "description": "UUID of the trusted issuer registry entry to remove.", "schema": { "type": "string", "format": "uuid" } } ], "tags": [ "Trust Registry" ], "responses": { "204": { "description": "The trusted issuer was removed (soft-deleted). No response body is returned.", "content": { "application/json": { "schema": { "type": "object", "properties": {} } } } } }, "summary": "Remove a trusted issuer", "description": "Remove an issuer from this tenant's trusted-issuer registry (soft delete). After removal, credentials from this issuer no longer pass the verification trust gate. Scoped to the caller's tenant \u2014 a UUID belonging to a different tenant returns 404. Returns 204 No Content on success with an empty body.", "security": [ { "bearerAuth": [] } ] } }, "/v1/trusted-issuers/{uuid}/validate": { "post": { "operationId": "v1_trusted_issuers_validate_create", "parameters": [ { "name": "uuid", "in": "path", "required": true, "description": "UUID of the trusted issuer to validate.", "schema": { "type": "string", "format": "uuid" } } ], "tags": [ "Trust Registry" ], "responses": { "200": { "description": "The trusted issuer object with an appended `validation` block describing the re-check result.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "description": "UUID of the trusted issuer registry entry.", "example": "0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9", "format": "uuid" }, "name": { "type": "string", "description": "Display name of the trusted issuer.", "example": "Gov PID" }, "iss": { "type": "string", "description": "The HTTPS `iss` identifier of the issuer.", "example": "https://pid.gov.example" }, "trust_anchor": { "type": "string", "description": "How the issuer's signing key is anchored and resolved.", "example": "https-jwks", "enum": [ "https-jwks", "did-web", "x509" ] }, "jwks_uri": { "type": "string", "description": "JWKS endpoint for the issuer's public keys; empty string when not set.", "example": "https://pid.gov.example/.well-known/jwks.json" }, "did": { "type": "string", "description": "Issuer DID; empty string when not set.", "example": "" }, "status": { "type": "string", "description": "Trust status of the issuer.", "example": "active", "enum": [ "active", "revoked" ] }, "added_by": { "type": "string", "description": "Identifier of the API principal that registered this trusted issuer.", "example": "tenant:airline-acme" }, "framework": { "type": "string", "description": "Slug of the associated trust framework, or null if none.", "example": "eidas2" }, "created_at": { "type": "string", "description": "ISO 8601 timestamp when the trusted issuer was registered.", "example": "2026-06-29T12:34:56.789012+00:00", "format": "date-time" }, "validation": { "type": "object", "description": "Summary of the freshly run trust-anchor re-check.", "properties": { "reachable": { "type": "boolean", "description": "Whether the issuer endpoint (e.g. its JWKS or DID document) was reachable.", "example": true }, "anchor_ok": { "type": "boolean", "description": "Whether the issuer's trust anchor resolved and validated successfully.", "example": true }, "status": { "type": "string", "description": "The current registry status of the issuer at validation time.", "example": "active", "enum": [ "active", "revoked" ] } } } } } } } } }, "summary": "Validate a trusted issuer", "description": "Re-check that a registered trusted issuer is reachable and its trust anchor resolves. Returns the full trusted issuer object plus a `validation` block summarizing the check. Scoped to the caller's tenant \u2014 a UUID belonging to a different tenant returns 404.", "security": [ { "bearerAuth": [] } ] } }, "/v1/relying-parties": { "get": { "operationId": "v1_relying_parties_retrieve", "tags": [ "Trust Registry" ], "responses": { "200": { "description": "A bare JSON array of relying party objects for the caller's tenant, newest first.", "content": { "application/json": { "schema": { "type": "object", "properties": { "items": { "type": "array", "description": "Array of relying party objects.", "items": { "type": "string" } } } } } } } }, "summary": "List relying parties", "description": "List the relying parties (verifiers) registered under this tenant. A relying party's web origin is checked when it requests a credential presentation over the Digital Credentials API. Scoped to the caller's tenant via the API key. Returns a bare JSON array ordered by most-recently-created first.", "security": [ { "bearerAuth": [] } ] }, "post": { "operationId": "v1_relying_parties_create", "tags": [ "Trust Registry" ], "responses": { "200": { "description": "No response body" }, "201": { "description": "The relying party was registered. Returns the full relying party object.", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "description": "UUID of the newly created relying party registration.", "example": "1b2c3d4e-5f60-7182-93a4-b5c6d7e8f901", "format": "uuid" }, "name": { "type": "string", "description": "Display name of the relying party / verifier.", "example": "Airline Check-in" }, "origin": { "type": "string", "description": "The registered web origin of the relying party.", "example": "https://checkin.airline.com" }, "client_id": { "type": "string", "description": "OAuth/OpenID4VP client identifier for the relying party; empty string when not set.", "example": "airline-checkin" }, "status": { "type": "string", "description": "Status of the relying party registration. Newly created relying parties are always `active`.", "example": "active", "enum": [ "active", "revoked" ] }, "created_at": { "type": "string", "description": "ISO 8601 timestamp when the relying party was registered.", "example": "2026-06-29T12:34:56.789012+00:00", "format": "date-time" } } } } } } }, "summary": "Register a relying party", "description": "Register a relying party (verifier) under this tenant. Its web origin is validated when it requests a credential presentation over the Digital Credentials API. The new registration is always created with status `active`. Scoped to the caller's tenant via the API key.", "security": [ { "bearerAuth": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "name": { "type": "string", "description": "Display name for the relying party / verifier.", "example": "Airline Check-in" }, "origin": { "type": "string", "description": "Web origin of the relying party; this is the value checked on DC-API presentation requests.", "example": "https://checkin.airline.com" }, "client_id": { "type": "string", "description": "OAuth/OpenID4VP client identifier for the relying party.", "example": "airline-checkin" } }, "required": [ "name", "origin" ] } } } } } }, "/v1/relying-parties/{uuid}": { "delete": { "operationId": "v1_relying_parties_destroy", "parameters": [ { "name": "uuid", "in": "path", "required": true, "description": "UUID of the relying party registration to remove.", "schema": { "type": "string", "format": "uuid" } } ], "tags": [ "Trust Registry" ], "responses": { "204": { "description": "The relying party registration was removed (soft-deleted). No response body is returned.", "content": { "application/json": { "schema": { "type": "object", "properties": {} } } } } }, "summary": "Remove a relying party", "description": "Remove a relying party registration from this tenant (soft delete). After removal, the verifier's origin is no longer recognized on presentation requests. Scoped to the caller's tenant \u2014 a UUID belonging to a different tenant returns 404. Returns 204 No Content on success with an empty body.", "security": [ { "bearerAuth": [] } ] } }, "/v1/keys": { "get": { "operationId": "v1_keys_retrieve", "tags": [ "Issuer Keys" ], "responses": { "200": { "description": "The tenant's signing keys, newest first, plus the JWKS URI for verifiers.", "content": { "application/json": { "schema": { "type": "object", "properties": { "keys": { "type": "array", "description": "All signing keys for the tenant that are still relevant to verification (status active or rotated), ordered by creation time descending. A rotated key remains listed until it passes its valid_until and is retired.", "items": { "type": "object", "properties": { "id": { "type": "string", "description": "Internal UUID primary key of the signing-key record.", "example": "5f6a1c2d-3e4b-5a6c-7d8e-9f0a1b2c3d4e", "format": "uuid" }, "kid": { "type": "string", "description": "JWK key identifier (the RFC 7638 thumbprint of the public key). This is the `kid` placed in the SD-JWT VC header; verifiers resolve the verification key by matching it.", "example": "Ab3kQ9z1Xy7..." }, "alg": { "type": "string", "description": "JWS signing algorithm used by this key. Always ES256 (ECDSA over P-256 with SHA-256).", "example": "ES256" }, "status": { "type": "string", "description": "Key lifecycle state. `active` signs newly issued credentials; `rotated` is superseded but still verifies credentials already issued under it until its valid_until; `retired` is past validity and no longer trusted (retired keys are not returned by this endpoint).", "example": "active", "enum": [ "active", "rotated", "retired" ] }, "is_active": { "type": "boolean", "description": "True only for the single current signing key (equivalent to status == active). Rotated keys have this set to false.", "example": true }, "valid_until": { "type": [ "string", "null" ], "description": "ISO 8601 timestamp until which a rotated key remains valid for verification (the overlapping-validity window, 365 days from rotation by default). Null for the active key, which has no expiry while active.", "example": "2027-06-30T12:00:00+00:00", "format": "date-time" }, "created_at": { "type": "string", "description": "ISO 8601 timestamp when the signing key was created.", "example": "2026-06-30T12:00:00+00:00", "format": "date-time" } } } }, "jwks_uri": { "type": "string", "description": "Absolute URL of this tenant's JWT VC Issuer metadata / JWKS endpoint, where verifiers fetch the public JWKs (active + rotated, not-expired keys) needed to verify issued credentials.", "example": "https://credentials.didit.me/v1/issuers/acme-air/.well-known/jwt-vc-issuer", "format": "uri" } } } } } } }, "summary": "List issuer signing keys", "description": "List this tenant's issuer signing keys (active, rotated, and not-yet-retired) and the JWKS endpoint where verifiers can fetch the public keys. Call it to inspect the current key lifecycle before or after a rotation, or to discover the JWKS URI to publish to relying parties. Authenticated with the tenant API key; the active signing key is lazily created on first call so the list is never empty.", "security": [ { "bearerAuth": [] } ] } }, "/v1/keys/rotate": { "post": { "operationId": "v1_keys_rotate_create", "tags": [ "Issuer Keys" ], "responses": { "200": { "description": "No response body" }, "201": { "description": "Rotation succeeded. Returns the newly created active key, the demoted previous key (if one existed), and an impact summary describing how many in-force credentials remain verifiable under the rotated key.", "content": { "application/json": { "schema": { "type": "object", "properties": { "new_key": { "type": "object", "description": "The freshly created signing key, now active and used to sign all newly issued credentials.", "properties": { "id": { "type": "string", "description": "Internal UUID primary key of the new signing-key record.", "example": "7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d", "format": "uuid" }, "kid": { "type": "string", "description": "JWK key identifier (RFC 7638 thumbprint) of the new active key. Future-issued credentials carry this `kid` in their header.", "example": "Zx9wV8u7T6s..." }, "alg": { "type": "string", "description": "JWS signing algorithm. Always ES256.", "example": "ES256" }, "status": { "type": "string", "description": "Lifecycle state of the new key; always `active` immediately after rotation.", "example": "active", "enum": [ "active", "rotated", "retired" ] }, "is_active": { "type": "boolean", "description": "Always true for the new key.", "example": true }, "valid_until": { "type": [ "string", "null" ], "description": "Validity expiry. Null for the new active key (no expiry while active).", "example": null, "format": "date-time" }, "created_at": { "type": "string", "description": "ISO 8601 timestamp when the new key was created.", "example": "2026-06-30T12:00:00+00:00", "format": "date-time" } } }, "rotated_key": { "type": [ "object", "null" ], "description": "The previously active key, now demoted to `rotated`. Null when no active key existed before this call (e.g. the very first rotation on a tenant). Remains verifiable until its valid_until.", "properties": { "id": { "type": "string", "description": "Internal UUID primary key of the rotated (formerly active) signing-key record.", "example": "5f6a1c2d-3e4b-5a6c-7d8e-9f0a1b2c3d4e", "format": "uuid" }, "kid": { "type": "string", "description": "JWK key identifier (RFC 7638 thumbprint) of the rotated key. Credentials already issued under this `kid` keep verifying during the overlap window.", "example": "Ab3kQ9z1Xy7..." }, "alg": { "type": "string", "description": "JWS signing algorithm. Always ES256.", "example": "ES256" }, "status": { "type": "string", "description": "Lifecycle state of the demoted key; always `rotated` immediately after rotation.", "example": "rotated", "enum": [ "active", "rotated", "retired" ] }, "is_active": { "type": "boolean", "description": "Always false for the rotated key.", "example": false }, "valid_until": { "type": [ "string", "null" ], "description": "ISO 8601 timestamp until which the rotated key remains valid for verification (set to now + 365 days at rotation).", "example": "2027-06-30T12:00:00+00:00", "format": "date-time" }, "created_at": { "type": "string", "description": "ISO 8601 timestamp when the rotated key was originally created.", "example": "2025-06-30T12:00:00+00:00", "format": "date-time" } } }, "impact": { "type": "object", "description": "Summary of the rotation's effect on credentials already in force.", "properties": { "credentials_under_previous_key": { "type": "integer", "description": "Count of the tenant's currently-issued (status == issued, not soft-deleted) credentials that were signed under the previous key and remain verifiable under it until its valid_until.", "example": 4213 }, "note": { "type": "string", "description": "Human-readable explanation of the overlapping-validity behaviour after rotation.", "example": "Existing credentials remain valid under the rotated key until its valid_until; new credentials use the new key." } } } } } } } } }, "summary": "Rotate the issuer signing key", "description": "Rotate the tenant's issuer signing key: mint a fresh active key for all future issuance and demote the previously active key to `rotated` with an overlapping-validity window (365 days) so credentials already issued under it keep verifying. Call it on a routine key-rotation schedule or in response to a suspected key compromise. Authenticated with the tenant API key.", "security": [ { "bearerAuth": [] } ], "requestBody": { "content": { "application/json": { "example": {} } } } } }, "/v1/keys/impact": { "get": { "operationId": "v1_keys_impact_retrieve", "tags": [ "Issuer Keys" ], "responses": { "200": { "description": "The rotation-impact preview for the tenant.", "content": { "application/json": { "schema": { "type": "object", "properties": { "credentials_under_active_key": { "type": "integer", "description": "Count of the tenant's currently-issued (status == issued, not soft-deleted) credentials that would continue to verify under the current key after rotation, because the rotated key stays valid during its overlap window.", "example": 4213 }, "note": { "type": "string", "description": "Human-readable explanation of the rotation-overlap behaviour.", "example": "These credentials would continue to verify under the current key after rotation (overlapping validity)." } } } } } } }, "summary": "Key-rotation impact (credentials under current key)", "description": "Preview the blast radius of a key rotation without performing one: returns how many currently-issued credentials would continue to verify under the current key after rotation (overlapping validity). Call it before POST /v1/keys/rotate to understand the impact. Authenticated with the tenant API key.", "security": [ { "bearerAuth": [] } ] } }, "/v1/credential-offers": { "post": { "operationId": "v1_credential_offers_create", "description": "Create an OpenID4VCI credential offer using the pre-authorized_code flow. Authenticated with the tenant API key (Bearer). The issuer picks a credential template (scoped to the caller's tenant), embeds the claim values, and \u2014 by default \u2014 generates a 6-digit transaction code (PIN). Returns the offer identifier, the wallet deep-link URI, the issuer identifier, the pre-authorized code, the tx_code (if any), and the `grants` object the wallet uses to drive the token exchange. The offer expires 10 minutes after creation and is single-use.", "tags": [ "OpenID4VCI Protocol" ], "responses": { "200": { "description": "No response body" }, "201": { "description": "Credential offer created.", "content": { "application/json": { "schema": { "type": "object", "properties": { "offer_id": { "type": "string", "description": "UUID of the created offer. Use it in GET /v1/credential-offers/{uuid} (holder preview) and POST /v1/credential-offers/{uuid}/accept.", "example": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d", "format": "uuid" }, "credential_offer_uri": { "type": "string", "description": "The OpenID4VCI credential-offer deep link for the holder wallet \u2014 `openid-credential-offer://?tenant={slug}&code={pre_authorized_code}`. Typically rendered as a QR code.", "example": "openid-credential-offer://?tenant=airline-demo&code=s8Ilre7v2K9pQx1bN4mZ", "format": "uri" }, "credential_issuer": { "type": "string", "description": "The issuer's credential-issuer identifier \u2014 `{base}/v1/issuers/{slug}`.", "example": "https://id.didit.me/v1/issuers/airline-demo", "format": "uri" }, "pre_authorized_code": { "type": "string", "description": "The pre-authorized code the wallet sends to POST /v1/oauth/token as `pre-authorized_code`. Also surfaced inside `grants`.", "example": "s8Ilre7v2K9pQx1bN4mZ" }, "tx_code": { "type": "string", "description": "The 6-digit transaction code (PIN) the holder must enter at token exchange. Empty string when the offer was created with `tx_code: false`.", "example": "517082" }, "grants": { "type": "object", "description": "OpenID4VCI grants object keyed by grant type. Contains a single key, `urn:ietf:params:oauth:grant-type:pre-authorized_code`, whose value object carries the `pre-authorized_code`. This is the structure a wallet reads to drive the token request.", "example": { "urn:ietf:params:oauth:grant-type:pre-authorized_code": { "pre-authorized_code": "s8Ilre7v2K9pQx1bN4mZ" } }, "properties": {} } } } } } }, "404": { "description": "No template with `template_id` exists for the caller's tenant (or it is soft-deleted).", "content": { "application/json": { "schema": { "type": "object", "properties": { "detail": { "type": "string", "description": "Human-readable not-found message.", "example": "Not found." } } } } } } }, "summary": "Create a credential offer", "security": [ { "bearerAuth": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "template_id": { "type": "string", "description": "UUID of the credential template to issue from. Must belong to the caller's tenant and not be soft-deleted, else 404. Determines the schema (`vct`, attributes, selective-disclosure flags).", "example": "8f3b1c2d-4e5a-6b7c-8d9e-0f1a2b3c4d5e", "format": "uuid" }, "claims": { "type": "object", "description": "Claim name/value map to embed in the credential, keyed by the schema's attribute names. Defaults to `{}` if omitted. Values are stored on the offer and bound into the SD-JWT VC at issuance.", "example": { "given_name": "Ada", "tier": "gold" }, "properties": {} }, "tx_code": { "type": "boolean", "description": "Whether to require a transaction code (PIN) at token exchange. Defaults to `true`. When `true`, a random 6-digit numeric tx_code is generated and returned (deliver it to the holder out-of-band). When `false`, no tx_code is required and the response `tx_code` is an empty string.", "example": true } }, "required": [ "template_id" ] } } } } } }, "/v1/credential-offers/{uuid}": { "get": { "operationId": "v1_credential_offers_retrieve", "description": "Holder-facing preview of a credential offer. Public and unauthenticated \u2014 the offer UUID is the capability. Returns a wallet-friendly summary of what would be issued: the issuer identity, the credential type and format, expiry, whether a transaction code is required, and the per-claim breakdown (label, value, selective-disclosure flag). Used to render a consent screen before the holder accepts the offer.", "parameters": [ { "name": "uuid", "in": "path", "required": true, "description": "`uuid` path parameter.", "schema": { "type": "string" } } ], "tags": [ "OpenID4VCI Protocol" ], "responses": { "200": { "description": "Holder offer preview.", "content": { "application/json": { "schema": { "type": "object", "properties": { "offer_id": { "type": "string", "description": "UUID of the offer.", "example": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d", "format": "uuid" }, "offer_uri": { "type": "string", "description": "The OpenID4VCI credential-offer deep link \u2014 `openid-credential-offer://?tenant={slug}&code={pre_authorized_code}`.", "example": "openid-credential-offer://?tenant=airline-demo&code=s8Ilre7v2K9pQx1bN4mZ", "format": "uri" }, "issuer": { "type": "object", "description": "Issuer identity for the consent screen.", "properties": { "name": { "type": "string", "description": "Human-readable tenant/issuer name.", "example": "Airline Demo" }, "did": { "type": "string", "description": "Decentralized identifier of the issuer \u2014 `did:web:{slug}.didit.test`.", "example": "did:web:airline-demo.didit.test" }, "domain": { "type": "string", "description": "The issuer's `iss` value (issuer domain/identifier configured on the tenant).", "example": "https://id.didit.me/v1/issuers/airline-demo" }, "id": { "type": "string", "description": "The tenant slug.", "example": "airline-demo" } } }, "vct": { "type": "string", "description": "Verifiable Credential Type that would be issued (the schema's `vct`).", "example": "AirlineLoyalty" }, "format": { "type": "string", "description": "Credential format. Always `SD_JWT_VC`.", "example": "SD_JWT_VC" }, "expires_at": { "type": "string", "description": "ISO-8601 timestamp when the offer expires (10 minutes after creation). Empty string if the offer has no expiry set.", "example": "2026-06-30T13:40:00+00:00", "format": "date-time" }, "tx_code_required": { "type": "boolean", "description": "Whether the holder must supply a transaction code at token exchange. True when the offer was created with `tx_code: true`.", "example": true }, "tx_code_length": { "type": "integer", "description": "Expected length of the transaction code, so the wallet can render the right PIN input. Equals the length of the offer's tx_code, or 6 when no tx_code is set.", "example": 6 }, "claims": { "type": "array", "description": "Per-claim breakdown of what the credential will contain, one entry per schema attribute.", "items": { "type": "string" } } } } } } }, "404": { "description": "No offer exists for the given UUID (or it is soft-deleted).", "content": { "application/json": { "schema": { "type": "object", "properties": { "detail": { "type": "string", "description": "Human-readable not-found message.", "example": "Not found." } } } } } } }, "summary": "Get a credential offer (holder preview)", "security": [] } }, "/v1/credential-offers/{uuid}/accept": { "post": { "operationId": "v1_credential_offers_accept_create", "description": "Demo convenience endpoint that accepts a credential offer and runs the full OpenID4VCI token -> nonce -> proof -> credential flow server-side using a freshly generated holder key. Public and unauthenticated (the offer UUID is the capability). It redeems the pre-authorized code with the offer's own tx_code, mints a c_nonce, builds the holder proof JWT, and issues the SD-JWT VC \u2014 returning the assembled credential, its type, and the generated holder public JWK. NOTE: real wallets perform proof-of-possession client-side; this endpoint exists so the holder demo UI can obtain a credential in one call. Because it consumes the single-use offer/token/nonce, an already-redeemed or expired offer returns 400.", "parameters": [ { "name": "uuid", "in": "path", "required": true, "description": "`uuid` path parameter.", "schema": { "type": "string" } } ], "tags": [ "OpenID4VCI Protocol" ], "responses": { "200": { "description": "Offer accepted and credential issued.", "content": { "application/json": { "schema": { "type": "object", "properties": { "credential": { "type": "object", "description": "The issued credential, expanded for the holder UI.", "properties": { "id": { "type": "string", "description": "UUID of the issued credential record. Empty string if no issued record is found.", "example": "9c0d1e2f-3a4b-5c6d-7e8f-9a0b1c2d3e4f", "format": "uuid" }, "issuer": { "type": "object", "description": "Issuer identity block \u2014 `name`, `did` (`did:web:{slug}.didit.test`), `domain` (the tenant `iss`), `id` (slug).", "example": { "name": "Airline Demo", "did": "did:web:airline-demo.didit.test", "domain": "https://id.didit.me/v1/issuers/airline-demo", "id": "airline-demo" }, "properties": {} }, "vct": { "type": "string", "description": "Verifiable Credential Type of the issued credential.", "example": "AirlineLoyalty" }, "credential_name": { "type": "string", "description": "Human-readable name of the credential (the schema name).", "example": "Airline Loyalty Card" }, "format": { "type": "string", "description": "Credential format. Always `SD_JWT_VC`.", "example": "SD_JWT_VC" }, "status": { "type": "string", "description": "Lifecycle status of the freshly issued credential. Always `issued` here.", "example": "issued" }, "issued_at": { "type": "string", "description": "ISO-8601 issuance timestamp. Empty string if no issued record is found.", "example": "2026-06-30T13:31:00+00:00", "format": "date-time" }, "expires_at": { "type": "string", "description": "ISO-8601 expiry of the credential. Empty string if the credential has no expiry (or no issued record).", "example": "2027-06-30T13:31:00+00:00", "format": "date-time" }, "subject": { "type": "string", "description": "Convenience subject label \u2014 the offer's `given_name` claim value (empty if absent).", "example": "Ada" }, "claims": { "type": "array", "description": "Per-claim breakdown (same shape as the holder preview): `name`, `label`, `value`, `selective_disclosure`, one entry per schema attribute.", "example": [ { "name": "given_name", "label": "Given Name", "value": "Ada", "selective_disclosure": true }, { "name": "tier", "label": "Tier", "value": "gold", "selective_disclosure": true } ], "items": { "type": "string" } }, "sd_jwt": { "type": "string", "description": "The full SD-JWT VC string \u2014 `~~...~` \u2014 issued and bound to the generated holder key.", "example": "eyJhbGciOiJFUzI1NiIsInR5cCI6ImRjK3NkLWp3dCJ9..~WyJzYWx0IiwgImdpdmVuX25hbWUiLCAiQWRhIl0~" } } }, "vct": { "type": "string", "description": "Verifiable Credential Type of the issued credential (top-level convenience copy of `credential.vct`).", "example": "AirlineLoyalty" }, "holder_jwk": { "type": "object", "description": "The public JWK of the server-generated holder key the credential is bound to (key-binding public key). In a real wallet flow this key lives only on the holder device.", "example": { "kty": "EC", "crv": "P-256", "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM" }, "properties": {} } } } } } }, "400": { "description": "The flow could not complete \u2014 e.g. the offer was already redeemed, expired, or a tx_code/proof step failed.", "content": { "application/json": { "schema": { "type": "object", "properties": { "error": { "type": "string", "description": "Human-readable failure reason. Examples: `offer already redeemed`, `offer expired`, `invalid tx_code`.", "example": "offer already redeemed" } } } } } }, "404": { "description": "No offer exists for the given UUID (or it is soft-deleted).", "content": { "application/json": { "schema": { "type": "object", "properties": { "detail": { "type": "string", "description": "Human-readable not-found message.", "example": "Not found." } } } } } } }, "summary": "Accept an offer (demo: server-side PoP)", "security": [], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": {} } } } } } }, "/v1/oauth/token": { "post": { "operationId": "v1_oauth_token_create", "description": "OpenID4VCI token endpoint. Implements the pre-authorized_code grant only: the wallet exchanges the `pre-authorized_code` from a credential offer (and the transaction code if the offer required one) for a short-lived Bearer access token used at the credential endpoint. Public and unauthenticated (the pre-authorized_code itself is the capability). The token is single-use and expires in 300 seconds. tx_code is brute-force protected \u2014 after 5 failed attempts the offer locks out. The grant fails (HTTP 400 `invalid_grant`) if the code is unknown, already redeemed, expired, or the tx_code is wrong; an unsupported `grant_type` returns 400 `unsupported_grant_type`.", "tags": [ "OpenID4VCI Protocol" ], "responses": { "200": { "description": "Access token issued.", "content": { "application/json": { "schema": { "type": "object", "properties": { "access_token": { "type": "string", "description": "Opaque Bearer access token. Send it as `Authorization: Bearer ` to POST /v1/credential. Single-use and valid for `expires_in` seconds.", "example": "5a0W4gQ2tR8yU1vC7pLxZ3mB6nK9dF0" }, "token_type": { "type": "string", "description": "Token type. Always the literal `bearer`.", "example": "bearer" }, "expires_in": { "type": "integer", "description": "Access token lifetime in seconds. Always 300 (5 minutes).", "example": 300 } } } } } }, "400": { "description": "Grant rejected \u2014 unsupported grant_type, or an invalid/expired/already-redeemed pre-authorized_code, or a wrong/exceeded tx_code.", "content": { "application/json": { "schema": { "type": "object", "properties": { "error": { "type": "string", "description": "OAuth error code. `unsupported_grant_type` when `grant_type` is not the pre-authorized_code URN; `invalid_grant` for any code/tx_code failure.", "example": "invalid_grant" }, "error_description": { "type": "string", "description": "Human-readable reason. Present on `invalid_grant` responses. Typical values: `invalid pre-authorized_code`, `offer already redeemed`, `offer expired`, `invalid tx_code`, `tx_code attempts exceeded`.", "example": "invalid tx_code" } } } } } } }, "summary": "Token endpoint (pre-authorized_code grant)", "security": [], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "grant_type": { "type": "string", "description": "OAuth grant type. Must be exactly `urn:ietf:params:oauth:grant-type:pre-authorized_code` \u2014 the only grant the token endpoint supports. Any other value returns 400 `unsupported_grant_type`.", "example": "urn:ietf:params:oauth:grant-type:pre-authorized_code" }, "pre-authorized_code": { "type": "string", "description": "The pre-authorized code issued with the credential offer (the `pre_authorized_code` field of POST /v1/credential-offers, also present in the offer's `grants` object). The endpoint also accepts the snake_case alias `pre_authorized_code`. Identifies the offer being redeemed.", "example": "s8Ilre7v2K9pQx1bN4mZ" }, "tx_code": { "type": "string", "description": "The transaction code (PIN) for offers created with `tx_code: true`. A 6-digit numeric string communicated to the holder out-of-band. Omit (or send empty) for offers that have no tx_code. After 5 wrong attempts the offer is locked.", "example": "517082" } }, "required": [ "grant_type", "pre-authorized_code" ] } } } } } }, "/v1/nonce": { "post": { "operationId": "v1_nonce_create", "description": "OpenID4VCI nonce endpoint. Public, unauthenticated. Mints a fresh single-use `c_nonce` that the holder must embed in the proof JWT presented to the credential endpoint. The c_nonce is consumed when the proof is verified and expires after 60 seconds. The request body is empty.", "tags": [ "OpenID4VCI Protocol" ], "responses": { "200": { "description": "A single-use challenge nonce.", "content": { "application/json": { "schema": { "type": "object", "properties": { "c_nonce": { "type": "string", "description": "The challenge nonce. Place it in the `nonce` claim of the holder proof JWT sent to POST /v1/credential. Single-use \u2014 consumed on successful proof verification.", "example": "NKQRYXz1aB2cD3eF4gH5" }, "c_nonce_expires_in": { "type": "integer", "description": "Nonce lifetime in seconds. Always 60.", "example": 60 } } } } } } }, "summary": "Issue a c_nonce for the credential proof", "security": [], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": {} } } } } } }, "/v1/credential": { "post": { "operationId": "v1_credential_create", "description": "OpenID4VCI credential endpoint. Exchanges a Bearer access token (from POST /v1/oauth/token) plus a holder proof-of-possession JWT for the issued SD-JWT VC. Requires `Authorization: Bearer `. The proof JWT (typ `openid4vci-proof+jwt`) carries the holder's public key and must include the `c_nonce` minted by POST /v1/nonce as its `nonce` claim and the issuer's `credential_issuer` identifier as its `aud`. On success the access token, c_nonce, and offer are all consumed (single-use), the credential is bound to the holder key, and the SD-JWT VC is returned. Returns 401 if the Authorization header is missing/not Bearer, and 400 if the proof is missing or invalid (expired token/nonce, bad signature, wrong audience, etc.).", "tags": [ "OpenID4VCI Protocol" ], "responses": { "200": { "description": "The issued SD-JWT VC.", "content": { "application/json": { "schema": { "type": "object", "properties": { "credential": { "type": "string", "description": "The issued credential as a `dc+sd-jwt` (SD-JWT VC) string: the issuer-signed JWS followed by tilde-separated selective-disclosure segments, ending in a trailing tilde \u2014 `~~~`. The holder stores this and presents it (optionally with a key-binding JWT) to verifiers.", "example": "eyJhbGciOiJFUzI1NiIsInR5cCI6ImRjK3NkLWp3dCJ9.eyJ2Y3QiOiJBaXJsaW5lTG95YWx0eSIsImlzcyI6Imh0dHBzOi8vaWQuZGlkaXQubWUvdjEvaXNzdWVycy9haXJsaW5lLWRlbW8iLC4uLn0.~WyJzYWx0IiwgImdpdmVuX25hbWUiLCAiQWRhIl0~WyJzYWx0IiwgInRpZXIiLCAiZ29sZCJd~" }, "vct": { "type": "string", "description": "Verifiable Credential Type of the issued credential (the schema's `vct`).", "example": "AirlineLoyalty" } } } } } }, "400": { "description": "Missing or invalid proof (no `proof.jwt`, expired/invalid access token, expired/consumed c_nonce, malformed proof payload, bad key binding, or wrong audience).", "content": { "application/json": { "schema": { "type": "object", "properties": { "error": { "type": "string", "description": "Error code. Always `invalid_proof` for proof/token/nonce failures.", "example": "invalid_proof" }, "error_description": { "type": "string", "description": "Human-readable reason. Examples: `missing proof.jwt`, `invalid access token`, `access token already used`, `access token expired`, `invalid or expired c_nonce`, `malformed proof payload`.", "example": "invalid or expired c_nonce" } } } } } }, "401": { "description": "Missing or non-Bearer Authorization header.", "content": { "application/json": { "schema": { "type": "object", "properties": { "error": { "type": "string", "description": "Error code. Always `invalid_token`.", "example": "invalid_token" } } } } } } }, "summary": "Credential endpoint (holder proof-of-possession \u2192 SD-JWT VC)", "security": [], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "proof": { "type": "object", "description": "The holder proof-of-possession object. Carries the JWT that proves control of the holder key the credential will be bound to.", "properties": { "proof_type": { "type": "string", "description": "Proof type. Must be `jwt` (the only type supported, per `proof_types_supported` in the issuer metadata).", "example": "jwt" }, "jwt": { "type": "string", "description": "The proof JWT (header typ `openid4vci-proof+jwt`, signed with ES256 or EdDSA). Its payload must contain `nonce` = the `c_nonce` from POST /v1/nonce, `aud` = the issuer's `credential_issuer` identifier, and `iat`; its header carries the holder public `jwk`. Required \u2014 a missing or empty `proof.jwt` returns 400 `invalid_proof`.", "example": "eyJ0eXAiOiJvcGVuaWQ0dmNpLXByb29mK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Li4ufX0.eyJub25jZSI6Ik5LUVJZWC4uLiIsImF1ZCI6Imh0dHBzOi8vaWQuZGlkaXQubWUvdjEvaXNzdWVycy9haXJsaW5lLWRlbW8iLCJpYXQiOjE3MTk3NjUwMDB9." } }, "required": [ "proof_type" ] } }, "required": [ "proof" ] } } } } } }, "/v1/issuers/{slug}/.well-known/openid-credential-issuer": { "get": { "operationId": "v1_issuers_.well_known_openid_credential_issuer_retrieve", "parameters": [ { "name": "slug", "in": "path", "required": true, "description": "`slug` path parameter.", "schema": { "type": "string" } } ], "tags": [ "OpenID4VCI Protocol" ], "responses": { "200": { "description": "OpenID4VCI issuer metadata.", "content": { "application/json": { "schema": { "type": "object", "properties": { "credential_issuer": { "type": "string", "description": "The issuer's stable credential-issuer identifier \u2014 `{base}/v1/issuers/{slug}`. Used as the proof JWT `aud` (audience) and as the trust anchor for this issuer.", "example": "https://id.didit.me/v1/issuers/airline-demo", "format": "uri" }, "credential_endpoint": { "type": "string", "description": "Absolute URL of the credential endpoint (POST /v1/credential) where the wallet redeems a Bearer access token + holder proof for the SD-JWT VC.", "example": "https://id.didit.me/v1/credential", "format": "uri" }, "nonce_endpoint": { "type": "string", "description": "Absolute URL of the nonce endpoint (POST /v1/nonce) that mints the single-use `c_nonce` the holder must embed in the proof JWT.", "example": "https://id.didit.me/v1/nonce", "format": "uri" }, "token_endpoint": { "type": "string", "description": "Absolute URL of the token endpoint (POST /v1/oauth/token) that exchanges a pre-authorized_code (plus tx_code) for an access token.", "example": "https://id.didit.me/v1/oauth/token", "format": "uri" }, "jwt_vc_issuer": { "type": "string", "description": "Absolute URL of the SD-JWT VC issuer metadata / JWKS document (GET /v1/issuers/{slug}/.well-known/jwt-vc-issuer) holding the public keys used to verify issued credentials.", "example": "https://id.didit.me/v1/issuers/airline-demo/.well-known/jwt-vc-issuer", "format": "uri" }, "credential_configurations_supported": { "type": "object", "description": "Map of supported credential configurations, keyed by the credential's `vct` (Verifiable Credential Type). One entry per non-deleted schema owned by the tenant. The key equals the value of the nested `vct` field. An empty object means the tenant has no schemas yet.", "example": { "AirlineLoyalty": { "format": "dc+sd-jwt", "vct": "AirlineLoyalty", "cryptographic_binding_methods_supported": [ "jwk" ], "credential_signing_alg_values_supported": [ "ES256" ], "proof_types_supported": { "jwt": { "proof_signing_alg_values_supported": [ "ES256", "EdDSA" ] } } } }, "properties": { "format": { "type": "string", "description": "Credential format identifier. Always `dc+sd-jwt` (Digital Credentials SD-JWT VC).", "example": "dc+sd-jwt" }, "vct": { "type": "string", "description": "Verifiable Credential Type \u2014 the schema's `vct`. Matches the map key.", "example": "AirlineLoyalty" }, "cryptographic_binding_methods_supported": { "type": "array", "description": "Holder key-binding methods the issuer accepts. Always `[\"jwk\"]` \u2014 the holder binds the credential to a public JWK.", "example": [ "jwk" ], "items": { "type": "string" } }, "credential_signing_alg_values_supported": { "type": "array", "description": "Algorithms the issuer uses to sign the credential. Always `[\"ES256\"]`.", "example": [ "ES256" ], "items": { "type": "string" } }, "proof_types_supported": { "type": "object", "description": "Proof types the credential endpoint accepts, keyed by proof type. Only `jwt` is supported; its `proof_signing_alg_values_supported` lists the algorithms the holder's proof JWT may be signed with (`ES256`, `EdDSA`).", "example": { "jwt": { "proof_signing_alg_values_supported": [ "ES256", "EdDSA" ] } }, "properties": {} } } } } } } } }, "404": { "description": "No tenant exists for the given slug (or it is soft-deleted).", "content": { "application/json": { "schema": { "type": "object", "properties": { "detail": { "type": "string", "description": "Human-readable not-found message.", "example": "Not found." } } } } } } }, "summary": "OpenID4VCI issuer metadata", "description": "OpenID4VCI 1.0 issuer metadata document. Public, unauthenticated, served per tenant under the tenant's issuer slug. A holder wallet fetches this first to discover the credential, token, nonce, and JWKS endpoints, plus the set of credential configurations the issuer supports. The `credential_configurations_supported` map is built dynamically from the tenant's (non-deleted) credential schemas, keyed by each schema's Verifiable Credential Type (`vct`). All endpoint URLs are absolute and derived from the request's base URL.", "security": [] } }, "/v1/issuers/{slug}/.well-known/jwt-vc-issuer": { "get": { "operationId": "v1_issuers_.well_known_jwt_vc_issuer_retrieve", "parameters": [ { "name": "slug", "in": "path", "required": true, "description": "`slug` path parameter.", "schema": { "type": "string" } } ], "tags": [ "OpenID4VCI Protocol" ], "responses": { "200": { "description": "SD-JWT VC issuer JWKS.", "content": { "application/json": { "schema": { "type": "object", "properties": { "issuer": { "type": "string", "description": "The issuer's credential-issuer identifier \u2014 `{base}/v1/issuers/{slug}`. Matches `credential_issuer` in the issuer metadata document.", "example": "https://id.didit.me/v1/issuers/airline-demo", "format": "uri" }, "jwks": { "type": "object", "description": "JSON Web Key Set wrapping the issuer's public signing key(s).", "properties": { "keys": { "type": "array", "description": "Array of public JWKs. Currently holds exactly one entry: the tenant's active issuer public key (EC P-256 / ES256). Each key includes a `kid` equal to its JWK thumbprint.", "example": [ { "kty": "EC", "crv": "P-256", "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0", "kid": "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs" } ], "items": { "type": "string" } } } } } } } } }, "404": { "description": "No tenant exists for the given slug (or it is soft-deleted).", "content": { "application/json": { "schema": { "type": "object", "properties": { "detail": { "type": "string", "description": "Human-readable not-found message.", "example": "Not found." } } } } } } }, "summary": "SD-JWT VC issuer JWKS", "description": "SD-JWT VC issuer metadata (the JWKS document) for a tenant. Public and unauthenticated. Returns the issuer identifier plus the JSON Web Key Set holding the tenant's active issuer public key, which verifiers and wallets use to validate the signature on issued SD-JWT VCs. The single returned key has its `kid` set to its JWK thumbprint.", "security": [] } } }, "components": { "schemas": {}, "securitySchemes": { "bearerAuth": { "type": "http", "scheme": "bearer", "description": "Tenant API key" } } }, "servers": [ { "url": "http://localhost:8011", "description": "Local dev" }, { "url": "https://credentials.didit.me", "description": "Production (placeholder)" } ], "tags": [ { "name": "Credentials" }, { "name": "OpenID4VP Verification" }, { "name": "Status Lists" }, { "name": "Trust Registry" }, { "name": "Issuer Keys" }, { "name": "OpenID4VCI Protocol" } ] }