I IMBOSS / Developers
BUILD WITH IMBOSS v1.4.4

Customer API reference.

Bring your branded app to life. Sign in customers, show their loyalty progress and connect them with rewards.

BASE URLhttps://www.zsuite.zelotech.com/api/app/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. Load config, then verify a codeSend X-App-Id from technical provisioning. Call /auth/start, then /auth/verify with the delivered code.
  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.zsuite.zelotech.com/api/app/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. X-App-Id identifies the business app on every request, including public sign-in routes.

Token lifetime

New customer tokens expire after 30 days. 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 at most 50. Public routes allow 30 requests/minute per IP; authenticated routes allow 120/minute per token. OTP sends have additional limits. 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 /auth/verify for a customer installation. Continue sending X-App-Id on authenticated requests.Bearer YOUR_TOKEN
X-App-IdBusiness app public identifier supplied by the platform team. Send X-App-Id on every customer API request, including sign-in. This identifies the business app and is not a customer authentication token.YOUR_APP_ID
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 X-App-Id for customer requests.
403Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.
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.
426App upgrade required — Use the supported minimum app version reported by configuration.
429Rate limited; Retry-After header — Wait for the Retry-After header before retrying; retain the same operation key for a mutation.
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 customer API

Configuration

GET/config

Branding, sign-in methods and feature availability

Read branding, enabled sign-in methods, minimum app versions and feature availability before displaying the sign-in screen. A public app ID is required; a customer token is not. Configured phone sign-in is advertised only when the selected phone verification provider is available or this active app has an explicit test-number allow-list with test OTP enabled. A globally advertised phone option does not make every number a test number: without usable SMS, a non-listed number can still return 422 method_not_enabled.

No bearer tokenX-App-Id
cURL request
curl --request GET "$BASE_URL/config" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID'

Responses

200 Success

Success

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

{
    "business": {
        "name": "string",
        "currency": "string",
        "locale": "string",
        "timezone": "string"
    },
    "app": {
        "name": "string",
        "sign_up_methods": [
            "phone"
        ],
        "min_app_version": {
            "ios": "string",
            "android": "string"
        },
        "customer_experience_mode": "app_only"
    },
    "card": {
        "title": "string",
        "logo_url": "string",
        "color": "string",
        "points_label": "string",
        "redemption_model": "string",
        "earn_rule_label": "string"
    },
    "whatsapp_number": "string",
    "google_wallet": false,
    "apple_wallet": false,
    "input_profile": {
        "country_code": "string",
        "country_name": "string",
        "dialing_code": "string",
        "address_format": "string",
        "fields": {
            "phone": {
                "label": "string",
                "placeholder": "string",
                "suggestions": [
                    "string"
                ]
            },
            "city": {
                "label": "string",
                "placeholder": "string",
                "suggestions": [
                    "string"
                ]
            },
            "area": {
                "label": "string",
                "placeholder": "string",
                "suggestions": [
                    "string"
                ]
            },
            "province": {
                "label": "string",
                "placeholder": "string",
                "suggestions": [
                    "string"
                ]
            },
            "postal_code": {
                "label": "string",
                "placeholder": "string",
                "suggestions": [
                    "string"
                ]
            },
            "address": {
                "label": "string",
                "placeholder": "string",
                "suggestions": [
                    "string"
                ]
            }
        }
    },
    "phone_otp": {
        "default_country": "string",
        "countries": [
            {
                "code": "GB",
                "name": "United Kingdom",
                "dialing_code": "+44",
                "flag": "🇬🇧"
            }
        ]
    },
    "anonymous": {
        "enabled": false,
        "remember_days": 0
    },
    "loyalty": {
        "available": false
    },
    "legal": {
        "terms_url": "https://example.com/resource",
        "privacy_url": "https://example.com/resource"
    }
}
Response schema
FieldTypeDescriptionConstraints & example
businessoptional; see conditionsobjectBusiness identity and regional settings for the resolved tenant.—
business.nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—
business.currencyoptional; see conditionsstringISO 4217 currency code, for example GBP or AED. Monetary amounts are expressed in its minor units.—
business.localeoptional; see conditionsstringLanguage/region code such as en_GB; determines localized presentation.—
business.timezoneoptional; see conditionsstringIANA timezone such as Europe/London or Asia/Dubai, used for business-local rules and schedules.—
appoptional; see conditionsobjectCustomer app display settings and supported sign-in policy.—
app.nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—
app.sign_up_methodsoptional; see conditionsarray<string>Enabled methods for both sign-in and sign-up. Show only these choices, including for returning customers. Configured phone sign-in is advertised only when a usable SMS verification account is available or this active app has an explicit test-number allow-list with test OTP enabled. A globally advertised phone option does not make every number a test number: without usable SMS, a non-listed number can still return 422 method_not_enabled.—
app.min_app_versionoptional; see conditionsobjectMinimum supported app version by platform, or the version required by an upgrade error.—
app.min_app_version.iosoptional; see conditionsstringMinimum supported iOS app version as major.minor.patch; absent when no minimum is configured.—
app.min_app_version.androidoptional; see conditionsstringMinimum supported Android app version as major.minor.patch; absent when no minimum is configured.—
app.customer_experience_modeoptional; see conditionsstringPlatform-managed customer experience mode. Native customer requests are refused with 403 customer_channel_unavailable for app_free_only. Staff access and installed Wallet validity are independent.Allowed: app_only, app_free_only, hybrid
cardoptional; see conditionsobject | nullLoyalty card branding or milestone progress, depending on this response.—
card.titleoptional; see conditionsstringCustomer-facing title printed on the loyalty card.—
card.logo_urloptional; see conditionsstring | nullURL of the business loyalty card logo, when configured.—
card.coloroptional; see conditionsstringConfigured loyalty card brand color, for example #6b3e26.—
card.points_labeloptional; see conditionsstringBusiness-specific display label for points, such as points or beans.—
card.redemption_modeloptional; see conditionsstringLoyalty program model: catalogue exchanges points for rewards; milestones unlocks rewards through card progress.—
card.earn_rule_labeloptional; see conditionsstring | nullShort text set by the business describing how points are earned. Hide the line when null.Max length: 60
whatsapp_numberoptional; see conditionsstring | nullBusiness WhatsApp number in international format, or null when none is configured.—
google_walletoptional; see conditionsbooleanWhether Google Wallet is available for this business/customer.—
apple_walletoptional; see conditionsbooleanWhether Apple Wallet pass downloads are configured for this business.—
input_profileoptional; see conditionsobjectCountry-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.—
phone_otpoptional; see conditionsobjectCountries this business can send phone verification codes to. Super admins switch them on per business from a reference list of every country; the business's own country is on by default. Build the phone country-code picker from these only; the server refuses other numbers with 422 country_not_allowed. An empty list means phone codes are unavailable.—
phone_otp.default_countryrequired in parent objectstring | nullISO 3166-1 alpha-2 code to preselect: the business country when allowed, else the first listed; null when the list is empty.—
phone_otp.countriesrequired in parent objectarray<object>Business country first, then by name.—
phone_otp.countries[].coderequired in parent objectstringISO 3166-1 alpha-2 code.—Example: GB
phone_otp.countries[].namerequired in parent objectstringCountry name in English.—Example: United Kingdom
phone_otp.countries[].dialing_coderequired in parent objectstringInternational dialing prefix with a leading +.—Example: +44
phone_otp.countries[].flagrequired in parent objectstringFlag emoji.—Example: 🇬🇧
anonymousoptional; see conditionsobjectAdditional optional browser enrollment policy. Native anonymous enrollment is not supported.—
anonymous.enabledoptional; see conditionsbooleanWhether the business currently accepts anonymous browser joins.—
anonymous.remember_daysoptional; see conditionsintegerAbsolute lifetime of anonymous private-browser authority; browser storage may be cleared earlier.—
loyaltyoptional; see conditionsobject | nullCurrent rewards programme availability; null when loyalty is not enabled.—
loyalty.availableoptional; see conditionsbooleanFalse while the store has paused the rewards programme; show the paused screen. Null object means loyalty is not enabled.—
legaloptional; see conditionsobjectPublic Terms of Use and Privacy Policy URLs for this business.—
legal.terms_urloptional; see conditionsstringPublic HTTPS Terms of Use page. Always present: the business's hosted page, or its own website when the business chose that.Format: uri
legal.privacy_urloptional; see conditionsstringPublic HTTPS Privacy Policy page. Always present: the business's hosted page, or its own website when the business chose that.Format: uri
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended

Branches

GET/branches

Active business branches

List only active branches belonging to the app’s business. Use the pagination metadata to load subsequent pages; per_page is capped at 50.

No bearer tokenX-App-Id

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 50. Use pagination metadata to retrieve further pages.Minimum: 1 · Maximum: 50 · Default: 20Example: 20
cURL request
curl --request GET "$BASE_URL/branches?page=1&per_page=20" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID'

Responses

200 Success

Success

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

{
    "branches": [
        {
            "id": "01JB0000000000000000000001",
            "name": "Demo High Street",
            "description": "string",
            "photo_url": "string",
            "address": "string",
            "phone": "+447700900123",
            "latitude": 0,
            "longitude": 0,
            "map_url": "string",
            "opening_hours": [
                {
                    "day_of_week": 0,
                    "closed": false,
                    "open": "string",
                    "close": "string"
                }
            ],
            "pickup_instructions": "string"
        }
    ],
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 20,
        "total": 1
    }
}
Response schema
FieldTypeDescriptionConstraints & example
branchesoptional; see conditionsarray<object>Branches included in this response. Follow pagination where supplied rather than assuming the list is complete.—
branches[].idoptional; see conditionsstringPublic branch ULID. Use it in GET /branches/{branch}; staff branch selection instead uses the staff integer branch ID.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
branches[].nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Demo High Street
branches[].descriptionoptional; see conditionsstring | nullCustomer-facing description of this resource, when configured.—
branches[].photo_urloptional; see conditionsstring | nullPublic URL of the branch image, or null when none is configured.—
branches[].addressoptional; see conditionsstring | nullBranch street/address text, or null when not configured.—
branches[].phoneoptional; see conditionsstring | nullPhone number. International E.164 format is recommended; customer inputs are normalized using the business country.—Example: +447700900123
branches[].latitudeoptional; see conditionsnumber | nullBranch latitude in decimal degrees, or null when not configured.—
branches[].longitudeoptional; see conditionsnumber | nullBranch longitude in decimal degrees, or null when not configured.—
branches[].map_urloptional; see conditionsstring | nullMap/directions URL for the branch, or null when not configured.—
branches[].opening_hoursoptional; see conditionsarray<object> | nullWeekly opening hours in the branch local time. Null means hours have not been configured.—
branches[].opening_hours[].day_of_weekoptional; see conditionsintegerDay index: 0 is Sunday, 1 is Monday, through 6 for Saturday.—
branches[].opening_hours[].closedoptional; see conditionsbooleanWhether this branch is closed for the whole day.—
branches[].opening_hours[].openoptional; see conditionsstring | nullLocal opening time in HH:MM format, or null on a closed day.—
branches[].opening_hours[].closeoptional; see conditionsstring | nullLocal closing time in HH:MM format. A value earlier than open means closing after midnight.—
branches[].pickup_instructionsoptional; see conditionsstring | nullCustomer pickup instructions, or null when absent or ordering is unavailable.—
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 X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
GET/branches/{branch}

One active branch

Retrieve one active branch by its stable public ID. Inactive, unknown and other-business branches return not_found.

No bearer tokenX-App-Id

Parameters

Name / locationTypeDescriptionConstraints & example
branchpath · requiredstringPublic branch ULID from GET /branches, not a branch name or legacy integer ID. Inactive, unknown or other-business branches return 404.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
cURL request
curl --request GET "$BASE_URL/branches/{branch}" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID'

Responses

200 Success

Success

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

{
    "branch": {
        "id": "01JB0000000000000000000001",
        "name": "Demo High Street",
        "description": "string",
        "photo_url": "string",
        "address": "string",
        "phone": "+447700900123",
        "latitude": 0,
        "longitude": 0,
        "map_url": "string",
        "opening_hours": [
            {
                "day_of_week": 0,
                "closed": false,
                "open": "string",
                "close": "string"
            }
        ],
        "pickup_instructions": "string"
    }
}
Response schema
FieldTypeDescriptionConstraints & example
branchoptional; see conditionsobjectCustomer-facing details of an active branch in the resolved business.—
branch.idoptional; see conditionsstringPublic branch ULID. Use it in GET /branches/{branch}; staff branch selection instead uses the staff integer branch ID.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
branch.nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Demo High Street
branch.descriptionoptional; see conditionsstring | nullCustomer-facing description of this resource, when configured.—
branch.photo_urloptional; see conditionsstring | nullPublic URL of the branch image, or null when none is configured.—
branch.addressoptional; see conditionsstring | nullBranch street/address text, or null when not configured.—
branch.phoneoptional; see conditionsstring | nullPhone number. International E.164 format is recommended; customer inputs are normalized using the business country.—Example: +447700900123
branch.latitudeoptional; see conditionsnumber | nullBranch latitude in decimal degrees, or null when not configured.—
branch.longitudeoptional; see conditionsnumber | nullBranch longitude in decimal degrees, or null when not configured.—
branch.map_urloptional; see conditionsstring | nullMap/directions URL for the branch, or null when not configured.—
branch.opening_hoursoptional; see conditionsarray<object> | nullWeekly opening hours in the branch local time. Null means hours have not been configured.—
branch.opening_hours[].day_of_weekoptional; see conditionsintegerDay index: 0 is Sunday, 1 is Monday, through 6 for Saturday.—
branch.opening_hours[].closedoptional; see conditionsbooleanWhether this branch is closed for the whole day.—
branch.opening_hours[].openoptional; see conditionsstring | nullLocal opening time in HH:MM format, or null on a closed day.—
branch.opening_hours[].closeoptional; see conditionsstring | nullLocal closing time in HH:MM format. A value earlier than open means closing after midnight.—
branch.pickup_instructionsoptional; see conditionsstring | nullCustomer pickup instructions, or null when absent or ordering is unavailable.—
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended

Authentication

POST/auth/start

Request a sign-in code with exactly one identifier

Start sign-up or sign-in using exactly one email or phone and a stable installation device_id. The response does not reveal whether the account already exists. Code delivery is asynchronous. Configured phone sign-in is advertised only when the selected phone verification provider is available or this active app has an explicit test-number allow-list with test OTP enabled. A globally advertised phone option does not make every number a test number: without usable SMS, a non-listed number can still return 422 method_not_enabled. Generated verification codes normally have 6 digits. Only phone sign-in/sign-up for explicitly listed numbers in an app with Super Admin-enabled test OTP accepts the fixed string 112233 without sending SMS. The setting is per app, defaults off and does not apply to email, identifier-linking or deletion OTPs. Pending test challenges stop working if test mode is disabled or the number is removed; expiry, attempt limits, one-use consumption and app/business scoping still apply. Demo access also requires the platform demo switch (DEMO_PHONE_OTP_ENABLED, off by default in production) and an unexpired per-app grant, configured for at most seven days. Demo identifiers remain unverified and cannot reserve a verified phone. Verified customer numbers are excluded; demo accounts cannot link identifiers, merge or purchase/redeem rewards. These login/registration requests skip SMS and request proof and never claim provisional accounts. Other numbers and sensitive actions retain proof requirements. Bird Verify returns a challenge_token and a pending send. Poll the matching status operation before asking for a code. Local SMS and email retain their existing check behavior. Ordinary code requests allow 5 per identifier, 20 per IP and 10 per device each hour. Registration for an active app's enabled, unexpired, allowlisted test phone instead allows 30 new challenges per minute per app and number, without consuming or waiting for ordinary hourly budgets. The public API request limit still applies. Verified customer numbers, other numbers, email and non-registration purposes retain normal limits. HTTP 429 supplies retry_after in seconds and a Retry-After header.

No bearer tokenX-App-Id

Parameters

Name / locationTypeDescriptionConstraints & example
X-App-Proofheader · optional; see conditionsstringRequired when proof protection is enabled (default in production). Obtain a fresh, one-use proof bound to this app and device from the trusted server issuer. Missing/replayed proof returns 403 app_verification_required; never embed the issuer secret in an app. Explicitly enabled apps' exact allowlisted demo phones are exempt for login/registration only; all other requests require proof when enabled.—Example: YOUR_VERIFICATION_PROOF

Request body application/json · required

Supply one contact identifier: email or phone. Sending both is rejected. device_id is required on every sign-in attempt.

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

FieldTypeDescriptionConstraints & example
emailoptional; see conditionsstringEmail address to receive the verification code. Required when phone is absent. Use a method enabled for this app.Format: email · Max length: 255Example: customer@example.com
phoneoptional; see conditionsstringPhone number to receive the SMS verification code. Required when email is absent. Use E.164 or a local number valid for the business country.Max length: 30Example: +447700900123
device_idrequired in parent objectstringStable identifier generated by the app once per installation. Reuse it across sign-in attempts; do not use a new value for each request.Max length: 100Example: install-demo-001
JSON request example
{
    "email": "customer@example.com",
    "device_id": "install-demo-001"
}
cURL request
curl --request POST "$BASE_URL/auth/start" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID' \
  --header 'Content-Type: application/json' \
  --data '{
    "email": "customer@example.com",
    "device_id": "install-demo-001"
}'

Responses

202 Success

Success

application/json · Illustrative example

{
    "challenge_id": "01JB0000000000000000000001",
    "channel": "email"
}
Response schema
FieldTypeDescriptionConstraints & example
challenge_idrequired in parent objectstringOpaque verification challenge ULID returned by the matching start endpoint; use only for that verification workflow.Min length: 26 · Max length: 26
channelrequired in parent objectstring | nullChannel used for this operation; see the endpoint-specific allowed values.Allowed: email, sms, whatsapp
challenge_tokenoptional; see conditionsstringBird Verify caller capability returned at start. Keep in memory; never place it in a URL.Min length: 64 · Max length: 64Example: YOUR_CHALLENGE_TOKEN
provideroptional; see conditionsstringSelected provider; bird_verify requires queued checks.—Example: bird_verify
statusoptional; see conditionsstringA remote send starts pending.—Example: pending
expires_atoptional; see conditionsstring | nullCurrent code expiry in the business time zone. Before remote send completion this is provisional; poll status for the provider expiry.Format: date-timeExample: 2026-10-30T10:22:00+00:00
resend_afteroptional; see conditionsstring | nullEarliest resend time in the business time zone. Null while a remote send is pending, after the send limit, or when the challenge is no longer pending. Poll status after a queued send.Format: date-timeExample: 2026-10-30T10:13:00+00:00
attempts_allowedoptional; see conditionsintegerMaximum number of code checks allowed for this challenge.—Example: 5
attempts_leftoptional; see conditionsintegerCurrent checks remaining; zero after attempt exhaustion. The final wrong check returns too_many_attempts.Minimum: 0Example: 4
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
POST/auth/verify

Consume a code and sign this installation in

Consume the sign-in challenge and issue a token for this installation. Codes expire after 10 minutes; newly issued tokens have a fixed 30-day lifetime. Signing in on the same device replaces its previous token. Generated verification codes normally have 6 digits. Only phone sign-in/sign-up for explicitly listed numbers in an app with Super Admin-enabled test OTP accepts the fixed string 112233 without sending SMS. The setting is per app, defaults off and does not apply to email, identifier-linking or deletion OTPs. Pending test challenges stop working if test mode is disabled or the number is removed; expiry, attempt limits, one-use consumption and app/business scoping still apply. Demo access also requires the platform demo switch (DEMO_PHONE_OTP_ENABLED, off by default in production) and an unexpired per-app grant, configured for at most seven days. Demo identifiers remain unverified and cannot reserve a verified phone. Verified customer numbers are excluded; demo accounts cannot link identifiers, merge or purchase/redeem rewards. These login/registration requests skip SMS and request proof and never claim provisional accounts. Other numbers and sensitive actions retain proof requirements. A device ID belonging to another customer returns 409 device_in_use without altering that installation or token. Generate a new random device ID and restart sign-in. With Bird Verify, submit challenge_token and an Idempotency-Key. HTTP 202 contains a queued check operation. Poll its status, then repeat this request with the same code/device, capability and operation_id to finalize locally. GET/status never returns a session token. A lost finalization response requires a fresh sign-in; completion does not replay a consumed challenge.

No bearer tokenX-App-Id Idempotency key

Parameters

Name / locationTypeDescriptionConstraints & example
Idempotency-Keyheader · optional; see conditionsstringRequired for a new Bird Verify check/resend. Reuse for a transport retry with identical input; use a new key for a different code.Max length: 100Example: verification-check-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
challenge_idrequired in parent objectstringSign-in challenge ULID returned by POST /auth/start for this business app. Login/register challenges only.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
coderequired in parent objectstringVerification code delivered for this challenge. Send as a string to preserve leading zeros. Expired, incorrect or consumed codes are refused. Generated verification codes normally have 6 digits. Only phone sign-in/sign-up for explicitly listed numbers in an app with Super Admin-enabled test OTP accepts the fixed string 112233 without sending SMS. The setting is per app, defaults off and does not apply to email, identifier-linking or deletion OTPs. Pending test challenges stop working if test mode is disabled or the number is removed; expiry, attempt limits, one-use consumption and app/business scoping still apply. Demo access also requires the platform demo switch (DEMO_PHONE_OTP_ENABLED, off by default in production) and an unexpired per-app grant, configured for at most seven days. Demo identifiers remain unverified and cannot reserve a verified phone. Verified customer numbers are excluded; demo accounts cannot link identifiers, merge or purchase/redeem rewards. These login/registration requests skip SMS and request proof and never claim provisional accounts. Other numbers and sensitive actions retain proof requirements.Max length: 10Example: 123456
devicerequired in parent objectobjectInstallation metadata for the sign-in token. Signing in again with the same device ID replaces that installation token.—
device.idrequired in parent objectstringStable installation ID, usually a UUID generated once by the app. Reuse the device_id sent to auth/start; signing in with the same installation ID replaces its token.Max length: 100Example: install-demo-001
device.platformrequired in parent objectstringNative app platform. Supply ios or android.Allowed: ios, androidExample: ios
device.nameoptional; see conditionsstringOptional human-readable installation label, such as Alex’s iPhone.Max length: 100Example: Demo iPhone
device.app_versionoptional; see conditionsstringInstalled app version such as 1.4.0. Required in practice when that platform has a minimum version policy.Max length: 30Example: 1.4.0
device.push_tokenoptional; see conditionsstring | nullFCM registration token for this installation. Send null to remove it and stop push delivery to this device.Max length: 500
device.firebase_project_idoptional; see conditionsstring | nullFirebase project ID from this app build's Firebase options, sent with device.push_token. A token whose project the business has not set up for the app is not stored; sign-in still succeeds.Max length: 100Example: cafe-app-prod
challenge_tokenoptional; see conditionsstringBird Verify caller capability returned at start. Keep in memory; never place it in a URL.Min length: 64 · Max length: 64Example: YOUR_CHALLENGE_TOKEN
operation_idoptional; see conditionsstringQueued verification operation returned by the prior check or resend.Min length: 26 · Max length: 26Example: 01JB0000000000000000000002
JSON request example
{
    "challenge_id": "01JB0000000000000000000001",
    "code": "123456",
    "device": {
        "id": "install-demo-001",
        "platform": "ios",
        "name": "Demo iPhone",
        "app_version": "1.0.0"
    }
}
cURL request
curl --request POST "$BASE_URL/auth/verify" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID' \
  --header 'Content-Type: application/json' \
  --data '{
    "challenge_id": "01JB0000000000000000000001",
    "code": "123456",
    "device": {
        "id": "install-demo-001",
        "platform": "ios",
        "name": "Demo iPhone",
        "app_version": "1.0.0"
    }
}'

