Till

7 operations. Every schema and example on this page is generated from the platform contract.

Turn an iPad into a till

Manager only (can_manage_staff). Returns the device secret once.

Request body

store_idstring · uuidrequired
labelstringrequired

What the owner calls this iPad, e.g. "Front desk".

vendor_device_idstring, nullableoptional

Response, 200

device_idstring · uuidrequired
secretstringrequired

Shown ONCE and never retrievable: only its hash is stored. If it is lost the iPad is re-enrolled, which revokes the old row rather than leaving an un-revokable ghost.

curl -X POST "https://www.membber.com/api/v1/till/devices" \
  -H "Authorization: Bearer $MEMBBER_TOKEN" \
  -H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
  -H "Content-Type: application/json" \
  -d '{
    "store_id": "6659c139-0000-4000-8000-d0c500000066",
    "label": "<label>"
  }'
Response, 200
{
  "device_id": "0180aba4-0000-4000-8000-d0c500000001",
  "secret": "<secret>"
}

Set your own till PIN

Acts on the CALLER, always. There is no path anywhere that lets one person choose or read another person’s PIN, because that would give every "Sara refunded £40" a second innocent explanation and the accountability would be worthless.

Request body

store_idstring · uuidrequired
pinstringrequired

Six digits. Six rather than four because the lockout ladder allows 15 guesses before a manager must intervene: against 10^4 that is a 1-in-667 shot per person, against 10^6 it is 1-in-66,667. The cost is two extra taps. NEVER returned by any endpoint.

Response, 200

okbooleanrequired
codeenumoptional
PIN_REJECTEDPIN_REUSED
messagestring, nullableoptional

Merchant-facing sentence. Never names another person’s PIN.

curl -X POST "https://www.membber.com/api/v1/till/pin" \
  -H "Authorization: Bearer $MEMBBER_TOKEN" \
  -H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
  -H "Content-Type: application/json" \
  -d '{
    "store_id": "6659c139-0000-4000-8000-d0c500000066",
    "pin": "<pin>"
  }'
Response, 200
{
  "ok": true,
  "code": "PIN_REJECTED",
  "message": "Added at the front desk"
}

Clear a lockout or remove someone from the till

Manager only (can_manage_staff). Deliberately cannot set a PIN: a manager helps someone back in, they do not become them.

Request body

store_idstring · uuidrequired
user_idstring · uuidrequired
actionenumrequired

clear_lock releases a tier-3 lockout, which no timer will release on its own. revoke removes this person from the till and ends their live sessions. Neither can SET a PIN.

clear_lockrevoke
reasonstring, nullableoptional

Response, 200

okbooleanrequired
curl -X POST "https://www.membber.com/api/v1/till/pin/manage" \
  -H "Authorization: Bearer $MEMBBER_TOKEN" \
  -H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
  -H "Content-Type: application/json" \
  -d '{
    "store_id": "6659c139-0000-4000-8000-d0c500000066",
    "user_id": "f73aee0f-0000-4000-8000-d0c5000000f7",
    "action": "clear_lock"
  }'
Response, 200
{
  "ok": true
}

The tiles this till shows

People with a till PIN at this store. Authenticates on the device secret. Deliberately NOT built on the gym staff list, which is gated behind can_use_gym_management and would 403 for every coffee shop. Returns names only: this screen faces the counter.

auth none, publicop tillRosterrate limitederrors

Response, 200

store_namestring, nullableoptional
staffarray of objectrequired
3 child fields
user_idstring · uuidrequired
display_namestringrequired

What the tile shows. Names only: this screen faces the counter.

lockedbooleanrequired

True when this person is inside a lockout window. The tile is shown but not tappable.

curl -G "https://www.membber.com/api/v1/till/roster"
Response, 200
{
  "store_name": "<store_name>",
  "staff": [
    {
      "user_id": "f73aee0f-0000-4000-8000-d0c5000000f7",
      "display_name": "<display_name>",
      "locked": true
    }
  ]
}

The till roster, for a device holding a merchant session

Same list as /till/roster, for callers that are signed in as the merchant rather than enrolled as a till. The gym kiosk needs it: a kiosk iPad runs the merchant session (that is how it shows the gym screens at all), so it cannot present a device secret. Names only.

Parameters

store_idstring · uuidqueryrequired

Response, 200

staffarray of objectrequired
3 child fields
user_idstring · uuidrequired
display_namestringrequired

What the tile shows. Names only: this screen faces the counter.

