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

# POST /ingest/events — Submit a batch of gameplay events

> Send a batch of up to 200 gameplay events to PlaySmart. Requires authentication. Events are buffered and processed asynchronously within seconds.

Send gameplay events in batches rather than one at a time to reduce network overhead and battery usage on the player's device. Group events that occurred during a session and flush them together — for example, at the end of a level or when the app moves to the background. PlaySmart accepts up to 200 events per request.

<Note>
  A `202 Accepted` response means the events were queued in the server-side buffer, not yet written to the database. Processing happens within a few seconds.
</Note>

## Endpoint

```text theme={null}
POST /ingest/events
```

**Authentication required.** Include the player's access token in the request header:

```text theme={null}
Authorization: Bearer <access_token>
```

## Request body

<ParamField body="user_id" type="string" required>
  The authenticated player's unique identifier. Must exactly match the `sub` claim in the access token. The request is rejected with `403` if there is a mismatch.
</ParamField>

<ParamField body="game_id" type="string" required>
  The game's identifier — the Android package name or iOS bundle identifier (e.g. `com.example.mygame`).
</ParamField>

<ParamField body="events" type="object[]" required>
  An array of event objects. Must contain between 1 and 200 items.

  <Expandable title="Event object fields">
    <ParamField body="type" type="string" required>
      The event type. One of: `AppOpen`, `LevelCompleted`, `GameEnd`, `AdImpression`, `IAPPurchase`, `Custom`.
    </ParamField>

    <ParamField body="customEventName" type="string">
      A custom event label. Required when `type` is `"Custom"`. Ignored for all other event types.
    </ParamField>

    <ParamField body="timestamp" type="string" required>
      The client-side time when the event occurred. Must be an ISO 8601 datetime string with a timezone offset (e.g. `"2026-04-22T14:30:00+00:00"`).
    </ParamField>

    <ParamField body="customdata" type="object">
      Any additional key-value pairs relevant to the event. Accepts arbitrary JSON values. Use this to attach context such as level number, score, or ad unit name.
    </ParamField>
  </Expandable>
</ParamField>

## Example request

```bash theme={null}
curl -X POST https://playsmart-gateway-1w8ko864.uc.gateway.dev/ingest/events \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "device-abc-123",
    "game_id": "com.example.stackblaster",
    "events": [
      {
        "type": "LevelCompleted",
        "timestamp": "2026-04-22T14:30:00+00:00",
        "customdata": {
          "level": 5,
          "score": 4800,
          "duration_seconds": 42
        }
      },
      {
        "type": "AdImpression",
        "timestamp": "2026-04-22T14:30:45+00:00",
        "customdata": {
          "ad_unit": "post_level_interstitial"
        }
      },
      {
        "type": "Custom",
        "customEventName": "PowerUpUsed",
        "timestamp": "2026-04-22T14:29:15+00:00",
        "customdata": {
          "power_up": "shield"
        }
      }
    ]
  }'
```

## Response

### 202 Accepted

```json theme={null}
{
  "success": true,
  "message": "accepted:3 buffered:3"
}
```

The `message` field reports how many events were accepted and how many are currently in the server-side buffer awaiting processing.

## Error responses

| HTTP status        | Code           | Message            | Meaning                                                                                                                                                                                    |
| ------------------ | -------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `401 UNAUTHORIZED` | `UNAUTHORIZED` | —                  | The `Authorization` header is missing or the token is invalid or expired.                                                                                                                  |
| `403 FORBIDDEN`    | `FORBIDDEN`    | `user_id_mismatch` | The `user_id` field in the request body does not match the `sub` claim of the authenticated token.                                                                                         |
| `400 BAD_REQUEST`  | `BAD_REQUEST`  | `invalid_payload`  | The request body failed schema validation — for example, an unsupported event `type`, a missing `timestamp`, or more than 200 events in the array. Check `details` for field-level errors. |

```json theme={null}
{
  "error": {
    "code": "FORBIDDEN",
    "message": "user_id_mismatch"
  }
}
```
