Skip to content

Organizations API

This document describes all organization-related API endpoints.

Overview

Organizations are the root of multi-tenancy. Every request operates within an organization context, determined by the domain of the incoming request. The frontend middleware resolves the hostname to an organization and passes the org ID via the X-Organization-ID header.

Base Conventions

  • Base path: /v1/organizations (authenticated), /v1/public/organizations (public)
  • Authentication: Clerk session token in Authorization: Bearer <token> header
  • Content-Type: application/json
  • Organization context: Set via X-Organization-ID header (from domain resolution), falls back to user's current_organization_id

Endpoints

GET /v1/organizations

List organizations the current user belongs to. Superadmins see all organizations.

Authentication: Required Authorization: Any authenticated user

Query Parameters:

  • page (optional, default: 1) - Page number
  • page_size (optional, default: 25, max: 100) - Items per page
  • sort (optional, default: name) - Sort field (prefix with - for descending)

Response: 200 OK

json
{
  "data": [
    {
      "id": 1,
      "name": "RestartiX",
      "slug": "restartix",
      "tagline": "Telemedicine platform",
      "email": "[email protected]",
      "phone": "+31 20 123 4567",
      "website": "https://restartix.com",
      "location": "Amsterdam, Netherlands",
      "logo_url": "https://s3.amazonaws.com/restartix/logo.png",
      "icon_url": "https://s3.amazonaws.com/restartix/icon.png",
      "created_at": "2024-01-01T00:00:00Z",
      "updated_at": "2025-01-15T10:30:00Z"
    },
    {
      "id": 3,
      "name": "HealthCorp",
      "slug": "healthcorp",
      "tagline": "Healthcare solutions",
      "email": "[email protected]",
      "phone": null,
      "website": null,
      "location": "Rotterdam, Netherlands",
      "logo_url": null,
      "icon_url": null,
      "created_at": "2024-06-15T00:00:00Z",
      "updated_at": "2024-12-01T08:00:00Z"
    }
  ],
  "meta": {
    "page": 1,
    "page_size": 25,
    "total": 2,
    "total_pages": 1
  }
}

Errors:

  • 401 unauthorized - Missing or invalid auth token

GET /v1/public/organizations/resolve

Resolve an organization by slug for domain-based routing. This endpoint is public (no authentication required) and returns minimal data needed for frontend middleware to establish org context.

Authentication: Not required (public endpoint)

Query Parameters (one required):

  • slug - Organization slug (e.g., healthcorp) — for platform subdomain resolution
  • domain - Custom domain (e.g., clinic.myclinic.com) — for custom domain resolution

Response: 200 OK

json
{
  "data": {
    "id": 1,
    "name": "HealthCorp",
    "slug": "healthcorp",
    "logo_url": "https://s3.amazonaws.com/restartix/healthcorp/logo.png",
    "icon_url": "https://s3.amazonaws.com/restartix/healthcorp/icon.png",
    "language_code": "en"
  }
}

Errors:

  • 400 validation_error - Neither slug nor domain query parameter provided
  • 404 organization_not_found - No organization found (or custom domain not verified)

Use Cases:

  • Frontend middleware: healthcorp.clinic.restartix.app → extract slug → ?slug=healthcorp
  • Custom domain: clinic.myclinic.com?domain=clinic.myclinic.com
  • Public booking page: Resolve org branding before authentication

Caching: Results are cached in Redis for 5 minutes (both slug and domain lookups).


GET /v1/organizations/{id}

Get organization details. Requires org membership.

Authentication: Required Authorization: User must be a member of this organization OR be a superadmin

Path Parameters:

  • id (required) - Organization ID

Response: 200 OK