Responses

200 Success

Success

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

{
    "customer": {
        "id": "01JB0000000000000000000001",
        "name": "Alex Morgan",
        "locale": "en_GB",
        "status": "active",
        "birthday": "1995-10-24",
        "push_consent": false,
        "marketing_consent": false,
        "identifiers": [
            {
                "type": "phone",
                "value": "string",
                "verified": false
            }
        ],
        "identity_assurance": "verified_contact",
        "recovery_available": false,
        "view_only_link_available": true
    },
    "membership": {
        "member_code": "48213906574",
        "balance": 10,
        "spendable": 10,
        "lifetime": 10,
        "points_label": "string",
        "cycle": {
            "number": 0,
            "points": 0,
            "target": 0,
            "points_to_next": 0,
            "next_reward": {
                "id": "string",
                "name": "string",
                "threshold": 0,
                "type": "free_item",
                "value_label": "string",
                "icon": "gift"
            },
            "reset_after_last": false,
            "carry_over": false,
            "milestones": [
                {
                    "points": 0,
                    "reward": {
                        "id": "string",
                        "name": "string",
                        "type": "free_item",
                        "value_label": "string",
                        "icon": "gift"
                    },
                    "status": "locked",
                    "entitlement_id": "string",
                    "reached": false,
                    "steps": [
                        {
                            "points": null,
                            "value": null,
                            "reached": null
                        }
                    ]
                }
            ],
            "completed": {
                "number": 0,
                "target": 0,
                "cards": 0,
                "completed_at": "2026-10-24T10:00:00Z",
                "milestones": [
                    {
                        "points": 0,
                        "reward": {
                            "id": null,
                            "name": null,
                            "type": null,
                            "value_label": null,
                            "icon": null
                        },
                        "status": "locked",
                        "entitlement_id": "string",
                        "reached": false,
                        "steps": [
                            null
                        ]
                    }
                ]
            }
        },
        "view_only_url": "https://example.com/experience/customer/cafe/card/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    },
    "rewards": [
        {
            "id": "01JB0000000000000000000001",
            "name": "Free coffee",
            "type": "amount_voucher",
            "value_label": "string",
            "value_minor": 1000,
            "currency": "GBP",
            "code": "DEMO7KQ2M",
            "status": "available",
            "used_at": "2026-10-24T10:00:00Z",
            "expires_at": "2026-10-24T10:00:00Z",
            "claim": {
                "eligible": true,
                "reason_code": null,
                "message": null,
                "points_required": 20,
                "available_actions": [
                    "claim"
                ]
            },
            "cycle": 0,
            "milestone": 0,
            "issued_at": "2026-10-24T10:00:00Z",
            "valid_through_date": "string",
            "expiry_timezone": "Asia/Dubai"
        }
    ],
    "google_wallet": false,
    "token": "string"
}
Response schema
FieldTypeDescriptionConstraints & example
customeroptional; see conditionsobjectCustomer profile belonging to the resolved business.—
customer.idoptional; see conditionsstringCustomer public ULID within this business. Do not substitute a phone number or legacy database ID.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.localeoptional; see conditionsstring | nullLanguage/region code such as en_GB; determines localized presentation.—Example: en_GB
customer.statusoptional; see conditionsstringCustomer lifecycle state, for example active or provisional. Suspended, merged or erased accounts cannot use normal authenticated operations. Suspended customers receive 403 account_unavailable with error.reason suspended instead of an authenticated profile.Allowed: active, provisional, suspended, merged, erased
customer.birthdayoptional; see conditionsstring | nullFull date of birth as YYYY-MM-DD, for example 1995-10-24. Must be a valid calendar date, no later than today in the business timezone. Null clears it.Format: dateExample: 1995-10-24
customer.push_consentoptional; see conditionsbooleanCustomer consent for push marketing. Marketing delivery additionally requires marketing_consent and a reachable signed-in device.—
customer.marketing_consentoptional; see conditionsbooleanCustomer consent for marketing messages. Marketing push also requires push_consent; a push token alone is not consent.—
customer.identifiersoptional; see conditionsarray<object>Customer contact identifiers and their ownership-verification state.—
customer.identifiers[].typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: phone, email
customer.identifiers[].valueoptional; see conditionsstringIdentifier value, such as an email address or E.164 phone number.—
customer.identifiers[].verifiedoptional; see conditionsbooleanWhether ownership of this identifier has been verified.—
customer.identity_assuranceoptional; see conditionsstringContact assurance, separate from lifecycle status. Anonymous accounts use browser sessions only; native sign-in remains contact-verified.Allowed: verified_contact, anonymous, unverified_entry, demo
customer.recovery_availableoptional; see conditionsbooleanAt least one verified contact or an unexpired unused private recovery key exists. Wallet installation alone does not provide browser recovery.—
customer.view_only_link_availableoptional; see conditionsbooleanThis active account has an active loyalty membership and can manage an optional view-only membership link through its authenticated browser session. Available to registered and anonymously enrolled members. The link never authorizes account access.—Example: true
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.—Example: 48213906574
membership.balanceoptional; see conditionsintegerAccumulated points balance, not current-cycle progress. Render claim controls only from server claim decisions.—Example: 10
membership.spendableoptional; see conditionsintegerPoints available to spend, excluding holds. Claim decisions remain authoritative.—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.cycleoptional; see conditionsobject | nullPublished ladder progress, separate from accumulated balance. Reads never issue rewards. Null without an active supported ladder.—
membership.cycle.numberrequired in parent objectintegerCurrent cycle number.—
membership.cycle.pointsrequired in parent objectintegerCurrent-cycle points; distinct from accumulated balance.—
membership.cycle.targetrequired in parent objectinteger | nullPublished final milestone threshold; null without a threshold.—
membership.cycle.points_to_nextrequired in parent objectinteger | nullPoints remaining to the next offered higher threshold; null when none.—
membership.cycle.next_rewardrequired in parent objectobject | nullNext offered reward above current-cycle points, or null.—
membership.cycle.next_reward.idrequired in parent objectstringPublic object ID; event IDs include their source prefix.—
membership.cycle.next_reward.namerequired in parent objectstringCustomer-facing display name.—
membership.cycle.next_reward.thresholdrequired in parent objectintegerPoints threshold of the next offered reward.—
membership.cycle.next_reward.typerequired in parent objectstringReward kind, for the reward stop's badge.Allowed: free_item, amount_voucher, percent_off
membership.cycle.next_reward.value_labelrequired in parent objectstring | nullShort badge value such as £10 or 10%; null for free items.—
membership.cycle.next_reward.iconrequired in parent objectstringPicture for the reward: the business's pick, else money (amount voucher), percent (percent off) or gift (free item). Treat unknown values as gift.Allowed: gift, coffee, drink, food, dessert, money, percent, ticket, star, bag
membership.cycle.reset_after_lastrequired in parent objectbooleanWhether earning through the last threshold starts the next cycle.—
membership.cycle.carry_overrequired in parent objectbooleanWhether excess earning carries into the next cycle.—
membership.cycle.milestonesrequired in parent objectarray<object>Published ladder milestones, at most ten. State does not authorize a claim.Max items: 10
membership.cycle.milestones[].pointsrequired in parent objectintegerCurrent-cycle points; distinct from accumulated balance.—
membership.cycle.milestones[].rewardrequired in parent objectobject | nullRelated reward identity and display name, or null for ledger-only activity.—
membership.cycle.milestones[].reward.idrequired in parent objectstringPublic object ID; event IDs include their source prefix.—
membership.cycle.milestones[].reward.namerequired in parent objectstringCustomer-facing display name.—
membership.cycle.milestones[].reward.typerequired in parent objectstringReward kind, for the reward stop's badge.Allowed: free_item, amount_voucher, percent_off
membership.cycle.milestones[].reward.value_labelrequired in parent objectstring | nullShort badge value such as £10 or 10%; null for free items.—
membership.cycle.milestones[].reward.iconrequired in parent objectstringPicture for the reward: the business's pick, else money (amount voucher), percent (percent off) or gift (free item). Treat unknown values as gift.Allowed: gift, coffee, drink, food, dessert, money, percent, ticket, star, bag
membership.cycle.milestones[].statusrequired in parent objectstringMilestone/grant state, or ledger posting status in activity. pending means reached but unissued, and never authorizes a claim. A reward a refund took back reports locked (or pending once reached), as it is issued again; revoked means revoked by staff.Allowed: locked, pending, unavailable, earned, available, used, expired, revoked
membership.cycle.milestones[].entitlement_idrequired in parent objectstring | nullOwned grant public ID for this cycle/milestone, or null.—
membership.cycle.milestones[].reachedrequired in parent objectbooleanCurrent-cycle points have reached this milestone.—
membership.cycle.milestones[].stepsrequired in parent objectarray<object>Stops a card shows before this milestone's reward, split by the server (one per point up to 10, else 5-10 even stops, else 5 rounded). Draw as given.Max items: 9
membership.cycle.milestones[].steps[].pointsrequired in parent objectintegerCumulative current-cycle threshold of this stop.—
membership.cycle.milestones[].steps[].valuerequired in parent objectintegerPoints this stop is worth (from the previous stop).—
membership.cycle.milestones[].steps[].reachedrequired in parent objectbooleanCurrent-cycle points have reached this stop.—
membership.cycle.completedrequired in parent objectobject | nullThe card the member's latest point-earning purchase filled (its last-milestone reward came from that purchase, is still the member's own and was not refunded). Null otherwise, including once points are earned again, once no reward on it is still waiting (all used, expired or withdrawn), for ladders that do not start a new card, and after a refund takes that reward back. Display only: the cycle has already moved on and its rewards are issued.—
membership.cycle.completed.numberrequired in parent objectintegerCycle number of the filled card.—
membership.cycle.completed.targetrequired in parent objectintegerPoints on a full card.—
membership.cycle.completed.cardsrequired in parent objectintegerCards that purchase filled (more than one for a very large purchase).—
membership.cycle.completed.completed_atrequired in parent objectstring | nullWhen its last reward was issued.Format: date-time
membership.cycle.completed.milestonesrequired in parent objectarray<object>Published ladder milestones, at most ten. State does not authorize a claim.Max items: 10
membership.cycle.completed.milestones[].pointsrequired in parent objectintegerCurrent-cycle points; distinct from accumulated balance.—
membership.cycle.completed.milestones[].rewardrequired in parent objectobject | nullRelated reward identity and display name, or null for ledger-only activity.—
membership.cycle.completed.milestones[].reward.idrequired in parent objectstringPublic object ID; event IDs include their source prefix.—
membership.cycle.completed.milestones[].reward.namerequired in parent objectstringCustomer-facing display name.—
membership.cycle.completed.milestones[].reward.typerequired in parent objectstringReward kind, for the reward stop's badge.Allowed: free_item, amount_voucher, percent_off
membership.cycle.completed.milestones[].reward.value_labelrequired in parent objectstring | nullShort badge value such as £10 or 10%; null for free items.—
membership.cycle.completed.milestones[].reward.iconrequired in parent objectstringPicture for the reward: the business's pick, else money (amount voucher), percent (percent off) or gift (free item). Treat unknown values as gift.Allowed: gift, coffee, drink, food, dessert, money, percent, ticket, star, bag
membership.cycle.completed.milestones[].statusrequired in parent objectstringMilestone/grant state, or ledger posting status in activity. pending means reached but unissued, and never authorizes a claim. A reward a refund took back reports locked (or pending once reached), as it is issued again; revoked means revoked by staff.Allowed: locked, pending, unavailable, earned, available, used, expired, revoked
membership.cycle.completed.milestones[].entitlement_idrequired in parent objectstring | nullOwned grant public ID for this cycle/milestone, or null.—
membership.cycle.completed.milestones[].reachedrequired in parent objectbooleanCurrent-cycle points have reached this milestone.—
membership.cycle.completed.milestones[].stepsrequired in parent objectarray<object>Stops a card shows before this milestone's reward, split by the server (one per point up to 10, else 5-10 even stops, else 5 rounded). Draw as given.Max items: 9
membership.cycle.completed.milestones[].steps[].pointsrequired in parent objectintegerCumulative current-cycle threshold of this stop.—
membership.cycle.completed.milestones[].steps[].valuerequired in parent objectintegerPoints this stop is worth (from the previous stop).—
membership.cycle.completed.milestones[].steps[].reachedrequired in parent objectbooleanCurrent-cycle points have reached this stop.—
membership.view_only_urloptional; see conditionsstring | nullOptional shareable view-only card URL showing membership code, points and progress. Anyone with the URL can view those fields. It cannot authenticate, recover an account, reveal vouchers or authorize claims. Null when no link is enabled.Format: uriExample: https://example.com/experience/customer/cafe/card/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
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[].nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Free coffee
rewards[].typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: amount_voucher, free_item, percent_off
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[].currencyoptional; see conditionsstringISO 4217 currency code, for example GBP or AED. Monetary amounts are expressed in its minor units.—Example: GBP
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[].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.—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[].cycleoptional; see conditionsinteger | nullCard (cycle) a milestone reward came from; null for rewards bought with points.—
rewards[].milestoneoptional; see conditionsinteger | nullMilestone points on that card; null for rewards bought with points.—
rewards[].issued_atoptional; see conditionsstring | nullWhen the reward was issued; for the last milestone, when its card was completed.Format: date-time
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
google_walletoptional; see conditionsbooleanWhether Google Wallet is available for this business/customer.—
tokenrequired in parent objectstringBearer credential returned at sign-in. Store securely and send in Authorization: Bearer YOUR_TOKEN; never put it in a URL.—
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
202 Remote verification is queued; no customer authority has been granted.

Remote verification is queued; no customer authority has been granted.

application/json · Illustrative example

{
    "status": "pending",
    "operation_id": "01JB0000000000000000000002",
    "retry_after": 3
}
Response schema
FieldTypeDescriptionConstraints & example
statusoptional; see conditionsstringPending until the worker records an outcome.—Example: pending
operation_idoptional; see conditionsstringQueued verification operation returned by the prior check or resend.Min length: 26 · Max length: 26Example: 01JB0000000000000000000002
challenge_idoptional; see conditionsstringOpaque challenge identifier returned by the matching start operation.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
retry_afteroptional; see conditionsintegerSeconds to wait before polling.—Example: 3
POST/auth/logout

Revoke this token

Revoke the current installation token. Remove the token from secure device storage after success.

Bearer tokenX-App-Id
cURL request
curl --request POST "$BASE_URL/auth/logout" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID' \
  --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 X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
POST/auth/complete

Finalize a successful queued sign-in check

Consume the sign-in challenge and issue a token for this installation. Codes expire after 10 minutes; newly issued tokens have a fixed 30-day lifetime. Signing in on the same device replaces its previous token. Generated verification codes normally have 6 digits. Only phone sign-in/sign-up for explicitly listed numbers in an app with Super Admin-enabled test OTP accepts the fixed string 112233 without sending SMS. The setting is per app, defaults off and does not apply to email, identifier-linking or deletion OTPs. Pending test challenges stop working if test mode is disabled or the number is removed; expiry, attempt limits, one-use consumption and app/business scoping still apply. Demo access also requires the platform demo switch (DEMO_PHONE_OTP_ENABLED, off by default in production) and an unexpired per-app grant, configured for at most seven days. Demo identifiers remain unverified and cannot reserve a verified phone. Verified customer numbers are excluded; demo accounts cannot link identifiers, merge or purchase/redeem rewards. These login/registration requests skip SMS and request proof and never claim provisional accounts. Other numbers and sensitive actions retain proof requirements. A device ID belonging to another customer returns 409 device_in_use without altering that installation or token. Generate a new random device ID and restart sign-in. With Bird Verify, submit challenge_token and an Idempotency-Key. HTTP 202 contains a queued check operation. Poll its status, then repeat this request with the same code/device, capability and operation_id to finalize locally. GET/status never returns a session token. A lost finalization response requires a fresh sign-in; completion does not replay a consumed challenge.

No bearer tokenX-App-Id Idempotency key

Parameters

Name / locationTypeDescriptionConstraints & example
Idempotency-Keyheader · optional; see conditionsstringRequired for a new Bird Verify check/resend. Reuse for a transport retry with identical input; use a new key for a different code.Max length: 100Example: verification-check-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
challenge_idrequired in parent objectstringSign-in challenge ULID returned by POST /auth/start for this business app. Login/register challenges only.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
coderequired in parent objectstringVerification code delivered for this challenge. Send as a string to preserve leading zeros. Expired, incorrect or consumed codes are refused. Generated verification codes normally have 6 digits. Only phone sign-in/sign-up for explicitly listed numbers in an app with Super Admin-enabled test OTP accepts the fixed string 112233 without sending SMS. The setting is per app, defaults off and does not apply to email, identifier-linking or deletion OTPs. Pending test challenges stop working if test mode is disabled or the number is removed; expiry, attempt limits, one-use consumption and app/business scoping still apply. Demo access also requires the platform demo switch (DEMO_PHONE_OTP_ENABLED, off by default in production) and an unexpired per-app grant, configured for at most seven days. Demo identifiers remain unverified and cannot reserve a verified phone. Verified customer numbers are excluded; demo accounts cannot link identifiers, merge or purchase/redeem rewards. These login/registration requests skip SMS and request proof and never claim provisional accounts. Other numbers and sensitive actions retain proof requirements.Max length: 10Example: 123456
devicerequired in parent objectobjectInstallation metadata for the sign-in token. Signing in again with the same device ID replaces that installation token.—
device.idrequired in parent objectstringStable installation ID, usually a UUID generated once by the app. Reuse the device_id sent to auth/start; signing in with the same installation ID replaces its token.Max length: 100Example: install-demo-001
device.platformrequired in parent objectstringNative app platform. Supply ios or android.Allowed: ios, androidExample: ios
device.nameoptional; see conditionsstringOptional human-readable installation label, such as Alex’s iPhone.Max length: 100Example: Demo iPhone
device.app_versionoptional; see conditionsstringInstalled app version such as 1.4.0. Required in practice when that platform has a minimum version policy.Max length: 30Example: 1.4.0
device.push_tokenoptional; see conditionsstring | nullFCM registration token for this installation. Send null to remove it and stop push delivery to this device.Max length: 500
device.firebase_project_idoptional; see conditionsstring | nullFirebase project ID from this app build's Firebase options, sent with device.push_token. A token whose project the business has not set up for the app is not stored; sign-in still succeeds.Max length: 100Example: cafe-app-prod
challenge_tokenoptional; see conditionsstringBird Verify caller capability returned at start. Keep in memory; never place it in a URL.Min length: 64 · Max length: 64Example: YOUR_CHALLENGE_TOKEN
operation_idoptional; see conditionsstringQueued verification operation returned by the prior check or resend.Min length: 26 · Max length: 26Example: 01JB0000000000000000000002
JSON request example
{
    "challenge_id": "01JB0000000000000000000001",
    "code": "123456",
    "device": {
        "id": "install-demo-001",
        "platform": "ios",
        "name": "Demo iPhone",
        "app_version": "1.0.0"
    }
}
cURL request
curl --request POST "$BASE_URL/auth/complete" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID' \
  --header 'Content-Type: application/json' \
  --data '{
    "challenge_id": "01JB0000000000000000000001",
    "code": "123456",
    "device": {
        "id": "install-demo-001",
        "platform": "ios",
        "name": "Demo iPhone",
        "app_version": "1.0.0"
    }
}'

Responses

200 Success

Success

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

