# Auth · Identities

> The account itself — its name, photo, passphrase, passkey, email, claims, connections and sessions.

The account, and everything hanging off it. An identity carries a `kind`, an id, a display name, and
whichever of username, email and phone it was registered with — plus a photo, a passphrase, a passkey, claims,
provider connections and its live sessions, each with a route of its own.

## Guards

Most of this surface is _session_-guarded and enforced by the handler against the identity in the path. Three
parts are stricter:

- `Own identity` — passphrase, passkey and email refuse any session that is not that identity, an admin's included. Changing the address a recovery mail goes to is not an administrative act.

- `Admin` — claims and the notification routes, which write into every token the account is issued or send mail on its behalf.

- `POST /api/auth/identity/:id/email` is the exception inside the exception: assigning an address without proving it is admin-only.

## Fields have routes

`POST /api/auth/identity/:id` changes several fields at once. Each field also has its own route —
`…/name`, `…/photo`, `…/passphrase` — which is what a settings screen wants, since
it can then save one control without sending the rest of the form.

## Claims

Claims ride inside the identity token, so anything written there reaches every request the account makes. They are
also what `GET /api/auth` watches: a token minted before a claim changed is silently refreshed rather
than left stale.

## List identities

`GET /api/auth/identity`

Guard: admin

Every identity on the deployment. `query` searches rather than pages; `expand` swaps
the id-only listing for whole records, which is the difference between one call and one call per row when
rendering a table.

### Parameters

- `query` _string_ — Search instead of listing. Query parameter.
- `expand` _boolean_ — Return whole identities rather than ids. Query parameter.
- `cursor` _string_ — Continue from a previous page. Query parameter.
- `limit` _number_ — Page size. Query parameter.

### Returns

The page of identities, and a cursor when there is more.

### Request (cURL)

```
curl "https://example.com/api/auth/identity?expand=true&limit=2" \
  -H "authorization: Bearer $TOKEN"
```

### Response

```json
{
  "identities": [
    { "id": "01J2788XY98FDTA410C8S716CD", "kind": "admin", "displayName": "Ada Lovelace" }
  ],
  "cursor": null
}
```

## Read an identity

`GET /api/auth/identity/:id`

Guard: session

The identity record.

A missing identity answers `404` — unless the requester _is_ that identity, in which case
it answers `401`. That is how a deleted account learns its session is worthless instead of
being told its own id does not exist.

### Parameters

- `id` _string_ **Required** — Path parameter.

### Returns

`{ identity }`, `404`, or `401` for a terminated own account.

### Request (cURL)

```
curl https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD \
  -H "authorization: Bearer $TOKEN"
```

### Response

```json
{
  "identity": {
    "id": "01J2788XY98FDTA410C8S716CD",
    "kind": "member",
    "displayName": "Ada Lovelace",
    "username": "ada",
    "registered": 1767225600
  }
}
```

## Check whether an identity exists

`HEAD /api/auth/identity/:id`

Guard: session

The same read with no body.

### Parameters

- `id` _string_ **Required** — Path parameter.

### Returns

`200`, `404`, or `401`.

### Request (cURL)

```
curl -I https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD \
  -H "authorization: Bearer $TOKEN"
```

### Response

```json
{}
```

## Create an identity at a known id

`PUT /api/auth/identity/:id`

Guard: session

Reserves the id and writes an identity into it. An id that is already taken answers `409` — this
never overwrites, which is what makes it safe to retry.

### Parameters

- `id` _string_ **Required** — The id to claim. Path parameter.
- `kind` _enum_ **Required** — What the identity is. Missing or unrecognised answers `400`.
  - One of: `guest`, `member`, `admin`, `service`, `developer`

### Returns

The created identity. `409` when the id is taken.

### Request (cURL)

```
curl -X PUT https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{ "kind": "member" }'
```

### Response

```json
{
  "identity": { "id": "01J2788XY98FDTA410C8S716CD", "kind": "member" }
}
```

