Noumaris API Documentation
Version: 2.0.0 Base URL: https://api.noumaris.com (production) or http://localhost:8000 (development)
Table of Contents
- Authentication
- Superadmin Endpoints
- Institution Admin Endpoints
- Invitation Endpoints
- Permission Endpoints
- Clinical Documentation Endpoints
- Error Codes
- Rate Limits
- Postman Collection
Authentication
All API endpoints (except /invite/* public endpoints) require JWT authentication via Keycloak.
Getting a Token
- Login via Keycloak: Navigate to your Keycloak login page
- Extract Token: After successful login, the JWT token will be available in your session
- Include in Requests: Add the token to the
Authorizationheader
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...Token Claims
The JWT token contains the following claims used by the API:
sub: User ID (matches database User.id)email: User email addresspreferred_username: Usernamerealm_access.roles: Array of role names (e.g.,["superadmin"],["institution_admin"])
Superadmin Endpoints
Base path: /admin/superadmin
Required Role: superadmin in Keycloak + active Superadmin database record
List Institutions
GET /admin/superadmin/institutionsQuery Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number (1-indexed) |
page_size | integer | 50 | Results per page (1-100) |
search | string | null | Search by name or email |
subscription_status | string | null | Filter: active, suspended, expired |
Response: 200 OK
{
"institutions": [
{
"id": 1,
"name": "General Teaching Hospital",
"institution_type": "Academic Medical Center",
"primary_contact_email": "[email protected]",
"billing_email": "[email protected]",
"address": "123 Medical Drive, Toronto, ON",
"max_residents": 50,
"max_admins": 5,
"resident_count": 12,
"admin_count": 2,
"subscription_status": "active",
"contract_start_date": "2025-01-01",
"contract_end_date": "2026-01-01",
"notes": "Pilot program participant",
"created_at": "2025-01-01T00:00:00",
"updated_at": "2025-10-14T12:00:00"
}
],
"total": 1,
"page": 1,
"page_size": 50
}Notes:
- The endpoint always returns a paginated envelope with
institutions,total,page, andpage_size. - Each institution includes
resident_countandadmin_count, which represent active users tracked by the backend.
Create Institution
POST /admin/superadmin/institutionsRequest Body:
{
"name": "New Hospital",
"institution_type": "Community Hospital",
"primary_contact_email": "[email protected]",
"billing_email": "[email protected]",
"address": "456 Health St, Vancouver, BC",
"max_residents": 30,
"max_admins": 3,
"subscription_status": "active",
"contract_start_date": "2025-10-15",
"contract_end_date": "2026-10-15",
"notes": "Standard subscription"
}Validation Rules:
name: Required, must be uniqueprimary_contact_email: Required, valid email formatmax_residents: Required, must be > 0max_admins: Required, must be > 0subscription_status: Must be one of:active,suspended,expired
Response: 201 Created
{
"id": 2,
"name": "New Hospital",
"institution_type": "Community Hospital",
...
}Errors:
409 Conflict: Institution with this name already exists422 Validation Error: Invalid field values
Update Institution
PUT /admin/superadmin/institutions/{institution_id}Request Body: (all fields optional)
{
"max_residents": 60,
"max_admins": 7,
"subscription_status": "active",
"notes": "Upgraded subscription"
}Response: 200 OK
Update Subscription Status
PATCH /admin/superadmin/institutions/{institution_id}/status?subscription_status=suspended&reason=Payment+overdueQuery Parameters:
subscription_status(required):active,suspended, orexpiredreason(optional): Explanation for status change
Response: 200 OK
{
"status": "success",
"message": "Institution status updated to suspended",
"previous_status": "active"
}Get Institution Features
GET /admin/superadmin/institutions/{institution_id}/featuresResponse: 200 OK
[
{
"feature_id": 1,
"feature_name": "Live Transcription",
"feature_description": "Real-time audio transcription",
"has_access": true,
"granted_by": "superadmin-001",
"granted_at": "2025-10-01T10:00:00"
},
{
"feature_id": 2,
"feature_name": "Note Generation",
"feature_description": "AI-powered clinical notes",
"has_access": false,
"granted_by": null,
"granted_at": null
}
]Grant/Revoke Features (Bulk)
POST /admin/superadmin/institutions/{institution_id}/featuresRequest Body:
{
"feature_ids": [1, 2, 3],
"has_access": true,
"reason": "Pilot program enrollment"
}Response: 200 OK
{
"status": "success",
"message": "Granted access to 3 features",
"feature_ids": [1, 2, 3]
}Use Cases:
- Grant features: Set
has_access: true - Revoke features: Set
has_access: false
System Analytics
GET /admin/superadmin/analyticsResponse: 200 OK
{
"total_institutions": 15,
"active_institutions": 12,
"suspended_institutions": 2,
"total_residents": 248,
"total_admins": 34,
"total_features": 12,
"avg_residents_per_institution": 16.53,
"institutions_at_capacity": 3
}Institution Admin Endpoints
Base path: /admin/institution
Required Role: institution_admin in Keycloak + active InstitutionAdmin database record
List Residents
GET /admin/institution/residentsQuery Parameters:
| Parameter | Type | Description |
|---|---|---|
pgy_level | integer | Filter by PGY level (1-10) |
specialty | string | Filter by specialty (partial match) |
search | string | Search by email, first name, last name |
Response: 200 OK
[
{
"user_id": "resident-001",
"email": "[email protected]",
"first_name": "Jane",
"last_name": "Doe",
"pgy_level": 2,
"specialty": "Internal Medicine",
"start_date": "2024-07-01",
"expected_completion_date": "2027-06-30",
"is_chief_resident": false,
"status": "active",
"permissions": ["live_transcription", "note_generation"],
"created_at": "2024-07-01T00:00:00"
}
]Invite Resident
POST /admin/institution/residents/inviteRequest Body:
{
"email": "[email protected]",
"first_name": "John",
"last_name": "Smith",
"pgy_level": 1,
"specialty": "Family Medicine"
}Validation:
pgy_level: Must be 1-10email: Must be valid format, cannot already exist
Response: 201 Created
{
"id": 5,
"email": "[email protected]",
"first_name": "John",
"last_name": "Smith",
"pgy_level": 1,
"specialty": "Family Medicine",
"invite_token": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"invited_at": "2025-10-14T12:00:00",
"expires_at": "2025-10-21T12:00:00",
"status": "pending"
}Errors:
400 Bad Request: Institution at resident capacity409 Conflict: Active invitation already exists for this email409 Conflict: User is already a resident at this institution
Next Steps:
- Admin receives the
invite_token - Admin sends invitation email to resident with link:
https://app.noumaris.com/invite/{invite_token} - Resident clicks link, validates token, registers via Keycloak
- Resident accepts invitation (see Invitation Endpoints)
Update Resident
PUT /admin/institution/residents/{user_id}Request Body: (all fields optional)
{
"pgy_level": 3,
"specialty": "Internal Medicine - Cardiology",
"is_chief_resident": true,
"status": "active"
}Response: 200 OK
Grant Single Permission
POST /admin/institution/residents/{user_id}/permissionsRequest Body:
{
"feature_id": "live_transcription",
"reason": "Completed orientation training"
}Response: 200 OK
Errors:
403 Forbidden: Institution does not have access to this feature404 Not Found: Feature or resident not found
Revoke Single Permission
DELETE /admin/institution/residents/{user_id}/permissions/{feature_id}?reason=Training+incompleteResponse: 200 OK
Bulk Permission Update
POST /admin/institution/residents/bulk-permissionsRequest Body:
{
"feature_ids": ["live_transcription", "note_generation"],
"grant": true,
"target": "pgy2",
"reason": "PGY-2 feature rollout"
}Target Options:
all: All active residentspgy1,pgy2,pgy3,pgy4,pgy5: Specific PGY levelchief: Chief residents only
Response: 200 OK
{
"status": "success",
"message": "Bulk permission update completed",
"residents_affected": 8,
"features_updated": 2,
"operations_succeeded": 16,
"operations_failed": 0
}Get Usage Metrics
GET /admin/institution/usageResponse: 200 OK
{
"residents_used": 12,
"residents_max": 50,
"residents_available": 38,
"residents_at_limit": false,
"admins_used": 2,
"admins_max": 5,
"admins_available": 3,
"admins_at_limit": false,
"subscription_status": "active"
}Get Audit Log
GET /admin/institution/audit-log?limit=50Response: 200 OK
[
{
"id": 123,
"feature_id": 1,
"action": "GRANT",
"performed_by": "admin-001",
"reason": "Completed training",
"previous_state": false,
"new_state": true,
"created_at": "2025-10-14T12:00:00"
}
]Invitation Endpoints
Base path: /invite
Authentication: Public endpoints (no authentication required for validation/acceptance)
Validate Invitation Token
GET /invite/{token}Response: 200 OK
{
"id": 5,
"email": "[email protected]",
"first_name": "John",
"last_name": "Smith",
"pgy_level": 1,
"specialty": "Family Medicine",
"institution_name": "General Teaching Hospital",
"institution_id": 1,
"invited_at": "2025-10-14T12:00:00",
"expires_at": "2025-10-21T12:00:00",
"status": "pending",
"is_valid": true,
"error_message": null
}Invalid Token Example:
{
"id": 5,
"email": "[email protected]",
...
"status": "expired",
"is_valid": false,
"error_message": "This invitation has expired"
}Accept Invitation
POST /invite/{token}/acceptRequest Body:
{
"user_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"first_name": "John",
"last_name": "Smith"
}Notes:
user_id: The Keycloak user ID (obtained after registration)first_name,last_name: Optional overrides (defaults to invitation values)
Response: 201 Created
{
"status": "success",
"message": "Welcome! Your account has been created successfully.",
"user_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"institution_id": 1,
"institution_name": "General Teaching Hospital",
"pgy_level": 1,
"specialty": "Family Medicine"
}Errors:
400 Bad Request: Invitation expired, already accepted, or institution at capacity404 Not Found: Invalid token409 Conflict: Resident profile already exists for this user
Error Codes
HTTP Status Codes
| Code | Meaning | Common Causes |
|---|---|---|
| 400 | Bad Request | Invalid input, capacity limits exceeded |
| 401 | Unauthorized | Missing or invalid JWT token |
| 403 | Forbidden | Insufficient permissions for action |
| 404 | Not Found | Resource doesn't exist |
| 409 | Conflict | Duplicate record (e.g., email already invited) |
| 422 | Validation Error | Pydantic validation failed (invalid field values) |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Unexpected server error |
| 503 | Service Unavailable | Database connection failed |
Error Response Format
{
"detail": "Detailed error message explaining what went wrong"
}Rate Limits
| Endpoint Type | Limit | Window |
|---|---|---|
| Health checks | 100 requests | 1 minute |
| Standard endpoints | 50 requests | 1 minute |
| Admin endpoints | 50 requests | 1 minute |
| WebSocket connections | 3 concurrent | Per user |
Rate Limit Headers:
X-RateLimit-Limit: 50
X-RateLimit-Remaining: 47
X-RateLimit-Reset: 1697282400Rate Limit Exceeded Response:
{
"error": "Rate limit exceeded",
"retry_after": 30
}Postman Collection
A Postman collection is available for testing all endpoints:
Download: Noumaris_API_Collection.json
Setup Instructions
- Import the collection into Postman
- Set environment variables:
base_url:http://localhost:8000orhttps://api.noumaris.comjwt_token: Your JWT token from Keycloak login
- Run requests in order (authentication first)
Environment Variables
{
"base_url": "http://localhost:8000",
"jwt_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"institution_id": "1",
"resident_user_id": "resident-001",
"invite_token": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}OpenAPI/Swagger Documentation
Interactive API documentation is available at:
- Swagger UI:
http://localhost:8000/docs - ReDoc:
http://localhost:8000/redoc
Accessing Swagger UI
- Navigate to
http://localhost:8000/docs - Click "Authorize" button
- Enter your JWT token:
Bearer <your_token> - Test endpoints directly from the browser
Best Practices
For Frontend Developers
- Always include JWT token in Authorization header
- Handle 401/403 errors by redirecting to login
- Check feature permissions before showing UI elements
- Cache institution usage metrics (refresh every 5 minutes)
- Validate email formats client-side before API calls
- Use optimistic UI updates for better UX (update UI immediately, rollback on error)
For Backend Developers
- Add rate limiting to new endpoints
- Use Pydantic models for all request/response validation
- Log all admin actions with
log_admin_action() - Check capacity limits before creating new resources
- Use database transactions for multi-step operations
- Add OpenAPI documentation to all new endpoints
Support
For API support, contact:
- Email: [email protected]
- Documentation: https://docs.noumaris.com
- GitHub Issues: https://github.com/noumaris/api/issues