{
    "customer": {
        "id": "01JB0000000000000000000001",
        "name": "Alex Morgan",
        "locale": "en_GB",
        "status": "active",
        "birthday": "1995-10-24",
        "push_consent": false,
        "marketing_consent": false,
        "identifiers": [
            {
                "type": "phone",
                "value": "string",
                "verified": false
            }
        ],
        "identity_assurance": "verified_contact",
        "recovery_available": false,
        "view_only_link_available": true
    },
    "membership": {
        "member_code": "48213906574",
        "balance": 10,
        "spendable": 10,
        "lifetime": 10,
        "points_label": "string",
        "cycle": {
            "number": 0,
            "points": 0,
            "target": 0,
            "points_to_next": 0,
            "next_reward": {
                "id": "string",
                "name": "string",
                "threshold": 0,
                "type": "free_item",
                "value_label": "string",
                "icon": "gift"
            },
            "reset_after_last": false,
            "carry_over": false,
            "milestones": [
                {
                    "points": 0,
                    "reward": {
                        "id": "string",
                        "name": "string",
                        "type": "free_item",
                        "value_label": "string",
                        "icon": "gift"
                    },
                    "status": "locked",
                    "entitlement_id": "string",
                    "reached": false,
                    "steps": [
                        {
                            "points": null,
                            "value": null,
                            "reached": null
                        }
                    ]
                }
            ],
            "completed": {
                "number": 0,
                "target": 0,
                "cards": 0,
                "completed_at": "2026-10-24T10:00:00Z",
                "milestones": [
                    {
                        "points": 0,
                        "reward": {
                            "id": null,
                            "name": null,
                            "type": null,
                            "value_label": null,
                            "icon": null
                        },
                        "status": "locked",
                        "entitlement_id": "string",
                        "reached": false,
                        "steps": [
                            null
                        ]
                    }
                ]
            }
        },
        "view_only_url": "https://example.com/experience/customer/cafe/card/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    },
    "rewards": [
        {
            "id": "01JB0000000000000000000001",
            "name": "Free coffee",
            "type": "amount_voucher",
            "value_label": "string",
            "value_minor": 1000,
            "currency": "GBP",
            "code": "DEMO7KQ2M",
            "status": "available",
            "used_at": "2026-10-24T10:00:00Z",
            "expires_at": "2026-10-24T10:00:00Z",
            "claim": {
                "eligible": true,
                "reason_code": null,
                "message": null,
                "points_required": 20,
                "available_actions": [
                    "claim"
                ]
            },
            "cycle": 0,
            "milestone": 0,
            "issued_at": "2026-10-24T10:00:00Z",
            "valid_through_date": "string",
            "expiry_timezone": "Asia/Dubai"
        }
    ],
    "google_wallet": false,
    "token": "string"
}
Response schema
FieldTypeDescriptionConstraints & example
customeroptional; see conditionsobjectCustomer profile belonging to the resolved business.—
customer.idoptional; see conditionsstringCustomer public ULID within this business. Do not substitute a phone number or legacy database ID.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.localeoptional; see conditionsstring | nullLanguage/region code such as en_GB; determines localized presentation.—Example: en_GB
customer.statusoptional; see conditionsstringCustomer lifecycle state, for example active or provisional. Suspended, merged or erased accounts cannot use normal authenticated operations. Suspended customers receive 403 account_unavailable with error.reason suspended instead of an authenticated profile.Allowed: active, provisional, suspended, merged, erased
customer.birthdayoptional; see conditionsstring | nullFull date of birth as YYYY-MM-DD, for example 1995-10-24. Must be a valid calendar date, no later than today in the business timezone. Null clears it.Format: dateExample: 1995-10-24
customer.push_consentoptional; see conditionsbooleanCustomer consent for push marketing. Marketing delivery additionally requires marketing_consent and a reachable signed-in device.—
customer.marketing_consentoptional; see conditionsbooleanCustomer consent for marketing messages. Marketing push also requires push_consent; a push token alone is not consent.—
customer.identifiersoptional; see conditionsarray<object>Customer contact identifiers and their ownership-verification state.—
customer.identifiers[].typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: phone, email
customer.identifiers[].valueoptional; see conditionsstringIdentifier value, such as an email address or E.164 phone number.—
customer.identifiers[].verifiedoptional; see conditionsbooleanWhether ownership of this identifier has been verified.—
customer.identity_assuranceoptional; see conditionsstringContact assurance, separate from lifecycle status. Anonymous accounts use browser sessions only; native sign-in remains contact-verified.Allowed: verified_contact, anonymous, unverified_entry, demo
customer.recovery_availableoptional; see conditionsbooleanAt least one verified contact or an unexpired unused private recovery key exists. Wallet installation alone does not provide browser recovery.—
customer.view_only_link_availableoptional; see conditionsbooleanThis active account has an active loyalty membership and can manage an optional view-only membership link through its authenticated browser session. Available to registered and anonymously enrolled members. The link never authorizes account access.—Example: true
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.—Example: 48213906574
membership.balanceoptional; see conditionsintegerAccumulated points balance, not current-cycle progress. Render claim controls only from server claim decisions.—Example: 10
membership.spendableoptional; see conditionsintegerPoints available to spend, excluding holds. Claim decisions remain authoritative.—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.cycleoptional; see conditionsobject | nullPublished ladder progress, separate from accumulated balance. Reads never issue rewards. Null without an active supported ladder.—
membership.cycle.numberrequired in parent objectintegerCurrent cycle number.—
membership.cycle.pointsrequired in parent objectintegerCurrent-cycle points; distinct from accumulated balance.—
membership.cycle.targetrequired in parent objectinteger | nullPublished final milestone threshold; null without a threshold.—
membership.cycle.points_to_nextrequired in parent objectinteger | nullPoints remaining to the next offered higher threshold; null when none.—
membership.cycle.next_rewardrequired in parent objectobject | nullNext offered reward above current-cycle points, or null.—
membership.cycle.next_reward.idrequired in parent objectstringPublic object ID; event IDs include their source prefix.—
membership.cycle.next_reward.namerequired in parent objectstringCustomer-facing display name.—
membership.cycle.next_reward.thresholdrequired in parent objectintegerPoints threshold of the next offered reward.—
membership.cycle.next_reward.typerequired in parent objectstringReward kind, for the reward stop's badge.Allowed: free_item, amount_voucher, percent_off
membership.cycle.next_reward.value_labelrequired in parent objectstring | nullShort badge value such as £10 or 10%; null for free items.—
membership.cycle.next_reward.iconrequired in parent objectstringPicture for the reward: the business's pick, else money (amount voucher), percent (percent off) or gift (free item). Treat unknown values as gift.Allowed: gift, coffee, drink, food, dessert, money, percent, ticket, star, bag
membership.cycle.reset_after_lastrequired in parent objectbooleanWhether earning through the last threshold starts the next cycle.—
membership.cycle.carry_overrequired in parent objectbooleanWhether excess earning carries into the next cycle.—
membership.cycle.milestonesrequired in parent objectarray<object>Published ladder milestones, at most ten. State does not authorize a claim.Max items: 10
membership.cycle.milestones[].pointsrequired in parent objectintegerCurrent-cycle points; distinct from accumulated balance.—
membership.cycle.milestones[].rewardrequired in parent objectobject | nullRelated reward identity and display name, or null for ledger-only activity.—
membership.cycle.milestones[].reward.idrequired in parent objectstringPublic object ID; event IDs include their source prefix.—
membership.cycle.milestones[].reward.namerequired in parent objectstringCustomer-facing display name.—
membership.cycle.milestones[].reward.typerequired in parent objectstringReward kind, for the reward stop's badge.Allowed: free_item, amount_voucher, percent_off
membership.cycle.milestones[].reward.value_labelrequired in parent objectstring | nullShort badge value such as £10 or 10%; null for free items.—
membership.cycle.milestones[].reward.iconrequired in parent objectstringPicture for the reward: the business's pick, else money (amount voucher), percent (percent off) or gift (free item). Treat unknown values as gift.Allowed: gift, coffee, drink, food, dessert, money, percent, ticket, star, bag
membership.cycle.milestones[].statusrequired in parent objectstringMilestone/grant state, or ledger posting status in activity. pending means reached but unissued, and never authorizes a claim. A reward a refund took back reports locked (or pending once reached), as it is issued again; revoked means revoked by staff.Allowed: locked, pending, unavailable, earned, available, used, expired, revoked
membership.cycle.milestones[].entitlement_idrequired in parent objectstring | nullOwned grant public ID for this cycle/milestone, or null.—
membership.cycle.milestones[].reachedrequired in parent objectbooleanCurrent-cycle points have reached this milestone.—
membership.cycle.milestones[].stepsrequired in parent objectarray<object>Stops a card shows before this milestone's reward, split by the server (one per point up to 10, else 5-10 even stops, else 5 rounded). Draw as given.Max items: 9
membership.cycle.milestones[].steps[].pointsrequired in parent objectintegerCumulative current-cycle threshold of this stop.—
membership.cycle.milestones[].steps[].valuerequired in parent objectintegerPoints this stop is worth (from the previous stop).—
membership.cycle.milestones[].steps[].reachedrequired in parent objectbooleanCurrent-cycle points have reached this stop.—
membership.cycle.completedrequired in parent objectobject | nullThe card the member's latest point-earning purchase filled (its last-milestone reward came from that purchase, is still the member's own and was not refunded). Null otherwise, including once points are earned again, once no reward on it is still waiting (all used, expired or withdrawn), for ladders that do not start a new card, and after a refund takes that reward back. Display only: the cycle has already moved on and its rewards are issued.—
membership.cycle.completed.numberrequired in parent objectintegerCycle number of the filled card.—
membership.cycle.completed.targetrequired in parent objectintegerPoints on a full card.—
membership.cycle.completed.cardsrequired in parent objectintegerCards that purchase filled (more than one for a very large purchase).—
membership.cycle.completed.completed_atrequired in parent objectstring | nullWhen its last reward was issued.Format: date-time
membership.cycle.completed.milestonesrequired in parent objectarray<object>Published ladder milestones, at most ten. State does not authorize a claim.Max items: 10
membership.cycle.completed.milestones[].pointsrequired in parent objectintegerCurrent-cycle points; distinct from accumulated balance.—
membership.cycle.completed.milestones[].rewardrequired in parent objectobject | nullRelated reward identity and display name, or null for ledger-only activity.—
membership.cycle.completed.milestones[].reward.idrequired in parent objectstringPublic object ID; event IDs include their source prefix.—
membership.cycle.completed.milestones[].reward.namerequired in parent objectstringCustomer-facing display name.—
membership.cycle.completed.milestones[].reward.typerequired in parent objectstringReward kind, for the reward stop's badge.Allowed: free_item, amount_voucher, percent_off
membership.cycle.completed.milestones[].reward.value_labelrequired in parent objectstring | nullShort badge value such as £10 or 10%; null for free items.—
membership.cycle.completed.milestones[].reward.iconrequired in parent objectstringPicture for the reward: the business's pick, else money (amount voucher), percent (percent off) or gift (free item). Treat unknown values as gift.Allowed: gift, coffee, drink, food, dessert, money, percent, ticket, star, bag
membership.cycle.completed.milestones[].statusrequired in parent objectstringMilestone/grant state, or ledger posting status in activity. pending means reached but unissued, and never authorizes a claim. A reward a refund took back reports locked (or pending once reached), as it is issued again; revoked means revoked by staff.Allowed: locked, pending, unavailable, earned, available, used, expired, revoked
membership.cycle.completed.milestones[].entitlement_idrequired in parent objectstring | nullOwned grant public ID for this cycle/milestone, or null.—
membership.cycle.completed.milestones[].reachedrequired in parent objectbooleanCurrent-cycle points have reached this milestone.—
membership.cycle.completed.milestones[].stepsrequired in parent objectarray<object>Stops a card shows before this milestone's reward, split by the server (one per point up to 10, else 5-10 even stops, else 5 rounded). Draw as given.Max items: 9
membership.cycle.completed.milestones[].steps[].pointsrequired in parent objectintegerCumulative current-cycle threshold of this stop.—
membership.cycle.completed.milestones[].steps[].valuerequired in parent objectintegerPoints this stop is worth (from the previous stop).—
membership.cycle.completed.milestones[].steps[].reachedrequired in parent objectbooleanCurrent-cycle points have reached this stop.—
membership.view_only_urloptional; see conditionsstring | nullOptional shareable view-only card URL showing membership code, points and progress. Anyone with the URL can view those fields. It cannot authenticate, recover an account, reveal vouchers or authorize claims. Null when no link is enabled.Format: uriExample: https://example.com/experience/customer/cafe/card/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
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[].nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Free coffee
rewards[].typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: amount_voucher, free_item, percent_off
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[].currencyoptional; see conditionsstringISO 4217 currency code, for example GBP or AED. Monetary amounts are expressed in its minor units.—Example: GBP
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[].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.—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[].cycleoptional; see conditionsinteger | nullCard (cycle) a milestone reward came from; null for rewards bought with points.—
rewards[].milestoneoptional; see conditionsinteger | nullMilestone points on that card; null for rewards bought with points.—
rewards[].issued_atoptional; see conditionsstring | nullWhen the reward was issued; for the last milestone, when its card was completed.Format: date-time
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
google_walletoptional; see conditionsbooleanWhether Google Wallet is available for this business/customer.—
tokenrequired in parent objectstringBearer credential returned at sign-in. Store securely and send in Authorization: Bearer YOUR_TOKEN; never put it in a URL.—
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
202 Remote verification is queued; no customer authority has been granted.

Remote verification is queued; no customer authority has been granted.

application/json · Illustrative example

{
    "status": "pending",
    "operation_id": "01JB0000000000000000000002",
    "retry_after": 3
}
Response schema
FieldTypeDescriptionConstraints & example
statusoptional; see conditionsstringPending until the worker records an outcome.—Example: pending
operation_idoptional; see conditionsstringQueued verification operation returned by the prior check or resend.Min length: 26 · Max length: 26Example: 01JB0000000000000000000002
challenge_idoptional; see conditionsstringOpaque challenge identifier returned by the matching start operation.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
retry_afteroptional; see conditionsintegerSeconds to wait before polling.—Example: 3
POST/auth/status

Status login or registration verification

Read local worker state without provider HTTP or customer-side effects. Uses only the original business, app, device and caller capability.

No bearer tokenX-App-Id

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
challenge_idrequired in parent objectstringOpaque challenge identifier returned by the matching start operation.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
challenge_tokenoptional; see conditionsstringBird Verify caller capability returned at start. Keep in memory; never place it in a URL.Min length: 64 · Max length: 64Example: YOUR_CHALLENGE_TOKEN
device_idoptional; see conditionsstringOriginal installation identifier for public login operations; authenticated operations use their bound installation.—Example: install-demo-001
operation_idoptional; see conditionsstringQueued verification operation returned by the prior check or resend.Min length: 26 · Max length: 26Example: 01JB0000000000000000000002
JSON request example
{
    "challenge_id": "01JB0000000000000000000001",
    "challenge_token": "YOUR_CHALLENGE_TOKEN",
    "device_id": "install-demo-001"
}
cURL request
curl --request POST "$BASE_URL/auth/status" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID' \
  --header 'Content-Type: application/json' \
  --data '{
    "challenge_id": "01JB0000000000000000000001",
    "challenge_token": "YOUR_CHALLENGE_TOKEN",
    "device_id": "install-demo-001"
}'

Responses

401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
200 Local state only. No code or session token is returned.

Local state only. No code or session token is returned.

application/json · Illustrative example

{
    "status": "pending",
    "operation_id": "01JB0000000000000000000002",
    "retry_after": 3,
    "challenge_id": "01JB0000000000000000000001",
    "expires_at": "2026-10-30T10:22:00+00:00",
    "resend_after": null,
    "attempts_allowed": 5,
    "attempts_left": 5,
    "error": null
}
Response schema
FieldTypeDescriptionConstraints & example
statusoptional; see conditionsstringPending until the worker records an outcome.—Example: pending
operation_idoptional; see conditionsstringQueued verification operation returned by the prior check or resend.Min length: 26 · Max length: 26Example: 01JB0000000000000000000002
challenge_idoptional; see conditionsstringOpaque challenge identifier returned by the matching start operation.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
retry_afteroptional; see conditionsintegerSeconds to wait before polling.—Example: 3
outcomeoptional; see conditionsstring | nullsent, verified, invalid_code, expired, too_many_attempts or a safe failure reason.—Example: sent
channeloptional; see conditionsstring | nullMost recently observed delivery channel.—Example: whatsapp
expires_atoptional; see conditionsstring | nullCurrent code expiry in the business time zone. Before remote send completion this is provisional; poll status for the provider expiry.Format: date-timeExample: 2026-10-30T10:22:00+00:00
resend_afteroptional; see conditionsstring | nullEarliest resend time in the business time zone. Null while a remote send is pending, after the send limit, or when the challenge is no longer pending. Poll status after a queued send.Format: date-timeExample: 2026-10-30T10:13:00+00:00
attempts_allowedoptional; see conditionsintegerMaximum number of code checks allowed for this challenge.—Example: 5
attempts_leftoptional; see conditionsintegerCurrent checks remaining; zero after attempt exhaustion. The final wrong check returns too_many_attempts.Minimum: 0Example: 4
erroroptional; see conditionsobject | nullPublic code-check error for a completed unsuccessful check. Status polling remains HTTP 200; null for pending work and successful sends/checks.—
error.codeoptional; see conditionsstringSame public code used by finalization: invalid_code, code_expired, too_many_attempts or challenge_not_found.—Example: invalid_code
error.messageoptional; see conditionsstringHuman-readable explanation of the check failure.—Example: That code is not right.
error.attempts_leftoptional; see conditionsintegerChecks remaining, included for invalid_code.Minimum: 0Example: 4
POST/auth/resend

Resend login or registration verification

Queue an explicit resend or next-channel request. Subject to local allowance, ownership and cooldown. Never changes provider. Uses only the original business, app, device and caller capability.

No bearer tokenX-App-Id Idempotency key

Parameters

Name / locationTypeDescriptionConstraints & example
Idempotency-Keyheader · optional; see conditionsstringRequired for a new Bird Verify check/resend. Reuse for a transport retry with identical input; use a new key for a different code.Max length: 100Example: verification-check-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
challenge_idrequired in parent objectstringOpaque challenge identifier returned by the matching start operation.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
challenge_tokenoptional; see conditionsstringBird Verify caller capability returned at start. Keep in memory; never place it in a URL.Min length: 64 · Max length: 64Example: YOUR_CHALLENGE_TOKEN
device_idoptional; see conditionsstringOriginal installation identifier for public login operations; authenticated operations use their bound installation.—Example: install-demo-001
next_channeloptional; see conditionsbooleanExplicitly advance Bird to its next allowed delivery channel. Earlier codes can remain valid. Unsupported for local SMS.—Example: false
JSON request example
{
    "challenge_id": "01JB0000000000000000000001",
    "challenge_token": "YOUR_CHALLENGE_TOKEN",
    "device_id": "install-demo-001"
}
cURL request
curl --request POST "$BASE_URL/auth/resend" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID' \
  --header 'Content-Type: application/json' \
  --data '{
    "challenge_id": "01JB0000000000000000000001",
    "challenge_token": "YOUR_CHALLENGE_TOKEN",
    "device_id": "install-demo-001"
}'

Responses

401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
202 Remote verification is queued; no customer authority has been granted.

Remote verification is queued; no customer authority has been granted.

application/json · Illustrative example

{
    "status": "pending",
    "operation_id": "01JB0000000000000000000002",
    "retry_after": 3,
    "challenge_id": "01JB0000000000000000000001",
    "expires_at": "2026-10-30T10:22:00+00:00",
    "resend_after": null,
    "attempts_allowed": 5,
    "attempts_left": 5
}
Response schema
FieldTypeDescriptionConstraints & example
statusoptional; see conditionsstringPending until the worker records an outcome.—Example: pending
operation_idoptional; see conditionsstringQueued verification operation returned by the prior check or resend.Min length: 26 · Max length: 26Example: 01JB0000000000000000000002
challenge_idoptional; see conditionsstringOpaque challenge identifier returned by the matching start operation.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
retry_afteroptional; see conditionsintegerSeconds to wait before polling.—Example: 3
expires_atoptional; see conditionsstring | nullCurrent code expiry in the business time zone. Before remote send completion this is provisional; poll status for the provider expiry.Format: date-timeExample: 2026-10-30T10:22:00+00:00
resend_afteroptional; see conditionsstring | nullEarliest resend time in the business time zone. Null while a remote send is pending, after the send limit, or when the challenge is no longer pending. Poll status after a queued send.Format: date-timeExample: 2026-10-30T10:13:00+00:00
attempts_allowedoptional; see conditionsintegerMaximum number of code checks allowed for this challenge.—Example: 5
attempts_leftoptional; see conditionsintegerCurrent checks remaining; zero after attempt exhaustion. The final wrong check returns too_many_attempts.Minimum: 0Example: 4
channeloptional; see conditionsstring | nullMost recently observed delivery channel; poll status for the newly queued send.—Example: sms

Devices

PUT/devices/push-token

Register or remove this installation push token

Register the FCM token of the signed-in installation together with the Firebase project it came from, or pass null to stop push to this device. Device registration alone is not marketing consent.

Bearer tokenX-App-Id

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
push_tokenrequired in parent objectstring | nullFCM registration token for this installation. Send null to remove it and stop push delivery to this device.Max length: 500Example: null
firebase_project_idoptional; see conditionsstring | nullFirebase project ID from this app build's own Firebase options. Required with a non-null push_token on a device that has no registered project yet; an older build may omit it only to refresh the token of a device already registered. 409 push_project_mismatch when the business has not set up this project for the app; 409 push_project_required when it is missing on a new device.Max length: 100Example: cafe-app-prod
JSON request example
{
    "push_token": "fcm…",
    "firebase_project_id": "cafe-app-prod"
}
cURL request
curl --request PUT "$BASE_URL/devices/push-token" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "push_token": "fcm…",
    "firebase_project_id": "cafe-app-prod"
}'

Responses

200 Success

Success

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

{
    "push": false
}
Response schema
FieldTypeDescriptionConstraints & example
pushoptional; see conditionsbooleanWhether a non-null push token is registered by this update.—
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended

Account

GET/me

Account, available rewards and card

Read the signed-in customer profile, membership and up to 50 available rewards. Use /me/rewards for paginated reward history.

Bearer tokenX-App-Id

Parameters

Name / locationTypeDescriptionConstraints & example
X-App-Platformheader · optional; see conditionsstringInstalled native platform, ios or android. Used with X-App-Version for minimum-version checks; if omitted, the signed-in installation value is used.Allowed: ios, androidExample: ios
X-App-Versionheader · optional; see conditionsstringInstalled app version, for example 1.4.0. Send after upgrading an existing installation. Missing or older versions return 426 when a minimum applies; omitted uses the signed-in installation value.—Example: 1.4.0
cURL request
curl --request GET "$BASE_URL/me" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID' \
  --header 'Authorization: Bearer YOUR_TOKEN'

Responses

200 Success

Success

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

