Tenant Service — API Contracts
Status: populated Owner: TBD Last updated: 2026-04-18 Companion: Service Template · 05 API Design · API_PATH_CONVENTIONS
Base URL: https://api.ghasi-ehealth.{tld}
Auth: Bearer JWT (RS256) except internal routes.
Error envelope: { error: ERROR_CODE, message, requestId, timestamp }.
1. Tenant lifecycle (Super Admin)
| Method | Path | Scope | Summary |
|---|---|---|---|
| GET | /api/v1/admin/tenants | SUPER_ADMIN | List all tenants (paginated) |
| POST | /api/v1/admin/tenants | SUPER_ADMIN | Create tenant |
| GET | /api/v1/admin/tenants/:id | SUPER_ADMIN | Get tenant details |
| POST | /api/v1/admin/tenants/:id/activate | SUPER_ADMIN | Activate tenant (saga) |
| POST | /api/v1/admin/tenants/:id/suspend | SUPER_ADMIN | Suspend tenant |
| POST | /api/v1/admin/tenants/:id/reactivate | SUPER_ADMIN | Reactivate suspended tenant |
| POST | /api/v1/admin/tenants/:id/terminate | SUPER_ADMIN | Terminate tenant (irreversible) |
| PATCH | /api/v1/admin/tenants/:id/subscription | SUPER_ADMIN | Update subscription tier/dates |
POST /api/v1/admin/tenants — request body:
{
"slug": "kabul-central-hospital",
"legalName": "Kabul Central Hospital",
"displayName": "KCH",
"countryCode": "AF",
"contactEmail": "admin@kch.af",
"timezone": "Asia/Kabul",
"locale": "ps",
"subscriptionTier": "PROFESSIONAL",
"subscriptionStart": "2026-01-01"
}
201 Created:
{
"id": "ten_01H...",
"slug": "kabul-central-hospital",
"status": "pending",
"createdAt": "2026-04-18T00:00:00Z"
}
2. Tenant profile (Tenant Admin)
| Method | Path | Scope | Summary |
|---|---|---|---|
| GET | /api/v1/tenants/:id | TENANT_ADMIN (own) or SUPER_ADMIN | Get tenant |
| PATCH | /api/v1/tenants/:id | TENANT_ADMIN | Update displayName, contactEmail, locale, timezone |
| GET | /api/v1/tenants/:id/config | TENANT_ADMIN | List all config KV |
| PUT | /api/v1/tenants/:id/config/:key | TENANT_ADMIN | Set config value |
| DELETE | /api/v1/tenants/:id/config/:key | TENANT_ADMIN | Remove config value |
Allowed config keys: mfa_required, session_timeout_minutes, branding.primary_color, branding.logo_url, max_failed_login_attempts.
Errors: 400 TENANT_CONFIG_KEY_UNKNOWN, 403 TENANT_CROSS_TENANT.
3. Hierarchy (Tenant Admin)
| Method | Path | Scope | Summary |
|---|---|---|---|
| GET | /api/v1/tenants/:id/nodes | TENANT_ADMIN | List all nodes (flat) |
| GET | /api/v1/tenants/:id/nodes/:nodeId/tree | TENANT_ADMIN | Subtree from node |
| GET | /api/v1/tenants/:id/nodes/:nodeId/ancestors | JWT | Ancestor chain (used by license resolver) |
| POST | /api/v1/tenants/:id/nodes | TENANT_ADMIN | Create node |
| PATCH | /api/v1/tenants/:id/nodes/:nodeId | TENANT_ADMIN | Update node name/attributes |
| POST | /api/v1/tenants/:id/nodes/:nodeId/archive | TENANT_ADMIN | Archive node |
POST node request:
{
"parentNodeId": "nod_01H...",
"nodeType": "facility",
"name": "Emergency Department",
"code": "ED-01",
"attributes": { "phone": "+93799000001" }
}
Errors: 422 TENANT_NODE_CROSS_TENANT, 422 TENANT_NODE_INVALID_TYPE.
4. User profiles and membership
| Method | Path | Scope | Summary |
|---|---|---|---|
| GET | /api/v1/tenants/:id/users | TENANT_ADMIN | List user profiles |
| POST | /api/v1/tenants/:id/users | TENANT_ADMIN | Invite user (create profile + send invitation event) |
| GET | /api/v1/tenants/:id/users/:userId | TENANT_ADMIN | Get user profile |
| PATCH | /api/v1/tenants/:id/users/:userId | TENANT_ADMIN | Update profile (name, specialty) |
| POST | /api/v1/tenants/:id/users/:userId/memberships | TENANT_ADMIN | Assign user to node |
| DELETE | /api/v1/tenants/:id/users/:userId/memberships/:nodeId | TENANT_ADMIN | Remove membership |
5. RBAC (Tenant Admin)
| Method | Path | Scope | Summary |
|---|---|---|---|
| GET | /api/v1/tenants/:id/roles | TENANT_ADMIN | List roles |
| POST | /api/v1/tenants/:id/roles | TENANT_ADMIN | Create custom role |
| POST | /api/v1/tenants/:id/users/:userId/roles | TENANT_ADMIN | Assign role at node |
| DELETE | /api/v1/tenants/:id/users/:userId/roles/:roleId | TENANT_ADMIN | Revoke role |
| POST | /api/v1/tenants/:id/access/evaluate | JWT | RBAC/ABAC evaluate — { decision, reasons } |
POST /evaluate request:
{
"subjectId": "usr_01H...",
"nodeId": "nod_01H...",
"resource": "patient_chart",
"action": "read"
}
200 response:
{ "decision": "allow", "reasons": ["role:CLINICIAN grants patient_chart:read at nod_01H..."] }
6. Internal (cluster-only)
| Method | Path | Summary |
|---|---|---|
| GET | /internal/tenant/tenants/:id | Resolve tenant by ID |
| GET | /internal/tenant/tenants/by-slug/:slug | Resolve tenant by slug |
| GET | /internal/tenant/tenants/:id/status | Active status probe |
| GET | /internal/tenant/nodes/:id/ancestors | Ancestor chain for license resolver |
| GET | /internal/tenant/users/:userId/access-context | Roles + memberships for identity access-context |
| POST | /internal/tenant/evaluate | Authorization evaluate for service-to-service |
7. Pagination and idempotency
- Pagination:
?page=1&pageSize=50(max 100). - Idempotency:
Idempotency-Keyrequired onPOST /admin/tenants/:id/activate(24h window).409 TENANT_IDEMPOTENCY_MISMATCHon mismatch. X-Request-Idechoed;Traceparentpropagated.
8. Rate limits (Kong)
| Surface | Limit |
|---|---|
POST /admin/tenants/:id/activate | 5/min/IP |
POST /api/v1/tenants/* (Tenant Admin writes) | 120/min/tenant |
GET /api/v1/tenants/* | 600/min/tenant |