## Update an identity

`POST /api/auth/identity/:id`

Guard: session

Applies whichever fields the body carries. A body with none of them answers `400` rather than
quietly succeeding at nothing.

Each field has a route of its own as well; this is the one call that changes several at once.

### Parameters

- `id` _string_ **Required** — Path parameter.
- `displayName` _string_ — The name shown to other people.
- `email` _string_ — The email address on the account.
- `phone` _string_ — The phone number on the account.
- `username` _string_ — The username on the account.
- `passphrase` _string_ — A new passphrase.
- `kind` _enum_ — What the identity is.
  - One of: `guest`, `member`, `admin`, `service`, `developer`

### Returns

A result naming what changed. `400` when the body carries nothing updatable.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{ "displayName": "Ada L." }'
```

### Response

```json
{
  "displayName": "Ada L."
}
```

## Terminate an identity

`DELETE /api/auth/identity/:id`

Guard: session

Runs termination for the account. An identity that is already gone still has its cache entry swept before
the `404`, so a half-deleted record cannot linger and keep answering lookups.

### Parameters

- `id` _string_ **Required** — Path parameter.

### Returns

`200` on termination, `404` when there was nothing to terminate.

### Request (cURL)

```
curl -X DELETE https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD \
  -H "authorization: Bearer $TOKEN"
```

### Response

```json
{
  "ok": true
}
```

## Read the display name

`GET /api/auth/identity/:id/name`

Guard: session

Answers `text/plain`, not JSON — it is one string, and wrapping it would only give a client something to unwrap.

### Parameters

- `id` _string_ **Required** — Path parameter.

### Returns

The display name, as text.

### Request (cURL)

```
curl https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/name
```

### Response

```json
"Ada Lovelace"
```

## Set the display name

`PUT /api/auth/identity/:id/name`

Guard: session

### Parameters

- `id` _string_ **Required** — Path parameter.
- `displayName` _string_ **Required** — The new name.

### Returns

`200` once assigned.

### Request (cURL)

```
curl -X PUT https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/name \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{ "displayName": "Ada L." }'
```

### Response

```json
{
  "ok": true
}
```

## Set the display name (alias)

`POST /api/auth/identity/:id/name`

Guard: session

The same handler as `PUT`, for clients that cannot send one.

### Parameters

- `id` _string_ **Required** — Path parameter.
- `displayName` _string_ **Required** — The new name.

### Returns

`200` once assigned.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/name \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{ "displayName": "Ada L." }'
```

### Response

```json
{
  "ok": true
}
```

## Remove the display name

`DELETE /api/auth/identity/:id/name`

Guard: session

### Parameters

- `id` _string_ **Required** — Path parameter.

### Returns

`200` once unassigned.

### Request (cURL)

```
curl -X DELETE https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/name \
  -H "authorization: Bearer $TOKEN"
```

### Response

```json
{
  "ok": true
}
```

## Read the photo

`GET /api/auth/identity/:id/photo`

Guard: session

The stored image. An identity with no photo gets a generated SVG avatar rather than a `404`, so
an `<img>` pointed at this never has to handle a missing case.

### Parameters

- `id` _string_ **Required** — Path parameter.

### Returns

The image, or a fallback `image/svg+xml` avatar.

### Request (cURL)

```
curl https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/photo
```

### Response

```json
"<svg xmlns=\"http://www.w3.org/2000/svg\" …>…<\/svg>"
```

## Set the photo

`PUT /api/auth/identity/:id/photo`

Guard: session

The request body is the image itself — send the bytes, not a JSON wrapper.

### Parameters

- `id` _string_ **Required** — Path parameter.
- `body` _binary_ **Required** — The image, with its `Content-Type`.

### Returns

`200` once stored.

### Request (cURL)

```
curl -X PUT https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/photo \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: image/png" \
  --data-binary @avatar.png
```

