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
| Status | Message |
|---|---|
403 | Sign-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
| Status | Message |
|---|---|
403 | Sign-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 parameter | Description |
|---|---|
:approvalId | Approval 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
| Status | Message |
|---|---|
403 | Sign-in approvals open only in the Authenticator app. |
404 | This 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 parameter | Description |
|---|---|
:approvalId | Approval id (apr_…): an issue's, from its approvals; under /v1/me/sign-in-approvals a sign-in request. |
Request body (up to 4 KB)
| Field | Type | Required | Notes |
|---|---|---|---|
deviceId | string | Yes | up to 64 characters |
decision | "approve" | "deny" | Yes | |
number | integer | No | 10–99 |
signature | string | Yes | 8–200 characters |
Also checked: Unknown fields are rejected.
Response 200
{
status: string
reason?: "not_me" | "wrong_number"
paused?: boolean
}Errors
| Status | Message |
|---|---|
403 | Sign-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)
| Field | Type | Required | Notes |
|---|---|---|---|
publicKey | string | Yes | up to 100 characters |
keyStorage | "secure-enclave" | "keychain" | "keystore" | Yes | |
name | string | Yes | up to 200 characters |
model | string | No | up to 200 characters |
os | string | No | up to 100 characters |
appVersion | string | No | up to 100 characters |
installId | string | No | up to 128 characters |
timeZone | string | No | up to 64 characters |
signedAt | integer | Yes | |
signature | string | Yes | 8–200 characters |
Also checked: Unknown fields are rejected.
Errors
| Status | Message |
|---|---|
403 | Sign-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 parameter | Description |
|---|---|
:deviceId | Device 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
| Status | Message |
|---|---|
403 | Sign-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)
| Field | Type | Required | Notes |
|---|---|---|---|
deviceId | string | Yes | up to 64 characters |
approvalId | string | Yes | up to 64 characters |
signedAt | integer | Yes | |
signature | string | Yes | 8–200 characters |
Also checked: Unknown fields are rejected.
Response 200
{
browsers: number
apps: number
commandLine: number
}Errors
| Status | Message |
|---|---|
403 | Sign-in approvals open only in the Authenticator app. |