Skip to content

Error Code Reference

All Curaway API errors follow the naming convention {DOMAIN}_{CATEGORY}_{SEQUENCE} where:

  • DOMAIN -- The system area (e.g., AUTH, INTAKE, FHIR)
  • CATEGORY -- The error type (e.g., TOKEN, VALIDATION, NOT_FOUND)
  • SEQUENCE -- A three-digit number (e.g., 001, 002)

Errors are returned inside the standard response envelope errors array.


AUTH -- Authentication & Authorization

Errors related to JWT validation, token lifecycle, and permission checks.

Code HTTP Status Description Common Cause Resolution
AUTH_TOKEN_EXPIRED_001 401 JWT token has expired User session timed out Re-authenticate via Clerk to obtain a fresh token
AUTH_TOKEN_INVALID_001 401 JWT signature verification failed Tampered or malformed token Ensure the token was issued by Clerk and has not been modified
AUTH_UNAUTHORIZED_001 403 User lacks permission for this action Role does not have required scope Check the user's role assignment in Clerk dashboard
AUTH_HEADER_MISSING_001 401 Authorization header not provided Client did not send Bearer token Add Authorization: Bearer <token> header to the request

TENANT -- Tenant Management

Errors related to multi-tenant isolation and tenant configuration.

Code HTTP Status Description Common Cause Resolution
TENANT_NOT_FOUND_001 404 Tenant ID does not exist Typo in X-Tenant-ID header Verify the tenant ID against the tenant registry
TENANT_HEADER_MISSING_001 400 X-Tenant-ID header not provided Client omitted the required header Add X-Tenant-ID header to all API requests
TENANT_MISMATCH_001 403 JWT tenant claim does not match X-Tenant-ID header Cross-tenant access attempt Ensure the JWT was issued for the same tenant specified in the header

INTAKE -- Patient Intake & Case Management

Errors during patient creation, case lifecycle, and intake form processing.

Code HTTP Status Description Common Cause Resolution
INTAKE_VALIDATION_001 422 One or more input fields failed validation Missing required fields or invalid format Check the field property in the error object for the specific field
INTAKE_DUPLICATE_001 409 A patient with matching identifiers already exists Same email + date of birth combination Use the existing patient record or call the merge endpoint
INTAKE_CASE_NOT_FOUND_001 404 The specified case ID does not exist Stale reference or typo Query /cases/ to list available cases for the patient
INTAKE_CASE_CLOSED_001 400 Cannot modify a case that has been closed Attempting to update a finalized case Create a new case if further action is needed

FHIR -- Clinical Data (FHIR R4)

Errors when reading or writing FHIR-formatted clinical resources.

