> 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/standards/agentic/ap2/flow-1/build-your-own-ui.md).

# Method 2: Build Your Own UI

Own the User-facing screens and drive the mandate request from your back end

[Method 1](/standards/agentic/ap2/flow-1/embed-ui-and-api.md) hands the User's approval to Embed UI. This page is for teams that want those screens themselves - because the experience has to match an existing product, or because you need control Embed UI doesn't expose.

The platform's role doesn't change: it still builds the signed mandate request and still verifies the response. What changes is that everything between those two calls is yours.

**Before you start** you need a Verifier and a single-input VP Schema - see [Agentic Payments (AP2)](/standards/agentic/ap2.md) - and your agent's public key - see [Flow 1](/standards/agentic/ap2/flow-1.md).

### What you own

| Step                          | Whose code             | Notes                                                                      |
| ----------------------------- | ---------------------- | -------------------------------------------------------------------------- |
| Create the request            | Your back end          | Protected call - carries your Tenant token, so it can't run in the browser |
| Get it to the User            | You                    | QR code, web wallet hand-off, or the DC API call                           |
| Collect the response          | Depends on the channel | Nothing to fetch on the DC API; one fetch otherwise                        |
| Bridge a cross-device session | You                    | The phone lands on your `redirectUri` - see step 3                         |
| Verify                        | Your back end          | Protected call                                                             |

The screens are yours too: something that tells the User what your agent is asking for and why, the QR code or wallet hand-off, the code-entry field on the cross-device path, and somewhere to land when the User declines or the request fails.

## 1. Create the mandate request

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

## \[Experimental] Create AP2 mandate request

> Create an OID4VP authorization request carrying one or more AP2 mandates, without needing to construct a DCQL query or handle dSD-JWT delegation directly.

```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":{"CreateMandateRequestRequest":{"type":"object","properties":{"mode":{"type":"string","description":"Mechanism used by the wallet to deliver the authorization response. Only encrypted modes are supported.","enum":["direct_post.jwt","dc_api.jwt"]},"vpSchemaId":{"type":"string","description":"The VP Schema to build the DCQL query against. Tenants create their own."},"mandates":{"description":"One or more AP2 mandates. All mandates must be Open, or all must be Closed — mixing is not supported.","type":"array","items":{"$ref":"#/components/schemas/Ap2BaseMandate"}},"agentKey":{"description":"Public JWK confirmation key for the delegate, embedded as cnf on Open mandates. Required when any mandate is Open; forbidden when every mandate is Closed.","allOf":[{"$ref":"#/components/schemas/PublicJWK"}]},"redirectUri":{"type":"string","description":"Same-device redirect target after the delegating wallet responds."},"verifierWebUrl":{"type":"string","description":"Web hand-off / QR surface the delegating human lands on."},"expectedOrigins":{"description":"Web origins (http or https) expected for a Digital Credentials API (DC API) response mode.","type":"array","items":{"type":"string"}}},"required":["mode","vpSchemaId","mandates"]},"Ap2BaseMandate":{"type":"object","properties":{"vct":{"enum":["mandate.checkout.open.1","mandate.checkout.1","mandate.payment.open.1","mandate.payment.1"],"type":"string","description":"Mandate type discriminator (the verifiable credential type)."},"exp":{"type":"number","minimum":0,"description":"Unix epoch. Defaults to iat + the configured mandate TTL when omitted."}},"required":["vct"]},"PublicJWK":{"type":"object","properties":{"kty":{"type":"string","description":"JWK key type, naming the key family the other members belong to."},"crv":{"type":"string","description":"Cryptographic curve the key uses. Present for EC and OKP key types."},"x":{"type":"string","description":"Base64url-encoded x coordinate. Present for EC and OKP key types; for OKP it carries the whole public key."},"y":{"type":"string","description":"Base64url-encoded y coordinate. Present for EC key types only."},"n":{"type":"string","description":"Base64url-encoded modulus. Present for RSA key types only."},"e":{"type":"string","description":"Base64url-encoded public exponent. Present for RSA key types only."},"use":{"type":"string","enum":["sig","enc"],"description":"JWK public key use."},"key_ops":{"description":"JWK key operations.","type":"array","items":{"type":"string"}},"alg":{"type":"string","description":"Cryptographic algorithm intended for use with this key."},"kid":{"type":"string","description":"JWK key ID, identifying the key within the JWK itself."},"x5u":{"type":"string","format":"uri","description":"URL of the X.509 certificate chain for this key."},"x5c":{"description":"X.509 certificate chain for this key.","type":"array","items":{"type":"string"}},"x5t":{"type":"string","description":"X.509 certificate SHA-1 thumbprint."},"x5t#S256":{"type":"string","description":"X.509 certificate SHA-256 thumbprint."}},"required":["kty"]},"CreateMandateRequestResponse":{"type":"object","properties":{"requestUri":{"type":"string","description":"The OID4VP request URI to render as a QR code or embed in a deep link for the delegating human."},"txId":{"type":"string","description":"Correlation id for the eventual verification call."}},"required":["requestUri","txId"]}}},"paths":{"/api/v1/{verifierId}/oid4vp/requests/ap2":{"post":{"description":"Create an OID4VP authorization request carrying one or more AP2 mandates, without needing to construct a DCQL query or handle dSD-JWT delegation directly.","operationId":"Oid4VpController_createMandateRequest_api/v1","parameters":[{"name":"verifierId","required":true,"in":"path","description":"The verifier to create the mandate request against.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMandateRequestRequest"}}}},"responses":{"200":{"description":"When the AP2 mandate request is created successfully. The uri for the authorization request and corresponding txId is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMandateRequestResponse"}}}},"400":{"description":"When mandate content fails validation against its vct schema, mixes open and closed mandates, is missing a required agentKey, an Open Checkout Mandate is missing a checkout.line_items constraint, a closed Checkout/Payment mandate pair does not link correctly."},"404":{"description":"When no VP Schema exists with such id."}},"summary":"[Experimental] Create AP2 mandate request","tags":["Oid4Vp"]}}}}
```

