Organization and doctor API workflows
Doctor registration at POST /api/v1/doctors/register is implemented for independent and invited signup; see Doctor registration. The organization doctor list is also implemented; see Organization management API. Doctor organization discovery at GET /api/v1/doctors/organizations is implemented as described below. The remaining onboarding and affiliation routes below are documentation-first contracts. Valid requests to those routes invoke a TODO use case and return 501 Not Implemented as application/problem+json. Malformed JSON returns 400 and schema validation errors return 422. Success schemas and future business errors describe intended behavior; they are not implemented. Bearer authentication is declared in OpenAPI but is not enforced by these stubs. Those stubs write no records. Registration atomically creates an account and profile, plus an active affiliation and consumed invitation when invited.
Doctor authentication is implemented separately: POST /api/v1/doctors/auth/login accepts email/password and GET /api/v1/doctors/auth/verify returns the authenticated identity and doctor profile. These require an active Identity account and doctor profile, without license approval or organization affiliation. See Doctor authentication.
Ownership and permissions
Identity owns credentials, account state, login, refresh, and logout. The new doctor service owns doctor profile records, including license and specialization. Organization owns ordinary memberships, separate doctor affiliations, invitations, and join requests. Subscription remains the owner of subscription and invoice data; the organization billing endpoints will eventually use curated public capabilities.
An organization admin is an organization member, not a platform admin from the admin service. Only organization admins manage ordinary memberships and roles, manage invitations and join requests, and read subscriptions and invoices. Ordinary organization members can read organization details, members, and affiliated doctors. Roles offered by the new API are admin, doctor, and staff. Existing manager data is retained; the new API does not offer manager assignment. An ordinary member role doctor creates neither a doctor profile nor an affiliation.
Doctors may belong to multiple organizations. Organization doctor lists and detail lookups are scoped to that organization's affiliations, never the global doctor list. Doctors can read their own profiles and affiliations and discover active organizations through summaries without member or billing information. Registration and invitation preview are public; other doctor operations will require an active authenticated doctor.
Independent registration and joining
POST /api/v1/doctors/registerwithname,email,password,license_number, andspecializationcreates an identity account and doctor profile without an organization. The 201 response uses{message, status_code, data}and containsdata.doctor, with no tokens.- Sign in using
POST /api/v1/doctors/auth/login, then validate the session withGET /api/v1/doctors/auth/verify. - Discover active organizations using
GET /api/v1/doctors/organizationswith the bearer token. An active account and doctor profile are required; an existing affiliation is unnecessary. The response contains{message, status_code, data, pagination: {next_cursor}}, with onlyid,name,slug,description,address, andstatusin each organization summary. Results are ordered by organization ID ascending. Uselimit(default 20, maximum 100) and passpagination.next_cursorascursorfor the next page. Invalid cursors return 422; suspended and deleted organizations are excluded. POST /api/v1/doctors/me/join-requestswithorganization_idcreates a pending organization-owned request. A duplicate pending request or an existing active affiliation is a documented 409 conflict.- An organization admin lists incoming requests and accepts or rejects them. Acceptance creates a separate active doctor affiliation and accepts the request atomically. A doctor can cancel only their own pending request.
Request states are pending, accepted, rejected, and canceled. Affiliation states are active and inactive. Prescription API changes, eligibility enforcement, subscription gates, and doctor verification are outside this pass. The workflow records that joining an organization is required before the future prescription-creation flow.
Invited registration
- An organization admin creates an invitation containing the new doctor's
email. Invitations target new identity accounts only; existing doctors use join requests. - The eventual implementation sends a registration link with a single-use token. Token generation, expiry duration and email delivery are TODOs. Registration redemption is implemented for stored invitations; Organization stores the SHA-256 hexadecimal token hash in
org_doctor_invitations, never the plaintext token. - The registration page can preview the invitation using its token.
- The doctor registers with the usual required fields and
invitation_token. Registration email must match the invitation email after normalization. Registration rejects expired, revoked, already consumed or unknown invitations and invitations for inactive organizations with 422. - Invited registration creates the doctor profile and directly creates active organization affiliation, without another approval. The response additionally contains
data.affiliation. All signup writes commit together; failures leave the invitation available for a corrected retry. Authentication uses a separate doctor login.
Invitation states are pending, accepted, revoked, and expired. Invitation lists never return secret tokens. Preview returns organization summary, recipient email, status, and expiry only.
Ordinary members and billing
Adding an ordinary member requires role plus exactly one of existing_user_id or new_user. New users require name, email, and temporary_password. Member detail, edit, and role-assignment paths use membership IDs. Editing accepts only membership status (active or inactive); assigning a role is a separate PUT. Account details are not edited by the membership endpoint.
The preserved legacy DELETE member endpoint still uses its existing identifier semantics (historically user_id); it is not part of the new member-ID contracts. Legacy organization creation, team routes, member removal, and active-context switching remain available. Admin-facing organization and membership capabilities are unchanged.
Current subscription uses the authoritative subscription/public schema, including assigned plan snapshot, overrides, effective features, billing period, status, price_minor, currency, and optional trial/cancellation dates. Subscription states remain trialing, active, past_due, canceled, and expired. Invoice history includes ID, invoice number, subscription ID, amount_minor, currency, status, invoice date, and optional paid date. Invoice states are draft, open, paid, void, and uncollectible (reserved for future write-off). Payments are manually recorded by platform admins against invoices; organization billing history remains invoice history with paid and remaining balances. See Subscription API. Amounts use integer minor currency units; currency is BDT.
All JSON fields use snake_case. Lists return items and optional next_cursor. Use cursor and limit query parameters; limit defaults to 20 and allows 1–100.
Route inventory
All paths below are prefixed with /api/v1. /doctors/register, GET /doctors/organizations, and GET /organizations/{organizationId}/doctors are implemented; other listed operations remain 501 stubs.
The additional profile, affiliation, team, active-context and verification routes are documented in Management API contracts; they are also 501 stubs.
| Method | Path | Intended success |
|---|---|---|
| GET | /organizations | 200 |
| GET | /organizations/{organizationId} | 200 |
| GET | /organizations/{organizationId}/members | 200 |
| GET | /organizations/{organizationId}/members/{memberId} | 200 |
| POST | /organizations/{organizationId}/members | 201 |
| PUT | /organizations/{organizationId}/members/{memberId} | 200 |
| PUT | /organizations/{organizationId}/members/{memberId}/role | 200 |
| GET | /organizations/{organizationId}/doctors | 200 |
| GET | /organizations/{organizationId}/doctors/{doctorId} | 200 |
| GET | /organizations/{organizationId}/subscription | 200 |
| GET | /organizations/{organizationId}/billing-history | 200 |
| POST | /organizations/{organizationId}/doctor-invitations | 201 |
| GET | /organizations/{organizationId}/doctor-invitations | 200 |
| POST | /organizations/{organizationId}/doctor-invitations/{invitationId}/revoke | 200 |
| GET | /organizations/{organizationId}/doctor-join-requests | 200 |
| POST | /organizations/{organizationId}/doctor-join-requests/{requestId}/accept | 200 |
| POST | /organizations/{organizationId}/doctor-join-requests/{requestId}/reject | 200 |
| POST | /doctors/register | 201 |
| GET | /doctors/profile | 200 |
| GET | /doctors/me/organizations | 200 |
| GET | /doctors/organizations | 200 |
| GET | /doctors/organizations/{organizationId} | 200 |
| GET | /doctors/invitations/{token} | 200 |
| POST | /doctors/me/join-requests | 201 |
| GET | /doctors/me/join-requests | 200 |
| POST | /doctors/me/join-requests/{requestId}/cancel | 200 |