I IMBOSS / Developers
BUILD WITH IMBOSS v1.6.0

Staff API reference.

Connect your store experience. Identify customers, award points and redeem rewards with a complete audit trail.

BASE URLhttps://www.imboss.zelotech.com/api/staff/v1
Shared API contract

This reference describes the shared API contract. Availability depends on the business’s grants and published policy. Examples contain fictional data and never include live credentials.

Make your first request

  1. Confirm accessAsk the platform team to enable the required business and app access. Loyalty operations also require Loyalty.
  2. Sign in a staff deviceCall /login with a personal staff username, password and device name. Staff requests do not use X-App-Id.
  3. Use the returned tokenSend Authorization: Bearer YOUR_TOKEN. Keep tokens in secure storage and never include them in URLs or logs.

For the cURL examples, set BASE_URL="https://www.imboss.zelotech.com/api/staff/v1". Replace path placeholders, demo IDs and credentials before running requests. Writes affect business data.

INTEGRATION ESSENTIALS

Authentication & conventions

Request headers

Accept: application/json
Content-Type: application/json

Send Content-Type when a JSON body is present. Staff identity and tenant context come from the token. Assigned staff use their own branch; authorized business admins supply branch_id.

Token lifetime

New staff tokens expire after 12 hours. Expiry is fixed, not sliding. Existing legacy tokens retain their stored lifetime. A 401 requires sign-in again.

Amounts, IDs & dates

Use integer currency minor units: 1000 GBP = £10.00. Times use ISO 8601. Public customer, reward and transaction IDs are ULIDs; legacy staff and branch IDs remain integers.

Pagination & limits

Use page and per_page where documented. Page size is usually at most 50; staff branch discovery allows 100. Authenticated operations allow 120 requests/minute per token. Login, award limits and business-local daily allowances also apply. Honor Retry-After.

Authentication headers

Each endpoint marks whether a bearer token is required. Customer requests also include X-App-Id before and after sign-in.

HeaderDescriptionExample
AuthorizationToken returned by POST /login for a personal staff device. The token determines the business and staff scope; do not send a customer app ID or POS key.Bearer YOUR_TOKEN
Retry writes safely

Endpoints marked “Idempotency key” require Idempotency-Key (up to 64 characters). Save one key for each intended operation. Retry an identical request with the same key after a connection failure; changing the payload with that key returns 409 idempotency_conflict. Do not create a new key to retry an uncertain write.

HANDLE FAILURES WITH CONFIDENCE

Errors & retries

Use HTTP status for the failure category and error.code for application behavior. Messages are display text; do not parse them. Validation details live in error.fields. Not every status applies to every endpoint; each operation lists its documented responses.

{
    "error": {
        "code": "invalid",
        "message": "Some fields are invalid.",
        "fields": { "field_name": ["A validation message."] }
    }
}
StatusMeaning & recovery
400Missing idempotency key — Supply a stable key for the intended operation, then retry.
401Authentication required — Sign in again when the token is expired or revoked. Check that the staff account is active.
403Access denied — Check business entitlements, feature availability and staff permissions. Retrying unchanged will not restore access. Capability denial can return operation_unavailable; a missing exact reversal permission can return forbidden. Refresh /config for the current branch and stop the refused action. An unchanged request is not made valid by using another operation key.
404Not found — Check the public identifier and business context. Resources belonging to another tenant are not accessible.
409Conflict — Reuse a key only for an identical operation. For voucher_already_used, refresh reward status instead of redeeming again.
422Validation or domain refusal — Correct the fields or handle the domain refusal identified by error.code. Do not retry an unchanged invalid request.
429Rate limited; Retry-After header — Wait for the Retry-After header before retrying; retain the same operation key for a mutation.
503Authorization state unavailable — Retry after the Retry-After delay using bounded backoff. For a write, retain the same Idempotency-Key and identical payload; do not assume the operation succeeded or fall back to role-based access.
5xxUnexpected server failure. Retry reads with bounded backoff. For writes, retain the original operation key and reconcile the outcome. Infrastructure failures may not use the JSON error envelope.

ENDPOINT REFERENCE

Explore the staff API

Authentication

POST/login

Personal username/password login; no X-App-Id

Sign in with the staff member’s personal username and password. No X-App-Id is required. New tokens expire after 12 hours; login is limited to five attempts per username per minute. Operation aliases in staff.permissions and /config.capabilities reflect current branch scope, business access and authorization mode. Do not infer allowed operations from role alone; enforced mode supports grants to staff and denies to managers. Refresh after a branch change or 403. Authorization lookup failures can return 503 authorization_unavailable; honor Retry-After. device_name is generated and stored by the app, not entered by the staff member (see the field). Signing in ends the earlier session for the same staff account and device_name; other devices stay signed in. Authenticated rate limits are shared across all tokens of a staff principal (120/minute).

No bearer token

Request body application/json · required

Required markers apply within each parent object. Conditional requirements, such as choosing one identifier or a branch, are explained in the descriptions.

FieldTypeDescriptionConstraints & example
usernamerequired in parent objectstringPersonal staff username issued by the business administrator; do not substitute a customer phone or app ID.Max length: 100Example: demo.staff
passwordrequired in parent objectstringPassword for the staff account. Send over HTTPS and never save it in shared examples or logs.Format: password · Max length: 255Example: YOUR_STAFF_PASSWORD
device_namerequired in parent objectstringSet by the app, not typed by the staff member. Names this app installation's sign-in session, up to 100 characters. On first sign-in, build it from the device model the operating system reports plus a short random suffix, for example iPad - 3f9c2a71 (iOS UIDevice.current.model) or Samsung SM-S921B - a7164466 (Android Build.MANUFACTURER and Build.MODEL). Save it on the device and send the same value on every later sign-in, including after logout. Signing in again with the same staff account and device_name ends that installation's previous session; other devices stay signed in. Do not create a new name for each sign-in or send a shared value such as Android or iPhone. No hardware ID or IMEI is needed.Max length: 100Example: iPad - 3f9c2a71
JSON request example
{
    "username": "demo.staff",
    "password": "YOUR_STAFF_PASSWORD",
    "device_name": "iPad - 3f9c2a71"
}
cURL request
curl --request POST "$BASE_URL/login" \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --data '{
    "username": "demo.staff",
    "password": "YOUR_STAFF_PASSWORD",
    "device_name": "iPad - 3f9c2a71"
}'

Responses

201 Success

Success

application/json · Schema-generated illustration; optional fields and values vary

{
    "token": "string",
    "staff": {
        "id": 1,
        "name": "Alex Morgan",
        "role": "business_staff",
        "business_id": 0,
        "branch_id": 0,
        "permissions": [
            "string"
        ],
        "branches": [
            {
                "id": 1,
                "public_id": "string",
                "name": "Demo High Street"
            }
        ],
        "public_id": "string"
    }
}
Response schema
FieldTypeDescriptionConstraints & example
tokenoptional; see conditionsstringBearer credential returned at sign-in. Store securely and send in Authorization: Bearer YOUR_TOKEN; never put it in a URL.—
staffoptional; see conditionsobjectAuthenticated staff identity, assignment and bounded legacy branch list. Use GET /branches for complete discovery.—
staff.idoptional; see conditionsintegerLegacy integer ID of the authenticated staff account.—Example: 1
staff.nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Alex Morgan
staff.roleoptional; see conditionsstringAccount role/category, not a complete list of allowed actions. Legacy/shadow mode uses legacy role rules; enforced mode can delegate actions to staff or deny actions to managers. Branch confinement always applies.Allowed: business_staff, business_admin, super_admin
staff.business_idoptional; see conditionsintegerLegacy integer business ID associated with this account; it does not override the token tenant.—
staff.branch_idoptional; see conditionsinteger | nullLegacy integer branch ID from the authorized branch list, not its public ULID. Managers supply a branch when needed; assigned staff may only use their own branch.—
staff.permissionsoptional; see conditionsarray<string>Currently available operation aliases for the authenticated staff/branch context, not RBAC permission-code names. Reflect current business features and authorization mode. Use for UI hints rather than role checks; refresh after branch changes or 403. A reversal alias still requires exact target authorization and the server rechecks every mutation/replay.—
staff.branchesoptional; see conditionsarray<object>Bounded legacy list of authorized branches, limited to 100. Use the paginated GET /branches endpoint for discovery.—
staff.branches[].idoptional; see conditionsintegerLegacy integer branch ID. Use this value as branch_id on staff operations; do not pass the public_id ULID.—Example: 1
staff.branches[].public_idoptional; see conditionsstringStable public ULID for this object. Keep it distinct from any legacy integer id.Min length: 26 · Max length: 26
staff.branches[].nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Demo High Street
staff.public_idoptional; see conditionsstringStable public ULID for this object. Keep it distinct from any legacy integer id.Min length: 26 · Max length: 26
401 Unknown username or incorrect password. Disabled status is disclosed only after a correct password.

Unknown username or incorrect password. Disabled status is disclosed only after a correct password.

application/json · Illustrative example

invalid