### Response

```json
{
  "ok": true
}
```

## Set the photo (alias)

`POST /api/auth/identity/:id/photo`

Guard: session

The same handler as `PUT`.

### Parameters

- `id` _string_ **Required** — Path parameter.
- `body` _binary_ **Required** — The image bytes.

### Returns

`200` once stored.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/photo \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: image/png" \
  --data-binary @avatar.png
```

### Response

```json
{
  "ok": true
}
```

## Remove the photo

`DELETE /api/auth/identity/:id/photo`

Guard: session

Reverts to the generated avatar.

### Parameters

- `id` _string_ **Required** — Path parameter.

### Returns

`200` once removed.

### Request (cURL)

```
curl -X DELETE https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/photo \
  -H "authorization: Bearer $TOKEN"
```

### Response

```json
{
  "ok": true
}
```

Setting and clearing a passphrase are refused for any session that is not the identity in the path — an
admin's included. There is no route here that reads one back; a passphrase is only ever written.

## Check whether a passphrase is set

`HEAD /api/auth/identity/:id/passphrase`

Guard: session

Readable by any session — it reports that a passphrase exists, not what it is.

### Parameters

- `id` _string_ **Required** — Path parameter.

### Returns

`200` when set, `404` when not.

### Request (cURL)

```
curl -I https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/passphrase \
  -H "authorization: Bearer $TOKEN"
```

### Response

```json
{}
```

## Set the passphrase

`POST /api/auth/identity/:id/passphrase`

Guard: own identity

`403` for a session belonging to anyone else. An empty body answers `400`.

### Parameters

- `id` _string_ **Required** — Path parameter.
- `passphrase` _string_ **Required** — The new passphrase.

### Returns

`200` once assigned.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/passphrase \
  --cookie "identity=$IDENTITY_TOKEN" \
  -H "content-type: application/json" \
  -d '{ "passphrase": "correct-horse-battery-staple" }'
```

### Response

```json
{
  "ok": true
}
```

## Remove the passphrase

`DELETE /api/auth/identity/:id/passphrase`

Guard: own identity

`403` for a session belonging to anyone else. Leaves the account reachable only by its other methods.

### Parameters

- `id` _string_ **Required** — Path parameter.

### Returns

`200` once removed.

### Request (cURL)

```
curl -X DELETE https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/passphrase \
  --cookie "identity=$IDENTITY_TOKEN"
```

### Response

```json
{
  "ok": true
}
```

Reading, setting and removing a passkey are refused for any session that is not the identity in the path.

## Check whether a credential is set

`HEAD /api/auth/identity/:id/credential`

Guard: session

Readable by any session — it reports that a passkey exists, not what it is.

### Parameters

- `id` _string_ **Required** — Path parameter.

### Returns

`200` when set, `404` when not.

### Request (cURL)

```
curl -I https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/credential \
  -H "authorization: Bearer $TOKEN"
```

### Response

```json
{}
```

## Read the credential

`GET /api/auth/identity/:id/credential`

Guard: own identity

`403` for a session belonging to anyone else, `404` when none is set.

### Parameters

- `id` _string_ **Required** — Path parameter.

### Returns

`{ credential }`.

### Request (cURL)

```
curl https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/credential \
  --cookie "identity=$IDENTITY_TOKEN"
```

### Response

```json
{
  "credential": { "id": "AXcv1234", "counter": 3 }
}
```

## Attach a credential

`POST /api/auth/identity/:id/credential`

Guard: own identity

Stores the attestation from `navigator.credentials.create()`. `403` for anyone else, `400` without a credential.

### Parameters

- `id` _string_ **Required** — Path parameter.
- `credential` _object_ **Required** — The attested credential, as the browser produced it.
  - `id` _string_ — The credential id.
  - `response` _object_ — The attestation payload.

### Returns

