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

# Questing

> Keep players engaged with in-game and pre-launch quest systems

Quests keep players engaged throughout your game's lifecycle. Players complete objectives, earn rewards, and climb leaderboards—whether your game is pre-launch or live.

## Overview

The Audience Builder Program includes in-game quests hosted on your game's Audience Builder page in [Immutable Play](https://play.immutable.com). Players sign up using [Immutable Passport](/docs/products/passport/overview) (simple social sign-on) to:

* Access the Immutable Play platform, game quests, and leaderboard competitions
* Track questing and leaderboard progress
* Receive rewards
* Link social accounts to their Immutable Play account

<Info>
  **No Passport integration required in-game.** Your game doesn't need to implement Immutable Passport as its login/wallet system or integrate any Immutable chain infrastructure. Simply track player progress using your existing backend and notify Immutable when a quest is completed via a lightweight API call.
</Info>

## Responsibilities

| Task                             | Owner              | Description                                                                |
| -------------------------------- | ------------------ | -------------------------------------------------------------------------- |
| **Campaign & quest design**      | Studio + Immutable | Collaborate to define quest structure, reward types, and timing            |
| **Player onboarding & linking**  | Immutable          | Players link an identifier matched to your backend during onboarding       |
| **Game login & player tracking** | Studio             | Use your existing login and backend systems                                |
| **Quest completion submission**  | Studio             | Track quest progress in-game and send completions to Immutable's Quest API |
| **Reward matching & delivery**   | Immutable          | Match identifier to Passport and deliver rewards                           |

## How to Integrate

<Steps>
  <Step title="Define Quests">
    Work with Immutable to agree on quests (e.g., "Play 30 arena matches") and their rewards
  </Step>

  <Step title="Get API Access">
    Create an API key on [Immutable Hub](/docs/products/hub/api-keys) and share your Org ID
  </Step>

  <Step title="Track Quest Logic">
    Use your existing backend to monitor when players meet quest criteria
  </Step>

  <Step title="Submit Quest Completion">
    When a quest is completed, send a POST request to the Immutable Quest API with the player's linked identifier
  </Step>

  <Step title="Rewards Delivered">
    Immutable handles player matching and instantly updates their Play inventory
  </Step>
</Steps>

## Supported Identifiers

Players can link one of these accounts to their Passport. Use this identifier when submitting quest completions.

| Account Type | Account ID              | Example                                      |
| ------------ | ----------------------- | -------------------------------------------- |
| `telegram`   | Telegram user ID        | `1191097385`                                 |
| `discord`    | Discord user ID         | `1374611107196440770`                        |
| `email`      | Email address           | `user@example.com`                           |
| `metamask`   | MetaMask wallet address | `0xc257274276a4e539741ca11b590b9447b26a8051` |
| `passport`   | Passport wallet address | `0xa9361c0dd68e1c3a69b43f646133fdab8d3859a2` |
| `epic_games` | Epic Games user ID      | `a26eca91eca340a7a8cadb886e7c1190`           |

<Warning>
  **Notes on account types:**

  * `email` is not supported in Sandbox. Use another account type for testing.
  * `epic_games` account linking is not yet supported in Immutable Play. However, quest completions with `epic_games` will be stored and credited once linking is available—no progress will be lost.
</Warning>

***

## Quest Types

<CardGroup cols={2}>
  <Card title="In-Game Quests" icon="gamepad">
    Track gameplay actions: matches played, levels completed, items collected
  </Card>

  <Card title="Social Quests" icon="share-nodes">
    Follow on Twitter, join Discord, share content, engage with posts
  </Card>

  <Card title="Referral Quests" icon="user-plus">
    Invite friends, reach referral milestones, build your network
  </Card>

  <Card title="On-Chain Quests" icon="link">
    Verify blockchain actions: first trade, first mint, collection completion
  </Card>
</CardGroup>

## Leaderboards

Gamify your community with competitive rankings:

| Leaderboard  | Criteria            | Rewards                           |
| ------------ | ------------------- | --------------------------------- |
| **All-Time** | Total points earned | Founder badges, exclusive items   |
| **Weekly**   | Points this week    | Featured spotlight, bonus rewards |
| **Referral** | Friends referred    | VIP access, revenue share         |