The body is the same as in [Method 1](/standards/agentic/ap2/flow-1/embed-ui-and-api.md): Open mandates plus `agentKey` for Human not present, Closed mandates and no `agentKey` for Human present. What you choose here is `mode`:

| Mode              | Use it for                                                                                                              |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `direct_post.jwt` | A QR code for the EQUS Mobile Wallet, or a same-device hand-off to the Web Wallet. Set `redirectUri` to a page of yours |
| `dc_api.jwt`      | The browser-native exchange over the Digital Credentials API. Set `expectedOrigins` to your page's origin               |

**One request carries one mode.** It returns `{ requestUri, txId }` - keep the `txId`.

## 2. Get it to the User

* **Across devices** - render the `requestUri` as a QR code for the User to scan with the [Mobile Wallet](/standards/wallets/equs-wallet.md).
* **Same device** - open the `requestUri` in the Web Wallet, in a frame or a new tab.
* **DC API** - pass the request to the browser and await the result in your page.

## 3. Collect the response

On the **DC API** the response arrives in your page, from the browser - pass it to your back end and go straight to step 4.

On **`direct_post.jwt`** the wallet posts the response to the platform, and redirects to your `redirectUri` with a `responseCode`. What you do next depends on where that redirect landed.

**Same device** is the easy case: the redirect lands in the browser that started the journey, so your page already has the `responseCode`. Fetch the response with it and the `txId`.

**Across devices** the redirect happens on the phone, so your main page never sees it. The phone lands on your `redirectUri`, and that page reads the `responseCode`, exchanges it for a one-time `txCode`, and shows the code. The User types it into your page on the device they started on, and you fetch with the `txCode` and the `txId`.

## Get one-time txCode for Authorization Response

> Exchange responseCode on OTP txCode

