Skip to content

Noumaris API Documentation

Version: 2.0.0 Base URL: https://api.noumaris.com (production) or http://localhost:8000 (development)


Table of Contents

  1. Authentication
  2. Superadmin Endpoints
  3. Institution Admin Endpoints
  4. Invitation Endpoints
  5. Permission Endpoints
  6. Clinical Documentation Endpoints
  7. Error Codes
  8. Rate Limits
  9. Postman Collection

Authentication

All API endpoints (except /invite/* public endpoints) require JWT authentication via Keycloak.

Getting a Token

  1. Login via Keycloak: Navigate to your Keycloak login page
  2. Extract Token: After successful login, the JWT token will be available in your session
  3. Include in Requests: Add the token to the Authorization header
http
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 address
  • preferred_username: Username
  • realm_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

http
GET /admin/superadmin/institutions

Query Parameters:

ParameterTypeDefaultDescription
pageinteger1Page number (1-indexed)
page_sizeinteger50Results per page (1-100)
searchstringnullSearch by name or email
subscription_statusstringnullFilter: active, suspended, expired

Response: 200 OK

json
{
  "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, and page_size.
  • Each institution includes resident_count and admin_count, which represent active users tracked by the backend.

Create Institution

http
POST /admin/superadmin/institutions

Request Body:

json
{
  "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 unique
  • primary_contact_email: Required, valid email format
  • max_residents: Required, must be > 0
  • max_admins: Required, must be > 0
  • subscription_status: Must be one of: active, suspended, expired

Response: 201 Created

json
{
  "id": 2,
  "name": "New Hospital",
  "institution_type": "Community Hospital",
  ...
}

Errors:

  • 409 Conflict: Institution with this name already exists
  • 422 Validation Error: Invalid field values

Update Institution

http
PUT /admin/superadmin/institutions/{institution_id}

Request Body: (all fields optional)

json
{
  "max_residents": 60,
  "max_admins": 7,
  "subscription_status": "active",
  "notes": "Upgraded subscription"
}

Response: 200 OK


Update Subscription Status

http
PATCH /admin/superadmin/institutions/{institution_id}/status?subscription_status=suspended&reason=Payment+overdue

Query Parameters:

  • subscription_status (required): active, suspended, or expired
  • reason (optional): Explanation for status change

Response: 200 OK

json
{
  "status": "success",
  "message": "Institution status updated to suspended",
  "previous_status": "active"
}

Get Institution Features

http
GET /admin/superadmin/institutions/{institution_id}/features

Response: 200 OK

json
[
  {
    "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)

http
POST /admin/superadmin/institutions/{institution_id}/features

Request Body:

json
{
  "feature_ids": [1, 2, 3],
  "has_access": true,
  "reason": "Pilot program enrollment"
}

Response: 200 OK

json
{
  "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

http
GET /admin/superadmin/analytics

Response: 200 OK

json
{
  "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

http
GET /admin/institution/residents

Query Parameters:

ParameterTypeDescription
pgy_levelintegerFilter by PGY level (1-10)
specialtystringFilter by specialty (partial match)
searchstringSearch by email, first name, last name

Response: 200 OK

json
[
  {
    "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

http
POST /admin/institution/residents/invite

Request Body:

json
{
  "email": "[email protected]",
  "first_name": "John",
  "last_name": "Smith",
  "pgy_level": 1,
  "specialty": "Family Medicine"
}

Validation:

  • pgy_level: Must be 1-10
  • email: Must be valid format, cannot already exist

Response: 201 Created

json
{
  "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 capacity
  • 409 Conflict: Active invitation already exists for this email
  • 409 Conflict: User is already a resident at this institution

Next Steps:

  1. Admin receives the invite_token
  2. Admin sends invitation email to resident with link: https://app.noumaris.com/invite/{invite_token}
  3. Resident clicks link, validates token, registers via Keycloak
  4. Resident accepts invitation (see Invitation Endpoints)

Update Resident

http
PUT /admin/institution/residents/{user_id}

Request Body: (all fields optional)

json
{
  "pgy_level": 3,
  "specialty": "Internal Medicine - Cardiology",
  "is_chief_resident": true,
  "status": "active"
}

Response: 200 OK


Grant Single Permission

http
POST /admin/institution/residents/{user_id}/permissions

Request Body:

json
{
  "feature_id": "live_transcription",
  "reason": "Completed orientation training"
}

Response: 200 OK

Errors:

  • 403 Forbidden: Institution does not have access to this feature
  • 404 Not Found: Feature or resident not found

Revoke Single Permission

http
DELETE /admin/institution/residents/{user_id}/permissions/{feature_id}?reason=Training+incomplete

Response: 200 OK


Bulk Permission Update

http
POST /admin/institution/residents/bulk-permissions

Request Body:

json
{
  "feature_ids": ["live_transcription", "note_generation"],
  "grant": true,
  "target": "pgy2",
  "reason": "PGY-2 feature rollout"
}

Target Options:

  • all: All active residents
  • pgy1, pgy2, pgy3, pgy4, pgy5: Specific PGY level
  • chief: Chief residents only

Response: 200 OK

json
{
  "status": "success",
  "message": "Bulk permission update completed",
  "residents_affected": 8,
  "features_updated": 2,
  "operations_succeeded": 16,
  "operations_failed": 0
}

Get Usage Metrics

http
GET /admin/institution/usage

Response: 200 OK

json
{
  "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

http
GET /admin/institution/audit-log?limit=50

Response: 200 OK

json
[
  {
    "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

http
GET /invite/{token}

Response: 200 OK

json
{
  "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:

json
{
  "id": 5,
  "email": "[email protected]",
  ...
  "status": "expired",
  "is_valid": false,
  "error_message": "This invitation has expired"
}

Accept Invitation

http
POST /invite/{token}/accept

Request Body:

json
{
  "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

json
{
  "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 capacity
  • 404 Not Found: Invalid token
  • 409 Conflict: Resident profile already exists for this user

Error Codes

HTTP Status Codes

CodeMeaningCommon Causes
400Bad RequestInvalid input, capacity limits exceeded
401UnauthorizedMissing or invalid JWT token
403ForbiddenInsufficient permissions for action
404Not FoundResource doesn't exist
409ConflictDuplicate record (e.g., email already invited)
422Validation ErrorPydantic validation failed (invalid field values)
429Too Many RequestsRate limit exceeded
500Internal Server ErrorUnexpected server error
503Service UnavailableDatabase connection failed

Error Response Format

json
{
  "detail": "Detailed error message explaining what went wrong"
}

Rate Limits

Endpoint TypeLimitWindow
Health checks100 requests1 minute
Standard endpoints50 requests1 minute
Admin endpoints50 requests1 minute
WebSocket connections3 concurrentPer user

Rate Limit Headers:

http
X-RateLimit-Limit: 50
X-RateLimit-Remaining: 47
X-RateLimit-Reset: 1697282400

Rate Limit Exceeded Response:

json
{
  "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

  1. Import the collection into Postman
  2. Set environment variables:
    • base_url: http://localhost:8000 or https://api.noumaris.com
    • jwt_token: Your JWT token from Keycloak login
  3. Run requests in order (authentication first)

Environment Variables

json
{
  "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

  1. Navigate to http://localhost:8000/docs
  2. Click "Authorize" button
  3. Enter your JWT token: Bearer <your_token>
  4. Test endpoints directly from the browser

Best Practices

For Frontend Developers

  1. Always include JWT token in Authorization header
  2. Handle 401/403 errors by redirecting to login
  3. Check feature permissions before showing UI elements
  4. Cache institution usage metrics (refresh every 5 minutes)
  5. Validate email formats client-side before API calls
  6. Use optimistic UI updates for better UX (update UI immediately, rollback on error)

For Backend Developers

  1. Add rate limiting to new endpoints
  2. Use Pydantic models for all request/response validation
  3. Log all admin actions with log_admin_action()
  4. Check capacity limits before creating new resources
  5. Use database transactions for multi-step operations
  6. Add OpenAPI documentation to all new endpoints

Support

For API support, contact:

Internal documentation for Noumaris platform