The onboarding chain, as a chain
Admin creates the merchant, merchant invites the staff, staff redeem the code on a phone. Every endpoint for it already existed and was already documented - scattered across four sections in the order the server groups them, not the order a person meets them. Now one section, in tier order, each step with the request that makes it and the response it hands to the next tier: the owner password shown once, the invitation code shown once, the session returned by register so a new salesperson is never sent to a login form. The status codes were checked against the handlers: all three creations are 201. Three absences named rather than left to be found: a merchant cannot create a staff login directly (invitation only, on purpose); there is no mobile app in this repository, only the API it will call; and an admin cannot reset an owner's password or suspend a merchant over HTTP. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj
This commit is contained in:
132
API.md
132
API.md
@@ -63,6 +63,138 @@ has no business learning that a resource exists.
|
||||
|
||||
---
|
||||
|
||||
## The onboarding chain — who creates whom
|
||||
|
||||
Three tiers. Each one creates the login for the next, and nobody ever creates
|
||||
their own from nothing.
|
||||
|
||||
```
|
||||
┌──────────────────┐ creates ┌──────────────────┐ invites ┌──────────────────┐
|
||||
│ Platform admin │ ───────────▶ │ Merchant owner │ ───────────▶ │ Sales staff │
|
||||
│ (web) │ company + │ (web) │ code, │ (mobile) │
|
||||
│ │ owner login │ │ shown once │ │
|
||||
└──────────────────┘ └──────────────────┘ └──────────────────┘
|
||||
POST /api/admin/clients POST /api/team/invitations POST /api/auth/register
|
||||
```
|
||||
|
||||
### Tier 1 — the platform admin registers a merchant
|
||||
|
||||
Signed in as an account with `role: "admin"` and no company.
|
||||
|
||||
```
|
||||
POST /api/auth/login
|
||||
{ "email": "admin@loyaly.ai", "password": "…", "device": "Admin console" }
|
||||
|
||||
POST /api/admin/clients
|
||||
{ "company_name": "TeNext Retail", "owner_email": "suriya@tenext.in",
|
||||
"owner_name": "Suriya" }
|
||||
→ 201 { "client_id": "…", "slug": "tenext-retail",
|
||||
"owner_email": "suriya@tenext.in", "password": "xK9…" }
|
||||
```
|
||||
|
||||
`password` is generated and shown **once**. The admin hands the owner their
|
||||
email and that password — that is the merchant login. Leave `password` out of
|
||||
the request; a password an operator invents for someone else is weak and
|
||||
travels over chat.
|
||||
|
||||
```
|
||||
GET /api/admin/clients → every merchant, with site and user counts
|
||||
```
|
||||
|
||||
### Tier 2 — the merchant owner registers sales staff
|
||||
|
||||
Signed in as the owner (or any manager).
|
||||
|
||||
```
|
||||
POST /api/auth/login
|
||||
{ "email": "suriya@tenext.in", "password": "xK9…", "device": "Head office" }
|
||||
|
||||
POST /api/team/invitations
|
||||
{ "email": "priya@tenext.in", "full_name": "Priya R", "role": "staff",
|
||||
"expires_in_days": 7 }
|
||||
→ 201 { "id": "…", "email": "priya@tenext.in", "role": "staff",
|
||||
"code": "LQOUHR-AYYTPE-7Q756N-PGAAN6", "expires_at": "…" }
|
||||
```
|
||||
|
||||
`code` is shown **once** and is what the merchant gives the salesperson —
|
||||
read aloud, WhatsApp, printed on a card. It is single-use and expires. The
|
||||
salesperson chooses their own password when they redeem it (Tier 3), so the
|
||||
merchant never knows or handles a staff password.
|
||||
|
||||
Managing the team afterwards:
|
||||
|
||||
```
|
||||
GET /api/team → everyone: role, active, last login
|
||||
GET /api/team/invitations → codes still unredeemed (without the code)
|
||||
DELETE /api/team/invitations/{id} → withdraw one before it is used
|
||||
PATCH /api/team/{id} { "role": "manager" } → promote
|
||||
PATCH /api/team/{id} { "active": false } → they have left; signs them out now
|
||||
```
|
||||
|
||||
The owner also sets the shop up from the same login — `POST
|
||||
/api/sites/{site}/enrolment-code` for the shop PC, `POST
|
||||
/api/sites/{site}/cameras` for cameras — see §8.
|
||||
|
||||
### Tier 3 — the salesperson gets their mobile login
|
||||
|
||||
Not signed in yet. They have the code.
|
||||
|
||||
```
|
||||
GET /api/auth/invitation?code=LQOUHR-AYYTPE-7Q756N-PGAAN6 (no auth)
|
||||
→ { "client_name": "TeNext Retail", "email": "priya@tenext.in",
|
||||
"full_name": "Priya R", "role": "staff" }
|
||||
```
|
||||
|
||||
Show *"Join TeNext Retail as Priya R"* and ask for a password. Then:
|
||||
|
||||
```
|
||||
POST /api/auth/register (no auth)
|
||||
{ "code": "LQOUHR-AYYTPE-7Q756N-PGAAN6",
|
||||
"full_name": "Priya R", "password": "…", "device": "Pixel 8" }
|
||||
→ 201 { "access_token": "…", "refresh_token": "…", "expires_at": "…",
|
||||
"user": { …, "role": "staff", "client_name": "TeNext Retail" } }
|
||||
```
|
||||
|
||||
**They are signed in.** Do not send them to a login form. From the next day:
|
||||
|
||||
```
|
||||
POST /api/auth/login { "email": "priya@tenext.in", "password": "…", "device": "Pixel 8" }
|
||||
POST /api/auth/refresh { "refresh_token": "…", "device": "Pixel 8" }
|
||||
```
|
||||
|
||||
And the screens a salesperson uses — all `staff` may call:
|
||||
|
||||
```
|
||||
GET /api/visits?limit=30 then ?cursor=… who just walked in
|
||||
GET /api/visits/stream the same, pushed
|
||||
GET /api/visitors?q=priya find a customer
|
||||
GET /api/visitors/V-42/history their past visits
|
||||
PUT /api/visitors/V-42/profile give them a name
|
||||
POST /api/purchases record a sale
|
||||
GET /api/visitors/V-42/image their photo, if any
|
||||
```
|
||||
|
||||
Do **not** send `email` or `role` on register — they come from the code, and a
|
||||
body naming either is refused. That is what stops a forwarded code becoming
|
||||
somebody else's account.
|
||||
|
||||
### What does not exist, stated plainly
|
||||
|
||||
- **The merchant cannot create a staff login directly** with a password of
|
||||
their choosing. The only path is an invitation code the staff member redeems
|
||||
themselves. This is deliberate — it keeps staff passwords out of the
|
||||
merchant's hands and off chat — but it means a salesperson without a phone in
|
||||
hand cannot be set up *for* them. If that friction is real, a
|
||||
`POST /api/team/members` that mirrors `POST /api/admin/clients` (generated
|
||||
password, shown once) is a small addition.
|
||||
- **There is no mobile app in this repository.** Tier 3 is a complete API with
|
||||
no client yet. Everything above is what that app will call.
|
||||
- **The admin cannot reset a merchant owner's password over HTTP**, nor suspend
|
||||
or delete a merchant. Today that is `behavision-server provision` on the
|
||||
server.
|
||||
|
||||
---
|
||||
|
||||
## Quick starts
|
||||
|
||||
### A mobile app for shop-floor staff
|
||||
|
||||
Reference in New Issue
Block a user