Manual verification

Record that an employee checked the customer's age in person, for example from a physical ID. The employee identifies themselves with their 6-digit access code, and the transaction is recorded against them.

Flow

  1. The employee enters their access code. Look it up with GET /api/v1/employees/code?code= to get the employee's id.
  2. Send POST /api/v1/verifications with "method": "manual" and that employeeId.

The access code is checked at the lookup step. The verification request itself identifies the employee by employeeId and does not take the code.

Both calls accept a service client (Bearer) or a linked device (DPoP plus a DPoP proof header, see Device authentication). Dashboard user tokens get 403.

Step 1: look up the employee

GET/api/v1/employees/code

Find an active employee in your tenant by their 6-digit access code. Requires the employees:read scope. Callers: service client (Bearer) or linked device (DPoP). A dashboard user token gets 403.

Headers

AuthorizationstringRequired

Bearer <access_token> (service client) or DPoP <access_token> (linked device). Requires the employees:read scope

DPoPstringOptional

Device callers only: a DPoP proof for this request. The htu is the URL without the query string

Query Parameters

codestringRequired

The employee's 6-digit access code. 404 if no active employee in your tenant has it

Examples

cURL

curl "https://abyrgverslun.is/api/v1/employees/code?code=483920" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Response

{
  "data": {
    "id": "kpwzv8tj1f5vhpvnra1dgwtamapfjw2g",
    "name": "Jón Jónsson",
    "updatedAt": 1758292800000,
    "createdAt": "2025-09-19T14:40:00.000Z"
  }
}

createdAt is an ISO 8601 string and updatedAt is epoch milliseconds (a number). An unset updatedAt is left out.

Step 2: record the verification

POST/api/v1/verifications

Record a manual verification with "method": "manual". The employee decides: "result": "pass" (the default) records that the customer meets requiredAge, "result": "fail" records a refusal. Either way the transaction records the employee as verifiedBy and you get a transactionId. Requires the transactions:write scope.

Headers

AuthorizationstringRequired

Bearer <access_token> (service client) or DPoP <access_token> (linked device). Requires the transactions:write scope

DPoPstringOptional

Device callers only: a DPoP proof for this request (htm POST, htu https://abyrgverslun.is/api/v1/verifications, ath)

Content-TypestringRequired

application/json

Idempotency-KeystringOptional

A unique key per verification, such as a UUID (1 to 255 printable ASCII characters). Send the same key when retrying, and you get the original response back instead of a second verification. See Retrying safely on the Pass verification page

Request Body

methodstringRequired

"manual"

employeeIdstringRequired

The id from the employee lookup. 404 if it isn't an active employee in your tenant (including a malformed or deleted one)

requiredAgeintegerRequired

Minimum age the employee checked for, 0 to 150 (for example, 20)

resultstringOptional

"pass" (default) if the customer meets requiredAge, "fail" to record a refusal. A refusal is stored as a failed verification (failReason underage) with its own transactionId

posReferencestringOptional

Your own reference, such as a receipt or basket ID, 1 to 20 characters. Stored on the transaction

deviceIdstringOptional

Service clients only: attribute the transaction to an active device of origin api in your tenant. 404 if the device doesn't exist, 400 if it isn't an active api device. Ignored for device callers, which are always recorded as themselves

verifiedBystringOptional

The Verifier. For a manual verification it must equal employeeId (400 otherwise). Becomes required, and the employee must have completed Employee Enrollment (403 verifier-not-enrolled otherwise), once enrollment is enforced

Examples

cURL (service client)

curl -X POST https://abyrgverslun.is/api/v1/verifications \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "method": "manual",
    "employeeId": "kpwzv8tj1f5vhpvnra1dgwtamapfjw2g",
    "requiredAge": 20,
    "posReference": "till4-1234",
    "deviceId": "khh2hxc6ghkzahz07azpb7qhn77m4w09"
  }'

JavaScript

const headers = { 'Authorization': `Bearer ${accessToken}` };

// Step 1: look up the employee by the code they entered
const lookup = await fetch(
  `https://abyrgverslun.is/api/v1/employees/code?code=${encodeURIComponent(code)}`,
  { headers },
);
if (lookup.status === 404) {
  throw new Error('Unknown access code');
}
const { data: employee } = await lookup.json();

// Step 2: record the verification
const response = await fetch('https://abyrgverslun.is/api/v1/verifications', {
  method: 'POST',
  headers: { ...headers, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    method: 'manual',
    employeeId: employee.id,
    requiredAge: 20,
    posReference: 'till4-1234',
  }),
});

const { result, transactionId } = await response.json();
// result === 'pass' (or 'fail' if you sent result: 'fail')

Response

{
  "result": "pass",
  "transactionId": "k1jcce4akgxtehpqa9pz2809dnxpxhfq",
  "livemode": true
}

Errors

Both calls return Problem Details (application/problem+json). See Errors. No transaction is recorded when a request is refused.

StatusWhen
400The body is not valid JSON, deviceId names a device that is not an active api device, verifiedBy is not employeeId (or is missing, once enrollment is enforced), or the Idempotency-Key header is empty, longer than 255 characters or not printable ASCII
401Missing, invalid, expired or revoked token, or a bad DPoP proof
403The token lacks the scope (employees:read for the lookup, transactions:write for the verification), or it is a dashboard user token. Type verifier-not-enrolled: the employee has not completed Employee Enrollment
404No active employee has that access code, the employeeId isn't an active employee in your tenant, or deviceId doesn't match a device in your tenant
422Validation failed: a missing code query parameter, or a body with a missing field or a value out of range. Also idempotency-key-reused: the Idempotency-Key was already used with a different body

To read a manual verification back later, call GET /api/v1/verifications/{transactionId} (see eID verification) or the Transactions API.