`200` once attached.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/credential \
  --cookie "identity=$IDENTITY_TOKEN" \
  -H "content-type: application/json" \
  -d '{ "credential": { "id": "AXcv1234", "response": { … } } }'
```

### Response

```json
{
  "ok": true
}
```

## Remove the credential

`DELETE /api/auth/identity/:id/credential`

Guard: own identity

`403` for a session belonging to anyone else.

### Parameters

- `id` _string_ **Required** — Path parameter.

### Returns

`200` once removed.

### Request (cURL)

```
curl -X DELETE https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/credential \
  --cookie "identity=$IDENTITY_TOKEN"
```

### Response

```json
{
  "ok": true
}
```

Guarded to the identity in the path, an admin's session included. Changing the address a recovery email goes to
is not an administrative action — the one exception is `POST`, which is admin-only precisely because
assigning an address without a challenge is.

## Check whether an address is set

`HEAD /api/auth/identity/:id/email`

Guard: own identity

### Parameters

- `id` _string_ **Required** — Path parameter.

### Returns

`200` when set, `404` when not.

### Request (cURL)

```
curl -I https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/email \
  --cookie "identity=$IDENTITY_TOKEN"
```

### Response

```json
{}
```

## Read the address

`GET /api/auth/identity/:id/email`

Guard: own identity

### Parameters

- `id` _string_ **Required** — Path parameter.

### Returns

`{ email }`, or `404`.

### Request (cURL)

```
curl https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/email \
  --cookie "identity=$IDENTITY_TOKEN"
```

### Response

```json
{
  "email": "ada@example.com"
}
```

## Assign an address without verifying it

`POST /api/auth/identity/:id/email`

Guard: admin

`Admin only.` Writes the address straight onto the account, skipping the challenge — `403` for anyone else. The verified path is `…/email/challenge` then `…/email/verify`.

### Parameters

- `id` _string_ **Required** — Path parameter.
- `email` _string_ **Required** — The address to assign.

### Returns

`200` once assigned. `400` without an address.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/email \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{ "email": "ada@example.com" }'
```

### Response

```json
{
  "ok": true
}
```

## Remove the address

`DELETE /api/auth/identity/:id/email`

Guard: own identity

### Parameters

- `id` _string_ **Required** — Path parameter.

### Returns

`200` once unassigned.

### Request (cURL)

```
curl -X DELETE https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/email \
  --cookie "identity=$IDENTITY_TOKEN"
```

### Response

```json
{
  "ok": true
}
```

## Send a verification challenge

`POST /api/auth/identity/:id/email/challenge`

Guard: own identity

Mails a challenge to the address, which `…/email/verify` then redeems. This is the first half of changing the email on an account.

### Parameters

- `id` _string_ **Required** — Path parameter.
- `address` _string_ **Required** — The address to challenge.

### Returns

`200`.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/email/challenge \
  --cookie "identity=$IDENTITY_TOKEN" \
  -H "content-type: application/json" \
  -d '{ "address": "ada@example.com" }'
```

### Response

```json
{
  "ok": true
}
```

## Redeem a verification

`POST /api/auth/identity/:id/email/verify`

Guard: own identity

Completes the change begun by `…/email/challenge`. Omitting `address` verifies whichever address the account already carries.

### Parameters

- `id` _string_ **Required** — Path parameter.
- `verification` _string_ **Required** — The token from the challenge email.
- `address` _string_ — The address being verified. Defaults to the account's current one.

### Returns

`200` when verified, `400` when the token does not match.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/email/verify \
  --cookie "identity=$IDENTITY_TOKEN" \
  -H "content-type: application/json" \
  -d '{ "verification": "01J2788XY98FDTA410C8S716CE", "address": "ada@example.com" }'
```

### Response

```json
{
  "ok": true
}
```

Claims are the arbitrary key–values carried inside the identity token, so anything written here reaches every
request that account makes. They are also what `GET /api/auth` watches: a token minted before a
claim changed is refreshed on the next call rather than being left stale.

