Record an age-verification attestation
Appends ONE row to the durable age-verification compliance trail: that a check was performed, by which operator, for which transaction, when, and with what outcome. This is the record a merchant produces to a regulator, so only ever call it as the immediate consequence of a real check a human performed at the till. It is APPEND-ONLY: no endpoint amends or deletes a row, and a wrong one stays in the trail forever. Never call it to ‘backfill’ history, to satisfy a workflow, or on anyone else’s behalf.
Fields. transactionRef links the check to the sale it belongs to. outcome is the decision the till acted on: pass (the customer was old enough and was served), block (they were not and the sale was refused) or needs_check (the proof was inconclusive) — report what actually happened, never what would be convenient. at is optional and defaults to the server clock; send the till’s own ISO instant when the check ran offline and is only now syncing, so the record keeps its true time. id is optional and only exists for idempotency — omit it and a deterministic one is derived; supply the SAME id to make a retry safe rather than duplicating the row.
PRIVACY, and it is enforced, not advisory: the body is STRICT and carries no customer identity. There is no field for a date of birth, a name or a document number, and a body containing one is rejected with a 400 rather than stored — so a retry that ‘adds the proof’ will always fail. operatorId is likewise NOT accepted: it is re-derived from the authenticated actor, so the trail always names the credential that called, and a body carrying it is a 400. Whatever token you hold is the operator the record will blame.
Gating: the router rides the till scope pos.checkout.operate and the use-case additionally asserts the kernel sale:create — which that scope does imply, so the two agree here. Returns 201 with the stored row.
Authorizations
Authorization: Bearer <token>. Accepts EITHER a Keycloak access token (scopes-in-token) OR an opaque POS session token; both resolve to the same pos.* scope vocabulary the route guards enforce.
Body
Response
The appended attestation, with the operator stamped from the caller's own token.
The appended attestation, with the operator stamped from the caller's own token.
^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$pass, block, needs_check 
