> ## Documentation Index
> Fetch the complete documentation index at: https://docs.scaling.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# Rotation layers

> Add, update, and remove rotation layers from an on-call schedule.

Rotation layers are sub-resources of schedules. Each layer defines a rotation pattern — who rotates, how frequently, and during what time window. A schedule can have multiple layers; when resolving who is on call, scaling.cloud evaluates all active layers for that schedule.

Each layer has a `position` (integer, unique per schedule) that controls precedence for ownership resolution. Lower position = higher precedence; position 0 is the **owner-bearing layer** — its resolved participant becomes the **Schedule Owner** at incident creation. New layers are appended at the next available position; use the [reorder endpoint](#put-oncall-schedules-scheduleid-layers-order) to change the order.

<Note>
  All layer endpoints require the parent schedule's ID in the path. To manage the schedule itself (name, timezone, description), see [Schedules](/api/oncall/schedules).
</Note>

## Rotation types

Each layer has a `rotationType` that controls how `rotationLengthDays` is interpreted:

| `rotationType` | Behavior                                                                         |
| -------------- | -------------------------------------------------------------------------------- |
| `daily`        | Each participant is on call for one day. `rotationLengthDays` must be `1`.       |
| `weekly`       | Each participant is on call for one week. `rotationLengthDays` must be `7`.      |
| `custom`       | Each participant is on call for `rotationLengthDays` days. Set to any value ≥ 1. |

The `handoffTime` field specifies the time of day (in the parent schedule's timezone) when the rotation hands off from one participant to the next.

***

## `POST /oncall/schedules/{scheduleId}/layers`

Add a rotation layer to an existing schedule. The layer name must be unique within the schedule.

### Path parameters

<ParamField path="scheduleId" type="string" required>
  The UUID of the schedule to add the layer to.
</ParamField>

### Request body

<ParamField body="name" type="string" required>
  Display name for the layer. Must be unique within the schedule. 1–255 characters.
</ParamField>

<ParamField body="rotationType" type="string" required>
  Rotation frequency. One of `daily`, `weekly`, or `custom`.
</ParamField>

<ParamField body="rotationLengthDays" type="number" required>
  Number of days each participant is on call before the rotation advances. Minimum `1`. For `daily` this should be `1`; for `weekly` this should be `7`.
</ParamField>

<ParamField body="handoffTime" type="string" required>
  Time of day when the rotation hands off to the next participant. Format: `HH:MM` or `HH:MM:SS` in 24-hour time (e.g. `09:00`). Interpreted in the schedule's timezone.
</ParamField>

<ParamField body="effectiveFrom" type="string" required>
  ISO 8601 datetime when this layer becomes active. The rotation starts from this point and the first participant in `participantIds` is on call.
</ParamField>

<ParamField body="effectiveUntil" type="string | null">
  ISO 8601 datetime when this layer stops being active. Pass `null` (or omit) for an open-ended layer with no end date.
</ParamField>

<ParamField body="participantIds" type="string[]" required>
  Ordered list of Clerk user IDs for the rotation. The rotation cycles through this list in order. Must contain at least 1 and at most 100 entries. All participants must be members of your organization.
</ParamField>

### Response fields

<ResponseField name="data" type="object" required>
  The created rotation layer.

  <Expandable title="Layer object fields">
    <ResponseField name="id" type="string" required>
      Unique layer ID (UUID).
    </ResponseField>

    <ResponseField name="scheduleId" type="string" required>
      ID of the parent schedule.
    </ResponseField>

    <ResponseField name="orgId" type="string" required>
      The organization this layer belongs to.
    </ResponseField>

    <ResponseField name="name" type="string" required>
      Display name of the layer.
    </ResponseField>

    <ResponseField name="position" type="integer" required>
      Ordering within the parent schedule. Lower = higher precedence; position 0 is the owner-bearing layer. Unique per schedule.
    </ResponseField>

    <ResponseField name="rotationType" type="string" required>
      `daily`, `weekly`, or `custom`.
    </ResponseField>

    <ResponseField name="rotationLengthDays" type="number" required>
      Number of days per rotation shift.
    </ResponseField>

    <ResponseField name="handoffTime" type="string" required>
      Handoff time in `HH:MM` format.
    </ResponseField>

    <ResponseField name="effectiveFrom" type="string" required>
      ISO 8601 datetime when the layer becomes active.
    </ResponseField>

    <ResponseField name="effectiveUntil" type="string | null" required>
      ISO 8601 datetime when the layer ends, or `null` for no end date.
    </ResponseField>

    <ResponseField name="participantIds" type="string[]" required>
      Ordered list of user IDs in the rotation.
    </ResponseField>

    <ResponseField name="createdAt" type="string" required>
      ISO 8601 creation timestamp.
    </ResponseField>

    <ResponseField name="updatedAt" type="string" required>
      ISO 8601 last-updated timestamp.
    </ResponseField>
  </Expandable>
</ResponseField>

<RequestExample>
  ```bash cURL — weekly rotation theme={null}
  curl --request POST \
    --url https://api.scaling.cloud/v1/oncall/schedules/a1b2c3d4-e5f6-7890-abcd-ef1234567890/layers \
    --header 'Authorization: Bearer scl_live_your_api_key_here' \
    --header 'Content-Type: application/json' \
    --data '{
      "name": "Primary rotation",
      "rotationType": "weekly",
      "rotationLengthDays": 7,
      "handoffTime": "09:00",
      "effectiveFrom": "2026-05-01T00:00:00.000Z",
      "effectiveUntil": null,
      "participantIds": ["user_aaa111", "user_bbb222", "user_ccc333"]
    }'
  ```

  ```bash cURL — custom 4-day rotation with end date theme={null}
  curl --request POST \
    --url https://api.scaling.cloud/v1/oncall/schedules/a1b2c3d4-e5f6-7890-abcd-ef1234567890/layers \
    --header 'Authorization: Bearer scl_live_your_api_key_here' \
    --header 'Content-Type: application/json' \
    --data '{
      "name": "Q2 sprint rotation",
      "rotationType": "custom",
      "rotationLengthDays": 4,
      "handoffTime": "08:00",
      "effectiveFrom": "2026-04-01T00:00:00.000Z",
      "effectiveUntil": "2026-06-30T23:59:59.000Z",
      "participantIds": ["user_ddd444", "user_eee555"]
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "data": {
      "id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
      "scheduleId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "orgId": "org_2abc123def456",
      "name": "Primary rotation",
      "rotationType": "weekly",
      "rotationLengthDays": 7,
      "handoffTime": "09:00",
      "effectiveFrom": "2026-05-01T00:00:00.000Z",
      "effectiveUntil": null,
      "participantIds": ["user_aaa111", "user_bbb222", "user_ccc333"],
      "createdAt": "2026-04-07T10:00:00.000Z",
      "updatedAt": "2026-04-07T10:00:00.000Z"
    }
  }
  ```

  ```json 400 — invalid participant theme={null}
  {
    "statusCode": 400,
    "type": "invalid_request_error",
    "code": "invalid_participant",
    "requestId": "550e8400-e29b-41d4-a716-446655440000"
  }
  ```

  ```json 404 — schedule not found theme={null}
  {
    "statusCode": 404,
    "type": "invalid_request_error",
    "code": "not_found",
    "requestId": "550e8400-e29b-41d4-a716-446655440000"
  }
  ```
</ResponseExample>

### Error codes

| HTTP  | Code                  | Description                                                        |
| ----- | --------------------- | ------------------------------------------------------------------ |
| `400` | `invalid_participant` | One or more `participantIds` are not members of your organization. |
| `401` | `not_authorized`      | Missing or invalid API key.                                        |
| `404` | `not_found`           | No schedule found with the given `scheduleId`.                     |
| `500` | `server_error`        | Unexpected server error.                                           |

***

## `PATCH /oncall/schedules/{scheduleId}/layers/{layerId}`

Update one or more fields on an existing rotation layer. All fields are optional — only the fields you include are updated.

### Path parameters

<ParamField path="scheduleId" type="string" required>
  The UUID of the parent schedule.
</ParamField>

<ParamField path="layerId" type="string" required>
  The UUID of the layer to update.
</ParamField>

### Request body

<ParamField body="name" type="string">
  New display name for the layer. Must be unique within the schedule. 1–255 characters.
</ParamField>

<ParamField body="rotationType" type="string">
  Updated rotation type. One of `daily`, `weekly`, or `custom`.
</ParamField>

<ParamField body="rotationLengthDays" type="number">
  Updated number of days per rotation shift. Minimum `1`.
</ParamField>

<ParamField body="handoffTime" type="string">
  Updated handoff time. Format: `HH:MM` or `HH:MM:SS` in 24-hour format.
</ParamField>

<ParamField body="effectiveFrom" type="string">
  Updated ISO 8601 datetime when the layer becomes active.
</ParamField>

<ParamField body="effectiveUntil" type="string | null">
  Updated ISO 8601 datetime when the layer ends. Pass `null` to remove the end date and make the layer open-ended.
</ParamField>

<ParamField body="participantIds" type="string[]">
  Updated ordered list of user IDs. Must contain 1–100 entries, all of whom must be org members.
</ParamField>

### Response fields

<ResponseField name="data" type="object" required>
  The updated rotation layer object.
</ResponseField>

<RequestExample>
  ```bash cURL theme={null}
  curl --request PATCH \
    --url https://api.scaling.cloud/v1/oncall/schedules/a1b2c3d4-e5f6-7890-abcd-ef1234567890/layers/c3d4e5f6-a7b8-9012-cdef-123456789012 \
    --header 'Authorization: Bearer scl_live_your_api_key_here' \
    --header 'Content-Type: application/json' \
    --data '{
      "participantIds": ["user_aaa111", "user_bbb222", "user_ccc333", "user_fff666"],
      "handoffTime": "10:00"
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "data": {
      "id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
      "scheduleId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "orgId": "org_2abc123def456",
      "name": "Primary rotation",
      "rotationType": "weekly",
      "rotationLengthDays": 7,
      "handoffTime": "10:00",
      "effectiveFrom": "2026-05-01T00:00:00.000Z",
      "effectiveUntil": null,
      "participantIds": ["user_aaa111", "user_bbb222", "user_ccc333", "user_fff666"],
      "createdAt": "2026-04-07T10:00:00.000Z",
      "updatedAt": "2026-04-07T11:15:00.000Z"
    }
  }
  ```

  ```json 400 — invalid participant theme={null}
  {
    "statusCode": 400,
    "type": "invalid_request_error",
    "code": "invalid_participant",
    "requestId": "550e8400-e29b-41d4-a716-446655440000"
  }
  ```

  ```json 404 theme={null}
  {
    "statusCode": 404,
    "type": "invalid_request_error",
    "code": "not_found",
    "requestId": "550e8400-e29b-41d4-a716-446655440000"
  }
  ```

  ```json 409 — would orphan overrides theme={null}
  {
    "statusCode": 409,
    "type": "invalid_request_error",
    "code": "layer_edit_would_orphan_overrides",
    "requestId": "550e8400-e29b-41d4-a716-446655440000",
    "details": {
      "rotationLayerId": "c3d4e5f6-a7b8-9012-cdef-123456789012",
      "blockingOverrides": [
        {
          "id": "override_01h2yzabcd",
          "startTime": "2026-06-01T00:00:00.000Z",
          "endTime": "2026-06-02T00:00:00.000Z"
        }
      ]
    }
  }
  ```
</ResponseExample>

### Error codes

| HTTP  | Code                                | Description                                                                                                                                                                                                                                                 |
| ----- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400` | `invalid_participant`               | One or more `participantIds` are not members of your organization.                                                                                                                                                                                          |
| `401` | `not_authorized`                    | Missing or invalid API key.                                                                                                                                                                                                                                 |
| `404` | `not_found`                         | No layer found with the given `layerId` on this schedule.                                                                                                                                                                                                   |
| `409` | `layer_edit_would_orphan_overrides` | The new `effectiveFrom`/`effectiveUntil` would leave existing overrides outside the layer's effective window. `details.blockingOverrides` lists every offending override's `id`, `startTime`, and `endTime` so you can cancel or move them before retrying. |
| `500` | `server_error`                      | Unexpected server error.                                                                                                                                                                                                                                    |

***

## `DELETE /oncall/schedules/{scheduleId}/layers/{layerId}`

Remove a rotation layer from a schedule. This action is permanent and cannot be undone.

### Path parameters

<ParamField path="scheduleId" type="string" required>
  The UUID of the parent schedule.
</ParamField>

<ParamField path="layerId" type="string" required>
  The UUID of the layer to delete.
</ParamField>

### Response fields

<ResponseField name="success" type="boolean" required>
  `true` when the layer was deleted.
</ResponseField>

<RequestExample>
  ```bash cURL theme={null}
  curl --request DELETE \
    --url https://api.scaling.cloud/v1/oncall/schedules/a1b2c3d4-e5f6-7890-abcd-ef1234567890/layers/c3d4e5f6-a7b8-9012-cdef-123456789012 \
    --header 'Authorization: Bearer scl_live_your_api_key_here'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "success": true
  }
  ```

  ```json 404 theme={null}
  {
    "statusCode": 404,
    "type": "invalid_request_error",
    "code": "not_found",
    "requestId": "550e8400-e29b-41d4-a716-446655440000"
  }
  ```

  ```json 409 — dependent overrides theme={null}
  {
    "statusCode": 409,
    "type": "invalid_request_error",
    "code": "layer_has_dependent_overrides",
    "requestId": "550e8400-e29b-41d4-a716-446655440000",
    "details": {
      "rotationLayerId": "c3d4e5f6-a7b8-9012-cdef-123456789012",
      "blockingOverrides": [
        {
          "id": "override_01h2yzabcd",
          "startTime": "2026-06-01T00:00:00.000Z",
          "endTime": "2026-06-02T00:00:00.000Z"
        }
      ]
    }
  }
  ```
</ResponseExample>

### Error codes

| HTTP  | Code                            | Description                                                                                                                                                                                               |
| ----- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401` | `not_authorized`                | Missing or invalid API key.                                                                                                                                                                               |
| `404` | `not_found`                     | No layer found with the given `layerId` on this schedule.                                                                                                                                                 |
| `409` | `layer_has_dependent_overrides` | One or more overrides — past, present, or future — still reference this layer. `details.blockingOverrides` lists every blocking override's `id`, `startTime`, and `endTime`; cancel each before retrying. |
| `500` | `server_error`                  | Unexpected server error.                                                                                                                                                                                  |

***

## `PUT /oncall/schedules/{scheduleId}/layers/order`

Reorder the rotation layers of a schedule (PUT-full-replacement). Send the full ordered list of layer IDs; the server assigns each layer's `position` from its index in the array (`layerIds[0]` becomes position 0, the owner-bearing layer).

The request must reference every existing layer of the schedule exactly once — no additions, no removals, no duplicates. Use [`POST .../layers`](#post-oncall-schedules-scheduleid-layers) and [`DELETE .../layers/{layerId}`](#delete-oncall-schedules-scheduleid-layers-layerid) for those operations.

### Path parameters

<ParamField path="scheduleId" type="string" required>
  The UUID of the schedule whose layers should be reordered.
</ParamField>

### Request body

<ParamField body="layerIds" type="string[]" required>
  Full ordered list of rotation layer IDs for the schedule. Each ID must reference an existing layer on this schedule; the array must contain every layer exactly once.
</ParamField>

### Response fields

<ResponseField name="success" type="boolean" required>
  Always `true` on success. Re-fetch the schedule (or the layer list) to observe the new `position` values.
</ResponseField>

<RequestExample>
  ```bash cURL theme={null}
  curl --request PUT \
    --url https://api.scaling.cloud/v1/oncall/schedules/a1b2c3d4-e5f6-7890-abcd-ef1234567890/layers/order \
    --header 'Authorization: Bearer scl_live_your_api_key_here' \
    --header 'Content-Type: application/json' \
    --data '{
      "layerIds": [
        "d4e5f6a7-b8c9-0123-defa-234567890123",
        "c3d4e5f6-a7b8-9012-cdef-123456789012"
      ]
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  { "success": true }
  ```

  ```json 400 — list does not cover every layer exactly once theme={null}
  {
    "statusCode": 400,
    "type": "invalid_request_error",
    "code": "invalid_reorder_request",
    "requestId": "550e8400-e29b-41d4-a716-446655440000",
    "message": "replaceLayerOrder must receive every layer of the schedule"
  }
  ```

  ```json 404 — schedule not found theme={null}
  {
    "statusCode": 404,
    "type": "invalid_request_error",
    "code": "not_found",
    "requestId": "550e8400-e29b-41d4-a716-446655440000"
  }
  ```
</ResponseExample>

### Error codes

| HTTP  | Code                      | Description                                                              |
| ----- | ------------------------- | ------------------------------------------------------------------------ |
| `400` | `invalid_reorder_request` | The `layerIds` array does not include every existing layer exactly once. |
| `401` | `not_authorized`          | Missing or invalid API key, or caller lacks the admin role.              |
| `404` | `not_found`               | Schedule not found in your organization.                                 |
| `500` | `server_error`            | Unexpected server error.                                                 |
