# Browser-direct remembered accounts and devices

Every endpoint here belongs to SSO and uses POST below the
[browser-direct base](https://m7.org/docs/api/sso.user.m7.org/browser-direct.md), JSON, admitted Origin and fresh
endpoint DPoP. There is no extra session-management OAuth scope. Carrier/key
binding remains mandatory even for logout after a child's access has expired.
Device operations require a currently selected, active, activated, admitted
member session; carrier listing/logout do not require an active child token.

## Scope matrix

| Operation | Target | Other members / apps / hosted sessions |
| --- | --- | --- |
| `sessions/list` | Remembered entries in this browser carrier for this app | Not listed |
| `sessions/logout` | The specified remembered child, normally current | Remain; no automatic replacement selection |
| `sessions/logout-all` | Every remembered member in this app's **current browser carrier**; closes carrier | Other carriers, apps, hosted SSO and consumer sessions remain |
| `devices/list` | This member's active browser-direct sessions in **this application**, across carriers | Other apps, members, hosted SSO and consumer sessions excluded |
| `devices/revoke` | One listed session belonging to that member/app | Other eligible sessions remain |
| `devices/revoke-all` | This member's sessions in **this application**, including current | Other members in those carriers, other apps and hosted/consumer sessions remain |

Device operations are not global account logout. Independent Device Code grants,
API keys and consumer sessions are not in this listing. Cross-app credential
removal and manager reset have separate [revocation scopes](https://m7.org/docs/api/api.user.m7.org/tenant-member-security).

## Remembered accounts

| Relative path | Exact input | Success |
| --- | --- | --- |
| `/sessions/list` | `bd_carrier,bd_key` | Carrier view |
| `/sessions/switch` | `bd_carrier,bd_key,bd_session` of target | [Token package](https://m7.org/docs/api/sso.user.m7.org/browser-direct-authentication.md#token-package-and-custody), possibly pending with `selection_id` |
| `/sessions/logout` | `bd_carrier,bd_key,bd_session` of target | Updated carrier view, optional remote-cleanup indication |
| `/sessions/logout-all` | `bd_carrier,bd_key` | Empty carrier view, `carrier_closed: true`, optional `revocation_pending` |

Carrier view contains `sessions` and `current` (session ID or null). Rows contain
`bd_session`, safe display `label`, boolean `current`, boolean `available`, and
`last_used`. There is no pagination; a carrier holds at most 16 remembered
entries. Labels are for display, never account identity. An expired entry may
remain unavailable in the list, and an expired current session clears selection.
`available` reflects local session activity, not a guarantee that current policy
will permit the next renewal.

New member-session records start with a 30-day expiry. This is separate from
the returned access-token and activation lifetimes and is not a guarantee of
30 days of access: current admission, credential validity and revocation still
apply. At capacity, adding a different member returns `session_limit` (409);
remove a remembered account before adding another.

```json
{"sessions":[{"bd_session":"<session A>","label":"Sample Member","current":true,"available":true,"last_used":"2026-01-01T00:00:00Z"}],"current":"<session A>"}
```

Switch renews the target and checks its current policy; refresh on `/token`
requires the selected child. Pending switch does not change selection until ACK.
Keep `selection_id` with that pending package and ACK it as documented. Lost
switch/ACK responses are recovered with saved handles and fresh proofs; denial
preserves the previous selection. A stale receipt yields `session_changed`
(409), not permission to install the wrong account.

Fresh sign-in can attach to an existing carrier by adding its `bd_carrier,bd_key`
to code pickup. SSO collapses older entries for the same canonical member UUID
once the new session becomes active. It temporarily permits an additional
replacement entry at the carrier limit, then removes duplicates; it does not
allow a seventeenth different remembered member. Pending/failed reauthentication
preserves old entries. New sign-in records fresh authentication; switching and
refresh retain the original authentication time. Cleanup can report
`session_cleanup_pending` without concealing successful activation.

Logout is locally idempotent: a missing target is a no-op, and removing current
sets `current: null`. The empty carrier can be reused. Logout-all closes the
carrier, and a fresh login needs a new one. Retry logout-all with fresh proof
can return the closed receipt; it must not be interpreted as proof that every
remote cleanup just succeeded. Never switch to another remembered member as an
implicit logout side effect.

## Other devices in this application

| Relative path | Additional fields beyond selected handles | Success |
| --- | --- | --- |
| `/devices/list` | Optional `after`, a lowercase 64-hex session ID | `scope: application`, `sub`, `sessions`, `next_after` |
| `/devices/revoke` | `session_id`, lowercase 64-hex ID | Revocation result |
| `/devices/revoke-all` | None | Revocation result including current session |

```json
{"bd_carrier":"<carrier>","bd_session":"<selected session>","bd_key":"<carrier secret>","session_id":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}
```

Listing returns up to 50 active, unexpired sessions ordered by ID. Continue with
the returned `next_after` (ID or null); this is not recent-activity ordering,
a stable total or a snapshot across concurrent writes. Rows contain `id`, coarse
`device` (for example “Browser on OS”), `current`, `created_at`, `last_active_at`,
`expires_at` (ISO timestamps or null). Current is included. Raw IP addresses,
user-agent strings, carrier secrets, root IDs and tokens are not projected.

Revocation returns `scope: application`, `sub`, integer `revoked`, boolean
`current_revoked`, boolean `revocation_pending`, and `pending_count`. A missing
or out-of-scope target returns 404 `invalid_request`, not details about another
member. A previously user-logged-out target can be retried from another valid
selected session. Revoking current/all invalidates the caller's selected-session
authority: do not blindly retry with it. Sign in again or use another active
session to inspect remaining devices. There is no implicit bulk carrier closure
or selection of a different account.

Local closure is immediate. Remote root/token-family revocation is attempted in
bounded batches of eight and can remain pending for the existing reconciliation
process. A revoked browser discovers closure on its next authenticated request
or renewal; there is no browser-direct push notification that instantly clears
its UI, application cookies or every offline-validated access token. Clear the
local active-account state when authority is rejected and offer explicit sign-in.
Network failures need a fresh-proof list/status check from still-valid authority,
not an assumption that a revocation failed.
