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

# How PlaySmart authentication works: JWT bearer tokens

> PlaySmart uses JWT bearer tokens for API authentication. Register or log in to receive tokens, then pass them in the Authorization header.

PlaySmart uses JSON Web Tokens (JWT) signed with HS256 to authenticate API requests. When you register or log in, the API returns two tokens. You send the access token with every protected request, and use the refresh token to obtain new credentials when the access token expires.

## Tokens

PlaySmart issues two tokens on every successful authentication:

| Token           | Header claim            | Expiry       | Purpose                                   |
| --------------- | ----------------------- | ------------ | ----------------------------------------- |
| `access_token`  | `Authorization: Bearer` | **30 days**  | Authenticate API requests                 |
| `refresh_token` | —                       | **180 days** | Re-authenticate after access token expiry |

The access token's `sub` claim contains the player's `auth_user_id`. Protected endpoints read this claim to identify the caller. The refresh token carries a `kind: "refresh"` claim and is used solely to issue a new access token — pass it to `/auth/login` or a dedicated refresh endpoint as appropriate for your integration.

<Warning>
  Tokens are not revocable server-side. If a token is compromised, it remains valid until it expires. Treat both tokens like passwords: store them in secure, device-local storage and never log or transmit them in plain text.
</Warning>

## Register a new player

Send a `POST` request to `/auth/register` to create a player account. The `deviceId` you provide becomes the player's permanent `auth_user_id` and is embedded as the JWT `sub` claim.

**Request fields**

<ParamField body="email" type="string" required>
  A valid email address. Must be unique across all players.
</ParamField>

<ParamField body="password" type="string" required>
  The player's password. Minimum 8 characters.
</ParamField>

<ParamField body="deviceId" type="string" required>
  A stable, unique identifier for the player's device. Stored as `auth_user_id`.
</ParamField>

**Example request**

```bash theme={null}
curl -X POST https://api.playsmart.io/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "email": "player@example.com",
    "password": "hunter12345",
    "deviceId": "device-abc-123"
  }'
```

**Example response** — `201 Created`

```json theme={null}
{
  "data": {
    "access_token": "eyJhbGciOiJIUzI1NiJ9...",
    "refresh_token": "eyJhbGciOiJIUzI1NiJ9...",
    "user": {
      "id": "device-abc-123",
      "email": "player@example.com",
      "pseudo": "",
      "total_games_completed": 0
    }
  }
}
```

## Log in an existing player

Send a `POST` request to `/auth/login` with the player's credentials. The response structure is identical to registration.

**Request fields**

<ParamField body="email" type="string" required>
  The player's registered email address.
</ParamField>

<ParamField body="password" type="string" required>
  The player's password.
</ParamField>

**Example request**

```bash theme={null}
curl -X POST https://api.playsmart.io/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "player@example.com",
    "password": "hunter12345"
  }'
```

**Example response** — `200 OK`

```json theme={null}
{
  "data": {
    "access_token": "eyJhbGciOiJIUzI1NiJ9...",
    "refresh_token": "eyJhbGciOiJIUzI1NiJ9...",
    "user": {
      "id": "device-abc-123",
      "email": "player@example.com",
      "pseudo": "",
      "total_games_completed": 0
    }
  }
}
```

## Using the access token

Pass the `access_token` in the `Authorization` header on every request to a protected endpoint:

```bash theme={null}
curl https://api.playsmart.io/ingest/events \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..."
```

The API also accepts the token in an `X-Forwarded-Authorization` header, which takes precedence over `Authorization` when both are present.

## Error responses

| HTTP status        | `code`         | `message`                  | Cause                                                                                            |
| ------------------ | -------------- | -------------------------- | ------------------------------------------------------------------------------------------------ |
| `400 Bad Request`  | `BAD_REQUEST`  | `invalid_payload`          | Request body is missing, malformed, or fails validation (e.g. password too short, invalid email) |
| `401 Unauthorized` | `UNAUTHORIZED` | `invalid_credentials`      | Email not found, or password does not match                                                      |
| `401 Unauthorized` | `UNAUTHORIZED` | `missing_bearer_token`     | Protected endpoint called without an `Authorization` header                                      |
| `409 Conflict`     | `CONFLICT`     | `email_already_registered` | A player with this email address already exists                                                  |
