Help center Rotation API

Learn how to interact with Rotation App programmatically using our REST API to get on-call users, assign users, and manage rotation queues.

Who can use the API?
  • All Slack workspace users with access to the rotation.
  • API authentication requires an API key that you can generate from the Home tab.

The Rotation App REST API provides developers with programmatic access to rotation data and controls. Use this API to build custom applications, scripts, and integrations that interact directly with your rotations through HTTP requests.

Example use cases:

  • Build monitoring dashboards that fetch current on-call users via API calls.
  • Create custom scheduling scripts that assign users based on complex business logic.
  • Integrate rotation data into existing applications and services (project management, incident management, etc.)
  • Develop automated tools that manage rotation queues programmatically instead of on a schedule.
  • Build custom incident management systems that query and update rotations.
Looking for other integration options?

Webhooks: Receive real-time event notifications when rotations change

• Developers: Build event-driven applications that react to rotation changes
• No-code: Connect with tools like Slack Workflows, Zapier, Make, or other webhook-compatible platforms

Slack Workflows: No-code automation directly within Slack

• Set up automated responses and notifications using Slack’s visual Workflow Builder

API Base URL and Authentication

All API requests should be made to:

https://api.rotation.app/v1/t/<team_id>/r/<rotation_id>/<action_type>

URL Parameters:

  • team_id: Your Slack workspace/team ID
  • rotation_id: The unique identifier of your rotation
  • action_type: The specific API action you want to perform

Actions that concern the whole workspace rather than one rotation, such as Time Off, drop the rotation part: https://api.rotation.app/v1/t/<team_id>/<action_type>.

Authentication:

All API requests require authentication using an API key. Include your API key in the x-api-key header:

-H "x-api-key: YOUR_API_KEY"

Learn how to configure your API key

Finding Your Team and Rotation IDs

To use the API, you’ll need your team_id and rotation_id. Learn how to find these identifiers in our dedicated guide: Learn how to find your rotation and team IDs

Available API Actions

Read shifts:

Modify the queue:

Modify individual shifts:

Time off (workspace-wide, not tied to a rotation):


Get Current Shift Assignees

Retrieve the list of users currently assigned to the rotation.

Endpoint: GET /v1/t/<team_id>/r/<rotation_id>/get_current_shift_assignees

Parameters: None required

Example Request:

curl -X GET "https://api.rotation.app/v1/t/T123456/r/ROTA123/get_current_shift_assignees" \
  -H "x-api-key: YOUR_API_KEY"

Response Format:

{
  "current_shift_user_ids": ["user_id_1", "user_id_2", "user_id_3"],
  "current_shift_user_ids_0": "user_id_1",
  "current_shift_user_ids_1": "user_id_2",
  "current_shift_user_ids_2": "user_id_3"
}

Response Fields:

  • current_shift_user_ids: Array of user IDs currently on call
  • current_shift_user_ids_<index>: Individual user ID at the specified index (provided for no-code tool compatibility)

Get Next Shift Assignees

Retrieve the list of users who will be assigned in the next shift.

Endpoint: GET /v1/t/<team_id>/r/<rotation_id>/get_next_shift_assignees

Parameters: None required

Example Request:

curl -X GET "https://api.rotation.app/v1/t/T123456/r/ROTA123/get_next_shift_assignees" \
  -H "x-api-key: YOUR_API_KEY"

Response Format:

{
  "next_shift_user_ids": ["user_id_1", "user_id_2", "user_id_3"],
  "next_shift_user_ids_0": "user_id_1",
  "next_shift_user_ids_1": "user_id_2",
  "next_shift_user_ids_2": "user_id_3"
}

Response Fields:

  • next_shift_user_ids: Array of user IDs assigned to the next shift
  • next_shift_user_ids_<index>: Individual user ID at the specified index (provided for no-code tool compatibility)

Get Other Shift Assignees

Retrieve the list of users assigned to future shifts (3rd, 4th, or 5th shift from now).

Available Endpoints:

  • GET /v1/t/<team_id>/r/<rotation_id>/get_shift_3_assignees - Third shift
  • GET /v1/t/<team_id>/r/<rotation_id>/get_shift_4_assignees - Fourth shift
  • GET /v1/t/<team_id>/r/<rotation_id>/get_shift_5_assignees - Fifth shift