```json
{"openapi":"3.0.0","info":{"title":"EqusDTK Integration API Documentation","version":"0.1.2"},"tags":[{"name":"Oid4Vp","description":""}],"paths":{"/api/v1/oid4vp/tx-codes":{"get":{"description":"Exchange responseCode on OTP txCode","operationId":"Oid4VpController_getTxCode_api/v1","parameters":[{"name":"responseCode","required":true,"in":"query","description":"Code returned by the wallet to fetch the pending tx-code.","schema":{"type":"string"}}],"responses":{"200":{"description":"When txCode was retrieved successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetTxCodeResponse"}}}}},"summary":"Get one-time txCode for Authorization Response","tags":["Oid4Vp"]}}},"components":{"schemas":{"GetTxCodeResponse":{"type":"object","properties":{"txCode":{"type":"string","description":"Transaction code the holder must present to redeem the offer. Exchanged for txId for real Cross-device flow."},"txCodeExpireAt":{"format":"date-time","type":"string","description":"Date and time the transaction code expires at."},"verifierWebUrl":{"type":"object","description":"URL of the verifier web page that displayed the presentation request."}},"required":["txCode","txCodeExpireAt","verifierWebUrl"]}}}}
```

{% hint style="warning" %}
Treat the `responseCode` as a one-time secret: it is all that stands between the redirect and the code. Don't log it, and don't leave it retrievable after the exchange.
{% endhint %}

## Get Authorization Response

> Retrieve Authorization Response by response code and txId

```json
{"openapi":"3.0.0","info":{"title":"EqusDTK Integration API Documentation","version":"0.1.2"},"tags":[{"name":"Oid4Vp","description":""}],"paths":{"/api/v1/oid4vp/responses":{"get":{"description":"Retrieve Authorization Response by response code and txId","operationId":"Oid4VpController_getAuthorizationResponse_api/v1","parameters":[{"name":"responseCode","required":false,"in":"query","description":"Code returned by the wallet to fetch the authorization response.","schema":{"type":"string"}},{"name":"txCode","required":false,"in":"query","description":"Transaction code used to authorize retrieval of the response.","schema":{"type":"string"}},{"name":"txId","required":true,"in":"query","description":"Transaction unique ID","schema":{"type":"string"}}],"responses":{"200":{"description":"When Authorization Response is resolved correctly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetAuthorizationResponseResponse"}}}}},"summary":"Get Authorization Response","tags":["Oid4Vp"]}}},"components":{"schemas":{"GetAuthorizationResponseResponse":{"type":"object","properties":{"vpToken":{"type":"object","description":"Plain verifiable presentation token. Only set when the response is not encrypted."},"jwe":{"type":"string","description":"Encrypted JWE containing the authorization response. Only set when the response is encrypted."},"type":{"type":"number","description":"Indicates whether the authorization response is plain or encrypted."},"transactionDataResponse":{"type":"object","description":"Hashes of the transaction data items the wallet signed over."}},"required":["type"]}}}}
```

**Fetch only after the User has answered.** The call fails while the request is still pending, and there is no status call to poll - the redirect, or the code the User types, is your signal.

The bridge works as it does for an ordinary presentation - see [Build Your Own UI](/standards/presentation/flows-1-and-2/build-your-own-ui.md) under Presentation for the full picture.

## 4. Verify

Send the response and the `txId` from your back end:

## 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"]}}}}
```

The platform verifies the presentation and the delegation chain in one go: the Issuer's signature on the User's credential, and the mandates the User signed over it. `transactionDataResponse` reports what the User actually signed.

## 5. Keep the grant

For Open mandates, what you've verified is the **grant**, bound to your agent's key. Store it with the agent - the platform keeps nothing - and close it with the agent's own key when a merchant offers a checkout; see [Flow 1](/standards/agentic/ap2/flow-1.md).

For Closed mandates the verified artifact **is** the Closed mandate, ready to show a merchant or payment provider.


---

# 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/standards/agentic/ap2/flow-1/build-your-own-ui.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.
