Developer docs

API reference

The Tessio.Cloud integration API: start an age check, read its result, manage the webhook and read your stats. Every operation takes an API key (tk_…) as a Bearer token. This is the public part of the full description, generated from it; the guides are at https://tessio.eu/docs/.

Base URL https://api.tessio.eu. Version 0.1.0. The same description as OpenAPI 3.1.0, for tools and agents: https://api.tessio.eu/v1/openapi.json. The guide walks through a first integration.

Authentication

Send Authorization: Bearer <key> on every request.

An API key (tk_…). A project's key with scope full calls every /v1 route for its project; with stats:read it calls /v1/stats/* only. An organisation's key is always stats:read and reads every project in the organisation.

Stats

GET /v1/stats/summary

Outcomes, rates and time to result for a period, beside the period before it

Exactly the days asked for, and the period of equal length just before them, so a client can show a change without a second request. Rates are over the checks that ended (passed, under age, failed, expired), so a check still pending moves no rate; each is null when nothing ended. p50Seconds and p95Seconds run from creation to completion, over completed checks only. retention says from which day the plan keeps every check. Under a project's key, over its project; under an organisation's key, over every project in it, or one with project_id. UTC days. Any key may call it, of either scope; each key spends its own budget.

ParameterInTypeDescription
from query string (date)

The first day, inclusive. A start further back than two years is refused rather than narrowed, so an answer never covers a shorter range than the one asked for.

Omitting both from and to gives the default window, the last 30 days. Omitting only from is a half-written request and is refused.

to query string (date)

The last day, inclusive. Defaults to today, and an end in the future is pulled back to today, so a preset like "this year" does not draw empty months that read as an outage.

mode query string

Live checks, test checks or both. Defaults to live at org scope (an organisation's key included), which is what a customer's own numbers are, and to the project's own mode at project scope (a project's key included), so a test project reads its checks. one of all, live, test.

project_id query string (uuid)

Only this project of the org. A project of another org is refused exactly as one that does not exist, invalid_project_id. Under a project's key, only that project may be named.

ResponseBodyDescription
200 AgeCheckStatsSummary

OK

400 Error

The range is refused as for every chart (invalid_from, invalid_to, range_inverted, range_too_old, range_too_long), or invalid_mode, invalid_metric, invalid_group_by or invalid_project_id. Checked before the plan, so a malformed request is never answered with an upgrade.

401 -

No key, or one that is unknown or revoked

403 PlanFeatureUnavailable

plan_feature_unavailable. The org's plan does not include this read. Every plan reads the summary, Starter adds the time series, Growth and above read all five.

429 Error

rate_limited: this key's budget is spent.

Header Retry-After (integer): Seconds until a request can succeed.

GET /v1/stats/timeseries

One metric over time, with zeros for empty buckets

One point per bucket from from to to, a bucket with no checks included as zero, so a chart never has to fill gaps. The granularity follows the range as on the Stats page, except that a range of two days or less is cut into hours, labelled YYYY-MM-DDTHH:00Z and ending at the current hour. Weeks and months are UTC calendar steps. pass_rate is null for a bucket where nothing ended. Under a project's key, over its project; under an organisation's key, over every project in it, or one with project_id. UTC days. Any key may call it, of either scope; each key spends its own budget.

ParameterInTypeDescription
from query string (date)

The first day, inclusive. A start further back than two years is refused rather than narrowed, so an answer never covers a shorter range than the one asked for.

Omitting both from and to gives the default window, the last 30 days. Omitting only from is a half-written request and is refused.

to query string (date)

The last day, inclusive. Defaults to today, and an end in the future is pulled back to today, so a preset like "this year" does not draw empty months that read as an outage.

mode query string

Live checks, test checks or both. Defaults to live at org scope (an organisation's key included), which is what a customer's own numbers are, and to the project's own mode at project scope (a project's key included), so a test project reads its checks. one of all, live, test.

metric query string

What each point counts. Defaults to checks, every check created in the bucket. one of checks, expired, failed, pass_rate, passed, under_age. default checks.