json
{
  "data": {
    "id": 1,
    "name": "RestartiX",
    "slug": "restartix",
    "tagline": "Telemedicine platform",
    "description": "Full-service telemedicine platform for remote consultations",
    "email": "[email protected]",
    "phone": "+31 20 123 4567",
    "website": "https://restartix.com",
    "location": "Amsterdam, Netherlands",
    "logo_url": "https://s3.amazonaws.com/restartix/logo.png",
    "icon_url": "https://s3.amazonaws.com/restartix/icon.png",
    "template_terms_id": 10,
    "template_policy_id": 11,
    "created_at": "2024-01-01T00:00:00Z",
    "updated_at": "2025-01-15T10:30:00Z"
  }
}

Errors:

  • 401 unauthorized - Missing or invalid auth token
  • 403 forbidden - User is not a member of this organization
  • 404 organization_not_found - Organization doesn't exist

POST /v1/organizations

Create a new organization. Superadmin only.

Authentication: Required Authorization: Superadmin only

Request Body:

json
{
  "name": "HealthCorp",
  "slug": "healthcorp",
  "tagline": "Healthcare solutions",
  "email": "[email protected]",
  "location": "Rotterdam, Netherlands",
  "language_code": "en"
}

Required fields: name, slug. All others are optional.

Response: 201 Created

json
{
  "data": {
    "id": 3,
    "name": "HealthCorp",
    "slug": "healthcorp",
    "tagline": "Healthcare solutions",
    "description": null,
    "email": "[email protected]",
    "phone": null,
    "website": null,
    "location": "Rotterdam, Netherlands",
    "logo_url": null,
    "icon_url": null,
    "language_code": "en",
    "created_at": "2025-03-04T10:00:00Z",
    "updated_at": "2025-03-04T10:00:00Z"
  }
}

Errors:

  • 400 validation_error - Missing name or slug
  • 401 unauthorized - Missing or invalid auth token
  • 403 forbidden - User is not a superadmin
  • 409 slug_taken - An organization with this slug already exists

PATCH /v1/organizations/{id}

Update organization details. Admin or superadmin only.

Authentication: Required Authorization: Organization admin OR superadmin

Path Parameters:

  • id (required) - Organization ID

Request Body:

json
{
  "name": "RestartiX NL",
  "tagline": "Telemedicine for the Netherlands",
  "description": "Complete telemedicine platform with integrated scheduling",
  "email": "[email protected]",
  "phone": "+31 20 123 4567",
  "website": "https://restartix.nl",
  "location": "Amsterdam, Netherlands",
  "logo_url": "https://s3.amazonaws.com/restartix/new-logo.png",
  "icon_url": "https://s3.amazonaws.com/restartix/new-icon.png",
  "template_terms_id": 12,
  "template_policy_id": 13
}

All fields are optional. Only provided fields will be updated.

Response: 200 OK

json
{
  "data": {
    "id": 1,
    "name": "RestartiX NL",
    "slug": "restartix",
    "tagline": "Telemedicine for the Netherlands",
    "description": "Complete telemedicine platform with integrated scheduling",
    "email": "[email protected]",
    "phone": "+31 20 123 4567",
    "website": "https://restartix.nl",
    "location": "Amsterdam, Netherlands",
    "logo_url": "https://s3.amazonaws.com/restartix/new-logo.png",
    "icon_url": "https://s3.amazonaws.com/restartix/new-icon.png",
    "template_terms_id": 12,
    "template_policy_id": 13,
    "created_at": "2024-01-01T00:00:00Z",
    "updated_at": "2025-01-15T14:22:00Z"
  }
}

Errors:

  • 401 unauthorized - Missing or invalid auth token
  • 403 forbidden - User is not an admin of this organization
  • 404 organization_not_found - Organization doesn't exist
  • 400 validation_error - Invalid field values

Field Restrictions:

  • slug cannot be changed via this endpoint (prevents breaking URLs)
  • id cannot be changed
  • created_at cannot be changed

GET /v1/organizations/{id}/domains

List custom domains configured for this organization.

Authentication: Required Authorization: Organization member (any role)

Path Parameters:

  • id (required) - Organization ID

Response: 200 OK

