> ## 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.

# API Based Integration Guide

> Direct API integration for complete control over your user verification experience

## Overview

API integration provides complete control over the user verification experience through custom UIs and direct API calls. Choose this approach when you need full customization of the user journey.

<Info>
  **When to use API Integration:**

  * Need complete UI customization
  * Building native mobile applications
  * Require specific flow control logic
  * Want to handle multiple providers differently
</Info>

## Prerequisites

* API credentials from the [Console](https://console.hopae.com)
* Understanding of [verification flows](/guides/concepts/verification-flow)

## Implementation Steps

### Step 1: Create Verification Session

Start a verification session with your chosen provider:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://sandbox.api.hopae.com/connect/v1/verifications \
    -u "CLIENT_ID:CLIENT_SECRET" \
    -H "Content-Type: application/json" \
    -d '{
      "providerId": "bankidse"
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch("https://sandbox.api.hopae.com/connect/v1/verifications", {
    method: "POST",
    headers: {
      Authorization: `Basic ${btoa(`${CLIENT_ID}:${CLIENT_SECRET}`)}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      providerId: "bankidse",
    }),
  });
  ```

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

  credentials = base64.b64encode(f"{CLIENT_ID}:{CLIENT_SECRET}".encode()).decode()

  response = requests.post(
      'https://sandbox.api.hopae.com/connect/v1/verifications',
      headers={
          'Authorization': f'Basic {credentials}',
          'Content-Type': 'application/json'
      },
      json={'providerId': 'bankidse'}
  )
  ```
</CodeGroup>

<Note>
  Redirect-flow providers (e.g., `mitid`, `id-austria`) require a `redirectUri` in the request body. QR and push providers do not.
</Note>

### Step 2: Handle Flow Response

The API response varies by verification flow type:

<Tabs>
  <Tab title="QR Code Flow">
    ```json Response theme={null}
    {
      "verificationId": "eec15acab80b472bb20160d5751b8b40",
      "status": "awaiting_user_action",
      "providerId": "bankidse",
      "flowType": "qr",
      "flowDetails": {
        "qrData": "bankidse:///?token=7c40b5c1-..."
      },
      "expiresAt": "2024-07-30T10:05:00.000Z"
    }
    ```

    **Implementation:**

    1. Generate QR code from `flowDetails.qrData`
    2. Display QR code to user
    3. Poll for verification status

    ```javascript Example theme={null}
    // Generate QR code
    const qrCode = await QRCode.toDataURL(response.flowDetails.qrData);

    // Display in UI
    document.getElementById('qr-code').src = qrCode;
    ```
  </Tab>

  <Tab title="Redirect Flow">
    ```json Response theme={null}
    {
      "verificationId": "fec15acab80b472bb20160d5751b8b41",
      "status": "awaiting_user_action",
      "providerId": "mitid",
      "flowType": "redirect",
      "flowDetails": {
        "authorizationUrl": "https://mitid.dk/auth?token=abc123..."
      },
      "expiresAt": "2024-07-30T10:05:00.000Z"
    }
    ```

    **Implementation:**

    1. Redirect the user to `flowDetails.authorizationUrl`
    2. After the provider completes, your backend polls the status
    3. When `status` is `completed`, fetch `GET /verifications/{id}/userinfo` to retrieve user and provenance

    ```javascript Example theme={null}
    // Redirect to provider
    window.location.href = response.flowDetails.authorizationUrl;
    ```
  </Tab>

  <Tab title="Push Flow">
    ```json Response theme={null}
    {
      "verificationId": "gec15acab80b472bb20160d5751b8b42",
      "status": "awaiting_user_action",
      "providerId": "smartid",
      "flowType": "push",
      "flowDetails": {
        "message": "Push notification sent to device",
        "userIdentifier": "+372XXXXXXXX"
      },
      "expiresAt": "2024-07-30T10:05:00.000Z"
    }
    ```

    **Implementation:**

    1. Show waiting UI with message
    2. Display masked user identifier
    3. Poll for verification status

    ```javascript Example theme={null}
    // Show waiting UI
    showPushNotificationUI({
      message: response.flowDetails.message,
      userIdentifier: response.flowDetails.userIdentifier
    });
    ```
  </Tab>
</Tabs>

### Step 3: Poll for Status

Poll `GET /verifications/{id}` until `status` becomes `completed`. Then call `GET /verifications/{id}/userinfo` to obtain the user attributes and provenance.

<CodeGroup>
  ```javascript Polling Implementation theme={null}
  const pollVerification = async (verificationId) => {
    const pollInterval = setInterval(async () => {
      try {
        const response = await fetch(`https://sandbox.api.hopae.com/connect/v1/verifications/${verificationId}`, {
          headers: {
            Authorization: `Basic ${btoa(`${CLIENT_ID}:${CLIENT_SECRET}`)}`,
          },
        });

        const data = await response.json();

        switch (data.status) {
          case "completed":
            clearInterval(pollInterval);
            // Fetch user + provenance
            const uiResp = await fetch(`https://sandbox.api.hopae.com/connect/v1/verifications/${verificationId}/userinfo`, {
              headers: { Authorization: `Basic ${btoa(`${CLIENT_ID}:${CLIENT_SECRET}`)}` }
            });
            const userinfo = await uiResp.json();
            handleSuccess(userinfo);
            break;
          case "failed":
          case "expired":
            clearInterval(pollInterval);
            handleError(data);
            break;
          // Continue polling for 'awaiting_user_action' status
        }
      } catch (error) {
        clearInterval(pollInterval);
        handleError(error);
      }
    }, 3000); // Poll every 3 seconds

    // Set timeout for maximum polling duration
    setTimeout(() => {
      clearInterval(pollInterval);
      handleTimeout();
    }, 300000); // 5 minutes timeout
  };
  ```

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

  def poll_verification(verification_id):
      max_attempts = 100  # 5 minutes with 3-second intervals
      attempts = 0

      while attempts < max_attempts:
          response = requests.get(
              f'https://sandbox.api.hopae.com/connect/v1/verifications/{verification_id}',
              headers={
                  'Authorization': f'Basic {credentials}'
              }
          )

          data = response.json()

          if data['status'] == 'completed':
              # Fetch user + provenance
              ui = requests.get(
                  f'https://sandbox.api.hopae.com/connect/v1/verifications/{verification_id}/userinfo',
                  headers={'Authorization': f'Basic {credentials}'}
              ).json()
              return handle_success(ui)
          elif data['status'] in ['failed', 'expired']:
              return handle_error(data)

          time.sleep(3)
          attempts += 1

      return handle_timeout()
  ```
</CodeGroup>

### Step 4: Fetch User and Provenance

When status is `completed`, fetch `GET /verifications/{id}/userinfo` to retrieve personal attributes and verification context. You’ll see the personal claims alongside provenance (channel, credentials, evidence, metadata).

```bash Request theme={null}
curl --request GET \
  --url 'https://sandbox.api.hopae.com/connect/v1/verifications/{verificationId}/userinfo' \
  -u 'CLIENT_ID:CLIENT_SECRET'
```

```json Response theme={null}
{
  "sub": "otV9EMJr-iG-dj-AHhrCslfdRkUUBQJ1",
  "acr": "urn:hopae:loa:3",
  "hopae_loa": 3,
  "hopae_loa_label": "substantial",
  "user": {
    "birthdate": "1905-04-04",
    "given_name": "OK",
    "family_name": "TESTNUMBER",
    "nationality": "LT",
    "name": "OK TESTNUMBER",
  },
  "missing_claims": ["email", "gender", "picture"],
  "provenance": {
    "presentation": {
      "channel": {"type": "centralized_idp", "transport": "internet"},
      "credentials": [
        {
          "type": "smartid",
          "issuer": {
            "id": "urn:hopae:issuer:ee:smartid",
            "authority_name": "SK ID Solutions AS",
            "is_government": false
          },
          "claims": {
            "documentNumber": "PNOLT-40504040001-MOCK-Q",
            "birthdate": "1905-04-04",
            "countryCode": "LT",
            "givenName": "OK",
            "surname": "TESTNUMBER"
          },
          "evidence": {
            "token": {
              "id_token": "<BASE64_ID_TOKEN>",
              "expires_at": "2025-10-31T05:43:14.000Z",
              "access_token": "<OPAQUE_ACCESS_TOKEN>",
              "token_type": "Bearer"
            },
            "names": "id_token;expires_at;access_token;token_type"
          }
        }
      ]
    },
    "_metadata": {
      "verification_id": "fc2523d3270f4b64a4c461fb9af7e086",
      "verified_at": "2025-10-28T06:09:48.484Z"
    }
  }
}
```

<Info>
  Parsing tips:

  * `provenance.presentation.credentials[].type` reuses the `providerId` you supplied (for example, `smartid`, `bankidse`).
  * `provenance.presentation.credentials[].evidence.token` contains provider-specific keys; inspect the semicolon-delimited `names` string to know which ones are present.
  * Call `GET /verifications/{verificationId}/evidence` if you only need this `token`/`names` object without the rest of the UserInfo payload.
</Info>

<Note>
  Evidence keys differ per provider. Use the semicolon-delimited `names` string to determine which fields are present under `evidence.token`.
</Note>

## Error Handling

<Tabs>
  <Tab title="Common Errors">
    | Error    | Description               | Action                          |
    | -------- | ------------------------- | ------------------------------- |
    | `300001` | Session not found         | Verify session ID is correct    |
    | `300003` | Invalid status transition | Session already completed       |
    | `400002` | Provider not enabled      | Enable provider in Console      |
    | `400005` | Provider unavailable      | Try again later or use fallback |
  </Tab>

  <Tab title="Error Response Example">
    ```json theme={null}
    {
      "type": "urn:org:hopae:problem-type:client/300001",
      "title": "SESSION_NOT_FOUND",
      "status": 404,
      "detail": "Verification session not found",
      "code": 300001,
      "timestamp": "2024-01-20T10:30:00.000Z"
    }
    ```
  </Tab>
</Tabs>

<Card title="Error Code Reference" icon="triangle-exclamation" href="/api-reference/error-codes">
  View complete error codes and handling strategies
</Card>

## Best Practices

<AccordionGroup>
  <Accordion title="Security Considerations">
    * Never expose Client Secret in frontend code
    * Implement proper session management
    * Use HTTPS for all API calls
    * Validate all responses server-side
  </Accordion>

  <Accordion title="User Experience">
    * Show clear loading states during polling
    * Provide timeout warnings before expiry
    * Offer alternative authentication methods
    * Display provider-specific instructions
  </Accordion>

  <Accordion title="Performance Optimization">
    * Implement exponential backoff for polling
    * Cache provider information
    * Minimize API calls with proper state management
    * Use webhooks for real-time updates (if available)
  </Accordion>
</AccordionGroup>

## Comparison with Hosted OIDC Flow

<Warning>
  **Consider the Hosted OIDC Flow First:** The [OIDC integration guide](/guides/oidc-integration) handles provider UX, PKCE, and token issuance automatically. Choose direct APIs only when you need full UI control, native-only UX, or bespoke flow logic.
</Warning>

## Next Steps

<CardGroup cols={2}>
  <Card title="API Reference" icon="code" href="/api-reference/introduction">
    Explore complete API documentation
  </Card>

  <Card title="OIDC Integration" icon="id-card" href="/guides/oidc-integration">
    Simpler integration alternative
  </Card>
</CardGroup>
