Keep the living API sheet aligned with demo personas, avatars, ID-card QR scan, list filters, and Admin/Ecom coordination. Co-authored-by: Cursor <cursoragent@cursor.com>
7.0 KiB
Endpoints: Members
Member CRUD, quick import, invites, join flow, and dependants.
Workspace: /Users/kirukib/Desktop/Yaltopia Project/Gishen-B2B
Entity: ../entities/member.md
GET /v1/members
List members.
| Auth | Required |
| Roles | SUPER_USER, HR_ADMIN, FINANCE (read metadata) |
Query
?page=1&status=active&department_id=dept_01H&package_id=pkg_01HGOLD&q=abel&member_type=primary&has_dependants=true
| Param | Notes |
|---|---|
q |
Search name, email, phone, employee_id |
status |
invited | active | inactive | offboarded |
department_id |
Filter by department |
package_id |
Filter by assigned package |
member_type |
primary | dependant |
has_dependants |
true | false — primaries with/without dependants |
Portal UI: when more than three filters are shown, they open in an Apply modal (client-only); API still accepts all query params above.
Response 200
Paginated members. HR/Finance receive withhold-safe fields (no Rx detail).
Clinical withhold
Response omits prescription arrays; includes prescription_count only.
POST /v1/members
Create member.
| Auth | Required |
| Roles | SUPER_USER, HR_ADMIN |
Request
{
"full_name": "Sara Hailu",
"phone": "+251933445566",
"email": "sara.h@acme.et",
"employee_id": "EMP-2001",
"department_id": "dept_01HABC",
"package_id": "pkg_01HGOLD",
"member_type": "primary",
"start_date": "2026-04-01",
"send_invite": true
}
Response 201
Created Member.
Errors
| Code | HTTP | When |
|---|---|---|
ORG_PENDING |
422 | Org not active |
DUPLICATE_MEMBER |
409 | Phone/employee_id conflict |
NOT_FOUND |
404 | Invalid department/package |
DEPENDANT_LIMIT_EXCEEDED |
422 | For dependant create |
GET /v1/members/:id
Member detail.
| Auth | Required |
| Roles | SUPER_USER, HR_ADMIN, FINANCE (metadata); MEMBER (own only) |
Errors
| Code | HTTP |
|---|---|
FORBIDDEN |
403 — MEMBER accessing other member |
NOT_FOUND |
404 |
Clinical withhold
HR/Finance: no linked prescriptions. SUPER_USER: may include prescriptions_summary. MEMBER own: full allowance, order pointers.
Detail payload includes avatar_url, card_id, verification_id for pharmacy ID card (safe for HR).
GET /v1/members/:id/id-card
Printable pharmacy verification card payload (B2B print UI + Admin/POS scan).
| Auth | Required |
| Roles | SUPER_USER, HR_ADMIN; MEMBER (own member_id only) |
Response 200
{
"data": {
"member_id": "mbr_01HMEM001",
"full_name": "Abel Mekonnen",
"employee_id": "EMP-1042",
"card_id": "GSH-1042",
"verification_id": "EMP-1042",
"avatar_url": "https://api.dicebear.com/9.x/lorelei/svg?seed=mbr_01HMEM001",
"org_name": "Acme Bank",
"org_join_code": "ACME-2026",
"package_name": "Gold Executive",
"status": "active",
"qr_payload": "gishen://member/mbr_01HMEM001?v=EMP-1042"
}
}
QR / scan contract
| Format | gishen://member/{member_id}?v={verification_id} |
| Consumer | Gishen-Admin POS / pharmacist checkout — resolve covered member |
| Not included | Prescription images, medicine lines, diagnosis |
Errors
| Code | HTTP |
|---|---|
FORBIDDEN |
403 |
NOT_FOUND |
404 |
MEMBER_INACTIVE |
422 — offboarded / inactive cards may still print with watermark (product TBD) |
Clinical withhold
ID card is verification metadata only — allowed for HR. No clinical fields.
PATCH /v1/members/:id
Update member.
| Auth | Required |
| Roles | SUPER_USER, HR_ADMIN |
Request (partial)
{
"department_id": "dept_01HNEW",
"package_id": "pkg_01HSILVER",
"avatar_url": "https://cdn.gishen.../avatars/mbr_01H.png",
"overrides": { "copay_percent": 5 }
}
avatar_url may also be set via future POST /v1/members/:id/avatar (multipart). Mock phase uses deterministic portrait URLs.
POST /v1/members/:id/offboard
Offboard member.
| Auth | Required |
| Roles | SUPER_USER, HR_ADMIN |
Request
{
"end_date": "2026-03-31",
"reason": "resignation"
}
Response 200
status: "offboarded". Prescriptions remain on customer_id.
POST /v1/members/import
Quick Excel/CSV import (simplified migration).
| Auth | Required |
| Roles | SUPER_USER, HR_ADMIN |
Request
multipart/form-data: file, optional send_invites=true
Response 202
{
"data": {
"job_id": "mig_01HQUICK",
"status": "validating"
}
}
Delegates to migration engine — see migration.md.
POST /v1/members/:id/invite
Resend or create invite.
| Auth | Required |
| Roles | SUPER_USER, HR_ADMIN |
Response 200
{
"data": {
"invite_url": "https://b2b.gishen.../invite/tok_abc123",
"expires_at": "2026-03-20T00:00:00Z"
}
}
GET /v1/invite/:token
Public invite preview.
| Auth | Public |
Response 200
{
"data": {
"org_name": "Acme Bank",
"member_name": "Sara Hailu",
"expires_at": "2026-03-20T00:00:00Z",
"valid": true
}
}
Errors
| Code | HTTP |
|---|---|
INVITE_EXPIRED |
422 |
NOT_FOUND |
404 |
POST /v1/invite/:token/accept
Accept invite and activate member account.
| Auth | Public (creates session) |
Request
{
"password": "SecurePass1",
"locale": "en"
}
POST /v1/join
Self-join via join code or email domain.
| Auth | Public |
Request
{
"join_code": "ACME-2026",
"full_name": "New Employee",
"email": "new@acme.et",
"phone": "+251944556677"
}
Or domain-verified email flow.
Response 201
Pending or active member + session depending on org verification settings.
Errors
| Code | HTTP |
|---|---|
VALIDATION_FAILED |
400 — bad code/domain |
ORG_PENDING |
422 |
POST /v1/members/:id/dependants
Add dependant.
| Auth | Required |
| Roles | SUPER_USER, HR_ADMIN |
Request
{
"full_name": "Kidus Hailu",
"phone": "+251955667788",
"relationship": "child",
"start_date": "2026-04-01"
}
UI routes
| Route | Endpoint(s) |
|---|---|
/members |
GET /v1/members (search + filters; modal when >3) |
/members/new |
POST /v1/members |
/members/import |
POST /v1/members/import |
/members/[id] |
GET/PATCH + invite/offboard; ID card tab → GET /v1/members/:id/id-card |
/invite/[token] |
GET/POST invite |
/join |
POST /v1/join |
Clinical withhold
Member endpoints never return prescription images or medicine lines to HR_ADMIN/FINANCE. Use prescription endpoints with role checks for SUPER_USER/MEMBER clinical access.