switching clears the token & responses
No token
22

Managing your trusted devices

Sessions & devices

A user reviews which browsers may skip the new-device challenge, and revokes the ones they no longer recognise. New in Enhancement 4; counterpart to session management (scenario 21).

  1. GET /v1/me/devices → { id, label, ip, trustedAt, lastSeenAt, expiresAt, isCurrent }
  2. DELETE /v1/me/devices/{deviceId} → 204, untrust one — the next login there is challenged again (scenario 11)
  3. DELETE /v1/me/devices → 204, untrust every device EXCEPT the one this session runs on
GET/v1/me/devicesauthList my trusted devices

isCurrent is derived from the SESSION's device_id (via the sid claim), not from the cookie — the device cookie is scoped to /v1/auth and deliberately never reaches /v1/me. KNOWN LIMITATION: the dev gateway-token minter does not stamp sid, so in a local setup isCurrent reads false on every row. The stored token hash never reaches the wire.

⛓ Needs an out-of-band value?
ℹ︎Field guide — what each value means & where it comes from1 field

Authenticated read with no inputs — the caller is resolved entirely from the verified gateway context. Lists the browsers that may skip the new-device challenge. Modelled on GET /v1/me/sessions: one lists where you are signed IN, this lists what is TRUSTED.

sidheaderclaimoptionalgateway-injected
The session id inside x-gateway-context. It is what makes isCurrent decidable — a row is flagged by matching sessions.device_id for the session named by sid, NOT by the device cookie, which is scoped to /v1/auth and deliberately never reaches /v1/me. KNOWN LIMITATION: the dev gateway-token minter does not stamp sid, so locally every row reports isCurrent: false.
Bearer token
GET https://api.kerja.team/v1/me/devices
↩︎Response guide — what comes back & what each value means7 fields

200 with a { data } array of the caller's live trusted devices — the browsers that may skip the new-device challenge. The stored token_hash NEVER reaches the wire.

data[].idUUIDalways
The trusted_devices row id — pass it to DELETE /v1/me/devices/{deviceId} to untrust this browser.
data[].labelstringalways
A human-readable device label (e.g. “Chrome on Windows”) so the user can recognise what they are revoking.
data[].ipstringalways
The IP the trust was earned from.
data[].trustedAttimestampalways
When this device earned trust.
data[].lastSeenAttimestampalways
When it was last used to sign in — the value that slides the 30-day window forward.
data[].expiresAttimestampalways
When the trust lapses. A user may hold at most DEVICE_TRUST_MAX_PER_USER (20) live devices; at the cap the least-recently-seen one is revoked.
data[].isCurrentbooleanalways
Whether this is the device the CURRENT SESSION runs on — derived from sessions.device_id via the sid claim, not from the cookie (which is scoped to /v1/auth and never reaches /v1/me).
  • trueThis row is the caller's own browser — DELETE /v1/me/devices (untrust-all) spares it.
  • falseAnother browser — or, locally, EVERY row: the dev gateway-token minter does not stamp sid, so isCurrent reads false on all of them until that gap closes.
DELETE/v1/me/devices/{deviceId}authUntrust one device

204. Appends device_untrusted. Ownership is checked explicitly, so probing another user's device id returns 404 not_found rather than a silent success (BOLA/IDOR) — ownership is never leaked as 403. Untrusting is NOT signing out: the sessions on that device keep running until they expire or are revoked (scenario 21). Revoke both to fully evict a lost laptop.

⛓ Needs an out-of-band value?
Path parameters
ℹ︎Field guide — what each value means & where it comes from1 field

Authenticated. Untrusts ONE device — the next login from that browser is challenged again (scenario 11). Returns 204 and appends a device_untrusted audit event. Untrusting is not signing out: sessions on that device keep running until they expire or are revoked (scenario 21).

deviceIdpathUUIDrequiredfrom a response
The device row to untrust. Copy it from GET /v1/me/devices, or from the trust-device helper, which returns the deviceId it seeded. Ownership is checked EXPLICITLY: probing a device belonging to another user returns 404 not_found — never 403, never a silent success — so ownership is not leaked (BOLA/IDOR).
Bearer token
DELETE https://api.kerja.team/v1/me/devices/{deviceId}
↩︎Response guide — what comes back & what each value meansno body

204 No Content — an empty body is the whole success shape. Appends a device_untrusted audit event. The next login from that browser is challenged again (scenario 11); any sessions running on it keep going until they expire or are revoked separately.

DELETE/v1/me/devicesauthUntrust all except the current device

204. When the context carries no sid, or that session has no device_id, “the current one” has no referent and ALL devices are untrusted — the fail-closed reading: revoking too much costs one extra challenge, while keeping an unidentifiable device would defeat the call. Locally that is the behaviour you will see (see the sid limitation above). Note: password CHANGE and RESET already revoke device trust automatically (scenarios 23 / 24) — a forgotten or changed password is a plausible symptom of compromise, so trust earned before it does not survive it.

⛓ Needs an out-of-band value?
Request body (JSON)
ℹ︎Field guide — what each value means & where it comes from1 field

Authenticated, no body and no parameters. Untrusts every device EXCEPT the one this session runs on; 204. When the context carries no sid, or that session has no device_id, “the current one” has no referent and ALL devices are untrusted — the fail-closed reading: revoking too much costs one extra challenge, while keeping an unidentifiable device would defeat the call.

sidheaderclaimoptionalgateway-injected
The session id inside x-gateway-context, used to resolve which device to SPARE. Locally the dev gateway-token minter omits it, so this call degrades to untrusting every device — the intended fail-closed behaviour, not a bug.
Bearer token
DELETE https://api.kerja.team/v1/me/devices
↩︎Response guide — what comes back & what each value meansno body

204 No Content — an empty body is the whole success shape. Untrusts every device except the one this session runs on; when sid is absent (as it is locally) or that session has no device_id, ALL devices are untrusted, which is the fail-closed reading.