Skip to main content
POST

Authorizations

Authorization
string
header
required

Your secret API key as a Bearer token: Authorization: Bearer vxn_sk_.... Keys are created and scoped in the dashboard - see Authentication.

Headers

Idempotency-Key
string
required

Required on every mutating POST. 24h byte-replay; 409 idempotency_key_conflict on body mismatch. See Idempotency.

Required string length: 8 - 255

Body

application/json

Adds a staff account. mode picks the flow:

  • invite (default) - invitation email with an accept link; status: "invited" until the member accepts
  • create - the account exists immediately (status: "active"); the member signs in right away with their email code (OTP) or social login. Built for till/scanner accounts.

Delegated billing is never configurable via API.

email
string<email>
required

Sign-in email, unique per organization. Already a member or already invited → 409 already_member.

role
enum<string>
required

Role. Determines the location_ids cardinality rule.

Available options:
admin,
manager,
scanner
mode
enum<string>
default:invite

invite - email invitation; the member accepts the link and the account activates. create - active immediately; the member signs in with their email code (OTP) or social login, no acceptance step. Defaults to invite.

Available options:
invite,
create
first_name
string

Given name (useful with mode: "create" - invited members fill their own).

Maximum string length: 100
last_name
string

Family name.

Maximum string length: 100
location_ids
string[]

Role-bound: scanner → REQUIRED, exactly one (422 scanner_requires_single_location); manager → one or more, defaults to the organization's default location when omitted; admin → must be omitted (422 admin_has_all_locations).

Maximum array length: 100
Pattern: ^loc_[A-Za-z0-9]+$
expires_at
integer | null

Auto-expiry for temporary staff - scanner role only (422 expires_at_scanner_only): seasonal hires, one-day events. Must be in the future (422 expires_at_in_past). At expiry: sessions revoked (~5 min), access removed, status: "expired", confirmation email; heads-up email 24 h before. Omit or null = permanent.

language
enum<string>

Email language. Defaults to the organization language.

Available options:
en,
sk,
cs,
de

Response

Team member created (invited or active per mode).

A staff account - dashboard and scanner access. Role lives in the auth provider; the location assignment is dual-written: a queryable mirror in VEXiON (surfaced as Location.team_members[]) plus auth-time enforcement metadata on the user account, which is what locks a scanner to their single store at every request. location_ids here is the ONE write path - both copies sync from it.

id and this resource's identity fields (first_name/last_name/email/language) are anchored to a registry row scoped to YOUR organization - the same person staffing another organization has a DIFFERENT id there, and a PATCH here never changes what that other organization sees for them. Manual transactions carry the acting member as Transaction.team_member_id - which is why revoked and expired members are preserved as records: history stays attributable forever. Members sign in with their email code (OTP) or social login.

id
string
required

Team member identifier, tm_ prefix. For an active member it is stable within your organization across role and location changes, and it is never shared with any other organization they may also staff. For a member still invited it belongs to the current invitation and changes when the invitation is re-sent, when a PATCH edits the invited member's name, role or expires_at (the invitation is re-issued and a new email sent), and when the invite is accepted; the previous id then returns 404. Key invited members by email.

Pattern: ^tm_[A-Za-z0-9]+$
Example:

"tm_01HW9R6M4XQ2N8MTX1RVTM0001"

object
string
required

Always "team_member".

Allowed value: "team_member"
email
string<email>
required

Sign-in email, unique per organization.

Example:

"anna@demo-coffee.example"

role
enum<string>
required

What they can do:

  • admin - everything incl. billing, cards, team; implicit access to ALL locations
  • manager - customers, notifications, scanner at assigned locations; no billing, no card editing
  • scanner - POS scanner only, at exactly one location (a till account)

Role changes email the member automatically.

Available options:
admin,
manager,
scanner
Example:

"scanner"

location_ids
string[]
required

Assigned locations - the write path for the location↔member relationship (Location.team_members[] is the read mirror). Cardinality is role-bound: admin → always [] (all locations implicit); manager → one or more; scanner → exactly one.

Pattern: ^loc_[A-Za-z0-9]+$
Example:
status
enum<string>
required

Lifecycle:

  • invited - invitation email sent, account not yet created (invitation origin only)
  • active - can sign in and act
  • revoked - removed by an admin (DELETE); sessions revoked, record preserved
  • expired - expires_at passed; sessions revoked automatically, record preserved

Terminal states (revoked, expired) are not editable - add the person again with a new POST.

Available options:
invited,
active,
revoked,
expired
Example:

"active"

origin
enum<string>
required

How the account came to be: invitation - the member accepted an email invite on the branded accept page; direct - an API call created the account, active immediately with email-code (OTP) or social sign-in (typical for till/scanner accounts).

Available options:
invitation,
direct
Example:

"direct"

created_at
integer
required

Unix seconds.

Example:

1750000000

first_name
string | null

Given name. Null until the member fills their profile (invitation origin) or you supply it (direct origin).

Example:

"Anna"

last_name
string | null

Family name.

Example:

"Kováčová"

expires_at
integer | null

Unix seconds when this member auto-expires - the temporary-staff feature, available on the scanner role only (seasonal hires, one-day events; a till account that cleans itself up). Null = permanent, and always null for admin/manager. At expiry the platform revokes all sessions (signed out within ~5 minutes), removes access, sets status: "expired", and emails the member; a heads-up email goes out 24 h before. Extend or clear by PATCHing before expiry.

Example:

null

language
enum<string> | null

Email language for invitations and lifecycle notices. Defaults to the organization language.

Available options:
en,
sk,
cs,
de,
null
Example:

"sk"

invited_at
integer | null

When the invitation email was sent. Null for direct origin.

Example:

1750000000

activated_at
integer | null

When the account became active - invitation acceptance time, or creation time for direct origin. Null while invited.

Example:

1750003600

updated_at
integer

Unix seconds of last change.

Example:

1751500000