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

# POST /session/verify

> Verify OTT and create a game session

<Badge color="green">POST</Badge> `/v1/game/session/verify`

Verifies an <Tooltip tip="One-Time Token — a single-use UUID token (1 min TTL) that transfers the session from the PlayWave launcher to the game server">OTT</Tooltip> issued by the PlayWave launcher. On success, starts billing and creates a game session.

## Request

### Headers

<ParamField header="X-Api-Key" type="string" required>
  Per-game API Key.
</ParamField>

<ParamField header="Content-Type" type="string" required>
  `application/json`
</ParamField>

### Body

<ParamField body="ott" type="string" required>
  <Tooltip tip="One-Time Token — extracted from Roblox GetJoinData().LaunchData">OTT</Tooltip> UUID extracted from LaunchData.
</ParamField>

<ParamField body="provider_user_id" type="string" required>
  Roblox Player UserId converted to string.
</ParamField>

```json theme={null}
{
  "ott": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "provider_user_id": "123456789"
}
```

## Response

### Success — <Badge color="green">200</Badge>

```json theme={null}
{
  "success": true,
  "request_id": "req_abc123",
  "data": {
    "is_valid": true,
    "game_session_id": "gs_550e8400-e29b-41d4-a716-446655440000",
    "is_pc_cafe": true
  }
}
```

<ResponseField name="is_valid" type="boolean" required>
  Whether verification succeeded.
</ResponseField>

<ResponseField name="game_session_id" type="string" required>
  Game session ID — use for heartbeat and session end calls.
</ResponseField>

<ResponseField name="is_pc_cafe" type="boolean" required>
  Whether the user is at a verified PC cafe.
</ResponseField>

### Verification failure — <Badge color="green">200</Badge>

Business logic failures also return HTTP 200. Always branch on the `is_valid` value.

```json theme={null}
{
  "success": true,
  "request_id": "req_abc123",
  "data": {
    "is_valid": false,
    "reason": "OTT_EXPIRED"
  }
}
```

| reason             | Description                                                                                                                   |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------- |
| `OTT_EXPIRED`      | OTT expired (over 1 min since issuance)                                                                                       |
| `OTT_ALREADY_USED` | OTT already consumed                                                                                                          |
| `GAME_MISMATCH`    | OTT game / API Key game mismatch                                                                                              |
| `NOT_CHARGEABLE`   | Billing not possible (insufficient <Tooltip tip="Virtual currency used for PC cafe play-time billing">G-coin</Tooltip>, etc.) |

### Errors

| HTTP                              | Code              | Description                             |
| --------------------------------- | ----------------- | --------------------------------------- |
| <Badge color="yellow">400</Badge> | `BAD_REQUEST`     | Missing required fields or format error |
| <Badge color="yellow">401</Badge> | `INVALID_API_KEY` | Invalid API Key                         |

## Luau example

```lua theme={null}
local function verifySession(player)
    local joinData = player:GetJoinData()
    local ott = joinData.LaunchData

    if not ott or ott == "" then
        return nil
    end

    local success, response = pcall(function()
        return HttpService:RequestAsync({
            Url = API_URL .. "/game/session/verify",
            Method = "POST",
            Headers = {
                ["X-Api-Key"] = API_KEY,
                ["Content-Type"] = "application/json",
            },
            Body = HttpService:JSONEncode({
                ott = ott,
                provider_user_id = tostring(player.UserId),
            }),
        })
    end)

    if not success or response.StatusCode ~= 200 then
        return nil
    end

    local body = HttpService:JSONDecode(response.Body)
    return body.data
end
```
