Attestation
How a device proves itself, what each verdict means, and why a device that cannot attest is never failed.
Attestation asks the platform to vouch for the device and the build. Android uses Play Integrity and iOS uses App Attest.
A device that cannot produce either, an older iPhone, a handset with no Play services, a de-Googled Android, is reported as degraded with a reason rather than failed, and your policy decides what that is worth.
The exchange
Ask for a nonce
POST /v1/sdk/attest/nonceReturns a single-use nonce and its expiry. It binds the platform's answer to this one attempt, so a captured attestation cannot be presented again later.
Get the platform's answer
The SDK passes the nonce to Play Integrity or App Attest and receives a signed token.
Exchange it
POST /v1/sdk/attestVouch verifies the token against Apple or Google, records a verdict, and returns a run token.
The response looks like this:
{
"verdict": "pass",
"reasons": [{ "code": "debug_device", "message": "Registered development device, attestation skipped." }],
"source": "debug",
"run_token": "…",
"expires_at": "2026-09-17T11:18:57Z",
"from_cache": false,
"debug": true
}Results are cached per device for a configurable window, so a busy app stays inside the platform quotas rather than attesting on every launch.
Verdicts
| Verdict | Meaning |
|---|---|
pass | Genuine app, genuine device, official install. |
degraded | Vouch could not fully establish the device. Not a failure. |
fail | The platform says the app or device is tampered, emulated or unofficial. |
Every verdict carries reasons, each with a machine code and a sentence. Common ones:
| Code | Means |
|---|---|
verifier_not_configured | No Play Integrity credentials are set, so Android cannot be checked. |
no_play_services | The handset has no Google Play services. |
platform_unavailable | Apple or Google could not be reached. |
app_not_recognised | The installed binary is not the one you published. |
app_unofficial_install | Installed from somewhere other than the official store. |
device_not_trusted | The platform does not trust the device. |
device_emulator | The platform reports an emulator. |
nonce_mismatch | The attestation does not match the nonce it was issued for. |
assertion_counter_replay | An App Attest assertion was replayed. |
Signals the SDK notices itself are reported as categories, never raw device data: signal_rooted,
signal_debugger, signal_hooking, signal_repackaged, signal_emulator and
signal_unofficial_installer.
Why degraded is never failed
If you take one thing from this page: a degraded device is usually a customer, not an attacker.
De-Googled handsets, older systems and unreachable platform APIs all produce degraded. Treat it
as a policy decision, not a verdict.
The verifier will not refuse a degraded device on your behalf. Your policies decide, and monitor mode tells you how many real people that decision would affect first.
Run tokens
A successful attestation returns a run token, once. Only its hash is stored.
| Property | Value |
|---|---|
| Bound to | Device, app, app version and platform |
| Default lifetime | 15 minutes |
| Maximum lifetime | 24 hours |
Revoking a device's tokens ends its access immediately:
curl -X POST https://api.vouch.dev/v1/apps/app_123/security/devices/dev_abc/revoke \
-H "Authorization: Bearer sk_live_…"Development devices
You cannot attest properly from a simulator, so register the handsets you develop on.
curl -X POST https://api.vouch.dev/v1/apps/app_123/security/debug-devices \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{"device_id":"dev_abc"}'A registered device gets a pass labelled debug, and only with a test key. A live key refuses
debug attestation outright, so the list can never become a way into production.
Credentials you need
| Variable | If unset |
|---|---|
VOUCH_PLAY_INTEGRITY_CREDENTIALS | A Google service-account JSON, as a path or contents, for a project with the Play Integrity API enabled. Without it every Android device is degraded with reason verifier_not_configured, and nothing fails. |
VOUCH_APP_ATTEST_ENV | production or development, choosing which App Attest environment to expect. App Attest needs no credentials: it verifies against Apple's pinned root. |