Skip to main content
Device pairing links a phone to a desktop session through a short-lived relay. The desktop mints a pairing, encodes the id and a single-use write token into a QR code, and the phone scans it and deposits its public keys. The desktop polls the relay until those keys arrive, then trusts the phone. Only public material crosses the relay; no secret does. The relay carries two of the phone’s public keys: an Ed25519 session public key and a P-256 (ECDH) public key. The desktop reads them once the pairing is ready. The relay only ever moves public keys, so a leak of the pairing mailbox leaks nothing usable.

The three calls

CallAuthBodyReturns
POST /api/v1/device-pairingX-DEVICE-KEYnoneCreatePairingResponse
PUT /api/v1/device-pairing/{pairing_id}Authorization: Bearer <write_token>WritePairingRequest204
GET /api/v1/device-pairing/{pairing_id}X-DEVICE-KEYnonePollPairingResponse
The desktop owns the first and third calls and authenticates them with its device key. The phone owns the middle call and authenticates it with the single-use bearer write token it read from the QR — not a device key.

The flow

1

Desktop mints the pairing

The desktop calls POST /api/v1/device-pairing with its X-DEVICE-KEY. The response is { pairing_id, write_token, expires_in_secs }. The write_token is returned once and is single-use; the exchange keeps no recoverable copy.
2

Desktop encodes the QR

The desktop puts pairing_id and write_token into a QR code on screen. The phone scans it. Nothing secret about the account travels here — the write token authorizes exactly one write to exactly one mailbox.
3

Phone deposits its public keys

The phone calls PUT /api/v1/device-pairing/{pairing_id} with Authorization: Bearer <write_token> and a body of its two public keys. On success the exchange returns 204 and the write token is spent.
4

Desktop polls until ready

The desktop calls GET /api/v1/device-pairing/{pairing_id} with its X-DEVICE-KEY on a short interval. It returns { status: "pending" } until the phone writes, then { status: "ready", session_pub, ecdh_pub }. Read the keys and stop polling.

The write body

The phone sends both public keys, each standard base64 (+ / / alphabet, = padding). URL-safe base64 is rejected.
session_pub
string
required
Ed25519 session public key, exactly 32 bytes, standard base64.
ecdh_pub
string
required
Uncompressed P-256 public key, exactly 65 bytes with a leading 0x04, standard base64.
A wrong byte length or a URL-safe encoding fails the write with 400, not 401 — the token was valid, the body was not. Encode both keys as standard base64 before sending.

The poll response

GET returns one of two shapes, distinguished by status. Branch on status, not on field presence.
status
string
pending while the mailbox is empty, then ready once the phone has written.
session_pub
string
Present only when status is ready. The phone’s Ed25519 session public key.
ecdh_pub
string
Present only when status is ready. The phone’s P-256 ECDH public key.

Lifetime and single use

The write token expires after the expires_in_secs returned by the mint. Read that value; do not hardcode a duration. The token is also single-use: the first successful PUT spends it, and any further write fails. A reused, expired, or missing token is rejected 401. Once a pairing is complete, a second write returns 409 with code pairing_already_completed. If the pairing expires before the phone writes, mint a fresh one. There is no extend call.

Errors

Failures carry application/problem+json (RFC 9457) with a stable code; branch on code, not on the human-readable detail. See the error model for the parsing rule.
StatusWhenWhat to do
400Malformed key encoding or length on PUTRe-encode both keys as standard base64 at the stated byte lengths
401Write token missing, expired, or already spentMint a new pairing; the old token cannot be reused
404Unknown, expired, or foreign pairing_idMint a new pairing; a pairing_id belongs to the account that minted it
409Pairing already completed (pairing_already_completed)Stop writing; the desktop should poll and read the keys
The write token is a bearer capability with no second factor. Anyone who reads the QR can write public keys into the mailbox until the token is spent or expires. Show the QR only to the phone you are pairing, and treat a pairing whose token you cannot account for as compromised — discard it and mint a new one.
Both desktop calls use the device key; for where it sits among the other credentials, see Credentials.