Parameters: None required

Example Request:

curl -X GET "https://api.rotation.app/v1/t/T123456/r/ROTA123/get_shift_3_assignees" \
  -H "x-api-key: YOUR_API_KEY"

Response Format:

{
  "shift_3_user_ids": ["user_id_1", "user_id_2", "user_id_3"],
  "shift_3_user_ids_0": "user_id_1",
  "shift_3_user_ids_1": "user_id_2",
  "shift_3_user_ids_2": "user_id_3"
}

Response Fields:

  • shift_<N>_user_ids: Array of user IDs assigned to that shift
  • shift_<N>_user_ids_<index>: Individual user ID at the specified index (provided for no-code tool compatibility)

Assign Specific User

Assign a specific user to be on call for the rotation.

Endpoint: POST /v1/t/<team_id>/r/<rotation_id>/assign_user

Body Parameters:

  • user_id (required): The ID of the user to assign

Example Request:

curl -X POST "https://api.rotation.app/v1/t/T123456/r/ROTA123/assign_user" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"user_id": "user_id_1"}'

Response Format:

{
  "on_call_user_ids": ["user_id_1"],
  "on_call_user_ids_0": "user_id_1"
}

Response Fields:

  • on_call_user_ids: Updated array of user IDs now on call
  • on_call_user_ids_<index>: Individual user ID at the specified index

Assign Next User(s)

Assign the next user(s) in the rotation queue to be on call.

Endpoint: POST /v1/t/<team_id>/r/<rotation_id>/assign_next

Parameters: None required

Example Request:

curl -X POST "https://api.rotation.app/v1/t/T123456/r/ROTA123/assign_next" \
  -H "x-api-key: YOUR_API_KEY"

Response Format:

{
  "on_call_user_ids": ["user_id_1", "user_id_2", "user_id_3"],
  "on_call_user_ids_0": "user_id_1",
  "on_call_user_ids_1": "user_id_2",
  "on_call_user_ids_2": "user_id_3"
}

Response Fields:

  • on_call_user_ids: Updated array of user IDs now on call
  • on_call_user_ids_<index>: Individual user ID at the specified index

Shuffle Rotation Queue

Randomize the order of users in the rotation queue.

Endpoint: POST /v1/t/<team_id>/r/<rotation_id>/shuffle_rotation_queue

Body Parameters:

  • include_currently_on_call (optional): Boolean indicating whether currently on-call users should be included in the shuffle

Example Request:

curl -X POST "https://api.rotation.app/v1/t/T123456/r/ROTA123/shuffle_rotation_queue" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"include_currently_on_call": true}'

Response Format:

{
  "on_call_user_ids": ["user_id_1", "user_id_2", "user_id_3"],
  "on_call_user_ids_0": "user_id_1",
  "on_call_user_ids_1": "user_id_2",
  "on_call_user_ids_2": "user_id_3"
}

Response Fields:

  • on_call_user_ids: Updated array of user IDs now on call after shuffle
  • on_call_user_ids_<index>: Individual user ID at the specified index

Get Shifts

Retrieve a range of upcoming shifts with their dates and assignees. Useful for pushing the rotation to dashboards, calendars, or external scheduling tools.

Endpoint: GET /v1/t/<team_id>/r/<rotation_id>/get_shifts

Query Parameters:

  • from (optional): Inclusive lower bound. Either a date (YYYY-MM-DD) or a 1-based shift number ("1", "2", …). Defaults to shift 1 (the current shift).
  • to (optional): Inclusive upper bound, same format as from. Defaults to from + 29 (a 30-shift window). The maximum window size is 365 shifts.
  • verbose (optional): If true, each shift value is an object { "assignees": [...], "naturalAssignees": [...] } instead of a bare user ID, array, or null. Useful for seeing who the queue would have placed at a shift if there were no overrides.

Example Request:

curl -X GET "https://api.rotation.app/v1/t/T123456/r/ROTA123/get_shifts?from=2026-05-04&to=2026-05-08" \
  -H "x-api-key: YOUR_API_KEY"

Response Format:

{
  "shifts": {
    "2026-05-04": "user_id_1",
    "2026-05-05": "user_id_2",
    "2026-05-06": null,
    "2026-05-07": "user_id_3",
    "2026-05-08": "user_id_1"
  }
}