Code HTTP Status Description Common Cause Resolution
FHIR_RESOURCE_INVALID_001 422 FHIR resource failed R4 schema validation Missing required fields or invalid coding system Validate the resource against the FHIR R4 spec before submission
FHIR_RESOURCE_NOT_FOUND_001 404 The requested FHIR resource does not exist Invalid resource ID or resource was deleted Verify the resource ID and type
FHIR_CODING_UNKNOWN_001 422 Unrecognized coding system or code value Typo in ICD-10, SNOMED CT, or LOINC code Cross-reference the code against the relevant coding system registry
FHIR_CASE_ID_REQUIRED_001 400 case_id query parameter is required on GET /api/v1/patients/{patient_id}/fhir (#1194 C7). #1194 C19: an explicit empty value (?case_id=) and whitespace-only values also trigger this code — the parameter must carry a non-empty UUID. Caller invoked the public FHIR list endpoint without scoping to a case (or with an empty value) — previously returned cross-case data, now rejected Pass the case UUID as ?case_id=<uuid>; cross-case FHIR reads via this public endpoint are no longer supported
FHIR_CASE_ID_INVALID_001 400 case_id is present but is not a parseable UUID (#1194 C15). Applies to the public FHIR list endpoint and to the MCP get_patient_clinical_summary + run_match tool handlers. Typo, stale identifier, accidental concatenation, or a non-UUID stub left over from earlier development Pass the case UUID exactly as returned by /api/v1/patients/{patient_id}/cases. Leading/trailing whitespace is stripped; malformed strings are rejected
CASE_NOT_FOUND_001 404 case_id is a valid UUID but no case exists with that ID (#1194 C16). Enforced on the public FHIR list endpoint and the two MCP tool handlers. Stale reference, deleted case, or a UUID that happens to parse but was never minted Verify the case exists via /api/v1/patients/{patient_id}/cases; create a new case if the previous one was closed
CASE_CROSS_TENANT_001 403 case_id exists but belongs to a different tenant than the request's X-Tenant-ID (#1194 C16). Distinct from CASE_NOT_FOUND_001 to surface cross-tenant probes in audit logs. Copy/pasted case_id from another tenant's data; misconfigured X-Tenant-ID header Use case IDs owned by your tenant; double-check the X-Tenant-ID header matches the JWT's tenant claim
CASE_WRONG_PATIENT_001 403 case_id exists in the right tenant but is bound to a different patient than the route's {patient_id} / MCP patient_id argument (#1194 C16/C20). Without this check the response would be a silent empty 200, masking a within-tenant cross-patient leak surface. Caller authorised for patient A passed a case_id belonging to patient B (within the same tenant) List the patient's cases via /api/v1/patients/{patient_id}/cases and pass a matching case_id

MATCH -- Provider Matching

Errors from the AI-powered provider matching engine.

Code HTTP Status Description Common Cause Resolution
MATCH_NO_RESULTS_001 404 No providers matched the given criteria Overly restrictive filters or unsupported procedure/country combination Broaden search criteria or check that providers exist for the procedure
MATCH_EMBEDDING_FAILED_001 500 Failed to generate embedding for the search query Embedding service (Qdrant/OpenAI) is unavailable Retry after a brief delay; check embedding service health
MATCH_GRAPH_QUERY_FAILED_001 500 Neo4j graph traversal failed during matching Neo4j connection timeout or corrupted graph data Check Neo4j connectivity; re-seed graph if data is stale

AGENT -- AI Agent & Chat

Errors from the conversational AI agent that guides patients through intake.

Code HTTP Status Description Common Cause Resolution
AGENT_GUARDRAIL_INPUT_001 400 Input was blocked by the guardrail classifier User message contained prohibited content (PII solicitation, medical advice request) Rephrase the message to stay within the agent's scope
AGENT_GUARDRAIL_OUTPUT_001 500 Agent response was blocked by output validator LLM generated content matching a forbidden output pattern Retry the request; if persistent, review guardrail rules
AGENT_LLM_TIMEOUT_001 504 LLM did not respond within the timeout window High load on the LLM provider or network issues Retry with exponential backoff; check LLM provider status
AGENT_CONTEXT_OVERFLOW_001 400 Conversation history exceeds the model's context window Very long chat session without summarization Start a new conversation or clear older messages

PROVIDER -- Provider Management

Errors related to provider profiles and configuration.

Code HTTP Status Description Common Cause Resolution
PROVIDER_NOT_FOUND_001 404 The specified provider does not exist Invalid provider ID Query /providers/ to list available providers
PROVIDER_INACTIVE_001 400 Provider is currently inactive and cannot accept cases Provider has been deactivated by admin Contact the admin to reactivate the provider or choose a different one
PROVIDER_CAPACITY_001 409 Provider has reached their case capacity limit Too many active cases assigned Wait for existing cases to complete or select an alternative provider
PROVIDER_NOT_FOUND 404 Admin: provider ID not found (cross-tenant admin scope) Invalid or non-existent provider_id Verify the provider_id via GET /api/v1/admin/providers
PROVIDER_SLUG_TAKEN 409 Admin: the requested slug is already used by another provider Duplicate slug on PATCH Choose a unique slug or query existing providers

DOCTOR -- Doctor Profiles

Errors related to doctor records, search, and procedure associations.

Code HTTP Status Description Common Cause Resolution
DOCTOR_NOT_FOUND_001 404 The specified doctor does not exist Invalid doctor ID or doctor was removed Query /doctors/ to list available doctors
DOCTOR_VALIDATION_001 422 Doctor profile data failed validation Missing required fields (name, specialty, license number) Check the field property in the error for specifics
DOCTOR_DUPLICATE_001 409 A doctor with the same license number already exists Attempting to create a duplicate record Use the existing doctor record or update it
DOCTOR_PROCEDURE_EXISTS_001 409 This doctor is already associated with the specified procedure Duplicate procedure association attempt No action needed -- the association already exists

PUBLIC -- Public Storefront

Errors from the public-facing provider storefront (unauthenticated).

Code HTTP Status Description Common Cause Resolution
PUBLIC_PROVIDER_NOT_FOUND_001 404 Provider slug does not exist Invalid or stale provider URL Verify the provider slug against the provider directory
PUBLIC_DOCTOR_NOT_FOUND_002 404 Doctor slug does not exist Invalid or stale doctor URL Verify the doctor slug against the doctor directory
PUBLIC_TREATMENT_NOT_FOUND_003 404 Treatment slug does not exist Invalid or stale treatment URL Verify the treatment slug against available treatments
PUBLIC_DESTINATION_NOT_FOUND_004 404 Country slug does not exist Invalid or stale country URL Verify the country slug against available destinations
PUBLIC_INVALID_FILTER_005 400 Invalid filter parameter value Unrecognized or malformed filter value Check the API docs for valid filter values
PUBLIC_INVALID_SORT_006 400 Invalid sort_by parameter Unrecognized sort field Valid sort fields: name, rating, price
PUBLIC_SEARCH_QUERY_TOO_SHORT_007 400 Search query less than 2 characters User submitted a single character Provide a search query of at least 2 characters
PUBLIC_SEARCH_QUERY_TOO_LONG_008 400 Search query exceeds 200 characters Extremely long search string Shorten the search query to 200 characters or fewer
PUBLIC_RATE_LIMIT_EXCEEDED_009 429 60 requests per minute per IP exceeded Automated scraping or high-frequency polling Wait for the rate limit window to reset (1 minute)
PUBLIC_CACHE_ERROR_010 500 Redis cache failure (non-fatal, falls through to DB) Redis connectivity issue No user action needed -- request will be served from DB
PUBLIC_PAGINATION_INVALID_011 400 page < 1 or per_page > 100 Invalid pagination parameters Use page >= 1 and per_page between 1 and 100
PUBLIC_SLUG_CONFLICT_012 409 Duplicate slug detected Slug already exists for another entity Choose a different slug or modify the existing one

Errors related to consent capture, revocation, and audit trails.

Code HTTP Status Description Common Cause Resolution
CONSENT_NOT_FOUND_001 404 No consent record found for this patient/purpose Consent was never captured or was fully revoked Prompt the patient to provide consent
CONSENT_ALREADY_REVOKED_001 409 Consent has already been revoked Duplicate revocation attempt No action needed -- consent is already revoked
CONSENT_REQUIRED_001 403 This action requires patient consent that has not been granted Attempting a data-sharing action without consent Capture consent from the patient before proceeding

GRAPH -- Knowledge Graph (Neo4j)

Errors from the Neo4j-backed knowledge graph used for provider relationships and procedure hierarchies.

Code HTTP Status Description Common Cause Resolution
GRAPH_CONNECTION_001 503 Cannot connect to Neo4j Neo4j instance is down or URI is misconfigured Verify NEO4J_URI environment variable and Neo4j service health
GRAPH_QUERY_FAILED_001 500 Cypher query execution failed Malformed query or constraint violation Check the Cypher query syntax and data integrity
GRAPH_REBUILD_UNAVAILABLE 503 Neo4j unavailable during graph rebuild Neo4j instance is down when POST /admin/graph/rebuild was called Check Neo4j connectivity; rebuild will proceed without clearing the subgraph and logs a warning
GRAPH_REBUILD_CONFIRM_REQUIRED 400 Rebuilding scope=all requires explicit confirmation POST /admin/graph/rebuild with scope=all was called without confirm: true in the body Add "confirm": true to the request body to acknowledge the full-graph clear

TENANT -- Tenant Authorization

Errors related to cross-tenant admin operations.

Code HTTP Status Description Common Cause Resolution
TENANT_OVERRIDE_REQUIRED 403 Operation requires explicit tenant:override permission Admin accessing cross-tenant data without the override permission Ensure the actor holds tenant:override permission, or filter to their own tenant

CACHE -- Redis Cache

Errors related to Redis caching operations (Upstash Redis).

Code HTTP Status Description Common Cause Resolution
CACHE_CONNECTION_001 503 Cannot connect to Redis Upstash Redis instance is down or URL is misconfigured Verify UPSTASH_REDIS_URL and UPSTASH_REDIS_TOKEN environment variables
CACHE_READ_FAILED_001 500 Failed to read from cache Key expired mid-operation or Redis timeout Non-fatal -- request falls through to database. Check Redis health if persistent
CACHE_WRITE_FAILED_001 500 Failed to write to cache Redis write quota exceeded or connection dropped Check Upstash free tier limits (10K commands/day). Non-fatal for most operations
CACHE_INVALIDATION_001 500 Cache invalidation failed Redis pipeline error during bulk key deletion Stale data may be served until TTL expires. Retry or wait for natural expiry

STREAMING -- SSE Streaming

Errors related to Server-Sent Event (SSE) streaming endpoints.

Code HTTP Status Description Common Cause Resolution
STREAMING_CONNECTION_001 503 Cannot establish SSE stream Redis pub/sub unavailable or endpoint misconfigured Verify Redis connectivity and retry connection
STREAMING_TIMEOUT_001 504 SSE stream exceeded maximum duration (5 minutes) Long-running document processing or stalled pipeline Reconnect and check document processing status via REST API
STREAMING_PUBLISH_FAILED_001 500 Failed to publish event to SSE channel Redis RPUSH failed or channel not found Non-fatal for document processing -- progress events are best-effort
STREAMING_INVALID_CHANNEL_001 400 Invalid or unauthorized SSE channel subscription Patient ID does not belong to the authenticated tenant Verify patient ownership and tenant isolation

NOTIFY -- Notifications (Email, SMS)

Errors from the notification subsystem powered by Resend.

Code HTTP Status Description Common Cause Resolution
NOTIFY_EMAIL_FAILED_001 500 Failed to send email via Resend Invalid recipient address or Resend API error Verify the email address and Resend API key
NOTIFY_TEMPLATE_NOT_FOUND_001 404 Email template ID does not exist Typo in template name or template was deleted Check available templates in the notification config
NOTIFY_RATE_LIMITED_001 429 Too many notifications sent in the current window Burst of notifications triggered by automation Wait for the rate limit window to reset
NOTIFY_RECIPIENT_INVALID_001 422 Recipient address failed validation Malformed email or phone number Validate the recipient address format before sending

VIDEO -- Video Consultation

Errors related to video consultation sessions.

Code HTTP Status Description Common Cause Resolution
VIDEO_SESSION_NOT_FOUND_001 404 The video session does not exist Invalid session ID or session expired Create a new video session
VIDEO_SESSION_EXPIRED_001 410 The video session has expired Session exceeded its TTL Create a new video session
VIDEO_PROVIDER_ERROR_001 502 Upstream video provider returned an error Third-party video service outage Retry after a delay; check provider status page
VIDEO_PARTICIPANT_LIMIT_001 400 Maximum number of participants reached Exceeded the configured participant cap Remove a participant or upgrade the session tier
VIDEO_ROOM_PROVISION_FAILED_001 502 Daily.co room creation failed Daily.co API error or network timeout Retry; if persistent check DAILY_API_KEY and Daily.co service status
VIDEO_HIPAA_TIER_REQUIRED_001 503 Non-HIPAA Daily.co tier in production with patient-bearing tenant DAILY_HIPAA_TIER_ENABLED=false after mso_post_launch flag flipped Upgrade Daily.co plan to HIPAA tier and set DAILY_HIPAA_TIER_ENABLED=true
VIDEO_PARTICIPANT_UNAUTHORIZED_001 403 Caller is not a participant of this session /join caller is not the doctor, patient, or companion on the session Verify the caller's identity matches a participant on the booking

SCHED -- MSO Teleconsultation Scheduling

Errors from the MSO video consultation scheduling subsystem (ADR-0018 Phase 2a).

Code HTTP Status Description Common Cause Resolution
SCHED_SLOT_CONFLICT_001 409 Doctor already has an active session at this slot Overlapping scheduled_for window for the same doctor Choose a different time slot
SCHED_OUT_OF_HOURS_001 422 Slot is outside the doctor's configured working hours scheduled_for outside 09:00–18:00 in tenant timezone Book within working hours (mso_consultation_hours_per_day setting)
SCHED_TOO_LATE_001 422 scheduled_for is less than 15 minutes from now Not enough time to provision the Daily.co room Book at least 15 minutes in advance

MSO -- MSO Payments

Errors from the MSO consultation payment subsystem (Stripe + Razorpay, ADR-0018 Phase 2a).

Code HTTP Status Description Common Cause Resolution
MSO_CHARGE_FAILED_001 402 Payment provider authorization failed; booking aborted Card declined or provider-side error Use a different payment method; provider name included in error envelope
MSO_PROVIDER_UNAVAILABLE_001 503 Configured payments provider is unreachable Stripe or Razorpay API is down Retry with exponential backoff; check provider status page
MSO_PROVIDER_NOT_CONFIGURED_001 503 Selected provider has no credentials in env RAZORPAY_KEY_ID or STRIPE_SECRET_KEY missing Operations team: add credentials to Railway env vars
MSO_WEBHOOK_INVALID_SIGNATURE_001 400 Webhook signature verification failed Invalid Stripe-Signature or X-Razorpay-Signature Verify the webhook secret matches the provider dashboard
MSO_WEBHOOK_DUPLICATE_001 200 Duplicate webhook event detected (idempotency) Provider retried a previously processed event No action needed — event was already processed; 2xx stops provider retries
MSO_SESSION_INVALID_TRANSITION_001 409 Status transition is not valid for the session's current state e.g. cancelling a completed session, ending an unstarted session, rescheduling a cancelled one Check the current session status; only valid transitions are permitted per the state machine
MSO_SESSION_ALREADY_CANCELLED_001 409 Admin force-cancel rejected — session is already cancelled Duplicate admin force-cancel request No action needed — session is already in cancelled state; idempotency guard
MSO_SESSION_NOT_FOUND_404 404 Session does not exist or is inaccessible Invalid session UUID or wrong tenant scope Verify the session_id; super_admin may query cross-tenant

STORAGE -- File Storage (Cloudflare R2)

Errors related to file uploads, presigned URLs, and document processing.

Code HTTP Status Description Common Cause Resolution
STORAGE_UPLOAD_FAILED_001 500 File upload to Cloudflare R2 failed R2 connectivity issue or bucket misconfiguration Check CLOUDFLARE_R2_* environment variables and R2 service status
STORAGE_PRESIGN_FAILED_001 500 Failed to generate presigned upload URL Invalid R2 credentials or expired signing key Rotate R2 credentials and verify configuration
STORAGE_FILE_TOO_LARGE_001 413 File exceeds the maximum allowed size Upload exceeds the 20 MB limit Compress the file or split into smaller documents
STORAGE_FILE_TYPE_001 422 File type is not allowed Uploaded file extension is not in the allow list Allowed types: PDF, JPEG, PNG, DICOM. Convert the file to an accepted format
DOCUMENT_NOT_FOUND_001 404 Document does not exist (or caller lacks access) Wrong document_id, tenant mismatch, or non-owner caller Verify the document_id and that the authenticated user owns the patient that owns the document. Returned (instead of 403) to avoid leaking document existence.
DOCUMENT_NOT_RETRIABLE_001 409 Document analysis cannot be retried in its current state Document is already completed or actively in_progress / processing No action needed — the document is either done or already running. Check the document's analysis_status field.
DOCUMENT_RETRY_LIMIT_001 429 Manual retry budget exhausted (max 3 per document) Patient retried the document more than MAX_MANUAL_RETRIES times Contact support if the document still needs analysis — they can either bypass the rate limit or escalate to a clinical coordinator.
DOCUMENT_RETRY_INTERNAL_001 500 Internal error while initiating a document retry Unexpected DB / dispatch error inside manual_retry (not the 409 / 429 contract paths). Surfaced with the standard envelope so FE A23 can render its error pill without special-casing raw 500s. Retry the request after a moment. If it persists, check the application logs for the underlying exception (Telegram WARNING document_retry_db is also fired).

SYS -- System & Infrastructure

General system-level errors not tied to a specific domain.

Code HTTP Status Description Common Cause Resolution
SYS_INTERNAL_001 500 Unhandled internal server error Uncaught exception in application code Check server logs for the stack trace; report as a bug if persistent
SYS_DATABASE_001 503 PostgreSQL connection failed Database is down or connection pool exhausted Check DATABASE_URL configuration and database health
SYS_RATE_LIMITED_001 429 Global rate limit exceeded Too many requests from this tenant Wait for the rate limit window to reset; consider upgrading tier
SYS_MAINTENANCE_001 503 System is undergoing scheduled maintenance Deployment or migration in progress Retry after the maintenance window (check status page)
SYS_TIMEOUT_001 504 Request processing exceeded the timeout limit Complex query or downstream service latency Retry with a simpler request; if persistent, contact support
SYS_CONFIG_MISSING 500 Required configuration value not set on the deployment e.g. CLERK_SECRET_KEY absent at runtime Ops: set the env var and redeploy. Not retryable.
FEATURE_DISABLED 503 Endpoint disabled by feature flag Caller is gated by a Flagsmith flag that is off for this identity/tenant Wait for rollout, request identity-override, or enable the flag
AUDIT_WRITE_FAILED 500 Database commit failed after a successful state change; the operation was rolled back Audit Event INSERT raised SQLAlchemyError; transactional safety preserved Retry is safe (no partial state); investigate DB if persistent

FACILITATOR -- Facilitator Management

Errors related to facilitator profiles, attribution, and admin CRUD operations.

Code HTTP Status Description Common Cause Resolution
FACILITATOR_NOT_FOUND 404 Facilitator ID does not exist Invalid facilitator_id in URL or in patient registration Verify the facilitator exists via GET /api/v1/admin/facilitators
FACILITATOR_INACTIVE 422 Facilitator exists but is inactive (is_active=false) Attempting to assign an inactive facilitator during patient registration Contact admin to reactivate the facilitator, or choose an active one
FACILITATOR_DUPLICATE_EMAIL 409 Email address already in use by another active facilitator Creating or updating facilitator with a duplicate email Choose a different email or use the existing facilitator record
FACILITATOR_HAS_ATTRIBUTED_RECORDS 409 Cannot delete facilitator — they have attributed patients/cases Attempting DELETE without ?force=true when attributions exist Bulk-reassign attributed records to another facilitator first, or use ?force=true (requires admin:force permission, super_admin only)

RBAC -- Role-Based Access Control

Errors emitted by the admin Users + Tenants endpoints (/api/v1/admin/users/*, /api/v1/admin/tenants/*) and any other RBAC-gated route.

Code HTTP Status Description Common Cause Resolution
RBAC_INVALID_ROLE 422 Body role_code not present in the roles catalog Typo, removed role, or new role not yet seeded Check the role code via /api/v1/admin/roles (when implemented)
RBAC_INVALID_FILTER 400 Query-string filter is malformed or refers to an unknown value e.g. ?role_code=… for a role that doesn't exist; path user_id doesn't match Clerk format Sanitize the filter and retry
RBAC_LAST_ADMIN_GUARD 409 Revoking would leave 0 active holders of role_code on tenant_id Caller is the last admin and didn't pass force=true Pass ?force=true (requires admin:force permission, super_admin only)
RBAC_FORCE_DENIED 403 force=true requested but caller lacks admin:force permission Only super_admin holds admin:force; platform_admin does not Have a super_admin perform the operation
RBAC_DATA_INTEGRITY 500 A user_roles row references a role_id that no longer exists Data drift; role was hard-deleted while assignments still pointed at it Investigate orphaned user_roles rows; restore the role row or hard-revoke the orphans
AUTH_NO_ACTOR 401 Caller identity (request.state.user_id) missing on an authenticated route RBACMiddleware did not populate user_id (auth header missing/invalid) Send a valid Authorization: Bearer <Clerk JWT>

TENANT -- Admin Tenants page

Errors emitted by /api/v1/admin/tenants/* and /api/v1/admin/org-mappings/* (slice 2b).

Code HTTP Status Description Common Cause Resolution
TENANT_NOT_FOUND_001 404 tenant_id not in the tenants table Typo, deleted tenant Verify against GET /api/v1/admin/tenants
TENANT_ALREADY_EXISTS 409 Conflicting id or slug on create that doesn't match the existing row Idempotent create with mismatched fields, or duplicate slug across tenants Use the existing row, or pick a different id/slug
TENANT_INVALID_INPUT 422 Tenant id/slug/type/country_code shape rejected before DB write Bad pattern (id must match ^[a-z][a-z0-9-]{1,35}$); country_code must be ISO 3166-1 alpha-3; tenant_type must be one of coordinator/mso/admin/facilitator Sanitize input
TENANT_ACTIVE_CASES_GUARD 409 Deactivating a tenant with active (non-closed) cases Tenant has open cases Pass force=true (requires admin:force permission)
TENANT_PROTECTED 409 Tenant is in PROTECTED_TENANT_IDS (today: tenant-curaway-admin) and cannot be deactivated by any actor — admin:force does NOT override Operator tried to deactivate tenant-curaway-admin This is by design (spec decision 9). The protected tenant holds super_admin role assignments; deactivating would lock everyone out.
ORG_MAPPING_NOT_FOUND 404 tenant_org_mappings row missing Typo, already deleted Verify via GET /api/v1/admin/org-mappings
ORG_MAPPING_DUPLICATE 409 clerk_org_id already mapped to a tenant Duplicate POST Use existing mapping or DELETE the old one first

CLERK -- Clerk integration

Errors raised by code that calls Clerk's admin API (e.g. email→user_id lookup).

Code HTTP Status Description Common Cause Resolution
CLERK_UNAVAILABLE 503 Clerk API call failed (network error or 5xx) Transient outage or rate limit Retry with backoff

SYS_CONFIG_MISSING (above) covers the related "CLERK_SECRET_KEY not set" case; that is intentionally a separate code so retry/back-off does not mask config drift.


MSO Doctor Admin

Errors from the admin MSO doctor credentialing endpoints (/api/v1/admin/mso/doctors).

Code HTTP Status Description Common Cause Resolution
MSO_DOCTOR_NOT_FOUND_404 404 Doctor not found in the specified tenant Invalid doctor_id or cross-tenant access Verify the doctor_id and X-Tenant-ID header
MSO_DOCTOR_ALREADY_VERIFIED_409 409 Doctor is already in verified status Duplicate verify call Check current credentialing_status before calling /verify
MSO_DOCTOR_NOT_SUSPENDED_409 409 Doctor is not in suspended status; cannot reinstate Calling /reinstate on a doctor that is not suspended Check current credentialing_status; only suspended doctors can be reinstated

COMMERCE -- Commerce IntentService

Errors raised by app/services/commerce/intents.py (IntentService). Per spec §10.3 / §3.4 / §12 the provider HTTP call lives OUTSIDE the DB transaction and idempotency-key replay collapses duplicate create_intent calls into the same row without re-calling the provider.

Code HTTP Status Description Common Cause Resolution
COMMERCE_INTENT_PROVIDER_DOWN_001 502 Stripe / Razorpay returned an error or the SDK raised Provider outage, transient 5xx, invalid provider response Retry with the same (source_domain, source_id) — internal idempotency key replay protects against double-charge
COMMERCE_INTENT_INVALID_AMOUNT_002 422 amount_minor_units <= 0, > $100M cap, or currency not a 3-letter ISO 4217 code Caller passed bad inputs Validate inputs at the router layer before invoking IntentService
COMMERCE_INTENT_COMMISSION_UNRESOLVED_003 422 No commission schedule matched (tenant / vendor / source_domain / currency) — fail-closed per spec §8 Missing platform-default schedule or expired schedule Seed the default commission schedule for the tenant; check effective_from/effective_to
COMMERCE_INTENT_NOT_FOUND_004 404 Intent not found within the calling tenant Cross-tenant access attempt OR stale intent_id Verify intent_id + X-Tenant-ID. Cross-tenant returns 404 (never 403) to avoid leaking existence
COMMERCE_INTENT_INVALID_STATE_005 409 capture / cancel called on an intent in an incompatible status e.g. cancelling an already-captured intent Check intent.status; captured intents must be refunded via RefundService (PR-5b), not cancelled

COMMERCE -- Commerce RefundService

Errors raised by app/services/commerce/refunds.py (RefundService). Per spec §10.3 the refund flow uses the strict two-transaction pattern: a pending row is inserted and committed BEFORE the provider HTTP call, then a second transaction commits success (status='succeeded' + ledger pair) or failure (status='failed' + error_log). Idempotency replay collapses duplicate calls onto the same row without re-calling the provider.

Code HTTP Status Description Common Cause Resolution
COMMERCE_REFUND_PROVIDER_DOWN_001 502 Stripe / Razorpay refund call failed or the SDK raised Provider outage, transient 5xx, network error during the up-to-30s refund call Pending row stays at status='failed' and an error_log row is written. Retry with the SAME caller_idempotency_key — the internal idempotency key replay returns the same row without double-charging
COMMERCE_REFUND_OVER_REFUND_002 422 amount_minor_units exceeds the remaining refundable balance (intent.amount - sum(prior_refunds)) Caller passed an amount larger than the captured-but-not-yet-refunded portion Compute remaining balance client-side or pass the full remaining amount. Concurrent in-flight refunds reserve balance — wait for them to settle
COMMERCE_REFUND_INTENT_NOT_CAPTURED_003 409 Payment intent is not in a refundable status (must be captured, settled, or partially_refunded) Refund attempted on a pending / authorized / cancelled / failed intent, or on an intent that never reached the provider Use CancelIntent for unfinalized intents; check intent.status before calling RefundService
COMMERCE_REFUND_INTENT_NOT_FOUND_004 404 Payment intent not found within the calling tenant Cross-tenant access attempt OR stale intent_id Verify intent_id + X-Tenant-ID. Cross-tenant returns 404 (never 403) to avoid leaking existence
COMMERCE_REFUND_INVALID_AMOUNT_005 422 amount_minor_units <= 0 or not an integer Caller passed bad inputs Validate inputs at the router layer before invoking RefundService

COMMERCE -- Commerce Webhook Router

Errors raised by app/routers/commerce_webhooks.py. Spec §6. The endpoint at POST /api/v1/commerce/webhooks/{provider} is gated by feature flag commerce_webhooks_enabled (default OFF); legacy /api/v1/webhooks/{stripe,razorpay} remains authoritative until cutover.

Code HTTP Status Description Common Cause Resolution
COMMERCE_WEBHOOK_DISABLED_001 503 Feature flag commerce_webhooks_enabled is off Pre-cutover; legacy webhook routes are still authoritative Provider will retry on its exponential schedule; flip the flag in both Production and Development to cut over
COMMERCE_WEBHOOK_INVALID_SIGNATURE_001 400 Signature header missing or HMAC mismatch Wrong webhook secret, forge attempt, or tampered body Verify the secret matches the provider dashboard; review alerting if rate is non-zero
COMMERCE_WEBHOOK_EXPIRED_SIGNATURE_001 400 Signed timestamp older than 300s (Stripe) or 7-day retention (Razorpay) Replay attack OR severe clock skew between provider + server Check NTP sync on Railway containers; investigate replay if rate is non-zero
COMMERCE_WEBHOOK_PROVIDER_ERROR_001 503 Provider adapter raised a non-signature error (SDK / configuration) Missing env var, SDK version mismatch, malformed payload Surface in logs; provider will retry. No double-process risk — dedup row not yet inserted
COMMERCE_WEBHOOK_DUPLICATE_001 200 (provider, provider_event_id) already present in webhook_events Provider retried a previously processed event No action needed; 200 stops provider retries
COMMERCE_WEBHOOK_NOT_IN_COMMERCE_001 200 Event references a payment_intent / refund that does not exist in commerce tables Legacy payment_intent (not migrated yet) or unknown id Legacy /api/v1/webhooks/{stripe,razorpay} handles it; commerce 200-acks per §6.3
COMMERCE_WEBHOOK_HANDLER_FAILED_001 500 Canonical event dispatcher raised after signature passed DB outage, repository error, downstream service down A commerce_webhook_dlq row is written + provider will retry. After 5 retries the DLQ row is the only remaining trace — manual review via §11.8

COMMERCE -- Receipts

Errors raised by app/services/commerce/receipts.py:ReceiptService. Spec §4.7, §11. Receipts are generated for captured payment intents, stored in R2 with content-addressable keys, and emit a commerce_receipt_access_log row on every presigned URL issuance. Presigned URLs use a 15-minute TTL per spec §4.7.

Code HTTP Status Description Common Cause Resolution
COMMERCE_RECEIPT_INTENT_NOT_FOUND_001 404 Payment intent not found within the calling tenant Cross-tenant access attempt OR stale intent_id Verify intent_id + X-Tenant-ID. Cross-tenant returns 404 (never 403) to avoid leaking existence
COMMERCE_RECEIPT_INTENT_NOT_CAPTURED_002 409 Payment intent is not in a receipt-eligible status (must be captured, settled, partially_refunded, or refunded) Receipt requested on a pending / authorized / cancelled / failed intent Capture the intent first via IntentService; receipts are only meaningful after settlement
COMMERCE_RECEIPT_STORAGE_UNAVAILABLE_003 503 R2 receipt bucket not configured or upload failed Missing R2_* env vars, R2 outage, or bucket policy denial Verify R2 credentials + bucket policy; transient errors are retry-safe (content-addressable keys mean retries don't duplicate objects)
COMMERCE_RECEIPT_NOT_FOUND_004 404 Receipt not found within the calling tenant Cross-tenant access attempt OR stale receipt_id Verify receipt_id + X-Tenant-ID. Cross-tenant returns 404 (never 403)

Commerce admin (PR-8)

Code HTTP Status Description Common Cause Resolution
COMMERCE_ADMIN_DISABLED_001 503 Feature flag commerce_admin_ui_enabled is off Pre-cutover; admin commerce surface is dark Flip commerce_admin_ui_enabled to true in BOTH Production and Development envs once the FE admin commerce screens ship
COMMERCE_ADMIN_INTENT_NOT_FOUND_001 404 Intent not found within the calling tenant (admin read) Cross-tenant access attempt OR stale intent_id Verify intent_id + X-Tenant-ID. Cross-tenant returns 404 (never 403) to avoid leaking existence
COMMERCE_ADMIN_SCHEDULE_NOT_FOUND_001 404 Commission schedule not found within the calling tenant Cross-tenant access attempt OR stale schedule_id Verify schedule_id + X-Tenant-ID
COMMERCE_ADMIN_RECEIPT_NOT_FOUND_001 404 Receipt not found within the calling tenant (admin read) Cross-tenant access attempt OR stale receipt_id Verify receipt_id + X-Tenant-ID. Admin reads also write an audit row to commerce_receipt_access_log
COMMERCE_ADMIN_INVALID_DATE_001 422 from_date / to_date query param is not an ISO-8601 timestamp Caller passed a non-ISO date string Use YYYY-MM-DDTHH:MM:SSZ (or +00:00 suffix) for the date filters on /admin/commerce/* reads
COMMERCE_WEBHOOK_LOG_NOT_FOUND_001 404 Webhook log entry not found within the calling tenant Cross-tenant access attempt OR stale event_log_id Verify event_log_id + X-Tenant-ID. Cross-tenant returns 404 (never 403)
COMMERCE_DLQ_NOT_FOUND_001 404 DLQ entry not found (global table) Stale dlq_id or entry already purged Verify dlq_id via GET /admin/commerce/dlq list
COMMERCE_DLQ_ALREADY_REPLAYED_002 409 DLQ replay attempted with an idempotency key that was already used Double-click or retry of a completed replay The first replay succeeded; check the webhook log or commerce tables to confirm the event was processed

Summary

Domain Code Count Section
AUTH 5 Authentication
RBAC 5 RBAC
FACILITATOR 4 Facilitator
CLERK 1 Clerk
TENANT 3 Tenant
INTAKE 4 Intake
FHIR 3 FHIR
MATCH 3 Matching
AGENT 4 Agent
PROVIDER 3 Provider
DOCTOR 4 Doctor
PUBLIC 12 Public Storefront
CONSENT 3 Consent
GRAPH 2 Graph
CACHE 4 Cache
STREAMING 4 Streaming
NOTIFY 4 Notifications
VIDEO 4 Video
STORAGE 4 Storage
SYS 5 System
COMMERCE 5 Commerce IntentService
COMMERCE 5 Commerce RefundService
Total 80

Procedure (admin onboarding)

Source: docs/specs/procedure-onboarding-admin-ui.md §4.5 / PR-1. All codes returned by /api/v1/admin/procedures/*.

Code HTTP Meaning
PROCEDURE_ADMIN_DISABLED_001 503 Feature flag procedure_admin_ui_enabled is off
PROCEDURE_NOT_FOUND_404 404 procedure_code not in the global catalog
PROCEDURE_CODE_DUPLICATE_409 409 Procedure code already exists
PROCEDURE_HAS_ACTIVE_CASES_409 409 Deactivate blocked by active cases (override with force=true + admin:force)
PROCEDURE_INVALID_INPUT_422 422 Validation failure (code shape, recovery days, etc.)
PROVIDER_PROCEDURE_NOT_FOUND_404 404 (provider, procedure) cell missing
PROVIDER_PROCEDURE_INVALID_COST_RANGE_422 422 min > avg or avg > max on cost cents

Capability (admin surface)

Source: docs/specs/procedure-onboarding-admin-ui.md §9.2 / PR-2. All codes returned by /api/v1/admin/capabilities, /api/v1/admin/providers/*/capabilities, /api/v1/admin/procedures/*/capability-requirements.

Code HTTP Meaning
CAPABILITY_NOT_FOUND_404 404 Provider capability record not found
CAPABILITY_DUPLICATE_409 409 Capability already assigned to this provider
CAPABILITY_INVALID_INPUT_422 422 Invalid capability_code format or negative weight
CAP_REQ_NOT_FOUND_404 404 Procedure capability requirement not found
CAP_REQ_DUPLICATE_409 409 Capability requirement already exists for this procedure
CAPABILITY_RECOMPUTE_FAILED_500 500 Readiness recompute failed (see logs)

Bulk Import (PR-3)

Source: docs/specs/procedure-onboarding-admin-ui.md §9.2 / PR-3. All codes returned by /api/v1/admin/procedures/bulk-import/*.

Code HTTP Meaning
BULK_IMPORT_PARSE_ERROR_422 422 YAML or CSV could not be parsed (syntax error)
PROCEDURE_BULK_IMPORT_VALIDATION_422 422 Payload has per-row validation errors; see details[] for row-level info
BULK_IMPORT_ROW_FAILED_500 500 A row failed during DB apply; entire batch rolled back

Capability Vocabulary (admin)

Source: docs/specs/procedure-onboarding-admin-ui.md §9.2 PR-4. All codes returned by /api/v1/admin/capability-vocabulary/*.

Code HTTP Meaning Cause Resolution
PROCEDURE_ADMIN_DISABLED_001 503 Feature flag procedure_admin_ui_enabled is off Flag not enabled in Flagsmith Enable the flag for the environment
CAP_VOCAB_NOT_FOUND_404 404 capability_code not in the vocabulary catalog Stale code or typo Verify code via GET /admin/capability-vocabulary list
CAP_VOCAB_DUPLICATE_409 409 Capability code already exists in the catalog Double-create or race The code is already present; use PATCH to update
CAP_VOCAB_REFERENCED_409 409 Cannot delete — code is still referenced in provider or procedure assignments Assignments exist for this code Remove all provider_capabilities + procedure_capability_requirements rows first
CAP_VOCAB_INVALID_CODE_422 422 capability_code fails pattern ^[A-Z0-9_\-]{2,64}$ Lowercase, spaces, or special chars Use uppercase alphanumeric + underscore/hyphen only

Recovery Milestones (admin)

Source: docs/specs/admin-ui-gap-audit-960-data.md § Chain D / PR-D1. All codes returned by /api/v1/admin/procedures/{code}/recovery-milestones.

Code HTTP Meaning
PROCEDURE_ADMIN_DISABLED_001 503 Feature flag procedure_admin_ui_enabled is off
RECOVERY_NEEDS_NOT_FOUND_404 404 procedure_code has no row in config/recovery_needs/seed.yaml

Recompute Jobs (PR-6)

Source: docs/specs/procedure-onboarding-admin-ui.md §9.2 / PR-6. All codes returned by /api/v1/admin/procedures/recompute-readiness/*.

Code HTTP Meaning
RECOMPUTE_JOB_NOT_FOUND_404 404 No recompute job with the given job_id

Transport Vendor Admin

Source: docs/specs/transport-vendor-admin-feature.md §5.5. All codes returned by POST/PATCH/DELETE /api/v1/transport/vendors/*.

Code HTTP Meaning
TRANSPORT_VENDOR_SLUG_EXISTS 409 Create request slug collides with an existing providers.slug in the tenant.
TRANSPORT_VENDOR_NOT_FOUND 404 Update / delete target vendor not in tenant or transport_provider_profiles row missing.
TRANSPORT_VENDOR_ALREADY_INACTIVE 409 Soft-delete attempted on a vendor with providers.is_active = false.
TRANSPORT_VENDOR_ALREADY_ACTIVE 409 Reactivate attempted on a vendor with providers.is_active = true.
TRANSPORT_VENDOR_VEHICLE_CLASS_MISMATCH 422 vendor_class=ground_accessible requires wheelchair_accessible in vehicle_types; vendor_class=medical requires medical_van or ambulance_basic.
TRANSPORT_VENDOR_CURRENCY_COUNTRY_MISMATCH 422 pricing_currency doesn't align with service_country per the ISO-4217↔ISO-3 alignment table (spec §7 rule 6).
TRANSPORT_VENDOR_PRICING_INCOMPLETE 422 pricing_model=per_km without per_km_minor_units, or pricing_model=flat_per_trip without base_fare_minor_units, or pricing_model=hybrid without both.
TRANSPORT_VENDOR_AIR_RESERVED 422 vendor_class=air rejected — reserved for Phase 7b.