{
    "customer": {
        "id": "01JB0000000000000000000001",
        "name": "Alex Morgan",
        "locale": "en_GB",
        "status": "active",
        "birthday": "1995-10-24",
        "push_consent": false,
        "marketing_consent": false,
        "identifiers": [
            {
                "type": "phone",
                "value": "string",
                "verified": false
            }
        ],
        "identity_assurance": "verified_contact",
        "recovery_available": false,
        "view_only_link_available": true
    },
    "membership": {
        "member_code": "48213906574",
        "balance": 10,
        "spendable": 10,
        "lifetime": 10,
        "points_label": "string",
        "cycle": {
            "number": 0,
            "points": 0,
            "target": 0,
            "points_to_next": 0,
            "next_reward": {
                "id": "string",
                "name": "string",
                "threshold": 0,
                "type": "free_item",
                "value_label": "string",
                "icon": "gift"
            },
            "reset_after_last": false,
            "carry_over": false,
            "milestones": [
                {
                    "points": 0,
                    "reward": {
                        "id": "string",
                        "name": "string",
                        "type": "free_item",
                        "value_label": "string",
                        "icon": "gift"
                    },
                    "status": "locked",
                    "entitlement_id": "string",
                    "reached": false,
                    "steps": [
                        {
                            "points": null,
                            "value": null,
                            "reached": null
                        }
                    ]
                }
            ],
            "completed": {
                "number": 0,
                "target": 0,
                "cards": 0,
                "completed_at": "2026-10-24T10:00:00Z",
                "milestones": [
                    {
                        "points": 0,
                        "reward": {
                            "id": null,
                            "name": null,
                            "type": null,
                            "value_label": null,
                            "icon": null
                        },
                        "status": "locked",
                        "entitlement_id": "string",
                        "reached": false,
                        "steps": [
                            null
                        ]
                    }
                ]
            }
        },
        "view_only_url": "https://example.com/experience/customer/cafe/card/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    },
    "rewards": [
        {
            "id": "01JB0000000000000000000001",
            "name": "Free coffee",
            "type": "amount_voucher",
            "value_label": "string",
            "value_minor": 1000,
            "currency": "GBP",
            "code": "DEMO7KQ2M",
            "status": "available",
            "used_at": "2026-10-24T10:00:00Z",
            "expires_at": "2026-10-24T10:00:00Z",
            "claim": {
                "eligible": true,
                "reason_code": null,
                "message": null,
                "points_required": 20,
                "available_actions": [
                    "claim"
                ]
            },
            "cycle": 0,
            "milestone": 0,
            "issued_at": "2026-10-24T10:00:00Z",
            "valid_through_date": "string",
            "expiry_timezone": "Asia/Dubai"
        }
    ],
    "google_wallet": false
}
Response schema
FieldTypeDescriptionConstraints & example
customeroptional; see conditionsobjectCustomer profile belonging to the resolved business.—
customer.idoptional; see conditionsstringCustomer public ULID within this business. Do not substitute a phone number or legacy database ID.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.localeoptional; see conditionsstring | nullLanguage/region code such as en_GB; determines localized presentation.—Example: en_GB
customer.statusoptional; see conditionsstringCustomer lifecycle state, for example active or provisional. Suspended, merged or erased accounts cannot use normal authenticated operations. Suspended customers receive 403 account_unavailable with error.reason suspended instead of an authenticated profile.Allowed: active, provisional, suspended, merged, erased
customer.birthdayoptional; see conditionsstring | nullFull date of birth as YYYY-MM-DD, for example 1995-10-24. Must be a valid calendar date, no later than today in the business timezone. Null clears it.Format: dateExample: 1995-10-24
customer.push_consentoptional; see conditionsbooleanCustomer consent for push marketing. Marketing delivery additionally requires marketing_consent and a reachable signed-in device.—
customer.marketing_consentoptional; see conditionsbooleanCustomer consent for marketing messages. Marketing push also requires push_consent; a push token alone is not consent.—
customer.identifiersoptional; see conditionsarray<object>Customer contact identifiers and their ownership-verification state.—
customer.identifiers[].typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: phone, email
customer.identifiers[].valueoptional; see conditionsstringIdentifier value, such as an email address or E.164 phone number.—
customer.identifiers[].verifiedoptional; see conditionsbooleanWhether ownership of this identifier has been verified.—
customer.identity_assuranceoptional; see conditionsstringContact assurance, separate from lifecycle status. Anonymous accounts use browser sessions only; native sign-in remains contact-verified.Allowed: verified_contact, anonymous, unverified_entry, demo
customer.recovery_availableoptional; see conditionsbooleanAt least one verified contact or an unexpired unused private recovery key exists. Wallet installation alone does not provide browser recovery.—
customer.view_only_link_availableoptional; see conditionsbooleanThis active account has an active loyalty membership and can manage an optional view-only membership link through its authenticated browser session. Available to registered and anonymously enrolled members. The link never authorizes account access.—Example: true
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.—Example: 48213906574
membership.balanceoptional; see conditionsintegerAccumulated points balance, not current-cycle progress. Render claim controls only from server claim decisions.—Example: 10
membership.spendableoptional; see conditionsintegerPoints available to spend, excluding holds. Claim decisions remain authoritative.—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.cycleoptional; see conditionsobject | nullPublished ladder progress, separate from accumulated balance. Reads never issue rewards. Null without an active supported ladder.—
membership.cycle.numberrequired in parent objectintegerCurrent cycle number.—
membership.cycle.pointsrequired in parent objectintegerCurrent-cycle points; distinct from accumulated balance.—
membership.cycle.targetrequired in parent objectinteger | nullPublished final milestone threshold; null without a threshold.—
membership.cycle.points_to_nextrequired in parent objectinteger | nullPoints remaining to the next offered higher threshold; null when none.—
membership.cycle.next_rewardrequired in parent objectobject | nullNext offered reward above current-cycle points, or null.—
membership.cycle.next_reward.idrequired in parent objectstringPublic object ID; event IDs include their source prefix.—
membership.cycle.next_reward.namerequired in parent objectstringCustomer-facing display name.—
membership.cycle.next_reward.thresholdrequired in parent objectintegerPoints threshold of the next offered reward.—
membership.cycle.next_reward.typerequired in parent objectstringReward kind, for the reward stop's badge.Allowed: free_item, amount_voucher, percent_off
membership.cycle.next_reward.value_labelrequired in parent objectstring | nullShort badge value such as £10 or 10%; null for free items.—
membership.cycle.next_reward.iconrequired in parent objectstringPicture for the reward: the business's pick, else money (amount voucher), percent (percent off) or gift (free item). Treat unknown values as gift.Allowed: gift, coffee, drink, food, dessert, money, percent, ticket, star, bag
membership.cycle.reset_after_lastrequired in parent objectbooleanWhether earning through the last threshold starts the next cycle.—
membership.cycle.carry_overrequired in parent objectbooleanWhether excess earning carries into the next cycle.—
membership.cycle.milestonesrequired in parent objectarray<object>Published ladder milestones, at most ten. State does not authorize a claim.Max items: 10
membership.cycle.milestones[].pointsrequired in parent objectintegerCurrent-cycle points; distinct from accumulated balance.—
membership.cycle.milestones[].rewardrequired in parent objectobject | nullRelated reward identity and display name, or null for ledger-only activity.—
membership.cycle.milestones[].reward.idrequired in parent objectstringPublic object ID; event IDs include their source prefix.—
membership.cycle.milestones[].reward.namerequired in parent objectstringCustomer-facing display name.—
membership.cycle.milestones[].reward.typerequired in parent objectstringReward kind, for the reward stop's badge.Allowed: free_item, amount_voucher, percent_off
membership.cycle.milestones[].reward.value_labelrequired in parent objectstring | nullShort badge value such as £10 or 10%; null for free items.—
membership.cycle.milestones[].reward.iconrequired in parent objectstringPicture for the reward: the business's pick, else money (amount voucher), percent (percent off) or gift (free item). Treat unknown values as gift.Allowed: gift, coffee, drink, food, dessert, money, percent, ticket, star, bag
membership.cycle.milestones[].statusrequired in parent objectstringMilestone/grant state, or ledger posting status in activity. pending means reached but unissued, and never authorizes a claim. A reward a refund took back reports locked (or pending once reached), as it is issued again; revoked means revoked by staff.Allowed: locked, pending, unavailable, earned, available, used, expired, revoked
membership.cycle.milestones[].entitlement_idrequired in parent objectstring | nullOwned grant public ID for this cycle/milestone, or null.—
membership.cycle.milestones[].reachedrequired in parent objectbooleanCurrent-cycle points have reached this milestone.—
membership.cycle.milestones[].stepsrequired in parent objectarray<object>Stops a card shows before this milestone's reward, split by the server (one per point up to 10, else 5-10 even stops, else 5 rounded). Draw as given.Max items: 9
membership.cycle.milestones[].steps[].pointsrequired in parent objectintegerCumulative current-cycle threshold of this stop.—
membership.cycle.milestones[].steps[].valuerequired in parent objectintegerPoints this stop is worth (from the previous stop).—
membership.cycle.milestones[].steps[].reachedrequired in parent objectbooleanCurrent-cycle points have reached this stop.—
membership.cycle.completedrequired in parent objectobject | nullThe card the member's latest point-earning purchase filled (its last-milestone reward came from that purchase, is still the member's own and was not refunded). Null otherwise, including once points are earned again, once no reward on it is still waiting (all used, expired or withdrawn), for ladders that do not start a new card, and after a refund takes that reward back. Display only: the cycle has already moved on and its rewards are issued.—
membership.cycle.completed.numberrequired in parent objectintegerCycle number of the filled card.—
membership.cycle.completed.targetrequired in parent objectintegerPoints on a full card.—
membership.cycle.completed.cardsrequired in parent objectintegerCards that purchase filled (more than one for a very large purchase).—
membership.cycle.completed.completed_atrequired in parent objectstring | nullWhen its last reward was issued.Format: date-time
membership.cycle.completed.milestonesrequired in parent objectarray<object>Published ladder milestones, at most ten. State does not authorize a claim.Max items: 10
membership.cycle.completed.milestones[].pointsrequired in parent objectintegerCurrent-cycle points; distinct from accumulated balance.—
membership.cycle.completed.milestones[].rewardrequired in parent objectobject | nullRelated reward identity and display name, or null for ledger-only activity.—
membership.cycle.completed.milestones[].reward.idrequired in parent objectstringPublic object ID; event IDs include their source prefix.—
membership.cycle.completed.milestones[].reward.namerequired in parent objectstringCustomer-facing display name.—
membership.cycle.completed.milestones[].reward.typerequired in parent objectstringReward kind, for the reward stop's badge.Allowed: free_item, amount_voucher, percent_off
membership.cycle.completed.milestones[].reward.value_labelrequired in parent objectstring | nullShort badge value such as £10 or 10%; null for free items.—
membership.cycle.completed.milestones[].reward.iconrequired in parent objectstringPicture for the reward: the business's pick, else money (amount voucher), percent (percent off) or gift (free item). Treat unknown values as gift.Allowed: gift, coffee, drink, food, dessert, money, percent, ticket, star, bag
membership.cycle.completed.milestones[].statusrequired in parent objectstringMilestone/grant state, or ledger posting status in activity. pending means reached but unissued, and never authorizes a claim. A reward a refund took back reports locked (or pending once reached), as it is issued again; revoked means revoked by staff.Allowed: locked, pending, unavailable, earned, available, used, expired, revoked
membership.cycle.completed.milestones[].entitlement_idrequired in parent objectstring | nullOwned grant public ID for this cycle/milestone, or null.—
membership.cycle.completed.milestones[].reachedrequired in parent objectbooleanCurrent-cycle points have reached this milestone.—
membership.cycle.completed.milestones[].stepsrequired in parent objectarray<object>Stops a card shows before this milestone's reward, split by the server (one per point up to 10, else 5-10 even stops, else 5 rounded). Draw as given.Max items: 9
membership.cycle.completed.milestones[].steps[].pointsrequired in parent objectintegerCumulative current-cycle threshold of this stop.—
membership.cycle.completed.milestones[].steps[].valuerequired in parent objectintegerPoints this stop is worth (from the previous stop).—
membership.cycle.completed.milestones[].steps[].reachedrequired in parent objectbooleanCurrent-cycle points have reached this stop.—
membership.view_only_urloptional; see conditionsstring | nullOptional shareable view-only card URL showing membership code, points and progress. Anyone with the URL can view those fields. It cannot authenticate, recover an account, reveal vouchers or authorize claims. Null when no link is enabled.Format: uriExample: https://example.com/experience/customer/cafe/card/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
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[].nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Free coffee
rewards[].typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: amount_voucher, free_item, percent_off
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[].currencyoptional; see conditionsstringISO 4217 currency code, for example GBP or AED. Monetary amounts are expressed in its minor units.—Example: GBP
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[].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.—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[].cycleoptional; see conditionsinteger | nullCard (cycle) a milestone reward came from; null for rewards bought with points.—
rewards[].milestoneoptional; see conditionsinteger | nullMilestone points on that card; null for rewards bought with points.—
rewards[].issued_atoptional; see conditionsstring | nullWhen the reward was issued; for the last milestone, when its card was completed.Format: date-time
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
google_walletoptional; see conditionsbooleanWhether Google Wallet is available for this business/customer.—
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
PATCH/me

Profile and separately recorded consent

Update profile and explicit consent. name aliases display_name; display_name takes precedence. Birthday is a full date of birth (YYYY-MM-DD); consent flags are independently recorded.

Bearer tokenX-App-Id

Request body application/json · required

Partial profile update. Omitted fields remain unchanged; nullable fields may be cleared with null. Consent changes must reflect the customer’s explicit choice.

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

FieldTypeDescriptionConstraints & example
display_nameoptional; see conditionsstring | nullCustomer display name. Send null to clear it; omitted fields are left unchanged.Max length: 100Example: Alex Morgan
nameoptional; see conditionsstring | nullAlias for display_name. If both fields are provided, display_name wins. Null clears the stored name.Max length: 100
localeoptional; see conditionsstring | nullCustomer locale such as en, en_GB or en-GB: two lowercase language letters, optionally followed by an underscore/hyphen and two region letters. Null clears it.Max length: 10Example: en_GB
birthdayoptional; see conditionsstring | nullFull date of birth as YYYY-MM-DD, for example 1995-10-24. Must be a valid calendar date, no later than today in the business timezone. Null clears it.Format: dateExample: 1995-10-24
push_consentoptional; see conditionsbooleanCustomer consent for push marketing. Marketing delivery additionally requires marketing_consent and a reachable signed-in device.—Example: true
marketing_consentoptional; see conditionsbooleanCustomer consent for marketing messages. Marketing push also requires push_consent; a push token alone is not consent.—Example: false
JSON request example
{
    "display_name": "Alex Morgan",
    "locale": "en_GB",
    "birthday": "1995-10-24",
    "push_consent": true,
    "marketing_consent": false
}
cURL request
curl --request PATCH "$BASE_URL/me" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "display_name": "Alex Morgan",
    "locale": "en_GB",
    "birthday": "1995-10-24",
    "push_consent": true,
    "marketing_consent": false
}'

Responses

200 Success

Success

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

{
    "customer": {
        "id": "01JB0000000000000000000001",
        "name": "Alex Morgan",
        "locale": "en_GB",
        "status": "active",
        "birthday": "1995-10-24",
        "push_consent": false,
        "marketing_consent": false,
        "identifiers": [
            {
                "type": "phone",
                "value": "string",
                "verified": false
            }
        ],
        "identity_assurance": "verified_contact",
        "recovery_available": false,
        "view_only_link_available": true
    },
    "membership": {
        "member_code": "48213906574",
        "balance": 10,
        "spendable": 10,
        "lifetime": 10,
        "points_label": "string",
        "cycle": {
            "number": 0,
            "points": 0,
            "target": 0,
            "points_to_next": 0,
            "next_reward": {
                "id": "string",
                "name": "string",
                "threshold": 0,
                "type": "free_item",
                "value_label": "string",
                "icon": "gift"
            },
            "reset_after_last": false,
            "carry_over": false,
            "milestones": [
                {
                    "points": 0,
                    "reward": {
                        "id": "string",
                        "name": "string",
                        "type": "free_item",
                        "value_label": "string",
                        "icon": "gift"
                    },
                    "status": "locked",
                    "entitlement_id": "string",
                    "reached": false,
                    "steps": [
                        {
                            "points": null,
                            "value": null,
                            "reached": null
                        }
                    ]
                }
            ],
            "completed": {
                "number": 0,
                "target": 0,
                "cards": 0,
                "completed_at": "2026-10-24T10:00:00Z",
                "milestones": [
                    {
                        "points": 0,
                        "reward": {
                            "id": null,
                            "name": null,
                            "type": null,
                            "value_label": null,
                            "icon": null
                        },
                        "status": "locked",
                        "entitlement_id": "string",
                        "reached": false,
                        "steps": [
                            null
                        ]
                    }
                ]
            }
        },
        "view_only_url": "https://example.com/experience/customer/cafe/card/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    },
    "rewards": [
        {
            "id": "01JB0000000000000000000001",
            "name": "Free coffee",
            "type": "amount_voucher",
            "value_label": "string",
            "value_minor": 1000,
            "currency": "GBP",
            "code": "DEMO7KQ2M",
            "status": "available",
            "used_at": "2026-10-24T10:00:00Z",
            "expires_at": "2026-10-24T10:00:00Z",
            "claim": {
                "eligible": true,
                "reason_code": null,
                "message": null,
                "points_required": 20,
                "available_actions": [
                    "claim"
                ]
            },
            "cycle": 0,
            "milestone": 0,
            "issued_at": "2026-10-24T10:00:00Z",
            "valid_through_date": "string",
            "expiry_timezone": "Asia/Dubai"
        }
    ],
    "google_wallet": false
}
Response schema
FieldTypeDescriptionConstraints & example
customeroptional; see conditionsobjectCustomer profile belonging to the resolved business.—
customer.idoptional; see conditionsstringCustomer public ULID within this business. Do not substitute a phone number or legacy database ID.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.localeoptional; see conditionsstring | nullLanguage/region code such as en_GB; determines localized presentation.—Example: en_GB
customer.statusoptional; see conditionsstringCustomer lifecycle state, for example active or provisional. Suspended, merged or erased accounts cannot use normal authenticated operations. Suspended customers receive 403 account_unavailable with error.reason suspended instead of an authenticated profile.Allowed: active, provisional, suspended, merged, erased
customer.birthdayoptional; see conditionsstring | nullFull date of birth as YYYY-MM-DD, for example 1995-10-24. Must be a valid calendar date, no later than today in the business timezone. Null clears it.Format: dateExample: 1995-10-24
customer.push_consentoptional; see conditionsbooleanCustomer consent for push marketing. Marketing delivery additionally requires marketing_consent and a reachable signed-in device.—
customer.marketing_consentoptional; see conditionsbooleanCustomer consent for marketing messages. Marketing push also requires push_consent; a push token alone is not consent.—
customer.identifiersoptional; see conditionsarray<object>Customer contact identifiers and their ownership-verification state.—
customer.identifiers[].typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: phone, email
customer.identifiers[].valueoptional; see conditionsstringIdentifier value, such as an email address or E.164 phone number.—
customer.identifiers[].verifiedoptional; see conditionsbooleanWhether ownership of this identifier has been verified.—
customer.identity_assuranceoptional; see conditionsstringContact assurance, separate from lifecycle status. Anonymous accounts use browser sessions only; native sign-in remains contact-verified.Allowed: verified_contact, anonymous, unverified_entry, demo
customer.recovery_availableoptional; see conditionsbooleanAt least one verified contact or an unexpired unused private recovery key exists. Wallet installation alone does not provide browser recovery.—
customer.view_only_link_availableoptional; see conditionsbooleanThis active account has an active loyalty membership and can manage an optional view-only membership link through its authenticated browser session. Available to registered and anonymously enrolled members. The link never authorizes account access.—Example: true
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.—Example: 48213906574
membership.balanceoptional; see conditionsintegerAccumulated points balance, not current-cycle progress. Render claim controls only from server claim decisions.—Example: 10
membership.spendableoptional; see conditionsintegerPoints available to spend, excluding holds. Claim decisions remain authoritative.—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.cycleoptional; see conditionsobject | nullPublished ladder progress, separate from accumulated balance. Reads never issue rewards. Null without an active supported ladder.—
membership.cycle.numberrequired in parent objectintegerCurrent cycle number.—
membership.cycle.pointsrequired in parent objectintegerCurrent-cycle points; distinct from accumulated balance.—
membership.cycle.targetrequired in parent objectinteger | nullPublished final milestone threshold; null without a threshold.—
membership.cycle.points_to_nextrequired in parent objectinteger | nullPoints remaining to the next offered higher threshold; null when none.—
membership.cycle.next_rewardrequired in parent objectobject | nullNext offered reward above current-cycle points, or null.—
membership.cycle.next_reward.idrequired in parent objectstringPublic object ID; event IDs include their source prefix.—
membership.cycle.next_reward.namerequired in parent objectstringCustomer-facing display name.—
membership.cycle.next_reward.thresholdrequired in parent objectintegerPoints threshold of the next offered reward.—
membership.cycle.next_reward.typerequired in parent objectstringReward kind, for the reward stop's badge.Allowed: free_item, amount_voucher, percent_off
membership.cycle.next_reward.value_labelrequired in parent objectstring | nullShort badge value such as £10 or 10%; null for free items.—
membership.cycle.next_reward.iconrequired in parent objectstringPicture for the reward: the business's pick, else money (amount voucher), percent (percent off) or gift (free item). Treat unknown values as gift.Allowed: gift, coffee, drink, food, dessert, money, percent, ticket, star, bag
membership.cycle.reset_after_lastrequired in parent objectbooleanWhether earning through the last threshold starts the next cycle.—
membership.cycle.carry_overrequired in parent objectbooleanWhether excess earning carries into the next cycle.—
membership.cycle.milestonesrequired in parent objectarray<object>Published ladder milestones, at most ten. State does not authorize a claim.Max items: 10
membership.cycle.milestones[].pointsrequired in parent objectintegerCurrent-cycle points; distinct from accumulated balance.—
membership.cycle.milestones[].rewardrequired in parent objectobject | nullRelated reward identity and display name, or null for ledger-only activity.—
membership.cycle.milestones[].reward.idrequired in parent objectstringPublic object ID; event IDs include their source prefix.—
membership.cycle.milestones[].reward.namerequired in parent objectstringCustomer-facing display name.—
membership.cycle.milestones[].reward.typerequired in parent objectstringReward kind, for the reward stop's badge.Allowed: free_item, amount_voucher, percent_off
membership.cycle.milestones[].reward.value_labelrequired in parent objectstring | nullShort badge value such as £10 or 10%; null for free items.—
membership.cycle.milestones[].reward.iconrequired in parent objectstringPicture for the reward: the business's pick, else money (amount voucher), percent (percent off) or gift (free item). Treat unknown values as gift.Allowed: gift, coffee, drink, food, dessert, money, percent, ticket, star, bag
membership.cycle.milestones[].statusrequired in parent objectstringMilestone/grant state, or ledger posting status in activity. pending means reached but unissued, and never authorizes a claim. A reward a refund took back reports locked (or pending once reached), as it is issued again; revoked means revoked by staff.Allowed: locked, pending, unavailable, earned, available, used, expired, revoked
membership.cycle.milestones[].entitlement_idrequired in parent objectstring | nullOwned grant public ID for this cycle/milestone, or null.—
membership.cycle.milestones[].reachedrequired in parent objectbooleanCurrent-cycle points have reached this milestone.—
membership.cycle.milestones[].stepsrequired in parent objectarray<object>Stops a card shows before this milestone's reward, split by the server (one per point up to 10, else 5-10 even stops, else 5 rounded). Draw as given.Max items: 9
membership.cycle.milestones[].steps[].pointsrequired in parent objectintegerCumulative current-cycle threshold of this stop.—
membership.cycle.milestones[].steps[].valuerequired in parent objectintegerPoints this stop is worth (from the previous stop).—
membership.cycle.milestones[].steps[].reachedrequired in parent objectbooleanCurrent-cycle points have reached this stop.—
membership.cycle.completedrequired in parent objectobject | nullThe card the member's latest point-earning purchase filled (its last-milestone reward came from that purchase, is still the member's own and was not refunded). Null otherwise, including once points are earned again, once no reward on it is still waiting (all used, expired or withdrawn), for ladders that do not start a new card, and after a refund takes that reward back. Display only: the cycle has already moved on and its rewards are issued.—
membership.cycle.completed.numberrequired in parent objectintegerCycle number of the filled card.—
membership.cycle.completed.targetrequired in parent objectintegerPoints on a full card.—
membership.cycle.completed.cardsrequired in parent objectintegerCards that purchase filled (more than one for a very large purchase).—
membership.cycle.completed.completed_atrequired in parent objectstring | nullWhen its last reward was issued.Format: date-time
membership.cycle.completed.milestonesrequired in parent objectarray<object>Published ladder milestones, at most ten. State does not authorize a claim.Max items: 10
membership.cycle.completed.milestones[].pointsrequired in parent objectintegerCurrent-cycle points; distinct from accumulated balance.—
membership.cycle.completed.milestones[].rewardrequired in parent objectobject | nullRelated reward identity and display name, or null for ledger-only activity.—
membership.cycle.completed.milestones[].reward.idrequired in parent objectstringPublic object ID; event IDs include their source prefix.—
membership.cycle.completed.milestones[].reward.namerequired in parent objectstringCustomer-facing display name.—
membership.cycle.completed.milestones[].reward.typerequired in parent objectstringReward kind, for the reward stop's badge.Allowed: free_item, amount_voucher, percent_off
membership.cycle.completed.milestones[].reward.value_labelrequired in parent objectstring | nullShort badge value such as £10 or 10%; null for free items.—
membership.cycle.completed.milestones[].reward.iconrequired in parent objectstringPicture for the reward: the business's pick, else money (amount voucher), percent (percent off) or gift (free item). Treat unknown values as gift.Allowed: gift, coffee, drink, food, dessert, money, percent, ticket, star, bag
membership.cycle.completed.milestones[].statusrequired in parent objectstringMilestone/grant state, or ledger posting status in activity. pending means reached but unissued, and never authorizes a claim. A reward a refund took back reports locked (or pending once reached), as it is issued again; revoked means revoked by staff.Allowed: locked, pending, unavailable, earned, available, used, expired, revoked
membership.cycle.completed.milestones[].entitlement_idrequired in parent objectstring | nullOwned grant public ID for this cycle/milestone, or null.—
membership.cycle.completed.milestones[].reachedrequired in parent objectbooleanCurrent-cycle points have reached this milestone.—
membership.cycle.completed.milestones[].stepsrequired in parent objectarray<object>Stops a card shows before this milestone's reward, split by the server (one per point up to 10, else 5-10 even stops, else 5 rounded). Draw as given.Max items: 9
membership.cycle.completed.milestones[].steps[].pointsrequired in parent objectintegerCumulative current-cycle threshold of this stop.—
membership.cycle.completed.milestones[].steps[].valuerequired in parent objectintegerPoints this stop is worth (from the previous stop).—
membership.cycle.completed.milestones[].steps[].reachedrequired in parent objectbooleanCurrent-cycle points have reached this stop.—
membership.view_only_urloptional; see conditionsstring | nullOptional shareable view-only card URL showing membership code, points and progress. Anyone with the URL can view those fields. It cannot authenticate, recover an account, reveal vouchers or authorize claims. Null when no link is enabled.Format: uriExample: https://example.com/experience/customer/cafe/card/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
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[].nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Free coffee
rewards[].typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: amount_voucher, free_item, percent_off
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[].currencyoptional; see conditionsstringISO 4217 currency code, for example GBP or AED. Monetary amounts are expressed in its minor units.—Example: GBP
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[].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.—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[].cycleoptional; see conditionsinteger | nullCard (cycle) a milestone reward came from; null for rewards bought with points.—
rewards[].milestoneoptional; see conditionsinteger | nullMilestone points on that card; null for rewards bought with points.—
rewards[].issued_atoptional; see conditionsstring | nullWhen the reward was issued; for the last milestone, when its card was completed.Format: date-time
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
google_walletoptional; see conditionsbooleanWhether Google Wallet is available for this business/customer.—
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
DELETE/me