Response Fields:

  • shifts: An object keyed by shift date (YYYY-MM-DD, in the rotation’s local timezone) for cron-scheduled rotations, or by 1-based shift number for manual rotations. Values are user IDs for assigned shifts, arrays of user IDs for group rotations, or null for shifts where nobody is assigned (skipped or cancelled).
  • In verbose mode, each value is an object { "assignees": [...] | null, "naturalAssignees": [...] }. assignees is null for skipped or cancelled shifts; naturalAssignees always shows who the queue would place there in the absence of overrides.

Set Shifts

Substitute, cancel, or set multiple shifts in a single call. This is the most flexible endpoint for syncing your rotation with an external source of truth (e.g. a Google Sheet or planning tool).

Endpoint: POST /v1/t/<team_id>/r/<rotation_id>/set_shifts

Body Parameters:

  • shifts (required): An object keyed by shift date (YYYY-MM-DD) or 1-based shift number ("1", "2", …). Values are:
    • A user ID ("U06S3JJUU92") to substitute that shift with the given user.
    • An array of user IDs (["U06S3JJUU92", "U02M888M3C7"]) for group rotations (length must equal the rotation’s group size).
    • null to cancel the shift (no assignee; following shifts stay on schedule). See Cancelling a shift for the semantics.
  • verbose (optional): If true, each shift value in the response becomes an object { "assignees": [...] | null, "naturalAssignees": [...] } instead of a bare user ID, array, or null.

Example Request (substitute and cancel):

curl -X POST "https://api.rotation.app/v1/t/T123456/r/ROTA123/set_shifts" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "shifts": {
      "2026-05-04": "U06S3JJUU92",
      "2026-05-05": null
    }
  }'

Response Format:

{
  "shifts": {
    "2026-05-04": "U06S3JJUU92",
    "2026-05-05": null
  }
}

Verbose response (?verbose=true):

{
  "shifts": {
    "2026-05-04": {
      "assignees": ["U06S3JJUU92"],
      "naturalAssignees": ["U02M888M3C7"]
    },
    "2026-05-05": {
      "assignees": null,
      "naturalAssignees": ["U06S3JJUU92"]
    }
  }
}

Values are the computed post-write state for each shift you addressed, useful for verifying what actually landed. In verbose mode, naturalAssignees shows who the queue would place there if the override were removed. If a write was rejected (for example, a user ID not in the rotation), the API returns HTTP 400 with a per-key error map under errors.

Dates are interpreted in the rotation’s local timezone. Use shift numbers ("1", "2") if you’d rather address shifts positionally. "1" is the current shift.

Substitute Shift

Substitute the assigned user(s) at a single shift. A convenience wrapper around set_shifts with a one-entry payload, for callers that prefer one operation per request (Slack Workflows, single-shift automations).

Endpoint: POST /v1/t/<team_id>/r/<rotation_id>/substitute_shift

Body Parameters:

  • shift (required): The date (YYYY-MM-DD) or 1-based shift number to substitute.
  • user_id (required for single-user rotations): The user ID to assign for this shift.
  • user_ids (required for group rotations): Array of user IDs (length must equal the rotation’s group size).
  • verbose (optional): Same as set_shifts, toggles the per-shift response shape.

Example Request:

curl -X POST "https://api.rotation.app/v1/t/T123456/r/ROTA123/substitute_shift" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "shift": "2026-05-04",
    "user_id": "U06S3JJUU92"
  }'

Response Format:

Same shape as set_shifts: a shifts object keyed by the shift you addressed, with the computed post-write state as the value.

{
  "shifts": {
    "2026-05-04": "U06S3JJUU92"
  }
}

Cancel Shift

Cancels a single shift. Nobody is assigned, the natural assignee’s turn is used up, and the rest of the queue stays on the original schedule. See Cancelling a shift for the full semantics. A convenience wrapper around set_shifts with a null value.

Endpoint: POST /v1/t/<team_id>/r/<rotation_id>/cancel_shift

Body Parameters:

  • shift (required): The date (YYYY-MM-DD) or 1-based shift number to cancel.
  • verbose (optional): Same as set_shifts, toggles the per-shift response shape.