project_id query string (uuid)

Only this project of the org. A project of another org is refused exactly as one that does not exist, invalid_project_id. Under a project's key, only that project may be named.

ResponseBodyDescription
200 AgeCheckStatsSeries

OK

400 Error

The range is refused as for every chart (invalid_from, invalid_to, range_inverted, range_too_old, range_too_long), or invalid_mode, invalid_metric, invalid_group_by or invalid_project_id. Checked before the plan, so a malformed request is never answered with an upgrade.

401 -

No key, or one that is unknown or revoked

403 PlanFeatureUnavailable

plan_feature_unavailable. The org's plan does not include this read. Every plan reads the summary, Starter adds the time series, Growth and above read all five.

429 Error

rate_limited: this key's budget is spent.

Header Retry-After (integer): Seconds until a request can succeed.

GET /v1/stats/failures

Failed presentations by reason category

Every refused presentation in the period, counted by the category of its reason, read from the check timelines. A check can be refused more than once before it ends, so this counts attempts, not checks. The plan keeps timelines for less time than checks: before retention.eventsFrom the counts are partial. group_by splits each category further. Under a project's key, over its project; under an organisation's key, over every project in it, or one with project_id. UTC days. Any key may call it, of either scope; each key spends its own budget.

ParameterInTypeDescription
from query string (date)

The first day, inclusive. A start further back than two years is refused rather than narrowed, so an answer never covers a shorter range than the one asked for.

Omitting both from and to gives the default window, the last 30 days. Omitting only from is a half-written request and is refused.

to query string (date)

The last day, inclusive. Defaults to today, and an end in the future is pulled back to today, so a preset like "this year" does not draw empty months that read as an outage.

mode query string

Live checks, test checks or both. Defaults to live at org scope (an organisation's key included), which is what a customer's own numbers are, and to the project's own mode at project scope (a project's key included), so a test project reads its checks. one of all, live, test.

group_by query string

Split each category further. method is the profile the check asked for (av or eudiw), which it carries from creation, or for a check from before profiles were recorded, its method; what answered it is written only when it ends, which most refused checks never do. Omit for one row per category. one of method.

project_id query string (uuid)

Only this project of the org. A project of another org is refused exactly as one that does not exist, invalid_project_id. Under a project's key, only that project may be named.

ResponseBodyDescription
200 AgeCheckStatsFailures

OK

400 Error

The range is refused as for every chart (invalid_from, invalid_to, range_inverted, range_too_old, range_too_long), or invalid_mode, invalid_metric, invalid_group_by or invalid_project_id. Checked before the plan, so a malformed request is never answered with an upgrade.

401 -

No key, or one that is unknown or revoked

403 PlanFeatureUnavailable

plan_feature_unavailable. The org's plan does not include this read. Every plan reads the summary, Starter adds the time series, Growth and above read all five.

429 Error

rate_limited: this key's budget is spent.

Header Retry-After (integer): Seconds until a request can succeed.

GET /v1/stats/funnel

How far the checks of a period got

