> For the complete documentation index, see [llms.txt](https://docs.domino.run/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.domino.run/docs/developer-resources/portal-api-integration-guide.md).

# Portal API integration guide

This guide is for teams that run quests on their own website or app and use Domino through the portal API instead of the hosted portal UI. It covers the recommended request flow, which calls belong on your server, and how to handle retries and concurrency.

### Two kinds of API keys

| Key                   | Where it comes from                                                                                                | Where it may be used        | Header                       |
| --------------------- | ------------------------------------------------------------------------------------------------------------------ | --------------------------- | ---------------------------- |
| **Community API key** | Created in your community dashboard                                                                                | **Your server only**        | `x-api-key`                  |
| **User API key**      | Returned by [External User Lookup](/docs/developer-resources/authenticating-users-with-the-portal-api.md) per user | Your server or a login link | `x-api-key` or `?authToken=` |

{% hint style="danger" %}
The community API key can create users and API keys for any member of your community. Never ship it to a browser or mobile app.
{% endhint %}

### Recommended flow

```
Your frontend          Your server                          Domino portal API
─────────────          ───────────                          ─────────────────
login (Discord/X/…) →  verify OAuth, get numeric IDs
                       POST /api/auth/external-lookup  →    find or create user,
                       (community key)                 ←    return user + user key
claim button        →  POST /api/quests/{id}/claim     →    validate tasks, grant rewards
                       (user key)                      ←    result message
                       POST /api/quests/check-claim    →    claimed: true/false
                       (community key)
                       GET  /api/v1/leaderboard        →    rankings
                       (community key)
```

#### 1. Authenticate the user yourself

Domino does not verify social accounts or wallets passed through the API. Your application is responsible for proving that the user owns them:

* **Discord, X, Telegram, Reddit:** run the platform's OAuth or login flow on your side and use the **numeric user ID** it returns. Usernames can change and are only supplementary data.
* **Wallets:** verify a signed message before passing the address.

Tasks such as "Join our Discord server" or "Hold a token" are validated against these IDs and wallets, so passing unverified identifiers lets users claim rewards for accounts they don't own.

#### 2. Look up the user (server-side)

Call [External User Lookup](/docs/developer-resources/authenticating-users-with-the-portal-api.md) with your community API key, your own user ID as `externalId`, and the verified social IDs and wallets. The response contains the Domino user and a **user API key**.

If the user signed up through a referral, pass the referrer's ID as `referrerExternalId` the first time you look the user up, so referral rewards are credited.

Reuse the user API key until it expires instead of calling external lookup on every request.

#### 3. Claim quests on behalf of the user

Either:

* **Hand the user over to the portal:** redirect them to `https://your-community.domino.page/quests?authToken=<user key>`. They get a normal portal session and claim in the portal UI. Use a short `keyExpiresInSeconds` for these links.
* **Claim from your server:** call [Claim a quest](/docs/claiming-a-quest.md) with the user API key in the `x-api-key` header.

{% hint style="warning" %}
When claiming from your server, **do not send cookies** with the request. If a request carries a portal session cookie, that session is used and the `x-api-key` header is ignored, so a shared cookie jar can claim quests as the wrong user.
{% endhint %}

#### 4. Check claims and read leaderboards (server-side)

Use [Checking claim status](/docs/checking-claim-status.md) and the [Portal Leaderboard API](/docs/developer-resources/portal-leaderboard-api.md) with your community API key. Proxy the results to your frontend rather than calling these endpoints from the browser.

### Retries, concurrency and limits

There are currently no published per-endpoint request limits, but limits may be introduced as usage grows. Build your integration so it keeps working if they are:

* **Back off on `429` and `5xx`.** Retry with exponential backoff and jitter, and respect the `Retry-After` header when present.
* **Don't retry `400` responses blindly.** A `400` from the claim endpoint usually means validation failed (for example a task isn't completed yet). Show the message to the user instead of retrying.
* **One claim at a time per user and quest.** Claims for the same user and quest are processed one at a time. A second request sent while the first is still running waits, and fails with `Quest claim is already in progress` if the first takes too long. Disable the claim button while a request is pending.
* **Cache read endpoints.** Leaderboards and claim status change only when claims are processed, so a short cache (for example 30–60 seconds) on your server avoids unnecessary load.
* **Reuse user API keys.** Store the key with its `expiresAt` and request a new one only when it has expired.

### Common errors

| Status | Message                                    | What to do                                                            |
| ------ | ------------------------------------------ | --------------------------------------------------------------------- |
| `401`  | `API Key is required` / `Invalid API key`  | Check that the right key type is sent for the endpoint                |
| `401`  | `Unauthorized`                             | The user API key is missing or expired. Call external lookup again    |
| `400`  | `Quest claim is already in progress`       | Wait for the pending claim to finish before retrying                  |
| `400`  | `Captcha token is required`                | The quest requires hCaptcha; claim through the portal or pass a token |
| `403`  | `You are not eligible to claim this quest` | The user doesn't meet the quest's conditions or is banned             |
| `404`  | `Quest not found`                          | Check the quest and deployment IDs from the quest's sharing panel     |

### Related resources

* [Authenticating users with the portal API](/docs/developer-resources/authenticating-users-with-the-portal-api.md)
* [Claiming a quest](/docs/claiming-a-quest.md)
* [Checking claim status](/docs/checking-claim-status.md)
* [Portal Leaderboard API](/docs/developer-resources/portal-leaderboard-api.md)
