diff --git a/API.md b/API.md index eeb0ce9..97235ca 100644 --- a/API.md +++ b/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