> For the complete documentation index, see [llms.txt](https://docs.mymedtrics.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.mymedtrics.com/api-reference/connect-api/clinical-schedules.md).

# Clinical Schedules

Clinical schedule definitions per program. Aggregates config flags, M2M user/site/role access lists, and schedule metadata. Legacy analog is /api-banner/program-schedules/.

## List visible clinical schedules.

> Returns cursor-paged clinical schedule definitions scoped to the authenticated API app tenant. Each entry represents a program schedule with config flags, access user/site/role lists, and legacy bridge identifiers. Legacy analog is /api-banner/program-schedules/.

```json
{"openapi":"3.1.0","info":{"title":"Medtrics Connect API","version":"2026-10-27"},"tags":[{"name":"Clinical Schedules","description":"Clinical schedule definitions per program. Aggregates config flags, M2M user/site/role access lists, and schedule metadata. Legacy analog is /api-banner/program-schedules/."}],"servers":[{"url":"https://sandbox.mymedtrics.com","description":"Client sandbox — shared demo tenant, X-Medtrics-Sandbox-Key or a sandbox API app"},{"url":"https://{client}.mymedtrics.com","description":"Your Medtrics instance","variables":{"client":{"default":"<client-name>","description":"The tenant subdomain Medtrics assigns to your institution"}}}],"security":[{"BearerToken":[]},{"SandboxApiKey":[]}],"components":{"securitySchemes":{"BearerToken":{"type":"http","scheme":"bearer"},"SandboxApiKey":{"type":"apiKey","in":"header","name":"X-Medtrics-Sandbox-Key"}},"parameters":{"ApiDateHeader":{"name":"Medtrics-API-Date","in":"header","required":true,"description":"API contract date. Use 2026-10-27 for this version. Contract dates select request behavior: rotation creates require explicit placement references from 2026-10-27; earlier accepted dates retain compatibility handling. Unknown dates return HTTP 400 with error.code unsupported_api_date.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},"Limit":{"name":"limit","in":"query","required":false,"description":"Maximum number of records to return per page. The server may return fewer rows than requested. Values above the maximum (100) are silently clamped to 100. Values below the minimum (1) are silently clamped to 1. The effective value after clamping is always returned in pagination.limit so callers can detect that they hit the gateway ceiling. Per-client max_page_size overrides are documented as a tenant-configurable lower cap that never raises the public ceiling.\n","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},"Cursor":{"name":"cursor","in":"query","required":false,"schema":{"type":"string"}},"ProgramIdQuery":{"name":"program_id","in":"query","required":false,"schema":{"type":"string"},"description":"Filter by program public ID (prog_ prefix). Returns only records belonging to this program."},"AcademicPeriodIdQuery":{"name":"academic_period_id","in":"query","required":false,"schema":{"type":"string"},"description":"Filter by academic period public ID (ap_ prefix)."},"SiteIdQuery":{"name":"site_id","in":"query","required":false,"schema":{"type":"string"},"description":"Filter by site public ID (site_ prefix). Returns only schedules that include this site in their site list."},"LegacyIdQuery":{"name":"legacy_id","in":"query","required":false,"schema":{"type":"string"},"description":"Filter by legacy source ID from etl_id_registry. Useful for bridge lookups."}},"responses":{"ClinicalScheduleListResponse":{"description":"Cursor-paged clinical schedule records.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data","pagination"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ClinicalSchedule"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"BadRequest":{"description":"Invalid request parameters.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"Unauthorized":{"description":"Missing or invalid credentials.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"Forbidden":{"description":"Credential lacks tenant visibility or required scope.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"RateLimited":{"description":"Request or API app rate limit exceeded.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"InternalError":{"description":"Unexpected server error.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"headers":{"MedtricsRequestId":{"description":"Correlates all gateway logs for the request.","schema":{"type":"string"}},"MedtricsApiDate":{"description":"API contract date applied to the request.","schema":{"type":"string"}},"RateLimitLimit":{"description":"Request limit for the current window.","schema":{"type":"string"}},"RateLimitRemaining":{"description":"Remaining requests in the current window.","schema":{"type":"string"}},"RateLimitReset":{"description":"Unix seconds when the current rate-limit window resets.","schema":{"type":"string"}}},"schemas":{"ClinicalSchedule":{"type":"object","description":"A clinical schedule definition scoped to the authenticated API app tenant. Owns M2M access lists for admin users, participant users, view-only users, sites, and role-based view access. Legacy analog is /api-banner/program-schedules/.","required":["id","program_id","academic_period_id","name","schedule_details","show_rotation_tab_default","restrict_rotation_view","show_block_naming","enable_rotation_block_directors","admin_user_ids","participant_user_ids","view_only_user_ids","site_ids","view_role_names","created_at","updated_at"],"properties":{"id":{"type":"string","description":"Public ID of the clinical schedule (csched_ prefix). Backed by clinical_schedules.id."},"program_id":{"type":"string","description":"Public ID of the owning program (prog_ prefix)."},"academic_period_id":{"type":"string","description":"Public ID of the linked academic period (ap_ prefix)."},"name":{"type":"string"},"description":{"type":["string","null"],"description":"Free-text description of the schedule."},"schedule_details":{"type":"boolean","description":"Corresponds to schedule_config.scheduleDetails. Default false."},"show_rotation_tab_default":{"type":"boolean","description":"Corresponds to schedule_config.showRotationTabDefault. Default false."},"restrict_rotation_view":{"type":"boolean","description":"Corresponds to schedule_config.restrictRotationView. Default false."},"show_block_naming":{"type":"boolean","description":"Corresponds to schedule_config.showBlockNaming. Default true."},"enable_rotation_block_directors":{"type":"boolean","description":"Corresponds to schedule_config.enableRotationBlockDirectors. Default false."},"admin_user_ids":{"type":"array","items":{"type":"string"},"description":"Public IDs (user_ prefix) of schedule administrators from clinical_schedule_admins."},"participant_user_ids":{"type":"array","items":{"type":"string"},"description":"Public IDs (user_ prefix) of users with access_type='participant' from clinical_schedule_users."},"view_only_user_ids":{"type":"array","items":{"type":"string"},"description":"Public IDs (user_ prefix) of users with access_type='view_only' from clinical_schedule_users."},"site_ids":{"type":"array","items":{"type":"string"},"description":"Public IDs (site_ prefix) of associated sites from clinical_schedule_sites."},"view_role_names":{"type":"array","items":{"type":"string"},"description":"Role names with access_type='view' from clinical_schedule_roles (name strings, no IDs)."},"updated_by_id":{"type":["string","null"],"description":"Public ID (user_ prefix) of the last user to update this schedule. Null when not set."},"legacy_id":{"type":["string","null"],"description":"Legacy SIS integer ID (as string) from etl_id_registry (source_entity_type='clinical_schedule'). Null until ETL registers mappings."},"program_legacy_id":{"type":["string","null"],"description":"Legacy SIS integer ID (as string) of the owning program. Bridge own-id source for the program FK."},"academic_period_legacy_id":{"type":["string","null"],"description":"Legacy SIS integer ID (as string) of the academic period. Bridge own-id source for the academic_year FK."},"admin_user_legacy_ids":{"type":"array","items":{"type":"string"},"description":"Legacy SIS integer IDs (as strings) for admin users. Null entries omitted; array empty until ETL registers user mappings."},"participant_user_legacy_ids":{"type":"array","items":{"type":"string"},"description":"Legacy SIS integer IDs (as strings) for participant users."},"view_only_user_legacy_ids":{"type":"array","items":{"type":"string"},"description":"Legacy SIS integer IDs (as strings) for view-only users."},"site_legacy_ids":{"type":"array","items":{"type":"string"},"description":"Legacy SIS integer IDs (as strings) for associated sites."},"updated_by_legacy_id":{"type":["string","null"],"description":"Legacy SIS integer ID (as string) of the last updater. Null when updated_by_id is null or ETL not yet registered."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"Pagination":{"type":"object","description":"Cursor pagination envelope. Exposes only limit and next_cursor. Count fields (approximate_total, total_count_exact, or any equivalent) are intentionally absent and reserved for future Medtrics-API-Date additions once a concrete partner use case justifies a count strategy per route. pagination.limit is the effective page size after server-side clamp, not the value the client requested.","required":["limit","next_cursor"],"properties":{"limit":{"type":"integer","description":"Effective page size after server-side clamping. Compare to your requested limit to detect ceiling enforcement."},"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor. Pass as the cursor query parameter on the next request. null when no more pages exist."}}},"ErrorEnvelope":{"type":"object","required":["error"],"description":"Structured error envelope. error.code is the machine-readable discriminant. Typed detail schemas are provided for codes where structured data aids client remediation. Use the discriminator.mapping to select the correct variant schema; for all other codes see GenericApiError.\n","properties":{"error":{"anyOf":[{"$ref":"#/components/schemas/InvalidStatusError"},{"$ref":"#/components/schemas/InvalidWebhookEventsError"},{"$ref":"#/components/schemas/RateLimitedError"},{"$ref":"#/components/schemas/CursorResourceMismatchError"},{"$ref":"#/components/schemas/InvalidIdError"},{"$ref":"#/components/schemas/UnsupportedApiDateError"},{"$ref":"#/components/schemas/InactiveClientError"},{"$ref":"#/components/schemas/InvalidWebhookUrlError"},{"$ref":"#/components/schemas/GenericApiError"}],"discriminator":{"propertyName":"code","mapping":{"invalid_status":"#/components/schemas/InvalidStatusError","invalid_webhook_events":"#/components/schemas/InvalidWebhookEventsError","rate_limited":"#/components/schemas/RateLimitedError","cursor_resource_mismatch":"#/components/schemas/CursorResourceMismatchError","invalid_id":"#/components/schemas/InvalidIdError","unsupported_api_date":"#/components/schemas/UnsupportedApiDateError","inactive_client":"#/components/schemas/InactiveClientError","invalid_webhook_url":"#/components/schemas/InvalidWebhookUrlError"}}}}},"InvalidStatusError":{"description":"Returned when a status query parameter value is outside the allowed enum. HTTP 400. details.field identifies the parameter; details.allowed lists every accepted value.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"invalid_status"},"details":{"type":"object","required":["field","allowed"],"additionalProperties":false,"description":"Structured remediation data for invalid_status.","properties":{"field":{"type":"string","description":"Query parameter name that received the invalid value (e.g. enrollment_status, status).\n"},"allowed":{"type":"array","items":{"type":"string"},"description":"Full list of accepted values for the parameter."}}}}}]},"ApiErrorBase":{"type":"object","required":["code","message","request_id"],"description":"Common fields shared by every Connect API error response.","properties":{"code":{"type":"string","description":"Machine-readable error code."},"message":{"type":"string","description":"Human-readable error description."},"request_id":{"type":"string","description":"Request correlation ID. Include this value when reporting issues to support alongside the Medtrics-Request-ID response header.\n"}}},"InvalidWebhookEventsError":{"description":"Returned when event_types contains values not in the approved Connect API event list, or when event_types is empty. HTTP 400. details.unsupported_events lists the rejected entries (empty array when event_types was missing or entirely empty).\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"invalid_webhook_events"},"details":{"type":"object","required":["unsupported_events"],"additionalProperties":false,"description":"Structured remediation data for invalid_webhook_events.","properties":{"unsupported_events":{"type":"array","items":{"type":"string"},"description":"Event type strings that are not approved Connect API event names. Empty array indicates event_types was missing or contained only invalid entries.\n"}}}}}]},"RateLimitedError":{"description":"Returned when the request or API app rate limit is exceeded. HTTP 429. details.retry_after_seconds indicates the wait duration before retrying.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"rate_limited"},"details":{"type":"object","required":["retry_after_seconds"],"additionalProperties":false,"description":"Structured remediation data for rate_limited.","properties":{"retry_after_seconds":{"type":"integer","description":"Seconds to wait before retrying. Mirrors the Retry-After response header value.\n"}}}}}]},"CursorResourceMismatchError":{"description":"Returned when the cursor was issued for a different resource type than the endpoint being called. HTTP 400. details.expected_resource is the resource this endpoint serves; details.cursor_resource is what the cursor encodes.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"cursor_resource_mismatch"},"details":{"type":"object","required":["expected_resource","cursor_resource"],"additionalProperties":false,"description":"Structured remediation data for cursor_resource_mismatch.","properties":{"expected_resource":{"type":"string","description":"Resource type this endpoint serves (e.g. programs, learners)."},"cursor_resource":{"type":"string","description":"Resource type encoded in the submitted cursor."}}}}}]},"InvalidIdError":{"description":"Returned when a path parameter public ID does not match the expected prefix for the resource. HTTP 400. details.field is the parameter name; details.expected_prefix is the required ID prefix.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"invalid_id"},"details":{"type":"object","required":["field","expected_prefix"],"additionalProperties":false,"description":"Structured remediation data for invalid_id.","properties":{"field":{"type":"string","description":"Path or query parameter name that received the invalid ID."},"expected_prefix":{"type":"string","description":"Required ID prefix for this parameter."}}}}}]},"UnsupportedApiDateError":{"description":"Returned when the Medtrics-API-Date header is missing, malformed (not YYYY-MM-DD), or not a supported contract date. HTTP 400. details.supported_dates lists all accepted values. details.reason is one of \"missing\", \"malformed\", or \"not_supported\".\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"unsupported_api_date"},"details":{"type":"object","required":["reason","supported_dates"],"additionalProperties":false,"description":"Structured remediation data for unsupported_api_date.","properties":{"reason":{"type":"string","enum":["missing","malformed","not_supported"],"description":"Specific cause: \"missing\" when the header is absent, \"malformed\" when the value is not YYYY-MM-DD format, \"not_supported\" when the format is valid but the date is not a known contract date.\n"},"supported_dates":{"type":"array","items":{"type":"string"},"description":"All accepted Medtrics-API-Date values."}}}}}]},"InactiveClientError":{"description":"Returned when the authenticated API client is revoked, expired, deleted, or in a non-active status. HTTP 403. No structured details beyond the human-readable message.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","properties":{"code":{"type":"string","const":"inactive_client"}}}]},"InvalidWebhookUrlError":{"description":"Returned when target_url fails HTTPS validation or is otherwise rejected. HTTP 422. No structured details field beyond the human-readable message.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","properties":{"code":{"type":"string","const":"invalid_webhook_url"}}}]},"GenericApiError":{"description":"Catch-all error variant for codes that carry no structured remediation data (invalid_cursor, not_found, invalid_credentials, scope_not_allowed, invalid_webhook_tolerance, internal_error, and any future codes not covered by a typed variant in the discriminator.mapping). details is absent or empty for these codes.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","properties":{"details":{"type":"object","additionalProperties":true,"description":"Absent or empty for most generic error codes."}}}]}}},"paths":{"/connect/clinical-schedules":{"get":{"operationId":"listClinicalSchedules","tags":["Clinical Schedules"],"summary":"List visible clinical schedules.","description":"Returns cursor-paged clinical schedule definitions scoped to the authenticated API app tenant. Each entry represents a program schedule with config flags, access user/site/role lists, and legacy bridge identifiers. Legacy analog is /api-banner/program-schedules/.","parameters":[{"$ref":"#/components/parameters/ApiDateHeader"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/ProgramIdQuery"},{"$ref":"#/components/parameters/AcademicPeriodIdQuery"},{"$ref":"#/components/parameters/SiteIdQuery"},{"$ref":"#/components/parameters/LegacyIdQuery"}],"responses":{"200":{"$ref":"#/components/responses/ClinicalScheduleListResponse"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}}}}
```

## Create a clinical schedule.

> Creates a clinical schedule (program-schedule template). Requires clinical-schedules:write scope. program\_id and academic\_period\_id are required and scope-checked. All M2M arrays (site\_ids, admin\_user\_ids, participant\_user\_ids, view\_only\_user\_ids) are optional and scope-checked per-element. view\_role\_names in the response is read-only and reflects clinical\_schedule\_roles — it cannot be set via this API. A synthetic legacy\_id is minted so the created schedule is immediately addressable through the legacy SIS bridge (/api-banner/program-schedules/) by numeric ID. Known read-only fields are silently ignored; genuinely unknown fields are rejected. Legacy analog is POST /api-banner/program-schedules/.

```json
{"openapi":"3.1.0","info":{"title":"Medtrics Connect API","version":"2026-10-27"},"tags":[{"name":"Clinical Schedules","description":"Clinical schedule definitions per program. Aggregates config flags, M2M user/site/role access lists, and schedule metadata. Legacy analog is /api-banner/program-schedules/."}],"servers":[{"url":"https://sandbox.mymedtrics.com","description":"Client sandbox — shared demo tenant, X-Medtrics-Sandbox-Key or a sandbox API app"},{"url":"https://{client}.mymedtrics.com","description":"Your Medtrics instance","variables":{"client":{"default":"<client-name>","description":"The tenant subdomain Medtrics assigns to your institution"}}}],"security":[{"BearerToken":[]},{"SandboxApiKey":[]}],"components":{"securitySchemes":{"BearerToken":{"type":"http","scheme":"bearer"},"SandboxApiKey":{"type":"apiKey","in":"header","name":"X-Medtrics-Sandbox-Key"}},"parameters":{"ApiDateHeader":{"name":"Medtrics-API-Date","in":"header","required":true,"description":"API contract date. Use 2026-10-27 for this version. Contract dates select request behavior: rotation creates require explicit placement references from 2026-10-27; earlier accepted dates retain compatibility handling. Unknown dates return HTTP 400 with error.code unsupported_api_date.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}}},"responses":{"ClinicalScheduleItemResponse":{"description":"Single clinical schedule record.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/ClinicalSchedule"}}}}}},"BadRequest":{"description":"Invalid request parameters.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"Unauthorized":{"description":"Missing or invalid credentials.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"Forbidden":{"description":"Credential lacks tenant visibility or required scope.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"NotFound":{"description":"Resource not found or outside tenant scope.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"RateLimited":{"description":"Request or API app rate limit exceeded.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"InternalError":{"description":"Unexpected server error.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"headers":{"MedtricsRequestId":{"description":"Correlates all gateway logs for the request.","schema":{"type":"string"}},"MedtricsApiDate":{"description":"API contract date applied to the request.","schema":{"type":"string"}},"RateLimitLimit":{"description":"Request limit for the current window.","schema":{"type":"string"}},"RateLimitRemaining":{"description":"Remaining requests in the current window.","schema":{"type":"string"}},"RateLimitReset":{"description":"Unix seconds when the current rate-limit window resets.","schema":{"type":"string"}}},"schemas":{"ClinicalSchedule":{"type":"object","description":"A clinical schedule definition scoped to the authenticated API app tenant. Owns M2M access lists for admin users, participant users, view-only users, sites, and role-based view access. Legacy analog is /api-banner/program-schedules/.","required":["id","program_id","academic_period_id","name","schedule_details","show_rotation_tab_default","restrict_rotation_view","show_block_naming","enable_rotation_block_directors","admin_user_ids","participant_user_ids","view_only_user_ids","site_ids","view_role_names","created_at","updated_at"],"properties":{"id":{"type":"string","description":"Public ID of the clinical schedule (csched_ prefix). Backed by clinical_schedules.id."},"program_id":{"type":"string","description":"Public ID of the owning program (prog_ prefix)."},"academic_period_id":{"type":"string","description":"Public ID of the linked academic period (ap_ prefix)."},"name":{"type":"string"},"description":{"type":["string","null"],"description":"Free-text description of the schedule."},"schedule_details":{"type":"boolean","description":"Corresponds to schedule_config.scheduleDetails. Default false."},"show_rotation_tab_default":{"type":"boolean","description":"Corresponds to schedule_config.showRotationTabDefault. Default false."},"restrict_rotation_view":{"type":"boolean","description":"Corresponds to schedule_config.restrictRotationView. Default false."},"show_block_naming":{"type":"boolean","description":"Corresponds to schedule_config.showBlockNaming. Default true."},"enable_rotation_block_directors":{"type":"boolean","description":"Corresponds to schedule_config.enableRotationBlockDirectors. Default false."},"admin_user_ids":{"type":"array","items":{"type":"string"},"description":"Public IDs (user_ prefix) of schedule administrators from clinical_schedule_admins."},"participant_user_ids":{"type":"array","items":{"type":"string"},"description":"Public IDs (user_ prefix) of users with access_type='participant' from clinical_schedule_users."},"view_only_user_ids":{"type":"array","items":{"type":"string"},"description":"Public IDs (user_ prefix) of users with access_type='view_only' from clinical_schedule_users."},"site_ids":{"type":"array","items":{"type":"string"},"description":"Public IDs (site_ prefix) of associated sites from clinical_schedule_sites."},"view_role_names":{"type":"array","items":{"type":"string"},"description":"Role names with access_type='view' from clinical_schedule_roles (name strings, no IDs)."},"updated_by_id":{"type":["string","null"],"description":"Public ID (user_ prefix) of the last user to update this schedule. Null when not set."},"legacy_id":{"type":["string","null"],"description":"Legacy SIS integer ID (as string) from etl_id_registry (source_entity_type='clinical_schedule'). Null until ETL registers mappings."},"program_legacy_id":{"type":["string","null"],"description":"Legacy SIS integer ID (as string) of the owning program. Bridge own-id source for the program FK."},"academic_period_legacy_id":{"type":["string","null"],"description":"Legacy SIS integer ID (as string) of the academic period. Bridge own-id source for the academic_year FK."},"admin_user_legacy_ids":{"type":"array","items":{"type":"string"},"description":"Legacy SIS integer IDs (as strings) for admin users. Null entries omitted; array empty until ETL registers user mappings."},"participant_user_legacy_ids":{"type":"array","items":{"type":"string"},"description":"Legacy SIS integer IDs (as strings) for participant users."},"view_only_user_legacy_ids":{"type":"array","items":{"type":"string"},"description":"Legacy SIS integer IDs (as strings) for view-only users."},"site_legacy_ids":{"type":"array","items":{"type":"string"},"description":"Legacy SIS integer IDs (as strings) for associated sites."},"updated_by_legacy_id":{"type":["string","null"],"description":"Legacy SIS integer ID (as string) of the last updater. Null when updated_by_id is null or ETL not yet registered."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"ErrorEnvelope":{"type":"object","required":["error"],"description":"Structured error envelope. error.code is the machine-readable discriminant. Typed detail schemas are provided for codes where structured data aids client remediation. Use the discriminator.mapping to select the correct variant schema; for all other codes see GenericApiError.\n","properties":{"error":{"anyOf":[{"$ref":"#/components/schemas/InvalidStatusError"},{"$ref":"#/components/schemas/InvalidWebhookEventsError"},{"$ref":"#/components/schemas/RateLimitedError"},{"$ref":"#/components/schemas/CursorResourceMismatchError"},{"$ref":"#/components/schemas/InvalidIdError"},{"$ref":"#/components/schemas/UnsupportedApiDateError"},{"$ref":"#/components/schemas/InactiveClientError"},{"$ref":"#/components/schemas/InvalidWebhookUrlError"},{"$ref":"#/components/schemas/GenericApiError"}],"discriminator":{"propertyName":"code","mapping":{"invalid_status":"#/components/schemas/InvalidStatusError","invalid_webhook_events":"#/components/schemas/InvalidWebhookEventsError","rate_limited":"#/components/schemas/RateLimitedError","cursor_resource_mismatch":"#/components/schemas/CursorResourceMismatchError","invalid_id":"#/components/schemas/InvalidIdError","unsupported_api_date":"#/components/schemas/UnsupportedApiDateError","inactive_client":"#/components/schemas/InactiveClientError","invalid_webhook_url":"#/components/schemas/InvalidWebhookUrlError"}}}}},"InvalidStatusError":{"description":"Returned when a status query parameter value is outside the allowed enum. HTTP 400. details.field identifies the parameter; details.allowed lists every accepted value.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"invalid_status"},"details":{"type":"object","required":["field","allowed"],"additionalProperties":false,"description":"Structured remediation data for invalid_status.","properties":{"field":{"type":"string","description":"Query parameter name that received the invalid value (e.g. enrollment_status, status).\n"},"allowed":{"type":"array","items":{"type":"string"},"description":"Full list of accepted values for the parameter."}}}}}]},"ApiErrorBase":{"type":"object","required":["code","message","request_id"],"description":"Common fields shared by every Connect API error response.","properties":{"code":{"type":"string","description":"Machine-readable error code."},"message":{"type":"string","description":"Human-readable error description."},"request_id":{"type":"string","description":"Request correlation ID. Include this value when reporting issues to support alongside the Medtrics-Request-ID response header.\n"}}},"InvalidWebhookEventsError":{"description":"Returned when event_types contains values not in the approved Connect API event list, or when event_types is empty. HTTP 400. details.unsupported_events lists the rejected entries (empty array when event_types was missing or entirely empty).\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"invalid_webhook_events"},"details":{"type":"object","required":["unsupported_events"],"additionalProperties":false,"description":"Structured remediation data for invalid_webhook_events.","properties":{"unsupported_events":{"type":"array","items":{"type":"string"},"description":"Event type strings that are not approved Connect API event names. Empty array indicates event_types was missing or contained only invalid entries.\n"}}}}}]},"RateLimitedError":{"description":"Returned when the request or API app rate limit is exceeded. HTTP 429. details.retry_after_seconds indicates the wait duration before retrying.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"rate_limited"},"details":{"type":"object","required":["retry_after_seconds"],"additionalProperties":false,"description":"Structured remediation data for rate_limited.","properties":{"retry_after_seconds":{"type":"integer","description":"Seconds to wait before retrying. Mirrors the Retry-After response header value.\n"}}}}}]},"CursorResourceMismatchError":{"description":"Returned when the cursor was issued for a different resource type than the endpoint being called. HTTP 400. details.expected_resource is the resource this endpoint serves; details.cursor_resource is what the cursor encodes.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"cursor_resource_mismatch"},"details":{"type":"object","required":["expected_resource","cursor_resource"],"additionalProperties":false,"description":"Structured remediation data for cursor_resource_mismatch.","properties":{"expected_resource":{"type":"string","description":"Resource type this endpoint serves (e.g. programs, learners)."},"cursor_resource":{"type":"string","description":"Resource type encoded in the submitted cursor."}}}}}]},"InvalidIdError":{"description":"Returned when a path parameter public ID does not match the expected prefix for the resource. HTTP 400. details.field is the parameter name; details.expected_prefix is the required ID prefix.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"invalid_id"},"details":{"type":"object","required":["field","expected_prefix"],"additionalProperties":false,"description":"Structured remediation data for invalid_id.","properties":{"field":{"type":"string","description":"Path or query parameter name that received the invalid ID."},"expected_prefix":{"type":"string","description":"Required ID prefix for this parameter."}}}}}]},"UnsupportedApiDateError":{"description":"Returned when the Medtrics-API-Date header is missing, malformed (not YYYY-MM-DD), or not a supported contract date. HTTP 400. details.supported_dates lists all accepted values. details.reason is one of \"missing\", \"malformed\", or \"not_supported\".\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"unsupported_api_date"},"details":{"type":"object","required":["reason","supported_dates"],"additionalProperties":false,"description":"Structured remediation data for unsupported_api_date.","properties":{"reason":{"type":"string","enum":["missing","malformed","not_supported"],"description":"Specific cause: \"missing\" when the header is absent, \"malformed\" when the value is not YYYY-MM-DD format, \"not_supported\" when the format is valid but the date is not a known contract date.\n"},"supported_dates":{"type":"array","items":{"type":"string"},"description":"All accepted Medtrics-API-Date values."}}}}}]},"InactiveClientError":{"description":"Returned when the authenticated API client is revoked, expired, deleted, or in a non-active status. HTTP 403. No structured details beyond the human-readable message.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","properties":{"code":{"type":"string","const":"inactive_client"}}}]},"InvalidWebhookUrlError":{"description":"Returned when target_url fails HTTPS validation or is otherwise rejected. HTTP 422. No structured details field beyond the human-readable message.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","properties":{"code":{"type":"string","const":"invalid_webhook_url"}}}]},"GenericApiError":{"description":"Catch-all error variant for codes that carry no structured remediation data (invalid_cursor, not_found, invalid_credentials, scope_not_allowed, invalid_webhook_tolerance, internal_error, and any future codes not covered by a typed variant in the discriminator.mapping). details is absent or empty for these codes.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","properties":{"details":{"type":"object","additionalProperties":true,"description":"Absent or empty for most generic error codes."}}}]}}},"paths":{"/connect/clinical-schedules":{"post":{"operationId":"createClinicalSchedule","tags":["Clinical Schedules"],"summary":"Create a clinical schedule.","description":"Creates a clinical schedule (program-schedule template). Requires clinical-schedules:write scope. program_id and academic_period_id are required and scope-checked. All M2M arrays (site_ids, admin_user_ids, participant_user_ids, view_only_user_ids) are optional and scope-checked per-element. view_role_names in the response is read-only and reflects clinical_schedule_roles — it cannot be set via this API. A synthetic legacy_id is minted so the created schedule is immediately addressable through the legacy SIS bridge (/api-banner/program-schedules/) by numeric ID. Known read-only fields are silently ignored; genuinely unknown fields are rejected. Legacy analog is POST /api-banner/program-schedules/.","parameters":[{"$ref":"#/components/parameters/ApiDateHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","program_id","academic_period_id"],"properties":{"name":{"type":"string","maxLength":255},"program_id":{"type":"string","description":"Public ID of the owning program (prog_ prefix). Must be in tenant scope."},"academic_period_id":{"type":"string","description":"Public ID of the linked academic period (ap_ prefix). Must be in tenant scope."},"description":{"type":"string","nullable":true},"schedule_details":{"type":"boolean","description":"Corresponds to schedule_config.scheduleDetails. Defaults to false when omitted."},"show_rotation_tab_default":{"type":"boolean","description":"Corresponds to schedule_config.showRotationTabDefault. Defaults to false when omitted."},"restrict_rotation_view":{"type":"boolean","description":"Corresponds to schedule_config.restrictRotationView. Defaults to false when omitted."},"show_block_naming":{"type":"boolean","description":"Corresponds to schedule_config.showBlockNaming. Defaults to true when omitted."},"enable_rotation_block_directors":{"type":"boolean","description":"Corresponds to schedule_config.enableRotationBlockDirectors. Defaults to false when omitted."},"site_ids":{"type":"array","items":{"type":"string"},"description":"Public IDs (site_ prefix) of sites to associate. Each must be in tenant scope."},"admin_user_ids":{"type":"array","items":{"type":"string"},"description":"Public IDs (user_ prefix) of schedule administrators (clinical_schedule_admins)."},"participant_user_ids":{"type":"array","items":{"type":"string"},"description":"Public IDs (user_ prefix) of participant users (clinical_schedule_users access_type='participant')."},"view_only_user_ids":{"type":"array","items":{"type":"string"},"description":"Public IDs (user_ prefix) of view-only users (clinical_schedule_users access_type='view_only')."}}}}}},"responses":{"201":{"$ref":"#/components/responses/ClinicalScheduleItemResponse"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}}}}
```

## Get a clinical schedule.

> Returns one clinical schedule definition when it belongs to the authenticated API app tenant.

```json
{"openapi":"3.1.0","info":{"title":"Medtrics Connect API","version":"2026-10-27"},"tags":[{"name":"Clinical Schedules","description":"Clinical schedule definitions per program. Aggregates config flags, M2M user/site/role access lists, and schedule metadata. Legacy analog is /api-banner/program-schedules/."}],"servers":[{"url":"https://sandbox.mymedtrics.com","description":"Client sandbox — shared demo tenant, X-Medtrics-Sandbox-Key or a sandbox API app"},{"url":"https://{client}.mymedtrics.com","description":"Your Medtrics instance","variables":{"client":{"default":"<client-name>","description":"The tenant subdomain Medtrics assigns to your institution"}}}],"security":[{"BearerToken":[]},{"SandboxApiKey":[]}],"components":{"securitySchemes":{"BearerToken":{"type":"http","scheme":"bearer"},"SandboxApiKey":{"type":"apiKey","in":"header","name":"X-Medtrics-Sandbox-Key"}},"parameters":{"ApiDateHeader":{"name":"Medtrics-API-Date","in":"header","required":true,"description":"API contract date. Use 2026-10-27 for this version. Contract dates select request behavior: rotation creates require explicit placement references from 2026-10-27; earlier accepted dates retain compatibility handling. Unknown dates return HTTP 400 with error.code unsupported_api_date.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},"ClinicalScheduleId":{"name":"clinicalScheduleId","in":"path","required":true,"schema":{"type":"string"},"description":"Public ID of the clinical schedule (csched_ prefix). Backed by clinical_schedules.id."}},"responses":{"ClinicalScheduleItemResponse":{"description":"Single clinical schedule record.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/ClinicalSchedule"}}}}}},"Unauthorized":{"description":"Missing or invalid credentials.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"Forbidden":{"description":"Credential lacks tenant visibility or required scope.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"NotFound":{"description":"Resource not found or outside tenant scope.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"RateLimited":{"description":"Request or API app rate limit exceeded.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"InternalError":{"description":"Unexpected server error.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"headers":{"MedtricsRequestId":{"description":"Correlates all gateway logs for the request.","schema":{"type":"string"}},"MedtricsApiDate":{"description":"API contract date applied to the request.","schema":{"type":"string"}},"RateLimitLimit":{"description":"Request limit for the current window.","schema":{"type":"string"}},"RateLimitRemaining":{"description":"Remaining requests in the current window.","schema":{"type":"string"}},"RateLimitReset":{"description":"Unix seconds when the current rate-limit window resets.","schema":{"type":"string"}}},"schemas":{"ClinicalSchedule":{"type":"object","description":"A clinical schedule definition scoped to the authenticated API app tenant. Owns M2M access lists for admin users, participant users, view-only users, sites, and role-based view access. Legacy analog is /api-banner/program-schedules/.","required":["id","program_id","academic_period_id","name","schedule_details","show_rotation_tab_default","restrict_rotation_view","show_block_naming","enable_rotation_block_directors","admin_user_ids","participant_user_ids","view_only_user_ids","site_ids","view_role_names","created_at","updated_at"],"properties":{"id":{"type":"string","description":"Public ID of the clinical schedule (csched_ prefix). Backed by clinical_schedules.id."},"program_id":{"type":"string","description":"Public ID of the owning program (prog_ prefix)."},"academic_period_id":{"type":"string","description":"Public ID of the linked academic period (ap_ prefix)."},"name":{"type":"string"},"description":{"type":["string","null"],"description":"Free-text description of the schedule."},"schedule_details":{"type":"boolean","description":"Corresponds to schedule_config.scheduleDetails. Default false."},"show_rotation_tab_default":{"type":"boolean","description":"Corresponds to schedule_config.showRotationTabDefault. Default false."},"restrict_rotation_view":{"type":"boolean","description":"Corresponds to schedule_config.restrictRotationView. Default false."},"show_block_naming":{"type":"boolean","description":"Corresponds to schedule_config.showBlockNaming. Default true."},"enable_rotation_block_directors":{"type":"boolean","description":"Corresponds to schedule_config.enableRotationBlockDirectors. Default false."},"admin_user_ids":{"type":"array","items":{"type":"string"},"description":"Public IDs (user_ prefix) of schedule administrators from clinical_schedule_admins."},"participant_user_ids":{"type":"array","items":{"type":"string"},"description":"Public IDs (user_ prefix) of users with access_type='participant' from clinical_schedule_users."},"view_only_user_ids":{"type":"array","items":{"type":"string"},"description":"Public IDs (user_ prefix) of users with access_type='view_only' from clinical_schedule_users."},"site_ids":{"type":"array","items":{"type":"string"},"description":"Public IDs (site_ prefix) of associated sites from clinical_schedule_sites."},"view_role_names":{"type":"array","items":{"type":"string"},"description":"Role names with access_type='view' from clinical_schedule_roles (name strings, no IDs)."},"updated_by_id":{"type":["string","null"],"description":"Public ID (user_ prefix) of the last user to update this schedule. Null when not set."},"legacy_id":{"type":["string","null"],"description":"Legacy SIS integer ID (as string) from etl_id_registry (source_entity_type='clinical_schedule'). Null until ETL registers mappings."},"program_legacy_id":{"type":["string","null"],"description":"Legacy SIS integer ID (as string) of the owning program. Bridge own-id source for the program FK."},"academic_period_legacy_id":{"type":["string","null"],"description":"Legacy SIS integer ID (as string) of the academic period. Bridge own-id source for the academic_year FK."},"admin_user_legacy_ids":{"type":"array","items":{"type":"string"},"description":"Legacy SIS integer IDs (as strings) for admin users. Null entries omitted; array empty until ETL registers user mappings."},"participant_user_legacy_ids":{"type":"array","items":{"type":"string"},"description":"Legacy SIS integer IDs (as strings) for participant users."},"view_only_user_legacy_ids":{"type":"array","items":{"type":"string"},"description":"Legacy SIS integer IDs (as strings) for view-only users."},"site_legacy_ids":{"type":"array","items":{"type":"string"},"description":"Legacy SIS integer IDs (as strings) for associated sites."},"updated_by_legacy_id":{"type":["string","null"],"description":"Legacy SIS integer ID (as string) of the last updater. Null when updated_by_id is null or ETL not yet registered."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"ErrorEnvelope":{"type":"object","required":["error"],"description":"Structured error envelope. error.code is the machine-readable discriminant. Typed detail schemas are provided for codes where structured data aids client remediation. Use the discriminator.mapping to select the correct variant schema; for all other codes see GenericApiError.\n","properties":{"error":{"anyOf":[{"$ref":"#/components/schemas/InvalidStatusError"},{"$ref":"#/components/schemas/InvalidWebhookEventsError"},{"$ref":"#/components/schemas/RateLimitedError"},{"$ref":"#/components/schemas/CursorResourceMismatchError"},{"$ref":"#/components/schemas/InvalidIdError"},{"$ref":"#/components/schemas/UnsupportedApiDateError"},{"$ref":"#/components/schemas/InactiveClientError"},{"$ref":"#/components/schemas/InvalidWebhookUrlError"},{"$ref":"#/components/schemas/GenericApiError"}],"discriminator":{"propertyName":"code","mapping":{"invalid_status":"#/components/schemas/InvalidStatusError","invalid_webhook_events":"#/components/schemas/InvalidWebhookEventsError","rate_limited":"#/components/schemas/RateLimitedError","cursor_resource_mismatch":"#/components/schemas/CursorResourceMismatchError","invalid_id":"#/components/schemas/InvalidIdError","unsupported_api_date":"#/components/schemas/UnsupportedApiDateError","inactive_client":"#/components/schemas/InactiveClientError","invalid_webhook_url":"#/components/schemas/InvalidWebhookUrlError"}}}}},"InvalidStatusError":{"description":"Returned when a status query parameter value is outside the allowed enum. HTTP 400. details.field identifies the parameter; details.allowed lists every accepted value.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"invalid_status"},"details":{"type":"object","required":["field","allowed"],"additionalProperties":false,"description":"Structured remediation data for invalid_status.","properties":{"field":{"type":"string","description":"Query parameter name that received the invalid value (e.g. enrollment_status, status).\n"},"allowed":{"type":"array","items":{"type":"string"},"description":"Full list of accepted values for the parameter."}}}}}]},"ApiErrorBase":{"type":"object","required":["code","message","request_id"],"description":"Common fields shared by every Connect API error response.","properties":{"code":{"type":"string","description":"Machine-readable error code."},"message":{"type":"string","description":"Human-readable error description."},"request_id":{"type":"string","description":"Request correlation ID. Include this value when reporting issues to support alongside the Medtrics-Request-ID response header.\n"}}},"InvalidWebhookEventsError":{"description":"Returned when event_types contains values not in the approved Connect API event list, or when event_types is empty. HTTP 400. details.unsupported_events lists the rejected entries (empty array when event_types was missing or entirely empty).\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"invalid_webhook_events"},"details":{"type":"object","required":["unsupported_events"],"additionalProperties":false,"description":"Structured remediation data for invalid_webhook_events.","properties":{"unsupported_events":{"type":"array","items":{"type":"string"},"description":"Event type strings that are not approved Connect API event names. Empty array indicates event_types was missing or contained only invalid entries.\n"}}}}}]},"RateLimitedError":{"description":"Returned when the request or API app rate limit is exceeded. HTTP 429. details.retry_after_seconds indicates the wait duration before retrying.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"rate_limited"},"details":{"type":"object","required":["retry_after_seconds"],"additionalProperties":false,"description":"Structured remediation data for rate_limited.","properties":{"retry_after_seconds":{"type":"integer","description":"Seconds to wait before retrying. Mirrors the Retry-After response header value.\n"}}}}}]},"CursorResourceMismatchError":{"description":"Returned when the cursor was issued for a different resource type than the endpoint being called. HTTP 400. details.expected_resource is the resource this endpoint serves; details.cursor_resource is what the cursor encodes.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"cursor_resource_mismatch"},"details":{"type":"object","required":["expected_resource","cursor_resource"],"additionalProperties":false,"description":"Structured remediation data for cursor_resource_mismatch.","properties":{"expected_resource":{"type":"string","description":"Resource type this endpoint serves (e.g. programs, learners)."},"cursor_resource":{"type":"string","description":"Resource type encoded in the submitted cursor."}}}}}]},"InvalidIdError":{"description":"Returned when a path parameter public ID does not match the expected prefix for the resource. HTTP 400. details.field is the parameter name; details.expected_prefix is the required ID prefix.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"invalid_id"},"details":{"type":"object","required":["field","expected_prefix"],"additionalProperties":false,"description":"Structured remediation data for invalid_id.","properties":{"field":{"type":"string","description":"Path or query parameter name that received the invalid ID."},"expected_prefix":{"type":"string","description":"Required ID prefix for this parameter."}}}}}]},"UnsupportedApiDateError":{"description":"Returned when the Medtrics-API-Date header is missing, malformed (not YYYY-MM-DD), or not a supported contract date. HTTP 400. details.supported_dates lists all accepted values. details.reason is one of \"missing\", \"malformed\", or \"not_supported\".\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"unsupported_api_date"},"details":{"type":"object","required":["reason","supported_dates"],"additionalProperties":false,"description":"Structured remediation data for unsupported_api_date.","properties":{"reason":{"type":"string","enum":["missing","malformed","not_supported"],"description":"Specific cause: \"missing\" when the header is absent, \"malformed\" when the value is not YYYY-MM-DD format, \"not_supported\" when the format is valid but the date is not a known contract date.\n"},"supported_dates":{"type":"array","items":{"type":"string"},"description":"All accepted Medtrics-API-Date values."}}}}}]},"InactiveClientError":{"description":"Returned when the authenticated API client is revoked, expired, deleted, or in a non-active status. HTTP 403. No structured details beyond the human-readable message.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","properties":{"code":{"type":"string","const":"inactive_client"}}}]},"InvalidWebhookUrlError":{"description":"Returned when target_url fails HTTPS validation or is otherwise rejected. HTTP 422. No structured details field beyond the human-readable message.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","properties":{"code":{"type":"string","const":"invalid_webhook_url"}}}]},"GenericApiError":{"description":"Catch-all error variant for codes that carry no structured remediation data (invalid_cursor, not_found, invalid_credentials, scope_not_allowed, invalid_webhook_tolerance, internal_error, and any future codes not covered by a typed variant in the discriminator.mapping). details is absent or empty for these codes.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","properties":{"details":{"type":"object","additionalProperties":true,"description":"Absent or empty for most generic error codes."}}}]}}},"paths":{"/connect/clinical-schedules/{clinicalScheduleId}":{"get":{"operationId":"getClinicalSchedule","tags":["Clinical Schedules"],"summary":"Get a clinical schedule.","description":"Returns one clinical schedule definition when it belongs to the authenticated API app tenant.","parameters":[{"$ref":"#/components/parameters/ApiDateHeader"},{"$ref":"#/components/parameters/ClinicalScheduleId"}],"responses":{"200":{"$ref":"#/components/responses/ClinicalScheduleItemResponse"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}}}}
```

## Delete a clinical schedule.

> Soft-deletes a clinical schedule (sets deleted\_at). Requires clinical-schedules:delete scope. Idempotent: deleting an already-deleted schedule returns 204 without error. Out-of-scope schedules return 404. Legacy analog is DELETE /api-banner/program-schedules/{id}/.

```json
{"openapi":"3.1.0","info":{"title":"Medtrics Connect API","version":"2026-10-27"},"tags":[{"name":"Clinical Schedules","description":"Clinical schedule definitions per program. Aggregates config flags, M2M user/site/role access lists, and schedule metadata. Legacy analog is /api-banner/program-schedules/."}],"servers":[{"url":"https://sandbox.mymedtrics.com","description":"Client sandbox — shared demo tenant, X-Medtrics-Sandbox-Key or a sandbox API app"},{"url":"https://{client}.mymedtrics.com","description":"Your Medtrics instance","variables":{"client":{"default":"<client-name>","description":"The tenant subdomain Medtrics assigns to your institution"}}}],"security":[{"BearerToken":[]},{"SandboxApiKey":[]}],"components":{"securitySchemes":{"BearerToken":{"type":"http","scheme":"bearer"},"SandboxApiKey":{"type":"apiKey","in":"header","name":"X-Medtrics-Sandbox-Key"}},"parameters":{"ApiDateHeader":{"name":"Medtrics-API-Date","in":"header","required":true,"description":"API contract date. Use 2026-10-27 for this version. Contract dates select request behavior: rotation creates require explicit placement references from 2026-10-27; earlier accepted dates retain compatibility handling. Unknown dates return HTTP 400 with error.code unsupported_api_date.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},"ClinicalScheduleId":{"name":"clinicalScheduleId","in":"path","required":true,"schema":{"type":"string"},"description":"Public ID of the clinical schedule (csched_ prefix). Backed by clinical_schedules.id."}},"responses":{"NoContent":{"description":"Success with no response body.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"Unauthorized":{"description":"Missing or invalid credentials.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"Forbidden":{"description":"Credential lacks tenant visibility or required scope.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"NotFound":{"description":"Resource not found or outside tenant scope.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"RateLimited":{"description":"Request or API app rate limit exceeded.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"InternalError":{"description":"Unexpected server error.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"headers":{"MedtricsRequestId":{"description":"Correlates all gateway logs for the request.","schema":{"type":"string"}},"MedtricsApiDate":{"description":"API contract date applied to the request.","schema":{"type":"string"}},"RateLimitLimit":{"description":"Request limit for the current window.","schema":{"type":"string"}},"RateLimitRemaining":{"description":"Remaining requests in the current window.","schema":{"type":"string"}},"RateLimitReset":{"description":"Unix seconds when the current rate-limit window resets.","schema":{"type":"string"}}},"schemas":{"ErrorEnvelope":{"type":"object","required":["error"],"description":"Structured error envelope. error.code is the machine-readable discriminant. Typed detail schemas are provided for codes where structured data aids client remediation. Use the discriminator.mapping to select the correct variant schema; for all other codes see GenericApiError.\n","properties":{"error":{"anyOf":[{"$ref":"#/components/schemas/InvalidStatusError"},{"$ref":"#/components/schemas/InvalidWebhookEventsError"},{"$ref":"#/components/schemas/RateLimitedError"},{"$ref":"#/components/schemas/CursorResourceMismatchError"},{"$ref":"#/components/schemas/InvalidIdError"},{"$ref":"#/components/schemas/UnsupportedApiDateError"},{"$ref":"#/components/schemas/InactiveClientError"},{"$ref":"#/components/schemas/InvalidWebhookUrlError"},{"$ref":"#/components/schemas/GenericApiError"}],"discriminator":{"propertyName":"code","mapping":{"invalid_status":"#/components/schemas/InvalidStatusError","invalid_webhook_events":"#/components/schemas/InvalidWebhookEventsError","rate_limited":"#/components/schemas/RateLimitedError","cursor_resource_mismatch":"#/components/schemas/CursorResourceMismatchError","invalid_id":"#/components/schemas/InvalidIdError","unsupported_api_date":"#/components/schemas/UnsupportedApiDateError","inactive_client":"#/components/schemas/InactiveClientError","invalid_webhook_url":"#/components/schemas/InvalidWebhookUrlError"}}}}},"InvalidStatusError":{"description":"Returned when a status query parameter value is outside the allowed enum. HTTP 400. details.field identifies the parameter; details.allowed lists every accepted value.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"invalid_status"},"details":{"type":"object","required":["field","allowed"],"additionalProperties":false,"description":"Structured remediation data for invalid_status.","properties":{"field":{"type":"string","description":"Query parameter name that received the invalid value (e.g. enrollment_status, status).\n"},"allowed":{"type":"array","items":{"type":"string"},"description":"Full list of accepted values for the parameter."}}}}}]},"ApiErrorBase":{"type":"object","required":["code","message","request_id"],"description":"Common fields shared by every Connect API error response.","properties":{"code":{"type":"string","description":"Machine-readable error code."},"message":{"type":"string","description":"Human-readable error description."},"request_id":{"type":"string","description":"Request correlation ID. Include this value when reporting issues to support alongside the Medtrics-Request-ID response header.\n"}}},"InvalidWebhookEventsError":{"description":"Returned when event_types contains values not in the approved Connect API event list, or when event_types is empty. HTTP 400. details.unsupported_events lists the rejected entries (empty array when event_types was missing or entirely empty).\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"invalid_webhook_events"},"details":{"type":"object","required":["unsupported_events"],"additionalProperties":false,"description":"Structured remediation data for invalid_webhook_events.","properties":{"unsupported_events":{"type":"array","items":{"type":"string"},"description":"Event type strings that are not approved Connect API event names. Empty array indicates event_types was missing or contained only invalid entries.\n"}}}}}]},"RateLimitedError":{"description":"Returned when the request or API app rate limit is exceeded. HTTP 429. details.retry_after_seconds indicates the wait duration before retrying.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"rate_limited"},"details":{"type":"object","required":["retry_after_seconds"],"additionalProperties":false,"description":"Structured remediation data for rate_limited.","properties":{"retry_after_seconds":{"type":"integer","description":"Seconds to wait before retrying. Mirrors the Retry-After response header value.\n"}}}}}]},"CursorResourceMismatchError":{"description":"Returned when the cursor was issued for a different resource type than the endpoint being called. HTTP 400. details.expected_resource is the resource this endpoint serves; details.cursor_resource is what the cursor encodes.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"cursor_resource_mismatch"},"details":{"type":"object","required":["expected_resource","cursor_resource"],"additionalProperties":false,"description":"Structured remediation data for cursor_resource_mismatch.","properties":{"expected_resource":{"type":"string","description":"Resource type this endpoint serves (e.g. programs, learners)."},"cursor_resource":{"type":"string","description":"Resource type encoded in the submitted cursor."}}}}}]},"InvalidIdError":{"description":"Returned when a path parameter public ID does not match the expected prefix for the resource. HTTP 400. details.field is the parameter name; details.expected_prefix is the required ID prefix.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"invalid_id"},"details":{"type":"object","required":["field","expected_prefix"],"additionalProperties":false,"description":"Structured remediation data for invalid_id.","properties":{"field":{"type":"string","description":"Path or query parameter name that received the invalid ID."},"expected_prefix":{"type":"string","description":"Required ID prefix for this parameter."}}}}}]},"UnsupportedApiDateError":{"description":"Returned when the Medtrics-API-Date header is missing, malformed (not YYYY-MM-DD), or not a supported contract date. HTTP 400. details.supported_dates lists all accepted values. details.reason is one of \"missing\", \"malformed\", or \"not_supported\".\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"unsupported_api_date"},"details":{"type":"object","required":["reason","supported_dates"],"additionalProperties":false,"description":"Structured remediation data for unsupported_api_date.","properties":{"reason":{"type":"string","enum":["missing","malformed","not_supported"],"description":"Specific cause: \"missing\" when the header is absent, \"malformed\" when the value is not YYYY-MM-DD format, \"not_supported\" when the format is valid but the date is not a known contract date.\n"},"supported_dates":{"type":"array","items":{"type":"string"},"description":"All accepted Medtrics-API-Date values."}}}}}]},"InactiveClientError":{"description":"Returned when the authenticated API client is revoked, expired, deleted, or in a non-active status. HTTP 403. No structured details beyond the human-readable message.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","properties":{"code":{"type":"string","const":"inactive_client"}}}]},"InvalidWebhookUrlError":{"description":"Returned when target_url fails HTTPS validation or is otherwise rejected. HTTP 422. No structured details field beyond the human-readable message.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","properties":{"code":{"type":"string","const":"invalid_webhook_url"}}}]},"GenericApiError":{"description":"Catch-all error variant for codes that carry no structured remediation data (invalid_cursor, not_found, invalid_credentials, scope_not_allowed, invalid_webhook_tolerance, internal_error, and any future codes not covered by a typed variant in the discriminator.mapping). details is absent or empty for these codes.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","properties":{"details":{"type":"object","additionalProperties":true,"description":"Absent or empty for most generic error codes."}}}]}}},"paths":{"/connect/clinical-schedules/{clinicalScheduleId}":{"delete":{"operationId":"deleteClinicalSchedule","tags":["Clinical Schedules"],"summary":"Delete a clinical schedule.","description":"Soft-deletes a clinical schedule (sets deleted_at). Requires clinical-schedules:delete scope. Idempotent: deleting an already-deleted schedule returns 204 without error. Out-of-scope schedules return 404. Legacy analog is DELETE /api-banner/program-schedules/{id}/.","parameters":[{"$ref":"#/components/parameters/ApiDateHeader"},{"$ref":"#/components/parameters/ClinicalScheduleId"}],"responses":{"204":{"$ref":"#/components/responses/NoContent"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}}}}
```

## Partially update a clinical schedule.

> Applies only the provided fields. Absent scalar fields are left unchanged. Requires clinical-schedules:write scope. For M2M arrays (site\_ids, admin\_user\_ids, participant\_user\_ids, view\_only\_user\_ids): omit to leave unchanged; supply (including an empty array) to replace the full set. view\_role\_names is read-only and cannot be set via this API. Known read-only fields are silently ignored; genuinely unknown fields are rejected. Legacy analog is PATCH /api-banner/program-schedules/{id}/.

```json
{"openapi":"3.1.0","info":{"title":"Medtrics Connect API","version":"2026-10-27"},"tags":[{"name":"Clinical Schedules","description":"Clinical schedule definitions per program. Aggregates config flags, M2M user/site/role access lists, and schedule metadata. Legacy analog is /api-banner/program-schedules/."}],"servers":[{"url":"https://sandbox.mymedtrics.com","description":"Client sandbox — shared demo tenant, X-Medtrics-Sandbox-Key or a sandbox API app"},{"url":"https://{client}.mymedtrics.com","description":"Your Medtrics instance","variables":{"client":{"default":"<client-name>","description":"The tenant subdomain Medtrics assigns to your institution"}}}],"security":[{"BearerToken":[]},{"SandboxApiKey":[]}],"components":{"securitySchemes":{"BearerToken":{"type":"http","scheme":"bearer"},"SandboxApiKey":{"type":"apiKey","in":"header","name":"X-Medtrics-Sandbox-Key"}},"parameters":{"ApiDateHeader":{"name":"Medtrics-API-Date","in":"header","required":true,"description":"API contract date. Use 2026-10-27 for this version. Contract dates select request behavior: rotation creates require explicit placement references from 2026-10-27; earlier accepted dates retain compatibility handling. Unknown dates return HTTP 400 with error.code unsupported_api_date.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},"ClinicalScheduleId":{"name":"clinicalScheduleId","in":"path","required":true,"schema":{"type":"string"},"description":"Public ID of the clinical schedule (csched_ prefix). Backed by clinical_schedules.id."}},"responses":{"ClinicalScheduleItemResponse":{"description":"Single clinical schedule record.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/ClinicalSchedule"}}}}}},"BadRequest":{"description":"Invalid request parameters.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"Unauthorized":{"description":"Missing or invalid credentials.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"Forbidden":{"description":"Credential lacks tenant visibility or required scope.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"NotFound":{"description":"Resource not found or outside tenant scope.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"RateLimited":{"description":"Request or API app rate limit exceeded.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"InternalError":{"description":"Unexpected server error.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"headers":{"MedtricsRequestId":{"description":"Correlates all gateway logs for the request.","schema":{"type":"string"}},"MedtricsApiDate":{"description":"API contract date applied to the request.","schema":{"type":"string"}},"RateLimitLimit":{"description":"Request limit for the current window.","schema":{"type":"string"}},"RateLimitRemaining":{"description":"Remaining requests in the current window.","schema":{"type":"string"}},"RateLimitReset":{"description":"Unix seconds when the current rate-limit window resets.","schema":{"type":"string"}}},"schemas":{"ClinicalSchedule":{"type":"object","description":"A clinical schedule definition scoped to the authenticated API app tenant. Owns M2M access lists for admin users, participant users, view-only users, sites, and role-based view access. Legacy analog is /api-banner/program-schedules/.","required":["id","program_id","academic_period_id","name","schedule_details","show_rotation_tab_default","restrict_rotation_view","show_block_naming","enable_rotation_block_directors","admin_user_ids","participant_user_ids","view_only_user_ids","site_ids","view_role_names","created_at","updated_at"],"properties":{"id":{"type":"string","description":"Public ID of the clinical schedule (csched_ prefix). Backed by clinical_schedules.id."},"program_id":{"type":"string","description":"Public ID of the owning program (prog_ prefix)."},"academic_period_id":{"type":"string","description":"Public ID of the linked academic period (ap_ prefix)."},"name":{"type":"string"},"description":{"type":["string","null"],"description":"Free-text description of the schedule."},"schedule_details":{"type":"boolean","description":"Corresponds to schedule_config.scheduleDetails. Default false."},"show_rotation_tab_default":{"type":"boolean","description":"Corresponds to schedule_config.showRotationTabDefault. Default false."},"restrict_rotation_view":{"type":"boolean","description":"Corresponds to schedule_config.restrictRotationView. Default false."},"show_block_naming":{"type":"boolean","description":"Corresponds to schedule_config.showBlockNaming. Default true."},"enable_rotation_block_directors":{"type":"boolean","description":"Corresponds to schedule_config.enableRotationBlockDirectors. Default false."},"admin_user_ids":{"type":"array","items":{"type":"string"},"description":"Public IDs (user_ prefix) of schedule administrators from clinical_schedule_admins."},"participant_user_ids":{"type":"array","items":{"type":"string"},"description":"Public IDs (user_ prefix) of users with access_type='participant' from clinical_schedule_users."},"view_only_user_ids":{"type":"array","items":{"type":"string"},"description":"Public IDs (user_ prefix) of users with access_type='view_only' from clinical_schedule_users."},"site_ids":{"type":"array","items":{"type":"string"},"description":"Public IDs (site_ prefix) of associated sites from clinical_schedule_sites."},"view_role_names":{"type":"array","items":{"type":"string"},"description":"Role names with access_type='view' from clinical_schedule_roles (name strings, no IDs)."},"updated_by_id":{"type":["string","null"],"description":"Public ID (user_ prefix) of the last user to update this schedule. Null when not set."},"legacy_id":{"type":["string","null"],"description":"Legacy SIS integer ID (as string) from etl_id_registry (source_entity_type='clinical_schedule'). Null until ETL registers mappings."},"program_legacy_id":{"type":["string","null"],"description":"Legacy SIS integer ID (as string) of the owning program. Bridge own-id source for the program FK."},"academic_period_legacy_id":{"type":["string","null"],"description":"Legacy SIS integer ID (as string) of the academic period. Bridge own-id source for the academic_year FK."},"admin_user_legacy_ids":{"type":"array","items":{"type":"string"},"description":"Legacy SIS integer IDs (as strings) for admin users. Null entries omitted; array empty until ETL registers user mappings."},"participant_user_legacy_ids":{"type":"array","items":{"type":"string"},"description":"Legacy SIS integer IDs (as strings) for participant users."},"view_only_user_legacy_ids":{"type":"array","items":{"type":"string"},"description":"Legacy SIS integer IDs (as strings) for view-only users."},"site_legacy_ids":{"type":"array","items":{"type":"string"},"description":"Legacy SIS integer IDs (as strings) for associated sites."},"updated_by_legacy_id":{"type":["string","null"],"description":"Legacy SIS integer ID (as string) of the last updater. Null when updated_by_id is null or ETL not yet registered."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"ErrorEnvelope":{"type":"object","required":["error"],"description":"Structured error envelope. error.code is the machine-readable discriminant. Typed detail schemas are provided for codes where structured data aids client remediation. Use the discriminator.mapping to select the correct variant schema; for all other codes see GenericApiError.\n","properties":{"error":{"anyOf":[{"$ref":"#/components/schemas/InvalidStatusError"},{"$ref":"#/components/schemas/InvalidWebhookEventsError"},{"$ref":"#/components/schemas/RateLimitedError"},{"$ref":"#/components/schemas/CursorResourceMismatchError"},{"$ref":"#/components/schemas/InvalidIdError"},{"$ref":"#/components/schemas/UnsupportedApiDateError"},{"$ref":"#/components/schemas/InactiveClientError"},{"$ref":"#/components/schemas/InvalidWebhookUrlError"},{"$ref":"#/components/schemas/GenericApiError"}],"discriminator":{"propertyName":"code","mapping":{"invalid_status":"#/components/schemas/InvalidStatusError","invalid_webhook_events":"#/components/schemas/InvalidWebhookEventsError","rate_limited":"#/components/schemas/RateLimitedError","cursor_resource_mismatch":"#/components/schemas/CursorResourceMismatchError","invalid_id":"#/components/schemas/InvalidIdError","unsupported_api_date":"#/components/schemas/UnsupportedApiDateError","inactive_client":"#/components/schemas/InactiveClientError","invalid_webhook_url":"#/components/schemas/InvalidWebhookUrlError"}}}}},"InvalidStatusError":{"description":"Returned when a status query parameter value is outside the allowed enum. HTTP 400. details.field identifies the parameter; details.allowed lists every accepted value.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"invalid_status"},"details":{"type":"object","required":["field","allowed"],"additionalProperties":false,"description":"Structured remediation data for invalid_status.","properties":{"field":{"type":"string","description":"Query parameter name that received the invalid value (e.g. enrollment_status, status).\n"},"allowed":{"type":"array","items":{"type":"string"},"description":"Full list of accepted values for the parameter."}}}}}]},"ApiErrorBase":{"type":"object","required":["code","message","request_id"],"description":"Common fields shared by every Connect API error response.","properties":{"code":{"type":"string","description":"Machine-readable error code."},"message":{"type":"string","description":"Human-readable error description."},"request_id":{"type":"string","description":"Request correlation ID. Include this value when reporting issues to support alongside the Medtrics-Request-ID response header.\n"}}},"InvalidWebhookEventsError":{"description":"Returned when event_types contains values not in the approved Connect API event list, or when event_types is empty. HTTP 400. details.unsupported_events lists the rejected entries (empty array when event_types was missing or entirely empty).\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"invalid_webhook_events"},"details":{"type":"object","required":["unsupported_events"],"additionalProperties":false,"description":"Structured remediation data for invalid_webhook_events.","properties":{"unsupported_events":{"type":"array","items":{"type":"string"},"description":"Event type strings that are not approved Connect API event names. Empty array indicates event_types was missing or contained only invalid entries.\n"}}}}}]},"RateLimitedError":{"description":"Returned when the request or API app rate limit is exceeded. HTTP 429. details.retry_after_seconds indicates the wait duration before retrying.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"rate_limited"},"details":{"type":"object","required":["retry_after_seconds"],"additionalProperties":false,"description":"Structured remediation data for rate_limited.","properties":{"retry_after_seconds":{"type":"integer","description":"Seconds to wait before retrying. Mirrors the Retry-After response header value.\n"}}}}}]},"CursorResourceMismatchError":{"description":"Returned when the cursor was issued for a different resource type than the endpoint being called. HTTP 400. details.expected_resource is the resource this endpoint serves; details.cursor_resource is what the cursor encodes.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"cursor_resource_mismatch"},"details":{"type":"object","required":["expected_resource","cursor_resource"],"additionalProperties":false,"description":"Structured remediation data for cursor_resource_mismatch.","properties":{"expected_resource":{"type":"string","description":"Resource type this endpoint serves (e.g. programs, learners)."},"cursor_resource":{"type":"string","description":"Resource type encoded in the submitted cursor."}}}}}]},"InvalidIdError":{"description":"Returned when a path parameter public ID does not match the expected prefix for the resource. HTTP 400. details.field is the parameter name; details.expected_prefix is the required ID prefix.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"invalid_id"},"details":{"type":"object","required":["field","expected_prefix"],"additionalProperties":false,"description":"Structured remediation data for invalid_id.","properties":{"field":{"type":"string","description":"Path or query parameter name that received the invalid ID."},"expected_prefix":{"type":"string","description":"Required ID prefix for this parameter."}}}}}]},"UnsupportedApiDateError":{"description":"Returned when the Medtrics-API-Date header is missing, malformed (not YYYY-MM-DD), or not a supported contract date. HTTP 400. details.supported_dates lists all accepted values. details.reason is one of \"missing\", \"malformed\", or \"not_supported\".\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"unsupported_api_date"},"details":{"type":"object","required":["reason","supported_dates"],"additionalProperties":false,"description":"Structured remediation data for unsupported_api_date.","properties":{"reason":{"type":"string","enum":["missing","malformed","not_supported"],"description":"Specific cause: \"missing\" when the header is absent, \"malformed\" when the value is not YYYY-MM-DD format, \"not_supported\" when the format is valid but the date is not a known contract date.\n"},"supported_dates":{"type":"array","items":{"type":"string"},"description":"All accepted Medtrics-API-Date values."}}}}}]},"InactiveClientError":{"description":"Returned when the authenticated API client is revoked, expired, deleted, or in a non-active status. HTTP 403. No structured details beyond the human-readable message.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","properties":{"code":{"type":"string","const":"inactive_client"}}}]},"InvalidWebhookUrlError":{"description":"Returned when target_url fails HTTPS validation or is otherwise rejected. HTTP 422. No structured details field beyond the human-readable message.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","properties":{"code":{"type":"string","const":"invalid_webhook_url"}}}]},"GenericApiError":{"description":"Catch-all error variant for codes that carry no structured remediation data (invalid_cursor, not_found, invalid_credentials, scope_not_allowed, invalid_webhook_tolerance, internal_error, and any future codes not covered by a typed variant in the discriminator.mapping). details is absent or empty for these codes.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","properties":{"details":{"type":"object","additionalProperties":true,"description":"Absent or empty for most generic error codes."}}}]}}},"paths":{"/connect/clinical-schedules/{clinicalScheduleId}":{"patch":{"operationId":"patchClinicalSchedule","tags":["Clinical Schedules"],"summary":"Partially update a clinical schedule.","description":"Applies only the provided fields. Absent scalar fields are left unchanged. Requires clinical-schedules:write scope. For M2M arrays (site_ids, admin_user_ids, participant_user_ids, view_only_user_ids): omit to leave unchanged; supply (including an empty array) to replace the full set. view_role_names is read-only and cannot be set via this API. Known read-only fields are silently ignored; genuinely unknown fields are rejected. Legacy analog is PATCH /api-banner/program-schedules/{id}/.","parameters":[{"$ref":"#/components/parameters/ApiDateHeader"},{"$ref":"#/components/parameters/ClinicalScheduleId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":255},"description":{"type":"string","nullable":true},"schedule_details":{"type":"boolean"},"show_rotation_tab_default":{"type":"boolean"},"restrict_rotation_view":{"type":"boolean"},"show_block_naming":{"type":"boolean"},"enable_rotation_block_directors":{"type":"boolean"},"site_ids":{"type":"array","items":{"type":"string"},"nullable":true,"description":"Pass to replace the full site set. Pass [] to clear all sites. Omit to leave unchanged."},"admin_user_ids":{"type":"array","items":{"type":"string"},"nullable":true,"description":"Pass to replace the full admin set. Pass [] to clear all admins. Omit to leave unchanged."},"participant_user_ids":{"type":"array","items":{"type":"string"},"nullable":true,"description":"Pass to replace the full participant set. Omit to leave unchanged."},"view_only_user_ids":{"type":"array","items":{"type":"string"},"nullable":true,"description":"Pass to replace the full view-only user set. Omit to leave unchanged."}}}}}},"responses":{"200":{"$ref":"#/components/responses/ClinicalScheduleItemResponse"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}}}}
```

## Add a participant user to a clinical schedule.

> Appends one user as a participant (access\_type='participant') on a clinical schedule. Requires clinical-schedules:write scope. Idempotent on the unique constraint (schedule\_id, user\_id) — adding the same user twice still returns 204. If the user already exists with access\_type='view\_only', their access\_type is updated to 'participant'. Legacy analog is POST /api-banner/program-schedules/{id}/add\_user/.

```json
{"openapi":"3.1.0","info":{"title":"Medtrics Connect API","version":"2026-10-27"},"tags":[{"name":"Clinical Schedules","description":"Clinical schedule definitions per program. Aggregates config flags, M2M user/site/role access lists, and schedule metadata. Legacy analog is /api-banner/program-schedules/."}],"servers":[{"url":"https://sandbox.mymedtrics.com","description":"Client sandbox — shared demo tenant, X-Medtrics-Sandbox-Key or a sandbox API app"},{"url":"https://{client}.mymedtrics.com","description":"Your Medtrics instance","variables":{"client":{"default":"<client-name>","description":"The tenant subdomain Medtrics assigns to your institution"}}}],"security":[{"BearerToken":[]},{"SandboxApiKey":[]}],"components":{"securitySchemes":{"BearerToken":{"type":"http","scheme":"bearer"},"SandboxApiKey":{"type":"apiKey","in":"header","name":"X-Medtrics-Sandbox-Key"}},"parameters":{"ApiDateHeader":{"name":"Medtrics-API-Date","in":"header","required":true,"description":"API contract date. Use 2026-10-27 for this version. Contract dates select request behavior: rotation creates require explicit placement references from 2026-10-27; earlier accepted dates retain compatibility handling. Unknown dates return HTTP 400 with error.code unsupported_api_date.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},"ClinicalScheduleId":{"name":"clinicalScheduleId","in":"path","required":true,"schema":{"type":"string"},"description":"Public ID of the clinical schedule (csched_ prefix). Backed by clinical_schedules.id."}},"responses":{"NoContent":{"description":"Success with no response body.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"BadRequest":{"description":"Invalid request parameters.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"Unauthorized":{"description":"Missing or invalid credentials.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"Forbidden":{"description":"Credential lacks tenant visibility or required scope.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"NotFound":{"description":"Resource not found or outside tenant scope.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"RateLimited":{"description":"Request or API app rate limit exceeded.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"InternalError":{"description":"Unexpected server error.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"headers":{"MedtricsRequestId":{"description":"Correlates all gateway logs for the request.","schema":{"type":"string"}},"MedtricsApiDate":{"description":"API contract date applied to the request.","schema":{"type":"string"}},"RateLimitLimit":{"description":"Request limit for the current window.","schema":{"type":"string"}},"RateLimitRemaining":{"description":"Remaining requests in the current window.","schema":{"type":"string"}},"RateLimitReset":{"description":"Unix seconds when the current rate-limit window resets.","schema":{"type":"string"}}},"schemas":{"ErrorEnvelope":{"type":"object","required":["error"],"description":"Structured error envelope. error.code is the machine-readable discriminant. Typed detail schemas are provided for codes where structured data aids client remediation. Use the discriminator.mapping to select the correct variant schema; for all other codes see GenericApiError.\n","properties":{"error":{"anyOf":[{"$ref":"#/components/schemas/InvalidStatusError"},{"$ref":"#/components/schemas/InvalidWebhookEventsError"},{"$ref":"#/components/schemas/RateLimitedError"},{"$ref":"#/components/schemas/CursorResourceMismatchError"},{"$ref":"#/components/schemas/InvalidIdError"},{"$ref":"#/components/schemas/UnsupportedApiDateError"},{"$ref":"#/components/schemas/InactiveClientError"},{"$ref":"#/components/schemas/InvalidWebhookUrlError"},{"$ref":"#/components/schemas/GenericApiError"}],"discriminator":{"propertyName":"code","mapping":{"invalid_status":"#/components/schemas/InvalidStatusError","invalid_webhook_events":"#/components/schemas/InvalidWebhookEventsError","rate_limited":"#/components/schemas/RateLimitedError","cursor_resource_mismatch":"#/components/schemas/CursorResourceMismatchError","invalid_id":"#/components/schemas/InvalidIdError","unsupported_api_date":"#/components/schemas/UnsupportedApiDateError","inactive_client":"#/components/schemas/InactiveClientError","invalid_webhook_url":"#/components/schemas/InvalidWebhookUrlError"}}}}},"InvalidStatusError":{"description":"Returned when a status query parameter value is outside the allowed enum. HTTP 400. details.field identifies the parameter; details.allowed lists every accepted value.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"invalid_status"},"details":{"type":"object","required":["field","allowed"],"additionalProperties":false,"description":"Structured remediation data for invalid_status.","properties":{"field":{"type":"string","description":"Query parameter name that received the invalid value (e.g. enrollment_status, status).\n"},"allowed":{"type":"array","items":{"type":"string"},"description":"Full list of accepted values for the parameter."}}}}}]},"ApiErrorBase":{"type":"object","required":["code","message","request_id"],"description":"Common fields shared by every Connect API error response.","properties":{"code":{"type":"string","description":"Machine-readable error code."},"message":{"type":"string","description":"Human-readable error description."},"request_id":{"type":"string","description":"Request correlation ID. Include this value when reporting issues to support alongside the Medtrics-Request-ID response header.\n"}}},"InvalidWebhookEventsError":{"description":"Returned when event_types contains values not in the approved Connect API event list, or when event_types is empty. HTTP 400. details.unsupported_events lists the rejected entries (empty array when event_types was missing or entirely empty).\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"invalid_webhook_events"},"details":{"type":"object","required":["unsupported_events"],"additionalProperties":false,"description":"Structured remediation data for invalid_webhook_events.","properties":{"unsupported_events":{"type":"array","items":{"type":"string"},"description":"Event type strings that are not approved Connect API event names. Empty array indicates event_types was missing or contained only invalid entries.\n"}}}}}]},"RateLimitedError":{"description":"Returned when the request or API app rate limit is exceeded. HTTP 429. details.retry_after_seconds indicates the wait duration before retrying.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"rate_limited"},"details":{"type":"object","required":["retry_after_seconds"],"additionalProperties":false,"description":"Structured remediation data for rate_limited.","properties":{"retry_after_seconds":{"type":"integer","description":"Seconds to wait before retrying. Mirrors the Retry-After response header value.\n"}}}}}]},"CursorResourceMismatchError":{"description":"Returned when the cursor was issued for a different resource type than the endpoint being called. HTTP 400. details.expected_resource is the resource this endpoint serves; details.cursor_resource is what the cursor encodes.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"cursor_resource_mismatch"},"details":{"type":"object","required":["expected_resource","cursor_resource"],"additionalProperties":false,"description":"Structured remediation data for cursor_resource_mismatch.","properties":{"expected_resource":{"type":"string","description":"Resource type this endpoint serves (e.g. programs, learners)."},"cursor_resource":{"type":"string","description":"Resource type encoded in the submitted cursor."}}}}}]},"InvalidIdError":{"description":"Returned when a path parameter public ID does not match the expected prefix for the resource. HTTP 400. details.field is the parameter name; details.expected_prefix is the required ID prefix.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"invalid_id"},"details":{"type":"object","required":["field","expected_prefix"],"additionalProperties":false,"description":"Structured remediation data for invalid_id.","properties":{"field":{"type":"string","description":"Path or query parameter name that received the invalid ID."},"expected_prefix":{"type":"string","description":"Required ID prefix for this parameter."}}}}}]},"UnsupportedApiDateError":{"description":"Returned when the Medtrics-API-Date header is missing, malformed (not YYYY-MM-DD), or not a supported contract date. HTTP 400. details.supported_dates lists all accepted values. details.reason is one of \"missing\", \"malformed\", or \"not_supported\".\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"unsupported_api_date"},"details":{"type":"object","required":["reason","supported_dates"],"additionalProperties":false,"description":"Structured remediation data for unsupported_api_date.","properties":{"reason":{"type":"string","enum":["missing","malformed","not_supported"],"description":"Specific cause: \"missing\" when the header is absent, \"malformed\" when the value is not YYYY-MM-DD format, \"not_supported\" when the format is valid but the date is not a known contract date.\n"},"supported_dates":{"type":"array","items":{"type":"string"},"description":"All accepted Medtrics-API-Date values."}}}}}]},"InactiveClientError":{"description":"Returned when the authenticated API client is revoked, expired, deleted, or in a non-active status. HTTP 403. No structured details beyond the human-readable message.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","properties":{"code":{"type":"string","const":"inactive_client"}}}]},"InvalidWebhookUrlError":{"description":"Returned when target_url fails HTTPS validation or is otherwise rejected. HTTP 422. No structured details field beyond the human-readable message.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","properties":{"code":{"type":"string","const":"invalid_webhook_url"}}}]},"GenericApiError":{"description":"Catch-all error variant for codes that carry no structured remediation data (invalid_cursor, not_found, invalid_credentials, scope_not_allowed, invalid_webhook_tolerance, internal_error, and any future codes not covered by a typed variant in the discriminator.mapping). details is absent or empty for these codes.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","properties":{"details":{"type":"object","additionalProperties":true,"description":"Absent or empty for most generic error codes."}}}]}}},"paths":{"/connect/clinical-schedules/{clinicalScheduleId}/users":{"post":{"operationId":"addClinicalScheduleUser","tags":["Clinical Schedules"],"summary":"Add a participant user to a clinical schedule.","description":"Appends one user as a participant (access_type='participant') on a clinical schedule. Requires clinical-schedules:write scope. Idempotent on the unique constraint (schedule_id, user_id) — adding the same user twice still returns 204. If the user already exists with access_type='view_only', their access_type is updated to 'participant'. Legacy analog is POST /api-banner/program-schedules/{id}/add_user/.","parameters":[{"$ref":"#/components/parameters/ApiDateHeader"},{"$ref":"#/components/parameters/ClinicalScheduleId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["user_id"],"properties":{"user_id":{"type":"string","description":"Public ID of the user to add as a participant (user_ prefix)."}}}}}},"responses":{"204":{"$ref":"#/components/responses/NoContent"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}}}}
```

## Add an administrator to a clinical schedule.

> Appends one user as a schedule administrator (clinical\_schedule\_admins). Requires clinical-schedules:write scope. Idempotent on the unique constraint (schedule\_id, user\_id) — adding the same admin twice still returns 204. Legacy analog is POST /api-banner/program-schedules/{id}/add\_admin/.

```json
{"openapi":"3.1.0","info":{"title":"Medtrics Connect API","version":"2026-10-27"},"tags":[{"name":"Clinical Schedules","description":"Clinical schedule definitions per program. Aggregates config flags, M2M user/site/role access lists, and schedule metadata. Legacy analog is /api-banner/program-schedules/."}],"servers":[{"url":"https://sandbox.mymedtrics.com","description":"Client sandbox — shared demo tenant, X-Medtrics-Sandbox-Key or a sandbox API app"},{"url":"https://{client}.mymedtrics.com","description":"Your Medtrics instance","variables":{"client":{"default":"<client-name>","description":"The tenant subdomain Medtrics assigns to your institution"}}}],"security":[{"BearerToken":[]},{"SandboxApiKey":[]}],"components":{"securitySchemes":{"BearerToken":{"type":"http","scheme":"bearer"},"SandboxApiKey":{"type":"apiKey","in":"header","name":"X-Medtrics-Sandbox-Key"}},"parameters":{"ApiDateHeader":{"name":"Medtrics-API-Date","in":"header","required":true,"description":"API contract date. Use 2026-10-27 for this version. Contract dates select request behavior: rotation creates require explicit placement references from 2026-10-27; earlier accepted dates retain compatibility handling. Unknown dates return HTTP 400 with error.code unsupported_api_date.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},"ClinicalScheduleId":{"name":"clinicalScheduleId","in":"path","required":true,"schema":{"type":"string"},"description":"Public ID of the clinical schedule (csched_ prefix). Backed by clinical_schedules.id."}},"responses":{"NoContent":{"description":"Success with no response body.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"BadRequest":{"description":"Invalid request parameters.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"Unauthorized":{"description":"Missing or invalid credentials.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"Forbidden":{"description":"Credential lacks tenant visibility or required scope.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"NotFound":{"description":"Resource not found or outside tenant scope.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"RateLimited":{"description":"Request or API app rate limit exceeded.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"InternalError":{"description":"Unexpected server error.","headers":{"Medtrics-Request-ID":{"$ref":"#/components/headers/MedtricsRequestId"},"Medtrics-API-Date":{"$ref":"#/components/headers/MedtricsApiDate"},"Medtrics-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"Medtrics-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"Medtrics-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"headers":{"MedtricsRequestId":{"description":"Correlates all gateway logs for the request.","schema":{"type":"string"}},"MedtricsApiDate":{"description":"API contract date applied to the request.","schema":{"type":"string"}},"RateLimitLimit":{"description":"Request limit for the current window.","schema":{"type":"string"}},"RateLimitRemaining":{"description":"Remaining requests in the current window.","schema":{"type":"string"}},"RateLimitReset":{"description":"Unix seconds when the current rate-limit window resets.","schema":{"type":"string"}}},"schemas":{"ErrorEnvelope":{"type":"object","required":["error"],"description":"Structured error envelope. error.code is the machine-readable discriminant. Typed detail schemas are provided for codes where structured data aids client remediation. Use the discriminator.mapping to select the correct variant schema; for all other codes see GenericApiError.\n","properties":{"error":{"anyOf":[{"$ref":"#/components/schemas/InvalidStatusError"},{"$ref":"#/components/schemas/InvalidWebhookEventsError"},{"$ref":"#/components/schemas/RateLimitedError"},{"$ref":"#/components/schemas/CursorResourceMismatchError"},{"$ref":"#/components/schemas/InvalidIdError"},{"$ref":"#/components/schemas/UnsupportedApiDateError"},{"$ref":"#/components/schemas/InactiveClientError"},{"$ref":"#/components/schemas/InvalidWebhookUrlError"},{"$ref":"#/components/schemas/GenericApiError"}],"discriminator":{"propertyName":"code","mapping":{"invalid_status":"#/components/schemas/InvalidStatusError","invalid_webhook_events":"#/components/schemas/InvalidWebhookEventsError","rate_limited":"#/components/schemas/RateLimitedError","cursor_resource_mismatch":"#/components/schemas/CursorResourceMismatchError","invalid_id":"#/components/schemas/InvalidIdError","unsupported_api_date":"#/components/schemas/UnsupportedApiDateError","inactive_client":"#/components/schemas/InactiveClientError","invalid_webhook_url":"#/components/schemas/InvalidWebhookUrlError"}}}}},"InvalidStatusError":{"description":"Returned when a status query parameter value is outside the allowed enum. HTTP 400. details.field identifies the parameter; details.allowed lists every accepted value.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"invalid_status"},"details":{"type":"object","required":["field","allowed"],"additionalProperties":false,"description":"Structured remediation data for invalid_status.","properties":{"field":{"type":"string","description":"Query parameter name that received the invalid value (e.g. enrollment_status, status).\n"},"allowed":{"type":"array","items":{"type":"string"},"description":"Full list of accepted values for the parameter."}}}}}]},"ApiErrorBase":{"type":"object","required":["code","message","request_id"],"description":"Common fields shared by every Connect API error response.","properties":{"code":{"type":"string","description":"Machine-readable error code."},"message":{"type":"string","description":"Human-readable error description."},"request_id":{"type":"string","description":"Request correlation ID. Include this value when reporting issues to support alongside the Medtrics-Request-ID response header.\n"}}},"InvalidWebhookEventsError":{"description":"Returned when event_types contains values not in the approved Connect API event list, or when event_types is empty. HTTP 400. details.unsupported_events lists the rejected entries (empty array when event_types was missing or entirely empty).\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"invalid_webhook_events"},"details":{"type":"object","required":["unsupported_events"],"additionalProperties":false,"description":"Structured remediation data for invalid_webhook_events.","properties":{"unsupported_events":{"type":"array","items":{"type":"string"},"description":"Event type strings that are not approved Connect API event names. Empty array indicates event_types was missing or contained only invalid entries.\n"}}}}}]},"RateLimitedError":{"description":"Returned when the request or API app rate limit is exceeded. HTTP 429. details.retry_after_seconds indicates the wait duration before retrying.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"rate_limited"},"details":{"type":"object","required":["retry_after_seconds"],"additionalProperties":false,"description":"Structured remediation data for rate_limited.","properties":{"retry_after_seconds":{"type":"integer","description":"Seconds to wait before retrying. Mirrors the Retry-After response header value.\n"}}}}}]},"CursorResourceMismatchError":{"description":"Returned when the cursor was issued for a different resource type than the endpoint being called. HTTP 400. details.expected_resource is the resource this endpoint serves; details.cursor_resource is what the cursor encodes.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"cursor_resource_mismatch"},"details":{"type":"object","required":["expected_resource","cursor_resource"],"additionalProperties":false,"description":"Structured remediation data for cursor_resource_mismatch.","properties":{"expected_resource":{"type":"string","description":"Resource type this endpoint serves (e.g. programs, learners)."},"cursor_resource":{"type":"string","description":"Resource type encoded in the submitted cursor."}}}}}]},"InvalidIdError":{"description":"Returned when a path parameter public ID does not match the expected prefix for the resource. HTTP 400. details.field is the parameter name; details.expected_prefix is the required ID prefix.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"invalid_id"},"details":{"type":"object","required":["field","expected_prefix"],"additionalProperties":false,"description":"Structured remediation data for invalid_id.","properties":{"field":{"type":"string","description":"Path or query parameter name that received the invalid ID."},"expected_prefix":{"type":"string","description":"Required ID prefix for this parameter."}}}}}]},"UnsupportedApiDateError":{"description":"Returned when the Medtrics-API-Date header is missing, malformed (not YYYY-MM-DD), or not a supported contract date. HTTP 400. details.supported_dates lists all accepted values. details.reason is one of \"missing\", \"malformed\", or \"not_supported\".\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["details"],"properties":{"code":{"type":"string","const":"unsupported_api_date"},"details":{"type":"object","required":["reason","supported_dates"],"additionalProperties":false,"description":"Structured remediation data for unsupported_api_date.","properties":{"reason":{"type":"string","enum":["missing","malformed","not_supported"],"description":"Specific cause: \"missing\" when the header is absent, \"malformed\" when the value is not YYYY-MM-DD format, \"not_supported\" when the format is valid but the date is not a known contract date.\n"},"supported_dates":{"type":"array","items":{"type":"string"},"description":"All accepted Medtrics-API-Date values."}}}}}]},"InactiveClientError":{"description":"Returned when the authenticated API client is revoked, expired, deleted, or in a non-active status. HTTP 403. No structured details beyond the human-readable message.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","properties":{"code":{"type":"string","const":"inactive_client"}}}]},"InvalidWebhookUrlError":{"description":"Returned when target_url fails HTTPS validation or is otherwise rejected. HTTP 422. No structured details field beyond the human-readable message.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","properties":{"code":{"type":"string","const":"invalid_webhook_url"}}}]},"GenericApiError":{"description":"Catch-all error variant for codes that carry no structured remediation data (invalid_cursor, not_found, invalid_credentials, scope_not_allowed, invalid_webhook_tolerance, internal_error, and any future codes not covered by a typed variant in the discriminator.mapping). details is absent or empty for these codes.\n","allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","properties":{"details":{"type":"object","additionalProperties":true,"description":"Absent or empty for most generic error codes."}}}]}}},"paths":{"/connect/clinical-schedules/{clinicalScheduleId}/admins":{"post":{"operationId":"addClinicalScheduleAdmin","tags":["Clinical Schedules"],"summary":"Add an administrator to a clinical schedule.","description":"Appends one user as a schedule administrator (clinical_schedule_admins). Requires clinical-schedules:write scope. Idempotent on the unique constraint (schedule_id, user_id) — adding the same admin twice still returns 204. Legacy analog is POST /api-banner/program-schedules/{id}/add_admin/.","parameters":[{"$ref":"#/components/parameters/ApiDateHeader"},{"$ref":"#/components/parameters/ClinicalScheduleId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["user_id"],"properties":{"user_id":{"type":"string","description":"Public ID of the user to add as an administrator (user_ prefix)."}}}}}},"responses":{"204":{"$ref":"#/components/responses/NoContent"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}}}}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.mymedtrics.com/api-reference/connect-api/clinical-schedules.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
