Launch on 01/01/2027Licenses available from 01/01/2027
PreviewThis page documents the release that launches on 01/01/2027. Packages, images and sign-up open then, and details may change before it.
Docs / Your own mobile app / Device-facing gRPC

Device-facing gRPC

Ready
TL;DR

The device-facing surface is the gRPC contract every authenticator speaks. It is two listeners: an onboarding endpoint over ordinary TLS, where each of two enrollment calls carries its own cryptographic proof, and a mutually-authenticated gateway of eight calls, where the device certificate proves the hardware and a per-call signature proves the user. The Swift and Kotlin SDKs are generated from it.

Who this is for: anyone building a client from the wire directly — a platform we don’t yet ship an SDK for, or a security team auditing exactly what the phone sends. If you use the Swift or Kotlin SDK, you never touch this; it is the ground truth underneath them.

ⓘ You probably want the SDK

Most integrators embed the Swift or Kotlin SDK and never implement this surface by hand — the SDKs generate their bindings straight from these definitions. Read on if you are bringing a platform we don’t yet ship, or auditing exactly what the device transmits.

Two listeners, two trust models#

The device talks to two separate endpoints. They differ in how a caller is trusted — which is the whole story of the surface.

Two checks on every callOne proves which phone. The other proves who is holding it. Neither substitutes for the other.LAYER ONEThe certificate — proves the hardwareWHAT IT ISOne certificate per phone,issued when it enrolledCHECKED WHEREAt the port, then oncemore before any call runsWHAT IT ESTABLISHESWhich device this is,under which relying party,and whether it is activeWHAT IT CANNOT DOSay which person isholding the phoneLAYER TWOThe signature — proves the personWHAT IT ISOne key per identity,inside the security chipCHECKED WHEREInside each handler,not in a shared layerWHAT IT ESTABLISHESThat this identity, on thishardware, passed a freshbiometric check just nowWHAT IT CANNOT BEReused, replayed, orproduced without the chipWhy layer two is not in the shared layer with layer oneOne phone can hold several identities, and some calls concern the device rather than a person. Which key to check depends on the call.
Two stacked layers: the certificate proving the hardware, and the per-user signature proving the person.
Onboarding listener
ordinary TLS · no client certificate
  • A device that has not enrolled yet has no certificate to present, so this endpoint asks for none.
  • Each call proves itself with its own cryptographic primitive — a single-use invite token, or a platform attestation bound to a server nonce.
  • Two calls, and no more, are reachable here.
Gateway listener
mutual TLS · client certificate required
  • Transport — the device’s mTLS certificate proves “I am this hardware,” verified before the handler runs.
  • Application — state-changing calls also carry a per-user signature proving “I am this user,” checked against the key stored at enrollment: an ECDSA-P256 signature, or a hybrid ES256 + ML-DSA-65 signature on devices that commit to post-quantum signing at enrollment. The device produces it under biometric unlock.
  • Everything after enrollment lives here.

Onboarding — two calls#

The enrollment ceremony. A device arrives with an invite, proves it is a genuine phone, and leaves with a certificate it keeps. Each name links to its full request/response reference.

The only way in without a certificateEach call carries its own proof. The connection proves nothing, because it cannot.BeginEnrollmentA phone with no credential claims an inviteAUTHENTICATED BYA 256-bit opaque token,single use, minted by therelying partyHANDS BACKA fresh number to signover, and the deployment’scertificate chainWHAT AN ATTACKER NEEDSGuessing the token. It is not derived from anything, so thereis nothing to guess from.CompleteEnrollmentThe phone proves itself and is issued a certificateAUTHENTICATED BYThe same token, plusplatform attestation signedover that numberHANDS BACKA signed certificate, anda registered deviceWHAT AN ATTACKER NEEDSForging a platform attestation, or replaying one bound to adifferent number.On top of both: one budget per call, for every callerIts own budget per call, separate from every other limit1,000 at once, then 100 a second, per server instanceShared by every caller — no address is countedChecked first, in memory: a refused call reaches nothingTwo calls. Nothing else listens on this port.
Two calls, one card each: what authenticates it, what it hands back, and what it cannot do.
RPCWhat it doesAuthenticated by
BeginEnrollmentClaim a single-use invite, and receive a fresh attestation nonce plus the deployment’s root-CA chain.invite token
CompleteEnrollmentProve platform attestation and the new keys; receive the signed device certificate (and the bundled TOTP secret).attestation