lockedbooleanrequired

True when this person is inside a lockout window. The tile is shown but not tappable.

curl -G "https://www.membber.com/api/v1/till/staff" \
  -H "Authorization: Bearer $MEMBBER_TOKEN" \
  --data-urlencode "store_id=6659c139-0000-4000-8000-d0c500000066"
Response, 200
{
  "staff": [
    {
      "user_id": "f73aee0f-0000-4000-8000-d0c5000000f7",
      "display_name": "<display_name>",
      "locked": true
    }
  ]
}

Unlock the till as one named person

Authenticates on the device secret (X-Till-Device header) plus the tapped person and their PIN. Returns a session that NAMES that person, so every action taken through it is attributable.

auth none, publicop tillUnlockidempotent retryrate limitederrors

Request body

user_idstring · uuidrequired

WHO is signing in, taken from the tile they tapped. The roster identifies and the PIN only proves, so the PIN is never a lookup key. That is what makes two staff sharing the same six digits a non-event, and what keeps a guesser at 1/keyspace however many people the store hires.

pinstringrequired

Six digits. Six rather than four because the lockout ladder allows 15 guesses before a manager must intervene: against 10^4 that is a 1-in-667 shot per person, against 10^6 it is 1-in-66,667. The cost is two extra taps. NEVER returned by any endpoint.

app_buildstring, nullableoptional

Response, 200

okbooleanrequired
tokenstring, nullableoptional

Till session token. 12 hours, a shift, not a remembered login.

display_namestring, nullableoptional
permissionsarray of stringoptional

What this person may do at the till, read from staff_permissions ONLY. There is deliberately no "they are in the organisation so they can do everything" fallback, which is how the Business app behaves today and is exactly what would let a Front desk tile issue a refund.

hot_untilstring, nullableoptional

Idle window. Slides on writes only, never on reads.

hot_ceiling_atstring, nullableoptional

Absolute. Never extended, however busy the till is.

failureenumoptional

INVALID is returned identically for a wrong PIN and for a person who is not on this roster, so the endpoint cannot be used to work out who works here.

DEVICE_UNKNOWNDEVICE_REVOKEDDEVICE_QUARANTINEDTILL_DISABLEDSTORE_PAUSEDINVALIDLOCKED
locked_untilstring, nullableoptional
attempts_remaininginteger, nullableoptional
curl -X POST "https://www.membber.com/api/v1/till/unlock" \
  -H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "f73aee0f-0000-4000-8000-d0c5000000f7",
    "pin": "<pin>"
  }'
Response, 200
{
  "ok": true,
  "token": "<token>",
  "display_name": "<display_name>",
  "permissions": [
    "<permission>"
  ],
  "hot_until": "<hot_until>",
  "hot_ceiling_at": "<hot_ceiling_at>",
  "failure": "DEVICE_UNKNOWN",
  "locked_until": "<locked_until>",
  "attempts_remaining": -9007199254740991
}

Prove a named person is standing there, without starting a till session

For a gate that needs to know WHO is present but has no session to hand out: the kiosk exit. Runs the same bcrypt check and the same lockout ladder as an unlock, so a kiosk cannot be used as an unlimited PIN oracle that the till itself would have stopped. Mints nothing.

Request body

store_idstring · uuidrequired
user_idstring · uuidrequired
pinstringrequired

Six digits. Six rather than four because the lockout ladder allows 15 guesses before a manager must intervene: against 10^4 that is a 1-in-667 shot per person, against 10^6 it is 1-in-66,667. The cost is two extra taps. NEVER returned by any endpoint.

Response, 200

okbooleanrequired
display_namestring, nullableoptional
failureenumoptional
INVALIDLOCKED
locked_untilstring, nullableoptional
attempts_remaininginteger, nullableoptional
curl -X POST "https://www.membber.com/api/v1/till/verify" \
  -H "Authorization: Bearer $MEMBBER_TOKEN" \
  -H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
  -H "Content-Type: application/json" \
  -d '{
    "store_id": "6659c139-0000-4000-8000-d0c500000066",
    "user_id": "f73aee0f-0000-4000-8000-d0c5000000f7",
    "pin": "<pin>"
  }'
Response, 200
{
  "ok": true,
  "display_name": "<display_name>",
  "failure": "INVALID",
  "locked_until": "<locked_until>",
  "attempts_remaining": -9007199254740991
}
WhatsApp
Book a Call
Start Free