## Read the claims

`GET /api/auth/identity/:id/claims`

Guard: admin

### Parameters

- `id` _string_ **Required** — Path parameter.

### Returns

`{ claims }`.

### Request (cURL)

```
curl https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/claims \
  -H "authorization: Bearer $TOKEN"
```

### Response

```json
{
  "claims": { "plan": "pro", "seats": 5 }
}
```

## Merge into the claims

`POST /api/auth/identity/:id/claims`

Guard: admin

A patch. Keys absent from the body keep their values.

### Parameters

- `id` _string_ **Required** — Path parameter.
- `<claim>` _any_ — Any JSON value, under any key.

### Returns

`{ claims }` after the merge.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/claims \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{ "plan": "enterprise" }'
```

### Response

```json
{
  "claims": { "plan": "enterprise", "seats": 5 }
}
```

## Replace the claims

`PUT /api/auth/identity/:id/claims`

Guard: admin

Wholesale. Anything not in the body is gone from the next token that account is issued.

### Parameters

- `id` _string_ **Required** — Path parameter.
- `<claim>` _any_ — The complete claim set.

### Returns

`{ claims }` as written.

### Request (cURL)

```
curl -X PUT https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/claims \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{ "plan": "free" }'
```

### Response

```json
{
  "claims": { "plan": "free" }
}
```

## Reset the claims

`DELETE /api/auth/identity/:id/claims`

Guard: admin

Back to empty.

### Parameters

- `id` _string_ **Required** — Path parameter.

### Returns

`{ claims }` after the reset.

### Request (cURL)

```
curl -X DELETE https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/claims \
  -H "authorization: Bearer $TOKEN"
```

### Response

```json
{
  "claims": {}
}
```

## List an identity's OAuth connections

`GET /api/auth/identity/:id/connection`

Guard: session

Every provider slot, whether or not it is connected — an unconnected provider is present with no value, which keeps a settings screen from having to know the provider list itself.

### Parameters

- `id` _string_ **Required** — Path parameter.

### Returns

`{ connections }` keyed by provider. `404` when the identity does not exist.

### Request (cURL)

```
curl https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/connection \
  --cookie "identity=$IDENTITY_TOKEN"
```

### Response

```json
{
  "connections": {
    "google": "104219383929281037492",
    "github": null,
    "apple": null
  }
}
```

## Read one connection

`GET /api/auth/identity/:id/connection/:provider`

Guard: session

The provider's account id for this identity. An unknown provider answers `400`, a missing identity `404`.

### Parameters

- `id` _string_ **Required** — Path parameter.
- `provider` _enum_ **Required** — Path parameter.
  - One of: `google`, `github`, `apple`, `twitter`, `snapchat`, `twitch`

### Returns

`{ connection }`.

### Request (cURL)

```
curl https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/connection/google \
  --cookie "identity=$IDENTITY_TOKEN"
```

### Response

```json
{
  "connection": "104219383929281037492"
}
```

## Disconnect a provider

`DELETE /api/auth/identity/:id/connection/:provider`

Guard: session

Unlinks the provider account. The identity stays; only the link goes.

### Parameters

- `id` _string_ **Required** — Path parameter.
- `provider` _enum_ **Required** — Path parameter.
  - One of: `google`, `github`, `apple`, `twitter`, `snapchat`, `twitch`

### Returns

`{ identity }` as it stands after the unlink.

### Request (cURL)

```
curl -X DELETE https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/connection/google \
  --cookie "identity=$IDENTITY_TOKEN"
```

### Response

```json
{
  "identity": { "id": "01J2788XY98FDTA410C8S716CD", "kind": "member", "oauth": {} }
}
```

## Read an identity's contact profile

`GET /api/auth/identity/:id/contact`

Guard: session

The notification-side record for the account — the row an audience holds when this identity is a member of one.

### Parameters

- `id` _string_ **Required** — Path parameter.

### Returns

`{ profile }`, or `404` when the identity has no contact record.

### Request (cURL)

```
curl https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/contact \
  --cookie "identity=$IDENTITY_TOKEN"