created, then responseReceived (a wallet's response was judged: refused, answered or held for a browser that never came back), then completed (an answer about age came out). The sandbox answers without a wallet, so its checks reach completed without responseReceived. A held presentation is read from the timeline, so before retention.eventsFrom it is not counted. eudiw adds the step only that profile has: a wallet fetched the signed request. An AV request travels inside its link, so nothing is fetched there and the step does not exist. Under a project's key, over its project; under an organisation's key, over every project in it, or one with project_id. UTC days. Any key may call it, of either scope; each key spends its own budget.

ParameterInTypeDescription
from query string (date)

The first day, inclusive. A start further back than two years is refused rather than narrowed, so an answer never covers a shorter range than the one asked for.

Omitting both from and to gives the default window, the last 30 days. Omitting only from is a half-written request and is refused.

to query string (date)

The last day, inclusive. Defaults to today, and an end in the future is pulled back to today, so a preset like "this year" does not draw empty months that read as an outage.

mode query string

Live checks, test checks or both. Defaults to live at org scope (an organisation's key included), which is what a customer's own numbers are, and to the project's own mode at project scope (a project's key included), so a test project reads its checks. one of all, live, test.

project_id query string (uuid)

Only this project of the org. A project of another org is refused exactly as one that does not exist, invalid_project_id. Under a project's key, only that project may be named.

ResponseBodyDescription
200 AgeCheckStatsFunnel

OK

400 Error

The range is refused as for every chart (invalid_from, invalid_to, range_inverted, range_too_old, range_too_long), or invalid_mode, invalid_metric, invalid_group_by or invalid_project_id. Checked before the plan, so a malformed request is never answered with an upgrade.

401 -

No key, or one that is unknown or revoked

403 PlanFeatureUnavailable

plan_feature_unavailable. The org's plan does not include this read. Every plan reads the summary, Starter adds the time series, Growth and above read all five.

429 Error

rate_limited: this key's budget is spent.

Header Retry-After (integer): Seconds until a request can succeed.

GET /v1/stats/breakdown

Outcomes grouped by a column

The same outcome counts as the summary, over exactly the days asked for, one row per value of group_by, largest first. project and api_key rows carry the name as label. A deleted key's checks lose their key, so they join the row whose key is null. key is null for checks with no value in that column, such as a check nothing answered, which has no method. Under a project's key, over its project; under an organisation's key, over every project in it, or one with project_id. UTC days. Any key may call it, of either scope; each key spends its own budget.

ParameterInTypeDescription
from query string (date)

The first day, inclusive. A start further back than two years is refused rather than narrowed, so an answer never covers a shorter range than the one asked for.

Omitting both from and to gives the default window, the last 30 days. Omitting only from is a half-written request and is refused.

to query string (date)

The last day, inclusive. Defaults to today, and an end in the future is pulled back to today, so a preset like "this year" does not draw empty months that read as an outage.

mode query string

Live checks, test checks or both. Defaults to live at org scope (an organisation's key included), which is what a customer's own numbers are, and to the project's own mode at project scope (a project's key included), so a test project reads its checks. one of all, live, test.

group_by (required) query string

The column to group by. one of api_key, format, method, mode, project, transport.

project_id query string (uuid)

Only this project of the org. A project of another org is refused exactly as one that does not exist, invalid_project_id. Under a project's key, only that project may be named.

ResponseBodyDescription
200 AgeCheckStatsBreakdown

OK

400 Error

The range is refused as for every chart (invalid_from, invalid_to, range_inverted, range_too_old, range_too_long), or invalid_mode, invalid_metric, invalid_group_by or invalid_project_id. Checked before the plan, so a malformed request is never answered with an upgrade.

401 -

No key, or one that is unknown or revoked

403 PlanFeatureUnavailable

plan_feature_unavailable. The org's plan does not include this read. Every plan reads the summary, Starter adds the time series, Growth and above read all five.

429 Error

rate_limited: this key's budget is spent.

Header Retry-After (integer): Seconds until a request can succeed.

Age Verification

GET /v1/age-checks

List the project's age checks

Newest first, keyset paginated, with optional status, method and billable filters. No request url, and no name, date of birth, document or photo from the credential. Several filters narrow to their intersection.

The cursor is a position in one fixed order that every filter shares, so paging on from a check the filter excludes is well defined and continues from where that check sits.

ParameterInTypeDescription
limit query integer

default 20. minimum 1. maximum 100.

startingAfter query string (uuid)

A check id; returns checks older than it (cursor).

status query string

Only checks in this status. Omit for every status. one of pending, completed, expired, failed.

method query string

Only checks answered by this profile. eudiw and av are real wallet presentations. demo is the sandbox and simulated is the public demo's explicit simulate call, neither of which involves a wallet. Omit for every method, which also includes checks nothing has answered yet. one of eudiw, av, demo, simulated.

billable query boolean

Only checks that are (true) or are not (false) on an invoice. Omit for both. This cannot be derived from status: a failed check may be a presentation that did not verify, which is free, or one that verified without a readable claim, which bills.

ResponseBodyDescription
200 AgeCheckList

OK

400 -

invalid_status / invalid_method

401 -

Missing / invalid / revoked key

403 Error

insufficient_scope. The key is valid but may only read stats (stats:read), so it cannot call this route. Use a project's key with scope full.

POST /v1/age-checks

Start an age check

The machine to machine surface. Creates an age verification check and returns the url of its hosted verification page, to send the person to, plus a check id to poll. Rate limited (default 60/min per key). No name, date of birth, document or photo from the credential is stored: the check keeps the boolean age result with your reference. Omit the body to check age_over_18. Pass an Idempotency-Key header to make create retries safe: a repeated key returns the original check.

Creating a check does not bill. A check is charged for when a wallet has presented an attestation and we have verified it, so an abandoned, declined, timed out or unverifiable check costs nothing, and a test mode check never bills at all. What this route does check is the plan's hard ceiling, which exists to bound a runaway integration. A metered plan stops at ten times its included volume, so passing the volume itself is ordinary business, charged nothing per unit; an unmetered plan stops at the bound it reports as both limits.

ParameterInTypeDescription
Idempotency-Key header string

Unique per project; a retry with the same key returns the original check (nothing is created or metered again). Kept for 24 hours, after which a retry carrying it creates and meters a new check. at most 255 characters.

Request body (application/json, optional): CreateAgeCheckRequest

ResponseBodyDescription
201 AgeCheckCreated

Check created (poll the returned id)

Header Location (string): The new check's path, /v1/age-checks/{id}.

400 Error

unsupported_min_age: minAge is outside 1 to 99, so it cannot be expressed as an age_over_NN claim. invalid_reference: the reference is longer than 200 characters or contains a control character. invalid_idempotency_key: the Idempotency-Key header is longer than 255 characters or is not well formed text. unknown_credential: credential is not one of the names listed on CreateAgeCheckRequest. credential_not_offered: credential is a real name that this deployment does not offer. All of these are refused before the idempotency key is reserved and before the check is metered, so a refusal costs you neither.

401 -

Missing / invalid / revoked key

403 Error

insufficient_scope. The key is valid but may only read stats (stats:read), so it cannot call this route. Use a project's key with scope full.

409 -

idempotency_key_reuse (same key, different body) or idempotency_in_progress (concurrent create)

429 Error

Rate limited, or the month has reached the plan's hard ceiling (error: quota_exceeded). On a metered plan the ceiling is ten times the included volume, so exceeding what the plan includes is fair use, charged nothing per unit, and is not what this refuses. On an unmetered plan the ceiling and the included volume are the same bound.

Header Retry-After (integer): Seconds to wait (rate limit).

GET /v1/age-checks/{id}

Poll an age check

Scoped to a project: a key can only read checks it created. result stays null until the check completes.

ParameterInTypeDescription
id (required) path string (uuid)
ResponseBodyDescription
200 AgeCheck

OK

401 -

Missing / invalid / revoked key

403 Error

insufficient_scope. The key is valid but may only read stats (stats:read), so it cannot call this route. Use a project's key with scope full.

404 -

Unknown check / not this project's

Webhooks

GET /v1/webhook

Get the configured endpoint (never the secret)

ResponseBodyDescription
200 WebhookConfig

OK

401 -

Missing / invalid / revoked key

403 Error

insufficient_scope. The key is valid but may only read stats (stats:read), so it cannot call this route. Use a project's key with scope full.

404 -

No endpoint configured

PUT /v1/webhook

Set the project's webhook endpoint

Registers (or replaces) the endpoint + the events it should receive (omit events to subscribe to all). The first call for a project returns a signing secret (shown once); a later call that changes the URL or the event list returns secret: null and leaves the existing secret in place, so your signature check keeps working across an edit. Replace it deliberately with POST /v1/webhook/secret. Each delivery is POSTed with an X-Tessio-Event header and an X-Tessio-Signature: t=<unix>,v1=<hex hmac-sha256(secret, "{t}.{body}")> header so you can verify it. Deliveries retry with backoff; GET /v1/age-checks/{id} stays the reliable fallback.

Request body (application/json, required): SetWebhookRequest

ResponseBodyDescription
200 WebhookCreated

Endpoint set (secret returned once, only when created)

400 -

invalid_url (not an absolute http(s) URL) or insecure_url. The endpoint must be https: a delivery is signed, but the signature proves integrity and not confidentiality, so the check outcome would otherwise travel in clear. The hosted service accepts https only.

401 -

Missing / invalid / revoked key

403 Error

insufficient_scope. The key is valid but may only read stats (stats:read), so it cannot call this route. Use a project's key with scope full.

DELETE /v1/webhook

Remove the endpoint (stops deliveries)

ResponseBodyDescription
204 -

Removed

401 -

Missing / invalid / revoked key

403 Error

insufficient_scope. The key is valid but may only read stats (stats:read), so it cannot call this route. Use a project's key with scope full.

404 -

No endpoint configured

POST /v1/webhook/secret

Replace the signing secret

Mints a new signing secret and returns it once. Deliveries are signed when they are sent, so this applies to anything already queued as well as to new events: until you install the new secret, every delivery fails your X-Tessio-Signature check. Setting the endpoint does NOT do this, so correcting a URL is safe.

ResponseBodyDescription
200 WebhookSecret

Replaced (new secret returned once)

401 -

Missing / invalid / revoked key

403 Error

insufficient_scope. The key is valid but may only read stats (stats:read), so it cannot call this route. Use a project's key with scope full.

404 -

No endpoint configured

Schemas

CreateAgeCheckRequest

All fields optional. Omit the body to check age_over_18.

FieldTypeDescription
minAge integer

Age threshold to verify. 18 is the only value guaranteed to work: the EU age verification attestation makes age_over_18 mandatory at issuance and leaves every other age_over_NN optional, so which further thresholds a holder can answer depends on who issued their credential. Any threshold from 1 to 99 is accepted (the ISO 18013-5 claim is age_over_NN, two digits); one the credential does not carry returns a check with status "failed" and a null meetsMinAge, rather than a guess. The sandbox only ever demonstrates 18. default 18. minimum 1. maximum 99.

reference string

Your correlation id, stored as you send it and returned with the check. Keep it opaque: do not put a name or other personal detail in it. Control characters are refused, including tab, carriage return and newline: the CSV export prints this column, and a newline in it would split one record into two. Printable characters are all accepted, so an id beginning with =, +, - or @ is fine; the export guards those itself. at most 200 characters.

credential string

Which credential the person is asked to present. Omit it for this deployment's default, which is what every check asked for before this field existed. A deployment offers its default plus any credentials it is configured to offer: naming one it does not offer is refused with credential_not_offered, and a name not in this list with unknown_credential. The names are proof-of-age (the EU age verification attestation), pid-mdoc and pid-sdjwt (the EU PID in its two encodings), pid-de-sdjwt (the German PID) and photo-id (the ISO Photo ID). Each check records what it asked for and is judged by that alone. one of proof-of-age, pid-mdoc, pid-sdjwt, pid-de-sdjwt, photo-id.

AgeCheckCreated

FieldTypeDescription
id string (uuid)
url string (uri)

The hosted verification page for this check: send the person here. Not a wallet deeplink. The page picks a transport for the browser and device in front of it, so a new transport reaches every integration without any of them changing.

status string

one of pending.

expiresAt string (date-time)
mode Mode

Error

FieldTypeDescription
error string

Machine readable code (for example invalid_from, insufficient_scope, idempotency_key_reuse).

AgeCheckList

FieldTypeDescription
data AgeCheckSummary[]
hasMore boolean

More rows exist; page with ?startingAfter=<last id>.

AgeCheck

FieldTypeDescription
id string (uuid)
status string

one of pending, completed, expired, failed.

result null | AgeCheckResult

Null until status is completed. Stays null when status is failed, which means no answer was obtained.

reference string | null

Your correlation id from create, echoed back.

mode Mode
failedAttempts integer

How many presentations failed verification, or wallet error responses arrived, while the check stayed pending, on either transport. Neither ends the check, so a pending or expired check with a count above zero means a response was posted and refused, or a wallet said it could not answer. It counts every refused response for the check, not only the holder's, so read it as a signal rather than as the person's own attempts.

lastFailureCodes string[] | null

The error codes of the most recent failed attempt, such as issuer_untrusted or signature_invalid. A code beginning wallet_ is the wallet's own error response, for example wallet_access_denied when the holder declined, the wallet held no matching credential or it could not authenticate the holder (OpenID4VP 1.0 section 8.5 gives one code for all three): it is the wallet's claim and nothing was verified. session_mismatch is a same device presentation whose redirect came back to a different browser from the one that opened the wallet, rejected under HAIP 1.0 section 5.1. Null when there has been none. Kept after the check completes.

AgeCheckStatsSeries

FieldTypeDescription
mode string

one of all, live, test.

metric string

one of checks, expired, failed, pass_rate, passed, under_age.

granularity string

one of day, hour, month, week.

points StatsPoint[]
retention StatsRetention

AgeCheckStatsFailures

FieldTypeDescription
mode string

one of all, live, test.

groupBy string | null

one of method, null.

rows StatsFailureRow[]
retention StatsRetention

AgeCheckStatsFunnel

FieldTypeDescription
mode string

one of all, live, test.

created integer (int64)
responseReceived integer (int64)
completed integer (int64)
eudiw StatsEudiwFunnel
retention StatsRetention

AgeCheckStatsBreakdown

FieldTypeDescription
mode string

one of all, live, test.

groupBy string

one of api_key, format, method, mode, project, transport.

rows StatsBreakdownRow[]
retention StatsRetention

SetWebhookRequest

FieldTypeDescription
url (required) string (uri)

Absolute https URL to receive events. Any other scheme is refused: http with insecure_url, the rest with invalid_url.

events WebhookEventType[]

Event types to receive. Omit (or empty) to subscribe to all.

WebhookCreated

FieldTypeDescription
url string (uri)
events WebhookEventType[]
secret string | null

Signing secret (whsec_…), shown once, store it to verify X-Tessio-Signature. Present when the call created the endpoint; null when it changed one that already had a secret, because editing the URL or the event list leaves the secret in place so your signature check keeps working. Ask for a replacement with POST /v1/webhook/secret.

WebhookSecret

FieldTypeDescription
secret string

The new signing secret (whsec_…), shown once.

Mode

The mode of the project that created this check, fixed when the project was created. test is a sandbox: the check completes on its own after a couple of seconds and its result is simulated, so meetsMinAge proves nothing about anyone. live means only a wallet presentation completes the check. Read this before acting on meetsMinAge. one of test, live.

AgeCheckSummary

FieldTypeDescription
id string (uuid)
status string

one of pending, completed, expired, failed.

minAge integer
meetsMinAge boolean | null
method string | null
transport string | null

How a real wallet's answer travelled, as on AgeCheckResult. Null when no wallet was involved, and also null while the check is still pending. one of openid4vp, dcapi, null.

reference string | null

Your correlation id from create, echoed back.

createdAt string (date-time)
mode Mode
billable boolean

Whether this check is on an invoice. NOT derivable from status: a failed check may be a presentation that did not verify, which is free, or one that verified without a readable claim, which bills.

failedAttempts integer

Presentations refused while the check stayed pending. It tells apart an expired check someone tried to answer from one nobody opened.

AgeCheckResult

FieldTypeDescription
minAge integer
meetsMinAge boolean

True if the user proved they meet the age threshold.

method string

What answered the check. 'demo' is a sandbox completion and 'simulated' is the public demo's explicit simulate call, neither of which involves a wallet. 'eudiw' and 'av' name the profile a real wallet presented under. one of demo, simulated, eudiw, av.

transport string | null

How a real wallet's answer travelled: openid4vp for the deeplink flow, dcapi for the W3C Digital Credentials API. Null when no wallet was involved, so a sandbox completion stays distinguishable on this field too. one of openid4vp, dcapi, null.

StatsPeriod

FieldTypeDescription
from string (date)

First UTC day, inclusive.

to string (date)

Last UTC day, inclusive.

outcomes StatsOutcomes
passRate number | null

passed / ended, 0 to 1, where ended is total minus pending. Null when nothing ended.

failRate number | null

(underAge + failed) / ended.

noAnswerRate number | null

noAnswer / ended.

p50Seconds number | null

Median seconds from creation to completion, over completed checks. For a same device check this includes the browser's return, because a check completes when the browser redeems the answer.

p95Seconds number | null

The 95th percentile of the same.

StatsRetention

The first UTC day the purge has left whole, from what it recorded it may have deleted: after an upgrade the longer plan does not bring back days the shorter one already took. It may understate, never overstate: a day it calls whole is. A range that starts earlier counts only what is still held, so read the older days as partial, not as "no checks". Null where no purge has run for the org, so nothing was deleted.

FieldTypeDescription
recordsFrom string | null (date)

For the checks: every read.

eventsFrom string | null (date)

For what is read from each check's timeline: failures, the funnel's step for a fetched request under eudiw, held presentations (part of the funnel's responseReceived) and noAnswer, which leaves out a check whose presentation was held.

PlanFeatureUnavailable

FieldTypeDescription
error string

one of plan_feature_unavailable.

feature string

one of stats.breakdown, stats.failures, stats.funnel, stats.summary, stats.timeseries.

StatsPoint

FieldTypeDescription
bucket string

YYYY-MM-DD, or YYYY-MM-DDTHH:00Z for hours (UTC).

value number | null

StatsFailureRow

FieldTypeDescription
category string

Why the presentations were refused, as a category. one of credential_expired, credential_revoked, invalid_presentation, issuer_untrusted, other, wallet_declined, wallet_error.

group string | null

The value grouped by. Null when not grouped, or when the check has none.

count integer (int64)

StatsEudiwFunnel

FieldTypeDescription
created integer (int64)
requestFetched integer (int64)

StatsBreakdownRow

FieldTypeDescription
key string | null
label string | null

The project or API key name, for those groups.

outcomes StatsOutcomes

WebhookEventType

age_check.completed: a result is in. age_check.failed: a presentation verified but gave no readable answer, because it did not disclose the claim. age_check.expired: the request lifetime elapsed with no answer (sandbox checks complete by themselves first). age_check.attempt_failed: a presentation failed verification, over either transport, or a wallet sent an error response such as a decline, and the check stays pending, so it is not terminal: the check still ends in one of the other three, announced to an endpoint subscribed to it. It is a notice, not a log: at most one waits unsent per check, one whose check has ended before it is picked up is dropped, and it does not follow the terminal event, except rarely when a delivery is interrupted while being sent. Read the check for failedAttempts and lastFailureCodes, which count every attempt. An endpoint saved before this event existed stored the events known then, so add it to receive it. one of age_check.completed, age_check.expired, age_check.failed, age_check.attempt_failed.

StatsOutcomes

Checks created in the period, by where they are now. passed, underAge, failed, expired and pending add up to total; noAnswer is a part of expired, not a sixth state.

FieldTypeDescription
total integer (int64)
passed integer (int64)

Completed and old enough.

underAge integer (int64)

Completed and not old enough.

failed integer (int64)

A presentation verified but carried no readable answer about age.

expired integer (int64)

Ran out of time: no wallet answered, or every answer was refused or never redeemed.

pending integer (int64)
noAnswer integer (int64)

Of expired, those no wallet answered at all: no refused attempt and no presentation held for the browser. A held presentation is read from the check's timeline, which the plan keeps for less time than the check (retention.eventsFrom), so before that day such a check is counted here.