# Devices
Source: https://docs.privacytracker.privacykey.org/devices

Tracking apps across more than one iPhone or iPad — the device menu, whose device is whose, re-sync, orphan handling, and the Apple Configurator actions.

Most people track one phone. If you track more than one — your own plus a
partner's, or a couple of the kids' — privacytracker keeps a row per device,
remembers which apps came from where, and lets you record whose each device is.

Devices are created by the **import** flow, not by hand. Whenever you import an
app list, privacytracker either matches an existing device or creates a new one.
Imports that carry an **ECID** (the Apple Configurator export, or the Python
helper reading a connected device) match on that identifier, so re-importing the
same phone updates the existing row rather than duplicating it. Imports without
one — screenshots, a pasted list — land against a placeholder device you can
rename once you know which phone they came from.

> **Note**
>
> The Devices page is gated behind `flag.settings.devices_page`. If
> **Settings → Devices** isn't in your sidebar, that flag is off for your
> current focus — see [Feature flags](https://docs.privacytracker.privacykey.org/develop/feature-flags).

## The device menu

The device menu at the top of every page shows whose apps you're looking at —
the device's icon and name, or **All devices**. Open it to pick one device, any
combination, or everything. Apps that aren't linked to any device, such as ones
you added by hand, have their own **Not tied to a device** entry.

The choice applies everywhere: the apps list, the dashboard's counts, Stats, the
Privacy Map, the shortlist and the review list all follow it, and it's remembered
between visits. When devices have owners recorded, the menu groups them under
each person's name. On a narrow screen it moves into the navigation drawer.

Bulk actions on the apps list follow it too, and name what they'll act on —
**Sync Mum's iPad** rather than **Sync All**.

Exports don't follow it. The CSV and JSON downloads, the shortlist export, the
audit bundle and database backups always cover every device, because whoever
opens an exported file can't tell a partial one from a complete one. While a
device is picked, each export control tells you so. Background maintenance in
Settings — the scheduled App Store sync, the Wayback backfill and the policy
sync — also runs across the whole library.

> **Note**
>
> The device menu is a view, not an access boundary. Anyone who can open
> privacytracker can pick any device. If family members' data needs keeping
> apart, run [separate instances](https://docs.privacytracker.privacykey.org/security#runtime-and-database-boundaries).

The menu is controlled by `flag.nav.device_scope` (on by default) and appears
once you've imported at least one device.

## Whose device is it

Each device can record who it belongs to: a name, such as *Mum* or *Leo*, and
whether it's **yours**, **someone you're helping**, or **a child you look
after**. privacytracker never guesses this from the device's name — it stays
blank until you say.

You set it in two places. From your second device onward, importing a new device
asks **Whose device is this?**, pre-selected from how you're set up, so your own
iPad is a single click. Your first device isn't asked. For any device, the owner
editor on **Settings → Devices** sets or changes it.

### Confirming you have permission

When a device belongs to someone else, you're also asked to confirm you have
their permission — to view the apps on their device, and to remove apps from it
if you decide to together. For a child's device, you confirm that you're
responsible for the child and their device. Importing someone else's device
won't continue until you tick it.

The confirmation is recorded with the time you gave it. Setting the device's
owner back to yourself, or clearing it, removes the confirmation, so a later
change of owner never inherits an old one.

### When the menu suggests switching mode

If you pick a device whose owner doesn't match how you're set up — a relative's
iPad while you're working on your own apps, for instance — the menu explains the
mismatch, says what switching would turn on, and offers to switch. It never
switches for you, and **Stay as I am** dismisses it. Only the mode changes; your
goals are kept.

## The Devices page

**Settings → Devices** (`/dashboard/settings/devices`) lists one row per device
with its app count. Each row can be renamed, given an owner, deleted, or
re-synced, and shows **permission confirmed** once you've confirmed it for
someone else's device.

Per device, privacytracker stores a name, the ECID if it has one, the model,
device class and iOS version reported at import, when it was created, when it
last synced, and — once you've recorded them — its owner and when you confirmed
permission.

Elsewhere in the app, an app's detail page shows a **Tracked on** row of device
chips, and an "Installed on *N* devices" panel that links back here.

## Re-syncing a device

Re-sync answers "what's changed on this phone since last time". It runs in two
phases, deliberately — nothing is written until you confirm.

**Step 1: Preview**

`POST /api/device-sync/preview` diffs the incoming app list against the set
currently linked to that device and returns `adds`, `removes` and
`unchanged`. Matching is keyed on App Store track ID; resolving bundle IDs
or names to a track ID is the importer's job, done before this point.

**Step 2: Confirm**

You tick the subset you actually want applied. `POST /api/device-sync/commit`
writes it in a single transaction.

### Orphans

An app can be linked to several devices. Removing it from one device usually
just drops that link.

But if the app isn't on any *other* device, removing it here untracks it
**entirely** — you lose its history along with it. Every row in the `removes`
list is flagged with `wouldOrphan` for exactly this reason, and the UI warns you
before you commit. The same sweep runs when you delete a whole device.

> **Warning**
>
> Deleting a device untracks every app that existed only on it. The confirmation
> dialog tells you how many apps that is. There is no undo — restore from a
> [backup](https://docs.privacytracker.privacykey.org/backup-and-restore) if you delete the wrong one.

After a successful re-sync you'll see a summary: how many apps were added,
removed, untracked entirely, and how many duplicate device rows were collapsed.

## Device actions

On the desktop build, privacytracker can drive Apple Configurator's `cfgutil`
against a connected device to **back it up** or **uninstall an app**.

The subprocess runs on the Tauri side, behind a Touch ID prompt. The server
never executes the command — the API routes exist to check whether the action is
permitted and to write the audit record.

### What has to be true first

Uninstall is guarded four ways, and all four must pass:

| Gate | Requirement |
|---|---|
| Device owner | The device's recorded owner must match how you're set up: your own device while you're working on your own apps (`self`), theirs while you're helping someone (`loved_one`), a child's while you're looking after a child (`guardian`). A device with no recorded owner keeps the original rule — you must be set up as `self`. |
| Permission | For anyone else's device, you must have [confirmed you have their permission](#confirming-you-have-permission), at import or on **Settings → Devices**. |
| Flag | `flag.devopts.cfgutil_uninstall` must be `on`. It is **off by default**. |
| Backup freshness | A successful backup of that device within the last 24 hours. The per-call opt-out relaxes this requirement only — never ownership, permission or the flag. |

None of this happens at a distance. The device has to be connected to this Mac
by cable, unlocked, and trusting the computer, and every app removal asks for
Touch ID or your password on the Mac. The ownership and permission gates make
the rest explicit: whose device it is, and that you're allowed to act on it.

When a gate refuses, the message names the device — and its owner, where one is
recorded — rather than citing a rule. On the review page, the removal steps
appear while you're set up as `self`, or once the device menu is narrowed to one
person's devices and their type matches how you're set up. If the device has no
owner recorded yet, the review page points you to **Settings → Devices**.

### The audit trail

Every attempt writes an activity row — `cfgutil_backup` or `cfgutil_uninstall` —
whether or not it succeeded. Those rows are visible in the Developer Options
activity log and travel with your [audit bundle](https://docs.privacytracker.privacykey.org/backup-and-restore).

Confirming permission for a device is recorded separately, in the
[audit log](https://docs.privacytracker.privacykey.org/security#audit-log), as `devices.permission_acknowledged`.

## API reference

| Endpoint | Methods | Purpose |
|---|---|---|
| `/api/devices` | `GET`, `POST` | List devices; create one, optionally with its owner |
| `/api/devices/{id}` | `GET`, `PATCH`, `DELETE` | Fetch; rename, set the owner, confirm or withdraw permission; delete |
| `/api/devices/{id}/bundles` | `GET` | Bundle IDs seen on this device |
| `/api/devices/{id}/tracked-apps` | `GET` | Apps currently linked to it |
| `/api/devices/for-app/{appId}` | `GET` | Which devices an app sits on |
| `/api/device-scope` | `GET`, `PUT`, `DELETE` | Read, save or reset the device menu's selection |
| `/api/device-sync/preview` | `POST` | Diff without writing |
| `/api/device-sync/commit` | `POST` | Apply the ticked subset |
| `/api/device-actions/backup` | `POST` | Record a completed `cfgutil` backup |
| `/api/device-actions/uninstall` | `GET`, `POST` | Check the gates; record the attempt |

`POST /api/devices` and `PATCH /api/devices/{id}` accept `ownerLabel` (a string,
or `null` to clear), `ownerAudience` (`self`, `loved_one`, `guardian`, or `null`),
and `permissionAcknowledged` (`true` records the confirmation now, `false`
withdraws it). A confirmation is only stored for a device owned by someone else.
When the uninstall gate refuses on ownership, its `reason` is `device_owner` or
`permission_unacknowledged`.

`GET /api/device-scope` returns the saved selection together with the device list
it was checked against. `PUT` takes `{ scope }` and returns the selection as saved
— IDs of devices that no longer exist are dropped, and a selection naming every
device plus unattached apps is stored as **All devices**.

### Reading one device's apps

Library reads accept an optional `devices` query parameter: `/api/apps` (every
list form), `/api/triage`, `/api/stats`, `/api/review-queue`, `/api/shortlist`
and `/api/privacy-profile/mismatches`. Pass comma-separated device IDs, include
`unattached` for apps linked to no device, or pass `all`.

It's opt-in. A request without it returns the whole library whatever is picked in
the device menu — the menu adds the parameter to its own requests, and nothing
adds it on your behalf. Device IDs that don't match a device are ignored; if
nothing matches, you get the whole library.

```bash
curl "http://localhost:3000/api/apps?limit=100&devices=<device-id>,unattached"
```

## Related

**[Getting apps in](https://docs.privacytracker.privacykey.org/quickstart)**

The four import routes, and which ones carry an ECID.

**[Backup & restore](https://docs.privacytracker.privacykey.org/backup-and-restore)**

What a backup contains, and how to undo a bad delete.