```

### Response

```json
{
  "profile": {
    "id": "01J2788XY98FDTA410C8S716CD",
    "name": "Ada Lovelace",
    "email": "ada@example.com"
  }
}
```

## List an identity's sessions

`GET /api/auth/identity/:id/session`

Guard: session

Every live session for the account — what a "signed in on these devices" list renders from.

### Parameters

- `id` _string_ **Required** — Path parameter.

### Returns

`{ sessions }`.

### Request (cURL)

```
curl https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/session \
  --cookie "identity=$IDENTITY_TOKEN"
```

### Response

```json
{
  "sessions": [
    { "jti": "01J2788XY98FDTA410C8S716CE", "created": 1767225600, "agent": "Chrome on macOS" }
  ]
}
```

## Invalidate one session

`DELETE /api/auth/identity/:id/session`

Guard: session

Ends the session named by `jti` — this is "sign out my other device". A body without one answers `400`.

### Parameters

- `id` _string_ **Required** — Path parameter.
- `jti` _string_ **Required** — The session to end.

### Returns

`{ session }` as invalidated.

### Request (cURL)

```
curl -X DELETE https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/session \
  --cookie "identity=$IDENTITY_TOKEN" \
  -H "content-type: application/json" \
  -d '{ "jti": "01J2788XY98FDTA410C8S716CE" }'
```

### Response

```json
{
  "session": { "jti": "01J2788XY98FDTA410C8S716CE", "invalidated": true }
}
```

## Invalidate one session (alias)

`POST /api/auth/identity/:id/session`

Guard: session

The same handler as `DELETE`, for clients that cannot send a body on one.

### Parameters

- `id` _string_ **Required** — Path parameter.
- `jti` _string_ **Required** — The session to end.

### Returns

`{ session }` as invalidated.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/session \
  --cookie "identity=$IDENTITY_TOKEN" \
  -H "content-type: application/json" \
  -d '{ "jti": "01J2788XY98FDTA410C8S716CE" }'
```

### Response

```json
{
  "session": { "jti": "01J2788XY98FDTA410C8S716CE", "invalidated": true }
}
```

## Read one session

`GET /api/auth/identity/:id/session/:jti`

Guard: session

### Parameters

- `id` _string_ **Required** — Path parameter.
- `jti` _string_ **Required** — The session id. Path parameter.

### Returns

`{ session }`.

### Request (cURL)

```
curl https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/session/01J2788XY98FDTA410C8S716CE \
  --cookie "identity=$IDENTITY_TOKEN"
```

### Response

```json
{
  "session": { "jti": "01J2788XY98FDTA410C8S716CE", "created": 1767225600 }
}
```

## Invalidate one session

`DELETE /api/auth/identity/:id/session/:jti`

Guard: session

The same effect as `DELETE …/session` with a `jti` body, addressed by URL instead.

### Parameters

- `id` _string_ **Required** — Path parameter.
- `jti` _string_ **Required** — The session id. Path parameter.

### Returns

`{ session }` as invalidated.

### Request (cURL)

```
curl -X DELETE https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/session/01J2788XY98FDTA410C8S716CE \
  --cookie "identity=$IDENTITY_TOKEN"
```

### Response

```json
{
  "session": { "jti": "01J2788XY98FDTA410C8S716CE", "invalidated": true }
}
```

## Send an arbitrary notification

`POST /api/auth/identity/:id/notification`

Guard: admin

`Not implemented.` Answers `501`. The specific notices below are the ones that
work — this route is the general form they were meant to collapse into, and has not been written.

### Parameters

- `id` _string_ **Required** — Path parameter.

### Returns

`501`.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/notification \
  -H "authorization: Bearer $TOKEN"