{
    "error": {
        "code": "invalid_credentials",
        "message": "The username or password is incorrect."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
403 Correct credentials for disabled staff return staff_disabled. Internal API entitlement failures return api_access_unavailable.

Correct credentials for disabled staff return staff_disabled. Internal API entitlement failures return api_access_unavailable.

application/json · Illustrative example

disabled

{
    "error": {
        "code": "staff_disabled",
        "message": "This staff account is switched off."
    }
}

access

{
    "error": {
        "code": "api_access_unavailable",
        "message": "Internal app API access is not enabled."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
404 Not found

Not found — Check the public identifier and business context. Resources belonging to another tenant are not accessible.

application/json · Illustrative example

{
    "error": {
        "code": "not_found",
        "message": "Not found."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
409 Conflict

Conflict — Reuse a key only for an identical operation. For voucher_already_used, refresh reward status instead of redeeming again.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_conflict",
        "message": "This key was used with different request details."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
422 Validation or domain refusal

Validation or domain refusal — Correct the fields or handle the domain refusal identified by error.code. Do not retry an unchanged invalid request.

application/json · Illustrative example

{
    "error": {
        "code": "invalid",
        "message": "Some fields are invalid.",
        "fields": {
            "field_name": [
                "A validation message."
            ]
        }
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
429 Rate limited; Retry-After header

Rate limited; Retry-After header — Wait for the Retry-After header before retrying; retain the same operation key for a mutation.

Retry-After — Seconds until a retry is allowed

application/json · Illustrative example

{
    "error": {
        "code": "rate_limited",
        "message": "Too many requests; slow down.",
        "retry_after": 60
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
503 Authorization state unavailable

Authorization state unavailable — Retry after the Retry-After delay using bounded backoff. For a write, retain the same Idempotency-Key and identical payload; do not assume the operation succeeded or fall back to role-based access.

Retry-After — Seconds to wait before retrying authorization resolution; currently 3.

application/json · Illustrative example

{
    "error": {
        "code": "authorization_unavailable",
        "message": "Access settings are unavailable. Try again shortly."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
GET/me

Profile, permissions, branch and device

Read the signed-in staff member, assigned branch, device and permissions. Use the returned permissions to control available actions; the server enforces them on every operation. Managers without a branch receive branch: null and can still fetch config, discover branches and logout. Operation aliases in staff.permissions and /config.capabilities reflect current branch scope, business access and authorization mode. Do not infer allowed operations from role alone; enforced mode supports grants to staff and denies to managers. Refresh after a branch change or 403. Authorization lookup failures can return 503 authorization_unavailable; honor Retry-After.

Bearer token

Parameters

Name / locationTypeDescriptionConstraints & example
branch_idquery · optional; see conditionsintegerLegacy integer branch ID from the authorized branch list, not its public ULID. Managers supply a branch when needed; assigned staff may only use their own branch. Omit if no selection is needed; a manager may receive no active branch until one is selected.—Example: 12
cURL request
curl --request GET "$BASE_URL/me" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer YOUR_TOKEN'

Responses

200 Success

Success

application/json · Schema-generated illustration; optional fields and values vary

{
    "staff": {
        "id": 1,
        "name": "Alex Morgan",
        "role": "business_staff",
        "business_id": 0,
        "branch_id": 0,
        "permissions": [
            "string"
        ],
        "branches": [
            {
                "id": 1,
                "public_id": "string",
                "name": "Demo High Street"
            }
        ],
        "public_id": "string"
    },
    "branch": {
        "id": 0,
        "name": "string"
    },
    "device": "string"
}
Response schema
FieldTypeDescriptionConstraints & example
staffoptional; see conditionsobjectAuthenticated staff identity, assignment and bounded legacy branch list. Use GET /branches for complete discovery.—
staff.idoptional; see conditionsintegerLegacy integer ID of the authenticated staff account.—Example: 1
staff.nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Alex Morgan
staff.roleoptional; see conditionsstringAccount role/category, not a complete list of allowed actions. Legacy/shadow mode uses legacy role rules; enforced mode can delegate actions to staff or deny actions to managers. Branch confinement always applies.Allowed: business_staff, business_admin, super_admin
staff.business_idoptional; see conditionsintegerLegacy integer business ID associated with this account; it does not override the token tenant.—
staff.branch_idoptional; see conditionsinteger | nullLegacy integer branch ID from the authorized branch list, not its public ULID. Managers supply a branch when needed; assigned staff may only use their own branch.—
staff.permissionsoptional; see conditionsarray<string>Currently available operation aliases for the authenticated staff/branch context, not RBAC permission-code names. Reflect current business features and authorization mode. Use for UI hints rather than role checks; refresh after branch changes or 403. A reversal alias still requires exact target authorization and the server rechecks every mutation/replay.—
staff.branchesoptional; see conditionsarray<object>Bounded legacy list of authorized branches, limited to 100. Use the paginated GET /branches endpoint for discovery.—
staff.branches[].idoptional; see conditionsintegerLegacy integer branch ID. Use this value as branch_id on staff operations; do not pass the public_id ULID.—Example: 1
staff.branches[].public_idoptional; see conditionsstringStable public ULID for this object. Keep it distinct from any legacy integer id.Min length: 26 · Max length: 26
staff.branches[].nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Demo High Street
staff.public_idoptional; see conditionsstringStable public ULID for this object. Keep it distinct from any legacy integer id.Min length: 26 · Max length: 26
branchoptional; see conditionsobject | nullBranch details or branch display name associated with this result.—
branch.idoptional; see conditionsintegerIdentifier of this object. Preserve the documented type and use the ID rather than a display name.—
branch.nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—
deviceoptional; see conditionsstringInstallation details used to issue a device token and register optional push delivery.—
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check that the staff account is active.

application/json · Illustrative example

{
    "error": {
        "code": "unauthenticated",
        "message": "Authentication required."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
403 Access denied

Access denied — Check business entitlements, feature availability and staff permissions. Retrying unchanged will not restore access. Capability denial can return operation_unavailable; a missing exact reversal permission can return forbidden. Refresh /config for the current branch and stop the refused action. An unchanged request is not made valid by using another operation key.

application/json · Illustrative example

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
404 Not found

Not found — Check the public identifier and business context. Resources belonging to another tenant are not accessible.

application/json · Illustrative example

{
    "error": {
        "code": "not_found",
        "message": "Not found."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
409 Conflict

Conflict — Reuse a key only for an identical operation. For voucher_already_used, refresh reward status instead of redeeming again.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_conflict",
        "message": "This key was used with different request details."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
422 Validation or domain refusal

Validation or domain refusal — Correct the fields or handle the domain refusal identified by error.code. Do not retry an unchanged invalid request.

application/json · Illustrative example

{
    "error": {
        "code": "invalid",
        "message": "Some fields are invalid.",
        "fields": {
            "field_name": [
                "A validation message."
            ]
        }
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
429 Rate limited; Retry-After header

Rate limited; Retry-After header — Wait for the Retry-After header before retrying; retain the same operation key for a mutation.

Retry-After — Seconds until a retry is allowed

application/json · Illustrative example

{
    "error": {
        "code": "rate_limited",
        "message": "Too many requests; slow down.",
        "retry_after": 60
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
503 Authorization state unavailable

Authorization state unavailable — Retry after the Retry-After delay using bounded backoff. For a write, retain the same Idempotency-Key and identical payload; do not assume the operation succeeded or fall back to role-based access.

Retry-After — Seconds to wait before retrying authorization resolution; currently 3.

application/json · Illustrative example

{
    "error": {
        "code": "authorization_unavailable",
        "message": "Access settings are unavailable. Try again shortly."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
POST/logout

Revoke this staff token

Revoke the current staff token and remove it from secure device storage.

Bearer token
cURL request
curl --request POST "$BASE_URL/logout" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer YOUR_TOKEN'

Responses

200 Success

Success

application/json · Schema-generated illustration; optional fields and values vary

{
    "signed_out": false
}
Response schema
FieldTypeDescriptionConstraints & example
signed_outoptional; see conditionsbooleanTrue after the current device token has been revoked. Clear the locally stored token.—
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check that the staff account is active.

application/json · Illustrative example

{
    "error": {
        "code": "unauthenticated",
        "message": "Authentication required."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
403 Access denied

Access denied — Check business entitlements, feature availability and staff permissions. Retrying unchanged will not restore access. Capability denial can return operation_unavailable; a missing exact reversal permission can return forbidden. Refresh /config for the current branch and stop the refused action. An unchanged request is not made valid by using another operation key.

application/json · Illustrative example

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
404 Not found

Not found — Check the public identifier and business context. Resources belonging to another tenant are not accessible.

application/json · Illustrative example

{
    "error": {
        "code": "not_found",
        "message": "Not found."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
409 Conflict

Conflict — Reuse a key only for an identical operation. For voucher_already_used, refresh reward status instead of redeeming again.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_conflict",
        "message": "This key was used with different request details."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
422 Validation or domain refusal

Validation or domain refusal — Correct the fields or handle the domain refusal identified by error.code. Do not retry an unchanged invalid request.

application/json · Illustrative example

{
    "error": {
        "code": "invalid",
        "message": "Some fields are invalid.",
        "fields": {
            "field_name": [
                "A validation message."
            ]
        }
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
429 Rate limited; Retry-After header

Rate limited; Retry-After header — Wait for the Retry-After header before retrying; retain the same operation key for a mutation.

Retry-After — Seconds until a retry is allowed

application/json · Illustrative example

{
    "error": {
        "code": "rate_limited",
        "message": "Too many requests; slow down.",
        "retry_after": 60
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—

Customers

POST/customers/lookup

Exactly one identifier; phone/email never expose redemption codes

Identify a customer using exactly one supported identifier. Phone/email lookups are for earning and never expose member or redemption codes. A membership scan may initiate voucher creation; redemption requires the separate customer-supplied reward code. In enforced mode, the current branch requires loyalty.members.lookup. Legacy/shadow behavior and business feature requirements continue to apply. Capability aliases are UI hints; the server rechecks current authorization.

Bearer token

Request body application/json · required

Supply exactly one of member_code, phone or email. Phone/email lookups never expose membership or redemption codes.

Required markers apply within each parent object. Conditional requirements, such as choosing one identifier or a branch, are explained in the descriptions.

FieldTypeDescriptionConstraints & example
member_codeoptional; see conditionsstringCustomer membership QR/card value: exactly 11 digits, sent as a string. Supply exactly one of member_code, phone or email. Store lookup never returns reward codes, even after a membership scan.Pattern: ^\d{11}$Example: 48213906574
phoneoptional; see conditionsstringCustomer phone for an earning-only lookup. E.164 is recommended; local numbers use the business country. Supply exactly one lookup identifier.Max length: 30Example: +447700900123
emailoptional; see conditionsstringCustomer email for an earning-only lookup. Supply exactly one lookup identifier; email lookup never authorizes redemption.Format: email · Max length: 255Example: customer@example.com
branch_idoptional; see conditionsintegerLegacy integer branch ID from the authorized branch list, not its public ULID. Managers supply a branch when needed; assigned staff may only use their own branch.—Example: 12
JSON request example
{
    "member_code": "48213906574"
}
cURL request
curl --request POST "$BASE_URL/customers/lookup" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "member_code": "48213906574"
}'

Responses

200 Success

Success

application/json · Schema-generated illustration; optional fields and values vary

{
    "identified_by": "string",
    "customer": {
        "id": "01JB0000000000000000000001",
        "name": "Alex Morgan",
        "status": "string",
        "can_redeem": false,
        "phone": "+971501234567"
    },
    "membership": {
        "member_code": "48213906574",
        "balance": 10,
        "spendable": 10,
        "lifetime": 10,
        "points_label": "string",
        "card": {
            "number": 0,
            "points": 2,
            "milestones": [
                {
                    "points": 2
                }
            ]
        }
    },
    "rewards": [
        {
            "id": "01JB0000000000000000000001",
            "reward": {
                "id": "01JB0000000000000000000001",
                "name": "Free coffee",
                "description": "string",
                "type": "amount_voucher",
                "points_cost": 0,
                "value_label": "string",
                "value_minor": 1000,
                "percent": 0,
                "currency": "GBP",
                "claim": {
                    "eligible": true,
                    "reason_code": null,
                    "message": null,
                    "points_required": 20,
                    "available_actions": [
                        "claim"
                    ]
                }
            },
            "code": "DEMO7KQ2M",
            "status": "available",
            "used_at": "2026-10-24T10:00:00Z",
            "expires_at": "2026-10-24T10:00:00Z",
            "bill_reference": "DEMO-BILL-001",
            "claim": {
                "eligible": true,
                "reason_code": null,
                "message": null,
                "points_required": 20,
                "available_actions": [
                    "claim"
                ]
            },
            "valid_through_date": "string",
            "expiry_timezone": "Asia/Dubai"
        }
    ],
    "rewards_truncated": false
}
Response schema
FieldTypeDescriptionConstraints & example
identified_byoptional; see conditionsstringIdentifier used to resolve this customer: member code, phone or email. This affects which private codes may be returned.—
customeroptional; see conditionsobjectCustomer identity associated with this operation.—
customer.idoptional; see conditionsstringIdentifier of this object. Preserve the documented type and use the ID rather than a display name.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
customer.nameoptional; see conditionsstring | nullDisplay name of the object represented here; do not use it as a stable identifier.—Example: Alex Morgan
customer.statusoptional; see conditionsstringCurrent lifecycle state of this object. Use the documented values and endpoint rules.—
customer.can_redeemoptional; see conditionsbooleanWhether the customer account is active for redemption. Individual rewards still have their own status and eligibility checks.—
customer.phoneoptional; see conditionsstring | nullThe customer's verified phone in E.164, else the phone they registered with; null when none.—Example: +971501234567
membershipoptional; see conditionsobjectCustomer loyalty membership, balances and optional milestone progress.—
membership.member_codeoptional; see conditionsstringPrivate 11-digit membership code shown on the customer card or QR. Send as a string to preserve leading zeros; it is distinct from a reward code. Returned only after a member-code lookup; phone and email lookups omit it because it authorizes buying rewards with points.—Example: 48213906574
membership.balanceoptional; see conditionsintegerCurrent points balance. Use spendable, when provided, to decide how many points can be redeemed.—Example: 10
membership.spendableoptional; see conditionsintegerPoints currently available to spend, excluding points on hold.—Example: 10
membership.lifetimeoptional; see conditionsintegerTotal lifetime points earned by this membership.—Example: 10
membership.points_labeloptional; see conditionsstringBusiness-specific display label for points, such as points or beans.—
membership.cardoptional; see conditionsobject | nullLoyalty card branding or milestone progress, depending on this response.—
membership.card.numberoptional; see conditionsintegerCurrent milestone card/cycle number.—
membership.card.pointsoptional; see conditionsintegerPoints earned, moved or accumulated in this context; these are loyalty points, not a currency amount.—Example: 2
membership.card.milestonesoptional; see conditionsarray<object>Configured points thresholds for the milestone card.—
membership.card.milestones[].pointsoptional; see conditionsintegerPoints threshold on this card at which the milestone reward is unlocked.—Example: 2
rewardsoptional; see conditionsarray<object>Reward catalogue entries or customer reward grants, as defined by the item schema for this endpoint.—
rewards[].idoptional; see conditionsstringPublic ULID of this issued reward grant. This differs from the catalogue reward ID and its redemption code.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
rewards[].rewardoptional; see conditionsobjectCatalogue reward definition. Its public ID is used when purchasing a reward.—
rewards[].reward.idoptional; see conditionsstringPublic ULID of the catalogue reward; pass it as reward when purchasing.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
rewards[].reward.nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Free coffee
rewards[].reward.descriptionoptional; see conditionsstring | nullCustomer-facing description of this resource, when configured.—
rewards[].reward.typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: amount_voucher, free_item, percent_off
rewards[].reward.points_costoptional; see conditionsinteger | nullPoints required to purchase this catalogue reward.—
rewards[].reward.value_labeloptional; see conditionsstringFormatted reward value for display, such as £10.00, 15% off or Free item. Use numeric fields for calculations.—
rewards[].reward.value_minoroptional; see conditionsinteger | nullFixed reward value in integer currency minor units; for example 1000 GBP means £10.00.—Example: 1000
rewards[].reward.percentoptional; see conditionsnumber | nullPercentage discount for a percentage reward, for example 15 means 15%.—
rewards[].reward.currencyoptional; see conditionsstringISO 4217 currency code, for example GBP or AED. Monetary amounts are expressed in its minor units.—Example: GBP
rewards[].reward.claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
rewards[].reward.claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
rewards[].reward.claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
rewards[].reward.claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
rewards[].reward.claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
rewards[].reward.claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
rewards[].codeoptional; see conditionsstringPrivate redemption code. Absent on earned entitlements. Staff creation and lookup never return it; validation/redemption require a code already supplied by the customer.—Example: DEMO7KQ2M
rewards[].statusoptional; see conditionsstringCurrent lifecycle state of this object. Use the documented values and endpoint rules.Allowed: available, used, expired, revoked, earned
rewards[].used_atoptional; see conditionsstring | nullISO 8601 timestamp when the reward was used; null or omitted if it has not been used.Format: date-time
rewards[].expires_atoptional; see conditionsstring | nullExclusive voucher cutoff in ISO 8601 with timezone offset. Redemption is refused at or after this instant. Null means no expiry. Use valid_through_date for date labels.Format: date-time
rewards[].bill_referenceoptional; see conditionsstring | nullReceipt/bill reference connecting the operation to the business transaction.—Example: DEMO-BILL-001
rewards[].claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
rewards[].claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
rewards[].claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
rewards[].claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
rewards[].claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
rewards[].claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
rewards[].valid_through_dateoptional; see conditionsstring | nullLast inclusive valid calendar date in expiry_timezone. Null means no expiry.Format: date
rewards[].expiry_timezoneoptional; see conditionsstring | nullIANA business timezone captured when this reward was issued; unchanged by later country edits. Null means no expiry.—Example: Asia/Dubai
rewards_truncatedoptional; see conditionsbooleanTrue when the legacy lookup summary omits additional rewards. Use GET /customers/rewards to page through the full list.—
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check that the staff account is active.

application/json · Illustrative example

{
    "error": {
        "code": "unauthenticated",
        "message": "Authentication required."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
403 Access denied

Access denied — Check business entitlements, feature availability and staff permissions. Retrying unchanged will not restore access. Capability denial can return operation_unavailable; a missing exact reversal permission can return forbidden. Refresh /config for the current branch and stop the refused action. An unchanged request is not made valid by using another operation key.

application/json · Illustrative example

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
404 Not found

Not found — Check the public identifier and business context. Resources belonging to another tenant are not accessible.

application/json · Illustrative example

{
    "error": {
        "code": "not_found",
        "message": "Not found."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
409 Conflict

Conflict — Reuse a key only for an identical operation. For voucher_already_used, refresh reward status instead of redeeming again.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_conflict",
        "message": "This key was used with different request details."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
422 Validation or domain refusal

Validation or domain refusal — Correct the fields or handle the domain refusal identified by error.code. Do not retry an unchanged invalid request.

application/json · Illustrative example

{
    "error": {
        "code": "invalid",
        "message": "Some fields are invalid.",
        "fields": {
            "field_name": [
                "A validation message."
            ]
        }
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
429 Rate limited; Retry-After header

Rate limited; Retry-After header — Wait for the Retry-After header before retrying; retain the same operation key for a mutation.

Retry-After — Seconds until a retry is allowed

application/json · Illustrative example

{
    "error": {
        "code": "rate_limited",
        "message": "Too many requests; slow down.",
        "retry_after": 60
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
503 Authorization state unavailable

Authorization state unavailable — Retry after the Retry-After delay using bounded backoff. For a write, retain the same Idempotency-Key and identical payload; do not assume the operation succeeded or fall back to role-based access.

Retry-After — Seconds to wait before retrying authorization resolution; currently 3.

application/json · Illustrative example

{
    "error": {
        "code": "authorization_unavailable",
        "message": "Access settings are unavailable. Try again shortly."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
POST/customers

Find or create a claimable provisional phone account

Find or create a provisional customer by phone. Provisional accounts can earn rewards but must verify their identity before redeeming. In enforced mode, the current branch requires customers.create. Legacy/shadow behavior and business feature requirements continue to apply. Capability aliases are UI hints; the server rechecks current authorization.

Bearer token Idempotency key

Parameters

Name / locationTypeDescriptionConstraints & example
Idempotency-Keyheader · requiredstringClient-generated stable key for one intended mutation, at most 64 characters. Save it before sending; retry an identical request with the same key after a timeout. Changing the payload with that key returns 409 idempotency_conflict.Max length: 64Example: demo-operation-001

Request body application/json · required

Required markers apply within each parent object. Conditional requirements, such as choosing one identifier or a branch, are explained in the descriptions.

FieldTypeDescriptionConstraints & example
phonerequired in parent objectstringPhone number for the till customer. E.164 is recommended; local format uses the business country. Finds an existing account or creates a provisional one; redemption requires verification.Max length: 30Example: +447700900123
branch_idoptional; see conditionsintegerLegacy integer branch ID from the authorized branch list, not its public ULID. Managers supply a branch when needed; assigned staff may only use their own branch.—Example: 12
JSON request example
{
    "phone": "+447700900123"
}
cURL request
curl --request POST "$BASE_URL/customers" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'Idempotency-Key: UNIQUE_OPERATION_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "phone": "+447700900123"
}'

Responses

201 Success

Success

application/json · Schema-generated illustration; optional fields and values vary

{
    "identified_by": "string",
    "customer": {
        "id": "01JB0000000000000000000001",
        "name": "Alex Morgan",
        "status": "string",
        "can_redeem": false,
        "phone": "+971501234567"
    },
    "membership": {
        "member_code": "48213906574",
        "balance": 10,
        "spendable": 10,
        "lifetime": 10,
        "points_label": "string",
        "card": {
            "number": 0,
            "points": 2,
            "milestones": [
                {
                    "points": 2
                }
            ]
        }
    },
    "rewards": [
        {
            "id": "01JB0000000000000000000001",
            "reward": {
                "id": "01JB0000000000000000000001",
                "name": "Free coffee",
                "description": "string",
                "type": "amount_voucher",
                "points_cost": 0,
                "value_label": "string",
                "value_minor": 1000,
                "percent": 0,
                "currency": "GBP",
                "claim": {
                    "eligible": true,
                    "reason_code": null,
                    "message": null,
                    "points_required": 20,
                    "available_actions": [
                        "claim"
                    ]
                }
            },
            "code": "DEMO7KQ2M",
            "status": "available",
            "used_at": "2026-10-24T10:00:00Z",
            "expires_at": "2026-10-24T10:00:00Z",
            "bill_reference": "DEMO-BILL-001",
            "claim": {
                "eligible": true,
                "reason_code": null,
                "message": null,
                "points_required": 20,
                "available_actions": [
                    "claim"
                ]
            },
            "valid_through_date": "string",
            "expiry_timezone": "Asia/Dubai"
        }
    ],
    "rewards_truncated": false
}
Response schema
FieldTypeDescriptionConstraints & example
identified_byoptional; see conditionsstringIdentifier used to resolve this customer: member code, phone or email. This affects which private codes may be returned.—
customeroptional; see conditionsobjectCustomer identity associated with this operation.—
customer.idoptional; see conditionsstringIdentifier of this object. Preserve the documented type and use the ID rather than a display name.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
customer.nameoptional; see conditionsstring | nullDisplay name of the object represented here; do not use it as a stable identifier.—Example: Alex Morgan
customer.statusoptional; see conditionsstringCurrent lifecycle state of this object. Use the documented values and endpoint rules.—
customer.can_redeemoptional; see conditionsbooleanWhether the customer account is active for redemption. Individual rewards still have their own status and eligibility checks.—
customer.phoneoptional; see conditionsstring | nullThe customer's verified phone in E.164, else the phone they registered with; null when none.—Example: +971501234567
membershipoptional; see conditionsobjectCustomer loyalty membership, balances and optional milestone progress.—
membership.member_codeoptional; see conditionsstringPrivate 11-digit membership code shown on the customer card or QR. Send as a string to preserve leading zeros; it is distinct from a reward code. Returned only after a member-code lookup; phone and email lookups omit it because it authorizes buying rewards with points.—Example: 48213906574
membership.balanceoptional; see conditionsintegerCurrent points balance. Use spendable, when provided, to decide how many points can be redeemed.—Example: 10
membership.spendableoptional; see conditionsintegerPoints currently available to spend, excluding points on hold.—Example: 10
membership.lifetimeoptional; see conditionsintegerTotal lifetime points earned by this membership.—Example: 10
membership.points_labeloptional; see conditionsstringBusiness-specific display label for points, such as points or beans.—
membership.cardoptional; see conditionsobject | nullLoyalty card branding or milestone progress, depending on this response.—
membership.card.numberoptional; see conditionsintegerCurrent milestone card/cycle number.—
membership.card.pointsoptional; see conditionsintegerPoints earned, moved or accumulated in this context; these are loyalty points, not a currency amount.—Example: 2
membership.card.milestonesoptional; see conditionsarray<object>Configured points thresholds for the milestone card.—
membership.card.milestones[].pointsoptional; see conditionsintegerPoints threshold on this card at which the milestone reward is unlocked.—Example: 2
rewardsoptional; see conditionsarray<object>Reward catalogue entries or customer reward grants, as defined by the item schema for this endpoint.—
rewards[].idoptional; see conditionsstringPublic ULID of this issued reward grant. This differs from the catalogue reward ID and its redemption code.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
rewards[].rewardoptional; see conditionsobjectCatalogue reward definition. Its public ID is used when purchasing a reward.—
rewards[].reward.idoptional; see conditionsstringPublic ULID of the catalogue reward; pass it as reward when purchasing.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
rewards[].reward.nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Free coffee
rewards[].reward.descriptionoptional; see conditionsstring | nullCustomer-facing description of this resource, when configured.—
rewards[].reward.typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: amount_voucher, free_item, percent_off
rewards[].reward.points_costoptional; see conditionsinteger | nullPoints required to purchase this catalogue reward.—
rewards[].reward.value_labeloptional; see conditionsstringFormatted reward value for display, such as £10.00, 15% off or Free item. Use numeric fields for calculations.—
rewards[].reward.value_minoroptional; see conditionsinteger | nullFixed reward value in integer currency minor units; for example 1000 GBP means £10.00.—Example: 1000
rewards[].reward.percentoptional; see conditionsnumber | nullPercentage discount for a percentage reward, for example 15 means 15%.—
rewards[].reward.currencyoptional; see conditionsstringISO 4217 currency code, for example GBP or AED. Monetary amounts are expressed in its minor units.—Example: GBP
rewards[].reward.claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
rewards[].reward.claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
rewards[].reward.claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
rewards[].reward.claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
rewards[].reward.claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
rewards[].reward.claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
rewards[].codeoptional; see conditionsstringPrivate redemption code. Absent on earned entitlements. Staff creation and lookup never return it; validation/redemption require a code already supplied by the customer.—Example: DEMO7KQ2M
rewards[].statusoptional; see conditionsstringCurrent lifecycle state of this object. Use the documented values and endpoint rules.Allowed: available, used, expired, revoked, earned
rewards[].used_atoptional; see conditionsstring | nullISO 8601 timestamp when the reward was used; null or omitted if it has not been used.Format: date-time
rewards[].expires_atoptional; see conditionsstring | nullExclusive voucher cutoff in ISO 8601 with timezone offset. Redemption is refused at or after this instant. Null means no expiry. Use valid_through_date for date labels.Format: date-time
rewards[].bill_referenceoptional; see conditionsstring | nullReceipt/bill reference connecting the operation to the business transaction.—Example: DEMO-BILL-001
rewards[].claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
rewards[].claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
rewards[].claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
rewards[].claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
rewards[].claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
rewards[].claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
rewards[].valid_through_dateoptional; see conditionsstring | nullLast inclusive valid calendar date in expiry_timezone. Null means no expiry.Format: date
rewards[].expiry_timezoneoptional; see conditionsstring | nullIANA business timezone captured when this reward was issued; unchanged by later country edits. Null means no expiry.—Example: Asia/Dubai
rewards_truncatedoptional; see conditionsbooleanTrue when the legacy lookup summary omits additional rewards. Use GET /customers/rewards to page through the full list.—
400 Missing idempotency key

Missing idempotency key — Supply a stable key for the intended operation, then retry.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_key_required",
        "message": "An Idempotency-Key header is required."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check that the staff account is active.

application/json · Illustrative example

{
    "error": {
        "code": "unauthenticated",
        "message": "Authentication required."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
403 Access denied

Access denied — Check business entitlements, feature availability and staff permissions. Retrying unchanged will not restore access. Capability denial can return operation_unavailable; a missing exact reversal permission can return forbidden. Refresh /config for the current branch and stop the refused action. An unchanged request is not made valid by using another operation key.

application/json · Illustrative example

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
404 Not found

Not found — Check the public identifier and business context. Resources belonging to another tenant are not accessible.

application/json · Illustrative example

{
    "error": {
        "code": "not_found",
        "message": "Not found."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
409 Conflict

Conflict — Reuse a key only for an identical operation. For voucher_already_used, refresh reward status instead of redeeming again.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_conflict",
        "message": "This key was used with different request details."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
422 Validation or domain refusal

Validation or domain refusal — Correct the fields or handle the domain refusal identified by error.code. Do not retry an unchanged invalid request.

application/json · Illustrative example

{
    "error": {
        "code": "invalid",
        "message": "Some fields are invalid.",
        "fields": {
            "field_name": [
                "A validation message."
            ]
        }
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
429 Rate limited; Retry-After header

Rate limited; Retry-After header — Wait for the Retry-After header before retrying; retain the same operation key for a mutation.

Retry-After — Seconds until a retry is allowed

application/json · Illustrative example

{
    "error": {
        "code": "rate_limited",
        "message": "Too many requests; slow down.",
        "retry_after": 60
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
200 Success

Success

application/json · Schema-generated illustration; optional fields and values vary

{
    "identified_by": "string",
    "customer": {
        "id": "01JB0000000000000000000001",
        "name": "Alex Morgan",
        "status": "string",
        "can_redeem": false,
        "phone": "+971501234567"
    },
    "membership": {
        "member_code": "48213906574",
        "balance": 10,
        "spendable": 10,
        "lifetime": 10,
        "points_label": "string",
        "card": {
            "number": 0,
            "points": 2,
            "milestones": [
                {
                    "points": 2
                }
            ]
        }
    },
    "rewards": [
        {
            "id": "01JB0000000000000000000001",
            "reward": {
                "id": "01JB0000000000000000000001",
                "name": "Free coffee",
                "description": "string",
                "type": "amount_voucher",
                "points_cost": 0,
                "value_label": "string",
                "value_minor": 1000,
                "percent": 0,
                "currency": "GBP",
                "claim": {
                    "eligible": true,
                    "reason_code": null,
                    "message": null,
                    "points_required": 20,
                    "available_actions": [
                        "claim"
                    ]
                }
            },
            "code": "DEMO7KQ2M",
            "status": "available",
            "used_at": "2026-10-24T10:00:00Z",
            "expires_at": "2026-10-24T10:00:00Z",
            "bill_reference": "DEMO-BILL-001",
            "claim": {
                "eligible": true,
                "reason_code": null,
                "message": null,
                "points_required": 20,
                "available_actions": [
                    "claim"
                ]
            },
            "valid_through_date": "string",
            "expiry_timezone": "Asia/Dubai"
        }
    ],
    "rewards_truncated": false
}
Response schema
FieldTypeDescriptionConstraints & example
identified_byoptional; see conditionsstringIdentifier used to resolve this customer: member code, phone or email. This affects which private codes may be returned.—
customeroptional; see conditionsobjectCustomer identity associated with this operation.—
customer.idoptional; see conditionsstringIdentifier of this object. Preserve the documented type and use the ID rather than a display name.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
customer.nameoptional; see conditionsstring | nullDisplay name of the object represented here; do not use it as a stable identifier.—Example: Alex Morgan
customer.statusoptional; see conditionsstringCurrent lifecycle state of this object. Use the documented values and endpoint rules.—
customer.can_redeemoptional; see conditionsbooleanWhether the customer account is active for redemption. Individual rewards still have their own status and eligibility checks.—
customer.phoneoptional; see conditionsstring | nullThe customer's verified phone in E.164, else the phone they registered with; null when none.—Example: +971501234567
membershipoptional; see conditionsobjectCustomer loyalty membership, balances and optional milestone progress.—
membership.member_codeoptional; see conditionsstringPrivate 11-digit membership code shown on the customer card or QR. Send as a string to preserve leading zeros; it is distinct from a reward code. Returned only after a member-code lookup; phone and email lookups omit it because it authorizes buying rewards with points.—Example: 48213906574
membership.balanceoptional; see conditionsintegerCurrent points balance. Use spendable, when provided, to decide how many points can be redeemed.—Example: 10
membership.spendableoptional; see conditionsintegerPoints currently available to spend, excluding points on hold.—Example: 10
membership.lifetimeoptional; see conditionsintegerTotal lifetime points earned by this membership.—Example: 10
membership.points_labeloptional; see conditionsstringBusiness-specific display label for points, such as points or beans.—
membership.cardoptional; see conditionsobject | nullLoyalty card branding or milestone progress, depending on this response.—
membership.card.numberoptional; see conditionsintegerCurrent milestone card/cycle number.—
membership.card.pointsoptional; see conditionsintegerPoints earned, moved or accumulated in this context; these are loyalty points, not a currency amount.—Example: 2
membership.card.milestonesoptional; see conditionsarray<object>Configured points thresholds for the milestone card.—
membership.card.milestones[].pointsoptional; see conditionsintegerPoints threshold on this card at which the milestone reward is unlocked.—Example: 2
rewardsoptional; see conditionsarray<object>Reward catalogue entries or customer reward grants, as defined by the item schema for this endpoint.—
rewards[].idoptional; see conditionsstringPublic ULID of this issued reward grant. This differs from the catalogue reward ID and its redemption code.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
rewards[].rewardoptional; see conditionsobjectCatalogue reward definition. Its public ID is used when purchasing a reward.—
rewards[].reward.idoptional; see conditionsstringPublic ULID of the catalogue reward; pass it as reward when purchasing.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
rewards[].reward.nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Free coffee
rewards[].reward.descriptionoptional; see conditionsstring | nullCustomer-facing description of this resource, when configured.—
rewards[].reward.typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: amount_voucher, free_item, percent_off
rewards[].reward.points_costoptional; see conditionsinteger | nullPoints required to purchase this catalogue reward.—
rewards[].reward.value_labeloptional; see conditionsstringFormatted reward value for display, such as £10.00, 15% off or Free item. Use numeric fields for calculations.—
rewards[].reward.value_minoroptional; see conditionsinteger | nullFixed reward value in integer currency minor units; for example 1000 GBP means £10.00.—Example: 1000
rewards[].reward.percentoptional; see conditionsnumber | nullPercentage discount for a percentage reward, for example 15 means 15%.—
rewards[].reward.currencyoptional; see conditionsstringISO 4217 currency code, for example GBP or AED. Monetary amounts are expressed in its minor units.—Example: GBP
rewards[].reward.claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
rewards[].reward.claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
rewards[].reward.claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
rewards[].reward.claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
rewards[].reward.claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
rewards[].reward.claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
rewards[].codeoptional; see conditionsstringPrivate redemption code. Absent on earned entitlements. Staff creation and lookup never return it; validation/redemption require a code already supplied by the customer.—Example: DEMO7KQ2M
rewards[].statusoptional; see conditionsstringCurrent lifecycle state of this object. Use the documented values and endpoint rules.Allowed: available, used, expired, revoked, earned
rewards[].used_atoptional; see conditionsstring | nullISO 8601 timestamp when the reward was used; null or omitted if it has not been used.Format: date-time
rewards[].expires_atoptional; see conditionsstring | nullExclusive voucher cutoff in ISO 8601 with timezone offset. Redemption is refused at or after this instant. Null means no expiry. Use valid_through_date for date labels.Format: date-time
rewards[].bill_referenceoptional; see conditionsstring | nullReceipt/bill reference connecting the operation to the business transaction.—Example: DEMO-BILL-001
rewards[].claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
rewards[].claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
rewards[].claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
rewards[].claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
rewards[].claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
rewards[].claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
rewards[].valid_through_dateoptional; see conditionsstring | nullLast inclusive valid calendar date in expiry_timezone. Null means no expiry.Format: date
rewards[].expiry_timezoneoptional; see conditionsstring | nullIANA business timezone captured when this reward was issued; unchanged by later country edits. Null means no expiry.—Example: Asia/Dubai
rewards_truncatedoptional; see conditionsbooleanTrue when the legacy lookup summary omits additional rewards. Use GET /customers/rewards to page through the full list.—
503 Authorization state unavailable

Authorization state unavailable — Retry after the Retry-After delay using bounded backoff. For a write, retain the same Idempotency-Key and identical payload; do not assume the operation succeeded or fall back to role-based access.

Retry-After — Seconds to wait before retrying authorization resolution; currently 3.

application/json · Illustrative example

{
    "error": {
        "code": "authorization_unavailable",
        "message": "Access settings are unavailable. Try again shortly."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—

Earning

POST/purchases/preview

Calculate points without posting

Calculate the current policy’s points without posting a transaction. Use amount_paid_minor after discounts and excluded_minor for ineligible spend. A preview does not reserve or award points. In enforced mode, the current branch requires loyalty.points.preview. Legacy/shadow behavior and business feature requirements continue to apply. Capability aliases are UI hints; the server rechecks current authorization. The recorded source is always pos. Point holds and expiry start from server processing time, not a supplied backdated occurred_at. The amount requirement follows loyalty.bill_amount_required from GET /config; an optional omitted amount counts as zero spend. Bill reference remains optional regardless of the recording policy.

Bearer token

Request body application/json · required

Required markers apply within each parent object. Conditional requirements, such as choosing one identifier or a branch, are explained in the descriptions.

FieldTypeDescriptionConstraints & example
customerrequired in parent objectobjectLookup object containing exactly one member_code, phone or email. Phone can create a provisional till customer on a purchase; member_code/email must resolve an existing customer.—
customer.member_codeoptional; see conditionsstringCustomer membership QR/card value: exactly 11 digits, sent as a string. Supply exactly one of member_code, phone or email. Store lookup never returns reward codes, even after a membership scan.Pattern: ^\d{11}$Example: 48213906574
customer.phoneoptional; see conditionsstringCustomer phone for an earning-only lookup. E.164 is recommended; local numbers use the business country. Supply exactly one lookup identifier.—Example: +447700900123
customer.emailoptional; see conditionsstringCustomer email for an earning-only lookup. Supply exactly one lookup identifier; email lookup never authorizes redemption.Format: emailExample: customer@example.com
amount_paid_minoroptional; see conditionsinteger | nullBill amount paid in integer currency minor units before subtracting excluded_minor. For example 2550 GBP means £25.50. Required only when GET /config returns loyalty.bill_amount_required: true (the default). Otherwise it may be omitted or null and is evaluated as zero spend. A supplied value must satisfy this field's integer/minimum constraints; staff limits still apply.Minimum: 0Example: 5000
excluded_minoroptional; see conditionsintegerPortion of amount_paid_minor excluded from earning, in the same currency minor units. Omit for zero.Minimum: 0Example: 0
branch_idoptional; see conditionsintegerLegacy integer branch ID from the authorized branch list, not its public ULID. Managers supply a branch when needed; assigned staff may only use their own branch.—Example: 12
bill_referenceoptional; see conditionsstring | nullOptional receipt/bill reference used to build the preview context. Preview does not record a purchase or reserve points/rewards.Max length: 100
occurred_atoptional; see conditionsstringPurchase timestamp in ISO 8601 with timezone. If supplied it must be within the previous 24 hours and not in the future; omitted uses server time. Use server-current time or omit this field in executable examples. Client timestamps do not move the server processing time used for point holds, point expiry or current-day earning limits.Format: date-timeExample: 2026-10-25T10:00:00Z
channeloptional; see conditionsstringCompatibility input: pos and app are accepted, but the server always records these staff/POS purchase operations with source pos. Sending app cannot select app-only earning rules.Allowed: pos, appExample: pos
linesoptional; see conditionsarray<object>Optional bill lines for item rules (points per item bought, bill contains items). Each line needs a sku or a category. A refund by amount reduces quantities in proportion to what is still paid, rounded down. Repeating an Idempotency-Key with different lines is a conflict.Max items: 200
lines[].skuoptional; see conditionsstringItem code as the POS knows it; required when category is absent.Max length: 64Example: LATTE
lines[].categoryoptional; see conditionsstringItem category as the POS knows it; required when sku is absent.Max length: 64Example: coffee
lines[].quantityrequired in parent objectintegerUnits of this item on the bill.Minimum: 1 · Maximum: 10000Example: 2
lines[].amount_minoroptional; see conditionsintegerWhat this line cost, in currency minor units; informational.Minimum: 0Example: 900
JSON request example
{
    "customer": {
        "member_code": "48213906574"
    },
    "amount_paid_minor": 5000,
    "excluded_minor": 0
}
cURL request
curl --request POST "$BASE_URL/purchases/preview" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "customer": {
        "member_code": "48213906574"
    },
    "amount_paid_minor": 5000,
    "excluded_minor": 0
}'

Responses

200 Success

Success

application/json · Schema-generated illustration; optional fields and values vary

{
    "new_customer": false,
    "calculation": {
        "points": 2,
        "explanation": [
            "string"
        ]
    }
}
Response schema
FieldTypeDescriptionConstraints & example
new_customeroptional; see conditionsbooleanWhether the preview found no existing customer. Preview does not create a customer or write points.—
calculationoptional; see conditionsobjectCalculated points and human-readable rule notes.—
calculation.pointsoptional; see conditionsintegerPoints earned, moved or accumulated in this context; these are loyalty points, not a currency amount.—Example: 2
calculation.explanationoptional; see conditionsarray<string>Human-readable calculation notes explaining how the published loyalty rules produced the points result.—
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check that the staff account is active.

application/json · Illustrative example

{
    "error": {
        "code": "unauthenticated",
        "message": "Authentication required."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
403 Access denied

Access denied — Check business entitlements, feature availability and staff permissions. Retrying unchanged will not restore access. Capability denial can return operation_unavailable; a missing exact reversal permission can return forbidden. Refresh /config for the current branch and stop the refused action. An unchanged request is not made valid by using another operation key.

application/json · Illustrative example

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
404 Not found

Not found — Check the public identifier and business context. Resources belonging to another tenant are not accessible.

application/json · Illustrative example

{
    "error": {
        "code": "not_found",
        "message": "Not found."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
409 Conflict

Conflict — Reuse a key only for an identical operation. For voucher_already_used, refresh reward status instead of redeeming again.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_conflict",
        "message": "This key was used with different request details."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
422 Validation or domain refusal

Validation or domain refusal — Correct the fields or handle the domain refusal identified by error.code. Do not retry an unchanged invalid request.

application/json · Illustrative example

{
    "error": {
        "code": "invalid",
        "message": "Some fields are invalid.",
        "fields": {
            "field_name": [
                "A validation message."
            ]
        }
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
429 Rate limited; Retry-After header

Rate limited; Retry-After header — Wait for the Retry-After header before retrying; retain the same operation key for a mutation.

Retry-After — Seconds until a retry is allowed

application/json · Illustrative example

{
    "error": {
        "code": "rate_limited",
        "message": "Too many requests; slow down.",
        "retry_after": 60
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
503 Authorization state unavailable

Authorization state unavailable — Retry after the Retry-After delay using bounded backoff. For a write, retain the same Idempotency-Key and identical payload; do not assume the operation succeeded or fall back to role-based access.

Retry-After — Seconds to wait before retrying authorization resolution; currently 3.

application/json · Illustrative example

{
    "error": {
        "code": "authorization_unavailable",
        "message": "Access settings are unavailable. Try again shortly."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
POST/purchases

Post server-calculated points

Record a bill and award server-calculated points. Require a stable Idempotency-Key. Amount and bill reference requirements follow loyalty.bill_amount_required and loyalty.bill_reference_required from GET /config; both default to true. Optional omitted amounts count as zero spend and optional bill references remain null. Staff limits and business-local daily allowances apply; retries do not consume another award allowance. In enforced mode, the current branch requires loyalty.points.award. Legacy/shadow behavior and business feature requirements continue to apply. Capability aliases are UI hints; the server rechecks current authorization. Award admission is shared across this staff account’s API and dashboard till, across devices and branches in the business. New accepted bills count toward the daily allowance even when they earn zero points; identical retries do not count again. Limited accounts default to a 100000-minor-unit bill maximum and 200 awards per business-local day, configurable by the platform. Legacy/shadow managers are exempt from these staff limits; in enforced mode only loyalty.points.award_unlimited grants that exemption, independently of role. The separate platform-wide supported-purchase maximum still applies. Limits are enforced by the server and are not a separate capability alias. The recorded source is always pos. Point holds and expiry start from server processing time, not a supplied backdated occurred_at. Till-generated bill_reference strings of at most 100 characters are accepted. Keep the reference, payload and Idempotency-Key unchanged on retries. Purchase bill uniqueness is business + branch with no daily reset; voucher redemption references have no uniqueness constraint. A generated reference does not identify duplicate entry of a physical bill.

Bearer token Idempotency key

Parameters

Name / locationTypeDescriptionConstraints & example
Idempotency-Keyheader · requiredstringClient-generated stable key for one intended mutation, at most 64 characters. Save it before sending; retry an identical request with the same key after a timeout. Changing the payload with that key returns 409 idempotency_conflict.Max length: 64Example: demo-operation-001

Request body application/json · required

Required markers apply within each parent object. Conditional requirements, such as choosing one identifier or a branch, are explained in the descriptions.

FieldTypeDescriptionConstraints & example
customerrequired in parent objectobjectLookup object containing exactly one member_code, phone or email. Phone can create a provisional till customer on a purchase; member_code/email must resolve an existing customer.—
customer.member_codeoptional; see conditionsstringCustomer membership QR/card value: exactly 11 digits, sent as a string. Supply exactly one of member_code, phone or email. Store lookup never returns reward codes, even after a membership scan.Pattern: ^\d{11}$Example: 48213906574
customer.phoneoptional; see conditionsstringCustomer phone for an earning-only lookup. E.164 is recommended; local numbers use the business country. Supply exactly one lookup identifier.—Example: +447700900123
customer.emailoptional; see conditionsstringCustomer email for an earning-only lookup. Supply exactly one lookup identifier; email lookup never authorizes redemption.Format: emailExample: customer@example.com
amount_paid_minoroptional; see conditionsinteger | nullBill amount paid in integer currency minor units before subtracting excluded_minor. For example 2550 GBP means £25.50. Required only when GET /config returns loyalty.bill_amount_required: true (the default). Otherwise it may be omitted or null and is evaluated as zero spend. A supplied value must satisfy this field's integer/minimum constraints; staff limits still apply.Minimum: 0Example: 5000
excluded_minoroptional; see conditionsintegerPortion of amount_paid_minor excluded from earning, in the same currency minor units. Omit for zero.Minimum: 0Example: 0
branch_idoptional; see conditionsintegerLegacy integer branch ID from the authorized branch list, not its public ULID. Managers supply a branch when needed; assigned staff may only use their own branch.—Example: 12
bill_referenceoptional; see conditionsstring | nullStable receipt/bill reference for the earning purchase in this branch, up to 100 characters. Keep the same value on retries and later refunds; it is separate from Idempotency-Key. Required only when GET /config returns loyalty.bill_reference_required: true (the default); otherwise omission or null is accepted. A provided value must be a string of at most 100 characters. Without a reference, reference-based refunds and duplicate-bill detection are unavailable; idempotency remains required.Max length: 100Example: DEMO-BILL-001
occurred_atoptional; see conditionsstringPurchase timestamp in ISO 8601 with timezone. If supplied it must be within the previous 24 hours and not in the future; omitted uses server time. Use server-current time or omit this field in executable examples. Client timestamps do not move the server processing time used for point holds, point expiry or current-day earning limits.Format: date-timeExample: 2026-10-25T10:00:00Z
channeloptional; see conditionsstringCompatibility input: pos and app are accepted, but the server always records these staff/POS purchase operations with source pos. Sending app cannot select app-only earning rules.Allowed: pos, appExample: pos
linesoptional; see conditionsarray<object>Optional bill lines for item rules (points per item bought, bill contains items). Each line needs a sku or a category. A refund by amount reduces quantities in proportion to what is still paid, rounded down. Repeating an Idempotency-Key with different lines is a conflict.Max items: 200
lines[].skuoptional; see conditionsstringItem code as the POS knows it; required when category is absent.Max length: 64Example: LATTE
lines[].categoryoptional; see conditionsstringItem category as the POS knows it; required when sku is absent.Max length: 64Example: coffee
lines[].quantityrequired in parent objectintegerUnits of this item on the bill.Minimum: 1 · Maximum: 10000Example: 2
lines[].amount_minoroptional; see conditionsintegerWhat this line cost, in currency minor units; informational.Minimum: 0Example: 900
JSON request example
{
    "customer": {
        "member_code": "48213906574"
    },
    "amount_paid_minor": 5000,
    "excluded_minor": 0,
    "bill_reference": "DEMO-BILL-001"
}
cURL request
curl --request POST "$BASE_URL/purchases" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'Idempotency-Key: UNIQUE_OPERATION_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "customer": {
        "member_code": "48213906574"
    },
    "amount_paid_minor": 5000,
    "excluded_minor": 0,
    "bill_reference": "DEMO-BILL-001"
}'

Responses

201 Success

Success

application/json · Schema-generated illustration; optional fields and values vary

{
    "customer": {
        "id": "01JB0000000000000000000001",
        "status": "string",
        "new": false
    },
    "transaction_id": "01JB0000000000000000000001",
    "points": 2,
    "balance": 10,
    "replayed": false,
    "calculation": {
        "points": 2,
        "explanation": [
            "string"
        ]
    },
    "rewards_unlocked": [
        {
            "id": "01JB0000000000000000000001",
            "reward": {
                "id": "01JB0000000000000000000001",
                "name": "Free coffee",
                "description": "string",
                "type": "amount_voucher",
                "points_cost": 0,
                "value_label": "string",
                "value_minor": 1000,
                "percent": 0,
                "currency": "GBP",
                "claim": {
                    "eligible": true,
                    "reason_code": null,
                    "message": null,
                    "points_required": 20,
                    "available_actions": [
                        "claim"
                    ]
                }
            },
            "code": "DEMO7KQ2M",
            "status": "available",
            "used_at": "2026-10-24T10:00:00Z",
            "expires_at": "2026-10-24T10:00:00Z",
            "bill_reference": "DEMO-BILL-001",
            "claim": {
                "eligible": true,
                "reason_code": null,
                "message": null,
                "points_required": 20,
                "available_actions": [
                    "claim"
                ]
            },
            "valid_through_date": "string",
            "expiry_timezone": "Asia/Dubai"
        }
    ]
}
Response schema
FieldTypeDescriptionConstraints & example
customeroptional; see conditionsobjectCustomer identity associated with this operation.—
customer.idoptional; see conditionsstringIdentifier of this object. Preserve the documented type and use the ID rather than a display name.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
customer.statusoptional; see conditionsstringCurrent lifecycle state of this object. Use the documented values and endpoint rules.—
customer.newoptional; see conditionsbooleanWhether this operation created a provisional customer rather than finding an existing one.—
transaction_idoptional; see conditionsstring | nullPublic transaction/audit ULID returned by a write. Save it for reconciliation and supported reversal workflows; it may be null when no ledger entry was created.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
pointsoptional; see conditionsintegerPoints earned, moved or accumulated in this context; these are loyalty points, not a currency amount.—Example: 2
balanceoptional; see conditionsinteger | nullCurrent points balance. Use spendable, when provided, to decide how many points can be redeemed.—Example: 10
replayedoptional; see conditionsbooleanWhether the underlying operation reports a previously applied result rather than a new mutation. Always retain and reuse the original idempotency key.—
calculationoptional; see conditionsobjectCalculated points and human-readable rule notes.—
calculation.pointsoptional; see conditionsintegerPoints earned, moved or accumulated in this context; these are loyalty points, not a currency amount.—Example: 2
calculation.explanationoptional; see conditionsarray<string>Human-readable calculation notes explaining how the published loyalty rules produced the points result.—
rewards_unlockedoptional; see conditionsarray<object>Reward grants unlocked by this purchase. Codes are not exposed merely by a phone/email lookup.—
rewards_unlocked[].idoptional; see conditionsstringPublic ULID of this issued reward grant. This differs from the catalogue reward ID and its redemption code.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
rewards_unlocked[].rewardoptional; see conditionsobjectCatalogue reward definition. Its public ID is used when purchasing a reward.—
rewards_unlocked[].reward.idoptional; see conditionsstringPublic ULID of the catalogue reward; pass it as reward when purchasing.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
rewards_unlocked[].reward.nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Free coffee
rewards_unlocked[].reward.descriptionoptional; see conditionsstring | nullCustomer-facing description of this resource, when configured.—
rewards_unlocked[].reward.typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: amount_voucher, free_item, percent_off
rewards_unlocked[].reward.points_costoptional; see conditionsinteger | nullPoints required to purchase this catalogue reward.—
rewards_unlocked[].reward.value_labeloptional; see conditionsstringFormatted reward value for display, such as £10.00, 15% off or Free item. Use numeric fields for calculations.—
rewards_unlocked[].reward.value_minoroptional; see conditionsinteger | nullFixed reward value in integer currency minor units; for example 1000 GBP means £10.00.—Example: 1000
rewards_unlocked[].reward.percentoptional; see conditionsnumber | nullPercentage discount for a percentage reward, for example 15 means 15%.—
rewards_unlocked[].reward.currencyoptional; see conditionsstringISO 4217 currency code, for example GBP or AED. Monetary amounts are expressed in its minor units.—Example: GBP
rewards_unlocked[].reward.claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
rewards_unlocked[].reward.claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
rewards_unlocked[].reward.claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
rewards_unlocked[].reward.claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
rewards_unlocked[].reward.claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
rewards_unlocked[].reward.claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
rewards_unlocked[].codeoptional; see conditionsstringPrivate redemption code. Absent on earned entitlements. Staff creation and lookup never return it; validation/redemption require a code already supplied by the customer.—Example: DEMO7KQ2M
rewards_unlocked[].statusoptional; see conditionsstringCurrent lifecycle state of this object. Use the documented values and endpoint rules.Allowed: available, used, expired, revoked, earned
rewards_unlocked[].used_atoptional; see conditionsstring | nullISO 8601 timestamp when the reward was used; null or omitted if it has not been used.Format: date-time
rewards_unlocked[].expires_atoptional; see conditionsstring | nullExclusive voucher cutoff in ISO 8601 with timezone offset. Redemption is refused at or after this instant. Null means no expiry. Use valid_through_date for date labels.Format: date-time
rewards_unlocked[].bill_referenceoptional; see conditionsstring | nullReceipt/bill reference connecting the operation to the business transaction.—Example: DEMO-BILL-001
rewards_unlocked[].claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
rewards_unlocked[].claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
rewards_unlocked[].claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
rewards_unlocked[].claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
rewards_unlocked[].claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
rewards_unlocked[].claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
rewards_unlocked[].valid_through_dateoptional; see conditionsstring | nullLast inclusive valid calendar date in expiry_timezone. Null means no expiry.Format: date
rewards_unlocked[].expiry_timezoneoptional; see conditionsstring | nullIANA business timezone captured when this reward was issued; unchanged by later country edits. Null means no expiry.—Example: Asia/Dubai
400 Missing idempotency key

Missing idempotency key — Supply a stable key for the intended operation, then retry.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_key_required",
        "message": "An Idempotency-Key header is required."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check that the staff account is active.

application/json · Illustrative example

{
    "error": {
        "code": "unauthenticated",
        "message": "Authentication required."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
403 Access denied

Access denied — Check business entitlements, feature availability and staff permissions. Retrying unchanged will not restore access. Capability denial can return operation_unavailable; a missing exact reversal permission can return forbidden. Refresh /config for the current branch and stop the refused action. An unchanged request is not made valid by using another operation key.

application/json · Illustrative example

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
404 Not found

Not found — Check the public identifier and business context. Resources belonging to another tenant are not accessible.

application/json · Illustrative example

{
    "error": {
        "code": "not_found",
        "message": "Not found."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
409 Conflict

Conflict — Reuse a key only for an identical operation. For voucher_already_used, refresh reward status instead of redeeming again.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_conflict",
        "message": "This key was used with different request details."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
422 Validation or domain refusal

Validation or domain refusal — Correct the fields or handle the domain refusal identified by error.code. Do not retry an unchanged invalid request.

application/json · Illustrative example

{
    "error": {
        "code": "invalid",
        "message": "Some fields are invalid.",
        "fields": {
            "field_name": [
                "A validation message."
            ]
        }
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
429 Rate limited; Retry-After header

Rate limited; Retry-After header — Wait for the Retry-After header before retrying; retain the same operation key for a mutation.

Retry-After — Seconds until a retry is allowed

application/json · Illustrative example

{
    "error": {
        "code": "rate_limited",
        "message": "Too many requests; slow down.",
        "retry_after": 60
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
200 Success

Success

application/json · Schema-generated illustration; optional fields and values vary

{
    "customer": {
        "id": "01JB0000000000000000000001",
        "status": "string",
        "new": false
    },
    "transaction_id": "01JB0000000000000000000001",
    "points": 2,
    "balance": 10,
    "replayed": false,
    "calculation": {
        "points": 2,
        "explanation": [
            "string"
        ]
    },
    "rewards_unlocked": [
        {
            "id": "01JB0000000000000000000001",
            "reward": {
                "id": "01JB0000000000000000000001",
                "name": "Free coffee",
                "description": "string",
                "type": "amount_voucher",
                "points_cost": 0,
                "value_label": "string",
                "value_minor": 1000,
                "percent": 0,
                "currency": "GBP",
                "claim": {
                    "eligible": true,
                    "reason_code": null,
                    "message": null,
                    "points_required": 20,
                    "available_actions": [
                        "claim"
                    ]
                }
            },
            "code": "DEMO7KQ2M",
            "status": "available",
            "used_at": "2026-10-24T10:00:00Z",
            "expires_at": "2026-10-24T10:00:00Z",
            "bill_reference": "DEMO-BILL-001",
            "claim": {
                "eligible": true,
                "reason_code": null,
                "message": null,
                "points_required": 20,
                "available_actions": [
                    "claim"
                ]
            },
            "valid_through_date": "string",
            "expiry_timezone": "Asia/Dubai"
        }
    ]
}
Response schema
FieldTypeDescriptionConstraints & example
customeroptional; see conditionsobjectCustomer identity associated with this operation.—
customer.idoptional; see conditionsstringIdentifier of this object. Preserve the documented type and use the ID rather than a display name.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
customer.statusoptional; see conditionsstringCurrent lifecycle state of this object. Use the documented values and endpoint rules.—
customer.newoptional; see conditionsbooleanWhether this operation created a provisional customer rather than finding an existing one.—
transaction_idoptional; see conditionsstring | nullPublic transaction/audit ULID returned by a write. Save it for reconciliation and supported reversal workflows; it may be null when no ledger entry was created.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
pointsoptional; see conditionsintegerPoints earned, moved or accumulated in this context; these are loyalty points, not a currency amount.—Example: 2
balanceoptional; see conditionsinteger | nullCurrent points balance. Use spendable, when provided, to decide how many points can be redeemed.—Example: 10
replayedoptional; see conditionsbooleanWhether the underlying operation reports a previously applied result rather than a new mutation. Always retain and reuse the original idempotency key.—
calculationoptional; see conditionsobjectCalculated points and human-readable rule notes.—
calculation.pointsoptional; see conditionsintegerPoints earned, moved or accumulated in this context; these are loyalty points, not a currency amount.—Example: 2
calculation.explanationoptional; see conditionsarray<string>Human-readable calculation notes explaining how the published loyalty rules produced the points result.—
rewards_unlockedoptional; see conditionsarray<object>Reward grants unlocked by this purchase. Codes are not exposed merely by a phone/email lookup.—
rewards_unlocked[].idoptional; see conditionsstringPublic ULID of this issued reward grant. This differs from the catalogue reward ID and its redemption code.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
rewards_unlocked[].rewardoptional; see conditionsobjectCatalogue reward definition. Its public ID is used when purchasing a reward.—
rewards_unlocked[].reward.idoptional; see conditionsstringPublic ULID of the catalogue reward; pass it as reward when purchasing.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
rewards_unlocked[].reward.nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Free coffee
rewards_unlocked[].reward.descriptionoptional; see conditionsstring | nullCustomer-facing description of this resource, when configured.—
rewards_unlocked[].reward.typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: amount_voucher, free_item, percent_off
rewards_unlocked[].reward.points_costoptional; see conditionsinteger | nullPoints required to purchase this catalogue reward.—
rewards_unlocked[].reward.value_labeloptional; see conditionsstringFormatted reward value for display, such as £10.00, 15% off or Free item. Use numeric fields for calculations.—
rewards_unlocked[].reward.value_minoroptional; see conditionsinteger | nullFixed reward value in integer currency minor units; for example 1000 GBP means £10.00.—Example: 1000
rewards_unlocked[].reward.percentoptional; see conditionsnumber | nullPercentage discount for a percentage reward, for example 15 means 15%.—
rewards_unlocked[].reward.currencyoptional; see conditionsstringISO 4217 currency code, for example GBP or AED. Monetary amounts are expressed in its minor units.—Example: GBP
rewards_unlocked[].reward.claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
rewards_unlocked[].reward.claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
rewards_unlocked[].reward.claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
rewards_unlocked[].reward.claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
rewards_unlocked[].reward.claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
rewards_unlocked[].reward.claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
rewards_unlocked[].codeoptional; see conditionsstringPrivate redemption code. Absent on earned entitlements. Staff creation and lookup never return it; validation/redemption require a code already supplied by the customer.—Example: DEMO7KQ2M
rewards_unlocked[].statusoptional; see conditionsstringCurrent lifecycle state of this object. Use the documented values and endpoint rules.Allowed: available, used, expired, revoked, earned
rewards_unlocked[].used_atoptional; see conditionsstring | nullISO 8601 timestamp when the reward was used; null or omitted if it has not been used.Format: date-time
rewards_unlocked[].expires_atoptional; see conditionsstring | nullExclusive voucher cutoff in ISO 8601 with timezone offset. Redemption is refused at or after this instant. Null means no expiry. Use valid_through_date for date labels.Format: date-time
rewards_unlocked[].bill_referenceoptional; see conditionsstring | nullReceipt/bill reference connecting the operation to the business transaction.—Example: DEMO-BILL-001
rewards_unlocked[].claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
rewards_unlocked[].claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
rewards_unlocked[].claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
rewards_unlocked[].claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
rewards_unlocked[].claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
rewards_unlocked[].claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
rewards_unlocked[].valid_through_dateoptional; see conditionsstring | nullLast inclusive valid calendar date in expiry_timezone. Null means no expiry.Format: date
rewards_unlocked[].expiry_timezoneoptional; see conditionsstring | nullIANA business timezone captured when this reward was issued; unchanged by later country edits. Null means no expiry.—Example: Asia/Dubai
503 Authorization state unavailable

Authorization state unavailable — Retry after the Retry-After delay using bounded backoff. For a write, retain the same Idempotency-Key and identical payload; do not assume the operation succeeded or fall back to role-based access.

Retry-After — Seconds to wait before retrying authorization resolution; currently 3.

application/json · Illustrative example

{
    "error": {
        "code": "authorization_unavailable",
        "message": "Access settings are unavailable. Try again shortly."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
POST/points/award

Record a bill and calculate points; amount is in minor units

Record a bill using the mobile award alias. The amount field is an integer in currency minor units, not a client-selected points total. Uses the same safeguards and limits as /purchases. In enforced mode, the current branch requires loyalty.points.award. Legacy/shadow behavior and business feature requirements continue to apply. Capability aliases are UI hints; the server rechecks current authorization. Award admission is shared across this staff account’s API and dashboard till, across devices and branches in the business. New accepted bills count toward the daily allowance even when they earn zero points; identical retries do not count again. Limited accounts default to a 100000-minor-unit bill maximum and 200 awards per business-local day, configurable by the platform. Legacy/shadow managers are exempt from these staff limits; in enforced mode only loyalty.points.award_unlimited grants that exemption, independently of role. The separate platform-wide supported-purchase maximum still applies. Limits are enforced by the server and are not a separate capability alias. The business controls whether amount and bill_reference are mandatory through Loyalty Settings. Both default to required. Discover the award form requirements through GET /config at loyalty.bill_amount_required and loyalty.bill_reference_required. Missing required fields return 422 invalid with messages in error.fields. When a field is optional, omission, null or an empty value is accepted. Omitted amount counts as zero spend: visit rules, eligible fixed bonuses and zero-spend bands can still earn; spend-based points and minimum-spend conditions use zero. A supplied amount must remain a positive integer. No bill reference is invented. The server reads the current saved policy for every new submission; request flags cannot override it. Existing successful retries retain their original result without awarding again.

Bearer token Idempotency key

Parameters

Name / locationTypeDescriptionConstraints & example
Idempotency-Keyheader · requiredstringClient-generated stable key for one intended mutation, at most 64 characters. Save it before sending; retry an identical request with the same key after a timeout. Changing the payload with that key returns 409 idempotency_conflict.Max length: 64Example: demo-operation-001

Request body application/json · required

Required markers apply within each parent object. Conditional requirements, such as choosing one identifier or a branch, are explained in the descriptions.

FieldTypeDescriptionConstraints & example
customerrequired in parent objectobjectLookup object containing exactly one member_code, phone or email. Phone can create a provisional till customer on a purchase; member_code/email must resolve an existing customer.—
customer.member_codeoptional; see conditionsstringCustomer membership QR/card value: exactly 11 digits, sent as a string. Supply exactly one of member_code, phone or email. Store lookup never returns reward codes, even after a membership scan.Pattern: ^\d{11}$Example: 48213906574
customer.phoneoptional; see conditionsstringCustomer phone for an earning-only lookup. E.164 is recommended; local numbers use the business country. Supply exactly one lookup identifier.—Example: +447700900123
customer.emailoptional; see conditionsstringCustomer email for an earning-only lookup. Supply exactly one lookup identifier; email lookup never authorizes redemption.Format: emailExample: customer@example.com
amountoptional; see conditionsinteger | nullBill value in integer currency minor units, not points to grant. The loyalty rules calculate points from this value; staff award limits also apply. Required only when GET /config returns loyalty.bill_amount_required: true (the default). Otherwise it may be omitted or null and is evaluated as zero spend. A supplied value must satisfy this field's integer/minimum constraints; staff limits still apply.Minimum: 1Example: 5000
branch_idoptional; see conditionsintegerLegacy integer branch ID from the authorized branch list, not its public ULID. Managers supply a branch when needed; assigned staff may only use their own branch.—Example: 12
bill_referenceoptional; see conditionsstring | nullStable receipt/bill reference for the earning purchase in this branch, up to 100 characters. Keep the same value on retries and later refunds; it is separate from Idempotency-Key. Required only when GET /config returns loyalty.bill_reference_required: true (the default); otherwise omission or null is accepted. A provided value must be a string of at most 100 characters. Without a reference, reference-based refunds and duplicate-bill detection are unavailable; idempotency remains required.Max length: 100Example: DEMO-BILL-001
linesoptional; see conditionsarray<object>Optional bill lines for item rules (points per item bought, bill contains items). Each line needs a sku or a category. A refund by amount reduces quantities in proportion to what is still paid, rounded down. Repeating an Idempotency-Key with different lines is a conflict.Max items: 200
lines[].skuoptional; see conditionsstringItem code as the POS knows it; required when category is absent.Max length: 64Example: LATTE
lines[].categoryoptional; see conditionsstringItem category as the POS knows it; required when sku is absent.Max length: 64Example: coffee
lines[].quantityrequired in parent objectintegerUnits of this item on the bill.Minimum: 1 · Maximum: 10000Example: 2
lines[].amount_minoroptional; see conditionsintegerWhat this line cost, in currency minor units; informational.Minimum: 0Example: 900
JSON request example
{
    "customer": {
        "member_code": "48213906574"
    },
    "amount": 5000,
    "bill_reference": "DEMO-BILL-001"
}
cURL request
curl --request POST "$BASE_URL/points/award" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'Idempotency-Key: UNIQUE_OPERATION_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "customer": {
        "member_code": "48213906574"
    },
    "amount": 5000,
    "bill_reference": "DEMO-BILL-001"
}'

Responses

201 Success

Success

application/json · Schema-generated illustration; optional fields and values vary

{
    "customer": {
        "id": "01JB0000000000000000000001",
        "status": "string",
        "new": false
    },
    "transaction_id": "01JB0000000000000000000001",
    "points": 2,
    "balance": 10,
    "replayed": false,
    "calculation": {
        "points": 2,
        "explanation": [
            "string"
        ]
    },
    "rewards_unlocked": [
        {
            "id": "01JB0000000000000000000001",
            "reward": {
                "id": "01JB0000000000000000000001",
                "name": "Free coffee",
                "description": "string",
                "type": "amount_voucher",
                "points_cost": 0,
                "value_label": "string",
                "value_minor": 1000,
                "percent": 0,
                "currency": "GBP",
                "claim": {
                    "eligible": true,
                    "reason_code": null,
                    "message": null,
                    "points_required": 20,
                    "available_actions": [
                        "claim"
                    ]
                }
            },
            "code": "DEMO7KQ2M",
            "status": "available",
            "used_at": "2026-10-24T10:00:00Z",
            "expires_at": "2026-10-24T10:00:00Z",
            "bill_reference": "DEMO-BILL-001",
            "claim": {
                "eligible": true,
                "reason_code": null,
                "message": null,
                "points_required": 20,
                "available_actions": [
                    "claim"
                ]
            },
            "valid_through_date": "string",
            "expiry_timezone": "Asia/Dubai"
        }
    ]
}
Response schema
FieldTypeDescriptionConstraints & example
customeroptional; see conditionsobjectCustomer identity associated with this operation.—
customer.idoptional; see conditionsstringIdentifier of this object. Preserve the documented type and use the ID rather than a display name.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
customer.statusoptional; see conditionsstringCurrent lifecycle state of this object. Use the documented values and endpoint rules.—
customer.newoptional; see conditionsbooleanWhether this operation created a provisional customer rather than finding an existing one.—
transaction_idoptional; see conditionsstring | nullPublic transaction/audit ULID returned by a write. Save it for reconciliation and supported reversal workflows; it may be null when no ledger entry was created.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
pointsoptional; see conditionsintegerPoints earned, moved or accumulated in this context; these are loyalty points, not a currency amount.—Example: 2
balanceoptional; see conditionsinteger | nullCurrent points balance. Use spendable, when provided, to decide how many points can be redeemed.—Example: 10
replayedoptional; see conditionsbooleanWhether the underlying operation reports a previously applied result rather than a new mutation. Always retain and reuse the original idempotency key.—
calculationoptional; see conditionsobjectCalculated points and human-readable rule notes.—
calculation.pointsoptional; see conditionsintegerPoints earned, moved or accumulated in this context; these are loyalty points, not a currency amount.—Example: 2
calculation.explanationoptional; see conditionsarray<string>Human-readable calculation notes explaining how the published loyalty rules produced the points result.—
rewards_unlockedoptional; see conditionsarray<object>Reward grants unlocked by this purchase. Codes are not exposed merely by a phone/email lookup.—
rewards_unlocked[].idoptional; see conditionsstringPublic ULID of this issued reward grant. This differs from the catalogue reward ID and its redemption code.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
rewards_unlocked[].rewardoptional; see conditionsobjectCatalogue reward definition. Its public ID is used when purchasing a reward.—
rewards_unlocked[].reward.idoptional; see conditionsstringPublic ULID of the catalogue reward; pass it as reward when purchasing.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
rewards_unlocked[].reward.nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Free coffee
rewards_unlocked[].reward.descriptionoptional; see conditionsstring | nullCustomer-facing description of this resource, when configured.—
rewards_unlocked[].reward.typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: amount_voucher, free_item, percent_off
rewards_unlocked[].reward.points_costoptional; see conditionsinteger | nullPoints required to purchase this catalogue reward.—
rewards_unlocked[].reward.value_labeloptional; see conditionsstringFormatted reward value for display, such as £10.00, 15% off or Free item. Use numeric fields for calculations.—
rewards_unlocked[].reward.value_minoroptional; see conditionsinteger | nullFixed reward value in integer currency minor units; for example 1000 GBP means £10.00.—Example: 1000
rewards_unlocked[].reward.percentoptional; see conditionsnumber | nullPercentage discount for a percentage reward, for example 15 means 15%.—
rewards_unlocked[].reward.currencyoptional; see conditionsstringISO 4217 currency code, for example GBP or AED. Monetary amounts are expressed in its minor units.—Example: GBP
rewards_unlocked[].reward.claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
rewards_unlocked[].reward.claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
rewards_unlocked[].reward.claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
rewards_unlocked[].reward.claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
rewards_unlocked[].reward.claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
rewards_unlocked[].reward.claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
rewards_unlocked[].codeoptional; see conditionsstringPrivate redemption code. Absent on earned entitlements. Staff creation and lookup never return it; validation/redemption require a code already supplied by the customer.—Example: DEMO7KQ2M
rewards_unlocked[].statusoptional; see conditionsstringCurrent lifecycle state of this object. Use the documented values and endpoint rules.Allowed: available, used, expired, revoked, earned
rewards_unlocked[].used_atoptional; see conditionsstring | nullISO 8601 timestamp when the reward was used; null or omitted if it has not been used.Format: date-time
rewards_unlocked[].expires_atoptional; see conditionsstring | nullExclusive voucher cutoff in ISO 8601 with timezone offset. Redemption is refused at or after this instant. Null means no expiry. Use valid_through_date for date labels.Format: date-time
rewards_unlocked[].bill_referenceoptional; see conditionsstring | nullReceipt/bill reference connecting the operation to the business transaction.—Example: DEMO-BILL-001
rewards_unlocked[].claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
rewards_unlocked[].claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
rewards_unlocked[].claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
rewards_unlocked[].claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
rewards_unlocked[].claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
rewards_unlocked[].claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
rewards_unlocked[].valid_through_dateoptional; see conditionsstring | nullLast inclusive valid calendar date in expiry_timezone. Null means no expiry.Format: date
rewards_unlocked[].expiry_timezoneoptional; see conditionsstring | nullIANA business timezone captured when this reward was issued; unchanged by later country edits. Null means no expiry.—Example: Asia/Dubai
400 Missing idempotency key

Missing idempotency key — Supply a stable key for the intended operation, then retry.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_key_required",
        "message": "An Idempotency-Key header is required."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check that the staff account is active.

application/json · Illustrative example

{
    "error": {
        "code": "unauthenticated",
        "message": "Authentication required."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
403 Access denied

Access denied — Check business entitlements, feature availability and staff permissions. Retrying unchanged will not restore access. Capability denial can return operation_unavailable; a missing exact reversal permission can return forbidden. Refresh /config for the current branch and stop the refused action. An unchanged request is not made valid by using another operation key.

application/json · Illustrative example

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
404 Not found

Not found — Check the public identifier and business context. Resources belonging to another tenant are not accessible.

application/json · Illustrative example

{
    "error": {
        "code": "not_found",
        "message": "Not found."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
409 Conflict

Conflict — Reuse a key only for an identical operation. For voucher_already_used, refresh reward status instead of redeeming again.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_conflict",
        "message": "This key was used with different request details."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
422 Validation or domain refusal

Validation or domain refusal — Correct the fields or handle the domain refusal identified by error.code. Do not retry an unchanged invalid request.

application/json · Illustrative example

Required bill fields missing

{
    "error": {
        "code": "invalid",
        "message": "Some fields are invalid.",
        "fields": {
            "amount": [
                "The amount field is required."
            ],
            "bill_reference": [
                "The bill reference field is required."
            ]
        }
    }
}

Bill amount must be positive

{
    "error": {
        "code": "invalid",
        "message": "Some fields are invalid.",
        "fields": {
            "amount": [
                "The amount field must be at least 1."
            ]
        }
    }
}

Bill exceeds staff award limit

{
    "error": {
        "code": "award_limit_exceeded",
        "message": "This bill exceeds the staff award limit."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
429 Rate limited; Retry-After header

Rate limited; Retry-After header — Wait for the Retry-After header before retrying; retain the same operation key for a mutation.

Retry-After — Seconds until a retry is allowed

application/json · Illustrative example

{
    "error": {
        "code": "rate_limited",
        "message": "Too many requests; slow down.",
        "retry_after": 60
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
200 Success

Success

application/json · Schema-generated illustration; optional fields and values vary

{
    "customer": {
        "id": "01JB0000000000000000000001",
        "status": "string",
        "new": false
    },
    "transaction_id": "01JB0000000000000000000001",
    "points": 2,
    "balance": 10,
    "replayed": false,
    "calculation": {
        "points": 2,
        "explanation": [
            "string"
        ]
    },
    "rewards_unlocked": [
        {
            "id": "01JB0000000000000000000001",
            "reward": {
                "id": "01JB0000000000000000000001",
                "name": "Free coffee",
                "description": "string",
                "type": "amount_voucher",
                "points_cost": 0,
                "value_label": "string",
                "value_minor": 1000,
                "percent": 0,
                "currency": "GBP",
                "claim": {
                    "eligible": true,
                    "reason_code": null,
                    "message": null,
                    "points_required": 20,
                    "available_actions": [
                        "claim"
                    ]
                }
            },
            "code": "DEMO7KQ2M",
            "status": "available",
            "used_at": "2026-10-24T10:00:00Z",
            "expires_at": "2026-10-24T10:00:00Z",
            "bill_reference": "DEMO-BILL-001",
            "claim": {
                "eligible": true,
                "reason_code": null,
                "message": null,
                "points_required": 20,
                "available_actions": [
                    "claim"
                ]
            },
            "valid_through_date": "string",
            "expiry_timezone": "Asia/Dubai"
        }
    ]
}
Response schema
FieldTypeDescriptionConstraints & example
customeroptional; see conditionsobjectCustomer identity associated with this operation.—
customer.idoptional; see conditionsstringIdentifier of this object. Preserve the documented type and use the ID rather than a display name.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
customer.statusoptional; see conditionsstringCurrent lifecycle state of this object. Use the documented values and endpoint rules.—
customer.newoptional; see conditionsbooleanWhether this operation created a provisional customer rather than finding an existing one.—
transaction_idoptional; see conditionsstring | nullPublic transaction/audit ULID returned by a write. Save it for reconciliation and supported reversal workflows; it may be null when no ledger entry was created.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
pointsoptional; see conditionsintegerPoints earned, moved or accumulated in this context; these are loyalty points, not a currency amount.—Example: 2
balanceoptional; see conditionsinteger | nullCurrent points balance. Use spendable, when provided, to decide how many points can be redeemed.—Example: 10
replayedoptional; see conditionsbooleanWhether the underlying operation reports a previously applied result rather than a new mutation. Always retain and reuse the original idempotency key.—
calculationoptional; see conditionsobjectCalculated points and human-readable rule notes.—
calculation.pointsoptional; see conditionsintegerPoints earned, moved or accumulated in this context; these are loyalty points, not a currency amount.—Example: 2
calculation.explanationoptional; see conditionsarray<string>Human-readable calculation notes explaining how the published loyalty rules produced the points result.—
rewards_unlockedoptional; see conditionsarray<object>Reward grants unlocked by this purchase. Codes are not exposed merely by a phone/email lookup.—
rewards_unlocked[].idoptional; see conditionsstringPublic ULID of this issued reward grant. This differs from the catalogue reward ID and its redemption code.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
rewards_unlocked[].rewardoptional; see conditionsobjectCatalogue reward definition. Its public ID is used when purchasing a reward.—
rewards_unlocked[].reward.idoptional; see conditionsstringPublic ULID of the catalogue reward; pass it as reward when purchasing.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
rewards_unlocked[].reward.nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Free coffee
rewards_unlocked[].reward.descriptionoptional; see conditionsstring | nullCustomer-facing description of this resource, when configured.—
rewards_unlocked[].reward.typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: amount_voucher, free_item, percent_off
rewards_unlocked[].reward.points_costoptional; see conditionsinteger | nullPoints required to purchase this catalogue reward.—
rewards_unlocked[].reward.value_labeloptional; see conditionsstringFormatted reward value for display, such as £10.00, 15% off or Free item. Use numeric fields for calculations.—
rewards_unlocked[].reward.value_minoroptional; see conditionsinteger | nullFixed reward value in integer currency minor units; for example 1000 GBP means £10.00.—Example: 1000
rewards_unlocked[].reward.percentoptional; see conditionsnumber | nullPercentage discount for a percentage reward, for example 15 means 15%.—
rewards_unlocked[].reward.currencyoptional; see conditionsstringISO 4217 currency code, for example GBP or AED. Monetary amounts are expressed in its minor units.—Example: GBP
rewards_unlocked[].reward.claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
rewards_unlocked[].reward.claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
rewards_unlocked[].reward.claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
rewards_unlocked[].reward.claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
rewards_unlocked[].reward.claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
rewards_unlocked[].reward.claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
rewards_unlocked[].codeoptional; see conditionsstringPrivate redemption code. Absent on earned entitlements. Staff creation and lookup never return it; validation/redemption require a code already supplied by the customer.—Example: DEMO7KQ2M
rewards_unlocked[].statusoptional; see conditionsstringCurrent lifecycle state of this object. Use the documented values and endpoint rules.Allowed: available, used, expired, revoked, earned
rewards_unlocked[].used_atoptional; see conditionsstring | nullISO 8601 timestamp when the reward was used; null or omitted if it has not been used.Format: date-time
rewards_unlocked[].expires_atoptional; see conditionsstring | nullExclusive voucher cutoff in ISO 8601 with timezone offset. Redemption is refused at or after this instant. Null means no expiry. Use valid_through_date for date labels.Format: date-time
rewards_unlocked[].bill_referenceoptional; see conditionsstring | nullReceipt/bill reference connecting the operation to the business transaction.—Example: DEMO-BILL-001
rewards_unlocked[].claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
rewards_unlocked[].claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
rewards_unlocked[].claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
rewards_unlocked[].claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
rewards_unlocked[].claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
rewards_unlocked[].claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
rewards_unlocked[].valid_through_dateoptional; see conditionsstring | nullLast inclusive valid calendar date in expiry_timezone. Null means no expiry.Format: date
rewards_unlocked[].expiry_timezoneoptional; see conditionsstring | nullIANA business timezone captured when this reward was issued; unchanged by later country edits. Null means no expiry.—Example: Asia/Dubai
503 Authorization state unavailable

Authorization state unavailable — Retry after the Retry-After delay using bounded backoff. For a write, retain the same Idempotency-Key and identical payload; do not assume the operation succeeded or fall back to role-based access.

Retry-After — Seconds to wait before retrying authorization resolution; currently 3.

application/json · Illustrative example

{
    "error": {
        "code": "authorization_unavailable",
        "message": "Access settings are unavailable. Try again shortly."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—

Corrections

POST/refunds

Cumulative bill refund; preserves milestone/reward rules

Submit the cumulative refunded amount for a bill, not the incremental amount of this refund. The domain recalculates spending, points and earned rewards according to the published policy. In enforced mode, the current branch requires loyalty.refunds.process. Legacy/shadow behavior and business feature requirements continue to apply. Capability aliases are UI hints; the server rechecks current authorization. A bill whose earn entry has already been reversed cannot be refunded; returns 422 loyalty_action_not_allowed. Refunds and full earn reversals must not correct the same bill twice.

Bearer token Idempotency key

Parameters

Name / locationTypeDescriptionConstraints & example
Idempotency-Keyheader · requiredstringClient-generated stable key for one intended mutation, at most 64 characters. Save it before sending; retry an identical request with the same key after a timeout. Changing the payload with that key returns 409 idempotency_conflict.Max length: 64Example: demo-operation-001

Request body application/json · required

Required markers apply within each parent object. Conditional requirements, such as choosing one identifier or a branch, are explained in the descriptions.

FieldTypeDescriptionConstraints & example
bill_referencerequired in parent objectstringExact original purchase bill reference in this branch. Refunds apply to that bill; do not send the refund receipt number.Max length: 100Example: DEMO-BILL-001
refunded_total_minorrequired in parent objectintegerCumulative total refunded for the original bill, in currency minor units, not the amount of this refund alone. If £5 was refunded then another £3, send 800 for GBP. Must not exceed the original bill.Minimum: 0Example: 1500
branch_idoptional; see conditionsintegerLegacy integer branch ID from the authorized branch list, not its public ULID. Managers supply a branch when needed; assigned staff may only use their own branch.—Example: 12
JSON request example
{
    "bill_reference": "DEMO-BILL-001",
    "refunded_total_minor": 1500
}
cURL request
curl --request POST "$BASE_URL/refunds" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'Idempotency-Key: UNIQUE_OPERATION_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "bill_reference": "DEMO-BILL-001",
    "refunded_total_minor": 1500
}'

Responses

200 Success

Success

application/json · Schema-generated illustration; optional fields and values vary

{
    "points_removed": 0,
    "points_written_off": 0,
    "replayed": false,
    "rewards_revoked": [
        {
            "id": "01JB0000000000000000000001",
            "reward": {
                "id": "01JB0000000000000000000001",
                "name": "Free coffee",
                "description": "string",
                "type": "amount_voucher",
                "points_cost": 0,
                "value_label": "string",
                "value_minor": 1000,
                "percent": 0,
                "currency": "GBP",
                "claim": {
                    "eligible": true,
                    "reason_code": null,
                    "message": null,
                    "points_required": 20,
                    "available_actions": [
                        "claim"
                    ]
                }
            },
            "code": "DEMO7KQ2M",
            "status": "available",
            "used_at": "2026-10-24T10:00:00Z",
            "expires_at": "2026-10-24T10:00:00Z",
            "bill_reference": "DEMO-BILL-001",
            "claim": {
                "eligible": true,
                "reason_code": null,
                "message": null,
                "points_required": 20,
                "available_actions": [
                    "claim"
                ]
            },
            "valid_through_date": "string",
            "expiry_timezone": "Asia/Dubai"
        }
    ],
    "rewards_written_off": [
        {
            "id": "01JB0000000000000000000001",
            "reward": {
                "id": "01JB0000000000000000000001",
                "name": "Free coffee",
                "description": "string",
                "type": "amount_voucher",
                "points_cost": 0,
                "value_label": "string",
                "value_minor": 1000,
                "percent": 0,
                "currency": "GBP",
                "claim": {
                    "eligible": true,
                    "reason_code": null,
                    "message": null,
                    "points_required": 20,
                    "available_actions": [
                        "claim"
                    ]
                }
            },
            "code": "DEMO7KQ2M",
            "status": "available",
            "used_at": "2026-10-24T10:00:00Z",
            "expires_at": "2026-10-24T10:00:00Z",
            "bill_reference": "DEMO-BILL-001",
            "claim": {
                "eligible": true,
                "reason_code": null,
                "message": null,
                "points_required": 20,
                "available_actions": [
                    "claim"
                ]
            },
            "valid_through_date": "string",
            "expiry_timezone": "Asia/Dubai"
        }
    ]
}
Response schema
FieldTypeDescriptionConstraints & example
points_removedoptional; see conditionsintegerPoints removed as a result of applying this cumulative bill refund.—
points_written_offoptional; see conditionsintegerPoints of points_removed the balance could not cover because the customer had already spent them; recorded on the refund entry as written off.—
replayedoptional; see conditionsbooleanWhether the underlying operation reports a previously applied result rather than a new mutation. Always retain and reuse the original idempotency key.—
rewards_revokedoptional; see conditionsarray<object>Unused rewards revoked because the refund removed their earning entitlement.—
rewards_revoked[].idoptional; see conditionsstringPublic ULID of this issued reward grant. This differs from the catalogue reward ID and its redemption code.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
rewards_revoked[].rewardoptional; see conditionsobjectCatalogue reward definition. Its public ID is used when purchasing a reward.—
rewards_revoked[].reward.idoptional; see conditionsstringPublic ULID of the catalogue reward; pass it as reward when purchasing.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
rewards_revoked[].reward.nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Free coffee
rewards_revoked[].reward.descriptionoptional; see conditionsstring | nullCustomer-facing description of this resource, when configured.—
rewards_revoked[].reward.typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: amount_voucher, free_item, percent_off
rewards_revoked[].reward.points_costoptional; see conditionsinteger | nullPoints required to purchase this catalogue reward.—
rewards_revoked[].reward.value_labeloptional; see conditionsstringFormatted reward value for display, such as £10.00, 15% off or Free item. Use numeric fields for calculations.—
rewards_revoked[].reward.value_minoroptional; see conditionsinteger | nullFixed reward value in integer currency minor units; for example 1000 GBP means £10.00.—Example: 1000
rewards_revoked[].reward.percentoptional; see conditionsnumber | nullPercentage discount for a percentage reward, for example 15 means 15%.—
rewards_revoked[].reward.currencyoptional; see conditionsstringISO 4217 currency code, for example GBP or AED. Monetary amounts are expressed in its minor units.—Example: GBP
rewards_revoked[].reward.claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
rewards_revoked[].reward.claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
rewards_revoked[].reward.claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
rewards_revoked[].reward.claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
rewards_revoked[].reward.claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
rewards_revoked[].reward.claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
rewards_revoked[].codeoptional; see conditionsstringPrivate redemption code. Absent on earned entitlements. Staff creation and lookup never return it; validation/redemption require a code already supplied by the customer.—Example: DEMO7KQ2M
rewards_revoked[].statusoptional; see conditionsstringCurrent lifecycle state of this object. Use the documented values and endpoint rules.Allowed: available, used, expired, revoked, earned
rewards_revoked[].used_atoptional; see conditionsstring | nullISO 8601 timestamp when the reward was used; null or omitted if it has not been used.Format: date-time
rewards_revoked[].expires_atoptional; see conditionsstring | nullExclusive voucher cutoff in ISO 8601 with timezone offset. Redemption is refused at or after this instant. Null means no expiry. Use valid_through_date for date labels.Format: date-time
rewards_revoked[].bill_referenceoptional; see conditionsstring | nullReceipt/bill reference connecting the operation to the business transaction.—Example: DEMO-BILL-001
rewards_revoked[].claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
rewards_revoked[].claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
rewards_revoked[].claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
rewards_revoked[].claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
rewards_revoked[].claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
rewards_revoked[].claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
rewards_revoked[].valid_through_dateoptional; see conditionsstring | nullLast inclusive valid calendar date in expiry_timezone. Null means no expiry.Format: date
rewards_revoked[].expiry_timezoneoptional; see conditionsstring | nullIANA business timezone captured when this reward was issued; unchanged by later country edits. Null means no expiry.—Example: Asia/Dubai
rewards_written_offoptional; see conditionsarray<object>Already used rewards recorded as written off when the refund removes their earning entitlement.—
rewards_written_off[].idoptional; see conditionsstringPublic ULID of this issued reward grant. This differs from the catalogue reward ID and its redemption code.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
rewards_written_off[].rewardoptional; see conditionsobjectCatalogue reward definition. Its public ID is used when purchasing a reward.—
rewards_written_off[].reward.idoptional; see conditionsstringPublic ULID of the catalogue reward; pass it as reward when purchasing.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
rewards_written_off[].reward.nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Free coffee
rewards_written_off[].reward.descriptionoptional; see conditionsstring | nullCustomer-facing description of this resource, when configured.—
rewards_written_off[].reward.typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: amount_voucher, free_item, percent_off
rewards_written_off[].reward.points_costoptional; see conditionsinteger | nullPoints required to purchase this catalogue reward.—
rewards_written_off[].reward.value_labeloptional; see conditionsstringFormatted reward value for display, such as £10.00, 15% off or Free item. Use numeric fields for calculations.—
rewards_written_off[].reward.value_minoroptional; see conditionsinteger | nullFixed reward value in integer currency minor units; for example 1000 GBP means £10.00.—Example: 1000
rewards_written_off[].reward.percentoptional; see conditionsnumber | nullPercentage discount for a percentage reward, for example 15 means 15%.—
rewards_written_off[].reward.currencyoptional; see conditionsstringISO 4217 currency code, for example GBP or AED. Monetary amounts are expressed in its minor units.—Example: GBP
rewards_written_off[].reward.claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
rewards_written_off[].reward.claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
rewards_written_off[].reward.claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
rewards_written_off[].reward.claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
rewards_written_off[].reward.claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
rewards_written_off[].reward.claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
rewards_written_off[].codeoptional; see conditionsstringPrivate redemption code. Absent on earned entitlements. Staff creation and lookup never return it; validation/redemption require a code already supplied by the customer.—Example: DEMO7KQ2M
rewards_written_off[].statusoptional; see conditionsstringCurrent lifecycle state of this object. Use the documented values and endpoint rules.Allowed: available, used, expired, revoked, earned
rewards_written_off[].used_atoptional; see conditionsstring | nullISO 8601 timestamp when the reward was used; null or omitted if it has not been used.Format: date-time
rewards_written_off[].expires_atoptional; see conditionsstring | nullExclusive voucher cutoff in ISO 8601 with timezone offset. Redemption is refused at or after this instant. Null means no expiry. Use valid_through_date for date labels.Format: date-time
rewards_written_off[].bill_referenceoptional; see conditionsstring | nullReceipt/bill reference connecting the operation to the business transaction.—Example: DEMO-BILL-001
rewards_written_off[].claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
rewards_written_off[].claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
rewards_written_off[].claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
rewards_written_off[].claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
rewards_written_off[].claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
rewards_written_off[].claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
rewards_written_off[].valid_through_dateoptional; see conditionsstring | nullLast inclusive valid calendar date in expiry_timezone. Null means no expiry.Format: date
rewards_written_off[].expiry_timezoneoptional; see conditionsstring | nullIANA business timezone captured when this reward was issued; unchanged by later country edits. Null means no expiry.—Example: Asia/Dubai
400 Missing idempotency key

Missing idempotency key — Supply a stable key for the intended operation, then retry.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_key_required",
        "message": "An Idempotency-Key header is required."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check that the staff account is active.

application/json · Illustrative example

{
    "error": {
        "code": "unauthenticated",
        "message": "Authentication required."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
403 Access denied

Access denied — Check business entitlements, feature availability and staff permissions. Retrying unchanged will not restore access. Capability denial can return operation_unavailable; a missing exact reversal permission can return forbidden. Refresh /config for the current branch and stop the refused action. An unchanged request is not made valid by using another operation key.

application/json · Illustrative example

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
404 Not found

Not found — Check the public identifier and business context. Resources belonging to another tenant are not accessible.

application/json · Illustrative example

{
    "error": {
        "code": "not_found",
        "message": "Not found."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
409 Conflict

Conflict — Reuse a key only for an identical operation. For voucher_already_used, refresh reward status instead of redeeming again.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_conflict",
        "message": "This key was used with different request details."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
422 Validation or domain refusal, including refund_too_large and loyalty_action_not_allowed when the original earn was already reversed.

Validation or domain refusal, including refund_too_large and loyalty_action_not_allowed when the original earn was already reversed.

application/json · Illustrative example

{
    "error": {
        "code": "loyalty_action_not_allowed",
        "message": "The points for this bill were already reversed."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
429 Rate limited; Retry-After header

Rate limited; Retry-After header — Wait for the Retry-After header before retrying; retain the same operation key for a mutation.

Retry-After — Seconds until a retry is allowed

application/json · Illustrative example

{
    "error": {
        "code": "rate_limited",
        "message": "Too many requests; slow down.",
        "retry_after": 60
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
503 Authorization state unavailable

Authorization state unavailable — Retry after the Retry-After delay using bounded backoff. For a write, retain the same Idempotency-Key and identical payload; do not assume the operation succeeded or fall back to role-based access.

Retry-After — Seconds to wait before retrying authorization resolution; currently 3.

application/json · Illustrative example

{
    "error": {
        "code": "authorization_unavailable",
        "message": "Access settings are unavailable. Try again shortly."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
POST/transactions/{id}/reverse

Reverse an eligible transaction or void a voucher use with current target-specific authorization

Correction with a required reason and public transaction ULID at the current authorized branch. Legacy/shadow mode requires a business manager. Enforced mode checks the exact target: loyalty.entries.reverse for earn/adjust entries, loyalty.rewards.revoke for a points-purchased reward debit, or loyalty.rewards.void_use for a particular voucher-use audit transaction. The transactions.reverse capability means at least one reversal kind is allowed; it does not authorize every target. Current identity, branch and exact target permission are rechecked before returning an idempotent replay, so a previously successful key may now return 403 after revocation. Milestone-moving earns require the bill refund workflow (422 refund_required). Other eligible earns with any bill refund already recorded cannot also be reversed (422 loyalty_action_not_allowed); use the bill refund workflow for further permitted correction.

Bearer token Idempotency key

Parameters

Name / locationTypeDescriptionConstraints & example
idpath · requiredstringPublic transaction ULID from the original write result in the same business and branch. Legacy/shadow mode requires a manager; enforced mode checks the exact earn/adjust, reward-revoke or voucher-use-void permission, including on replay. Milestone-moving earns require the bill refund workflow.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
Idempotency-Keyheader · requiredstringClient-generated stable key for one intended mutation, at most 64 characters. Save it before sending; retry an identical request with the same key after a timeout. Changing the payload with that key returns 409 idempotency_conflict.Max length: 64Example: demo-operation-001

Request body application/json · required

Required markers apply within each parent object. Conditional requirements, such as choosing one identifier or a branch, are explained in the descriptions.

FieldTypeDescriptionConstraints & example
reasonrequired in parent objectstringHuman-readable reason for the correction, retained in the audit trail.Max length: 255Example: Duplicate entry confirmed by manager
branch_idoptional; see conditionsintegerLegacy integer branch ID from the authorized branch list, not its public ULID. Managers supply a branch when needed; assigned staff may only use their own branch.—Example: 12
JSON request example
{
    "reason": "Duplicate entry confirmed by manager"
}
cURL request
curl --request POST "$BASE_URL/transactions/{id}/reverse" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'Idempotency-Key: UNIQUE_OPERATION_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "reason": "Duplicate entry confirmed by manager"
}'

Responses

200 Success

Success

application/json · Schema-generated illustration; optional fields and values vary

{
    "reversed": false,
    "reason": "string"
}
Response schema
FieldTypeDescriptionConstraints & example
reversedoptional; see conditionsbooleanTrue when the requested reversal completed.—
reasonoptional; see conditionsstringHuman-readable reason for the correction, retained in the audit trail.—
400 Missing idempotency key

Missing idempotency key — Supply a stable key for the intended operation, then retry.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_key_required",
        "message": "An Idempotency-Key header is required."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check that the staff account is active.

application/json · Illustrative example

{
    "error": {
        "code": "unauthenticated",
        "message": "Authentication required."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
403 Current capability or exact target permission is denied; refresh configuration. A saved successful response does not bypass revocation.

Current capability or exact target permission is denied; refresh configuration. A saved successful response does not bypass revocation.

application/json · Illustrative example

{
    "error": {
        "code": "forbidden",
        "message": "You do not have permission for this reversal."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
404 Not found

Not found — Check the public identifier and business context. Resources belonging to another tenant are not accessible.

application/json · Illustrative example

{
    "error": {
        "code": "not_found",
        "message": "Not found."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
409 Conflict

Conflict — Reuse a key only for an identical operation. For voucher_already_used, refresh reward status instead of redeeming again.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_conflict",
        "message": "This key was used with different request details."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
422 Validation or domain refusal: refund_required for milestone-moving earns; loyalty_action_not_allowed for a refunded earn or another ineligible correction.

Validation or domain refusal: refund_required for milestone-moving earns; loyalty_action_not_allowed for a refunded earn or another ineligible correction.

application/json · Illustrative example

{
    "error": {
        "code": "loyalty_action_not_allowed",
        "message": "This bill has already been refunded. Correct it through the bill refund workflow."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
429 Rate limited; Retry-After header

Rate limited; Retry-After header — Wait for the Retry-After header before retrying; retain the same operation key for a mutation.

Retry-After — Seconds until a retry is allowed

application/json · Illustrative example

{
    "error": {
        "code": "rate_limited",
        "message": "Too many requests; slow down.",
        "retry_after": 60
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
503 Authorization state unavailable

Authorization state unavailable — Retry after the Retry-After delay using bounded backoff. For a write, retain the same Idempotency-Key and identical payload; do not assume the operation succeeded or fall back to role-based access.

Retry-After — Seconds to wait before retrying authorization resolution; currently 3.

application/json · Illustrative example

{
    "error": {
        "code": "authorization_unavailable",
        "message": "Access settings are unavailable. Try again shortly."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—

Rewards

GET/rewards

Active reward catalogue

List the active points-purchase catalogue for the business, with pagination. This is not the list of rewards already owned by a customer. In enforced mode, the current branch requires loyalty.rewards.view. Legacy/shadow behavior and business feature requirements continue to apply. Capability aliases are UI hints; the server rechecks current authorization. Anonymous customers must create their voucher in their authenticated browser card. Store/POS membership-only claims are refused; discovery returns customer_confirmation_required with no claim action. This does not change verified-customer staff claims.

Bearer token

Parameters

Name / locationTypeDescriptionConstraints & example
branch_idquery · optional; see conditionsintegerLegacy integer branch ID from the authorized branch list, not its public ULID. Managers supply a branch when needed; assigned staff may only use their own branch.—Example: 12
pagequery · optional; see conditionsintegerOne-based page number. Omit to request the first page; use meta.last_page to know when to stop.Minimum: 1 · Default: 1Example: 1
per_pagequery · optional; see conditionsintegerNumber of records requested per page. Defaults to 20; minimum 1 and maximum 50. Use pagination metadata to retrieve further pages.Minimum: 1 · Maximum: 50 · Default: 20Example: 20
member_codequery · optional; see conditionsstringOptional scanned membership code for personalized eligibility. Without it, catalogue entries are informational and have no claim action.Pattern: ^[0-9]{11}$Example: 48213906574
cURL request
curl --request GET "$BASE_URL/rewards?page=1&per_page=20" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer YOUR_TOKEN'

Responses

200 Success

Success

application/json · Schema-generated illustration; optional fields and values vary

{
    "rewards": [
        {
            "id": "01JB0000000000000000000001",
            "name": "Free coffee",
            "description": "string",
            "type": "amount_voucher",
            "points_cost": 0,
            "value_label": "string",
            "value_minor": 1000,
            "percent": 0,
            "currency": "GBP",
            "claim": {
                "eligible": true,
                "reason_code": null,
                "message": null,
                "points_required": 20,
                "available_actions": [
                    "claim"
                ]
            }
        }
    ],
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 20,
        "total": 1
    }
}
Response schema
FieldTypeDescriptionConstraints & example
rewardsoptional; see conditionsarray<object>Reward catalogue entries or customer reward grants, as defined by the item schema for this endpoint.—
rewards[].idoptional; see conditionsstringPublic ULID of the catalogue reward; pass it as reward when purchasing.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
rewards[].nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Free coffee
rewards[].descriptionoptional; see conditionsstring | nullCustomer-facing description of this resource, when configured.—
rewards[].typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: amount_voucher, free_item, percent_off
rewards[].points_costoptional; see conditionsinteger | nullPoints required to purchase this catalogue reward.—
rewards[].value_labeloptional; see conditionsstringFormatted reward value for display, such as £10.00, 15% off or Free item. Use numeric fields for calculations.—
rewards[].value_minoroptional; see conditionsinteger | nullFixed reward value in integer currency minor units; for example 1000 GBP means £10.00.—Example: 1000
rewards[].percentoptional; see conditionsnumber | nullPercentage discount for a percentage reward, for example 15 means 15%.—
rewards[].currencyoptional; see conditionsstringISO 4217 currency code, for example GBP or AED. Monetary amounts are expressed in its minor units.—Example: GBP
rewards[].claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
rewards[].claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
rewards[].claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
rewards[].claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
rewards[].claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
rewards[].claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
metaoptional; see conditionsobjectPagination metadata for a bounded list response.—
meta.current_pagerequired in parent objectintegerOne-based page number returned for this request.—Example: 1
meta.last_pagerequired in parent objectintegerLast available page number for the current filters.—Example: 1
meta.per_pagerequired in parent objectintegerMaximum number of records included per page, subject to the endpoint limit.—Example: 20
meta.totalrequired in parent objectintegerTotal matching records across all pages, not just the current page.—Example: 1
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check that the staff account is active.

application/json · Illustrative example

{
    "error": {
        "code": "unauthenticated",
        "message": "Authentication required."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
403 Access denied

Access denied — Check business entitlements, feature availability and staff permissions. Retrying unchanged will not restore access. Capability denial can return operation_unavailable; a missing exact reversal permission can return forbidden. Refresh /config for the current branch and stop the refused action. An unchanged request is not made valid by using another operation key.

application/json · Illustrative example

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
404 Not found

Not found — Check the public identifier and business context. Resources belonging to another tenant are not accessible.

application/json · Illustrative example

{
    "error": {
        "code": "not_found",
        "message": "Not found."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
409 Conflict

Conflict — Reuse a key only for an identical operation. For voucher_already_used, refresh reward status instead of redeeming again.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_conflict",
        "message": "This key was used with different request details."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
422 Validation or domain refusal

Validation or domain refusal — Correct the fields or handle the domain refusal identified by error.code. Do not retry an unchanged invalid request.

application/json · Illustrative example

{
    "error": {
        "code": "invalid",
        "message": "Some fields are invalid.",
        "fields": {
            "field_name": [
                "A validation message."
            ]
        }
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
429 Rate limited; Retry-After header

Rate limited; Retry-After header — Wait for the Retry-After header before retrying; retain the same operation key for a mutation.

Retry-After — Seconds until a retry is allowed

application/json · Illustrative example

{
    "error": {
        "code": "rate_limited",
        "message": "Too many requests; slow down.",
        "retry_after": 60
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
503 Authorization state unavailable

Authorization state unavailable — Retry after the Retry-After delay using bounded backoff. For a write, retain the same Idempotency-Key and identical payload; do not assume the operation succeeded or fall back to role-based access.

Retry-After — Seconds to wait before retrying authorization resolution; currently 3.

application/json · Illustrative example

{
    "error": {
        "code": "authorization_unavailable",
        "message": "Access settings are unavailable. Try again shortly."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
POST/rewards/purchase

Needs the member code; verified customers only

Purchase a points-based reward for a verified customer using the member code. Phone or email alone cannot authorize redemption. Requires an Idempotency-Key. In enforced mode, the current branch requires loyalty.rewards.purchase. Legacy/shadow behavior and business feature requirements continue to apply. Capability aliases are UI hints; the server rechecks current authorization. Store creation never returns the reward code. Get the code from the customer before redemption. Eligibility is authoritative on the server. Catalogue claims respect allow_catalogue_with_milestones. Staff-assisted claims require store_reward_claiming_enabled as well as current actor/branch permission; customer claims and redemption of issued vouchers are independent. Catalogue points are deducted at creation without a separate Customer App approval, per product policy. Sharing the private code separately authorizes redemption. The store/POS response never returns a reward code. Anonymous customers must create their voucher in their authenticated browser card. Store/POS membership-only claims are refused; discovery returns customer_confirmation_required with no claim action. This does not change verified-customer staff claims.

Bearer token Idempotency key

Parameters

Name / locationTypeDescriptionConstraints & example
Idempotency-Keyheader · requiredstringClient-generated stable key for one intended mutation, at most 64 characters. Save it before sending; retry an identical request with the same key after a timeout. Changing the payload with that key returns 409 idempotency_conflict.Max length: 64Example: demo-operation-001

Request body application/json · required

Required markers apply within each parent object. Conditional requirements, such as choosing one identifier or a branch, are explained in the descriptions.

FieldTypeDescriptionConstraints & example
member_coderequired in parent objectstringPrivate 11-digit member code scanned from the customer card. Required to authorize spending points; a phone/email is insufficient.—Example: 48213906574
rewardrequired in parent objectstringPublic catalogue reward ULID from GET /rewards. Do not send an issued grant ID or redemption code. The operation spends points to issue a customer reward.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
branch_idoptional; see conditionsintegerLegacy integer branch ID from the authorized branch list, not its public ULID. Managers supply a branch when needed; assigned staff may only use their own branch.—Example: 12
JSON request example
{
    "member_code": "48213906574",
    "reward": "01JB0000000000000000000001"
}
cURL request
curl --request POST "$BASE_URL/rewards/purchase" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'Idempotency-Key: UNIQUE_OPERATION_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "member_code": "48213906574",
    "reward": "01JB0000000000000000000001"
}'

Responses

201 Success

Success

application/json · Schema-generated illustration; optional fields and values vary

{
    "reward": {
        "id": "01JB0000000000000000000001",
        "reward": {
            "id": "01JB0000000000000000000001",
            "name": "Free coffee",
            "description": "string",
            "type": "amount_voucher",
            "points_cost": 0,
            "value_label": "string",
            "value_minor": 1000,
            "percent": 0,
            "currency": "GBP",
            "claim": {
                "eligible": true,
                "reason_code": null,
                "message": null,
                "points_required": 20,
                "available_actions": [
                    "claim"
                ]
            }
        },
        "code": "DEMO7KQ2M",
        "status": "available",
        "used_at": "2026-10-24T10:00:00Z",
        "expires_at": "2026-10-24T10:00:00Z",
        "bill_reference": "DEMO-BILL-001",
        "claim": {
            "eligible": true,
            "reason_code": null,
            "message": null,
            "points_required": 20,
            "available_actions": [
                "claim"
            ]
        },
        "valid_through_date": "string",
        "expiry_timezone": "Asia/Dubai"
    },
    "transaction_id": "string"
}
Response schema
FieldTypeDescriptionConstraints & example
rewardoptional; see conditionsobjectIssued customer reward as visible to staff. Lookup and voucher creation omit the code. Validation/redemption use a code supplied by the customer.—
reward.idoptional; see conditionsstringPublic ULID of this issued reward grant. This differs from the catalogue reward ID and its redemption code.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
reward.rewardoptional; see conditionsobjectCatalogue reward definition. Its public ID is used when purchasing a reward.—
reward.reward.idoptional; see conditionsstringPublic ULID of the catalogue reward; pass it as reward when purchasing.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
reward.reward.nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Free coffee
reward.reward.descriptionoptional; see conditionsstring | nullCustomer-facing description of this resource, when configured.—
reward.reward.typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: amount_voucher, free_item, percent_off
reward.reward.points_costoptional; see conditionsinteger | nullPoints required to purchase this catalogue reward.—
reward.reward.value_labeloptional; see conditionsstringFormatted reward value for display, such as £10.00, 15% off or Free item. Use numeric fields for calculations.—
reward.reward.value_minoroptional; see conditionsinteger | nullFixed reward value in integer currency minor units; for example 1000 GBP means £10.00.—Example: 1000
reward.reward.percentoptional; see conditionsnumber | nullPercentage discount for a percentage reward, for example 15 means 15%.—
reward.reward.currencyoptional; see conditionsstringISO 4217 currency code, for example GBP or AED. Monetary amounts are expressed in its minor units.—Example: GBP
reward.reward.claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
reward.reward.claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
reward.reward.claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
reward.reward.claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
reward.reward.claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
reward.reward.claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
reward.codeoptional; see conditionsstringPrivate redemption code. Absent on earned entitlements. Staff creation and lookup never return it; validation/redemption require a code already supplied by the customer.—Example: DEMO7KQ2M
reward.statusoptional; see conditionsstringCurrent lifecycle state of this object. Use the documented values and endpoint rules.Allowed: available, used, expired, revoked, earned
reward.used_atoptional; see conditionsstring | nullISO 8601 timestamp when the reward was used; null or omitted if it has not been used.Format: date-time
reward.expires_atoptional; see conditionsstring | nullExclusive voucher cutoff in ISO 8601 with timezone offset. Redemption is refused at or after this instant. Null means no expiry. Use valid_through_date for date labels.Format: date-time
reward.bill_referenceoptional; see conditionsstring | nullReceipt/bill reference connecting the operation to the business transaction.—Example: DEMO-BILL-001
reward.claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
reward.claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
reward.claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
reward.claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
reward.claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
reward.claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
reward.valid_through_dateoptional; see conditionsstring | nullLast inclusive valid calendar date in expiry_timezone. Null means no expiry.Format: date
reward.expiry_timezoneoptional; see conditionsstring | nullIANA business timezone captured when this reward was issued; unchanged by later country edits. Null means no expiry.—Example: Asia/Dubai
transaction_idoptional; see conditionsstring | nullPublic transaction/audit ULID returned by a write. Save it for reconciliation and supported reversal workflows; it may be null when no ledger entry was created.Min length: 26 · Max length: 26
400 Missing idempotency key

Missing idempotency key — Supply a stable key for the intended operation, then retry.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_key_required",
        "message": "An Idempotency-Key header is required."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check that the staff account is active.

application/json · Illustrative example

{
    "error": {
        "code": "unauthenticated",
        "message": "Authentication required."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
403 Access denied

Access denied — Check business entitlements, feature availability and staff permissions. Retrying unchanged will not restore access. Capability denial can return operation_unavailable; a missing exact reversal permission can return forbidden. Refresh /config for the current branch and stop the refused action. An unchanged request is not made valid by using another operation key.

application/json · Illustrative example

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
404 Not found

Not found — Check the public identifier and business context. Resources belonging to another tenant are not accessible.

application/json · Illustrative example

{
    "error": {
        "code": "not_found",
        "message": "Not found."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
409 Conflict

Conflict — Reuse a key only for an identical operation. For voucher_already_used, refresh reward status instead of redeeming again.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_conflict",
        "message": "This key was used with different request details."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
422 Validation or domain refusal

Validation or domain refusal — Correct the fields or handle the domain refusal identified by error.code. Do not retry an unchanged invalid request.

application/json · Illustrative example

{
    "error": {
        "code": "invalid",
        "message": "Some fields are invalid.",
        "fields": {
            "field_name": [
                "A validation message."
            ]
        }
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
429 Rate limited; Retry-After header

Rate limited; Retry-After header — Wait for the Retry-After header before retrying; retain the same operation key for a mutation.

Retry-After — Seconds until a retry is allowed

application/json · Illustrative example

{
    "error": {
        "code": "rate_limited",
        "message": "Too many requests; slow down.",
        "retry_after": 60
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
503 Authorization state unavailable

Authorization state unavailable — Retry after the Retry-After delay using bounded backoff. For a write, retain the same Idempotency-Key and identical payload; do not assume the operation succeeded or fall back to role-based access.

Retry-After — Seconds to wait before retrying authorization resolution; currently 3.

application/json · Illustrative example

{
    "error": {
        "code": "authorization_unavailable",
        "message": "Access settings are unavailable. Try again shortly."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
POST/rewards/validate

Read-only validation

Check a reward code and branch eligibility without consuming it. Validation does not reserve the reward; final use can still fail if its state changes. In enforced mode, the current branch requires loyalty.rewards.validate. Legacy/shadow behavior and business feature requirements continue to apply. Capability aliases are UI hints; the server rechecks current authorization. Uses the same eligibility policy as locked redemption, including membership, program, branch and pending refund effects. Returns a read-only snapshot, not a reservation. An anonymously enrolled customer may present an issued voucher: current program and reward anonymous-redemption permissions must both be enabled, or the customer must first verify a recovery contact. Scan or enter the voucher code; a membership code alone does not redeem it.

Bearer token

Request body application/json · required

Required markers apply within each parent object. Conditional requirements, such as choosing one identifier or a branch, are explained in the descriptions.

FieldTypeDescriptionConstraints & example
reward_coderequired in parent objectstringPrivate code of an issued customer reward, not the catalogue reward ID or membership code. Scan/type the customer-provided code.Max length: 12Example: DEMO7KQ2M
branch_idoptional; see conditionsintegerLegacy integer branch ID from the authorized branch list, not its public ULID. Managers supply a branch when needed; assigned staff may only use their own branch.—Example: 12
JSON request example
{
    "reward_code": "DEMO7KQ2M"
}
cURL request
curl --request POST "$BASE_URL/rewards/validate" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "reward_code": "DEMO7KQ2M"
}'

Responses

200 Success

Success

application/json · Schema-generated illustration; optional fields and values vary

{
    "reward": {
        "id": "01JB0000000000000000000001",
        "reward": {
            "id": "01JB0000000000000000000001",
            "name": "Free coffee",
            "description": "string",
            "type": "amount_voucher",
            "points_cost": 0,
            "value_label": "string",
            "value_minor": 1000,
            "percent": 0,
            "currency": "GBP",
            "claim": {
                "eligible": true,
                "reason_code": null,
                "message": null,
                "points_required": 20,
                "available_actions": [
                    "claim"
                ]
            }
        },
        "code": "DEMO7KQ2M",
        "status": "available",
        "used_at": "2026-10-24T10:00:00Z",
        "expires_at": "2026-10-24T10:00:00Z",
        "bill_reference": "DEMO-BILL-001",
        "claim": {
            "eligible": true,
            "reason_code": null,
            "message": null,
            "points_required": 20,
            "available_actions": [
                "claim"
            ]
        },
        "valid_through_date": "string",
        "expiry_timezone": "Asia/Dubai"
    },
    "usable": false,
    "reason_code": "string",
    "message": "string",
    "snapshot": true
}
Response schema
FieldTypeDescriptionConstraints & example
rewardoptional; see conditionsobjectIssued customer reward as visible to staff. Lookup and voucher creation omit the code. Validation/redemption use a code supplied by the customer.—
reward.idoptional; see conditionsstringPublic ULID of this issued reward grant. This differs from the catalogue reward ID and its redemption code.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
reward.rewardoptional; see conditionsobjectCatalogue reward definition. Its public ID is used when purchasing a reward.—
reward.reward.idoptional; see conditionsstringPublic ULID of the catalogue reward; pass it as reward when purchasing.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
reward.reward.nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Free coffee
reward.reward.descriptionoptional; see conditionsstring | nullCustomer-facing description of this resource, when configured.—
reward.reward.typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: amount_voucher, free_item, percent_off
reward.reward.points_costoptional; see conditionsinteger | nullPoints required to purchase this catalogue reward.—
reward.reward.value_labeloptional; see conditionsstringFormatted reward value for display, such as £10.00, 15% off or Free item. Use numeric fields for calculations.—
reward.reward.value_minoroptional; see conditionsinteger | nullFixed reward value in integer currency minor units; for example 1000 GBP means £10.00.—Example: 1000
reward.reward.percentoptional; see conditionsnumber | nullPercentage discount for a percentage reward, for example 15 means 15%.—
reward.reward.currencyoptional; see conditionsstringISO 4217 currency code, for example GBP or AED. Monetary amounts are expressed in its minor units.—Example: GBP
reward.reward.claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
reward.reward.claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
reward.reward.claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
reward.reward.claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
reward.reward.claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
reward.reward.claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
reward.codeoptional; see conditionsstringPrivate redemption code. Absent on earned entitlements. Staff creation and lookup never return it; validation/redemption require a code already supplied by the customer.—Example: DEMO7KQ2M
reward.statusoptional; see conditionsstringCurrent lifecycle state of this object. Use the documented values and endpoint rules.Allowed: available, used, expired, revoked, earned
reward.used_atoptional; see conditionsstring | nullISO 8601 timestamp when the reward was used; null or omitted if it has not been used.Format: date-time
reward.expires_atoptional; see conditionsstring | nullExclusive voucher cutoff in ISO 8601 with timezone offset. Redemption is refused at or after this instant. Null means no expiry. Use valid_through_date for date labels.Format: date-time
reward.bill_referenceoptional; see conditionsstring | nullReceipt/bill reference connecting the operation to the business transaction.—Example: DEMO-BILL-001
reward.claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
reward.claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
reward.claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
reward.claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
reward.claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
reward.claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
reward.valid_through_dateoptional; see conditionsstring | nullLast inclusive valid calendar date in expiry_timezone. Null means no expiry.Format: date
reward.expiry_timezoneoptional; see conditionsstring | nullIANA business timezone captured when this reward was issued; unchanged by later country edits. Null means no expiry.—Example: Asia/Dubai
usableoptional; see conditionsbooleanWhether the current validation permits use. Redeem still rechecks account, expiry, branch and grant state; do not treat this flag as a reservation.—
reason_codeoptional; see conditionsstring | nullCanonical refusal reason shared with redemption; null when usable.—
messageoptional; see conditionsstring | nullHuman-readable refusal reason.—
snapshotoptional; see conditionsbooleanAlways true: this read-only result does not reserve the voucher.—Example: true
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check that the staff account is active.

application/json · Illustrative example

{
    "error": {
        "code": "unauthenticated",
        "message": "Authentication required."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
403 Access denied

Access denied — Check business entitlements, feature availability and staff permissions. Retrying unchanged will not restore access. Capability denial can return operation_unavailable; a missing exact reversal permission can return forbidden. Refresh /config for the current branch and stop the refused action. An unchanged request is not made valid by using another operation key.

application/json · Illustrative example

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
404 Not found

Not found — Check the public identifier and business context. Resources belonging to another tenant are not accessible.

application/json · Illustrative example

{
    "error": {
        "code": "not_found",
        "message": "Not found."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
409 Conflict

Conflict — Reuse a key only for an identical operation. For voucher_already_used, refresh reward status instead of redeeming again.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_conflict",
        "message": "This key was used with different request details."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
422 Validation or domain refusal

Validation or domain refusal — Correct the fields or handle the domain refusal identified by error.code. Do not retry an unchanged invalid request.

application/json · Illustrative example

{
    "error": {
        "code": "invalid",
        "message": "Some fields are invalid.",
        "fields": {
            "field_name": [
                "A validation message."
            ]
        }
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
429 Rate limited; Retry-After header

Rate limited; Retry-After header — Wait for the Retry-After header before retrying; retain the same operation key for a mutation.

Retry-After — Seconds until a retry is allowed

application/json · Illustrative example

{
    "error": {
        "code": "rate_limited",
        "message": "Too many requests; slow down.",
        "retry_after": 60
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
503 Authorization state unavailable

Authorization state unavailable — Retry after the Retry-After delay using bounded backoff. For a write, retain the same Idempotency-Key and identical payload; do not assume the operation succeeded or fall back to role-based access.

Retry-After — Seconds to wait before retrying authorization resolution; currently 3.

application/json · Illustrative example

{
    "error": {
        "code": "authorization_unavailable",
        "message": "Access settings are unavailable. Try again shortly."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
POST/rewards/use

Atomic reward use with a bill number

Atomically consume a reward code with an optional bill_reference. Requires a verified customer and an eligible branch. Preserve the Idempotency-Key on retries. In enforced mode, the current branch requires loyalty.rewards.redeem. Legacy/shadow behavior and business feature requirements continue to apply. Capability aliases are UI hints; the server rechecks current authorization. An anonymously enrolled customer may present an issued voucher: current program and reward anonymous-redemption permissions must both be enabled, or the customer must first verify a recovery contact. Scan or enter the voucher code; a membership code alone does not redeem it. Bill reference is optional for earned-voucher redemption; do not require staff to enter or invent one. Idempotency-Key remains mandatory.

Bearer token Idempotency key

Parameters

Name / locationTypeDescriptionConstraints & example
Idempotency-Keyheader · requiredstringClient-generated stable key for one intended mutation, at most 64 characters. Save it before sending; retry an identical request with the same key after a timeout. Changing the payload with that key returns 409 idempotency_conflict.Max length: 64Example: demo-operation-001

Request body application/json · required

Required markers apply within each parent object. Conditional requirements, such as choosing one identifier or a branch, are explained in the descriptions.

FieldTypeDescriptionConstraints & example
reward_coderequired in parent objectstringPrivate code of an issued customer reward, not the catalogue reward ID or membership code. Scan/type the customer-provided code.Max length: 12Example: DEMO7KQ2M
bill_referenceoptional; see conditionsstring | nullOptional receipt reference for reconciliation, at most 100 characters. Omitted, null or empty is accepted independently of purchase bill_reference_required.Max length: 100Example: DEMO-REDEEM-001
branch_idoptional; see conditionsintegerLegacy integer branch ID from the authorized branch list, not its public ULID. Managers supply a branch when needed; assigned staff may only use their own branch.—Example: 12
JSON request example
{
    "reward_code": "DEMO7KQ2M",
    "bill_reference": "DEMO-REDEEM-001"
}
cURL request
curl --request POST "$BASE_URL/rewards/use" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'Idempotency-Key: UNIQUE_OPERATION_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "reward_code": "DEMO7KQ2M",
    "bill_reference": "DEMO-REDEEM-001"
}'

Responses

200 Success

Success

application/json · Schema-generated illustration; optional fields and values vary

{
    "reward": {
        "id": "01JB0000000000000000000001",
        "reward": {
            "id": "01JB0000000000000000000001",
            "name": "Free coffee",
            "description": "string",
            "type": "amount_voucher",
            "points_cost": 0,
            "value_label": "string",
            "value_minor": 1000,
            "percent": 0,
            "currency": "GBP",
            "claim": {
                "eligible": true,
                "reason_code": null,
                "message": null,
                "points_required": 20,
                "available_actions": [
                    "claim"
                ]
            }
        },
        "code": "DEMO7KQ2M",
        "status": "available",
        "used_at": "2026-10-24T10:00:00Z",
        "expires_at": "2026-10-24T10:00:00Z",
        "bill_reference": "DEMO-BILL-001",
        "claim": {
            "eligible": true,
            "reason_code": null,
            "message": null,
            "points_required": 20,
            "available_actions": [
                "claim"
            ]
        },
        "valid_through_date": "string",
        "expiry_timezone": "Asia/Dubai"
    },
    "transaction_id": "string"
}
Response schema
FieldTypeDescriptionConstraints & example
rewardoptional; see conditionsobjectIssued customer reward as visible to staff. Lookup and voucher creation omit the code. Validation/redemption use a code supplied by the customer.—
reward.idoptional; see conditionsstringPublic ULID of this issued reward grant. This differs from the catalogue reward ID and its redemption code.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
reward.rewardoptional; see conditionsobjectCatalogue reward definition. Its public ID is used when purchasing a reward.—
reward.reward.idoptional; see conditionsstringPublic ULID of the catalogue reward; pass it as reward when purchasing.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
reward.reward.nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Free coffee
reward.reward.descriptionoptional; see conditionsstring | nullCustomer-facing description of this resource, when configured.—
reward.reward.typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: amount_voucher, free_item, percent_off
reward.reward.points_costoptional; see conditionsinteger | nullPoints required to purchase this catalogue reward.—
reward.reward.value_labeloptional; see conditionsstringFormatted reward value for display, such as £10.00, 15% off or Free item. Use numeric fields for calculations.—
reward.reward.value_minoroptional; see conditionsinteger | nullFixed reward value in integer currency minor units; for example 1000 GBP means £10.00.—Example: 1000
reward.reward.percentoptional; see conditionsnumber | nullPercentage discount for a percentage reward, for example 15 means 15%.—
reward.reward.currencyoptional; see conditionsstringISO 4217 currency code, for example GBP or AED. Monetary amounts are expressed in its minor units.—Example: GBP
reward.reward.claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
reward.reward.claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
reward.reward.claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
reward.reward.claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
reward.reward.claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
reward.reward.claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
reward.codeoptional; see conditionsstringPrivate redemption code. Absent on earned entitlements. Staff creation and lookup never return it; validation/redemption require a code already supplied by the customer.—Example: DEMO7KQ2M
reward.statusoptional; see conditionsstringCurrent lifecycle state of this object. Use the documented values and endpoint rules.Allowed: available, used, expired, revoked, earned
reward.used_atoptional; see conditionsstring | nullISO 8601 timestamp when the reward was used; null or omitted if it has not been used.Format: date-time
reward.expires_atoptional; see conditionsstring | nullExclusive voucher cutoff in ISO 8601 with timezone offset. Redemption is refused at or after this instant. Null means no expiry. Use valid_through_date for date labels.Format: date-time
reward.bill_referenceoptional; see conditionsstring | nullReceipt/bill reference connecting the operation to the business transaction.—Example: DEMO-BILL-001
reward.claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
reward.claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
reward.claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
reward.claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
reward.claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
reward.claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
reward.valid_through_dateoptional; see conditionsstring | nullLast inclusive valid calendar date in expiry_timezone. Null means no expiry.Format: date
reward.expiry_timezoneoptional; see conditionsstring | nullIANA business timezone captured when this reward was issued; unchanged by later country edits. Null means no expiry.—Example: Asia/Dubai
transaction_idoptional; see conditionsstringPublic transaction/audit ULID returned by a write. Save it for reconciliation and supported reversal workflows; it may be null when no ledger entry was created.Min length: 26 · Max length: 26
400 Missing idempotency key

Missing idempotency key — Supply a stable key for the intended operation, then retry.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_key_required",
        "message": "An Idempotency-Key header is required."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check that the staff account is active.

application/json · Illustrative example

{
    "error": {
        "code": "unauthenticated",
        "message": "Authentication required."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
403 Access denied

Access denied — Check business entitlements, feature availability and staff permissions. Retrying unchanged will not restore access. Capability denial can return operation_unavailable; a missing exact reversal permission can return forbidden. Refresh /config for the current branch and stop the refused action. An unchanged request is not made valid by using another operation key.

application/json · Illustrative example

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
404 Not found

Not found — Check the public identifier and business context. Resources belonging to another tenant are not accessible.

application/json · Illustrative example

{
    "error": {
        "code": "not_found",
        "message": "Not found."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
409 Conflict

Conflict — Reuse a key only for an identical operation. For voucher_already_used, refresh reward status instead of redeeming again.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_conflict",
        "message": "This key was used with different request details."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
422 Validation or domain refusal

Validation or domain refusal — Correct the fields or handle the domain refusal identified by error.code. Do not retry an unchanged invalid request.

application/json · Illustrative example

{
    "error": {
        "code": "invalid",
        "message": "Some fields are invalid.",
        "fields": {
            "field_name": [
                "A validation message."
            ]
        }
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
429 Rate limited; Retry-After header

Rate limited; Retry-After header — Wait for the Retry-After header before retrying; retain the same operation key for a mutation.

Retry-After — Seconds until a retry is allowed

application/json · Illustrative example

{
    "error": {
        "code": "rate_limited",
        "message": "Too many requests; slow down.",
        "retry_after": 60
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
503 Authorization state unavailable

Authorization state unavailable — Retry after the Retry-After delay using bounded backoff. For a write, retain the same Idempotency-Key and identical payload; do not assume the operation succeeded or fall back to role-based access.

Retry-After — Seconds to wait before retrying authorization resolution; currently 3.

application/json · Illustrative example

{
    "error": {
        "code": "authorization_unavailable",
        "message": "Access settings are unavailable. Try again shortly."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
POST/rewards/create-voucher

Create the single voucher for an earned milestone reward

Claim an earned entitlement in place. Both customer and store creation share the same locked record and cannot issue duplicate vouchers. Requires an active verified customer, active membership/program and unexpired, unrevoked eligibility. No additional milestone point deduction; expiry remains measured from earning. This does not redeem the voucher. Requires a membership code and loyalty.rewards.purchase permission. The response never contains a redemption code, including retries. Customer must read the code in their own app and share it for validation/redemption. Eligibility is authoritative on the server. Catalogue claims respect allow_catalogue_with_milestones. Staff-assisted claims require store_reward_claiming_enabled as well as current actor/branch permission; customer claims and redemption of issued vouchers are independent. Catalogue points are deducted at creation without a separate Customer App approval, per product policy. Sharing the private code separately authorizes redemption. Anonymous customers must create their voucher in their authenticated browser card. Store/POS membership-only claims are refused; discovery returns customer_confirmation_required with no claim action. This does not change verified-customer staff claims.

Bearer token Idempotency key

Parameters

Name / locationTypeDescriptionConstraints & example
Idempotency-Keyheader · requiredstringClient-generated stable key for one intended mutation, at most 64 characters. Save it before sending; retry an identical request with the same key after a timeout. Changing the payload with that key returns 409 idempotency_conflict.Max length: 64Example: demo-operation-001

Request body application/json · required

Required markers apply within each parent object. Conditional requirements, such as choosing one identifier or a branch, are explained in the descriptions.

FieldTypeDescriptionConstraints & example
entitlementrequired in parent objectstringPublic earned customer reward grant ULID, from owned rewards or staff lookup; not a catalogue reward ID.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
member_coderequired in parent objectstringScanned customer membership code. Phone or email cannot authorize issuance.Pattern: ^[0-9]{11}$Example: 48213906574
JSON request example
{
    "entitlement": "01JB0000000000000000000001",
    "member_code": "48213906574"
}
cURL request
curl --request POST "$BASE_URL/rewards/create-voucher" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'Idempotency-Key: UNIQUE_OPERATION_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "entitlement": "01JB0000000000000000000001",
    "member_code": "48213906574"
}'

Responses

201 Success

Success

application/json · Schema-generated illustration; optional fields and values vary

{
    "reward": {
        "id": "01JB0000000000000000000001",
        "reward": {
            "id": "01JB0000000000000000000001",
            "name": "Free coffee",
            "description": "string",
            "type": "amount_voucher",
            "points_cost": 0,
            "value_label": "string",
            "value_minor": 1000,
            "percent": 0,
            "currency": "GBP",
            "claim": {
                "eligible": true,
                "reason_code": null,
                "message": null,
                "points_required": 20,
                "available_actions": [
                    "claim"
                ]
            }
        },
        "status": "available",
        "used_at": "2026-10-24T10:00:00Z",
        "expires_at": "2026-10-24T10:00:00Z",
        "bill_reference": "DEMO-BILL-001",
        "valid_through_date": "string",
        "expiry_timezone": "Asia/Dubai"
    }
}
Response schema
FieldTypeDescriptionConstraints & example
rewardoptional; see conditionsobjectVoucher creation status and non-secret metadata only. The customer alone receives the code.—
reward.idoptional; see conditionsstringPublic ULID of this issued reward grant. This differs from the catalogue reward ID and its redemption code.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
reward.rewardoptional; see conditionsobjectCatalogue reward definition. Its public ID is used when purchasing a reward.—
reward.reward.idoptional; see conditionsstringPublic ULID of the catalogue reward; pass it as reward when purchasing.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
reward.reward.nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Free coffee
reward.reward.descriptionoptional; see conditionsstring | nullCustomer-facing description of this resource, when configured.—
reward.reward.typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: amount_voucher, free_item, percent_off
reward.reward.points_costoptional; see conditionsinteger | nullPoints required to purchase this catalogue reward.—
reward.reward.value_labeloptional; see conditionsstringFormatted reward value for display, such as £10.00, 15% off or Free item. Use numeric fields for calculations.—
reward.reward.value_minoroptional; see conditionsinteger | nullFixed reward value in integer currency minor units; for example 1000 GBP means £10.00.—Example: 1000
reward.reward.percentoptional; see conditionsnumber | nullPercentage discount for a percentage reward, for example 15 means 15%.—
reward.reward.currencyoptional; see conditionsstringISO 4217 currency code, for example GBP or AED. Monetary amounts are expressed in its minor units.—Example: GBP
reward.reward.claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
reward.reward.claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
reward.reward.claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
reward.reward.claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
reward.reward.claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
reward.reward.claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
reward.statusoptional; see conditionsstringCurrent lifecycle state of this object. Use the documented values and endpoint rules.Allowed: available
reward.used_atoptional; see conditionsstring | nullISO 8601 timestamp when the reward was used; null or omitted if it has not been used.Format: date-time
reward.expires_atoptional; see conditionsstring | nullExclusive voucher cutoff in ISO 8601 with timezone offset. Redemption is refused at or after this instant. Null means no expiry. Use valid_through_date for date labels.Format: date-time
reward.bill_referenceoptional; see conditionsstring | nullReceipt/bill reference connecting the operation to the business transaction.—Example: DEMO-BILL-001
reward.valid_through_dateoptional; see conditionsstring | nullLast inclusive valid calendar date in expiry_timezone. Null means no expiry.Format: date
reward.expiry_timezoneoptional; see conditionsstring | nullIANA business timezone captured when this reward was issued; unchanged by later country edits. Null means no expiry.—Example: Asia/Dubai
400 Missing idempotency key

Missing idempotency key — Supply a stable key for the intended operation, then retry.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_key_required",
        "message": "An Idempotency-Key header is required."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check that the staff account is active.

application/json · Illustrative example

{
    "error": {
        "code": "unauthenticated",
        "message": "Authentication required."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
403 Access denied

Access denied — Check business entitlements, feature availability and staff permissions. Retrying unchanged will not restore access. Capability denial can return operation_unavailable; a missing exact reversal permission can return forbidden. Refresh /config for the current branch and stop the refused action. An unchanged request is not made valid by using another operation key.

application/json · Illustrative example

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
404 Not found

Not found — Check the public identifier and business context. Resources belonging to another tenant are not accessible.

application/json · Illustrative example

{
    "error": {
        "code": "not_found",
        "message": "Not found."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
409 Conflict

Conflict — Reuse a key only for an identical operation. For voucher_already_used, refresh reward status instead of redeeming again.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_conflict",
        "message": "This key was used with different request details."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
422 Validation or domain refusal

Validation or domain refusal — Correct the fields or handle the domain refusal identified by error.code. Do not retry an unchanged invalid request.

application/json · Illustrative example

{
    "error": {
        "code": "invalid",
        "message": "Some fields are invalid.",
        "fields": {
            "field_name": [
                "A validation message."
            ]
        }
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
429 Rate limited; Retry-After header

Rate limited; Retry-After header — Wait for the Retry-After header before retrying; retain the same operation key for a mutation.

Retry-After — Seconds until a retry is allowed

application/json · Illustrative example

{
    "error": {
        "code": "rate_limited",
        "message": "Too many requests; slow down.",
        "retry_after": 60
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
503 Authorization state unavailable

Authorization state unavailable — Retry after the Retry-After delay using bounded backoff. For a write, retain the same Idempotency-Key and identical payload; do not assume the operation succeeded or fall back to role-based access.

Retry-After — Seconds to wait before retrying authorization resolution; currently 3.

application/json · Illustrative example

{
    "error": {
        "code": "authorization_unavailable",
        "message": "Access settings are unavailable. Try again shortly."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
GET/customers/rewards

Paginated owned rewards and server-calculated claim actions

Identify a customer using exactly one member_code, phone or email within the authorized business and branch. Returns paginated earned/available rewards by default, or a requested status. No reward codes are ever returned. Phone/email discovery is informational; claiming requires the membership code. Requires customers.rewards capability mapped to loyalty.members.lookup. Claim decisions include business settings and actor permissions and are rechecked at submission. Anonymous customers must create their voucher in their authenticated browser card. Store/POS membership-only claims are refused; discovery returns customer_confirmation_required with no claim action. This does not change verified-customer staff claims.

Bearer token

Parameters

Name / locationTypeDescriptionConstraints & example
branch_idquery · optional; see conditionsintegerLegacy integer branch ID from the authorized branch list, not its public ULID. Managers supply a branch when needed; assigned staff may only use their own branch.—Example: 12
member_codequery · optional; see conditionsstringCustomer membership QR/card value: exactly 11 digits, sent as a string. Supply exactly one of member_code, phone or email. Store lookup never returns reward codes, even after a membership scan.Pattern: ^\d{11}$Example: 48213906574
phonequery · optional; see conditionsstringCustomer phone for an earning-only lookup. E.164 is recommended; local numbers use the business country. Supply exactly one lookup identifier.—Example: +447700900123
emailquery · optional; see conditionsstringCustomer email for an earning-only lookup. Supply exactly one lookup identifier; email lookup never authorizes redemption.Format: emailExample: customer@example.com
pagequery · optional; see conditionsintegerOne-based page number. Omit to request the first page; use meta.last_page to know when to stop.Minimum: 1 · Default: 1Example: 1
per_pagequery · optional; see conditionsintegerNumber of records requested per page. Defaults to 20; minimum 1 and maximum 50. Use pagination metadata to retrieve further pages.Minimum: 1 · Maximum: 50 · Default: 20Example: 20
statusquery · optional; see conditionsstringFilter reward lifecycle state. Omit to list earned and available rewards.Allowed: earned, available, used, expired, revokedExample: earned
cURL request
curl --request GET "$BASE_URL/customers/rewards?page=1&per_page=20" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer YOUR_TOKEN'

Responses

200 Success

Success

application/json · Schema-generated illustration; optional fields and values vary

{
    "rewards": [
        {
            "id": "01JB0000000000000000000001",
            "reward": {
                "id": "01JB0000000000000000000001",
                "name": "Free coffee",
                "description": "string",
                "type": "amount_voucher",
                "points_cost": 0,
                "value_label": "string",
                "value_minor": 1000,
                "percent": 0,
                "currency": "GBP",
                "claim": {
                    "eligible": true,
                    "reason_code": null,
                    "message": null,
                    "points_required": 20,
                    "available_actions": [
                        "claim"
                    ]
                }
            },
            "code": "DEMO7KQ2M",
            "status": "available",
            "used_at": "2026-10-24T10:00:00Z",
            "expires_at": "2026-10-24T10:00:00Z",
            "bill_reference": "DEMO-BILL-001",
            "claim": {
                "eligible": true,
                "reason_code": null,
                "message": null,
                "points_required": 20,
                "available_actions": [
                    "claim"
                ]
            },
            "valid_through_date": "string",
            "expiry_timezone": "Asia/Dubai"
        }
    ],
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 20,
        "total": 1
    }
}
Response schema
FieldTypeDescriptionConstraints & example
rewardsoptional; see conditionsarray<object>Reward catalogue entries or customer reward grants, as defined by the item schema for this endpoint.—
rewards[].idoptional; see conditionsstringPublic ULID of this issued reward grant. This differs from the catalogue reward ID and its redemption code.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
rewards[].rewardoptional; see conditionsobjectCatalogue reward definition. Its public ID is used when purchasing a reward.—
rewards[].reward.idoptional; see conditionsstringPublic ULID of the catalogue reward; pass it as reward when purchasing.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
rewards[].reward.nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Free coffee
rewards[].reward.descriptionoptional; see conditionsstring | nullCustomer-facing description of this resource, when configured.—
rewards[].reward.typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: amount_voucher, free_item, percent_off
rewards[].reward.points_costoptional; see conditionsinteger | nullPoints required to purchase this catalogue reward.—
rewards[].reward.value_labeloptional; see conditionsstringFormatted reward value for display, such as £10.00, 15% off or Free item. Use numeric fields for calculations.—
rewards[].reward.value_minoroptional; see conditionsinteger | nullFixed reward value in integer currency minor units; for example 1000 GBP means £10.00.—Example: 1000
rewards[].reward.percentoptional; see conditionsnumber | nullPercentage discount for a percentage reward, for example 15 means 15%.—
rewards[].reward.currencyoptional; see conditionsstringISO 4217 currency code, for example GBP or AED. Monetary amounts are expressed in its minor units.—Example: GBP
rewards[].reward.claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
rewards[].reward.claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
rewards[].reward.claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
rewards[].reward.claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
rewards[].reward.claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
rewards[].reward.claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
rewards[].codeoptional; see conditionsstringPrivate redemption code. Absent on earned entitlements. Staff creation and lookup never return it; validation/redemption require a code already supplied by the customer.—Example: DEMO7KQ2M
rewards[].statusoptional; see conditionsstringCurrent lifecycle state of this object. Use the documented values and endpoint rules.Allowed: available, used, expired, revoked, earned
rewards[].used_atoptional; see conditionsstring | nullISO 8601 timestamp when the reward was used; null or omitted if it has not been used.Format: date-time
rewards[].expires_atoptional; see conditionsstring | nullExclusive voucher cutoff in ISO 8601 with timezone offset. Redemption is refused at or after this instant. Null means no expiry. Use valid_through_date for date labels.Format: date-time
rewards[].bill_referenceoptional; see conditionsstring | nullReceipt/bill reference connecting the operation to the business transaction.—Example: DEMO-BILL-001
rewards[].claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
rewards[].claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
rewards[].claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
rewards[].claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
rewards[].claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
rewards[].claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
rewards[].valid_through_dateoptional; see conditionsstring | nullLast inclusive valid calendar date in expiry_timezone. Null means no expiry.Format: date
rewards[].expiry_timezoneoptional; see conditionsstring | nullIANA business timezone captured when this reward was issued; unchanged by later country edits. Null means no expiry.—Example: Asia/Dubai
metaoptional; see conditionsobjectPagination metadata for a bounded list response.—
meta.current_pagerequired in parent objectintegerOne-based page number returned for this request.—Example: 1
meta.last_pagerequired in parent objectintegerLast available page number for the current filters.—Example: 1
meta.per_pagerequired in parent objectintegerMaximum number of records included per page, subject to the endpoint limit.—Example: 20
meta.totalrequired in parent objectintegerTotal matching records across all pages, not just the current page.—Example: 1
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check that the staff account is active.

application/json · Illustrative example

{
    "error": {
        "code": "unauthenticated",
        "message": "Authentication required."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
403 Access denied

Access denied — Check business entitlements, feature availability and staff permissions. Retrying unchanged will not restore access. Capability denial can return operation_unavailable; a missing exact reversal permission can return forbidden. Refresh /config for the current branch and stop the refused action. An unchanged request is not made valid by using another operation key.

application/json · Illustrative example

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
404 Not found

Not found — Check the public identifier and business context. Resources belonging to another tenant are not accessible.

application/json · Illustrative example

{
    "error": {
        "code": "not_found",
        "message": "Not found."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
409 Conflict

Conflict — Reuse a key only for an identical operation. For voucher_already_used, refresh reward status instead of redeeming again.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_conflict",
        "message": "This key was used with different request details."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
422 Validation or domain refusal

Validation or domain refusal — Correct the fields or handle the domain refusal identified by error.code. Do not retry an unchanged invalid request.

application/json · Illustrative example

{
    "error": {
        "code": "invalid",
        "message": "Some fields are invalid.",
        "fields": {
            "field_name": [
                "A validation message."
            ]
        }
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
429 Rate limited; Retry-After header

Rate limited; Retry-After header — Wait for the Retry-After header before retrying; retain the same operation key for a mutation.

Retry-After — Seconds until a retry is allowed

application/json · Illustrative example

{
    "error": {
        "code": "rate_limited",
        "message": "Too many requests; slow down.",
        "retry_after": 60
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
503 Authorization state unavailable

Authorization state unavailable — Retry after the Retry-After delay using bounded backoff. For a write, retain the same Idempotency-Key and identical payload; do not assume the operation succeeded or fall back to role-based access.

Retry-After — Seconds to wait before retrying authorization resolution; currently 3.

application/json · Illustrative example

{
    "error": {
        "code": "authorization_unavailable",
        "message": "Access settings are unavailable. Try again shortly."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—

Vouchers

GET/vouchers/{code}

Voucher, masked owner and branch policy

Inspect a voucher by code, including masked owner and branch policy. Handle voucher_not_found, voucher_already_used and eligibility refusals. In enforced mode, the current branch requires loyalty.rewards.validate. Legacy/shadow behavior and business feature requirements continue to apply. Capability aliases are UI hints; the server rechecks current authorization. Uses the same eligibility policy as locked redemption, including membership, program, branch and pending refund effects. Returns a read-only snapshot, not a reservation. An anonymously enrolled customer may present an issued voucher: current program and reward anonymous-redemption permissions must both be enabled, or the customer must first verify a recovery contact. Scan or enter the voucher code; a membership code alone does not redeem it. Accepts codes issued by customer POST /api/app/v1/rewards/create-voucher. Check usable and reason_code before offering redemption; redemption rechecks eligibility.

Bearer token

Parameters

Name / locationTypeDescriptionConstraints & example
codepath · requiredstringPrivate issued reward/voucher code shown by the customer, not a catalogue ULID or member code. The server trims whitespace and normalizes to uppercase.—Example: DEMO-REWARD
branch_idquery · optional; see conditionsintegerLegacy integer branch ID from the authorized branch list, not its public ULID. Managers supply a branch when needed; assigned staff may only use their own branch.—Example: 12
cURL request
curl --request GET "$BASE_URL/vouchers/{code}" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer YOUR_TOKEN'

Responses

200 Success

Success

application/json · Schema-generated illustration; optional fields and values vary

{
    "reward": {
        "id": "01JB0000000000000000000001",
        "reward": {
            "id": "01JB0000000000000000000001",
            "name": "Free coffee",
            "description": "string",
            "type": "amount_voucher",
            "points_cost": 0,
            "value_label": "string",
            "value_minor": 1000,
            "percent": 0,
            "currency": "GBP",
            "claim": {
                "eligible": true,
                "reason_code": null,
                "message": null,
                "points_required": 20,
                "available_actions": [
                    "claim"
                ]
            }
        },
        "code": "DEMO7KQ2M",
        "status": "available",
        "used_at": "2026-10-24T10:00:00Z",
        "expires_at": "2026-10-24T10:00:00Z",
        "bill_reference": "DEMO-BILL-001",
        "claim": {
            "eligible": true,
            "reason_code": null,
            "message": null,
            "points_required": 20,
            "available_actions": [
                "claim"
            ]
        },
        "valid_through_date": "string",
        "expiry_timezone": "Asia/Dubai"
    },
    "usable": false,
    "owner": {
        "name": "string",
        "phone": "string"
    },
    "branch_ids": [
        0
    ],
    "reason_code": "string",
    "message": "string",
    "snapshot": true
}
Response schema
FieldTypeDescriptionConstraints & example
rewardoptional; see conditionsobjectIssued customer reward as visible to staff. Lookup and voucher creation omit the code. Validation/redemption use a code supplied by the customer.—
reward.idoptional; see conditionsstringPublic ULID of this issued reward grant. This differs from the catalogue reward ID and its redemption code.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
reward.rewardoptional; see conditionsobjectCatalogue reward definition. Its public ID is used when purchasing a reward.—
reward.reward.idoptional; see conditionsstringPublic ULID of the catalogue reward; pass it as reward when purchasing.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
reward.reward.nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Free coffee
reward.reward.descriptionoptional; see conditionsstring | nullCustomer-facing description of this resource, when configured.—
reward.reward.typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: amount_voucher, free_item, percent_off
reward.reward.points_costoptional; see conditionsinteger | nullPoints required to purchase this catalogue reward.—
reward.reward.value_labeloptional; see conditionsstringFormatted reward value for display, such as £10.00, 15% off or Free item. Use numeric fields for calculations.—
reward.reward.value_minoroptional; see conditionsinteger | nullFixed reward value in integer currency minor units; for example 1000 GBP means £10.00.—Example: 1000
reward.reward.percentoptional; see conditionsnumber | nullPercentage discount for a percentage reward, for example 15 means 15%.—
reward.reward.currencyoptional; see conditionsstringISO 4217 currency code, for example GBP or AED. Monetary amounts are expressed in its minor units.—Example: GBP
reward.reward.claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
reward.reward.claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
reward.reward.claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
reward.reward.claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
reward.reward.claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
reward.reward.claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
reward.codeoptional; see conditionsstringPrivate redemption code. Absent on earned entitlements. Staff creation and lookup never return it; validation/redemption require a code already supplied by the customer.—Example: DEMO7KQ2M
reward.statusoptional; see conditionsstringCurrent lifecycle state of this object. Use the documented values and endpoint rules.Allowed: available, used, expired, revoked, earned
reward.used_atoptional; see conditionsstring | nullISO 8601 timestamp when the reward was used; null or omitted if it has not been used.Format: date-time
reward.expires_atoptional; see conditionsstring | nullExclusive voucher cutoff in ISO 8601 with timezone offset. Redemption is refused at or after this instant. Null means no expiry. Use valid_through_date for date labels.Format: date-time
reward.bill_referenceoptional; see conditionsstring | nullReceipt/bill reference connecting the operation to the business transaction.—Example: DEMO-BILL-001
reward.claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
reward.claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
reward.claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
reward.claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
reward.claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
reward.claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
reward.valid_through_dateoptional; see conditionsstring | nullLast inclusive valid calendar date in expiry_timezone. Null means no expiry.Format: date
reward.expiry_timezoneoptional; see conditionsstring | nullIANA business timezone captured when this reward was issued; unchanged by later country edits. Null means no expiry.—Example: Asia/Dubai
usableoptional; see conditionsbooleanWhether the current validation permits use. Redeem still rechecks account, expiry, branch and grant state; do not treat this flag as a reservation.—
owneroptional; see conditionsobjectMasked customer name and phone for confirmation; not the full contact details.—
owner.nameoptional; see conditionsstring | nullDisplay name of the object represented here; do not use it as a stable identifier.—
owner.phoneoptional; see conditionsstring | nullPhone number. International E.164 format is recommended; customer inputs are normalized using the business country.—
branch_idsoptional; see conditionsarray<integer>Branch restrictions for this reward. An empty list means no configured branch restriction.—
reason_codeoptional; see conditionsstring | nullCanonical refusal reason shared with redemption; null when usable.—
messageoptional; see conditionsstring | nullHuman-readable refusal reason.—
snapshotoptional; see conditionsbooleanAlways true: this read-only result does not reserve the voucher.—Example: true
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check that the staff account is active.

application/json · Illustrative example

{
    "error": {
        "code": "unauthenticated",
        "message": "Authentication required."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
403 Access denied

Access denied — Check business entitlements, feature availability and staff permissions. Retrying unchanged will not restore access. Capability denial can return operation_unavailable; a missing exact reversal permission can return forbidden. Refresh /config for the current branch and stop the refused action. An unchanged request is not made valid by using another operation key.

application/json · Illustrative example

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
404 Not found

Not found — Check the public identifier and business context. Resources belonging to another tenant are not accessible.

application/json · Illustrative example

{
    "error": {
        "code": "not_found",
        "message": "Not found."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
409 Conflict

Conflict — Reuse a key only for an identical operation. For voucher_already_used, refresh reward status instead of redeeming again.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_conflict",
        "message": "This key was used with different request details."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
422 Validation or domain refusal

Validation or domain refusal — Correct the fields or handle the domain refusal identified by error.code. Do not retry an unchanged invalid request.

application/json · Illustrative example

{
    "error": {
        "code": "invalid",
        "message": "Some fields are invalid.",
        "fields": {
            "field_name": [
                "A validation message."
            ]
        }
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
429 Rate limited; Retry-After header

Rate limited; Retry-After header — Wait for the Retry-After header before retrying; retain the same operation key for a mutation.

Retry-After — Seconds until a retry is allowed

application/json · Illustrative example

{
    "error": {
        "code": "rate_limited",
        "message": "Too many requests; slow down.",
        "retry_after": 60
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
503 Authorization state unavailable

Authorization state unavailable — Retry after the Retry-After delay using bounded backoff. For a write, retain the same Idempotency-Key and identical payload; do not assume the operation succeeded or fall back to role-based access.

Retry-After — Seconds to wait before retrying authorization resolution; currently 3.

application/json · Illustrative example

{
    "error": {
        "code": "authorization_unavailable",
        "message": "Access settings are unavailable. Try again shortly."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
POST/vouchers/{code}/redeem

Atomic voucher use; optional bill reference and separate voucher errors

Atomically redeem a voucher and record its optional bill_reference. A concurrent or previously completed use returns voucher_already_used; do not retry using a new operation key. In enforced mode, the current branch requires loyalty.rewards.redeem. Legacy/shadow behavior and business feature requirements continue to apply. Capability aliases are UI hints; the server rechecks current authorization. An anonymously enrolled customer may present an issued voucher: current program and reward anonymous-redemption permissions must both be enabled, or the customer must first verify a recovery contact. Scan or enter the voucher code; a membership code alone does not redeem it. Till-generated bill_reference strings of at most 100 characters are accepted. Keep the reference, payload and Idempotency-Key unchanged on retries. Purchase bill uniqueness is business + branch with no daily reset; voucher redemption references have no uniqueness constraint. A generated reference does not identify duplicate entry of a physical bill. Bill reference is optional for earned-voucher redemption; do not require staff to enter or invent one. Idempotency-Key remains mandatory.

Bearer token Idempotency key

Parameters

Name / locationTypeDescriptionConstraints & example
codepath · requiredstringPrivate issued reward/voucher code shown by the customer, not a catalogue ULID or member code. The server trims whitespace and normalizes to uppercase.—Example: DEMO-REWARD
Idempotency-Keyheader · requiredstringClient-generated stable key for one intended mutation, at most 64 characters. Save it before sending; retry an identical request with the same key after a timeout. Changing the payload with that key returns 409 idempotency_conflict.Max length: 64Example: demo-operation-001

Request body application/json · required

Required markers apply within each parent object. Conditional requirements, such as choosing one identifier or a branch, are explained in the descriptions.

FieldTypeDescriptionConstraints & example
bill_referenceoptional; see conditionsstring | nullOptional receipt reference for reconciliation, at most 100 characters. Omitted, null or empty is accepted independently of purchase bill_reference_required.Max length: 100Example: DEMO-REDEEM-001
branch_idoptional; see conditionsintegerLegacy integer branch ID from the authorized branch list, not its public ULID. Managers supply a branch when needed; assigned staff may only use their own branch.—Example: 12
JSON request example
{
    "bill_reference": "DEMO-REDEEM-001"
}
cURL request
curl --request POST "$BASE_URL/vouchers/{code}/redeem" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'Idempotency-Key: UNIQUE_OPERATION_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "bill_reference": "DEMO-REDEEM-001"
}'

Responses

200 Success

Success

application/json · Schema-generated illustration; optional fields and values vary

{
    "reward": {
        "id": "01JB0000000000000000000001",
        "reward": {
            "id": "01JB0000000000000000000001",
            "name": "Free coffee",
            "description": "string",
            "type": "amount_voucher",
            "points_cost": 0,
            "value_label": "string",
            "value_minor": 1000,
            "percent": 0,
            "currency": "GBP",
            "claim": {
                "eligible": true,
                "reason_code": null,
                "message": null,
                "points_required": 20,
                "available_actions": [
                    "claim"
                ]
            }
        },
        "code": "DEMO7KQ2M",
        "status": "available",
        "used_at": "2026-10-24T10:00:00Z",
        "expires_at": "2026-10-24T10:00:00Z",
        "bill_reference": "DEMO-BILL-001",
        "claim": {
            "eligible": true,
            "reason_code": null,
            "message": null,
            "points_required": 20,
            "available_actions": [
                "claim"
            ]
        },
        "valid_through_date": "string",
        "expiry_timezone": "Asia/Dubai"
    },
    "transaction_id": "string"
}
Response schema
FieldTypeDescriptionConstraints & example
rewardoptional; see conditionsobjectIssued customer reward as visible to staff. Lookup and voucher creation omit the code. Validation/redemption use a code supplied by the customer.—
reward.idoptional; see conditionsstringPublic ULID of this issued reward grant. This differs from the catalogue reward ID and its redemption code.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
reward.rewardoptional; see conditionsobjectCatalogue reward definition. Its public ID is used when purchasing a reward.—
reward.reward.idoptional; see conditionsstringPublic ULID of the catalogue reward; pass it as reward when purchasing.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
reward.reward.nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Free coffee
reward.reward.descriptionoptional; see conditionsstring | nullCustomer-facing description of this resource, when configured.—
reward.reward.typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: amount_voucher, free_item, percent_off
reward.reward.points_costoptional; see conditionsinteger | nullPoints required to purchase this catalogue reward.—
reward.reward.value_labeloptional; see conditionsstringFormatted reward value for display, such as £10.00, 15% off or Free item. Use numeric fields for calculations.—
reward.reward.value_minoroptional; see conditionsinteger | nullFixed reward value in integer currency minor units; for example 1000 GBP means £10.00.—Example: 1000
reward.reward.percentoptional; see conditionsnumber | nullPercentage discount for a percentage reward, for example 15 means 15%.—
reward.reward.currencyoptional; see conditionsstringISO 4217 currency code, for example GBP or AED. Monetary amounts are expressed in its minor units.—Example: GBP
reward.reward.claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
reward.reward.claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
reward.reward.claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
reward.reward.claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
reward.reward.claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
reward.reward.claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
reward.codeoptional; see conditionsstringPrivate redemption code. Absent on earned entitlements. Staff creation and lookup never return it; validation/redemption require a code already supplied by the customer.—Example: DEMO7KQ2M
reward.statusoptional; see conditionsstringCurrent lifecycle state of this object. Use the documented values and endpoint rules.Allowed: available, used, expired, revoked, earned
reward.used_atoptional; see conditionsstring | nullISO 8601 timestamp when the reward was used; null or omitted if it has not been used.Format: date-time
reward.expires_atoptional; see conditionsstring | nullExclusive voucher cutoff in ISO 8601 with timezone offset. Redemption is refused at or after this instant. Null means no expiry. Use valid_through_date for date labels.Format: date-time
reward.bill_referenceoptional; see conditionsstring | nullReceipt/bill reference connecting the operation to the business transaction.—Example: DEMO-BILL-001
reward.claimoptional; see conditionsobjectServer-calculated snapshot; mutations always recheck current eligibility.—
reward.claim.eligibleoptional; see conditionsbooleanWhether this actor may claim this reward now.—Example: true
reward.claim.reason_codeoptional; see conditionsstring | nullStable refusal reason, or null when eligible. For anonymous customers, customer_confirmation_required means the customer must create the voucher in their private browser; verified_contact_required means verification is required by current reward/program policy.—Example: null
reward.claim.messageoptional; see conditionsstring | nullHuman-readable refusal message.—Example: null
reward.claim.points_requiredoptional; see conditionsinteger | nullPoints spent at claim creation; zero for an earned entitlement.—Example: 20
reward.claim.available_actionsoptional; see conditionsarray<string>Actions the client may present. Discovery is not authorization.—Example: ["claim"]
reward.valid_through_dateoptional; see conditionsstring | nullLast inclusive valid calendar date in expiry_timezone. Null means no expiry.Format: date
reward.expiry_timezoneoptional; see conditionsstring | nullIANA business timezone captured when this reward was issued; unchanged by later country edits. Null means no expiry.—Example: Asia/Dubai
transaction_idoptional; see conditionsstringPublic transaction/audit ULID returned by a write. Save it for reconciliation and supported reversal workflows; it may be null when no ledger entry was created.Min length: 26 · Max length: 26
400 Missing idempotency key

Missing idempotency key — Supply a stable key for the intended operation, then retry.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_key_required",
        "message": "An Idempotency-Key header is required."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check that the staff account is active.

application/json · Illustrative example

{
    "error": {
        "code": "unauthenticated",
        "message": "Authentication required."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
403 Access denied

Access denied — Check business entitlements, feature availability and staff permissions. Retrying unchanged will not restore access. Capability denial can return operation_unavailable; a missing exact reversal permission can return forbidden. Refresh /config for the current branch and stop the refused action. An unchanged request is not made valid by using another operation key.

application/json · Illustrative example

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
404 Not found

Not found — Check the public identifier and business context. Resources belonging to another tenant are not accessible.

application/json · Illustrative example

{
    "error": {
        "code": "not_found",
        "message": "Not found."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
409 Conflict

Conflict — Reuse a key only for an identical operation. For voucher_already_used, refresh reward status instead of redeeming again.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_conflict",
        "message": "This key was used with different request details."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
422 Validation or domain refusal

Validation or domain refusal — Correct the fields or handle the domain refusal identified by error.code. Do not retry an unchanged invalid request.

application/json · Illustrative example

{
    "error": {
        "code": "invalid",
        "message": "Some fields are invalid.",
        "fields": {
            "field_name": [
                "A validation message."
            ]
        }
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
429 Rate limited; Retry-After header

Rate limited; Retry-After header — Wait for the Retry-After header before retrying; retain the same operation key for a mutation.

Retry-After — Seconds until a retry is allowed

application/json · Illustrative example

{
    "error": {
        "code": "rate_limited",
        "message": "Too many requests; slow down.",
        "retry_after": 60
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
503 Authorization state unavailable

Authorization state unavailable — Retry after the Retry-After delay using bounded backoff. For a write, retain the same Idempotency-Key and identical payload; do not assume the operation succeeded or fall back to role-based access.

Retry-After — Seconds to wait before retrying authorization resolution; currently 3.

application/json · Illustrative example

{
    "error": {
        "code": "authorization_unavailable",
        "message": "Access settings are unavailable. Try again shortly."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—

Activity & messaging

GET/activity

Own successful audited actions

Own successful actions at the selected authorized branch, newest created_at first (ID breaks ties). Optional date selects a business-local calendar day, including daylight-saving changes. Details are nullable when inapplicable or the encrypted customer snapshot has been erased. Development records without request metadata may have null amount_minor or bill_reference.

Bearer token

Parameters

Name / locationTypeDescriptionConstraints & example
branch_idquery · optional; see conditionsintegerLegacy integer branch ID from the authorized branch list, not its public ULID. Managers supply a branch when needed; assigned staff may only use their own branch.—Example: 12
pagequery · optional; see conditionsintegerOne-based page number. Omit to request the first page; use meta.last_page to know when to stop.Minimum: 1 · Default: 1Example: 1
per_pagequery · optional; see conditionsintegerNumber of records requested per page. Defaults to 20; minimum 1 and maximum 50. Use pagination metadata to retrieve further pages.Minimum: 1 · Maximum: 50 · Default: 20Example: 20
datequery · optional; see conditionsstringYYYY-MM-DD in /config.business.timezone. Invalid dates return 422 invalid. Omit for all dates.Format: dateExample: 2026-10-30
cURL request
curl --request GET "$BASE_URL/activity?page=1&per_page=20" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer YOUR_TOKEN'

Responses

200 Success

Success

application/json · Illustrative example

{
    "actions": [
        {
            "id": "01JF0000000000000000000002",
            "action": "voucher_redeem",
            "branch_id": 1,
            "device": "Till 2",
            "created_at": "2026-10-30T10:14:00+00:00",
            "transaction_id": "01JF0000000000000000000002",
            "customer": {
                "name": "S***"
            },
            "amount_minor": null,
            "points": null,
            "bill_reference": "TILL2-20261030-101400-R7KQ",
            "reward": {
                "name": "Free coffee or tea"
            }
        },
        {
            "id": "01JF0000000000000000000001",
            "action": "purchase",
            "branch_id": 1,
            "device": "Till 2",
            "created_at": "2026-10-30T10:12:00+00:00",
            "transaction_id": "01JD0000000000000000000001",
            "customer": {
                "name": "S***"
            },
            "amount_minor": 346,
            "points": 3,
            "bill_reference": "20431",
            "reward": null
        }
    ],
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 50,
        "total": 2
    }
}
Response schema
FieldTypeDescriptionConstraints & example
actionsoptional; see conditionsarray<object>Page of audit operations performed by the authenticated staff member.—
actions[].idoptional; see conditionsstringIdentifier of this object. Preserve the documented type and use the ID rather than a display name.Min length: 26 · Max length: 26
actions[].actionoptional; see conditionsstringActivity display action. purchase includes purchases and points/award; voucher_redeem includes vouchers/{code}/redeem and rewards/use. Other names match operation aliases.Allowed: purchase, voucher_redeem, customers.create, refunds, rewards.purchase, rewards.create-voucher, transactions.reverse, wallet.send
actions[].branch_idoptional; see conditionsintegerLegacy integer branch ID from the authorized branch list, not its public ULID. Managers supply a branch when needed; assigned staff may only use their own branch.—
actions[].deviceoptional; see conditionsstringInstallation details used to issue a device token and register optional push delivery.—
actions[].created_atoptional; see conditionsstringISO 8601 creation timestamp of the record.Format: date-time
actions[].transaction_idoptional; see conditionsstring | nullPublic transaction/audit ULID returned by a write. Save it for reconciliation and supported reversal workflows; it may be null when no ledger entry was created.Min length: 26 · Max length: 26
actions[].customeroptional; see conditionsobject | nullCustomer with a masked current display name, or null when unavailable.—
actions[].customer.nameoptional; see conditionsstringMasked current display name, e.g. S***.—
actions[].amount_minoroptional; see conditionsinteger | nullSubmitted purchase amount in business currency minor units, including zero-point purchases.—
actions[].pointsoptional; see conditionsinteger | nullPoints awarded for a purchase; zero when none were awarded.—
actions[].bill_referenceoptional; see conditionsstring | nullRecorded bill reference, or null when inapplicable or no longer retained.Max length: 100
actions[].rewardoptional; see conditionsobject | nullReward display metadata, or null when inapplicable or no longer retained.—
actions[].reward.nameoptional; see conditionsstringReward name at the time of the recorded operation.—
metaoptional; see conditionsobjectPagination metadata for a bounded list response.—
meta.current_pagerequired in parent objectintegerOne-based page number returned for this request.—Example: 1
meta.last_pagerequired in parent objectintegerLast available page number for the current filters.—Example: 1
meta.per_pagerequired in parent objectintegerMaximum number of records included per page, subject to the endpoint limit.—Example: 20
meta.totalrequired in parent objectintegerTotal matching records across all pages, not just the current page.—Example: 1
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check that the staff account is active.

application/json · Illustrative example

{
    "error": {
        "code": "unauthenticated",
        "message": "Authentication required."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
403 Access denied

Access denied — Check business entitlements, feature availability and staff permissions. Retrying unchanged will not restore access. Capability denial can return operation_unavailable; a missing exact reversal permission can return forbidden. Refresh /config for the current branch and stop the refused action. An unchanged request is not made valid by using another operation key.

application/json · Illustrative example

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
404 Not found

Not found — Check the public identifier and business context. Resources belonging to another tenant are not accessible.

application/json · Illustrative example

{
    "error": {
        "code": "not_found",
        "message": "Not found."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
409 Conflict

Conflict — Reuse a key only for an identical operation. For voucher_already_used, refresh reward status instead of redeeming again.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_conflict",
        "message": "This key was used with different request details."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
422 Validation or domain refusal

Validation or domain refusal — Correct the fields or handle the domain refusal identified by error.code. Do not retry an unchanged invalid request.

application/json · Illustrative example

{
    "error": {
        "code": "invalid",
        "message": "Some fields are invalid.",
        "fields": {
            "field_name": [
                "A validation message."
            ]
        }
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
429 Rate limited; Retry-After header

Rate limited; Retry-After header — Wait for the Retry-After header before retrying; retain the same operation key for a mutation.

Retry-After — Seconds until a retry is allowed

application/json · Illustrative example

{
    "error": {
        "code": "rate_limited",
        "message": "Too many requests; slow down.",
        "retry_after": 60
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
503 Authorization state unavailable

Authorization state unavailable — Retry after the Retry-After delay using bounded backoff. For a write, retain the same Idempotency-Key and identical payload; do not assume the operation succeeded or fall back to role-based access.

Retry-After — Seconds to wait before retrying authorization resolution; currently 3.

application/json · Illustrative example

{
    "error": {
        "code": "authorization_unavailable",
        "message": "Access settings are unavailable. Try again shortly."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—

Store App

GET/config

Store App configuration, schema version 1

Authenticated business defaults and current branch capabilities. Managers may omit branch_id and use a valid assigned branch as legacy fallback; without one selected_branch is null. No branded customer-app provisioning information. All fields are required; additive optional fields and unknown capability identifiers must be ignored. Breaking semantics require a new version. Operation aliases in staff.permissions and /config.capabilities reflect current branch scope, business access and authorization mode. Do not infer allowed operations from role alone; enforced mode supports grants to staff and denies to managers. Refresh after a branch change or 403. Authorization lookup failures can return 503 authorization_unavailable; honor Retry-After. When capabilities includes points.award, loyalty.bill_amount_required and loyalty.bill_reference_required are required booleans reflecting the business's current Loyalty Settings, both defaulting to true. They describe the amount and bill_reference fields of POST /points/award; business managers can change each requirement independently. The flags do not grant operation permissions. Programme state and requirements are present independently of points.award permission and branch selection. loyalty.available is false while the programme is paused; loyalty is null only when the business lacks the loyalty feature. Use the flags for required indicators and client validation, refresh config after branch changes, and show a retry state if config cannot be loaded rather than treating missing flags as optional fields.

Bearer token

Parameters

Name / locationTypeDescriptionConstraints & example
branch_idquery · optional; see conditionsintegerLegacy integer branch ID from the authorized branch list, not its public ULID. Managers supply a branch when needed; assigned staff may only use their own branch. Omit if no selection is needed; a manager may receive no active branch until one is selected.—Example: 12
cURL request
curl --request GET "$BASE_URL/config" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer YOUR_TOKEN'

Responses

200 Current Store App configuration

Current Store App configuration

application/json · Illustrative example

Awarding available

{
    "schema_version": 1,
    "input_profile": {
        "country_code": "AE",
        "country_name": "United Arab Emirates",
        "dialing_code": "+971",
        "address_format": null,
        "fields": {
            "phone": {
                "label": "Phone Number",
                "placeholder": "+971 50 123 4567"
            },
            "city": {
                "label": "City",
                "placeholder": "City or town",
                "suggestions": []
            },
            "area": {
                "label": "Area",
                "placeholder": "Neighbourhood or area",
                "suggestions": []
            },
            "province": {
                "label": "Emirate",
                "placeholder": "Emirate",
                "suggestions": []
            },
            "postal_code": {
                "label": "Postal Code",
                "placeholder": "Postal code"
            },
            "address": {
                "label": "Address",
                "placeholder": "Building and street address"
            }
        }
    },
    "customer_identifier_types": [
        "member_code",
        "phone",
        "email"
    ],
    "loyalty": {
        "points_label": "points",
        "redemption_model": "catalogue",
        "bill_amount_required": true,
        "bill_reference_required": true,
        "available": true
    },
    "business": {
        "id": 1,
        "public_id": "01J00000000000000000000001",
        "name": "Example Business",
        "currency": "AED",
        "locale": "en_AE",
        "timezone": "Asia/Dubai"
    },
    "staff": {
        "id": 7,
        "public_id": "01J00000000000000000000007",
        "name": "Example Manager",
        "role": "business_admin"
    },
    "selected_branch": {
        "id": 12,
        "public_id": "01J00000000000000000000012",
        "name": "Example Branch"
    },
    "branch_required": false,
    "can_select_branch": true,
    "capabilities": [
        "customers.lookup",
        "points.award",
        "purchases.preview",
        "purchases",
        "activity"
    ],
    "unavailable_reason": null
}

Branch selection required

{
    "schema_version": 1,
    "input_profile": {
        "country_code": "AE",
        "country_name": "United Arab Emirates",
        "dialing_code": "+971",
        "address_format": null,
        "fields": {
            "phone": {
                "label": "Phone Number",
                "placeholder": "+971 50 123 4567"
            },
            "city": {
                "label": "City",
                "placeholder": "City or town",
                "suggestions": []
            },
            "area": {
                "label": "Area",
                "placeholder": "Neighbourhood or area",
                "suggestions": []
            },
            "province": {
                "label": "Emirate",
                "placeholder": "Emirate",
                "suggestions": []
            },
            "postal_code": {
                "label": "Postal Code",
                "placeholder": "Postal code"
            },
            "address": {
                "label": "Address",
                "placeholder": "Building and street address"
            }
        }
    },
    "customer_identifier_types": [
        "member_code",
        "phone",
        "email"
    ],
    "loyalty": {
        "available": true,
        "points_label": "points",
        "redemption_model": "catalogue",
        "bill_amount_required": true,
        "bill_reference_required": true
    },
    "business": {
        "id": 1,
        "public_id": "01J00000000000000000000001",
        "name": "Example Business",
        "currency": "AED",
        "locale": "en_AE",
        "timezone": "Asia/Dubai"
    },
    "staff": {
        "id": 7,
        "public_id": "01J00000000000000000000007",
        "name": "Example Manager",
        "role": "business_admin"
    },
    "selected_branch": null,
    "branch_required": true,
    "can_select_branch": true,
    "capabilities": [],
    "unavailable_reason": "branch_required"
}

Awarding unavailable

{
    "schema_version": 1,
    "input_profile": {
        "country_code": "AE",
        "country_name": "United Arab Emirates",
        "dialing_code": "+971",
        "address_format": null,
        "fields": {
            "phone": {
                "label": "Phone Number",
                "placeholder": "+971 50 123 4567"
            },
            "city": {
                "label": "City",
                "placeholder": "City or town",
                "suggestions": []
            },
            "area": {
                "label": "Area",
                "placeholder": "Neighbourhood or area",
                "suggestions": []
            },
            "province": {
                "label": "Emirate",
                "placeholder": "Emirate",
                "suggestions": []
            },
            "postal_code": {
                "label": "Postal Code",
                "placeholder": "Postal code"
            },
            "address": {
                "label": "Address",
                "placeholder": "Building and street address"
            }
        }
    },
    "customer_identifier_types": [
        "member_code",
        "phone",
        "email"
    ],
    "loyalty": {
        "points_label": "points",
        "redemption_model": "catalogue",
        "bill_amount_required": true,
        "bill_reference_required": true,
        "available": true
    },
    "business": {
        "id": 1,
        "public_id": "01J00000000000000000000001",
        "name": "Example Business",
        "currency": "AED",
        "locale": "en_AE",
        "timezone": "Asia/Dubai"
    },
    "staff": {
        "id": 7,
        "public_id": "01J00000000000000000000007",
        "name": "Example Manager",
        "role": "business_admin"
    },
    "selected_branch": {
        "id": 12,
        "public_id": "01J00000000000000000000012",
        "name": "Example Branch"
    },
    "branch_required": false,
    "can_select_branch": true,
    "capabilities": [],
    "unavailable_reason": "no_available_operations"
}

Optional bill fields

{
    "schema_version": 1,
    "input_profile": {
        "country_code": "AE",
        "country_name": "United Arab Emirates",
        "dialing_code": "+971",
        "address_format": null,
        "fields": {
            "phone": {
                "label": "Phone Number",
                "placeholder": "+971 50 123 4567"
            },
            "city": {
                "label": "City",
                "placeholder": "City or town",
                "suggestions": []
            },
            "area": {
                "label": "Area",
                "placeholder": "Neighbourhood or area",
                "suggestions": []
            },
            "province": {
                "label": "Emirate",
                "placeholder": "Emirate",
                "suggestions": []
            },
            "postal_code": {
                "label": "Postal Code",
                "placeholder": "Postal code"
            },
            "address": {
                "label": "Address",
                "placeholder": "Building and street address"
            }
        }
    },
    "customer_identifier_types": [
        "member_code",
        "phone",
        "email"
    ],
    "loyalty": {
        "points_label": "points",
        "redemption_model": "catalogue",
        "bill_amount_required": false,
        "bill_reference_required": false,
        "available": true
    },
    "business": {
        "id": 1,
        "public_id": "01J00000000000000000000001",
        "name": "Example Business",
        "currency": "AED",
        "locale": "en_AE",
        "timezone": "Asia/Dubai"
    },
    "staff": {
        "id": 7,
        "public_id": "01J00000000000000000000007",
        "name": "Example Manager",
        "role": "business_admin"
    },
    "selected_branch": {
        "id": 12,
        "public_id": "01J00000000000000000000012",
        "name": "Example Branch"
    },
    "branch_required": false,
    "can_select_branch": true,
    "capabilities": [
        "customers.lookup",
        "points.award",
        "purchases.preview",
        "purchases",
        "activity"
    ],
    "unavailable_reason": null
}

Rewards paused

{
    "schema_version": 1,
    "input_profile": {
        "country_code": "AE",
        "country_name": "United Arab Emirates",
        "dialing_code": "+971",
        "address_format": null,
        "fields": {
            "phone": {
                "label": "Phone Number",
                "placeholder": "+971 50 123 4567"
            },
            "city": {
                "label": "City",
                "placeholder": "City or town",
                "suggestions": []
            },
            "area": {
                "label": "Area",
                "placeholder": "Neighbourhood or area",
                "suggestions": []
            },
            "province": {
                "label": "Emirate",
                "placeholder": "Emirate",
                "suggestions": []
            },
            "postal_code": {
                "label": "Postal Code",
                "placeholder": "Postal code"
            },
            "address": {
                "label": "Address",
                "placeholder": "Building and street address"
            }
        }
    },
    "customer_identifier_types": [
        "member_code",
        "phone",
        "email"
    ],
    "loyalty": {
        "points_label": "points",
        "redemption_model": "catalogue",
        "bill_amount_required": true,
        "bill_reference_required": true,
        "available": false
    },
    "business": {
        "id": 1,
        "public_id": "01J00000000000000000000001",
        "name": "Example Business",
        "currency": "AED",
        "locale": "en_AE",
        "timezone": "Asia/Dubai"
    },
    "staff": {
        "id": 7,
        "public_id": "01J00000000000000000000007",
        "name": "Example Manager",
        "role": "business_admin"
    },
    "selected_branch": {
        "id": 12,
        "public_id": "01J00000000000000000000012",
        "name": "Example Branch"
    },
    "branch_required": false,
    "can_select_branch": true,
    "capabilities": [
        "customers.lookup",
        "points.award",
        "purchases.preview",
        "purchases",
        "activity"
    ],
    "unavailable_reason": null
}
Response schema
FieldTypeDescriptionConstraints & example
schema_versionrequired in parent objectintegerVersion of the Store App configuration payload; currently 1.Allowed: 1
businessrequired in parent objectobjectBusiness identity and regional settings for the resolved tenant.—
business.idrequired in parent objectintegerLegacy integer ID of this business.—
business.public_idrequired in parent objectstringStable public ULID of this business; preserve it separately from the integer id.—
business.namerequired in parent objectstringDisplay name of the object represented here; do not use it as a stable identifier.—
business.currencyrequired in parent objectstringISO 4217 currency code, for example GBP or AED. Monetary amounts are expressed in its minor units.—
business.localerequired in parent objectstringLanguage/region code such as en_GB; determines localized presentation.—
business.timezonerequired in parent objectstringIANA timezone such as Europe/London or Asia/Dubai, used for business-local rules and schedules.—
staffrequired in parent objectobjectAuthenticated staff identity and, where provided, authorized branch/capability information.—
staff.idrequired in parent objectintegerLegacy integer ID of this staff account.—
staff.public_idrequired in parent objectstringStable public ULID of this staff account; preserve it separately from the integer id.—
staff.namerequired in parent objectstringDisplay name of the object represented here; do not use it as a stable identifier.—
staff.rolerequired in parent objectstringAccount role/category, not a complete list of allowed actions. Legacy/shadow mode uses legacy role rules; enforced mode can delegate actions to staff or deny actions to managers. Branch confinement always applies.—
selected_branchrequired in parent objectobject | nullActive branch resolved for this request, or null when a manager still needs to select a branch.—
selected_branch.idrequired in parent objectintegerLegacy integer ID of this selected branch.—
selected_branch.public_idrequired in parent objectstringStable public ULID of this selected branch; preserve it separately from the integer id.Min length: 26 · Max length: 26
selected_branch.namerequired in parent objectstringDisplay name of the object represented here; do not use it as a stable identifier.—
branch_requiredrequired in parent objectbooleanTrue when no active branch is selected and branch-scoped operations cannot run yet.—
can_select_branchrequired in parent objectbooleanWhether this staff role may select from the business authorized branches.—
capabilitiesrequired in parent objectarray<string>Currently runnable operation identifiers: customers.lookup, customers.rewards, customers.create, points.award, vouchers.show, vouchers.redeem, purchases.preview, purchases, refunds, rewards, rewards.purchase, rewards.create-voucher, rewards.validate, rewards.use, wallet.send, transactions.reverse, activity. Ignore unknown identifiers; they never grant permission. These are operation aliases, not RBAC permission codes. Legacy/shadow mode preserves legacy rules; enforced mode reflects current grants/denies at the selected branch. Role alone does not authorize an action. transactions.reverse can represent only one allowed reversal kind, so exact target checks may still deny with 403. Refresh after branch changes or authorization errors. This list does not expose an unlimited-award flag.—
unavailable_reasonrequired in parent objectstring | nullReason no operations are available: branch_required or no_available_operations; null when available.Allowed: branch_required, no_available_operations, null
loyaltyrequired in parent objectobject | nullProgramme state and award requirements independent of staff permissions and branch selection. Null only when the loyalty feature is unavailable to the business. available=false means the programme is paused; use capabilities separately to decide which staff operations are permitted. Staff purchase previews use the amount policy and never require a bill reference.—
loyalty.points_labelrequired in parent objectstringBusiness-specific display label for points, such as points or beans.—
loyalty.redemption_modelrequired in parent objectstringLoyalty program model: catalogue exchanges points for rewards; milestones unlocks rewards through card progress.Allowed: catalogue, milestones
loyalty.bill_amount_requiredrequired in parent objectbooleanWhether Store App awards require a bill amount, configured per business in Loyalty Settings and defaulting to true. When true, POST /points/award requires amount: an integer of at least 1 in currency minor units. When false, amount may be omitted or null; omission counts as zero spend and existing visit/fixed earning rules may still apply. Provided amounts must still be positive integers. Staff purchases and previews apply the same requirement to amount_paid_minor (minimum 0 when supplied).—Example: true
loyalty.bill_reference_requiredrequired in parent objectbooleanWhether Store App awards require a bill reference, configured per business in Loyalty Settings and defaulting to true. When true, POST /points/award and staff POST /purchases require bill_reference: a non-empty string of at most 100 characters. When false, it may be omitted or null. Previews always allow omission. No bill reference is invented; reference-based refunds and duplicate-bill detection require one to have been recorded. Idempotency keys and limits remain mandatory.—Example: true
loyalty.availablerequired in parent objectbooleanProgramme availability; false while paused. Does not grant staff permission.—Example: true
input_profilerequired in parent objectobjectCountry-specific presentation hints resolved from the business country. Apply these labels and placeholders without treating phone hints as validation masks. International phone numbers remain allowed.—
input_profile.country_coderequired in parent objectstring | nullISO 3166-1 alpha-2 code of the business country, or null when not configured.—
input_profile.country_namerequired in parent objectstring | nullDisplay name of the business country, or null when not configured.—
input_profile.dialing_coderequired in parent objectstring | nullDefault international dialing prefix used to interpret local phone input; international numbers remain allowed.—
input_profile.address_formatrequired in parent objectstring | nullConfigured country address-format hint, or null when not configured.—
input_profile.fieldsrequired in parent objectobjectLocalized field labels, example placeholders and optional non-exhaustive suggestions.—
input_profile.fields.phonerequired in parent objectobjectPresentation hints for the phone field; these do not impose additional validation rules.—
input_profile.fields.phone.labelrequired in parent objectstringLocalized display label for this input.—
input_profile.fields.phone.placeholderrequired in parent objectstringExample input hint; not a validation mask or a required value.—
input_profile.fields.phone.suggestionsoptional; see conditionsarray<string>Optional non-exhaustive suggestions; free text remains valid.—
input_profile.fields.cityrequired in parent objectobjectPresentation hints for the city field; these do not impose additional validation rules.—
input_profile.fields.city.labelrequired in parent objectstringLocalized display label for this input.—
input_profile.fields.city.placeholderrequired in parent objectstringExample input hint; not a validation mask or a required value.—
input_profile.fields.city.suggestionsoptional; see conditionsarray<string>Optional non-exhaustive suggestions; free text remains valid.—
input_profile.fields.arearequired in parent objectobjectPresentation hints for the area field; these do not impose additional validation rules.—
input_profile.fields.area.labelrequired in parent objectstringLocalized display label for this input.—
input_profile.fields.area.placeholderrequired in parent objectstringExample input hint; not a validation mask or a required value.—
input_profile.fields.area.suggestionsoptional; see conditionsarray<string>Optional non-exhaustive suggestions; free text remains valid.—
input_profile.fields.provincerequired in parent objectobjectPresentation hints for the province field; these do not impose additional validation rules.—
input_profile.fields.province.labelrequired in parent objectstringLocalized display label for this input.—
input_profile.fields.province.placeholderrequired in parent objectstringExample input hint; not a validation mask or a required value.—
input_profile.fields.province.suggestionsoptional; see conditionsarray<string>Optional non-exhaustive suggestions; free text remains valid.—
input_profile.fields.postal_coderequired in parent objectobjectPresentation hints for the postal code field; these do not impose additional validation rules.—
input_profile.fields.postal_code.labelrequired in parent objectstringLocalized display label for this input.—
input_profile.fields.postal_code.placeholderrequired in parent objectstringExample input hint; not a validation mask or a required value.—
input_profile.fields.postal_code.suggestionsoptional; see conditionsarray<string>Optional non-exhaustive suggestions; free text remains valid.—
input_profile.fields.addressrequired in parent objectobjectPresentation hints for the address field; these do not impose additional validation rules.—
input_profile.fields.address.labelrequired in parent objectstringLocalized display label for this input.—
input_profile.fields.address.placeholderrequired in parent objectstringExample input hint; not a validation mask or a required value.—
input_profile.fields.address.suggestionsoptional; see conditionsarray<string>Optional non-exhaustive suggestions; free text remains valid.—
customer_identifier_typesrequired in parent objectarray<string>Supported customer lookup identifiers for this business, with member_code first. Phone and email follow the configured sign-up methods; an independent Store App without a branded customer app supports both. This list does not verify a customer or grant permission to award points.—Example: ["member_code","phone","email"]
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check that the staff account is active.

application/json · Illustrative example

{
    "error": {
        "code": "unauthenticated",
        "message": "Authentication required."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
403 Access denied

Access denied — Check business entitlements, feature availability and staff permissions. Retrying unchanged will not restore access. Capability denial can return operation_unavailable; a missing exact reversal permission can return forbidden. Refresh /config for the current branch and stop the refused action. An unchanged request is not made valid by using another operation key.

application/json · Illustrative example

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
404 Not found

Not found — Check the public identifier and business context. Resources belonging to another tenant are not accessible.

application/json · Illustrative example

{
    "error": {
        "code": "not_found",
        "message": "Not found."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
409 Conflict

Conflict — Reuse a key only for an identical operation. For voucher_already_used, refresh reward status instead of redeeming again.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_conflict",
        "message": "This key was used with different request details."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
422 Validation or domain refusal

Validation or domain refusal — Correct the fields or handle the domain refusal identified by error.code. Do not retry an unchanged invalid request.

application/json · Illustrative example

{
    "error": {
        "code": "invalid",
        "message": "Some fields are invalid.",
        "fields": {
            "field_name": [
                "A validation message."
            ]
        }
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
429 Rate limited; Retry-After header

Rate limited; Retry-After header — Wait for the Retry-After header before retrying; retain the same operation key for a mutation.

Retry-After — Seconds until a retry is allowed

application/json · Illustrative example

{
    "error": {
        "code": "rate_limited",
        "message": "Too many requests; slow down.",
        "retry_after": 60
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
503 Authorization state unavailable

Authorization state unavailable — Retry after the Retry-After delay using bounded backoff. For a write, retain the same Idempotency-Key and identical payload; do not assume the operation succeeded or fall back to role-based access.

Retry-After — Seconds to wait before retrying authorization resolution; currently 3.

application/json · Illustrative example

{
    "error": {
        "code": "authorization_unavailable",
        "message": "Access settings are unavailable. Try again shortly."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
GET/branches

Discover authorized active branches

Paginated tenant-scoped discovery; branch staff see only their assignment. Managers select per native request; browser managers use CSRF-protected branch-select. Does not mutate a global selection.

Bearer token

Parameters

Name / locationTypeDescriptionConstraints & example
pagequery · optional; see conditionsintegerOne-based page number. Omit to request the first page; use meta.last_page to know when to stop.Minimum: 1 · Default: 1Example: 1
per_pagequery · optional; see conditionsintegerNumber of records requested per page. Defaults to 20; minimum 1 and maximum 100. Use pagination metadata to retrieve further pages.Minimum: 1 · Maximum: 100 · Default: 20Example: 20
cURL request
curl --request GET "$BASE_URL/branches?page=1&per_page=20" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer YOUR_TOKEN'

Responses

200 A bounded page of authorized active branches

A bounded page of authorized active branches

application/json · Schema-generated illustration; optional fields and values vary

{
    "branches": [
        {
            "id": 0,
            "public_id": "string",
            "name": "Demo High Street"
        }
    ],
    "meta": {
        "current_page": 0,
        "last_page": 0,
        "per_page": 0,
        "total": 0
    }
}
Response schema
FieldTypeDescriptionConstraints & example
branchesrequired in parent objectarray<object>Branches included in this response. Follow pagination where supplied rather than assuming the list is complete.—
branches[].idrequired in parent objectintegerLegacy integer branch ID. Use this value as branch_id on staff operations; do not pass the public_id ULID.—
branches[].public_idrequired in parent objectstringExisting ULID identifier.Min length: 26 · Max length: 26
branches[].namerequired in parent objectstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Demo High Street
metarequired in parent objectobjectPagination metadata for this result set.—
meta.current_pagerequired in parent objectintegerOne-based page number returned for this request.—
meta.last_pagerequired in parent objectintegerLast available page number for the current filters.—
meta.per_pagerequired in parent objectintegerMaximum number of records included per page, subject to the endpoint limit.—
meta.totalrequired in parent objectintegerTotal matching records across all pages, not just the current page.—
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check that the staff account is active.

application/json · Illustrative example

{
    "error": {
        "code": "unauthenticated",
        "message": "Authentication required."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
403 Access denied

Access denied — Check business entitlements, feature availability and staff permissions. Retrying unchanged will not restore access. Capability denial can return operation_unavailable; a missing exact reversal permission can return forbidden. Refresh /config for the current branch and stop the refused action. An unchanged request is not made valid by using another operation key.

application/json · Illustrative example

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
404 Not found

Not found — Check the public identifier and business context. Resources belonging to another tenant are not accessible.

application/json · Illustrative example

{
    "error": {
        "code": "not_found",
        "message": "Not found."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
409 Conflict

Conflict — Reuse a key only for an identical operation. For voucher_already_used, refresh reward status instead of redeeming again.

application/json · Illustrative example

{
    "error": {
        "code": "idempotency_conflict",
        "message": "This key was used with different request details."
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
422 Validation or domain refusal

Validation or domain refusal — Correct the fields or handle the domain refusal identified by error.code. Do not retry an unchanged invalid request.

application/json · Illustrative example

{
    "error": {
        "code": "invalid",
        "message": "Some fields are invalid.",
        "fields": {
            "field_name": [
                "A validation message."
            ]
        }
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—
429 Rate limited; Retry-After header

Rate limited; Retry-After header — Wait for the Retry-After header before retrying; retain the same operation key for a mutation.

Retry-After — Seconds until a retry is allowed

application/json · Illustrative example

{
    "error": {
        "code": "rate_limited",
        "message": "Too many requests; slow down.",
        "retry_after": 60
    }
}
Response schema
FieldTypeDescriptionConstraints & example
errorrequired in parent objectobjectApplication error envelope. Use code for application behavior and message for display.—
error.coderequired in parent objectstringStable application error code such as invalid or rate_limited. Use this value for application decisions, not message text.—Example: invalid
error.messagerequired in parent objectstringHuman-readable message; do not parse its wording to decide application behavior.—
error.fieldsoptional; see conditionsobjectValidation messages keyed by request field name, including dotted paths for nested fields.—
error.retry_afteroptional; see conditionsintegerSeconds to wait before retrying after a rate limit. Also honor the Retry-After response header.—
error.min_app_versionoptional; see conditionsstringMinimum supported app version by platform, or the version required by an upgrade error.—

DEEPER INTEGRATION

Full integration guide

Start with the customer or staff workflow below, then follow the endpoint schemas above.

Integration workflows

Integration guide

The references describe the shared Customer and Staff API contracts. Documentation is public; API access requires the business's enabled features and access grants. Ask the platform team to arrange access for the intended business and environment.

Business-specific setup

Before development, obtain the business name, environment (test or production), hosted API base URL, enabled features and test account details. For a customer app, also obtain that business app's X-App-Id and confirm whether phone, email or both sign-in methods are enabled. Agree how the developer will receive test verification codes.

For a staff app, obtain a personal test staff username, a password shared through a secure channel, the account's role and its authorized branch assignments. Discover branch IDs using the staff /branches endpoint. Customer branch path parameters use public ULIDs; staff branch_id parameters use the returned integer IDs. IDs and app IDs from one database/environment must not be assumed valid in another.

API documentation access does not activate a business or grant API permissions. Never send platform administrator credentials or provider secrets to an app developer. Customer and staff bearer tokens are obtained through their respective sign-in flows.

Customer app workflow

  1. Obtain the business app's X-App-Id from the platform team. It identifies the app and does not authenticate a customer.
  2. Send X-App-Id on every customer request. Load GET /config for enabled sign-in methods and app settings, and GET /branches for the paginated branch list.
  3. Call POST /auth/start with an allowed email or phone identifier and the installation's stable device_id. Call POST /auth/verify with the returned challenge, delivered code and device details.
  4. Store the returned token securely and send Authorization: Bearer YOUR_TOKEN on authenticated requests. Use GET /me for the customer's profile and membership. Loyalty endpoints require Loyalty access.
  5. Use POST /auth/logout to revoke the current device token. A 401 requires sign-in again. If an endpoint returns 426 upgrade_required, follow its minimum app version requirement.

Phone sign-in must be enabled and have usable SMS configuration, or explicit per-app test OTP eligibility. Generated codes normally have six digits; only listed test phone numbers in an app with test mode enabled use 112233, without sending SMS. This exception never applies to email, identifier-linking or account-deletion codes. A phone option advertised in config does not make unlisted numbers eligible for a test code.

If sign-in returns app_verification_required, obtain a verification proof through the platform team's configured trusted issuer. Never embed server secrets in an app.

For an otherwise valid customer token, 403 account_unavailable with error.reason: suspended means the account is on hold: show the suspension screen and stop automatic retries. The same reason is returned by auth/verify or auth/complete after a correct verification code. auth/start does not reveal suspension. Missing, expired, revoked and wrong-business tokens remain 401 unauthenticated; do not interpret every 401 or 403 as suspension. A suspended profile cannot be fetched through /me, so use the error envelope rather than waiting for customer.status.

Using the Swagger explorer

Open the additional Swagger UI from the Customer or Staff detailed reference (or the documentation overview). Choose Customer Swagger or Staff Swagger, filter by path/keyword, and expand an operation to see its parameters, request body, examples and response schemas. In request/response examples, use the schema/model tab to inspect individual field descriptions and constraints. Use Download OpenAPI to import the same contract into your API client.

Select Authorize to enter the customer app ID and/or the token for the relevant API. Enter bearer tokens without the Bearer prefix; Swagger adds that header prefix. Customer app IDs identify the business app and do not sign a customer in. Staff requests need a staff token rather than an app ID. Authorization values are cleared on refresh and are never embedded in the public page.

Try it out sends real requests to the displayed server, using the credentials you enter. Interactive GET, POST, PUT, PATCH and DELETE operations are enabled in every environment, including the hosted development installation. You can sign in through the documented authentication endpoints, then enter the returned token in Authorize. Use dedicated test accounts in the agreed test environment: writes can create accounts, send verification codes and change business records. Existing API permissions and rate limits apply to every executed request.

Staff app workflow

  1. Obtain a personal staff account from the business administrator.
  2. Call POST /login with the username, password and device name. Staff requests use the Staff API base URL and do not send X-App-Id.
  3. Store the returned bearer token securely. Fetch GET /config, GET /me and GET /branches to initialize the app. Re-fetch config after branch changes or authorization errors; cached capabilities do not authorize operations.
  4. Assigned staff act within their own branch. Authorized business admins supply branch_id for operations that require a branch. Use only branches returned for the authenticated account.
  5. Follow the documented customer lookup, purchase, points and voucher workflows. Use the customer's member code to buy a reward and the reward's code to redeem it. End the device session with POST /logout.

Staff access requires Internal App API Access. Loyalty operations additionally require Loyalty. Staff access is independent of a branded customer app and does not require an external POS API key.

Required fields when awarding points

Business managers control Require a bill amount and Require a bill reference under Loyalty Settings → Store App bill fields. Both settings default to on and can be changed independently. Changes are audited and apply to that business.

After sign-in, call GET /api/staff/v1/config. Show the award form only when capabilities contains points.award. Read the saved requirements from loyalty:

{
  "loyalty": {
    "points_label": "points",
    "redemption_model": "catalogue",
    "bill_amount_required": false,
    "bill_reference_required": false
  }
}

This example is a response excerpt for a business that made both fields optional; configuration schema_version remains 1. When awarding is unavailable or no branch is selected, loyalty is null.

Config property Field in POST /api/staff/v1/points/award When true When false
loyalty.bill_amount_required amount Required integer greater than zero, in currency minor units May be omitted, null or empty; supplied values must still be positive integers
loyalty.bill_reference_required bill_reference Required non-empty string, maximum 100 characters May be omitted, null or empty; supplied values must still be strings of at most 100 characters

Mark and validate each input according to its own flag. For an AED bill of 25.50, send amount: 2550; the server calculates the points. amount_paid_minor is prohibited on points/award; use that name for purchases and purchases/preview. Those staff endpoints apply the same amount requirement, with minimum 0 when a value is supplied. Staff purchases apply the same reference requirement; previews always allow an omitted reference. External POS requirements are unchanged.

When both flags are false, an award can be submitted with only the customer:

{ "customer": { "member_code": "48213906574" } }

The request still needs the staff bearer token and a stable Idempotency-Key. An omitted amount counts as zero spend. Existing per-visit rules, eligible fixed bonuses and zero-spend bands can still award points. Spend-based rules do not invent spending, and minimum-spend conditions still apply. If no earning rule qualifies, the accepted award earns zero points and still consumes one staff admission. A missing bill reference stays null; reference-based refunds and duplicate-bill detection need a reference recorded at submission. Idempotency, permissions and staff limits apply even when both fields are omitted.

Missing required fields return HTTP 422, error.code: invalid, with messages in error.fields.amount or error.fields.bill_reference. Show them beside the inputs. The server reads the current saved policy for every new submission; request flags cannot override it. Refresh config after branch or policy changes and validation/authorization errors. If config cannot load, show a retry state before enabling the form. Retry an uncertain award with the same key and payload; a successful historical retry returns the original result without awarding again.

In Staff Swagger, expand GET /config and select Awarding available, Optional bill fields, Branch selection required or Awarding unavailable. Expand POST /points/award for request examples and its 422 examples for field and amount errors. The detailed reference and Postman show the same responses.

Current capabilities and authorization

Use /config.capabilities or staff.permissions for operation aliases, not role labels. Legacy/shadow businesses keep legacy admission rules; enforced businesses apply current branch-scoped grants and denies. Staff can receive delegated actions and managers can lose actions. Neither a grant nor a cached capability widens branch scope or activates a missing feature/add-on. Refresh after branch changes or 403 errors.

For reversal, transactions.reverse can mean only one permitted target kind: earn/adjust reversal, points-purchased reward revocation, or voucher-use voiding. The server checks the exact target permission again, including before an idempotent replay. Bulk notifications additionally need current bulk permission; branch-bound staff remain limited to their branch's recipients.

A 503 authorization_unavailable with Retry-After: 3 means access settings could not be resolved; it does not grant legacy access. Retry with bounded backoff, keeping the same key/payload for writes. Do not fall back to cached role checks or issue a new key for an uncertain write.

Earning, corrections and queued notifications

Staff/POS purchases always record source pos; the compatibility channel input cannot select app-only rules. occurred_at, if supplied, must be within the preceding 24 hours and not in the future. Point holds and expiry use server processing time; current-day limits use the current business day.

API and dashboard till awards share each account's daily allowance, including zero-point bills. Identical retries do not consume another allowance. In enforced mode the separate loyalty.points.award_unlimited permission controls staff-limit exemption, independently of role; a supported-purchase maximum still applies. Treat amount/daily refusals as authoritative server decisions.

Use the bill refund workflow for milestone corrections. A refunded earn cannot also be reversed, and a reversed earn cannot subsequently be refunded; double correction returns 422 loyalty_action_not_allowed. A milestone-moving earn reversal returns 422 refund_required.

Manual notifications are reauthorized during queued audience work and before delivery. Revoking the actor's access blocks/skips later unsent unauthorized work; a 202 queued response is not a delivery guarantee. Revocation cannot recall provider requests already sent, and marketing push never falls back to SMS/WhatsApp.

Separate POS integrations

The Staff OpenAPI contract documents /api/staff/v1. External POS integrations use /api/loyalty/v1, a business API key and separate external POS access. Do not substitute a POS key for a staff token or assume all staff routes exist on the POS surface. Ask the platform team for the POS integration contract.

Requests, retries and data handling

  • Send Accept: application/json and Content-Type: application/json for JSON bodies. All example identifiers, codes and credentials are fictional placeholders.
  • Amounts use integer currency minor units. Use the documented public IDs and preserve their types. Dates and timestamps follow the endpoint schema.
  • Follow pagination metadata and documented page-size limits. Honor Retry-After on 429 responses and use bounded backoff.
  • Save one Idempotency-Key for each intended mutation where required. Retry the identical request with the same key after a connection failure. A changed payload with that key returns 409 idempotency_conflict; creating a new key can duplicate an uncertain operation.
  • Handle HTTP status and error.code; do not parse display messages. Validation details are in error.fields where provided.
  • Keep tokens, customer details, reward codes and wallet links private. Never put credentials in query strings or public logs. Use test accounts in the agreed test environment before issuing writes against production.

The endpoint reference and downloadable OpenAPI contracts contain request fields, response schemas, examples and endpoint-specific errors. Deployment, server configuration and credential provisioning remain platform-managed.