Personal username/password login; no X-App-Id
Sign in with the staff member’s personal username and password. No X-App-Id is required. New tokens expire after 12 hours; login is limited to five attempts per username per minute. Operation aliases in staff.permissions and /config.capabilities reflect current branch scope, business access and authorization mode. Do not infer allowed operations from role alone; enforced mode supports grants to staff and denies to managers. Refresh after a branch change or 403. Authorization lookup failures can return 503 authorization_unavailable; honor Retry-After. device_name is generated and stored by the app, not entered by the staff member (see the field). Signing in ends the earlier session for the same staff account and device_name; other devices stay signed in. Authenticated rate limits are shared across all tokens of a staff principal (120/minute).
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.
| Field | Type | Description | Constraints & example |
|---|---|---|---|
usernamerequired in parent object | string | Personal staff username issued by the business administrator; do not substitute a customer phone or app ID. | Max length: 100Example: demo.staff |
passwordrequired in parent object | string | Password for the staff account. Send over HTTPS and never save it in shared examples or logs. | Format: password · Max length: 255Example: YOUR_STAFF_PASSWORD |
device_namerequired in parent object | string | Set by the app, not typed by the staff member. Names this app installation's sign-in session, up to 100 characters. On first sign-in, build it from the device model the operating system reports plus a short random suffix, for example iPad - 3f9c2a71 (iOS UIDevice.current.model) or Samsung SM-S921B - a7164466 (Android Build.MANUFACTURER and Build.MODEL). Save it on the device and send the same value on every later sign-in, including after logout. Signing in again with the same staff account and device_name ends that installation's previous session; other devices stay signed in. Do not create a new name for each sign-in or send a shared value such as Android or iPhone. No hardware ID or IMEI is needed. | Max length: 100Example: iPad - 3f9c2a71 |
JSON request example
{
"username": "demo.staff",
"password": "YOUR_STAFF_PASSWORD",
"device_name": "iPad - 3f9c2a71"
}cURL request
curl --request POST "$BASE_URL/login" \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '{
"username": "demo.staff",
"password": "YOUR_STAFF_PASSWORD",
"device_name": "iPad - 3f9c2a71"
}'Responses
201 Success
Success
application/json · Schema-generated illustration; optional fields and values vary
{
"token": "string",
"staff": {
"id": 1,
"name": "Alex Morgan",
"role": "business_staff",
"business_id": 0,
"branch_id": 0,
"permissions": [
"string"
],
"branches": [
{
"id": 1,
"public_id": "string",
"name": "Demo High Street"
}
],
"public_id": "string"
}
}Response schema
| Field | Type | Description | Constraints & example |
|---|---|---|---|
tokenoptional; see conditions | string | Bearer credential returned at sign-in. Store securely and send in Authorization: Bearer YOUR_TOKEN; never put it in a URL. | — |
staffoptional; see conditions | object | Authenticated staff identity, assignment and bounded legacy branch list. Use GET /branches for complete discovery. | — |
staff.idoptional; see conditions | integer | Legacy integer ID of the authenticated staff account. | —Example: 1 |
staff.nameoptional; see conditions | string | Display name of the object represented here; do not use it as a stable identifier. | —Example: Alex Morgan |
staff.roleoptional; see conditions | string | Account role/category, not a complete list of allowed actions. Legacy/shadow mode uses legacy role rules; enforced mode can delegate actions to staff or deny actions to managers. Branch confinement always applies. | Allowed: business_staff, business_admin, super_admin |
staff.business_idoptional; see conditions | integer | Legacy integer business ID associated with this account; it does not override the token tenant. | — |
staff.branch_idoptional; see conditions | integer | null | Legacy integer branch ID from the authorized branch list, not its public ULID. Managers supply a branch when needed; assigned staff may only use their own branch. | — |
staff.permissionsoptional; see conditions | array<string> | Currently available operation aliases for the authenticated staff/branch context, not RBAC permission-code names. Reflect current business features and authorization mode. Use for UI hints rather than role checks; refresh after branch changes or 403. A reversal alias still requires exact target authorization and the server rechecks every mutation/replay. | — |
staff.branchesoptional; see conditions | array<object> | Bounded legacy list of authorized branches, limited to 100. Use the paginated GET /branches endpoint for discovery. | — |
staff.branches[].idoptional; see conditions | integer | Legacy integer branch ID. Use this value as branch_id on staff operations; do not pass the public_id ULID. | —Example: 1 |
staff.branches[].public_idoptional; see conditions | string | Stable public ULID for this object. Keep it distinct from any legacy integer id. | Min length: 26 · Max length: 26 |
staff.branches[].nameoptional; see conditions | string | Display name of the object represented here; do not use it as a stable identifier. | —Example: Demo High Street |
staff.public_idoptional; see conditions | string | Stable public ULID for this object. Keep it distinct from any legacy integer id. | Min length: 26 · Max length: 26 |
401 Unknown username or incorrect password. Disabled status is disclosed only after a correct password.
Unknown username or incorrect password. Disabled status is disclosed only after a correct password.
application/json · Illustrative example
invalid
{
"error": {
"code": "invalid_credentials",
"message": "The username or password is incorrect."
}
}Response schema
| Field | Type | Description | Constraints & example |
|---|---|---|---|
errorrequired in parent object | object | Application error envelope. Use code for application behavior and message for display. | — |
error.coderequired in parent object | string | Stable application error code such as invalid or rate_limited. Use this value for application decisions, not message text. | —Example: invalid |
error.messagerequired in parent object | string | Human-readable message; do not parse its wording to decide application behavior. | — |
error.fieldsoptional; see conditions | object | Validation messages keyed by request field name, including dotted paths for nested fields. | — |
error.retry_afteroptional; see conditions | integer | Seconds to wait before retrying after a rate limit. Also honor the Retry-After response header. | — |
error.min_app_versionoptional; see conditions | string | Minimum supported app version by platform, or the version required by an upgrade error. | — |
403 Correct credentials for disabled staff return staff_disabled. Internal API entitlement failures return api_access_unavailable.
Correct credentials for disabled staff return staff_disabled. Internal API entitlement failures return api_access_unavailable.
application/json · Illustrative example
disabled
{
"error": {
"code": "staff_disabled",
"message": "This staff account is switched off."
}
}access
{
"error": {
"code": "api_access_unavailable",
"message": "Internal app API access is not enabled."
}
}Response schema
| Field | Type | Description | Constraints & example |
|---|---|---|---|
errorrequired in parent object | object | Application error envelope. Use code for application behavior and message for display. | — |
error.coderequired in parent object | string | Stable application error code such as invalid or rate_limited. Use this value for application decisions, not message text. | —Example: invalid |
error.messagerequired in parent object | string | Human-readable message; do not parse its wording to decide application behavior. | — |
error.fieldsoptional; see conditions | object | Validation messages keyed by request field name, including dotted paths for nested fields. | — |
error.retry_afteroptional; see conditions | integer | Seconds to wait before retrying after a rate limit. Also honor the Retry-After response header. | — |
error.min_app_versionoptional; see conditions | string | Minimum supported app version by platform, or the version required by an upgrade error. | — |
404 Not found
Not found — Check the public identifier and business context. Resources belonging to another tenant are not accessible.
application/json · Illustrative example
{
"error": {
"code": "not_found",
"message": "Not found."
}
}Response schema
| Field | Type | Description | Constraints & example |
|---|---|---|---|
errorrequired in parent object | object | Application error envelope. Use code for application behavior and message for display. | — |
error.coderequired in parent object | string | Stable application error code such as invalid or rate_limited. Use this value for application decisions, not message text. | —Example: invalid |
error.messagerequired in parent object | string | Human-readable message; do not parse its wording to decide application behavior. | — |
error.fieldsoptional; see conditions | object | Validation messages keyed by request field name, including dotted paths for nested fields. | — |
error.retry_afteroptional; see conditions | integer | Seconds to wait before retrying after a rate limit. Also honor the Retry-After response header. | — |
error.min_app_versionoptional; see conditions | string | Minimum supported app version by platform, or the version required by an upgrade error. | — |
409 Conflict
Conflict — Reuse a key only for an identical operation. For voucher_already_used, refresh reward status instead of redeeming again.
application/json · Illustrative example
{
"error": {
"code": "idempotency_conflict",
"message": "This key was used with different request details."
}
}Response schema
| Field | Type | Description | Constraints & example |
|---|---|---|---|
errorrequired in parent object | object | Application error envelope. Use code for application behavior and message for display. | — |
error.coderequired in parent object | string | Stable application error code such as invalid or rate_limited. Use this value for application decisions, not message text. | —Example: invalid |
error.messagerequired in parent object | string | Human-readable message; do not parse its wording to decide application behavior. | — |
error.fieldsoptional; see conditions | object | Validation messages keyed by request field name, including dotted paths for nested fields. | — |
error.retry_afteroptional; see conditions | integer | Seconds to wait before retrying after a rate limit. Also honor the Retry-After response header. | — |
error.min_app_versionoptional; see conditions | string | Minimum supported app version by platform, or the version required by an upgrade error. | — |
422 Validation or domain refusal
Validation or domain refusal — Correct the fields or handle the domain refusal identified by error.code. Do not retry an unchanged invalid request.
application/json · Illustrative example
{
"error": {
"code": "invalid",
"message": "Some fields are invalid.",
"fields": {
"field_name": [
"A validation message."
]
}
}
}Response schema
| Field | Type | Description | Constraints & example |
|---|---|---|---|
errorrequired in parent object | object | Application error envelope. Use code for application behavior and message for display. | — |
error.coderequired in parent object | string | Stable application error code such as invalid or rate_limited. Use this value for application decisions, not message text. | —Example: invalid |
error.messagerequired in parent object | string | Human-readable message; do not parse its wording to decide application behavior. | — |
error.fieldsoptional; see conditions | object | Validation messages keyed by request field name, including dotted paths for nested fields. | — |
error.retry_afteroptional; see conditions | integer | Seconds to wait before retrying after a rate limit. Also honor the Retry-After response header. | — |
error.min_app_versionoptional; see conditions | string | Minimum supported app version by platform, or the version required by an upgrade error. | — |
429 Rate limited; Retry-After header
Rate limited; Retry-After header — Wait for the Retry-After header before retrying; retain the same operation key for a mutation.
Retry-After — Seconds until a retry is allowed
application/json · Illustrative example
{
"error": {
"code": "rate_limited",
"message": "Too many requests; slow down.",
"retry_after": 60
}
}Response schema
| Field | Type | Description | Constraints & example |
|---|---|---|---|
errorrequired in parent object | object | Application error envelope. Use code for application behavior and message for display. | — |
error.coderequired in parent object | string | Stable application error code such as invalid or rate_limited. Use this value for application decisions, not message text. | —Example: invalid |
error.messagerequired in parent object | string | Human-readable message; do not parse its wording to decide application behavior. | — |
error.fieldsoptional; see conditions | object | Validation messages keyed by request field name, including dotted paths for nested fields. | — |
error.retry_afteroptional; see conditions | integer | Seconds to wait before retrying after a rate limit. Also honor the Retry-After response header. | — |
error.min_app_versionoptional; see conditions | string | Minimum supported app version by platform, or the version required by an upgrade error. | — |
503 Authorization state unavailable
Authorization state unavailable — Retry after the Retry-After delay using bounded backoff. For a write, retain the same Idempotency-Key and identical payload; do not assume the operation succeeded or fall back to role-based access.
Retry-After — Seconds to wait before retrying authorization resolution; currently 3.
application/json · Illustrative example
{
"error": {
"code": "authorization_unavailable",
"message": "Access settings are unavailable. Try again shortly."
}
}Response schema
| Field | Type | Description | Constraints & example |
|---|---|---|---|
errorrequired in parent object | object | Application error envelope. Use code for application behavior and message for display. | — |
error.coderequired in parent object | string | Stable application error code such as invalid or rate_limited. Use this value for application decisions, not message text. | —Example: invalid |
error.messagerequired in parent object | string | Human-readable message; do not parse its wording to decide application behavior. | — |
error.fieldsoptional; see conditions | object | Validation messages keyed by request field name, including dotted paths for nested fields. | — |
error.retry_afteroptional; see conditions | integer | Seconds to wait before retrying after a rate limit. Also honor the Retry-After response header. | — |
error.min_app_versionoptional; see conditions | string | Minimum supported app version by platform, or the version required by an upgrade error. | — |