Erase the app account, revoke tokens, queue snapshot cleanup

Erase the app account with a fresh, single-use deletion OTP from POST /me/deletion/start. Revokes tokens immediately and queues retained snapshot cleanup. Financial audit records remain for reconciliation. With Bird Verify, submit challenge_token and an Idempotency-Key. HTTP 202 contains a queued check operation. Poll its status, then repeat this request with the same code/device, capability and operation_id to finalize locally. GET/status never returns a session token. A lost finalization response requires a fresh sign-in; completion does not replay a consumed challenge.

Bearer tokenX-App-Id Idempotency key

Parameters

Name / locationTypeDescriptionConstraints & example
Idempotency-Keyheader · optional; see conditionsstringRequired for a new Bird Verify check/resend. Reuse for a transport retry with identical input; use a new key for a different code.Max length: 100Example: verification-check-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
challenge_idrequired in parent objectstringDedicated deletion challenge ULID from POST /me/deletion/start for this signed-in customer. Login and identifier-link challenges cannot delete an account.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
coderequired in parent objectstringVerification code delivered for this challenge. Send as a string to preserve leading zeros. Expired, incorrect or consumed codes are refused.Max length: 10Example: 123456
challenge_tokenoptional; see conditionsstringBird Verify caller capability returned at start. Keep in memory; never place it in a URL.Min length: 64 · Max length: 64Example: YOUR_CHALLENGE_TOKEN
operation_idoptional; see conditionsstringQueued verification operation returned by the prior check or resend.Min length: 26 · Max length: 26Example: 01JB0000000000000000000002
JSON request example
{
    "challenge_id": "01JB0000000000000000000001",
    "code": "123456"
}
cURL request
curl --request DELETE "$BASE_URL/me" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "challenge_id": "01JB0000000000000000000001",
    "code": "123456"
}'

Responses

200 Success

Success

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

{
    "deleted": false
}
Response schema
FieldTypeDescriptionConstraints & example
deletedoptional; see conditionsbooleanTrue after account erasure has started and the account tokens have been revoked. Some cleanup completes asynchronously.—
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
202 Remote verification is queued; no customer authority has been granted.

Remote verification is queued; no customer authority has been granted.

application/json · Illustrative example

{
    "status": "pending",
    "operation_id": "01JB0000000000000000000002",
    "retry_after": 3
}
Response schema
FieldTypeDescriptionConstraints & example
statusoptional; see conditionsstringPending until the worker records an outcome.—Example: pending
operation_idoptional; see conditionsstringQueued verification operation returned by the prior check or resend.Min length: 26 · Max length: 26Example: 01JB0000000000000000000002
challenge_idoptional; see conditionsstringOpaque challenge identifier returned by the matching start operation.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
retry_afteroptional; see conditionsintegerSeconds to wait before polling.—Example: 3
GET/me/history

Own points history

Paginated history for this customer only. Monetary values are minor units; points and balances are integers. Page size is capped at 50.

Bearer tokenX-App-Id

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 50. Use pagination metadata to retrieve further pages.Minimum: 1 · Maximum: 50 · Default: 20Example: 20
cURL request
curl --request GET "$BASE_URL/me/history?page=1&per_page=20" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID' \
  --header 'Authorization: Bearer YOUR_TOKEN'

Responses

200 Success

Success

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

{
    "entries": [
        {
            "type": "earn",
            "points": 2,
            "balance_after": 0,
            "status": "string",
            "branch": "string",
            "bill_reference": "DEMO-BILL-001",
            "reason": "string",
            "notes": [
                "string"
            ],
            "created_at": "2026-10-24T10:00:00Z"
        }
    ],
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 20,
        "total": 1
    }
}
Response schema
FieldTypeDescriptionConstraints & example
entriesoptional; see conditionsarray<object>Loyalty ledger entries in this page, ordered newest first.—
entries[].typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: earn, redeem, adjust, reversal, expiry, refund, claim_in, claim_out
entries[].pointsoptional; see conditionsintegerSigned loyalty points movement for this ledger entry; positive earns and negative debits are expressed in points, not currency.—Example: 2
entries[].balance_afteroptional; see conditionsintegerPoints balance immediately after this ledger entry.—
entries[].statusoptional; see conditionsstringLedger posting state: posted points have been applied; held points are not yet spendable.—
entries[].branchoptional; see conditionsstring | nullBranch details or branch display name associated with this result.—
entries[].bill_referenceoptional; see conditionsstring | nullReceipt/bill reference connecting the operation to the business transaction.—Example: DEMO-BILL-001
entries[].reasonoptional; see conditionsstring | nullHuman-readable reason for the correction, retained in the audit trail.—
entries[].notesoptional; see conditionsarray<string>Human-readable calculation notes associated with this ledger entry.—
entries[].created_atoptional; see conditionsstringISO 8601 creation timestamp of the record.Format: date-time
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 X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
POST/me/deletion/start

Request an account deletion OTP

Send a dedicated account-deletion OTP to an existing verified identifier. Pass the resulting challenge_id and code to DELETE /me; sign-in challenges cannot authorize erasure. Requires a fresh X-App-Proof bound to the signed-in installation when proof protection is enabled. Authenticated code requests share a 10/hour customer limit across devices. Bird Verify returns a challenge_token and a pending send. Poll the matching status operation before asking for a code. Local SMS and email retain their existing check behavior.

Bearer tokenX-App-Id

Parameters

Name / locationTypeDescriptionConstraints & example
X-App-Proofheader · optional; see conditionsstringRequired when proof protection is enabled (default in production). Obtain a fresh, one-use proof bound to this app and device from the trusted server issuer. Missing/replayed proof returns 403 app_verification_required; never embed the issuer secret in an app.—Example: YOUR_VERIFICATION_PROOF

Request body application/json · optional

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

FieldTypeDescriptionConstraints & example
methodoptional; see conditionsstringVerified identifier type used to deliver the account-deletion code. If omitted, a verified phone is preferred, then email.Allowed: phone, emailExample: email
JSON request example
{
    "method": "email"
}
cURL request
curl --request POST "$BASE_URL/me/deletion/start" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "method": "email"
}'

Responses

202 OTP queued to an existing verified identifier

OTP queued to an existing verified identifier

application/json · Illustrative example

{
    "challenge_id": "01JB0000000000000000000001",
    "channel": "email"
}
Response schema
FieldTypeDescriptionConstraints & example
challenge_idrequired in parent objectstringOpaque verification challenge ULID returned by the matching start endpoint; use only for that verification workflow.Min length: 26 · Max length: 26
channelrequired in parent objectstring | nullChannel used for this operation; see the endpoint-specific allowed values.Allowed: email, sms, whatsapp
challenge_tokenoptional; see conditionsstringBird Verify caller capability returned at start. Keep in memory; never place it in a URL.Min length: 64 · Max length: 64Example: YOUR_CHALLENGE_TOKEN
provideroptional; see conditionsstringSelected provider; bird_verify requires queued checks.—Example: bird_verify
statusoptional; see conditionsstringA remote send starts pending.—Example: pending
expires_atoptional; see conditionsstring | nullCurrent code expiry in the business time zone. Before remote send completion this is provisional; poll status for the provider expiry.Format: date-timeExample: 2026-10-30T10:22:00+00:00
resend_afteroptional; see conditionsstring | nullEarliest resend time in the business time zone. Null while a remote send is pending, after the send limit, or when the challenge is no longer pending. Poll status after a queued send.Format: date-timeExample: 2026-10-30T10:13:00+00:00
attempts_allowedoptional; see conditionsintegerMaximum number of code checks allowed for this challenge.—Example: 5
attempts_leftoptional; see conditionsintegerCurrent checks remaining; zero after attempt exhaustion. The final wrong check returns too_many_attempts.Minimum: 0Example: 4
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
GET/me/activity

Own points and immutable voucher-use activity

Cursor-paginated customer activity. redeem is catalogue creation; reward_used is actual voucher use with null points/balance_after. Voids remain visible in all. Filters apply before pagination. Cursors bind to this customer and filter; invalid cursors return 422 invalid_cursor. Loyalty required.

Bearer tokenX-App-Id

Parameters

Name / locationTypeDescriptionConstraints & example
filterquery · optional; see conditionsstringearned selects positive posted earn entries; used selects actual voucher uses.Allowed: all, earned, used · Default: allExample: all
per_pagequery · optional; see conditionsintegerNumber of events per page, from 1 to 50; default 20.Minimum: 1 · Maximum: 50 · Default: 20Example: 20
cursorquery · optional; see conditionsstringOpaque meta.next_cursor, omitted for the first page. URL-encode it.Max length: 4096Example: opaque-cursor-from-meta
cURL request
curl --request GET "$BASE_URL/me/activity?per_page=20" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID' \
  --header 'Authorization: Bearer YOUR_TOKEN'

Responses

200 Success

Success

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

{
    "entries": [
        {
            "id": "string",
            "type": "earn",
            "created_at": "2026-10-24T10:00:00Z",
            "points": 0,
            "balance_after": 0,
            "reward": {
                "id": "string",
                "name": "string"
            },
            "branch": {
                "id": 0,
                "name": "string"
            },
            "status": "string",
            "bill_reference": "string",
            "reverses_event_id": "string"
        }
    ],
    "meta": {
        "per_page": 0,
        "has_more": false,
        "next_cursor": "string"
    }
}
Response schema
FieldTypeDescriptionConstraints & example
entriesrequired in parent objectarray<object>Activity events ordered by time, source and internal ID, newest first.—
entries[].idrequired in parent objectstringStable source-prefixed public event ID.—
entries[].typerequired in parent objectstringExplicit event type. redeem is points spent at catalogue creation; reward_used is voucher use.Allowed: earn, redeem, adjust, reversal, expiry, refund, claim_in, claim_out, reward_used, reward_use_voided
entries[].created_atrequired in parent objectstringISO 8601 event occurrence timestamp.Format: date-time
entries[].pointsrequired in parent objectinteger | nullSigned ledger point amount; null for voucher use/void, which do not spend points.—
entries[].balance_afterrequired in parent objectinteger | nullLedger balance after this entry, or null for voucher use/void.—
entries[].rewardrequired in parent objectobject | nullRelated reward identity and display name, or null for ledger-only activity.—
entries[].reward.idrequired in parent objectstringPublic object ID; event IDs include their source prefix.—
entries[].reward.namerequired in parent objectstringCustomer-facing display name.—
entries[].branchrequired in parent objectobject | nullBranch identity and name, or null when absent.—
entries[].branch.idoptional; see conditionsintegerPublic object ID; event IDs include their source prefix.—
entries[].branch.nameoptional; see conditionsstringCustomer-facing display name.—
entries[].statusrequired in parent objectstring | nullMilestone/grant state, or ledger posting status in activity. pending means reached but unissued, and never authorizes a claim.—
entries[].bill_referencerequired in parent objectstring | nullBill reference supplied for the operation, or null.—
entries[].reverses_event_idrequired in parent objectstring | nullSource-prefixed use event ID being voided, or null.—
metarequired in parent objectobjectCursor pagination metadata.—
meta.per_pagerequired in parent objectintegerRequested bounded page size.—
meta.has_morerequired in parent objectbooleanWhether another page is available.—
meta.next_cursorrequired in parent objectstring | nullOpaque cursor for the next page, or null; retain the selected filter.—
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended

Rewards

GET/me/rewards

Own reward grants in all lifecycle states

List this customer’s owned rewards, optionally filtered by earned, available, used, expired or revoked. Earned entitlements have no code: use server claim.available_actions and POST rewards/create-voucher before showing a code. Use this endpoint for full paginated discovery rather than the limited account rewards list.

Bearer tokenX-App-Id

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 50. Use pagination metadata to retrieve further pages.Minimum: 1 · Maximum: 50 · Default: 20Example: 20
statusquery · optional; see conditionsstringFilter owned reward grants by lifecycle state. Omit to include all states. Expired available grants are reported as expired even before background expiry processing.Allowed: earned, available, used, expired, revokedExample: available
cURL request
curl --request GET "$BASE_URL/me/rewards?page=1&per_page=20" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID' \
  --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",
            "type": "amount_voucher",
            "value_label": "string",
            "value_minor": 1000,
            "currency": "GBP",
            "code": "DEMO7KQ2M",
            "status": "available",
            "used_at": "2026-10-24T10:00:00Z",
            "expires_at": "2026-10-24T10:00:00Z",
            "claim": {
                "eligible": true,
                "reason_code": null,
                "message": null,
                "points_required": 20,
                "available_actions": [
                    "claim"
                ]
            },
            "cycle": 0,
            "milestone": 0,
            "issued_at": "2026-10-24T10:00:00Z",
            "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[].nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Free coffee
rewards[].typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: amount_voucher, free_item, percent_off
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[].currencyoptional; see conditionsstringISO 4217 currency code, for example GBP or AED. Monetary amounts are expressed in its minor units.—Example: GBP
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[].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.—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[].cycleoptional; see conditionsinteger | nullCard (cycle) a milestone reward came from; null for rewards bought with points.—
rewards[].milestoneoptional; see conditionsinteger | nullMilestone points on that card; null for rewards bought with points.—
rewards[].issued_atoptional; see conditionsstring | nullWhen the reward was issued; for the last milestone, when its card was completed.Format: date-time
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 X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
GET/rewards

Active reward catalogue

Browse the active points-purchase catalogue. Milestone programs issue rewards automatically; catalogue availability depends on the published business policy.

Bearer tokenX-App-Id

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 50. Use pagination metadata to retrieve further pages.Minimum: 1 · Maximum: 50 · Default: 20Example: 20
cURL request
curl --request GET "$BASE_URL/rewards?page=1&per_page=20" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID' \
  --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.—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 X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
POST/rewards/purchase

Buy one reward for points

Buy a catalogue reward with spendable points and return the updated account snapshot. Send an Idempotency-Key and retain it when retrying the identical purchase. 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.

Bearer tokenX-App-Id 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
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
JSON request example
{
    "reward": "01JB0000000000000000000001"
}
cURL request
curl --request POST "$BASE_URL/rewards/purchase" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'Idempotency-Key: UNIQUE_OPERATION_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "reward": "01JB0000000000000000000001"
}'

Responses

201 Success

Success

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