Example Request:

curl -X POST "https://api.rotation.app/v1/t/T123456/r/ROTA123/cancel_shift" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "shift": "2026-05-04" }'

Response Format:

Same shape as set_shifts. The value is null for the cancelled shift.

{
  "shifts": {
    "2026-05-04": null
  }
}
The INSERT-style skip described in the Manage Rotation Shifts guide (which defers the queue by one shift instead of using up the natural assignee’s turn) isn’t currently exposed in the public API. Use the Slack queue manager for that.

Uncancel Shift

Reverts a previously cancelled shift back to its natural assignee.

Endpoint: POST /v1/t/<team_id>/r/<rotation_id>/uncancel_shift

Body Parameters:

  • shift (required): The date (YYYY-MM-DD) or 1-based shift number to uncancel.
  • verbose (optional): Same as set_shifts, toggles the per-shift response shape.

Example Request:

curl -X POST "https://api.rotation.app/v1/t/T123456/r/ROTA123/uncancel_shift" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "shift": "2026-05-04" }'

Response Format:

Same shape as set_shifts. The value is the natural assignee that now occupies the shift.

{
  "shifts": {
    "2026-05-04": "U06S3JJUU92"
  }
}

Errors:

  • 400 if the shift isn’t currently cancelled or skipped.
  • 400 if the shift was skipped via the queue manager’s INSERT-style “Skip this shift” action. Those need to be undone from Slack’s queue manager first.

Undo Substitution

Remove a substitution from a shift, returning it to the natural assignee.

Endpoint: POST /v1/t/<team_id>/r/<rotation_id>/undo_substitution

Body Parameters:

  • shift (required): The date (YYYY-MM-DD) or 1-based shift number whose substitution to remove.
  • verbose (optional): Same as set_shifts, toggles the per-shift response shape.

Example Request:

curl -X POST "https://api.rotation.app/v1/t/T123456/r/ROTA123/undo_substitution" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "shift": "2026-05-04" }'

Response Format:

Same shape as set_shifts. The value is the natural assignee that now occupies the shift.

{
  "shifts": {
    "2026-05-04": "U06S3JJUU92"
  }
}

Errors:

  • 400 if the shift doesn’t currently have an active substitution to undo.

Time Off

Planned absences (Time Off) belong to a user, not to a rotation, so these endpoints live one level up, without a rotation ID:

https://api.rotation.app/v1/t/<team_id>/<action_type>

They use the same x-api-key header. Use them to mirror an HR system or a spreadsheet: every rotation the user is part of picks the time off up automatically, exactly as if it had been added in Slack.

Get Time Off

Read the time off of the whole workspace or of one user.

Endpoint: GET /v1/t/<team_id>/get_time_off

Query Parameters:

  • user_id (optional): Only this user’s time off.
  • from (optional): Start of the window (YYYY-MM-DD, inclusive). Defaults to today.
  • to (optional): End of the window (YYYY-MM-DD, inclusive). Defaults to one year after from.

Every period that overlaps the window is returned, so a vacation that started before from and is still running is included.

Example Request:

curl -X GET "https://api.rotation.app/v1/t/T123456/get_time_off?user_id=U06S3JJUU92&from=2026-10-01&to=2026-12-31" \
  -H "x-api-key: YOUR_API_KEY"

Response Format:

{
  "time_off": [
    {
      "id": "01JX7E4K3MZ9Q8R2T6V0W1XYZA",
      "user_id": "U06S3JJUU92",
      "start_date": "2026-10-03",
      "end_date": "2026-10-07",
      "start_at": "2026-10-02T22:00:00.000Z",
      "end_at": "2026-10-07T22:00:00.000Z",
      "all_day": true,
      "timezone_offset": 7200,
      "reason": "vacation",
      "note": null,
      "source": "api",
      "external_id": "bamboohr-4711",
      "external_url": null
    }
  ]
}

Response Fields:

  • start_date / end_date: Inclusive local days, in the timezone of timezone_offset (seconds from UTC).
  • start_at / end_at: The same bounds as absolute timestamps; end_at is exclusive.
  • all_day: true for periods made of whole days (always the case for periods added in Slack or via the API); false for a Google Calendar event with times.
  • reason: One of vacation, out_sick, out_of_office, not_available.
  • note: The free-text note, or null.
  • source: manual (added in Slack), api, or google (synced from Google Calendar; read-only).
  • external_id: The identifier you passed to set_time_off, or the Google event ID. null otherwise.
  • external_url: A link to the Google Calendar event, null for other sources.