Players can spend gems to showcase their username on leaderboards across Immutable Play.

***

## Questing API

The Questing API provides endpoints for recording quest completions, retrieving active quests, and fetching player earnings. Use these endpoints to integrate quest functionality into your game backend.

### Prerequisites

* **Immutable API Key**: [Create one in Immutable Hub](/docs/products/hub/api-keys)
* **Game ID**: Provided during onboarding

### Required Quest: Login and Play Game

As part of the Quest API integration, partners are required to implement the **Login and Play Game** quest, identified by the `login-play-game` `external_id`.

This quest is configured as a **daily recurring quest** and should be triggered every time a player logs in. While triggered on login, it tracks participation on a daily basis, providing valuable insights into player engagement and recurring behavior patterns.

```json theme={null}
{
  "external_id": "login-play-game",
  "account_type": "discord",
  "account_id": "901290221440733204"
}
```

### Important Notes

<Info>
  **Quest completions are stored for non-Passport users.** If the identified user is not yet a registered Immutable Passport user, the quest completion will be stored with a reference to the account type and ID supplied in the request. When the user registers with Immutable Passport and links that account, they will be instantly rewarded and credited for this quest (and any other stored completions). This means user accounts do not need to be current Passport users when making requests.
</Info>

<Warning>
  **Always use the `reference` field for idempotency.** We highly recommend using a unique `reference` field (e.g., `{accountId}-{externalId}-{timestamp}`) to ensure the same user cannot complete the same quest twice. If you receive a `409 Conflict` response, the quest completion was already recorded—don't retry.
</Warning>

### API Endpoints

The Questing API provides three main endpoints:

* **Quest Completions** — Record quest completions and trigger rewards (requires authentication)
* **Get Live Quests** — Retrieve all currently active quests for your game (no authentication required)
* **Latest Earnings** — Fetch player gem earning history by wallet address (no authentication required)

Complete API documentation for all endpoints, including request/response schemas, authentication details, and code examples, is available in the API Reference tab.

***

## Best Practices

Follow these best practices to ensure reliable quest completion tracking and optimal integration with the Quest API.

### Idempotency

Always use the `reference` field to prevent duplicate quest completions.

**Recommended Pattern**

Use a unique reference string that combines player identifier, quest ID, and timestamp:

```typescript theme={null}
const reference = `${accountId}-${externalId}-${Date.now()}`;
```

**Why It Matters**

* Prevents accidental duplicate submissions
* Enables safe retries on network failures
* Provides audit trail for debugging

<Warning>
  If you receive a `409 Conflict` response, the quest completion was already recorded. Don't retry the request—treat it as success.
</Warning>

### Error Handling

Implement robust error handling with appropriate retry logic.

**Retry Strategy**

