> ## Documentation Index
> Fetch the complete documentation index at: https://doc.playsmart.api.dolly.gg/llms.txt
> Use this file to discover all available pages before exploring further.

# Track and send gameplay analytics events to PlaySmart

> Learn how to send gameplay analytics events to PlaySmart in batches, including all supported event types and the customdata payload format.

The event ingestion endpoint lets you send multiple gameplay events in a single HTTP call. Batching reduces network overhead and powers automated features like coin rewards — the server processes `LevelCompleted` events to award coins as soon as they are flushed from the queue. You can include up to 200 events per request.

<Steps>
  <Step title="Authenticate your request">
    `POST /ingest/events` requires a valid Bearer token. Use the `access_token` obtained from registration or login.

    ```bash theme={null}
    -H "Authorization: Bearer <access_token>"
    ```

    If the token is missing or expired, the server returns `401 UNAUTHORIZED`. If the token is valid but the `user_id` in the payload does not match the token's `sub` claim, the server returns `403 FORBIDDEN`.
  </Step>

  <Step title="Build your event batch">
    Every request body has three top-level fields:

    | Field     | Type   | Description                                                      |
    | --------- | ------ | ---------------------------------------------------------------- |
    | `user_id` | string | The player's ID. Must match the `sub` claim in the access token. |
    | `game_id` | string | Your game's bundle ID (e.g., `com.example.mygame`).              |
    | `events`  | array  | Between 1 and 200 event objects.                                 |

    Each event object in the `events` array has:

    | Field             | Type   | Required | Description                                |
    | ----------------- | ------ | -------- | ------------------------------------------ |
    | `type`            | string | Yes      | One of the six supported event type names. |
    | `timestamp`       | string | Yes      | ISO 8601 datetime with timezone offset.    |
    | `customdata`      | object | No       | Any JSON object with additional context.   |
    | `customEventName` | string | No       | Required when `type` is `Custom`.          |
  </Step>

  <Step title="Choose your event types">
    PlaySmart supports six event types:

    | Type             | When to send                                                                                               |
    | ---------------- | ---------------------------------------------------------------------------------------------------------- |
    | `AppOpen`        | The player opens the app and the game session begins.                                                      |
    | `LevelCompleted` | The player successfully completes a level. This event triggers coin awards when `coins_enabled` is `true`. |
    | `GameEnd`        | The current game session ends, whether through completion, failure, or exit.                               |
    | `AdImpression`   | An ad was shown to the player.                                                                             |
    | `IAPPurchase`    | The player completed an in-app purchase.                                                                   |
    | `Custom`         | Any other game-specific event. Set `customEventName` to describe it.                                       |

    When sending a `Custom` event, include `customEventName` in the event object alongside `type`. For example:

    ```json theme={null}
    {
      "type": "Custom",
      "customEventName": "PowerUpUsed",
      "timestamp": "2024-01-15T12:05:00+00:00",
      "customdata": { "power_up": "shield", "level": 5 }
    }
    ```
  </Step>

  <Step title="Send the batch">
    Post your assembled batch to `POST /ingest/events`:

    ```bash theme={null}
    curl -X POST https://playsmart-gateway-1w8ko864.uc.gateway.dev/ingest/events \
      -H "Authorization: Bearer <access_token>" \
      -H "Content-Type: application/json" \
      -d '{
        "user_id": "device-abc-123",
        "game_id": "com.example.stackblaster",
        "events": [
          {
            "type": "AppOpen",
            "timestamp": "2024-01-15T12:00:00+00:00",
            "customdata": {}
          },
          {
            "type": "LevelCompleted",
            "timestamp": "2024-01-15T12:04:30+00:00",
            "customdata": { "level": 5, "score": 1250 }
          },
          {
            "type": "AdImpression",
            "timestamp": "2024-01-15T12:04:35+00:00",
            "customdata": { "ad_unit_id": "ca-app-pub-0000/1234567890" }
          },
          {
            "type": "GameEnd",
            "timestamp": "2024-01-15T12:04:40+00:00",
            "customdata": { "reason": "level_complete" }
          }
        ]
      }'
    ```

    The server responds with `202 Accepted`:

    ```json theme={null}
    {
      "success": true,
      "message": "accepted:4 buffered:4"
    }
    ```
  </Step>
</Steps>

## Timestamp format

All `timestamp` values must be ISO 8601 strings with an explicit timezone offset. UTC is recommended:

```text theme={null}
2024-01-15T12:00:00+00:00
```

Timestamps without a timezone offset are rejected with a `400 BAD_REQUEST`.

## customdata

The `customdata` field accepts any flat or nested JSON object. Use it to attach context that is useful for analytics or debugging — for example, the level number, score, ad unit ID, or purchase SKU. There is no enforced schema, but keep payloads small to avoid unnecessary overhead.

<Note>
  A `202 Accepted` response means the events were received and queued in memory. They are flushed to the database within a few seconds. There is no need to retry a `202` response.
</Note>

<Warning>
  The `user_id` in the request body must exactly match the `sub` claim in the Bearer token. Sending events on behalf of another user returns `403 FORBIDDEN` with error code `user_id_mismatch`.
</Warning>
