Learn how to interact with Rotation App programmatically using our REST API to get on-call users, assign users, and manage rotation queues.
- 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.
Webhooks: Receive real-time event notifications when rotations change
Slack Workflows: No-code automation directly within Slack
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 IDrotation_id: The unique identifier of your rotationaction_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:
- Set Shifts: substitute, cancel, or set one or multiple shifts
- Substitute Shift
- Undo Substitution
- Cancel Shift
- Uncancel Shift
Time off (workspace-wide, not tied to a rotation):
- Get Time Off
- Set Time Off: add or update a period, idempotently
- Remove Time Off
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 callcurrent_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 shiftnext_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 shiftGET /v1/t/<team_id>/r/<rotation_id>/get_shift_4_assignees- Fourth shiftGET /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 shiftshift_<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 callon_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 callon_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 shuffleon_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 shift1(the current shift).to(optional): Inclusive upper bound, same format asfrom. Defaults tofrom + 29(a 30-shift window). The maximum window size is 365 shifts.verbose(optional): Iftrue, each shift value is an object{ "assignees": [...], "naturalAssignees": [...] }instead of a bare user ID, array, ornull. 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, ornullfor shifts where nobody is assigned (skipped or cancelled).- In verbose mode, each value is an object
{ "assignees": [...] | null, "naturalAssignees": [...] }.assigneesisnullfor skipped or cancelled shifts;naturalAssigneesalways 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). nullto cancel the shift (no assignee; following shifts stay on schedule). See Cancelling a shift for the semantics.
- A user ID (
verbose(optional): Iftrue, each shift value in the response becomes an object{ "assignees": [...] | null, "naturalAssignees": [...] }instead of a bare user ID, array, ornull.
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.
"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 asset_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 asset_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 asset_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:
400if the shift isn’t currently cancelled or skipped.400if 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 asset_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:
400if 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 afterfrom.
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 oftimezone_offset(seconds from UTC).start_at/end_at: The same bounds as absolute timestamps;end_atis exclusive.all_day:truefor periods made of whole days (always the case for periods added in Slack or via the API);falsefor a Google Calendar event with times.reason: One ofvacation,out_sick,out_of_office,not_available.note: The free-text note, ornull.source:manual(added in Slack),api, orgoogle(synced from Google Calendar; read-only).external_id: The identifier you passed toset_time_off, or the Google event ID.nullotherwise.external_url: A link to the Google Calendar event,nullfor 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 afterstart_date.reason(optional):vacation(default),out_sick,out_of_office, ornot_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:
400with anerrorsmap naming the invalid field.404if 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.idorexternal_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:
404if no such period exists for this user.400if 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.