```typescript theme={null}
async function submitQuestCompletionWithRetry(
  gameId: string,
  apiKey: string,
  externalId: string,
  accountType: string,
  accountId: string,
  reference: string,
  maxRetries = 3
) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(
        `https://api.immutable.com/quests/v2/games/${gameId}/quest-completions`,
        {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'x-immutable-api-key': apiKey,
          },
          body: JSON.stringify({
            external_id: externalId,
            account_type: accountType,
            account_id: accountId,
            reference,
            completion_time: new Date().toISOString(),
          }),
        }
      );

      // Success - return immediately
      if (response.ok) {
        return await response.json();
      }

      // 409 Conflict - already recorded, treat as success
      if (response.status === 409) {
        console.log(`Quest completion already recorded: ${reference}`);
        return { result: { status: 'already_completed' } };
      }

      // 4xx errors - don't retry (client error)
      if (response.status >= 400 && response.status < 500) {
        const error = await response.json();
        throw new Error(`Client error: ${error.message || response.statusText}`);
      }

      // 5xx errors - retry with exponential backoff
      if (response.status >= 500) {
        if (attempt < maxRetries) {
          const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
          await new Promise(resolve => setTimeout(resolve, delay));
          continue;
        }
        throw new Error(`Server error after ${maxRetries} attempts`);
      }
    } catch (error) {
      if (attempt === maxRetries) {
        throw error;
      }
      // Network errors - retry with exponential backoff
      const delay = Math.pow(2, attempt) * 1000;
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}
```

**Error Response Handling**

| Status Code                | Action                                           |
| -------------------------- | ------------------------------------------------ |
| `200`                      | Success - quest completion recorded              |
| `409`                      | Already recorded - treat as success, don't retry |
| `400`, `401`, `403`, `404` | Client error - don't retry, log and investigate  |
| `500`, `504`               | Server error - retry with exponential backoff    |

### Logging

Always log quest completion requests for debugging and auditing.

**What to Log**

```typescript theme={null}
function logQuestCompletion(
  externalId: string,
  accountType: string,
  accountId: string,
  reference: string,
  status: 'success' | 'error',
  error?: string
) {
  const logEntry = {
    timestamp: new Date().toISOString(),
    quest: externalId,
    account: { type: accountType, id: accountId },
    reference,
    status,
    error,
  };

  // Log to your preferred logging service
  console.log(JSON.stringify(logEntry));
  
  // Optionally send to monitoring service
  // sendToMonitoring(logEntry);
}
```

**Logging Best Practices**

* Log before making the API request
* Log the response status and any errors
* Include the reference ID for correlation
* Don't log sensitive data (mask account IDs if needed)
* Use structured logging for easier querying

### Rate Limiting

The Quest API implements rate limiting to ensure fair usage.

**Rate Limit Tiers**

| Tier           | Rate Limit |
| -------------- | ---------- |
| **Standard**   | 50 req/sec |
| **Enterprise** | Custom     |

**Default rate limits:**

* Global default: 5 requests per second per public IP
* Partner adjusted rate limit: 50 requests per second per API key
* Enterprise: Custom rate limits based on your agreement

**Recommendations**

* **Batch quest completions** when possible (if multiple quests complete simultaneously)
* **Implement client-side rate limiting** to avoid hitting limits
* **Use exponential backoff** when receiving rate limit errors (typically `429` status)
* **Monitor your API usage** in [Immutable Hub](https://hub.immutable.com) — see [Hub Overview](/docs/products/hub/overview) for details

**Handling Rate Limits**

```typescript theme={null}
if (response.status === 429) {
  const retryAfter = response.headers.get('Retry-After');
  const delay = retryAfter ? parseInt(retryAfter) * 1000 : 60000; // Default 60s
  await new Promise(resolve => setTimeout(resolve, delay));
  // Retry the request
}
```

### Account ID Validation

Validate account IDs before submitting to ensure correct format.

**Validation Examples**

```typescript theme={null}
function validateAccountId(accountType: string, accountId: string): boolean {
  switch (accountType) {
    case 'discord':
      // Discord IDs are numeric strings (snowflakes)
      return /^\d+$/.test(accountId);
    
    case 'telegram':
      // Telegram IDs are numeric
      return /^\d+$/.test(accountId);
    
    case 'metamask':
    case 'passport':
      // Wallet addresses are hex strings starting with 0x
      return /^0x[a-fA-F0-9]{40}$/.test(accountId);
    
    case 'email':
      // Basic email validation
      return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(accountId);
    
    case 'epic_games':
      // Epic Games IDs are alphanumeric strings
      return /^[a-zA-Z0-9]+$/.test(accountId);
    
    default:
      return false;
  }
}
```

### Async Processing

Consider processing quest completions asynchronously to avoid blocking game logic.

**Queue-Based Approach**

```typescript theme={null}
// Add to queue instead of blocking
questCompletionQueue.add({
  externalId: 'catch-3-fish',
  accountType: 'discord',
  accountId: playerDiscordId,
  reference: generateReference(),
});

