Quickstart

Follow one verification from creation to its result.

Before you start

Contact contact@zeroage.org for your credentials. You receive an API key and a webhook verification secret together. See Pricing for more information.

Every request requires sending your API key in the Authorization header. Requests with a missing or invalid API key return HTTP 401.

Authorization: Bearer YOUR_API_KEY

Use the webhook verification secret to respond to ZeroAge’s endpoint ownership challenge. This confirms that you control the endpoint. Keep both credentials on your backend.

Configure the two shared destinations in Webhooks and recovery to receive completion and later status-change events. The guide covers endpoint verification and receiver setup. Webhooks are the expected notification path; you do not need to poll the event feed routinely.

1. Create a verification

Send this request from your backend. Use a new Idempotency-Key for each intended verification.

evaluation_date is optional in the request. When omitted, ZeroAge resolves it to the UTC civil date when the verification is first created. You can provide today or a future date in YYYY-MM-DD form. The resolved date never changes, including on exact retries after UTC midnight.

curl --request POST 'https://api.zeroage/v1/verifications' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: order-8f34d6f1-age-check' \
  --data '{
    "age_threshold": 18,
    "client_reference": "order-8f34d6f1"
  }'

2. Read the response

A successful creation returns HTTP 201 with a pending verification and the user’s handoff URL:

{
  "id": "ver_example_827",
  "client_reference": "order-8f34d6f1",
  "age_threshold": 18,
  "evaluation_date": "2026-10-08",
  "status": "pending",
  "created_at": "2026-10-08T10:00:00Z",
  "request_expires_at": "2026-10-08T10:15:00Z",
  "revision": 0,
  "original": null,
  "current": null,
  "verification_url": "https://verify.zeroage.example/start/example-handoff"
}

Save the verification id to match later events. original and current are null until the request becomes terminal.

3. Send the user to the check

Direct the user to verification_url to complete the check in the mobile app before request_expires_at. Treat the URL as sensitive bearer access: do not log it or put it in client_reference.

Only creation and exact creation retries return this URL. Lookup responses and events omit it.

4. Read the completion event

The completion destination receives a verification.completed event:

{
  "id": "evt_example_completion_827",
  "type": "verification.completed",
  "created_at": "2026-10-08T10:02:00Z",
  "verification_id": "ver_example_827",
  "revision": 1,
  "data": {
    "id": "ver_example_827",
    "client_reference": "order-8f34d6f1",
    "age_threshold": 18,
    "evaluation_date": "2026-10-08",
    "status": "completed",
    "created_at": "2026-10-08T10:00:00Z",
    "request_expires_at": "2026-10-08T10:15:00Z",
    "revision": 1,
    "original": {
      "result": "yes",
      "reason": "verified",
      "evaluated_at": "2026-10-08T10:02:00Z"
    },
    "current": {
      "result": "yes",
      "review_status": "clear",
      "reasons": [],
      "assessed_at": "2026-10-08T10:02:00Z"
    }
  }
}

After validating the event, read data.original.result as yes, no, or unsure. A failed, cancelled, expired, or unsupported check produces unsure; it does not establish that the user is under the age threshold. See Results and revisions.

Deduplicate event IDs and compare revisions. Later verification.status_changed events report changes to the current assessment without changing the original decision. See Webhooks and recovery for receiver setup, ordering, and recovery, and Integration testing for checks to cover. Detailed requests and schemas are in the API reference.

Errors and retries

API errors use an error object with a stable code and a safe message. Branch on error.code, not message. The message is for display only and never contains submitted values, credentials, secrets, or document data. See the API reference for the JSON error envelope and response examples.

HTTP status Error code Action
400 invalid_request Correct the request input before sending it again.
401 unauthorized Stop automatic retries and correct the API credentials.
404 not_found Check the verification ID and customer credentials. An unknown verification and a verification owned by another customer are indistinguishable.
409 idempotency_conflict Reconcile the changed creation request with the original request. Do not automatically create a new idempotency key.
429 rate_limited Respect Retry-After when supplied. Otherwise, use bounded backoff with jitter.
503 service_unavailable Retry with bounded backoff and jitter.

An API error is never an age result. This also applies to an unknown error code: do not infer yes, no, or unsure from it.

A creation timeout or HTTP 503 can leave the outcome unknown. Retry with the same Idempotency-Key and the same request body. If evaluation_date was omitted, keep it omitted, including after UTC midnight. Never respond to an uncertain outcome by blindly minting a new key.

For a read-only request, retry the same query and cursor. After an uncertain timeout or service error from PUT /v1/webhook-endpoints, read the current configuration and reconcile it before resubmitting. Do not assume whether an endpoint ownership challenge will repeat.

Keep every automatic retry sequence bounded and use backoff with jitter.