API reference

Sign-in approvals

The Authenticator app's side of sign-in approvals: requests waiting for an answer, the signed answer, enrolling the phone's key, and signing out everywhere after No.

See Sign-in approvals. These routes are at /v1/me/sign-in-approvals, reachable only with the Authenticator app's own sign-in (a mobile token issued to the authenticator app): organization keys, Caity, the phone assistant and the other apps get 403. A token alone can read requests but can't answer them: answers, enrolling and signing out everywhere carry a signature from the phone's key.

Keys. Each phone has a P-256 key (on iPhone, made in the Secure Enclave). Public keys are the 65-byte uncompressed point in base64url; signatures are ECDSA with SHA-256 over the UTF-8 message, DER-encoded, in base64. Every message is one field a line:

si-sign-in-approval/v1
approval:<approvalId>
user:<userId>
device:<deviceId>
decision:<approve | deny>
number:<the number tapped, or ->
binding:<the request's binding>
expires:<the request's expiresAt>

Enrolling signs si-sign-in-approval-enrol/v1, user:, key: (the public key) and signed: (epoch milliseconds, within 5 minutes of the server's clock); signing out everywhere signs si-sign-in-approval-secure/v1, user:, device:, approval: (the request answered with No) and signed:.

Enrolling needs the app's sign-in to be recent (10 minutes) and made with a passkey or a password and a second step; otherwise 403 with code: "reauth", and the app signs in again with prompt=login.

Answering. The right number approves; another number or deny denies. A request can be answered once, before its expiresAt (2 minutes after it was made); the third denial in 15 minutes pauses requests for an hour (paused: true). Answers are limited to 20 per account in 15 minutes, signatures that don't verify included.

GET /v1/me/sign-in-approvals/settings

Sign-in approvals for your account: on, pausedUntil (epoch ms, while requests are paused after denials), the phones that approve (devices: name, model, system, where the key lives, when added and last used; never keys) and twoStep (whether the account has a code to fall back to; requests need one). From the Authenticator app's sign-in only.

Auth: user access token or platform agent key

Response 200

{
  twoStep: boolean
  on: boolean
  pausedUntil?: number
  devices: {
    deviceId: string
    name: string
    model?: string
    os?: string
    keyStorage: "secure-enclave" | "keychain" | "keystore"
    createdAt: number
    lastUsedAt?: number
  }[]
}

Errors

StatusMessage
403Sign-in approvals open only in the Authenticator app.

GET /v1/me/sign-in-approvals/requests

Sign-in requests waiting for an answer, newest first: what's signing in (app), from which browser (device), roughly where (place), createdAt and expiresAt. No numbers. What the app shows when it opens, for a notification that never arrived.

Auth: user access token or platform agent key

Response 200

{
  requests: {
    approvalId: string
    app: string
    device?: string
    place?: string
    createdAt: number
    expiresAt: number
  }[]
}

Errors

StatusMessage
403Sign-in approvals open only in the Authenticator app.

GET /v1/me/sign-in-approvals/requests/:approvalId

One waiting request with the three numbers to show (choices), the account (userId, email) and the binding the answer is signed with; 404 once answered, withdrawn or expired.

Auth: user access token or platform agent key

Path parameterDescription
:approvalIdApproval id (apr_…): an issue's, from its approvals; under /v1/me/sign-in-approvals a sign-in request.

Response 200

{
  request: {
    approvalId: string
    app: string
    device?: string
    place?: string
    createdAt: number
    expiresAt: number
    userId: string
    email: string
    choices: number[]
    binding: string
  }
}

Errors

StatusMessage
403Sign-in approvals open only in the Authenticator app.
404This request was answered or has expired.

POST /v1/me/sign-in-approvals/requests/:approvalId

Answers a request from this phone (deviceId): decision approve with the number tapped, or deny, with signature (see above). The right number approves; another number or deny denies (reason wrong_number or not_me, and paused when requests are now paused for an hour). 403 when the signature doesn't verify or the phone was removed, 409 when already answered or expired, 429 past 20 answers in 15 minutes.

Auth: user access token or platform agent key

Path parameterDescription
:approvalIdApproval id (apr_…): an issue's, from its approvals; under /v1/me/sign-in-approvals a sign-in request.

Request body (up to 4 KB)

FieldTypeRequiredNotes
deviceIdstringYesup to 64 characters
decision"approve" | "deny"Yes
numberintegerNo10–99
signaturestringYes8–200 characters

Also checked: Unknown fields are rejected.

Response 200

{
  status: string
  reason?: "not_me" | "wrong_number"
  paused?: boolean
}

Errors

StatusMessage
403Sign-in approvals open only in the Authenticator app.

POST /v1/me/sign-in-approvals/devices

Enrols this phone's key: publicKey, keyStorage (secure-enclave, keychain or keystore), name, optional model, os, appVersion, installId, timeZone, with signedAt and the key's signature over the enrolment message. 201 with the device (200 for a key already enrolled); the same phone's older key is replaced; up to 10 phones. 403 with code: "reauth" until the app's sign-in is recent and two-step or a passkey. Turns approvals on.

Auth: user access token or platform agent key

Request body (up to 4 KB)

FieldTypeRequiredNotes
publicKeystringYesup to 100 characters
keyStorage"secure-enclave" | "keychain" | "keystore"Yes
namestringYesup to 200 characters
modelstringNoup to 200 characters
osstringNoup to 100 characters
appVersionstringNoup to 100 characters
installIdstringNoup to 128 characters
timeZonestringNoup to 64 characters
signedAtintegerYes
signaturestringYes8–200 characters

Also checked: Unknown fields are rejected.

Errors

StatusMessage
403Sign-in approvals open only in the Authenticator app.

DELETE /v1/me/sign-in-approvals/devices/:deviceId

Removes a phone: it gets no more requests and its answers are refused.

Auth: user access token or platform agent key

Path parameterDescription
:deviceIdDevice id: an agent's (dev_…); under /v1/me/notifications a phone's install id; under /v1/me/sign-in-approvals a phone that approves sign-ins (apd_…); under /v1/me/vault the id a device made for its vault (22 base64url characters).

Response 204 with no body.

Errors

StatusMessage
403Sign-in approvals open only in the Authenticator app.

POST /v1/me/sign-in-approvals/secure

Signs your account out everywhere but this app, after this phone answered a request with No (approvalId, within 30 minutes): every browser session at ID with the web apps' sign-ins from them, every other phone app and every command-line sign-in. Signed with the phone's key (deviceId, signedAt, signature). Returns how many browsers, apps and commandLine sign-ins ended.

Auth: user access token or platform agent key

Request body (up to 4 KB)

FieldTypeRequiredNotes
deviceIdstringYesup to 64 characters
approvalIdstringYesup to 64 characters
signedAtintegerYes
signaturestringYes8–200 characters

Also checked: Unknown fields are rejected.

Response 200

{
  browsers: number
  apps: number
  commandLine: number
}

Errors

StatusMessage
403Sign-in approvals open only in the Authenticator app.