{
    "customer": {
        "id": "01JB0000000000000000000001",
        "name": "Alex Morgan",
        "locale": "en_GB",
        "status": "active",
        "birthday": "1995-10-24",
        "push_consent": false,
        "marketing_consent": false,
        "identifiers": [
            {
                "type": "phone",
                "value": "string",
                "verified": false
            }
        ],
        "identity_assurance": "verified_contact",
        "recovery_available": false,
        "view_only_link_available": true
    },
    "membership": {
        "member_code": "48213906574",
        "balance": 10,
        "spendable": 10,
        "lifetime": 10,
        "points_label": "string",
        "cycle": {
            "number": 0,
            "points": 0,
            "target": 0,
            "points_to_next": 0,
            "next_reward": {
                "id": "string",
                "name": "string",
                "threshold": 0,
                "type": "free_item",
                "value_label": "string",
                "icon": "gift"
            },
            "reset_after_last": false,
            "carry_over": false,
            "milestones": [
                {
                    "points": 0,
                    "reward": {
                        "id": "string",
                        "name": "string",
                        "type": "free_item",
                        "value_label": "string",
                        "icon": "gift"
                    },
                    "status": "locked",
                    "entitlement_id": "string",
                    "reached": false,
                    "steps": [
                        {
                            "points": null,
                            "value": null,
                            "reached": null
                        }
                    ]
                }
            ],
            "completed": {
                "number": 0,
                "target": 0,
                "cards": 0,
                "completed_at": "2026-10-24T10:00:00Z",
                "milestones": [
                    {
                        "points": 0,
                        "reward": {
                            "id": null,
                            "name": null,
                            "type": null,
                            "value_label": null,
                            "icon": null
                        },
                        "status": "locked",
                        "entitlement_id": "string",
                        "reached": false,
                        "steps": [
                            null
                        ]
                    }
                ]
            }
        },
        "view_only_url": "https://example.com/experience/customer/cafe/card/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    },
    "rewards": [
        {
            "id": "01JB0000000000000000000001",
            "name": "Free coffee",
            "type": "amount_voucher",
            "value_label": "string",
            "value_minor": 1000,
            "currency": "GBP",
            "code": "DEMO7KQ2M",
            "status": "available",
            "used_at": "2026-10-24T10:00:00Z",
            "expires_at": "2026-10-24T10:00:00Z",
            "claim": {
                "eligible": true,
                "reason_code": null,
                "message": null,
                "points_required": 20,
                "available_actions": [
                    "claim"
                ]
            },
            "cycle": 0,
            "milestone": 0,
            "issued_at": "2026-10-24T10:00:00Z",
            "valid_through_date": "string",
            "expiry_timezone": "Asia/Dubai"
        }
    ],
    "google_wallet": false,
    "reward": {
        "id": "01JB0000000000000000000001",
        "name": "Free coffee",
        "type": "amount_voucher",
        "value_label": "string",
        "value_minor": 1000,
        "currency": "GBP",
        "code": "DEMO7KQ2M",
        "status": "available",
        "used_at": "2026-10-24T10:00:00Z",
        "expires_at": "2026-10-24T10:00:00Z",
        "claim": {
            "eligible": true,
            "reason_code": null,
            "message": null,
            "points_required": 20,
            "available_actions": [
                "claim"
            ]
        },
        "cycle": 0,
        "milestone": 0,
        "issued_at": "2026-10-24T10:00:00Z",
        "valid_through_date": "string",
        "expiry_timezone": "Asia/Dubai"
    }
}
Response schema
FieldTypeDescriptionConstraints & example
customeroptional; see conditionsobjectCustomer profile belonging to the resolved business.—
customer.idoptional; see conditionsstringCustomer public ULID within this business. Do not substitute a phone number or legacy database ID.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.localeoptional; see conditionsstring | nullLanguage/region code such as en_GB; determines localized presentation.—Example: en_GB
customer.statusoptional; see conditionsstringCustomer lifecycle state, for example active or provisional. Suspended, merged or erased accounts cannot use normal authenticated operations. Suspended customers receive 403 account_unavailable with error.reason suspended instead of an authenticated profile.Allowed: active, provisional, suspended, merged, erased
customer.birthdayoptional; see conditionsstring | nullFull date of birth as YYYY-MM-DD, for example 1995-10-24. Must be a valid calendar date, no later than today in the business timezone. Null clears it.Format: dateExample: 1995-10-24
customer.push_consentoptional; see conditionsbooleanCustomer consent for push marketing. Marketing delivery additionally requires marketing_consent and a reachable signed-in device.—
customer.marketing_consentoptional; see conditionsbooleanCustomer consent for marketing messages. Marketing push also requires push_consent; a push token alone is not consent.—
customer.identifiersoptional; see conditionsarray<object>Customer contact identifiers and their ownership-verification state.—
customer.identifiers[].typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: phone, email
customer.identifiers[].valueoptional; see conditionsstringIdentifier value, such as an email address or E.164 phone number.—
customer.identifiers[].verifiedoptional; see conditionsbooleanWhether ownership of this identifier has been verified.—
customer.identity_assuranceoptional; see conditionsstringContact assurance, separate from lifecycle status. Anonymous accounts use browser sessions only; native sign-in remains contact-verified.Allowed: verified_contact, anonymous, unverified_entry, demo
customer.recovery_availableoptional; see conditionsbooleanAt least one verified contact or an unexpired unused private recovery key exists. Wallet installation alone does not provide browser recovery.—
customer.view_only_link_availableoptional; see conditionsbooleanThis active account has an active loyalty membership and can manage an optional view-only membership link through its authenticated browser session. Available to registered and anonymously enrolled members. The link never authorizes account access.—Example: true
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.—Example: 48213906574
membership.balanceoptional; see conditionsintegerAccumulated points balance, not current-cycle progress. Render claim controls only from server claim decisions.—Example: 10
membership.spendableoptional; see conditionsintegerPoints available to spend, excluding holds. Claim decisions remain authoritative.—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.cycleoptional; see conditionsobject | nullPublished ladder progress, separate from accumulated balance. Reads never issue rewards. Null without an active supported ladder.—
membership.cycle.numberrequired in parent objectintegerCurrent cycle number.—
membership.cycle.pointsrequired in parent objectintegerCurrent-cycle points; distinct from accumulated balance.—
membership.cycle.targetrequired in parent objectinteger | nullPublished final milestone threshold; null without a threshold.—
membership.cycle.points_to_nextrequired in parent objectinteger | nullPoints remaining to the next offered higher threshold; null when none.—
membership.cycle.next_rewardrequired in parent objectobject | nullNext offered reward above current-cycle points, or null.—
membership.cycle.next_reward.idrequired in parent objectstringPublic object ID; event IDs include their source prefix.—
membership.cycle.next_reward.namerequired in parent objectstringCustomer-facing display name.—
membership.cycle.next_reward.thresholdrequired in parent objectintegerPoints threshold of the next offered reward.—
membership.cycle.next_reward.typerequired in parent objectstringReward kind, for the reward stop's badge.Allowed: free_item, amount_voucher, percent_off
membership.cycle.next_reward.value_labelrequired in parent objectstring | nullShort badge value such as £10 or 10%; null for free items.—
membership.cycle.next_reward.iconrequired in parent objectstringPicture for the reward: the business's pick, else money (amount voucher), percent (percent off) or gift (free item). Treat unknown values as gift.Allowed: gift, coffee, drink, food, dessert, money, percent, ticket, star, bag
membership.cycle.reset_after_lastrequired in parent objectbooleanWhether earning through the last threshold starts the next cycle.—
membership.cycle.carry_overrequired in parent objectbooleanWhether excess earning carries into the next cycle.—
membership.cycle.milestonesrequired in parent objectarray<object>Published ladder milestones, at most ten. State does not authorize a claim.Max items: 10
membership.cycle.milestones[].pointsrequired in parent objectintegerCurrent-cycle points; distinct from accumulated balance.—
membership.cycle.milestones[].rewardrequired in parent objectobject | nullRelated reward identity and display name, or null for ledger-only activity.—
membership.cycle.milestones[].reward.idrequired in parent objectstringPublic object ID; event IDs include their source prefix.—
membership.cycle.milestones[].reward.namerequired in parent objectstringCustomer-facing display name.—
membership.cycle.milestones[].reward.typerequired in parent objectstringReward kind, for the reward stop's badge.Allowed: free_item, amount_voucher, percent_off
membership.cycle.milestones[].reward.value_labelrequired in parent objectstring | nullShort badge value such as £10 or 10%; null for free items.—
membership.cycle.milestones[].reward.iconrequired in parent objectstringPicture for the reward: the business's pick, else money (amount voucher), percent (percent off) or gift (free item). Treat unknown values as gift.Allowed: gift, coffee, drink, food, dessert, money, percent, ticket, star, bag
membership.cycle.milestones[].statusrequired in parent objectstringMilestone/grant state, or ledger posting status in activity. pending means reached but unissued, and never authorizes a claim. A reward a refund took back reports locked (or pending once reached), as it is issued again; revoked means revoked by staff.Allowed: locked, pending, unavailable, earned, available, used, expired, revoked
membership.cycle.milestones[].entitlement_idrequired in parent objectstring | nullOwned grant public ID for this cycle/milestone, or null.—
membership.cycle.milestones[].reachedrequired in parent objectbooleanCurrent-cycle points have reached this milestone.—
membership.cycle.milestones[].stepsrequired in parent objectarray<object>Stops a card shows before this milestone's reward, split by the server (one per point up to 10, else 5-10 even stops, else 5 rounded). Draw as given.Max items: 9
membership.cycle.milestones[].steps[].pointsrequired in parent objectintegerCumulative current-cycle threshold of this stop.—
membership.cycle.milestones[].steps[].valuerequired in parent objectintegerPoints this stop is worth (from the previous stop).—
membership.cycle.milestones[].steps[].reachedrequired in parent objectbooleanCurrent-cycle points have reached this stop.—
membership.cycle.completedrequired in parent objectobject | nullThe card the member's latest point-earning purchase filled (its last-milestone reward came from that purchase, is still the member's own and was not refunded). Null otherwise, including once points are earned again, once no reward on it is still waiting (all used, expired or withdrawn), for ladders that do not start a new card, and after a refund takes that reward back. Display only: the cycle has already moved on and its rewards are issued.—
membership.cycle.completed.numberrequired in parent objectintegerCycle number of the filled card.—
membership.cycle.completed.targetrequired in parent objectintegerPoints on a full card.—
membership.cycle.completed.cardsrequired in parent objectintegerCards that purchase filled (more than one for a very large purchase).—
membership.cycle.completed.completed_atrequired in parent objectstring | nullWhen its last reward was issued.Format: date-time
membership.cycle.completed.milestonesrequired in parent objectarray<object>Published ladder milestones, at most ten. State does not authorize a claim.Max items: 10
membership.cycle.completed.milestones[].pointsrequired in parent objectintegerCurrent-cycle points; distinct from accumulated balance.—
membership.cycle.completed.milestones[].rewardrequired in parent objectobject | nullRelated reward identity and display name, or null for ledger-only activity.—
membership.cycle.completed.milestones[].reward.idrequired in parent objectstringPublic object ID; event IDs include their source prefix.—
membership.cycle.completed.milestones[].reward.namerequired in parent objectstringCustomer-facing display name.—
membership.cycle.completed.milestones[].reward.typerequired in parent objectstringReward kind, for the reward stop's badge.Allowed: free_item, amount_voucher, percent_off
membership.cycle.completed.milestones[].reward.value_labelrequired in parent objectstring | nullShort badge value such as £10 or 10%; null for free items.—
membership.cycle.completed.milestones[].reward.iconrequired in parent objectstringPicture for the reward: the business's pick, else money (amount voucher), percent (percent off) or gift (free item). Treat unknown values as gift.Allowed: gift, coffee, drink, food, dessert, money, percent, ticket, star, bag
membership.cycle.completed.milestones[].statusrequired in parent objectstringMilestone/grant state, or ledger posting status in activity. pending means reached but unissued, and never authorizes a claim. A reward a refund took back reports locked (or pending once reached), as it is issued again; revoked means revoked by staff.Allowed: locked, pending, unavailable, earned, available, used, expired, revoked
membership.cycle.completed.milestones[].entitlement_idrequired in parent objectstring | nullOwned grant public ID for this cycle/milestone, or null.—
membership.cycle.completed.milestones[].reachedrequired in parent objectbooleanCurrent-cycle points have reached this milestone.—
membership.cycle.completed.milestones[].stepsrequired in parent objectarray<object>Stops a card shows before this milestone's reward, split by the server (one per point up to 10, else 5-10 even stops, else 5 rounded). Draw as given.Max items: 9
membership.cycle.completed.milestones[].steps[].pointsrequired in parent objectintegerCumulative current-cycle threshold of this stop.—
membership.cycle.completed.milestones[].steps[].valuerequired in parent objectintegerPoints this stop is worth (from the previous stop).—
membership.cycle.completed.milestones[].steps[].reachedrequired in parent objectbooleanCurrent-cycle points have reached this stop.—
membership.view_only_urloptional; see conditionsstring | nullOptional shareable view-only card URL showing membership code, points and progress. Anyone with the URL can view those fields. It cannot authenticate, recover an account, reveal vouchers or authorize claims. Null when no link is enabled.Format: uriExample: https://example.com/experience/customer/cafe/card/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
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[].nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Free coffee
rewards[].typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: amount_voucher, free_item, percent_off
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[].currencyoptional; see conditionsstringISO 4217 currency code, for example GBP or AED. Monetary amounts are expressed in its minor units.—Example: GBP
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[].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.—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[].cycleoptional; see conditionsinteger | nullCard (cycle) a milestone reward came from; null for rewards bought with points.—
rewards[].milestoneoptional; see conditionsinteger | nullMilestone points on that card; null for rewards bought with points.—
rewards[].issued_atoptional; see conditionsstring | nullWhen the reward was issued; for the last milestone, when its card was completed.Format: date-time
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
google_walletoptional; see conditionsbooleanWhether Google Wallet is available for this business/customer.—
rewardoptional; see conditionsobjectAn individual reward issued to a customer, including its private redemption code when permitted.—
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.nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Free coffee
reward.typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: amount_voucher, free_item, percent_off
reward.value_labeloptional; see conditionsstringFormatted reward value for display, such as £10.00, 15% off or Free item. Use numeric fields for calculations.—
reward.value_minoroptional; see conditionsinteger | nullFixed reward value in integer currency minor units; for example 1000 GBP means £10.00.—Example: 1000
reward.currencyoptional; see conditionsstringISO 4217 currency code, for example GBP or AED. Monetary amounts are expressed in its minor units.—Example: GBP
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.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.—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.cycleoptional; see conditionsinteger | nullCard (cycle) a milestone reward came from; null for rewards bought with points.—
reward.milestoneoptional; see conditionsinteger | nullMilestone points on that card; null for rewards bought with points.—
reward.issued_atoptional; see conditionsstring | nullWhen the reward was issued; for the last milestone, when its card was completed.Format: date-time
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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. The signed-in customer must own the entitlement. Returns the voucher code, including when the store already created it. Reusing an idempotency key for a different entitlement returns 409. 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.

Bearer tokenX-App-Id 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
JSON request example
{
    "entitlement": "01JB0000000000000000000001"
}
cURL request
curl --request POST "$BASE_URL/rewards/create-voucher" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'Idempotency-Key: UNIQUE_OPERATION_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "entitlement": "01JB0000000000000000000001"
}'

Responses

201 Success

Success

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

{
    "reward": {
        "id": "01JB0000000000000000000001",
        "name": "Free coffee",
        "type": "amount_voucher",
        "value_label": "string",
        "value_minor": 1000,
        "currency": "GBP",
        "code": "DEMO7KQ2M",
        "status": "available",
        "used_at": "2026-10-24T10:00:00Z",
        "expires_at": "2026-10-24T10:00:00Z",
        "claim": {
            "eligible": true,
            "reason_code": null,
            "message": null,
            "points_required": 20,
            "available_actions": [
                "claim"
            ]
        },
        "cycle": 0,
        "milestone": 0,
        "issued_at": "2026-10-24T10:00:00Z",
        "valid_through_date": "string",
        "expiry_timezone": "Asia/Dubai"
    }
}
Response schema
FieldTypeDescriptionConstraints & example
rewardoptional; see conditionsobjectAn individual reward issued to a customer, including its private redemption code when permitted.—
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.nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Free coffee
reward.typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: amount_voucher, free_item, percent_off
reward.value_labeloptional; see conditionsstringFormatted reward value for display, such as £10.00, 15% off or Free item. Use numeric fields for calculations.—
reward.value_minoroptional; see conditionsinteger | nullFixed reward value in integer currency minor units; for example 1000 GBP means £10.00.—Example: 1000
reward.currencyoptional; see conditionsstringISO 4217 currency code, for example GBP or AED. Monetary amounts are expressed in its minor units.—Example: GBP
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.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.—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.cycleoptional; see conditionsinteger | nullCard (cycle) a milestone reward came from; null for rewards bought with points.—
reward.milestoneoptional; see conditionsinteger | nullMilestone points on that card; null for rewards bought with points.—
reward.issued_atoptional; see conditionsstring | nullWhen the reward was issued; for the last milestone, when its card was completed.Format: date-time
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended

Wallet & channels

GET/wallet

Direct Google Wallet save URL and Android save JWT

Get a private signed URL that opens Google's save flow directly, without an intermediate wallet page. google_wallet_jwt is the same card as a JWT for Android's PayClient.savePassesJwt(). Both carry member information including the member code; never log or publish either value. Returns 404 wallet_unavailable when Google Wallet is unavailable, or 503 wallet_unavailable if the pass cannot be prepared right now.

Bearer tokenX-App-Id
cURL request
curl --request GET "$BASE_URL/wallet" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID' \
  --header 'Authorization: Bearer YOUR_TOKEN'

Responses

200 Success

Success

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

{
    "google_wallet_url": "https://example.com/resource",
    "google_wallet_jwt": "string"
}
Response schema
FieldTypeDescriptionConstraints & example
google_wallet_urloptional; see conditionsstringDirect Google Wallet save URL containing a signed JWT with member information. Never log or publish the URL.Format: uri
google_wallet_jwtoptional; see conditionsstringSigned save JWT for Android's PayClient.savePassesJwt(). Present while a Google Wallet card can be issued. Treat as sensitive: it carries the member code.—
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
503 wallet_unavailable: the pass cannot be prepared right now; try again shortly.

wallet_unavailable: the pass cannot be prepared right now; try again shortly.

application/json · Illustrative example

{
    "error": {
        "code": "wallet_unavailable",
        "message": "Your Wallet pass cannot be prepared right now. Please 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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
GET/wallet/passes

The customer's current wallet passes of every kind, with their live state

The customer's current passes of every kind. The set and total are stable across pages; each pass's state is computed when the page is read, so only show passes whose state is active as usable. Issuing is not installing: a pass listed here may not be on the device. On iOS, check with PKPassLibrary using pass_type_identifier and serial_number; this works only for Pass Type IDs in the app's com.apple.developer.pass-type-identifiers entitlement, which covers the app's own signing team only. Treat an unknown result as not added.

Bearer tokenX-App-Id

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 50. Use pagination metadata to retrieve further pages.Minimum: 1 · Maximum: 50 · Default: 20Example: 20
cURL request
curl --request GET "$BASE_URL/wallet/passes?page=1&per_page=20" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID' \
  --header 'Authorization: Bearer YOUR_TOKEN'

Responses

200 Success

Success

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

{
    "passes": [
        {
            "id": "string",
            "kind": "string",
            "platform": "google",
            "state": "active",
            "apple": {
                "pass_type_identifier": "string",
                "serial_number": "string"
            },
            "google": {
                "object_id": "string"
            }
        }
    ],
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 20,
        "total": 1
    }
}
Response schema
FieldTypeDescriptionConstraints & example
passesoptional; see conditionsarray<object>Current passes of every kind issued to this customer, oldest first.—
passes[].idoptional; see conditionsstringStable public ID of the pass.—
passes[].kindoptional; see conditionsstringPass kind, e.g. loyalty (the loyalty card).—
passes[].platformoptional; see conditionsstringWallet the pass belongs to.Allowed: google, apple
passes[].stateoptional; see conditionsstringLive state of the pass. Only active passes are usable; the others stay installed but show as not valid.Allowed: active, inactive, expired, completed, revoked
passes[].appleoptional; see conditionsobjectPresent for Apple passes: the identity PKPassLibrary looks passes up by.—
passes[].apple.pass_type_identifieroptional; see conditionsstringThe pass's Pass Type ID. The app can read it only when listed in its pass-type-identifiers entitlement (same signing team).—
passes[].apple.serial_numberoptional; see conditionsstringThe pass's serial number; never changes.—
passes[].googleoptional; see conditionsobjectPresent for Google passes.—
passes[].google.object_idoptional; see conditionsstringThe Google Wallet object ID; never changes.—
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 X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
POST/me/card/reissue

Issue a new member code; old code and wallet links stop working

For a card that was shared or photographed. Generates a new member code, updates Google and Apple Wallet cards to show it, and ends the old code and every wallet link made before. Limited to 3 calls an hour; returns loyalty_unavailable while loyalty is off.

Bearer tokenX-App-Id
cURL request
curl --request POST "$BASE_URL/me/card/reissue" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID' \
  --header 'Authorization: Bearer YOUR_TOKEN'

Responses

200 Success

Success

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

{
    "member_code": "string"
}
Response schema
FieldTypeDescriptionConstraints & example
member_codeoptional; see conditionsstringThe member's new 11-digit member code. Staff scan it to spend points; show it only to the signed-in member.—
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
GET/wallet/apple

Signed Apple Wallet pass with update support

Download a signed Apple Wallet pass, not JSON. Stable pass identity and per-pass authentication support queued automatic updates through the separate PassKit service. Returns wallet_unavailable until signing assets are provisioned. Device refresh depends on Apple delivery.

Bearer tokenX-App-Id
cURL request
curl --request GET "$BASE_URL/wallet/apple" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID' \
  --header 'Authorization: Bearer YOUR_TOKEN'

Responses

200 Success

Success

application/vnd.apple.pkpass · Schema-generated illustration; optional fields and values vary

Binary pass download. Handle this response as a file using the declared media type.

401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
POST/whatsapp/link

Existing optional WhatsApp linking endpoint

Create a one-use WhatsApp linking URL valid for 15 minutes. The customer must send the prefilled message to complete linking; creating the URL alone does not link the account. Sending the prefilled message only requests confirmation. The recipient must tap the WhatsApp confirmation button naming the app account before any account linking. The app-visible token cannot confirm; a new single-use secret is bound to the WhatsApp recipient.

Bearer tokenX-App-Id
cURL request
curl --request POST "$BASE_URL/whatsapp/link" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID' \
  --header 'Authorization: Bearer YOUR_TOKEN'

Responses

201 Success

Success

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

{
    "url": "https://example.com/resource",
    "expires_at": "2026-10-24T10:00:00Z"
}
Response schema
FieldTypeDescriptionConstraints & example
urloptional; see conditionsstringPrivate WhatsApp linking URL. Open it for the signed-in customer before expires_at; it contains a short-lived linking code.Format: uri
expires_atoptional; see conditionsstringISO 8601 expiration timestamp, or null if no expiry applies. Do not offer an expired link or reward for use.Format: date-time
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended

Identity

POST/identifiers/start

Verify an additional phone or email

Request verification of exactly one additional email or phone on the signed-in account. Requires a fresh X-App-Proof bound to the signed-in installation when proof protection is enabled. Authenticated code requests share a 10/hour customer limit across devices. Bird Verify returns a challenge_token and a pending send. Poll the matching status operation before asking for a code. Local SMS and email retain their existing check behavior.

Bearer tokenX-App-Id

Parameters

Name / locationTypeDescriptionConstraints & example
X-App-Proofheader · optional; see conditionsstringRequired when proof protection is enabled (default in production). Obtain a fresh, one-use proof bound to this app and device from the trusted server issuer. Missing/replayed proof returns 403 app_verification_required; never embed the issuer secret in an app.—Example: YOUR_VERIFICATION_PROOF

Request body application/json · required

Supply one contact identifier: email or phone. Send only the identifier you want to add to the signed-in customer; if both are provided, email takes precedence.

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

FieldTypeDescriptionConstraints & example
emailoptional; see conditionsstringEmail address to receive the verification code. Required when phone is absent. Use a method enabled for this app.Format: email · Max length: 255Example: customer@example.com
phoneoptional; see conditionsstringPhone number to receive the SMS verification code. Required when email is absent. Use E.164 or a local number valid for the business country.Max length: 30Example: +447700900123
JSON request example
{
    "phone": "+447700900123"
}
cURL request
curl --request POST "$BASE_URL/identifiers/start" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "phone": "+447700900123"
}'

Responses

202 Success

Success

application/json · Illustrative example

{
    "challenge_id": "01JB0000000000000000000001",
    "channel": "email"
}
Response schema
FieldTypeDescriptionConstraints & example
challenge_idrequired in parent objectstringOpaque verification challenge ULID returned by the matching start endpoint; use only for that verification workflow.Min length: 26 · Max length: 26
channelrequired in parent objectstring | nullChannel used for this operation; see the endpoint-specific allowed values.Allowed: email, sms, whatsapp
challenge_tokenoptional; see conditionsstringBird Verify caller capability returned at start. Keep in memory; never place it in a URL.Min length: 64 · Max length: 64Example: YOUR_CHALLENGE_TOKEN
provideroptional; see conditionsstringSelected provider; bird_verify requires queued checks.—Example: bird_verify
statusoptional; see conditionsstringA remote send starts pending.—Example: pending
expires_atoptional; see conditionsstring | nullCurrent code expiry in the business time zone. Before remote send completion this is provisional; poll status for the provider expiry.Format: date-timeExample: 2026-10-30T10:22:00+00:00
resend_afteroptional; see conditionsstring | nullEarliest resend time in the business time zone. Null while a remote send is pending, after the send limit, or when the challenge is no longer pending. Poll status after a queued send.Format: date-timeExample: 2026-10-30T10:13:00+00:00
attempts_allowedoptional; see conditionsintegerMaximum number of code checks allowed for this challenge.—Example: 5
attempts_leftoptional; see conditionsintegerCurrent checks remaining; zero after attempt exhaustion. The final wrong check returns too_many_attempts.Minimum: 0Example: 4
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
POST/identifiers/verify

