Skip to content

Specialties API

All specialty endpoints are organization-scoped via Row-Level Security. Users can only access specialties within their current organization (set via /v1/me/switch-organization).

Authentication

All endpoints require authentication via Clerk session token:

Authorization: Bearer <clerk_session_token>

RBAC Summary

OperationRoles Allowed
List/ReadAll authenticated users in the organization
CreateAdmin, Superadmin
UpdateAdmin, Superadmin
DeleteAdmin, Superadmin

GET /v1/specialties

List all specialties in the current organization.

Request

Query Parameters:

  • page (integer, optional) - Page number (default: 1)
  • page_size (integer, optional) - Items per page (default: 25, max: 100)
  • sort (string, optional) - Sort field (e.g., title, -created_at for descending)

Response: 200 OK

json
{
  "data": [
    {
      "id": 1,
      "organization_id": 1,
      "title": "Cardiology",
      "slug": "cardiology",
      "created_at": "2025-01-10T08:00:00Z",
      "updated_at": "2025-01-10T08:00:00Z"
    },
    {
      "id": 2,
      "organization_id": 1,
      "title": "Dermatology",
      "slug": "dermatology",
      "created_at": "2025-01-12T10:30:00Z",
      "updated_at": "2025-01-12T10:30:00Z"
    }
  ],
  "meta": {
    "page": 1,
    "page_size": 25,
    "total": 2,
    "total_pages": 1
  }
}

Errors

  • 401 unauthorized - Missing or invalid authentication token
  • 403 forbidden - User not a member of any organization

POST /v1/specialties

Create a new specialty. Admin-only.

Request

Headers:

Content-Type: application/json

Body:

json
{
  "title": "Cardiology",
  "slug": "cardiology"
}

Fields:

  • title (string, required) - Display name for the specialty
  • slug (string, required) - URL-safe identifier (must be unique within organization)

Response: 201 Created

json
{
  "data": {
    "id": 1,
    "organization_id": 1,
    "title": "Cardiology",
    "slug": "cardiology",
    "created_at": "2025-01-10T08:00:00Z",
    "updated_at": "2025-01-10T08:00:00Z"
  }
}

Errors

  • 400 validation_error - Missing or invalid fields
    json
    {
      "error": {
        "code": "validation_error",
        "message": "Validation failed",
        "details": {
          "fields": {
            "title": "is required",
            "slug": "must be lowercase alphanumeric with hyphens only"
          }
        }
      }
    }
  • 403 forbidden - Non-admin attempting to create specialty
  • 409 conflict - Slug already exists in this organization
    json
    {
      "error": {
        "code": "conflict",
        "message": "Specialty with slug 'cardiology' already exists in this organization"
      }
    }

GET /v1/specialties/{id}

Get a single specialty with related specialists.

Request

Path Parameters:

  • id (integer, required) - Specialty ID

Query Parameters:

  • include (string, optional) - Comma-separated list of relations to include (e.g., specialists)

Response: 200 OK

json
{
  "data": {
    "id": 1,
    "organization_id": 1,
    "title": "Cardiology",
    "slug": "cardiology",
    "created_at": "2025-01-10T08:00:00Z",
    "updated_at": "2025-01-10T08:00:00Z",
    "specialists": [
      {
        "id": 5,
        "name": "Dr. Jane Smith",
        "title": "Cardiologist",
        "avatar_url": "https://s3.../avatar.jpg"
      }
    ]
  }
}

Response: 200 OK (without include parameter)

json
{
  "data": {
    "id": 1,
    "organization_id": 1,
    "title": "Cardiology",
    "slug": "cardiology",
    "created_at": "2025-01-10T08:00:00Z",
    "updated_at": "2025-01-10T08:00:00Z"
  }
}

Errors

  • 404 not_found - Specialty doesn't exist or not accessible in current organization
    json
    {
      "error": {
        "code": "not_found",
        "message": "Specialty not found"
      }
    }

PUT /v1/specialties/{id}

Update a specialty. Admin-only.

Request

Path Parameters:

  • id (integer, required) - Specialty ID

Body:

json
{
  "title": "Cardiovascular Medicine",
  "slug": "cardiovascular-medicine"
}

Fields:

  • title (string, optional) - Updated display name
  • slug (string, optional) - Updated slug (must be unique within organization)

Response: 200 OK

json
{
  "data": {
    "id": 1,
    "organization_id": 1,
    "title": "Cardiovascular Medicine",
    "slug": "cardiovascular-medicine",
    "created_at": "2025-01-10T08:00:00Z",
    "updated_at": "2025-01-15T14:22:00Z"
  }
}

Errors

  • 400 validation_error - Invalid field values
  • 403 forbidden - Non-admin attempting to update specialty
  • 404 not_found - Specialty doesn't exist or not accessible
  • 409 conflict - New slug already exists in this organization

DELETE /v1/specialties/{id}

Delete a specialty. Admin-only.

Request

Path Parameters:

  • id (integer, required) - Specialty ID

Response: 204 No Content

No response body.

Errors

  • 403 forbidden - Non-admin attempting to delete specialty
  • 404 not_found - Specialty doesn't exist or not accessible
  • 409 conflict - Cannot delete specialty with active references
    json
    {
      "error": {
        "code": "conflict",
        "message": "Cannot delete specialty: 5 specialists are linked to this specialty"
      }
    }

Notes on Deletion

Foreign key constraints prevent deletion if:

  • Specialists are linked via specialist_specialties junction table
  • Appointment templates reference this specialty via specialty_id

The API returns a 409 conflict error with details about blocking references. Admins must first:

  1. Remove specialist associations, or
  2. Reassign appointment templates to a different specialty

Specialist-Specialty Association

Specialists link to specialties via the specialist_specialties junction table. Use the Specialists API to manage these associations:

  • POST /v1/specialists - Include specialty_ids array in request body
  • PUT /v1/specialists/{id} - Update specialty_ids array

Example:

json
{
  "name": "Dr. Jane Smith",
  "title": "Cardiologist",
  "specialty_ids": [1, 3]
}

See /docs/features/specialists/api.md for complete documentation.