json
{
  "data": [
    {
      "id": 1,
      "organization_id": 1,
      "domain": "clinic.myclinic.com",
      "domain_type": "clinic",
      "status": "verified",
      "verification_token": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "verified_at": "2025-03-01T12:00:00Z",
      "last_check_at": "2025-03-04T06:00:00Z",
      "created_at": "2025-02-28T10:00:00Z",
      "updated_at": "2025-03-01T12:00:00Z"
    }
  ]
}

POST /v1/organizations/{id}/domains

Add a custom domain to this organization. Returns the domain record and DNS verification instructions.

Authentication: Required Authorization: Organization admin OR superadmin

Path Parameters:

  • id (required) - Organization ID

Request Body:

json
{
  "domain": "clinic.myclinic.com",
  "domain_type": "clinic"
}
  • domain_type: "clinic" or "portal"

Response: 201 Created

json
{
  "data": {
    "id": 2,
    "organization_id": 1,
    "domain": "clinic.myclinic.com",
    "domain_type": "clinic",
    "status": "pending",
    "verification_token": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "verified_at": null,
    "last_check_at": null,
    "created_at": "2025-03-04T10:00:00Z",
    "updated_at": "2025-03-04T10:00:00Z"
  },
  "verification": {
    "txt_host": "_restartix-verification.clinic.myclinic.com",
    "txt_value": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }
}

Next Steps: The admin must add a DNS TXT record at _restartix-verification.{domain} with the txt_value, then trigger verification via POST /v1/organizations/{id}/domains/{domainId}/verify.

Errors:

  • 400 validation_error - Missing domain or invalid domain_type
  • 403 forbidden - User is not an admin

POST /v1/organizations/{id}/domains/{domainId}/verify

Trigger DNS TXT verification for a custom domain. Looks up _restartix-verification.{domain} and checks if any TXT record matches the stored verification token.

Authentication: Required Authorization: Organization admin OR superadmin

Path Parameters:

  • id (required) - Organization ID
  • domainId (required) - Domain ID

Response: 200 OK

json
{
  "data": {
    "id": 2,
    "organization_id": 1,
    "domain": "clinic.myclinic.com",
    "domain_type": "clinic",
    "status": "verified",
    "verification_token": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "verified_at": "2025-03-04T10:05:00Z",
    "last_check_at": "2025-03-04T10:05:00Z",
    "created_at": "2025-03-04T10:00:00Z",
    "updated_at": "2025-03-04T10:05:00Z"
  }
}

If DNS verification fails, status will be "failed" and verified_at will remain null.


DELETE /v1/organizations/{id}/domains/{domainId}

Remove a custom domain from this organization. Also invalidates the domain resolve cache.

Authentication: Required Authorization: Organization admin OR superadmin

Path Parameters:

  • id (required) - Organization ID
  • domainId (required) - Domain ID

Response: 204 No Content


GET /v1/organizations/{id}/api-keys

Get decrypted integration API keys for this organization. Admin only. Rate limited.

Authentication: Required Authorization: Organization admin OR superadmin

Path Parameters:

  • id (required) - Organization ID

Response: 200 OK

json
{
  "data": [
    {
      "id": 1,
      "title": "Service Production",
      "service": "service_name",
      "api_key": "sk_live_abc123def456ghi789jkl012mno345pqr678",
      "created_at": "2024-01-01T00:00:00Z",
      "updated_at": "2024-01-01T00:00:00Z"
    },
    {
      "id": 2,
      "title": "Service Staging",
      "service": "service_name",
      "api_key": "sk_test_xyz789uvw456rst123opq890lmn567ijk234",
      "created_at": "2024-02-15T00:00:00Z",
      "updated_at": "2024-02-15T00:00:00Z"
    }
  ]
}

Errors:

  • 401 unauthorized - Missing or invalid auth token
  • 403 forbidden - User is not an admin of this organization
  • 404 organization_not_found - Organization doesn't exist
  • 429 rate_limited - Too many requests (max 10 per minute)

