Skip to content

Prove who is at the desk: unlock, step up, or switch

POST
/v1/desk/sessions/{id}/passcode
curl --request POST \
--url https://api.aerandil.com/v1/desk/sessions/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/passcode \
--header 'Content-Type: application/json' \
--data '{ "employee_id": "example", "passcode": "example", "staff_member_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "unlock_token": "example" }'

Public — a locked desk holds no bearer, only the shift’s unlock token. With it, an employee and their passcode (the floor handset’s passcode, under the same per-staff lockout): the person already at the desk unlocks or steps up (desk_session.stepped_up); another member of the property’s desk staff takes the desk (desk_session.switched, under their own principal). Either way the answer is a fresh bearer for that person carrying a step-up window (AERANDIL_DESK_STEP_UP_TTL), inside which the step-up-gated verbs and markings are theirs. Every refusal about the person reads the same (invalid_desk_credentials); a session that has ended says so (desk_session_ended). Rate-limited per IP like /oauth/token.

id
required
string format: uuid

The desk_session id.

Media typeapplication/json
object
employee_id

Who is at the desk, as typed at the lock screen (the staff_member business key). Exactly one of employee_id and staff_member_id.

string
passcode
required

The staff member’s passcode — the same one the floor handset takes.

string
staff_member_id

Who is at the desk, when the desk already knows (a step-up).

string format: uuid
unlock_token
required

The shift’s unlock token, from opening the session. It grants nothing alone and is never a bearer.

string
Examplegenerated
{
"employee_id": "example",
"passcode": "example",
"staff_member_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"unlock_token": "example"
}

Proven; the bearer runs as the person at the desk.

Media typeapplication/json
object
access_token
required

Short-lived HS256 JWT. Send as Authorization: Bearer <token> on subsequent calls.

string
expires_in
required

Token lifetime in seconds. Default 3600 (one hour); deployment-configurable via AERANDIL_TOKEN_TTL_SECS.

integer
>= 1
scope
required

Space-separated list of granted scopes. The set the token actually carries — already narrowed by any scope request filter and the client’s pre-provisioned grants.

string
token_type
required

Always Bearer in v1.

Allowed value: Bearer
desk_session_id
required
string format: uuid
idle_lock_secs
required

Idle seconds after which the desk locks (AERANDIL_DESK_IDLE_LOCK).

integer
outlet_id

The outlet whose till the session is; absent at the front desk.

string format: uuid
properties
required

The houses the person the bearer runs as may hold the desk at — one per front-office staff record claiming their identity — the choices POST /v1/desk/sessions/{id}/move takes.

Array<string>
property_id
required
string format: uuid
role
required

The desk role the scope vector came from (the F&B roles at a till).

string
Allowed values: front_desk front_office_manager food_beverage_server food_beverage_manager
shift_ends_at
required

When the shift — and every token minted for it — ends.

string format: date-time
staff_member_id
required

The staff member the bearer runs as — its User principal.

string format: uuid
step_up_secs
required

Seconds a step-up is honoured for (AERANDIL_DESK_STEP_UP_TTL).

integer
step_up_until

Until when this bearer carries a step-up proof. Absent on the sign-in’s own bearer: an identity-provider session may be silently re-used, so it is not a proof.

string format: date-time
switched

On a passcode proof: whether the desk changed hands.

boolean
unlock_token

Only when opening: what a locked desk keeps to come back by passcode.

string
Example
{
"token_type": "Bearer",
"role": "front_desk"
}
Cache-Control
string

Always no-store. Object and link state is live data and is never cacheable.

invalid_desk_credentials (no detail by design) or desk_session_ended.

Media typeapplication/json

Structured error wrapper. Every non-2xx response from any Aerandil endpoint serialises to this shape; the HTTP status carries the failure class (4xx/5xx), the body adds a stable code for programmatic dispatch plus a human-readable message.

object
error
required
object
code
required

Stable, programmatic error code (e.g. unauthorized, forbidden, not_found, invalid_body, invalid_url, precondition_failed, no_secondary_key). Clients dispatch on code, not on the message (which may be localised or contextualised in future versions).

string
instance_id

Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.

string
message
required

Human-readable explanation of the failure.

string
parameters

Per-error structured detail. The shape varies by code (e.g. forbidden_action carries {"action": "..."}; precondition_failed carries {"current_version": N}).

Examplegenerated
{
"error": {
"code": "example",
"instance_id": "example",
"message": "example",
"parameters": "example"
}
}

Neither or both of employee_id and staff_member_id, or no passcode.

Media typeapplication/json

Structured error wrapper. Every non-2xx response from any Aerandil endpoint serialises to this shape; the HTTP status carries the failure class (4xx/5xx), the body adds a stable code for programmatic dispatch plus a human-readable message.

object
error
required
object
code
required

Stable, programmatic error code (e.g. unauthorized, forbidden, not_found, invalid_body, invalid_url, precondition_failed, no_secondary_key). Clients dispatch on code, not on the message (which may be localised or contextualised in future versions).

string
instance_id

Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.

string
message
required

Human-readable explanation of the failure.

string
parameters

Per-error structured detail. The shape varies by code (e.g. forbidden_action carries {"action": "..."}; precondition_failed carries {"current_version": N}).

Examplegenerated
{
"error": {
"code": "example",
"instance_id": "example",
"message": "example",
"parameters": "example"
}
}

Per-IP rate limit exceeded (the /oauth/token limiter).