Add a verified identifier; may offer a merge challenge

Verify an additional identifier. If another account holds it, handle held_by_another_account and ask the customer before submitting the returned merge challenge. With Bird Verify, submit challenge_token and an Idempotency-Key. HTTP 202 contains a queued check operation. Poll its status, then repeat this request with the same code/device, capability and operation_id to finalize locally. GET/status never returns a session token. A lost finalization response requires a fresh sign-in; completion does not replay a consumed challenge.

Bearer tokenX-App-Id Idempotency key

Parameters

Name / locationTypeDescriptionConstraints & example
Idempotency-Keyheader · optional; see conditionsstringRequired for a new Bird Verify check/resend. Reuse for a transport retry with identical input; use a new key for a different code.Max length: 100Example: verification-check-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
challenge_idrequired in parent objectstringAdd-identifier challenge ULID returned by POST /identifiers/start for this signed-in customer.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
coderequired in parent objectstringVerification code delivered for this challenge. Send as a string to preserve leading zeros. Expired, incorrect or consumed codes are refused.Max length: 10Example: 123456
challenge_tokenoptional; see conditionsstringBird Verify caller capability returned at start. Keep in memory; never place it in a URL.Min length: 64 · Max length: 64Example: YOUR_CHALLENGE_TOKEN
operation_idoptional; see conditionsstringQueued verification operation returned by the prior check or resend.Min length: 26 · Max length: 26Example: 01JB0000000000000000000002
JSON request example
{
    "challenge_id": "01JB0000000000000000000001",
    "code": "123456"
}
cURL request
curl --request POST "$BASE_URL/identifiers/verify" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "challenge_id": "01JB0000000000000000000001",
    "code": "123456"
}'

Responses

200 Success

Success

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

{
    "customer": {
        "id": "01JB0000000000000000000001",
        "name": "Alex Morgan",
        "locale": "en_GB",
        "status": "active",
        "birthday": "1995-10-24",
        "push_consent": false,
        "marketing_consent": false,
        "identifiers": [
            {
                "type": "phone",
                "value": "string",
                "verified": false
            }
        ],
        "identity_assurance": "verified_contact",
        "recovery_available": false,
        "view_only_link_available": true
    },
    "membership": {
        "member_code": "48213906574",
        "balance": 10,
        "spendable": 10,
        "lifetime": 10,
        "points_label": "string",
        "cycle": {
            "number": 0,
            "points": 0,
            "target": 0,
            "points_to_next": 0,
            "next_reward": {
                "id": "string",
                "name": "string",
                "threshold": 0,
                "type": "free_item",
                "value_label": "string",
                "icon": "gift"
            },
            "reset_after_last": false,
            "carry_over": false,
            "milestones": [
                {
                    "points": 0,
                    "reward": {
                        "id": "string",
                        "name": "string",
                        "type": "free_item",
                        "value_label": "string",
                        "icon": "gift"
                    },
                    "status": "locked",
                    "entitlement_id": "string",
                    "reached": false,
                    "steps": [
                        {
                            "points": null,
                            "value": null,
                            "reached": null
                        }
                    ]
                }
            ],
            "completed": {
                "number": 0,
                "target": 0,
                "cards": 0,
                "completed_at": "2026-10-24T10:00:00Z",
                "milestones": [
                    {
                        "points": 0,
                        "reward": {
                            "id": null,
                            "name": null,
                            "type": null,
                            "value_label": null,
                            "icon": null
                        },
                        "status": "locked",
                        "entitlement_id": "string",
                        "reached": false,
                        "steps": [
                            null
                        ]
                    }
                ]
            }
        },
        "view_only_url": "https://example.com/experience/customer/cafe/card/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    },
    "rewards": [
        {
            "id": "01JB0000000000000000000001",
            "name": "Free coffee",
            "type": "amount_voucher",
            "value_label": "string",
            "value_minor": 1000,
            "currency": "GBP",
            "code": "DEMO7KQ2M",
            "status": "available",
            "used_at": "2026-10-24T10:00:00Z",
            "expires_at": "2026-10-24T10:00:00Z",
            "claim": {
                "eligible": true,
                "reason_code": null,
                "message": null,
                "points_required": 20,
                "available_actions": [
                    "claim"
                ]
            },
            "cycle": 0,
            "milestone": 0,
            "issued_at": "2026-10-24T10:00:00Z",
            "valid_through_date": "string",
            "expiry_timezone": "Asia/Dubai"
        }
    ],
    "google_wallet": false
}
Response schema
FieldTypeDescriptionConstraints & example
customeroptional; see conditionsobjectCustomer profile belonging to the resolved business.—
customer.idoptional; see conditionsstringCustomer public ULID within this business. Do not substitute a phone number or legacy database ID.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.localeoptional; see conditionsstring | nullLanguage/region code such as en_GB; determines localized presentation.—Example: en_GB
customer.statusoptional; see conditionsstringCustomer lifecycle state, for example active or provisional. Suspended, merged or erased accounts cannot use normal authenticated operations. Suspended customers receive 403 account_unavailable with error.reason suspended instead of an authenticated profile.Allowed: active, provisional, suspended, merged, erased
customer.birthdayoptional; see conditionsstring | nullFull date of birth as YYYY-MM-DD, for example 1995-10-24. Must be a valid calendar date, no later than today in the business timezone. Null clears it.Format: dateExample: 1995-10-24
customer.push_consentoptional; see conditionsbooleanCustomer consent for push marketing. Marketing delivery additionally requires marketing_consent and a reachable signed-in device.—
customer.marketing_consentoptional; see conditionsbooleanCustomer consent for marketing messages. Marketing push also requires push_consent; a push token alone is not consent.—
customer.identifiersoptional; see conditionsarray<object>Customer contact identifiers and their ownership-verification state.—
customer.identifiers[].typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: phone, email
customer.identifiers[].valueoptional; see conditionsstringIdentifier value, such as an email address or E.164 phone number.—
customer.identifiers[].verifiedoptional; see conditionsbooleanWhether ownership of this identifier has been verified.—
customer.identity_assuranceoptional; see conditionsstringContact assurance, separate from lifecycle status. Anonymous accounts use browser sessions only; native sign-in remains contact-verified.Allowed: verified_contact, anonymous, unverified_entry, demo
customer.recovery_availableoptional; see conditionsbooleanAt least one verified contact or an unexpired unused private recovery key exists. Wallet installation alone does not provide browser recovery.—
customer.view_only_link_availableoptional; see conditionsbooleanThis active account has an active loyalty membership and can manage an optional view-only membership link through its authenticated browser session. Available to registered and anonymously enrolled members. The link never authorizes account access.—Example: true
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.—Example: 48213906574
membership.balanceoptional; see conditionsintegerAccumulated points balance, not current-cycle progress. Render claim controls only from server claim decisions.—Example: 10
membership.spendableoptional; see conditionsintegerPoints available to spend, excluding holds. Claim decisions remain authoritative.—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.cycleoptional; see conditionsobject | nullPublished ladder progress, separate from accumulated balance. Reads never issue rewards. Null without an active supported ladder.—
membership.cycle.numberrequired in parent objectintegerCurrent cycle number.—
membership.cycle.pointsrequired in parent objectintegerCurrent-cycle points; distinct from accumulated balance.—
membership.cycle.targetrequired in parent objectinteger | nullPublished final milestone threshold; null without a threshold.—
membership.cycle.points_to_nextrequired in parent objectinteger | nullPoints remaining to the next offered higher threshold; null when none.—
membership.cycle.next_rewardrequired in parent objectobject | nullNext offered reward above current-cycle points, or null.—
membership.cycle.next_reward.idrequired in parent objectstringPublic object ID; event IDs include their source prefix.—
membership.cycle.next_reward.namerequired in parent objectstringCustomer-facing display name.—
membership.cycle.next_reward.thresholdrequired in parent objectintegerPoints threshold of the next offered reward.—
membership.cycle.next_reward.typerequired in parent objectstringReward kind, for the reward stop's badge.Allowed: free_item, amount_voucher, percent_off
membership.cycle.next_reward.value_labelrequired in parent objectstring | nullShort badge value such as £10 or 10%; null for free items.—
membership.cycle.next_reward.iconrequired in parent objectstringPicture for the reward: the business's pick, else money (amount voucher), percent (percent off) or gift (free item). Treat unknown values as gift.Allowed: gift, coffee, drink, food, dessert, money, percent, ticket, star, bag
membership.cycle.reset_after_lastrequired in parent objectbooleanWhether earning through the last threshold starts the next cycle.—
membership.cycle.carry_overrequired in parent objectbooleanWhether excess earning carries into the next cycle.—
membership.cycle.milestonesrequired in parent objectarray<object>Published ladder milestones, at most ten. State does not authorize a claim.Max items: 10
membership.cycle.milestones[].pointsrequired in parent objectintegerCurrent-cycle points; distinct from accumulated balance.—
membership.cycle.milestones[].rewardrequired in parent objectobject | nullRelated reward identity and display name, or null for ledger-only activity.—
membership.cycle.milestones[].reward.idrequired in parent objectstringPublic object ID; event IDs include their source prefix.—
membership.cycle.milestones[].reward.namerequired in parent objectstringCustomer-facing display name.—
membership.cycle.milestones[].reward.typerequired in parent objectstringReward kind, for the reward stop's badge.Allowed: free_item, amount_voucher, percent_off
membership.cycle.milestones[].reward.value_labelrequired in parent objectstring | nullShort badge value such as £10 or 10%; null for free items.—
membership.cycle.milestones[].reward.iconrequired in parent objectstringPicture for the reward: the business's pick, else money (amount voucher), percent (percent off) or gift (free item). Treat unknown values as gift.Allowed: gift, coffee, drink, food, dessert, money, percent, ticket, star, bag
membership.cycle.milestones[].statusrequired in parent objectstringMilestone/grant state, or ledger posting status in activity. pending means reached but unissued, and never authorizes a claim. A reward a refund took back reports locked (or pending once reached), as it is issued again; revoked means revoked by staff.Allowed: locked, pending, unavailable, earned, available, used, expired, revoked
membership.cycle.milestones[].entitlement_idrequired in parent objectstring | nullOwned grant public ID for this cycle/milestone, or null.—
membership.cycle.milestones[].reachedrequired in parent objectbooleanCurrent-cycle points have reached this milestone.—
membership.cycle.milestones[].stepsrequired in parent objectarray<object>Stops a card shows before this milestone's reward, split by the server (one per point up to 10, else 5-10 even stops, else 5 rounded). Draw as given.Max items: 9
membership.cycle.milestones[].steps[].pointsrequired in parent objectintegerCumulative current-cycle threshold of this stop.—
membership.cycle.milestones[].steps[].valuerequired in parent objectintegerPoints this stop is worth (from the previous stop).—
membership.cycle.milestones[].steps[].reachedrequired in parent objectbooleanCurrent-cycle points have reached this stop.—
membership.cycle.completedrequired in parent objectobject | nullThe card the member's latest point-earning purchase filled (its last-milestone reward came from that purchase, is still the member's own and was not refunded). Null otherwise, including once points are earned again, once no reward on it is still waiting (all used, expired or withdrawn), for ladders that do not start a new card, and after a refund takes that reward back. Display only: the cycle has already moved on and its rewards are issued.—
membership.cycle.completed.numberrequired in parent objectintegerCycle number of the filled card.—
membership.cycle.completed.targetrequired in parent objectintegerPoints on a full card.—
membership.cycle.completed.cardsrequired in parent objectintegerCards that purchase filled (more than one for a very large purchase).—
membership.cycle.completed.completed_atrequired in parent objectstring | nullWhen its last reward was issued.Format: date-time
membership.cycle.completed.milestonesrequired in parent objectarray<object>Published ladder milestones, at most ten. State does not authorize a claim.Max items: 10
membership.cycle.completed.milestones[].pointsrequired in parent objectintegerCurrent-cycle points; distinct from accumulated balance.—
membership.cycle.completed.milestones[].rewardrequired in parent objectobject | nullRelated reward identity and display name, or null for ledger-only activity.—
membership.cycle.completed.milestones[].reward.idrequired in parent objectstringPublic object ID; event IDs include their source prefix.—
membership.cycle.completed.milestones[].reward.namerequired in parent objectstringCustomer-facing display name.—
membership.cycle.completed.milestones[].reward.typerequired in parent objectstringReward kind, for the reward stop's badge.Allowed: free_item, amount_voucher, percent_off
membership.cycle.completed.milestones[].reward.value_labelrequired in parent objectstring | nullShort badge value such as £10 or 10%; null for free items.—
membership.cycle.completed.milestones[].reward.iconrequired in parent objectstringPicture for the reward: the business's pick, else money (amount voucher), percent (percent off) or gift (free item). Treat unknown values as gift.Allowed: gift, coffee, drink, food, dessert, money, percent, ticket, star, bag
membership.cycle.completed.milestones[].statusrequired in parent objectstringMilestone/grant state, or ledger posting status in activity. pending means reached but unissued, and never authorizes a claim. A reward a refund took back reports locked (or pending once reached), as it is issued again; revoked means revoked by staff.Allowed: locked, pending, unavailable, earned, available, used, expired, revoked
membership.cycle.completed.milestones[].entitlement_idrequired in parent objectstring | nullOwned grant public ID for this cycle/milestone, or null.—
membership.cycle.completed.milestones[].reachedrequired in parent objectbooleanCurrent-cycle points have reached this milestone.—
membership.cycle.completed.milestones[].stepsrequired in parent objectarray<object>Stops a card shows before this milestone's reward, split by the server (one per point up to 10, else 5-10 even stops, else 5 rounded). Draw as given.Max items: 9
membership.cycle.completed.milestones[].steps[].pointsrequired in parent objectintegerCumulative current-cycle threshold of this stop.—
membership.cycle.completed.milestones[].steps[].valuerequired in parent objectintegerPoints this stop is worth (from the previous stop).—
membership.cycle.completed.milestones[].steps[].reachedrequired in parent objectbooleanCurrent-cycle points have reached this stop.—
membership.view_only_urloptional; see conditionsstring | nullOptional shareable view-only card URL showing membership code, points and progress. Anyone with the URL can view those fields. It cannot authenticate, recover an account, reveal vouchers or authorize claims. Null when no link is enabled.Format: uriExample: https://example.com/experience/customer/cafe/card/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
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[].nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Free coffee
rewards[].typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: amount_voucher, free_item, percent_off
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[].currencyoptional; see conditionsstringISO 4217 currency code, for example GBP or AED. Monetary amounts are expressed in its minor units.—Example: GBP
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[].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.—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[].cycleoptional; see conditionsinteger | nullCard (cycle) a milestone reward came from; null for rewards bought with points.—
rewards[].milestoneoptional; see conditionsinteger | nullMilestone points on that card; null for rewards bought with points.—
rewards[].issued_atoptional; see conditionsstring | nullWhen the reward was issued; for the last milestone, when its card was completed.Format: date-time
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
google_walletoptional; see conditionsbooleanWhether Google Wallet is available for this business/customer.—
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
202 Remote verification is queued; no customer authority has been granted.

Remote verification is queued; no customer authority has been granted.

application/json · Illustrative example

{
    "status": "pending",
    "operation_id": "01JB0000000000000000000002",
    "retry_after": 3
}
Response schema
FieldTypeDescriptionConstraints & example
statusoptional; see conditionsstringPending until the worker records an outcome.—Example: pending
operation_idoptional; see conditionsstringQueued verification operation returned by the prior check or resend.Min length: 26 · Max length: 26Example: 01JB0000000000000000000002
challenge_idoptional; see conditionsstringOpaque challenge identifier returned by the matching start operation.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
retry_afteroptional; see conditionsintegerSeconds to wait before polling.—Example: 3
POST/accounts/merge

Merge another account after verifying its identifier

Consume the merge challenge after explicit customer confirmation. Move supported account data into the signed-in account and revoke the merged account’s devices. Challenge validity is 15 minutes.

Bearer tokenX-App-Id

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
challenge_idrequired in parent objectstringMerge challenge ULID from error.merge_challenge_id when POST /identifiers/verify returns held_by_another_account. Obtain explicit customer agreement before merging.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
JSON request example
{
    "challenge_id": "01JB0000000000000000000001"
}
cURL request
curl --request POST "$BASE_URL/accounts/merge" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "challenge_id": "01JB0000000000000000000001"
}'

Responses

200 Success

Success

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

