> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hopae.com/llms.txt
> Use this file to discover all available pages before exploring further.

# REST API Integration

> Drive a verification from your backend with the Verification REST API and build your own UI.

With the REST API, your backend starts the verification, your UI handles the user step, and your backend reads the result.

**Before you start**

* An app's **App ID** and **App Secret**. See [Authentication](/v2/api-reference/authentication).
* At least one connection that is activated and enabled in a workflow. See [Activate Connections](/v2/guides/concepts/connections/activation).

Every call uses HTTP Basic auth with the App ID and App Secret against `https://api.hopae.com/connect/v2`.

```mermaid theme={null}
sequenceDiagram
    participant UI as Your UI
    participant B as Your backend
    participant H as Hopae
    B->>H: GET /connections
    B->>H: POST /verifications
    H-->>B: flowType + flowDetails
    B-->>UI: Show QR / redirect / waiting screen
    loop Until terminal
        B->>H: GET /verifications/{id}
    end
    B->>H: GET /verifications/{id}/userinfo
```

## 1. Find a connection

```bash theme={null}
curl "https://api.hopae.com/connect/v2/connections?status=enabled" \
  -u "APP_ID:APP_SECRET"
```

`status=enabled` returns exactly the connections you can start for the default workflow. For each one, note `connectionId`, `flowTypes`, and `userInputSchema` (the fields to send). See [Get Connections](/v2/api-reference/verifications/get-connections).

## 2. Create a verification

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.hopae.com/connect/v2/verifications" \
    -u "APP_ID:APP_SECRET" \
    -H "Content-Type: application/json" \
    -d '{
      "connectionId": "smart-id",
      "userInput": { "registrationId": "PNOLT-40504040001" }
    }'
  ```

  ```javascript Node.js theme={null}
  const res = await fetch("https://api.hopae.com/connect/v2/verifications", {
    method: "POST",
    headers: {
      Authorization: `Basic ${Buffer.from(`${APP_ID}:${APP_SECRET}`).toString("base64")}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      connectionId: "smart-id",
      userInput: { registrationId: "PNOLT-40504040001" },
    }),
  });
  const verification = await res.json();
  ```

  ```python Python theme={null}
  import requests

  verification = requests.post(
      "https://api.hopae.com/connect/v2/verifications",
      auth=(APP_ID, APP_SECRET),
      json={
          "connectionId": "smart-id",
          "userInput": {"registrationId": "PNOLT-40504040001"},
      },
  ).json()
  ```
</CodeGroup>

* Send one of `connectionId`, `connectionInstanceId`, or `providerId`. Add `credentialId` to narrow a `providerId` that has several connections. An ambiguous one returns `400`.
* Optional: `workflowId` (otherwise the default workflow), `redirectUri` (for redirect flows).
* The workflow decides which claims are returned. You do not send claims.

See [Create Verification](/v2/api-reference/verifications/create-verification) and [User Input](/v2/api-reference/verifications/user-input).

## 3. Handle the flow

Act on `flowType`:

```javascript theme={null}
switch (verification.flowType) {
  case "redirect": redirectTo(verification.flowDetails.authorizationUrl); break;
  case "qr":       showQr(verification.flowDetails.qrData); break;        // on a phone: open linkData
  case "push":     showCode(verification.flowDetails?.verificationCode); break;
  case "dc":       openOnPhone(verification.flowDetails.linkData); break;
  case "query":    /* already completed or failed: skip to step 5 */ break;
}
```

Full responses and diagrams for each type: [Flow Types](/v2/guides/reference/flow-types).

## 4. Poll until finished

Skip this for `query`: its create response is already final.

Poll `GET /verifications/{id}` until the status is `completed`, `failed`, or `cancelled`. Wait `polling.intervalMs` between calls (each non-terminal response has it). Stop at `expiresAt`: a verification not finished by then has expired and is deleted, so later calls return `404`. See [Expiry](/v2/guides/verifications/verification-flow#expiry).

<Warning>
  For `qr` and `push`, your polls drive the verification: Hopae checks the provider only when you poll. If you stop polling, the status stops updating and no terminal [webhook](/v2/guides/webhook-signing) is sent. Redirect and `dc` verifications finish without polling.
</Warning>

<CodeGroup>
  ```javascript Node.js theme={null}
  // `created` is the create response
  async function waitForResult(created) {
    const deadline = Date.parse(created.expiresAt);
    while (Date.now() < deadline) {
      const v = await get(`/verifications/${created.verificationId}`);
      if (["completed", "failed", "cancelled"].includes(v.status)) return v;
      await sleep(v.polling?.intervalMs ?? 3000);
    }
    return { status: "expired" }; // past expiresAt: the verification is gone
  }
  ```

  ```python Python theme={null}
  import time
  from datetime import datetime, timezone

  # `created` is the create response
  def wait_for_result(created):
      deadline = datetime.fromisoformat(created["expiresAt"].replace("Z", "+00:00"))
      while datetime.now(timezone.utc) < deadline:
          v = get(f"/verifications/{created['verificationId']}")
          if v["status"] in ("completed", "failed", "cancelled"):
              return v
          time.sleep((v.get("polling") or {}).get("intervalMs", 3000) / 1000)
      return {"status": "expired"}  # past expiresAt: the verification is gone
  ```
</CodeGroup>

Statuses are explained in [Verification Flow](/v2/guides/verifications/verification-flow#lifecycle).

## 5. Read the result

When the status is `completed`:

```bash theme={null}
curl "https://api.hopae.com/connect/v2/verifications/{verificationId}/userinfo" \
  -u "APP_ID:APP_SECRET"
```

```json Response (abridged) theme={null}
{
  "user": {
    "name": "OK TESTNUMBER",
    "birthdate": "1905-04-04",
    "nationality": "LT",
    "source_id": "PNOLT-40504040001"
  },
  "hopae_loa": 4,
  "hopae_loa_label": "high",
  "verification_model": "disclosure",
  "sub": "019bc4f2-8a31-7c5e-9d02-4f7a1b3e60d8"
}
```

* Read identity data from `user`. To recognise a returning person, use `user.source_id`, not `sub`.
* Add `provenance=true` and `missing_claims=true` to the query for the audit trail and the claims the source could not provide.
* Read it before `expiresAt`. After that the result is gone. See [Expiry](/v2/guides/verifications/verification-flow#expiry).

The full payload, including `match` results, is in [Return Data](/v2/guides/verifications/return-data-model).

## Cancel

`DELETE /verifications/{id}` cancels a verification that has not finished (`204`). A completed, failed, or cancelled one answers `409 SESSION_INVALID_STATUS_TRANSITION`. See [Cancel Verification](/v2/api-reference/verifications/cancel-verification).

## Errors

Errors share one envelope. Switch on `error.code` and quote `request_id` to support.

```json theme={null}
{
  "error": { "code": "PROVIDER_DISABLED_IN_WORKFLOW", "message": "The requested eID is not enabled in the selected workflow" },
  "request_id": "c9a35b71-8e4f-4d20-b6a1-5f82e07d3c94"
}
```

Every code and what to do: [Error Codes](/v2/api-reference/error-codes).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.