Security Notes:

  • This endpoint returns decrypted API keys in plain text
  • API keys are encrypted in the database using AES-256-GCM
  • Rate limited to prevent abuse
  • All accesses are logged in the audit log
  • Only admins can access this endpoint

Use Cases:

  • Admin configuring integrations
  • Debugging integration issues
  • Rotating API keys

POST /v1/organizations/{id}/connect-user

Connect an existing user to this organization. Admin only. Rate limited.

Authentication: Required Authorization: Organization admin OR superadmin

Path Parameters:

  • id (required) - Organization ID

Request Body:

json
{
  "email": "[email protected]"
}

Response: 200 OK

json
{
  "data": {
    "user_id": 42,
    "organization_id": 1,
    "message": "User connected successfully"
  }
}

Errors:

  • 401 unauthorized - Missing or invalid auth token
  • 403 forbidden - User is not an admin of this organization
  • 404 user_not_found - No user with this email exists
  • 404 organization_not_found - Organization doesn't exist
  • 409 user_already_connected - User already belongs to this organization
  • 429 rate_limited - Too many requests (max 50 per hour)

Business Logic:

  1. Find user by email address
  2. Check if user is already connected to this organization
  3. If not connected, create entry in user_organizations table
  4. Return success with user and organization IDs

Use Cases:

  • Admin adding an existing user to their organization
  • Merging user accounts across organizations
  • Granting access to consultants or temporary staff

Security Notes:

  • Rate limited to prevent abuse
  • All connections are logged in the audit log
  • User will see this organization in their organization list
  • User can switch to this organization via /v1/me/switch-organization

Organization Switching

To switch the current user's active organization, use:

PUT /v1/me/switch-organization

json
{
  "organization_id": 3
}

See Auth & User Endpoints for full documentation.


Error Code Reference

CodeHTTPDescription
validation_error400Input validation failed
unauthorized401Missing or invalid auth token
forbidden403Insufficient permissions
organization_not_found404Organization doesn't exist
domain_not_found404Domain doesn't exist or doesn't belong to org
user_not_found404User doesn't exist
user_already_connected409User already belongs to organization
rate_limited429Too many requests
internal_error500Unexpected server error

Examples

Full Organization Onboarding Flow

  1. Superadmin creates the organization

    bash
    POST /v1/organizations
    { "name": "RestartiX", "slug": "restartix" }
  2. User visits branded subdomain (restartix.clinic.restartix.app)

    bash
    # Frontend proxy resolves the org:
    GET /v1/public/organizations/resolve?slug=restartix
  3. User signs up via Clerk (handled by Clerk)

  4. User is auto-provisioned on first API call (ClerkAuth middleware creates the internal user record)

  5. Admin connects user to the organization

    bash
    POST /v1/organizations/1/connect-user
    { "email": "[email protected]" }
  6. User can now access organization resources

    bash
    GET /v1/me
    # Returns user with organization_ids: [1]

Admin Managing Organization

  1. Admin updates organization details

    bash
    PATCH /v1/organizations/1
    Content-Type: application/json
    
    {
      "tagline": "New tagline",
      "email": "[email protected]"
    }
  2. Admin adds existing user to organization

    bash
    POST /v1/organizations/1/connect-user
    Content-Type: application/json
    
    {
      "email": "[email protected]"
    }
  3. Admin retrieves API keys for integration setup

    bash
    GET /v1/organizations/1/api-keys

Multi-Organization User Flow

  1. User lists their organizations

    bash
    GET /v1/organizations
    # Returns: [{ id: 1, name: "RestartiX" }, { id: 3, name: "HealthCorp" }]
  2. User switches to different organization

    bash
    PUT /v1/me/switch-organization
    Content-Type: application/json
    
    {
      "organization_id": 3
    }
  3. All subsequent requests now use organization 3's context

    bash
    GET /v1/appointments
    # Returns only appointments for HealthCorp (org 3)