All notable changes to the DeepCredentials B2B API.
Everything below is new since v2.3.0.
- Images at most 800 KB per credential — the
image/jpegclaims of one credential may together be at most 800 KB decoded (was 4 MB per image). A credential is now delivered as a batch of up to ten copies, and a wallet takes at most 20 MB per batch. Offers, invitations and self-service requests over the limit are400 invalid_argument. POST /b2b/v1/verification-sessionsverifies one credential definition, nothing else —credential_definition_idis required;case_idandallowed_issuer_didsare no longer taken, so a request that named only acase_idis now400 invalid_argument(credential_definition_id is required). A verification case is launched at its own endpoint,POST /b2b/v1/verification-cases/{caseId}/sessions(see Added). A session trusts exactly the issuer DIDs pinned on its credential definition inX-Environment, reported inaccepted_issuer_dids; there is no per-session allow-list.claimsis optional: omitted or empty asks for every claim of the definition, where v2.3.0 refused an empty list.- The session resource has one shape (
GET /b2b/v1/verification-sessions/{sessionId}, the list, and theverification.completedwebhook's snapshot), whichever way the session was started. Removed:requested_fields,credential_subject_data,credential_subject_types,matched_vct, andcredential_definition.vct. In their place:case(id,version,title) on a session launched from a verification case, orcredential_definition(id,name,has_logo) on a session created for a credential definition — one of the two, never both.requested_attributes— what was asked for, each with itsid,label,value_type,sensitive,constraintsandbindings(keyed by credential definition id, never by vct).attributes— onsuccess, the disclosed values, keyed by the case's attribute ids or, on a credential-definition session, by claim keys. Value types are onrequested_attributes[].value_type.presented_credential_definition_id— onsuccess, the credential definition of the credential the holder actually presented: one of the case'scredential_definition_ids, orcredential_definition.idon a credential-definition session.
- A credential definition is used by its id, whoever owns it —
POST /b2b/v1/verification-sessionsand a verification case accept any organization's definition by id, not only your own and the official ones, and the newGET /b2b/v1/credential-definitions/{credentialDefinitionId}answers for any id. A definition id is not a secret (it is thevctof every credential of its type) and its schema is readable by protocol, so the id is what it takes. The list (GET /b2b/v1/credential-definitions) still shows your own and the official ones; its entries addownandis_listed, andis_listedmeans only that the owner lists the definition in the catalogue (other organizations' pickers in the portal) — it gates nothing. is_publicis renamedis_listedon credential definitions (GET /b2b/v1/credential-definitions): it means listed in the catalogue and nothing more — read below. A "public" flag implied a "private" that does not exist: a definition's id is in every credential and every QR code, and its schema is readable by protocol, listed or not.
A new Verification Cases API and guide. A verification case is a rule: which credential definitions are accepted, which attributes are asked for, and the constraints their values must meet. It is authored once and launched by id.
- Read —
GET /b2b/v1/verification-caseslists your organization's cases (by default those that run inX-Environmentand are not archived; filtersavailability=all,archived=exclude|only|include,reference_id).GET /b2b/v1/verification-cases/{caseId}reads one of yours, archived too, or any other organization's case by id (not archived; without its owner'sreference_id). Requiresdeepcredentials.verify. - Author —
POST /b2b/v1/verification-casescreates a case,PUT /b2b/v1/verification-cases/{caseId}replaces its contract and texts,PATCH /b2b/v1/verification-cases/{caseId}/archivedarchives or restores it. Requiresdeepcredentials.verification-cases. A case names its credentials ascredential_definition_ids(your own, official ones, or any other organization's, by id; with several, the holder presents any one of them) and binds each attribute to a claim per definition id (bindings); an attribute'svalue_typemay be omitted and is taken from the bound claims, and itslabelis one string for your staff. What a value must satisfy is grouped under the attribute'sconstraints(values, ormin/max; a date bound may be relative, such astoday-18y). What the holder reads is grouped underpresentation:title(the verification screen's headline) andpurpose(why the verification asks), each per language; a case has no separate internal description. In the swiyu Sandbox both are published with the case's request in the swiyu trust registry at its first launch. A case reads back in the shape it is written, and a session'srequested_attributescarry the constraints the same way.POSTdocuments404 not_foundfor an unknown entry ofcredential_definition_ids. - Launch —
POST /b2b/v1/verification-cases/{caseId}/sessionsstarts a session from a case, with optionalcase_version,reference_idandpurpose. The case is the contract: the session asks for every attribute with its constraints, as authored; there is no per-session narrowing. The session is polled and reported like any other. - Your key —
reference_idis your own identifier for what a case guards (a door, a zone), unique within your organization, archived cases included. A key in use is409 already_existswith a message startingreference_id_conflict:and the holder's id indetails.case_id. - Versions —
versionmoves whenever what a case asks for changes, never on a rename.case_versionon a launch runs the current version, or an older one the owner keeps open (launchable_versions, a migration window;keep_previous_launchableon an update opens it for the version it replaces); anything else is409 aborted(case_version_mismatch:), as is a staleexpected_versionon an update or an update another save overtook. - Environments — a case runs wherever every credential it accepts is enabled, listed in
environments. A launch in another environment is400 failed_preconditionwith a message startingcase_not_available:anddetailsnaming the environment and the definitions not enabled there, byidandname. A case whose date bounds have crossed since it was saved is refused at launch with400 failed_preconditionnaming the attribute. - Any organization's case, by id — a case is run by whoever holds its id, as their own verification; the case stays its owner's to change, archive and grant. A case id is not a secret (it is in every QR code the case produces), and everything the case asks for is in the OpenID4VP request object any wallet fetches. Another organization's case reads in the same shape as your own — contract,
bindings,presentation,launchable_versions,accepted_issuer_didson its sessions — without the owner'sreference_id; archived, it answers404to other organizations, on read and on launch. See Whose case. - What the verifier learns — a case session's
attributescome back only when every constraint is met. A value that fails one closes the session asfailedwithattribute_value_rejectedand one fixederror_descriptionthat names neither the attribute nor the value; nothing disclosed is stored.
- Same-device return — both session endpoints take an optional
redirect_uri(absolutehttps, no fragment, noresponse_code). After the holder answers, the wallet offers a button back to it, with a freshresponse_code. Such a session returns itsattributesonly toGET /b2b/v1/verification-sessions/{sessionId}?response_code=…with that code (valid 30 minutes; a wrong or expired code is403 permission_denied). Without the code, and always in the list,attributesis left out andattributes_withheldistrue. Sessions withoutredirect_uriare unchanged. See Same-Device Return. case_idfilter —GET /b2b/v1/verification-sessions?case_id=…lists the sessions your organization launched from one case, your own or another organization's.POST /b2b/v1/verification-sessionsdocuments404 not_foundfor an unknowncredential_definition_id, and400 failed_preconditionwhen no issuer of the definition is enabled inX-Environment.- Wallet errors speak the session's vocabulary — an error the holder's wallet reports is mapped before the session is stored:
access_denied(the holder declined, or had nothing matching to present) reads aserror_code: presentation_declined, a refusal of the request itself asrequest_rejected_by_wallet, anything else aswallet_error, each with anerror_descriptionwritten by the platform. The raw OAuth / OpenID4VP codes are no longer exposed. Everyerror_codea session can carry, with whether a new session can succeed, is listed under Sessionerror_codevalues.
GET /b2b/v1/credential-definitions/{credentialDefinitionId}reads one definition by id: your own, an official one, or any other organization's, listed in the catalogue or not. A definition id is not a secret (it is thevctof every credential of its type) and its schema is readable by protocol, so the id is what it takes. Definitions carryownandis_listed;is_listedmeans the definition is listed in the catalogue (other organizations' pickers in the portal), nothing more.- Name and description per language — definitions carry
display(the name per language, base language first) anddescription(the description per language), in the{locale, name}shape a claim'sdisplayhas. Your organization writes each language;namestays the name in the base language. The wallet shows the credential under the holder's language. - Images issued as data URLs — an
image/jpegclaim is carried in the credential as a data URL (data:image/jpeg;base64,…), as the credential's rendering metadata declares and the swiyu wallets require to show it as a picture. Offers and invitations still take plain base64. A verification reports an image attribute as the wallet presented it, so for credentials issued from now on it is a data URL. - Batch delivery and background refill — a credential is delivered to the wallet as up to ten copies of the same data, and the wallet refills them for as long as the credential is active and not expired, with the same data and expiry; verifiers cannot link two presentations of it. Nothing changes in the API: one credential, one id, one status, which applies to every copy. See Copies in the wallet.
- Richer claims — claims may carry
description[],rules,options[]andsensitive; two new value types,dateandenum.value_typeis always one ofstring,integer,number,boolean,date,enum,image/jpeg; the legacy spellings are still accepted in requests. vctis a URN minted by the platform —urn:vct:deepcredentials:<definition id>for definitions created from now on; treat it as opaque. Official types keep their identifiers.- Issuers per environment —
GET /b2b/v1/credential-definitionslists the issuer DIDs pinned per definition (issuers). Offers and invitations refuse an environment the definition is not enabled in with400 failed_precondition. - Lifetime as a day —
valid_until(YYYY-MM-DD, end of day Europe/Zurich) on offers and invitations, mutually exclusive withvalidity_seconds; offers reportcredential_valid_until. - Validity claim — a definition may name one date claim as its
validity_claim, filled from the credential lifetime; a request that sends it is refused. - Claim rules are enforced at issuance — offers, invitations and self-service reject a value that violates a claim's
rulesoroptionswith400 invalid_argument,detailsnaming theclaimandrule. POST /b2b/v1/credential-offers/{offerId}/revokewithdraws a pending offer; its status becomesrevoked.issuing.status_changedreportsrevoked— an offer an operator withdrew (in the portal or through the revoke endpoint) is delivered withstatus: revoked, next toissued,failedandexpired.- Swiss Beta-ID claims carry their real types (
date,boolean,integer); self-serviceextra_fieldsuse the claim shape (the old shape is still accepted on input).
- Requests end in
revokedwhen their invitation or offer is withdrawn — an operator revoking it, or disabling the environment on the credential definition, used to leave the request atinvitation_sentorissuedfor good. It now moves to the new terminal statusrevoked, and a new opt-in webhook,self_service.request.revoked({ request_id }), is sent. Existing webhook configurations must enable the event in the portal.
- Every guide was checked against the API. Optional response fields are no longer marked as always present, offer statuses list
revoked, the fourswiyu.*system events that were delivered but undocumented are described,X-Webhook-Timestampis documented as the unsigned delivery time, andvalid_untilis documented on invitations.
is_systemremoved —GET /b2b/v1/credential-definitionsno longer returnsis_system, and it is no longer a required field of the credential definition schema. There are no system credential definitions any more: the platform's starter shapes (e.g. City Card) are templates in the portal's create wizard, not credential definitions, and have no id this API accepts.- You issue only from credential definitions your organization owns —
POST /b2b/v1/credential-offersreturns404 not_found, andPOST /b2b/v1/credential-invitationsreturns400 invalid_argument, whencredential_definition_idnames an official credential definition or one that belongs to another organization — the same answers an unknown id gets. Previously any official, system or public credential definition was accepted. If you issued City Card as-is, create your own credential definition from the City Card template in the portal and use its id.
- What the list holds —
GET /b2b/v1/credential-definitionsreturns your organization's credential definitions plus the official ones (is_official: true), which are listed so you can verify against them. Other organizations' public credential definitions are no longer listed. Every entry withis_official: falseis your own and issuable. - Self-service — a self-service configuration must be built on a credential definition your organization owns. The quickstart and the City Card walkthrough now start from the template.
- Credential Invitations API — Service users can create, list, get, rotate, and revoke invitations under
/b2b/v1/credential-invitations. Create and rotate return the one-time plaintext code; list, get, and revoke expose code-free metadata. credential_invitation.status_changedwebhook — An opt-in terminal event for invitationredeemed,expired, andrevokedtransitions. The slim payload containsinvitation_id,status, andoffer_idonly after redemption. Existing webhook configurations must enable the event in the portal.
- Partner-owned invitation delivery — DeepCredentials does not email codes created or rotated through the B2B invitation API. The partner owns how it delivers the code and presents the redemption experience; its backend can exchange the recipient's email and code through the public redemption endpoint.
- Progress callbacks clarified — Verification, invitation, and issuance webhooks are slim hints followed by authenticated B2B GET reconciliation. Webhook delivery currently makes one synchronous attempt and can be missed.
reference_idclarified — The field is optional correlation metadata only; it does not provide idempotency, uniqueness, or response caching.
template_id→credential_definition_id— B2B request bodies and query parameters that previously acceptedtemplate_idnow requirecredential_definition_id. Applies toPOST /b2b/v1/verification-sessions,POST /b2b/v1/credential-offers, and the list query filters on those resources.template→credential_definitionin responses — The top-level response object previously namedtemplateis now namedcredential_definition. Inner fields (id,vct,name,has_logo) are unchanged. Terminology throughout the API is now "Credential Definition" rather than "Template".
GET /b2b/v1/credential-definitions— Returns the credential definitions available to your organization. Any authenticated service user can call this endpoint (no additional scope required beyond a valid token withX-Org-IdandX-Environment).
- "Open in API reference" deep-links — Every code sample in the guides now has an immediate deep-link to the matching operation in the rendered API reference.
- Broadened intro — The home page and Quickstart now cover all three API pillars: verification, issuance, and self-service.
- Portal-issued credentials retired. API keys and OAuth clients minted in the DeepCredentials portal no longer work. Authentication now uses DeepAdmin service users authenticated against the DeepCloud SSO realm with the OAuth 2.0 Resource Owner Password Credentials grant. See the new Service Users guide.
POST /b2b/v1/oauth/tokenremoved. Exchange credentials directly withPOST <keycloak-realm>/protocol/openid-connect/tokenagainstdeepcloud.swiss(prod) orint.deepcloud.swiss(test).X-Org-IdandX-Environmentheaders are now required on every/b2b/v1/*request.X-Org-Idis the UUID of the organization the service user is a member of;X-Environmentisbetaorproduction.X-Api-Keyheader is no longer accepted. Authenticate exclusively withAuthorization: Bearer <access_token>from the DeepCloud SSO token endpoint.
- API Keys and OAuth Clients management surfaces in the DeepCredentials portal.
Authentication APIOpenAPI spec (the/b2b/v1/oauth/tokenoperation is gone).
Scope strings renamed — B2B scopes are now namespaced under
deepcredentials.*:issuance→deepcredentials.issueverification→deepcredentials.verifyself_service→deepcredentials.self-service
Provision new credentials with the namespaced names. Enforcement will roll out together with the matching DeepAdmin update; until then, in-flight tokens carrying the legacy names continue to work.
identification_filledclaim source — Self-service configs can now mark a template claim as automatically pre-filled from the identification provider's verified attributes. Tenants configure the per-claim mapping in the portal (no B2B API surface). The webhook decision flow benefits passively: by the timeself_service.request.submittedfires, the snapshot'sclaimsfield already reflects the merged result (user → identification → admin), so an emptyadmin_claimsapprove issues a credential carrying the identification-derived values.editable_field_sourcesenum extended —GET /b2b/v1/self-service/requests/{id}may now return"identification_filled"alongside"user_filled"and"admin_filled". See the Self-Service Flow guide.
- Credential validity — Credentials are now issued with a tenant-controlled
expclaim. Set a default on the credential template (portal → Templates) and override per-issuance via the new optionalvalidity_secondsfield onPOST /b2b/v1/credential-offers. Send no value to inherit the template default; the credential is issued without anexpclaim (never expires) only when both the template default and request override are absent. Self-service configs always carry their own explicit validity (or explicit "never expires") chosen at config creation. Allowed range: 60 seconds to 10 years. Issued-credential responses (GET /b2b/v1/issued-credentials/{id}, list, and webhook payload-fetch) now includevalid_fromandvalid_untilso you can show expiry in your UI and react before a credential lapses. - Self-service request snapshot — full claim schema —
GET /b2b/v1/self-service/requests/{requestId}now returnseditable_fields/editable_field_types/editable_field_sourcesdescribing every claim on the template (not just admin-filled ones). Enables rendering a "review & override" form without a second template fetch. See the Self-Service Flow guide.
- Issued credential status semantics — The
statusfield (active/suspended/revoked) is the revocation lifecycle (driven by the status list). Expiration is now expressed separately viavalid_until. A credential can beactiveand past itsvalid_untilsimultaneously — wallets and verifiers reject it on theexpclaim; we don't automatically flip itsstatus. admin_claimsmay override user-supplied claims —POST /b2b/v1/self-service/requests/{requestId}/decisionalready accepted any template claim key in theadmin_claimspayload; the contract is now explicit: when a key appears in both the user submission and youradmin_claims, your value wins and is the one stamped on the credential.admin_filled_*continues to describe the required contract only.
- Self-Service API —
GET /b2b/v1/self-service/requests/{requestId}returns the full request snapshot (status, captured claims, identification result, offer id, timestamps, plus the admin-claims schema —admin_filled_fields/admin_filled_types/admin_filled_required, mirroring how verification shipsrequested_fields+credential_subject_typesso the tenant can dynamically render or validateadmin_claimswithout hard-coded template knowledge).POST /b2b/v1/self-service/requests/{requestId}/decisionapproves or rejects an end-user-initiated request — required for tenants whose configs run inwebhookverification mode. - Self-service webhook events — Six events covering the full lifecycle:
self_service.request.submitted— fires forwebhook-mode configs when a request is ready for a decision.self_service.request.approved/self_service.request.rejected— fire for all verification modes so you have an audit trail of every decision.self_service.request.credential_issued— terminal success; the wallet accepted the credential.self_service.request.offer_expired/self_service.request.failed— terminal post-approval failure paths (offer TTL exceeded, wallet rejected).
self_servicescope — Required for the Self-Service API and selectable on API keys / OAuth clients in the portal.- Self-Service Flow guide — End-to-end walkthrough of the three verification modes (manual, webhook, automatic), lifecycle diagram, full user ↔ Portal ↔ tenant-backend sequence, and webhook payload contract.
- Webhook subscription management — The portal webhook page now has a per-event checkbox grid grouped by family (Verification / Credential Issuance / Self-Service). Unchecked events are silently dropped at our edge — no delivery, no retry. New webhook configs default to all events on; existing configs keep their current set and have to explicitly opt in to events added in future releases.
- Scopes documentation — The scopes table previously listed
credentials.read/credentials.write, which never existed. Replaced with the actual scopes:verification,issuance,self_service.
- Verification API — Create and poll OID4VP verification sessions (
POSTandGET /b2b/v1/verification-sessions). - Authentication API — OAuth 2.0 Client Credentials token exchange (
POST /b2b/v1/oauth/token). - Health API — Credential validation and connectivity check (
GET /b2b/v1/health). - Webhook Events API — OpenAPI 3.1 webhook spec for
verification.completedwith full event schema and signature headers. - Webhooks guide — Setup, HMAC-SHA256 signature verification, and secret rotation.
- Authentication methods — API keys and OAuth 2.0.