{
    "customer": {
        "id": "01JB0000000000000000000001",
        "name": "Alex Morgan",
        "locale": "en_GB",
        "status": "active",
        "birthday": "1995-10-24",
        "push_consent": false,
        "marketing_consent": false,
        "identifiers": [
            {
                "type": "phone",
                "value": "string",
                "verified": false
            }
        ],
        "identity_assurance": "verified_contact",
        "recovery_available": false,
        "view_only_link_available": true
    },
    "membership": {
        "member_code": "48213906574",
        "balance": 10,
        "spendable": 10,
        "lifetime": 10,
        "points_label": "string",
        "cycle": {
            "number": 0,
            "points": 0,
            "target": 0,
            "points_to_next": 0,
            "next_reward": {
                "id": "string",
                "name": "string",
                "threshold": 0,
                "type": "free_item",
                "value_label": "string",
                "icon": "gift"
            },
            "reset_after_last": false,
            "carry_over": false,
            "milestones": [
                {
                    "points": 0,
                    "reward": {
                        "id": "string",
                        "name": "string",
                        "type": "free_item",
                        "value_label": "string",
                        "icon": "gift"
                    },
                    "status": "locked",
                    "entitlement_id": "string",
                    "reached": false,
                    "steps": [
                        {
                            "points": null,
                            "value": null,
                            "reached": null
                        }
                    ]
                }
            ],
            "completed": {
                "number": 0,
                "target": 0,
                "cards": 0,
                "completed_at": "2026-10-24T10:00:00Z",
                "milestones": [
                    {
                        "points": 0,
                        "reward": {
                            "id": null,
                            "name": null,
                            "type": null,
                            "value_label": null,
                            "icon": null
                        },
                        "status": "locked",
                        "entitlement_id": "string",
                        "reached": false,
                        "steps": [
                            null
                        ]
                    }
                ]
            }
        },
        "view_only_url": "https://example.com/experience/customer/cafe/card/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    },
    "rewards": [
        {
            "id": "01JB0000000000000000000001",
            "name": "Free coffee",
            "type": "amount_voucher",
            "value_label": "string",
            "value_minor": 1000,
            "currency": "GBP",
            "code": "DEMO7KQ2M",
            "status": "available",
            "used_at": "2026-10-24T10:00:00Z",
            "expires_at": "2026-10-24T10:00:00Z",
            "claim": {
                "eligible": true,
                "reason_code": null,
                "message": null,
                "points_required": 20,
                "available_actions": [
                    "claim"
                ]
            },
            "cycle": 0,
            "milestone": 0,
            "issued_at": "2026-10-24T10:00:00Z",
            "valid_through_date": "string",
            "expiry_timezone": "Asia/Dubai"
        }
    ],
    "google_wallet": false
}
Response schema
FieldTypeDescriptionConstraints & example
customeroptional; see conditionsobjectCustomer profile belonging to the resolved business.—
customer.idoptional; see conditionsstringCustomer public ULID within this business. Do not substitute a phone number or legacy database ID.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.localeoptional; see conditionsstring | nullLanguage/region code such as en_GB; determines localized presentation.—Example: en_GB
customer.statusoptional; see conditionsstringCustomer lifecycle state, for example active or provisional. Suspended, merged or erased accounts cannot use normal authenticated operations. Suspended customers receive 403 account_unavailable with error.reason suspended instead of an authenticated profile.Allowed: active, provisional, suspended, merged, erased
customer.birthdayoptional; see conditionsstring | nullFull date of birth as YYYY-MM-DD, for example 1995-10-24. Must be a valid calendar date, no later than today in the business timezone. Null clears it.Format: dateExample: 1995-10-24
customer.push_consentoptional; see conditionsbooleanCustomer consent for push marketing. Marketing delivery additionally requires marketing_consent and a reachable signed-in device.—
customer.marketing_consentoptional; see conditionsbooleanCustomer consent for marketing messages. Marketing push also requires push_consent; a push token alone is not consent.—
customer.identifiersoptional; see conditionsarray<object>Customer contact identifiers and their ownership-verification state.—
customer.identifiers[].typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: phone, email
customer.identifiers[].valueoptional; see conditionsstringIdentifier value, such as an email address or E.164 phone number.—
customer.identifiers[].verifiedoptional; see conditionsbooleanWhether ownership of this identifier has been verified.—
customer.identity_assuranceoptional; see conditionsstringContact assurance, separate from lifecycle status. Anonymous accounts use browser sessions only; native sign-in remains contact-verified.Allowed: verified_contact, anonymous, unverified_entry, demo
customer.recovery_availableoptional; see conditionsbooleanAt least one verified contact or an unexpired unused private recovery key exists. Wallet installation alone does not provide browser recovery.—
customer.view_only_link_availableoptional; see conditionsbooleanThis active account has an active loyalty membership and can manage an optional view-only membership link through its authenticated browser session. Available to registered and anonymously enrolled members. The link never authorizes account access.—Example: true
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.—Example: 48213906574
membership.balanceoptional; see conditionsintegerAccumulated points balance, not current-cycle progress. Render claim controls only from server claim decisions.—Example: 10
membership.spendableoptional; see conditionsintegerPoints available to spend, excluding holds. Claim decisions remain authoritative.—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.cycleoptional; see conditionsobject | nullPublished ladder progress, separate from accumulated balance. Reads never issue rewards. Null without an active supported ladder.—
membership.cycle.numberrequired in parent objectintegerCurrent cycle number.—
membership.cycle.pointsrequired in parent objectintegerCurrent-cycle points; distinct from accumulated balance.—
membership.cycle.targetrequired in parent objectinteger | nullPublished final milestone threshold; null without a threshold.—
membership.cycle.points_to_nextrequired in parent objectinteger | nullPoints remaining to the next offered higher threshold; null when none.—
membership.cycle.next_rewardrequired in parent objectobject | nullNext offered reward above current-cycle points, or null.—
membership.cycle.next_reward.idrequired in parent objectstringPublic object ID; event IDs include their source prefix.—
membership.cycle.next_reward.namerequired in parent objectstringCustomer-facing display name.—
membership.cycle.next_reward.thresholdrequired in parent objectintegerPoints threshold of the next offered reward.—
membership.cycle.next_reward.typerequired in parent objectstringReward kind, for the reward stop's badge.Allowed: free_item, amount_voucher, percent_off
membership.cycle.next_reward.value_labelrequired in parent objectstring | nullShort badge value such as £10 or 10%; null for free items.—
membership.cycle.next_reward.iconrequired in parent objectstringPicture for the reward: the business's pick, else money (amount voucher), percent (percent off) or gift (free item). Treat unknown values as gift.Allowed: gift, coffee, drink, food, dessert, money, percent, ticket, star, bag
membership.cycle.reset_after_lastrequired in parent objectbooleanWhether earning through the last threshold starts the next cycle.—
membership.cycle.carry_overrequired in parent objectbooleanWhether excess earning carries into the next cycle.—
membership.cycle.milestonesrequired in parent objectarray<object>Published ladder milestones, at most ten. State does not authorize a claim.Max items: 10
membership.cycle.milestones[].pointsrequired in parent objectintegerCurrent-cycle points; distinct from accumulated balance.—
membership.cycle.milestones[].rewardrequired in parent objectobject | nullRelated reward identity and display name, or null for ledger-only activity.—
membership.cycle.milestones[].reward.idrequired in parent objectstringPublic object ID; event IDs include their source prefix.—
membership.cycle.milestones[].reward.namerequired in parent objectstringCustomer-facing display name.—
membership.cycle.milestones[].reward.typerequired in parent objectstringReward kind, for the reward stop's badge.Allowed: free_item, amount_voucher, percent_off
membership.cycle.milestones[].reward.value_labelrequired in parent objectstring | nullShort badge value such as £10 or 10%; null for free items.—
membership.cycle.milestones[].reward.iconrequired in parent objectstringPicture for the reward: the business's pick, else money (amount voucher), percent (percent off) or gift (free item). Treat unknown values as gift.Allowed: gift, coffee, drink, food, dessert, money, percent, ticket, star, bag
membership.cycle.milestones[].statusrequired in parent objectstringMilestone/grant state, or ledger posting status in activity. pending means reached but unissued, and never authorizes a claim. A reward a refund took back reports locked (or pending once reached), as it is issued again; revoked means revoked by staff.Allowed: locked, pending, unavailable, earned, available, used, expired, revoked
membership.cycle.milestones[].entitlement_idrequired in parent objectstring | nullOwned grant public ID for this cycle/milestone, or null.—
membership.cycle.milestones[].reachedrequired in parent objectbooleanCurrent-cycle points have reached this milestone.—
membership.cycle.milestones[].stepsrequired in parent objectarray<object>Stops a card shows before this milestone's reward, split by the server (one per point up to 10, else 5-10 even stops, else 5 rounded). Draw as given.Max items: 9
membership.cycle.milestones[].steps[].pointsrequired in parent objectintegerCumulative current-cycle threshold of this stop.—
membership.cycle.milestones[].steps[].valuerequired in parent objectintegerPoints this stop is worth (from the previous stop).—
membership.cycle.milestones[].steps[].reachedrequired in parent objectbooleanCurrent-cycle points have reached this stop.—
membership.cycle.completedrequired in parent objectobject | nullThe card the member's latest point-earning purchase filled (its last-milestone reward came from that purchase, is still the member's own and was not refunded). Null otherwise, including once points are earned again, once no reward on it is still waiting (all used, expired or withdrawn), for ladders that do not start a new card, and after a refund takes that reward back. Display only: the cycle has already moved on and its rewards are issued.—
membership.cycle.completed.numberrequired in parent objectintegerCycle number of the filled card.—
membership.cycle.completed.targetrequired in parent objectintegerPoints on a full card.—
membership.cycle.completed.cardsrequired in parent objectintegerCards that purchase filled (more than one for a very large purchase).—
membership.cycle.completed.completed_atrequired in parent objectstring | nullWhen its last reward was issued.Format: date-time
membership.cycle.completed.milestonesrequired in parent objectarray<object>Published ladder milestones, at most ten. State does not authorize a claim.Max items: 10
membership.cycle.completed.milestones[].pointsrequired in parent objectintegerCurrent-cycle points; distinct from accumulated balance.—
membership.cycle.completed.milestones[].rewardrequired in parent objectobject | nullRelated reward identity and display name, or null for ledger-only activity.—
membership.cycle.completed.milestones[].reward.idrequired in parent objectstringPublic object ID; event IDs include their source prefix.—
membership.cycle.completed.milestones[].reward.namerequired in parent objectstringCustomer-facing display name.—
membership.cycle.completed.milestones[].reward.typerequired in parent objectstringReward kind, for the reward stop's badge.Allowed: free_item, amount_voucher, percent_off
membership.cycle.completed.milestones[].reward.value_labelrequired in parent objectstring | nullShort badge value such as £10 or 10%; null for free items.—
membership.cycle.completed.milestones[].reward.iconrequired in parent objectstringPicture for the reward: the business's pick, else money (amount voucher), percent (percent off) or gift (free item). Treat unknown values as gift.Allowed: gift, coffee, drink, food, dessert, money, percent, ticket, star, bag
membership.cycle.completed.milestones[].statusrequired in parent objectstringMilestone/grant state, or ledger posting status in activity. pending means reached but unissued, and never authorizes a claim. A reward a refund took back reports locked (or pending once reached), as it is issued again; revoked means revoked by staff.Allowed: locked, pending, unavailable, earned, available, used, expired, revoked
membership.cycle.completed.milestones[].entitlement_idrequired in parent objectstring | nullOwned grant public ID for this cycle/milestone, or null.—
membership.cycle.completed.milestones[].reachedrequired in parent objectbooleanCurrent-cycle points have reached this milestone.—
membership.cycle.completed.milestones[].stepsrequired in parent objectarray<object>Stops a card shows before this milestone's reward, split by the server (one per point up to 10, else 5-10 even stops, else 5 rounded). Draw as given.Max items: 9
membership.cycle.completed.milestones[].steps[].pointsrequired in parent objectintegerCumulative current-cycle threshold of this stop.—
membership.cycle.completed.milestones[].steps[].valuerequired in parent objectintegerPoints this stop is worth (from the previous stop).—
membership.cycle.completed.milestones[].steps[].reachedrequired in parent objectbooleanCurrent-cycle points have reached this stop.—
membership.view_only_urloptional; see conditionsstring | nullOptional shareable view-only card URL showing membership code, points and progress. Anyone with the URL can view those fields. It cannot authenticate, recover an account, reveal vouchers or authorize claims. Null when no link is enabled.Format: uriExample: https://example.com/experience/customer/cafe/card/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
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[].nameoptional; see conditionsstringDisplay name of the object represented here; do not use it as a stable identifier.—Example: Free coffee
rewards[].typeoptional; see conditionsstringType discriminator for this object; use the documented enum when present.Allowed: amount_voucher, free_item, percent_off
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[].currencyoptional; see conditionsstringISO 4217 currency code, for example GBP or AED. Monetary amounts are expressed in its minor units.—Example: GBP
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[].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.—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[].cycleoptional; see conditionsinteger | nullCard (cycle) a milestone reward came from; null for rewards bought with points.—
rewards[].milestoneoptional; see conditionsinteger | nullMilestone points on that card; null for rewards bought with points.—
rewards[].issued_atoptional; see conditionsstring | nullWhen the reward was issued; for the last milestone, when its card was completed.Format: date-time
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
google_walletoptional; see conditionsbooleanWhether Google Wallet is available for this business/customer.—
401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
POST/verification/status

Status identifier linking or browser reauthentication verification

Read local worker state without provider HTTP or customer-side effects. Uses only the original business, app, device and caller capability.

Bearer tokenX-App-Id

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
challenge_idrequired in parent objectstringOpaque challenge identifier returned by the matching start operation.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
challenge_tokenoptional; see conditionsstringBird Verify caller capability returned at start. Keep in memory; never place it in a URL.Min length: 64 · Max length: 64Example: YOUR_CHALLENGE_TOKEN
device_idoptional; see conditionsstringOriginal installation identifier for public login operations; authenticated operations use their bound installation.—Example: install-demo-001
operation_idoptional; see conditionsstringQueued verification operation returned by the prior check or resend.Min length: 26 · Max length: 26Example: 01JB0000000000000000000002
JSON request example
{
    "challenge_id": "01JB0000000000000000000001",
    "challenge_token": "YOUR_CHALLENGE_TOKEN",
    "device_id": "install-demo-001"
}
cURL request
curl --request POST "$BASE_URL/verification/status" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "challenge_id": "01JB0000000000000000000001",
    "challenge_token": "YOUR_CHALLENGE_TOKEN",
    "device_id": "install-demo-001"
}'

Responses

401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
200 Local state only. No code or session token is returned.

Local state only. No code or session token is returned.

application/json · Illustrative example

{
    "status": "pending",
    "operation_id": "01JB0000000000000000000002",
    "retry_after": 3,
    "challenge_id": "01JB0000000000000000000001",
    "expires_at": "2026-10-30T10:22:00+00:00",
    "resend_after": null,
    "attempts_allowed": 5,
    "attempts_left": 5,
    "error": null
}
Response schema
FieldTypeDescriptionConstraints & example
statusoptional; see conditionsstringPending until the worker records an outcome.—Example: pending
operation_idoptional; see conditionsstringQueued verification operation returned by the prior check or resend.Min length: 26 · Max length: 26Example: 01JB0000000000000000000002
challenge_idoptional; see conditionsstringOpaque challenge identifier returned by the matching start operation.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
retry_afteroptional; see conditionsintegerSeconds to wait before polling.—Example: 3
outcomeoptional; see conditionsstring | nullsent, verified, invalid_code, expired, too_many_attempts or a safe failure reason.—Example: sent
channeloptional; see conditionsstring | nullMost recently observed delivery channel.—Example: whatsapp
expires_atoptional; see conditionsstring | nullCurrent code expiry in the business time zone. Before remote send completion this is provisional; poll status for the provider expiry.Format: date-timeExample: 2026-10-30T10:22:00+00:00
resend_afteroptional; see conditionsstring | nullEarliest resend time in the business time zone. Null while a remote send is pending, after the send limit, or when the challenge is no longer pending. Poll status after a queued send.Format: date-timeExample: 2026-10-30T10:13:00+00:00
attempts_allowedoptional; see conditionsintegerMaximum number of code checks allowed for this challenge.—Example: 5
attempts_leftoptional; see conditionsintegerCurrent checks remaining; zero after attempt exhaustion. The final wrong check returns too_many_attempts.Minimum: 0Example: 4
erroroptional; see conditionsobject | nullPublic code-check error for a completed unsuccessful check. Status polling remains HTTP 200; null for pending work and successful sends/checks.—
error.codeoptional; see conditionsstringSame public code used by finalization: invalid_code, code_expired, too_many_attempts or challenge_not_found.—Example: invalid_code
error.messageoptional; see conditionsstringHuman-readable explanation of the check failure.—Example: That code is not right.
error.attempts_leftoptional; see conditionsintegerChecks remaining, included for invalid_code.Minimum: 0Example: 4
POST/verification/resend

Resend identifier linking or browser reauthentication verification

Queue an explicit resend or next-channel request. Subject to local allowance, ownership and cooldown. Never changes provider. Uses only the original business, app, device and caller capability.

Bearer tokenX-App-Id Idempotency key

Parameters

Name / locationTypeDescriptionConstraints & example
Idempotency-Keyheader · optional; see conditionsstringRequired for a new Bird Verify check/resend. Reuse for a transport retry with identical input; use a new key for a different code.Max length: 100Example: verification-check-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
challenge_idrequired in parent objectstringOpaque challenge identifier returned by the matching start operation.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
challenge_tokenoptional; see conditionsstringBird Verify caller capability returned at start. Keep in memory; never place it in a URL.Min length: 64 · Max length: 64Example: YOUR_CHALLENGE_TOKEN
device_idoptional; see conditionsstringOriginal installation identifier for public login operations; authenticated operations use their bound installation.—Example: install-demo-001
next_channeloptional; see conditionsbooleanExplicitly advance Bird to its next allowed delivery channel. Earlier codes can remain valid. Unsupported for local SMS.—Example: false
JSON request example
{
    "challenge_id": "01JB0000000000000000000001",
    "challenge_token": "YOUR_CHALLENGE_TOKEN",
    "device_id": "install-demo-001"
}
cURL request
curl --request POST "$BASE_URL/verification/resend" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "challenge_id": "01JB0000000000000000000001",
    "challenge_token": "YOUR_CHALLENGE_TOKEN",
    "device_id": "install-demo-001"
}'

Responses

401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
202 Remote verification is queued; no customer authority has been granted.

Remote verification is queued; no customer authority has been granted.

application/json · Illustrative example

{
    "status": "pending",
    "operation_id": "01JB0000000000000000000002",
    "retry_after": 3,
    "challenge_id": "01JB0000000000000000000001",
    "expires_at": "2026-10-30T10:22:00+00:00",
    "resend_after": null,
    "attempts_allowed": 5,
    "attempts_left": 5
}
Response schema
FieldTypeDescriptionConstraints & example
statusoptional; see conditionsstringPending until the worker records an outcome.—Example: pending
operation_idoptional; see conditionsstringQueued verification operation returned by the prior check or resend.Min length: 26 · Max length: 26Example: 01JB0000000000000000000002
challenge_idoptional; see conditionsstringOpaque challenge identifier returned by the matching start operation.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
retry_afteroptional; see conditionsintegerSeconds to wait before polling.—Example: 3
expires_atoptional; see conditionsstring | nullCurrent code expiry in the business time zone. Before remote send completion this is provisional; poll status for the provider expiry.Format: date-timeExample: 2026-10-30T10:22:00+00:00
resend_afteroptional; see conditionsstring | nullEarliest resend time in the business time zone. Null while a remote send is pending, after the send limit, or when the challenge is no longer pending. Poll status after a queued send.Format: date-timeExample: 2026-10-30T10:13:00+00:00
attempts_allowedoptional; see conditionsintegerMaximum number of code checks allowed for this challenge.—Example: 5
attempts_leftoptional; see conditionsintegerCurrent checks remaining; zero after attempt exhaustion. The final wrong check returns too_many_attempts.Minimum: 0Example: 4
channeloptional; see conditionsstring | nullMost recently observed delivery channel; poll status for the newly queued send.—Example: sms
POST/me/deletion/status

Status account deletion verification

Read local worker state without provider HTTP or customer-side effects. Uses only the original business, app, device and caller capability. This cleanup operation remains available to the authenticated owner after normal app access is revoked.

Bearer tokenX-App-Id

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
challenge_idrequired in parent objectstringOpaque challenge identifier returned by the matching start operation.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
challenge_tokenoptional; see conditionsstringBird Verify caller capability returned at start. Keep in memory; never place it in a URL.Min length: 64 · Max length: 64Example: YOUR_CHALLENGE_TOKEN
device_idoptional; see conditionsstringOriginal installation identifier for public login operations; authenticated operations use their bound installation.—Example: install-demo-001
operation_idoptional; see conditionsstringQueued verification operation returned by the prior check or resend.Min length: 26 · Max length: 26Example: 01JB0000000000000000000002
JSON request example
{
    "challenge_id": "01JB0000000000000000000001",
    "challenge_token": "YOUR_CHALLENGE_TOKEN",
    "device_id": "install-demo-001"
}
cURL request
curl --request POST "$BASE_URL/me/deletion/status" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "challenge_id": "01JB0000000000000000000001",
    "challenge_token": "YOUR_CHALLENGE_TOKEN",
    "device_id": "install-demo-001"
}'

Responses

401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
200 Local state only. No code or session token is returned.

Local state only. No code or session token is returned.

application/json · Illustrative example

{
    "status": "pending",
    "operation_id": "01JB0000000000000000000002",
    "retry_after": 3,
    "challenge_id": "01JB0000000000000000000001",
    "expires_at": "2026-10-30T10:22:00+00:00",
    "resend_after": null,
    "attempts_allowed": 5,
    "attempts_left": 5,
    "error": null
}
Response schema
FieldTypeDescriptionConstraints & example
statusoptional; see conditionsstringPending until the worker records an outcome.—Example: pending
operation_idoptional; see conditionsstringQueued verification operation returned by the prior check or resend.Min length: 26 · Max length: 26Example: 01JB0000000000000000000002
challenge_idoptional; see conditionsstringOpaque challenge identifier returned by the matching start operation.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
retry_afteroptional; see conditionsintegerSeconds to wait before polling.—Example: 3
outcomeoptional; see conditionsstring | nullsent, verified, invalid_code, expired, too_many_attempts or a safe failure reason.—Example: sent
channeloptional; see conditionsstring | nullMost recently observed delivery channel.—Example: whatsapp
expires_atoptional; see conditionsstring | nullCurrent code expiry in the business time zone. Before remote send completion this is provisional; poll status for the provider expiry.Format: date-timeExample: 2026-10-30T10:22:00+00:00
resend_afteroptional; see conditionsstring | nullEarliest resend time in the business time zone. Null while a remote send is pending, after the send limit, or when the challenge is no longer pending. Poll status after a queued send.Format: date-timeExample: 2026-10-30T10:13:00+00:00
attempts_allowedoptional; see conditionsintegerMaximum number of code checks allowed for this challenge.—Example: 5
attempts_leftoptional; see conditionsintegerCurrent checks remaining; zero after attempt exhaustion. The final wrong check returns too_many_attempts.Minimum: 0Example: 4
erroroptional; see conditionsobject | nullPublic code-check error for a completed unsuccessful check. Status polling remains HTTP 200; null for pending work and successful sends/checks.—
error.codeoptional; see conditionsstringSame public code used by finalization: invalid_code, code_expired, too_many_attempts or challenge_not_found.—Example: invalid_code
error.messageoptional; see conditionsstringHuman-readable explanation of the check failure.—Example: That code is not right.
error.attempts_leftoptional; see conditionsintegerChecks remaining, included for invalid_code.Minimum: 0Example: 4
POST/me/deletion/resend

Resend account deletion verification

Queue an explicit resend or next-channel request. Subject to local allowance, ownership and cooldown. Never changes provider. Uses only the original business, app, device and caller capability. This cleanup operation remains available to the authenticated owner after normal app access is revoked.

Bearer tokenX-App-Id Idempotency key

Parameters

Name / locationTypeDescriptionConstraints & example
Idempotency-Keyheader · optional; see conditionsstringRequired for a new Bird Verify check/resend. Reuse for a transport retry with identical input; use a new key for a different code.Max length: 100Example: verification-check-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
challenge_idrequired in parent objectstringOpaque challenge identifier returned by the matching start operation.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
challenge_tokenoptional; see conditionsstringBird Verify caller capability returned at start. Keep in memory; never place it in a URL.Min length: 64 · Max length: 64Example: YOUR_CHALLENGE_TOKEN
device_idoptional; see conditionsstringOriginal installation identifier for public login operations; authenticated operations use their bound installation.—Example: install-demo-001
next_channeloptional; see conditionsbooleanExplicitly advance Bird to its next allowed delivery channel. Earlier codes can remain valid. Unsupported for local SMS.—Example: false
JSON request example
{
    "challenge_id": "01JB0000000000000000000001",
    "challenge_token": "YOUR_CHALLENGE_TOKEN",
    "device_id": "install-demo-001"
}
cURL request
curl --request POST "$BASE_URL/me/deletion/resend" \
  --header 'Accept: application/json' \
  --header 'X-App-Id: YOUR_APP_ID' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "challenge_id": "01JB0000000000000000000001",
    "challenge_token": "YOUR_CHALLENGE_TOKEN",
    "device_id": "install-demo-001"
}'

Responses

401 Authentication required

Authentication required — Sign in again when the token is expired or revoked. Check X-App-Id for customer requests.

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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
403 Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

Customer access denied. For an otherwise valid customer token or a successfully verified sign-in code, account_unavailable with reason suspended means the account is on hold. Show the suspension screen and stop automatic retries. Invalid/expired/revoked/wrong-business tokens remain 401; auth/start does not reveal account state. Other 403 codes retain their feature/access meanings.

application/json · Illustrative example

Feature or API access is unavailable

{
    "error": {
        "code": "api_access_unavailable",
        "message": "API access is unavailable."
    }
}

Customer account is suspended

{
    "error": {
        "code": "account_unavailable",
        "reason": "suspended",
        "message": "This account is suspended. Please contact the business."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
426 App upgrade required

App upgrade required — Use the supported minimum app version reported by configuration.

application/json · Illustrative example

{
    "error": {
        "code": "upgrade_required",
        "message": "Please update the app."
    }
}
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
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.—
error.attempts_leftoptional; see conditionsintegerOnly on invalid_code: wrong codes still allowed before too_many_attempts.—
error.reasonoptional; see conditionsstringOptional machine-readable account refusal reason. suspended is returned with account_unavailable after valid customer token authentication or successful sign-in code verification. Never infer suspension from an unauthenticated request.—Example: suspended
202 Remote verification is queued; no customer authority has been granted.

Remote verification is queued; no customer authority has been granted.

application/json · Illustrative example

{
    "status": "pending",
    "operation_id": "01JB0000000000000000000002",
    "retry_after": 3,
    "challenge_id": "01JB0000000000000000000001",
    "expires_at": "2026-10-30T10:22:00+00:00",
    "resend_after": null,
    "attempts_allowed": 5,
    "attempts_left": 5
}
Response schema
FieldTypeDescriptionConstraints & example
statusoptional; see conditionsstringPending until the worker records an outcome.—Example: pending
operation_idoptional; see conditionsstringQueued verification operation returned by the prior check or resend.Min length: 26 · Max length: 26Example: 01JB0000000000000000000002
challenge_idoptional; see conditionsstringOpaque challenge identifier returned by the matching start operation.Min length: 26 · Max length: 26Example: 01JB0000000000000000000001
retry_afteroptional; see conditionsintegerSeconds to wait before polling.—Example: 3
expires_atoptional; see conditionsstring | nullCurrent code expiry in the business time zone. Before remote send completion this is provisional; poll status for the provider expiry.Format: date-timeExample: 2026-10-30T10:22:00+00:00
resend_afteroptional; see conditionsstring | nullEarliest resend time in the business time zone. Null while a remote send is pending, after the send limit, or when the challenge is no longer pending. Poll status after a queued send.Format: date-timeExample: 2026-10-30T10:13:00+00:00
attempts_allowedoptional; see conditionsintegerMaximum number of code checks allowed for this challenge.—Example: 5
attempts_leftoptional; see conditionsintegerCurrent checks remaining; zero after attempt exhaustion. The final wrong check returns too_many_attempts.Minimum: 0Example: 4
channeloptional; see conditionsstring | nullMost recently observed delivery channel; poll status for the newly queued send.—Example: sms

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.