Gateway — eight calls#

Everything an enrolled device does. device means the mTLS certificate is proof enough; device + user means a per-user signature rides along.

What the streams do, and what they costThe phone dials out. Nothing dials in — which is the property, and also the limitation.App openedthe phone dials out andholds the connection openRecent history replaysthe last few hours of requests,whatever their outcomeA request arrivespushed down the open stream,no push service involvedApp closedthe stream ends. Requestswait for it to reopenApp reopeneddials out again, and thehistory replaysSTREAM OPENCLOSEDSubscribeChallengesNew authentications and approvals for this identity, as they are issuedSubscribeAccountDevicesThe account’s whole device set, sent again whenever a device is added, removed or changes statusA dropped stream is not an error. The phone reconnects, the history replays, and nothing needs acknowledging or redelivering.
A stream opening when the app opens, replaying recent history, and receiving what arrives while it is held.
RPCWhat it doesAuthenticated by
PingReachability, certificate, and device-status check in one — drives the “check status now” affordance.device
RenewCertificateReplace the device’s mTLS certificate; the server signs a fresh one-year leaf. Active devices do this monthly.device
DeactivateDeviceDevice-initiated self-deactivation of this enrollment.device
FetchChallengePull a pending challenge by its ID — the one encoded in a scanned QR.device
SubmitChallengeResponseThe user’s signed terminal decision — approve, deny, or dismiss.device + user
SubscribeChallengesstreamA long-lived stream of incoming challenges — the push channel, with no APNs or FCM in the path.device
SubscribeAccountDevicesstreamThe account’s whole device set, sent on open and again whenever a device is added, removed or changes status.device
FetchAccountTOTPSecretFetch the current TOTP secret when the phone detects its copy has drifted.device
ⓘ No push intermediary

SubscribeChallenges is a plain server-stream: challenges arrive over the same direct, mutually-authenticated connection, with no Apple or Google push service in the path. That is what lets a deployment run entirely on its own — the sovereignty point covered in Why gRPC-only.

Straight from the proto#

Skip the SDK entirely: generate gRPC stubs from the public .proto files and drive both listeners yourself. You then own what the SDK otherwise handles for you — the hardware-backed keys, the canonical payload below, and the signature over it. This is the escape hatch for a platform we don’t yet ship, or a team that wants the transport in its own hands.

// No SDK: generate grpc-swift-2 stubs from the public .proto files.
$ buf generate     // or protoc --swift_out --grpc-swift_out

// You then open the mTLS channel, present the device certificate,
// build the canonical payload (see "The signed decision"), sign it
// with your Secure Enclave key, and call the generated client.
// The RPCs mirror the tables above.

The generated client mirrors the calls in the tables above; the full worked example is published with the public .proto files at launch.

The signed decision#

The one call that commits a user’s intent — SubmitChallengeResponse — is signed over a canonical byte payload: a fixed digest assembled from the challenge, the outcome, and the context, in a layout every client builds identically. Because the format is pinned, a signature from a Swift phone, a Kotlin phone, or a hand-rolled client all verify the same way on the server. Get that layout wrong and the signature simply fails to verify — the server never guesses.

ⓘ Get the contract

The full device-facing surface ships as a public .proto file — generate a client in any gRPC language with buf or protoc: download device_gateway_service.proto and device_onboard_service.proto. Each RPC above also links to its detailed endpoint reference. The relying-party surface your own backend calls is separate — see the API reference.

Opens on 01/01/2027

Available at launch

Until then, you can try the whole ceremony on your own phone.