```

### Response

```json
{
  "status": 501,
  "statusText": "Not Implemented"
}
```

## Send the session created notice

`POST /api/auth/identity/:id/notification/session/created`

Guard: admin

Tells somebody a new session was opened on their account — the "new sign-in from a new device" mail. The account is addressed by id, so this reaches whichever contact the identity currently carries.

### Parameters

- `id` _string_ **Required** — Path parameter.
- `jti` _string_ **Required** — The session that was created.
- `agent` _string_ — The browser and platform it was opened from.
- `created` _number_ — When, as a Unix timestamp.

### Returns

The send result. `400` when the body is empty — the notice needs something to say.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/notification/session/created \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{ "jti": "01J2788XY98FDTA410C8S716CE", "agent": "Chrome on macOS" }'
```

### Response

```json
{
  "sent": true
}
```

## Send the session invalidated notice

`POST /api/auth/identity/:id/notification/session/invalidated`

Guard: admin

Tells somebody a session on their account was ended. The account is addressed by id, so this reaches whichever contact the identity currently carries.

### Parameters

- `id` _string_ **Required** — Path parameter.
- `jti` _string_ **Required** — The session that was ended.

### Returns

The send result. `400` when the body is empty — the notice needs something to say.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/notification/session/invalidated \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{ "jti": "01J2788XY98FDTA410C8S716CE" }'
```

### Response

```json
{
  "sent": true
}
```

## Send the suspension imposed notice

`POST /api/auth/identity/:id/notification/suspension/imposed`

Guard: admin

Tells somebody their account has been suspended, and why. The account is addressed by id, so this reaches whichever contact the identity currently carries.

### Parameters

- `id` _string_ **Required** — Path parameter.
- `reason` _string_ — What to tell them.
- `until` _number_ — When the suspension lifts, as a Unix timestamp.

### Returns

The send result. `400` when the body is empty — the notice needs something to say.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/notification/suspension/imposed \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{ "reason": "Unusual activity", "until": 1767830400 }'
```

### Response

```json
{
  "sent": true
}
```

## Send the suspension lifted notice

`POST /api/auth/identity/:id/notification/suspension/lifted`

Guard: admin

Tells somebody their account is usable again. The account is addressed by id, so this reaches whichever contact the identity currently carries.

### Parameters

- `id` _string_ **Required** — Path parameter.
- `reason` _string_ — What to tell them.

### Returns

The send result. `400` when the body is empty — the notice needs something to say.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/notification/suspension/lifted \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{ "reason": "Review complete" }'
```

### Response

```json
{
  "sent": true
}
```

## Send the termination warning notice

`POST /api/auth/identity/:id/notification/termination/warning`

Guard: admin

Warns somebody their account is scheduled for deletion — the mail that has to arrive before the one below. The account is addressed by id, so this reaches whichever contact the identity currently carries.

### Parameters

- `id` _string_ **Required** — Path parameter.
- `reason` _string_ — What to tell them.
- `scheduled` _number_ — When termination runs, as a Unix timestamp.

### Returns

The send result. `400` when the body is empty — the notice needs something to say.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/notification/termination/warning \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{ "reason": "Inactive for 24 months", "scheduled": 1767830400 }'
```

### Response

```json
{
  "sent": true
}
```

## Send the termination executed notice

`POST /api/auth/identity/:id/notification/termination/executed`

Guard: admin

Tells somebody their account has been deleted. The account is addressed by id, so this reaches whichever contact the identity currently carries.

### Parameters

- `id` _string_ **Required** — Path parameter.
- `reason` _string_ — What to tell them.

### Returns

The send result. `400` when the body is empty — the notice needs something to say.

### Request (cURL)

```
curl -X POST https://example.com/api/auth/identity/01J2788XY98FDTA410C8S716CD/notification/termination/executed \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -d '{ "reason": "Requested by the account holder" }'
```

### Response

```json
{
  "sent": true
}
```
