> ## Documentation Index
> Fetch the complete documentation index at: https://285e39fd5e337e58f16290.sightscreen.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Device registration & push tokens

> How iOS devices register for push notifications — now with multi-device support.

```mermaid theme={null}
sequenceDiagram
    participant User as User (iOS)
    participant App as iOS App
    participant OS as iOS / APNs
    participant API as Backend API
    participant DB as DynamoDB (devices)

    Note over User,DB: App launch — device registration

    User->>App: App launch
    App->>OS: Request notification permissions
    OS-->>User: Permission prompt (first launch only)
    User-->>OS: Allow
    OS->>OS: Register with APNs
    OS-->>App: Device pushToken
    App->>API: POST /devices {pushToken, deviceId, osVersion, appVersion}
    API->>DB: Upsert device (PK: userId, SK: deviceId)
    API->>DB: Set lastSeenAt, update ttl (90 days)
    API-->>App: 200 OK

    Note over User,DB: Push-to-start token registration

    App->>App: Observe ActivityKit pushToStartToken
    OS-->>App: pushToStartToken
    App->>API: POST /devices/push-to-start-token {token, deviceId}
    API->>DB: Update device record with pushToStartToken
    API-->>App: 200 OK

    Note over User,DB: Per-activity update token (during live match)

    App->>App: Live Activity created (follow or push-to-start)
    OS-->>App: pushTokenUpdates → updateToken
    App->>API: POST /matches/:matchId/activity-token {token, deviceId}
    API->>API: Update live-activities record (not devices table)
    API-->>App: 200 OK

    Note over User,DB: Sign-out cleanup

    User->>App: Signs out
    App->>API: DELETE /devices/:deviceId
    API->>DB: Remove device record
    API-->>App: 200 OK
    App->>App: Clear local state
```

## Three token types

The push system uses three distinct token types. Understanding which is which will save you debugging time at 2am.

| Token                | Stored in               | Registered via                          | Purpose                                                                                                         |
| -------------------- | ----------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| **pushToken**        | `devices` table         | `POST /devices`                         | Standard push notifications. Used for prematch alerts, marketing, etc. One per device.                          |
| **pushToStartToken** | `devices` table         | `POST /devices/push-to-start-token`     | Allows backend to remotely create a Live Activity on the device. One per device. Registered once on app launch. |
| **updateToken**      | `live-activities` table | `POST /matches/:matchId/activity-token` | Update or end a specific Live Activity instance. One per LA. Tied to the LA lifecycle.                          |

<Warning>
  The `updateToken` is NOT stored in the `devices` table. It lives in `live-activities` because it's scoped to a specific match + user + device combination. If you're debugging why a LA isn't updating, check `live-activities`, not `devices`.
</Warning>

## Multi-device support

A user can be signed in on multiple iOS devices simultaneously. Each device registers independently.

The `devices` table uses `deviceId` (`identifierForVendor`) as the sort key -- not the push token. This means:

* Each device gets its own record under the same userId
* Token rotation (OS updates, reinstalls) updates the existing record in-place
* No single-device enforcement -- all devices receive pushes

<Note>
  `identifierForVendor` resets on app reinstall. When a user reinstalls, the old device record becomes orphaned. The 90-day TTL on `lastSeenAt` handles eventual cleanup, but there's a window where both old and new records exist.
</Note>

## Devices table schema

| Attribute          | Type   | Description                                      |
| ------------------ | ------ | ------------------------------------------------ |
| `userId` (PK)      | String | Cognito user ID                                  |
| `deviceId` (SK)    | String | `identifierForVendor` from iOS                   |
| `pushToken`        | String | APNs device push token                           |
| `pushToStartToken` | String | ActivityKit push-to-start token                  |
| `osVersion`        | String | e.g., `18.2`                                     |
| `appVersion`       | String | e.g., `2.1.0`                                    |
| `lastSeenAt`       | Number | Epoch timestamp, updated on each registration    |
| `ttl`              | Number | 90 days from `lastSeenAt` (DynamoDB auto-delete) |

## Registration flow on app launch

Every app launch triggers a registration call. This keeps tokens fresh and metadata current.

<Steps>
  <Step title="Request permissions">
    On first launch, iOS shows the notification permission prompt. On subsequent launches, the app checks existing permission status.
  </Step>

  <Step title="Get device pushToken">
    If permissions are granted, iOS registers with APNs and returns the device push token. This token can change after OS updates.
  </Step>

  <Step title="POST /devices">
    The app sends `pushToken`, `deviceId`, `osVersion`, and `appVersion`. The backend upserts the device record and refreshes `lastSeenAt` and `ttl`.
  </Step>

  <Step title="Observe pushToStartToken">
    The app starts observing `Activity<ScorecardAttributes>.pushToStartTokenUpdates`. When a token arrives, it's sent via `POST /devices/push-to-start-token`.
  </Step>
</Steps>

## Unregistration on sign-out

When the user signs out:

1. iOS calls `DELETE /devices/:deviceId`
2. Backend removes the device record from the `devices` table
3. Any active `live-activities` records for this device are NOT automatically cleaned up (they'll expire via TTL or get caught by the Orchestrator's recovery loop)
4. iOS clears all local state

## Failure modes

| Scenario                             | Behavior                                                                |
| ------------------------------------ | ----------------------------------------------------------------------- |
| User denies notification permissions | No tokens generated; app works but no push of any kind                  |
| Token changes after OS update        | App re-registers on next launch; record updated in-place                |
| App reinstalled                      | New `deviceId` generated; old record orphaned until TTL expires         |
| Registration POST fails              | Logged but not retried until next app launch                            |
| pushToStartToken POST fails          | LA cannot be started remotely; user must open app to create LA manually |

## Key endpoints

| Method | Path                               | Purpose                            |
| ------ | ---------------------------------- | ---------------------------------- |
| POST   | `/devices`                         | Register device + pushToken        |
| POST   | `/devices/push-to-start-token`     | Register push-to-start token       |
| POST   | `/matches/:matchId/activity-token` | Register per-activity update token |
| DELETE | `/devices/:deviceId`               | Unregister device on sign-out      |

## Related pages

* [Live Activity lifecycle](/business-flows/live-activity-lifecycle) -- how push-to-start tokens are used to create LAs
* [Match following](/business-flows/match-following) -- the flow that triggers update token registration
* [Authentication flow](/business-flows/auth-flow) -- sign-in triggers device registration