Set Time Off

Add a period of time off, or update it when you pass the same external_id again. With an external_id, re-running a sync is safe: an unchanged period is left alone, a moved one is updated in place. Without one, every call creates a new period.

Periods that are already over are rejected, so only send absences whose last day is today or later.

Endpoint: POST /v1/t/<team_id>/set_time_off

Body Parameters:

  • user_id (required): The Slack user ID.
  • start_date (required): First day (YYYY-MM-DD).
  • end_date (required): Last day (YYYY-MM-DD), inclusive. Must not be in the past, and at most a year after start_date.
  • reason (optional): vacation (default), out_sick, out_of_office, or not_available.
  • note (optional): Free text, up to 120 characters.
  • external_id (optional): Your own identifier for the absence, e.g. the ID of the leave request in your HR system. Makes the call idempotent.
  • timezone_offset (optional): UTC offset in seconds used to anchor the days. Defaults to the user’s Slack timezone.

Example Request:

curl -X POST "https://api.rotation.app/v1/t/T123456/set_time_off" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "U06S3JJUU92",
    "start_date": "2026-10-03",
    "end_date": "2026-10-07",
    "reason": "vacation",
    "external_id": "bamboohr-4711"
  }'

Response Format:

{
  "time_off": {
    "id": "01JX7E4K3MZ9Q8R2T6V0W1XYZA",
    "user_id": "U06S3JJUU92",
    "start_date": "2026-10-03",
    "end_date": "2026-10-07",
    "start_at": "2026-10-02T22:00:00.000Z",
    "end_at": "2026-10-07T22:00:00.000Z",
    "all_day": true,
    "timezone_offset": 7200,
    "reason": "vacation",
    "note": null,
    "source": "api",
    "external_id": "bamboohr-4711",
    "external_url": null
  },
  "created": true
}

created is true when a new period was stored and false when an existing one (matched by external_id) was updated or left unchanged.

What happens next is the same as for time off added in Slack: rotations with Proactive Adjustments on reorder their queue right away, the others send the user and the owners a heads-up about the shifts that overlap.

Errors:

  • 400 with an errors map naming the invalid field.
  • 404 if the Slack user is unknown.

Remove Time Off

Delete a period that was added by hand or via the API. Periods synced from Google Calendar can only be removed in the calendar.

Endpoint: POST /v1/t/<team_id>/remove_time_off

Body Parameters:

  • user_id (required): The Slack user ID.
  • id or external_id (one required): Which period to remove.

Example Request:

curl -X POST "https://api.rotation.app/v1/t/T123456/remove_time_off" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "user_id": "U06S3JJUU92", "external_id": "bamboohr-4711" }'

Response Format:

{
  "removed": true,
  "id": "01JX7E4K3MZ9Q8R2T6V0W1XYZA"
}

Errors:

  • 404 if no such period exists for this user.
  • 400 if the period is synced from Google Calendar.

Integration Examples

Dashboard Integration

Use get_current_shift_assignees to display current on-call users on monitoring dashboards or status pages.

Incident Management

Combine get_current_shift_assignees with incident management tools to automatically assign incidents to the current on-call user.

Custom Scheduling

Use assign_user and assign_next to implement custom scheduling logic based on external factors like workload or availability.

Load Balancing

Use shuffle_rotation_queue periodically to ensure fair distribution of on-call duties across team members.

CSV-to-rotation push

Use set_shifts to push an externally-managed schedule (a Google Sheet, a planning tool, an HR system) into Rotation App. Send the whole next month in one call, with assignments as user IDs and days off as null. Combine with get_shifts?verbose=true afterwards to verify the result.

HR system sync

Use set_time_off with the leave request ID as external_id to mirror approved leave from your HR system (BambooHR, Deel, Personio…). Re-run the sync as often as you like for upcoming leave: unchanged requests are ignored, moved ones are updated, and remove_time_off cleans up cancelled ones. Skip requests that have already ended, the API rejects them.