// Process queue in background
async function processQuestQueue() {
  while (true) {
    const item = await questCompletionQueue.get();
    try {
      await submitQuestCompletion(item);
    } catch (error) {
      // Log error and potentially re-queue
      logError(error);
    }
  }
}
```

### Testing

Test your integration thoroughly before going live.

**Test Scenarios**

1. **Successful completion** - Verify quest is recorded correctly
2. **Duplicate submission** - Ensure idempotency works with same reference
3. **Invalid account ID** - Verify proper error handling
4. **Network failures** - Test retry logic
5. **Rate limiting** - Verify backoff behavior
6. **Required quest** - Ensure `login-play-game` is triggered on login

**Sandbox Testing**

* Use Sandbox environment for all testing
* Test with `discord`, `telegram`, or `metamask` account types (not `email`)
* Verify quest completions appear in Immutable Play (Sandbox)

### Monitoring

Set up monitoring and alerts for quest completion failures.

**Key Metrics to Track**

* Quest completion success rate
* API error rates by status code
* Average response time
* Rate limit hits
* Duplicate completion attempts

**Alert Thresholds**

* Error rate > 5%
* Response time > 2 seconds
* Rate limit errors detected

***

## FAQ

<AccordionGroup>
  <Accordion title="Do I need to integrate Passport in my game?">
    No. The Quest API is designed to work with your existing authentication system. Players link their accounts (Discord, Telegram, email, etc.) to [Immutable Passport](/docs/products/passport/overview) through Immutable Play, but your game doesn't need to implement Passport authentication.
  </Accordion>

  <Accordion title="What happens if a player hasn't linked their account yet?">
    Quest completions are stored retroactively. When a player registers with [Immutable Passport](/docs/products/passport/overview) and links their account (Discord, Telegram, etc.), they'll automatically receive credit for all previously completed quests. Quest completions are stored with the account type and ID you provide—when the player eventually links that account to Passport, they'll receive all stored rewards.
  </Accordion>

  <Accordion title="Can I submit quest completions for players who aren't Passport users?">
    Yes. Quest completions are stored with the account type and ID you provide. When the player eventually links that account to [Immutable Passport](/docs/products/passport/overview), they'll receive all stored rewards.
  </Accordion>

  <Accordion title="What is the login-play-game quest?">
    The `login-play-game` quest is a required daily recurring quest that tracks player engagement. You must trigger this quest every time a player logs into your game. It provides valuable data on daily active users and recurring behavior patterns.
  </Accordion>

  <Accordion title="How do I prevent duplicate quest completions?">
    Always include a unique `reference` field in your quest completion requests. Use a pattern like `{accountId}-{externalId}-{timestamp}`. If you receive a `409 Conflict` response, the quest was already recorded—don't retry.
  </Accordion>

  <Accordion title="What account types can I use?">
    Supported account types are: `telegram`, `discord`, `email`, `metamask`, `passport`, and `epic_games`. Note that `email` is not supported in Sandbox—use `discord`, `telegram`, or `metamask` for testing.

    **Important:** `metamask` and other third-party wallets need to be linked by the user either through the Play profile settings or a custom UX built by the studio using the [link wallet API/SDK functionality](/docs/products/passport/wallet#linked-addresses).
  </Accordion>

  <Accordion title="How do I get my Game ID?">
    Your Game ID is provided by Immutable's Growth team during onboarding. Contact them if you don't have it yet.
  </Accordion>

  <Accordion title="What should I do if I get a 409 Conflict error?">
    A `409 Conflict` means the quest completion was already recorded (likely due to a duplicate `reference`). Treat this as success—don't retry the request.
  </Accordion>

  <Accordion title="How should I handle API errors?">
    * **4xx errors (400, 401, 403)**: Don't retry—these indicate client errors that need investigation
    * **404 Not Found**: Don't retry—see common causes below
    * **409 Conflict**: Treat as success—quest already recorded
    * **429 Rate Limit**: Wait for the `Retry-After` header duration, then retry
    * **5xx errors (500, 504)**: Retry with exponential backoff
  </Accordion>

  <Accordion title="What causes 404 Not Found errors?">
    A `404 Not Found` error can occur when:

    * The quest doesn't exist (check the `external_id` is correct)
    * The quest has expired (check `valid_from` and `valid_to` dates)
    * The request was made to a production endpoint with a sandbox quest ID (or vice versa)

    Verify you're using the correct environment (Sandbox vs Production) and that the quest is active. If the issue persists, contact your Growth team.
  </Accordion>

  <Accordion title="Can I submit quest completions in batches?">
    The Quest API accepts one quest completion per request. If multiple quests complete simultaneously, submit them sequentially. Consider using a queue system to handle high-volume scenarios.
  </Accordion>

  <Accordion title="How do I test my integration?">
    Use the Sandbox environment (`https://api.sandbox.immutable.com`) for testing. Test with `discord`, `telegram`, or `metamask` account types. Verify quest completions appear in Immutable Play (Sandbox).

    Additionally, studios can:

    * Visit [play.sandbox.immutable.com](https://play.sandbox.immutable.com) to see quest and reward status
    * Use the Live Quests endpoint (`GET /v1/quests/games/{game_id}/quests`) to see the number of quest completions
  </Accordion>

  <Accordion title="What is the completion_time field for?">
    The `completion_time` field allows you to specify when the quest was actually completed (in RFC3339 UTC format). This is useful for retroactive attribution or batch processing. If omitted, the API uses the current time.
  </Accordion>

  <Accordion title="How quickly are rewards delivered?">
    Rewards are typically delivered instantly when a quest completion is successfully recorded. If a player hasn't linked their account yet, rewards are stored and delivered when they link their account to Passport.
  </Accordion>

  <Accordion title="Where can I see quest completion data?">
    Quest completion data is visible in Immutable Play. Players can see their quest progress and rewards in their Play inventory. For analytics, you can view quest completion data via the [Immutable Hub Insights feature](https://hub.immutable.com). The Insights feature is enabled with an Immutable Audience Builder subscription. You can also contact Immutable's Growth team for additional analytics.
  </Accordion>

  <Accordion title="What if I need to change quest definitions?">
    Quest definitions are managed in collaboration with Immutable's Growth team. Contact them to discuss changes to quest structure, rewards, or timing.
  </Accordion>

  <Accordion title="Is there rate limiting on the Quest API?">
    Yes, the Quest API implements rate limiting to ensure fair usage:

    * **Standard tier**: 50 requests per second per API key
    * **Global default**: 5 requests per second per public IP
    * **Enterprise**: Custom rate limits based on your agreement

    If you receive a `429 Too Many Requests` response, implement exponential backoff and retry after the duration specified in the `Retry-After` header.

    **Common causes of 429 errors:**

    * The API key is not being used correctly in the request flow
    * Sub-optimal integration where too many requests are made than needed
    * Increase in base traffic (which is a great sign for the project)

    If you experience persistent 429 errors, contact your Growth team to review your integration and discuss potential optimizations or rate limit adjustments.
  </Accordion>

  <Accordion title="Can I use the same API key for other Immutable APIs?">
    Yes, if your API key has the necessary permissions. However, ensure your key has access to the Quest API endpoints for your game.
  </Accordion>

  <Accordion title="How do I get help or report issues?">
    Contact Immutable support through [support.immutable.com](https://support.immutable.com) or reach out to your Growth team contact for Quest API-specific questions.
  </Accordion>
</AccordionGroup>

***

<CardGroup cols={2}>
  <Card title="Get API Access" icon="key" href="https://hub.immutable.com">
    Create your API key in Immutable Hub
  </Card>

  <Card title="Contact Us" icon="envelope" href="https://www.immutable.com/contact">
    Get help setting up your quest campaign
  </Card>
</CardGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Audience Overview" icon="chart-line-up" href="/docs/products/audience/overview">
    Learn about the full Audience suite
  </Card>

  <Card title="Game Page" icon="browser" href="/docs/products/audience/game-page">
    Set up your pre-launch landing page
  </Card>

  <Card title="Engagement" icon="heart-pulse" href="/docs/products/audience/engagement">
    Configure quest completion campaigns
  </Card>

  <Card title="Attribution" icon="route" href="/docs/products/audience/attribution">
    Track quest performance data
  </Card>
</CardGroup>
