> For the complete documentation index, see [llms.txt](https://docs.toolkit.equs.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.toolkit.equs.ai/docs/presentation/flows-1-and-2/embed-ui-and-api.md).

# Method 1: Embed UI + API

Run Presentation with a pre-built Holder flow

Embed UI takes over the Holder's part of a presentation - the overview of what's being asked, the wallet hand-off, the QR code, and the whole cross-device bridge. Your back end still creates the request and still verifies the response.

This is the recommended path, and it covers both Flow 1 and Flow 2: Embed UI implements each and picks between them at runtime, according to the Holder's device and the wallet they choose. The DC API is [off by default](/docs/presentation/flows-1-and-2/embed-ui-and-api.md#which-channels-the-holder-can-use).

**Before you start** you need [a Verifier and a VP Schema](/docs/presentation/configuration.md), Embed UI installed and initialized - see [Embedded UI](/docs/embedded-ui/overview.md).

### What the Holder goes through

Something on your page calls `presentVCs` - a button, usually - and Embed UI opens.

{% hint style="warning" %}
**Call `presentVCs` synchronously from the Holder's click.** It opens a window, and browsers only allow that inside a user gesture - `await` anything first and some will block it. Any work you need to do beforehand belongs inside `getReqUriAndTxId`, which Embed UI calls for you once it's open.
{% endhint %}

1. **Pick a wallet.** They choose from what you enabled - a web wallet, a mobile wallet, or the browser-native exchange. Options that don't suit their device aren't shown, and with only one enabled there's nothing to choose.\
   ![](https://701364325-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFaucIkpW1i4WOAQC06fj%2Fuploads%2FWeE5KtbA9QgxvlO5RqVQ%2Fimage.png?alt=media\&token=d8a464e2-ec0f-4906-a433-67b0c3831e8e)
2. **Hand off to the wallet.** A web wallet opens in an embedded frame or a new tab. For a mobile wallet, Embed UI shows a QR code. On the DC API path, the browser and operating system open the wallet natively.\
   ![](https://701364325-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFaucIkpW1i4WOAQC06fj%2Fuploads%2FL2pT0UmPfbszljRi0lLJ%2Fimage.png?alt=media\&token=32649af7-92ba-47b2-bac0-4cd5748d8fe3)
3. **Approve in the wallet**, choosing what to disclose.
4. **Come back.** Cross-device, the phone shows a short code to type into your page - Embed UI renders both that bridge page and the entry field. Same-device and DC API need nothing.

### Who calls what

The two calls that carry your credentials - creating the request and verifying the response - go through your back end:

{% code collapsedlinecount="1000" %}

```mermaid
flowchart LR
    E["Embed UI<br/>in your page"] --> B["Your back end"] --> P["Platform<br/>Integration API"]
    style E fill:#e6f4ea,stroke:#34a853
    style B fill:#e8f0fe,stroke:#4285f4
    style P fill:#fff4e5,stroke:#f9ab00
```

{% endcode %}

### What you build

#### Creating the request

`presentVCs` takes a single options object, and one of its properties is `getReqUriAndTxId` - an async function you write. Embed UI calls it when the Holder starts, and expects a request per channel back.

{% code collapsedlinecount="1000" %}

```typescript
{
  web:    { requestUri, txId } | null,
  mobile: { requestUri, txId } | null,
  dc:     { requestUri, txId } | null,
}
```

{% endcode %}

Inside it, call your own back end, which builds all the channels in one go with `POST /api/v1/{verifierId}/oid4vp/requests/multi`. It returns one request per channel, which is the shape `getReqUriAndTxId` has to hand back. Creating a channel commits you to nothing - an unused request simply goes unanswered.

{% hint style="info" icon="rectangle-api" %}
All calls mentioned on this page are on the [Integration API](https://docs.toolkit.equs.ai/docs/presentation/flows-1-and-2/pages/MvAvsnLQU3ER2hOIIDU2#id-2.4.1-apis).
{% endhint %}

## Create multiple Authorization Requests (web and mobile)

> Create multiple Authorization Requests by Verifier based on VP Schema pointing to default Embed UI

```json
{"openapi":"3.0.0","info":{"title":"EqusDTK Integration API Documentation","version":"0.1.2"},"tags":[{"name":"Oid4Vp","description":""}],"security":[{"sp":[]},{"tenant":[]},{"sp":[]}],"components":{"securitySchemes":{"sp":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Service Provider. Token of the Service Provider client in the `agents` realm, with no `tenantId` claim. Authorize one bearer scheme at a time."},"tenant":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Tenant. Token of a Tenant machine client or of a Tenant portal user, carrying the `tenantId` claim. Authorize one bearer scheme at a time."}},"schemas":{"CreateMultiRequestRequest":{"type":"object","properties":{"vpSchemaId":{"type":"string","description":"VP schema unique ID"},"encrypted":{"type":"boolean","description":"Whether the generated presentation requests should be encrypted"},"modes":{"type":"array","description":"Presentation channels to generate an authorization request for.","items":{"type":"string","enum":["web","mobile","dcApi"]}},"verifierWebUrl":{"type":"string","description":"URL of the verifier web page that will display the presentation request."},"intendedUseId":{"type":"string","description":"Intended use to create this request under. Required once the Verifier has registered one; its registration certificate is emitted to the Wallet as a `verifier_info` entry."}},"required":["vpSchemaId","modes","verifierWebUrl"]},"CreateMultiRequestResponse":{"type":"object","properties":{"web":{"description":"Authorization request generated for the web presentation channel.","allOf":[{"$ref":"#/components/schemas/CreateAuthorizationRequestResponse"}]},"mobile":{"description":"Authorization request generated for the mobile presentation channel.","allOf":[{"$ref":"#/components/schemas/CreateAuthorizationRequestResponse"}]},"dc":{"description":"Authorization request generated for the Digital Credentials API channel.","allOf":[{"$ref":"#/components/schemas/CreateAuthorizationRequestResponse"}]}},"required":["web"]},"CreateAuthorizationRequestResponse":{"type":"object","properties":{"requestUri":{"type":"string","description":"URI from which the wallet can fetch the full authorization request object."},"txId":{"type":"string","description":"Transaction unique ID"}},"required":["requestUri","txId"]}}},"paths":{"/api/v1/{verifierId}/oid4vp/requests/multi":{"post":{"description":"Create multiple Authorization Requests by Verifier based on VP Schema pointing to default Embed UI","operationId":"Oid4VpController_createMultiRequest_api/v1","parameters":[{"name":"verifierId","required":true,"in":"path","description":"Verifier unique ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMultiRequestRequest"}}}},"responses":{"200":{"description":"When multiple Authorization Requests are created successfully, the web, mobile and dc request uris and corresponding txIds are returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMultiRequestResponse"}}}},"400":{"description":"When the Verifier uses x509_san_dns and the response URI FQDN does not match its Client Identifier."},"404":{"description":"- When no VP Schema exists with such id.\n- When no verifier found."},"422":{"description":"- When the Verifier uses an x509 Client Identifier Prefix and no access certificate is active.\n- When the active access certificate has been revoked, is expired or not yet valid, or carries no dNSName SAN naming the protocol host.\n- When the SDK refuses the x509 Client Identifier, because the certificate chain, the identifier and the signing key do not agree."}},"summary":"Create multiple Authorization Requests (web and mobile)","tags":["Oid4Vp"]}}}}
```

#### Verifying the response

Embed UI hands the response to your `onSuccess` callback:

{% code collapsedlinecount="1000" %}

```typescript
{
  authResponse: { type, vpToken?, jwe? } | null,
  txId: string,
}
```

{% endcode %}

Send that to your own back end and verify it there with `POST /api/v1/{verifierId}/oid4vp/verify`, passing the `authResponse` and the `txId`.

The platform checks the Issuer's signature, the credential's revocation status, and that what came back actually answers what you asked. You get `verifiedClaims` - the disclosed values - and optionally `presentations`, the raw verifiable presentations keyed by credential, if you need to keep evidence rather than just the values.

## Verify Presentation

> Verify submitted Authorization Response and return verification result

```json
{"openapi":"3.0.0","info":{"title":"EqusDTK Integration API Documentation","version":"0.1.2"},"tags":[{"name":"Oid4Vp","description":""}],"security":[{"sp":[]},{"tenant":[]},{"sp":[]}],"components":{"securitySchemes":{"sp":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Service Provider. Token of the Service Provider client in the `agents` realm, with no `tenantId` claim. Authorize one bearer scheme at a time."},"tenant":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Tenant. Token of a Tenant machine client or of a Tenant portal user, carrying the `tenantId` claim. Authorize one bearer scheme at a time."}},"schemas":{"VerifyPresentationRequest":{"type":"object","properties":{"authResponse":{"description":"Authorization response returned by the wallet containing the presentation.","allOf":[{"$ref":"#/components/schemas/AuthResponse"}]},"txId":{"type":"string","description":"Transaction unique ID"},"origin":{"type":"string","description":"Web origin (http or https) the presentation response was submitted from."}},"required":["authResponse","txId"]},"AuthResponse":{"type":"object","properties":{"vpToken":{"type":"object","description":"Plain verifiable presentation token. Mutually exclusive with jwe."},"jwe":{"type":"string","description":"Encrypted JWE containing the authorization response. Mutually exclusive with vpToken."},"transactionDataResponse":{"type":"object","description":"Hashes of the transaction data items the wallet signed over."},"type":{"type":"number","description":"Indicates whether the authorization response is plain or encrypted."}},"required":["type"]},"VerifyPresentationResponse":{"type":"object","properties":{"verifiedClaims":{"type":"object","description":"Claims disclosed and verified from the holder-presented credential."},"presentations":{"type":"object","description":"Raw verifiable presentations submitted by the holder, keyed by credential."}},"required":["verifiedClaims"]}}},"paths":{"/api/v1/{verifierId}/oid4vp/verify":{"post":{"description":"Verify submitted Authorization Response and return verification result","operationId":"Oid4VpController_verifyPresentation_api/v1","parameters":[{"name":"verifierId","required":true,"in":"path","description":"Verifier unique ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyPresentationRequest"}}}},"responses":{"200":{"description":"When Presentation is verified successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyPresentationResponse"}}}},"400":{"description":"- When the session advertised a response encryption key but the Authorization Response was sent in plain, encrypted under a foreign key, or carried a malformed JWE.\n- When the origin supplied for a DC API verification is not one the Authorization Request declared in its expected origins."},"409":{"description":"When the verification session was verified, failed, deleted or expired by another request while this presentation was being verified."},"422":{"description":"When the access certificate the verification session was created under can no longer be resolved, or has since been revoked."},"500":{"description":"When a DC API verification session was stored without the expected origins its Authorization Request declared."}},"summary":"Verify Presentation","tags":["Oid4Vp"]}}}}
```

### Start Presentation

{% code collapsedlinecount="1000" %}

```javascript
import { presentVCs } from '@equs-ai/dtk-embedded-ui'

presentVCs({
  getReqUriAndTxId,            // async () => ({ web, mobile, dc })
  walletPlatform,              // ['web'] | ['mobile'] | both - default ['mobile']
  dcApiEnabled,                // default false
  dcApiSignedRequestEnabled,   // default true
  hidePresentationOverview,    // default false
  vpPurposeText,               // default ''
  onLoading,
  onSuccess,
  onError,
  onReject,
})
```

{% endcode %}

#### Which channels the Holder can use

`walletPlatform` and `dcApiEnabled` decide what's offered, and they have to agree with the `modes` you built in `getReqUriAndTxId` - a channel needs both a request and permission to be used.

**`walletPlatform` defaults to `['mobile']`**, so out of the box the Holder gets the QR code. Add `'web'` to offer the web wallet as well.

**`dcApiEnabled` defaults to `false`**. Set it to `true` to split the mobile choice in the wallet picker into **iOS** and **Android**: Android Holders get the browser-native path wherever their browser and phone support it - the cleanest Holder experience on offer - and the QR code where they don't; iOS Holders get the QR code. Turning it on costs nothing; see [DC API prerequisites](/docs/presentation/flows-1-and-2/embed-ui-and-api.md#dc-api-prerequisites) for what it needs.

`dcApiSignedRequestEnabled`, on by default, signs the request sent on the DC API path.

#### What the Holder is told

`hidePresentationOverview: true` skips the overview step that shows the Holder what's being asked - worth it for routine, familiar, low-stakes requests.

`vpPurposeText` tells the Holder why you're asking. It appears on the overview step, or on every step when the overview is hidden.

#### Callbacks

| Callback              | Called when                                                                                                                                                                   |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `onLoading(loading)`  | Embed UI starts and finishes opening. Use it to show a spinner on the button that triggered it                                                                                |
| `onSuccess(response)` | The Holder shared. Verify the response as described above                                                                                                                     |
| `onReject()`          | The Holder declined                                                                                                                                                           |
| `onError(error)`      | `getReqUriAndTxId` threw, the Holder doesn't have a requested credential, presenting or declining failed, or the response's `txId` isn't one that `getReqUriAndTxId` returned |

When a requested credential is missing, `error` lists what was asked for and what was found, input by input. Trimmed:

{% code collapsedlinecount="1000" %}

```json
{
  "error": "no_credentials_found",
  "message": "Not all credentials found",
  "details": {
    "verifier": { "name": "Demo Verifier" },
    "inputs": [
      {
        "inputId": "…",
        "credTypes": ["VC Schema: Email v.1"],
        "credentials": [],
        "fields": [{ "name": "Email", "path": "email", "values": null }],
        "error": { "type": "TypesNotMatched", "credTypes": ["VC Schema: Email v.1"], "claims": ["Email"] }
      }
    ]
  }
}
```

{% endcode %}

#### Cancelling a presentation in flight

If the Holder navigates away mid-flow, `cleanUpPresentVCs` cancels the presentation - it removes Embed UI's message listeners and closes its iframe. `cleanUpEmbeddedUi` calls it for you, so you only need this when you're tearing down a presentation without tearing down Embed UI itself.

{% code collapsedlinecount="1000" %}

```javascript
import { cleanUpPresentVCs } from '@equs-ai/dtk-embedded-ui'

cleanUpPresentVCs()
```

{% endcode %}

### DC API prerequisites

The DC API path works with **Android** phones - in the browser on the phone itself, or from a desktop browser with the phone nearby:

* **Chrome 141 or newer**, which has the Digital Credentials API on by default.
* **Android with Google Play services 24.0.** Not every Android phone supports the DC API, since it also depends on the manufacturer's OS.
* **From a desktop:** Bluetooth enabled on the phone, and permitted for the browser at the OS level on the desktop.

{% hint style="info" %}
**iPhones use the QR code.** With the DC API on, the Holder picks iOS or Android in the wallet picker, and iOS always takes the QR-code path. There's nothing extra to set up for iPhone users.
{% endhint %}

### Tracking the result

Every request leaves a record - what was asked, whether the Holder answered, and how verification went. See [Verification Result](/docs/presentation/verification-result.md).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.toolkit.equs.ai/docs/presentation/flows-1-and-2/embed-ui-and-api.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
