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-IDheader (from domain resolution), falls back to user'scurrent_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 numberpage_size(optional, default: 25, max: 100) - Items per pagesort(optional, default:name) - Sort field (prefix with-for descending)
Response: 200 OK
{
"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 resolutiondomain- Custom domain (e.g.,clinic.myclinic.com) — for custom domain resolution
Response: 200 OK
{
"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- Neitherslugnordomainquery parameter provided404 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
{
"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 token403 forbidden- User is not a member of this organization404 organization_not_found- Organization doesn't exist
POST /v1/organizations
Create a new organization. Superadmin only.
Authentication: Required Authorization: Superadmin only
Request Body:
{
"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
{
"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- Missingnameorslug401 unauthorized- Missing or invalid auth token403 forbidden- User is not a superadmin409 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:
{
"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
{
"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 token403 forbidden- User is not an admin of this organization404 organization_not_found- Organization doesn't exist400 validation_error- Invalid field values
Field Restrictions:
slugcannot be changed via this endpoint (prevents breaking URLs)idcannot be changedcreated_atcannot 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
{
"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:
{
"domain": "clinic.myclinic.com",
"domain_type": "clinic"
}domain_type:"clinic"or"portal"
Response: 201 Created
{
"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_type403 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 IDdomainId(required) - Domain ID
Response: 200 OK
{
"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 IDdomainId(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
{
"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 token403 forbidden- User is not an admin of this organization404 organization_not_found- Organization doesn't exist429 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:
{
"email": "[email protected]"
}Response: 200 OK
{
"data": {
"user_id": 42,
"organization_id": 1,
"message": "User connected successfully"
}
}Errors:
401 unauthorized- Missing or invalid auth token403 forbidden- User is not an admin of this organization404 user_not_found- No user with this email exists404 organization_not_found- Organization doesn't exist409 user_already_connected- User already belongs to this organization429 rate_limited- Too many requests (max 50 per hour)
Business Logic:
- Find user by email address
- Check if user is already connected to this organization
- If not connected, create entry in
user_organizationstable - 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
Related Endpoints
Organization Switching
To switch the current user's active organization, use:
PUT /v1/me/switch-organization
{
"organization_id": 3
}See Auth & User Endpoints for full documentation.
Error Code Reference
| Code | HTTP | Description |
|---|---|---|
validation_error | 400 | Input validation failed |
unauthorized | 401 | Missing or invalid auth token |
forbidden | 403 | Insufficient permissions |
organization_not_found | 404 | Organization doesn't exist |
domain_not_found | 404 | Domain doesn't exist or doesn't belong to org |
user_not_found | 404 | User doesn't exist |
user_already_connected | 409 | User already belongs to organization |
rate_limited | 429 | Too many requests |
internal_error | 500 | Unexpected server error |
Examples
Full Organization Onboarding Flow
Superadmin creates the organization
bashPOST /v1/organizations { "name": "RestartiX", "slug": "restartix" }User visits branded subdomain (
restartix.clinic.restartix.app)bash# Frontend proxy resolves the org: GET /v1/public/organizations/resolve?slug=restartixUser signs up via Clerk (handled by Clerk)
User is auto-provisioned on first API call (ClerkAuth middleware creates the internal user record)
Admin connects user to the organization
bashPOST /v1/organizations/1/connect-user { "email": "[email protected]" }User can now access organization resources
bashGET /v1/me # Returns user with organization_ids: [1]
Admin Managing Organization
Admin updates organization details
bashPATCH /v1/organizations/1 Content-Type: application/json { "tagline": "New tagline", "email": "[email protected]" }Admin adds existing user to organization
bashPOST /v1/organizations/1/connect-user Content-Type: application/json { "email": "[email protected]" }Admin retrieves API keys for integration setup
bashGET /v1/organizations/1/api-keys
Multi-Organization User Flow
User lists their organizations
bashGET /v1/organizations # Returns: [{ id: 1, name: "RestartiX" }, { id: 3, name: "HealthCorp" }]User switches to different organization
bashPUT /v1/me/switch-organization Content-Type: application/json { "organization_id": 3 }All subsequent requests now use organization 3's context
bashGET /v1/appointments # Returns only appointments for HealthCorp (org 3)