Skip to content

Change a rotation's configuration

PATCH
/v1/rotations/{rotationId}
curl --request PATCH \
--url https://api.roundrobinbot.eu/v1/rotations/example \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json-patch+json' \
--header 'If-Match: example' \
--data '{ "name": "example", "description": "example", "code": "example", "enabled": true, "public": true, "dutySize": 1, "owners": [ "example" ], "channels": [ "example" ] }'

Changes only the fields you send. Anything you leave out keeps its current value, so renaming a rotation means sending the name and nothing else. description and code take an empty string to clear them. owners and channels replace the current list rather than adding to it. Answers with the rotation and a fresh ETag. The schedule and the on-call state are changed elsewhere: use PUT /v1/rotations/{rotationId}/schedule and the on-call endpoints.

rotationId
required
string
If-Match
required
string

The ETag from your last read of this rotation, sent back exactly as you received it, quotes included. The write happens only if nothing changed in between: a stale validator is answered 412 and nothing is written. Send * to write against whatever is current on purpose. Omitting the header is 428, never an unconditional write. https://docs.roundrobinbot.eu/api/conventions/#conditional-writes

The parts of a rotation’s configuration PATCH /v1/rotations/{rotationId} will change.

object
name

What the rotation is called.

null | string
description

A sentence about what the rotation covers. Send "" to clear it; omit to leave it.

null | string
code

The short code the rotation answers to in Slack. Send "" to clear it; omit to leave it. Must be unique in the workspace, and a clash is refused rather than resolved.

null | string
enabled

Whether the rotation is running. A disabled rotation keeps its members, its plan and its schedule, and simply stops handing over.

null | boolean
public

Whether every member of the workspace can see the rotation.

null | boolean
dutySize

How many people hold the shift at once. Cannot exceed the number of people in the rotation.

null | integer | string format: int32
/^-?(?:0|[1-9]\d*)$/
owners

The Slack user ids that may administer the rotation, replacing the current list wholesale. Send [] to leave the rotation with no owners beyond workspace admins.

Array<string> | null
channels

The Slack channel ids the rotation posts to, replacing the current list wholesale. The app must already be in every channel named.

Array<string> | null
Examplegenerated
{
"name": "example",
"description": "example",
"code": "example",
"enabled": true,
"public": true,
"dutySize": 1,
"owners": [
"example"
],
"channels": [
"example"
]
}

OK

Media typeapplication/json

A rotation: who is in it, when it hands over, and who holds the shift right now.

object
id
string
name
string
description
null | string
code

The short code the rotation answers to in Slack.

null | string
enabled
boolean
public

Whether every member of the workspace can see the rotation.

boolean
mode

manual, auto or external.

string
dutySize

How many people hold the shift at once.

integer | string format: int32
/^-?(?:0|[1-9]\d*)$/
owners
Array<string>
channels
Array<string>
members

Everybody in the rotation, split by how they got there.

object
users

Members added individually.

Array<object>

One member of a rotation.

object
userId
string
sequenceNumber

Where the member sits in the order the plan is built from.

integer | string format: int32
/^-?(?:0|[1-9]\d*)$/
userGroups

Members that came in through a Slack user group, listed under the group they came from. Add or remove somebody in Slack’s group and the rotation follows.

Array<object>

A Slack user group in the rotation, and the people currently in it.

object
userGroupId
string
users
Array<object>

One member of a rotation.

object
userId
string
sequenceNumber

Where the member sits in the order the plan is built from.

integer | string format: int32
/^-?(?:0|[1-9]\d*)$/
plan

The turn order.

object
items
Array<object>

One position in the turn order.

object
userId
string
userGroupId

Set when the member is in the plan by way of a user group.

null | string
turn
integer | string format: int32
/^-?(?:0|[1-9]\d*)$/
schedule

When the rotation hands over. Read type first: it says which of the fields below carry anything.

object
type

One of dayOfWeek, daysOfWeekList, dayOfMonth, nthWeekdayOfMonth, daily, workday, manual or external. Fields that mean nothing for the type are absent rather than defaulted, so test for presence rather than for a zero.

string
timeZone

The time zone the schedule was configured with, as its own identifier (Europe/Rome). Never rewritten to the tzdb canonical name a linked city shares.

string
startsAt

When the schedule starts, in its own time zone.

string format: date-time
period

How many units of the schedule’s own kind sit between two hand-overs.

integer | string format: int32
/^-?(?:0|[1-9]\d*)$/
skipWeekends
boolean
skipNationalHolidays
boolean
countryCodes

The countries whose weekends and holidays the skips are read from.

Array<string>
nextEvents

The next hand-overs this rotation has already scheduled. For anything further out, ask for a window from GET /v1/rotations/{rotationId}/schedule.

Array<string>
dayOfWeek

Set for dayOfWeek and nthWeekdayOfMonth.

null | string
daysOfWeek

Set for daysOfWeekList.

Array<string> | null
dayOfMonth

Set for dayOfMonth.

null | integer | string format: int32
/^-?(?:0|[1-9]\d*)$/
weeksInMonth

Set for nthWeekdayOfMonth: which occurrences in the month, 1 through 5.

Array<integer | string> | null
times

Set for daily: the times of day it hands over at.

Array<string> | null
partner

Set for external: which partner drives the rotation. One of pagerDuty, opsGenie, or none when the rotation is external but no partner is linked yet.

null | string
businessHours

When the rotation is covered, and whether it is covered right now.

object
enabled

Whether business hours are switched on. Switching them off keeps the coverage week, so it can be switched back on without re-entering it.

boolean
inEffect

Whether business hours actually restrict the rotation. This differs from enabled when the switch is on but the coverage week is empty, which restricts nothing.

boolean
timeZone
string
coverage

The repeating week, as stored.

Array<object>

One stretch of the coverage week, which may run past midnight into the next day.

object
startDay

A weekday name, monday through sunday.

string
startTime

A time of day, HH:mm.

string
endDay
string
endTime
string
considerScheduleSkipsAsOffTime

Whether days the schedule skips (weekends, national holidays) count as off time.

boolean
isInBusinessHours

Whether the rotation is inside its hours at the moment of the response.

boolean
currentBusinessHours
One of:
null
nextBusinessHour
One of:
null
onDutyUserGroupId

The Slack user group kept in sync with whoever is on call.

null | string
onCall

The on-call resource, embedded, so the common question needs no second call.

object
rotationId
string
onCall

Empty when nobody holds the shift, and also when an external rotation’s on-duty person has no Slack identity: read unmappedExternalDuty before concluding “nobody”.

Array<object>

One person holding the shift, and when they hold it until.

object
userId

The Slack user id of the person actually on duty.

string
turn

Their position in the rotation’s turn order.

integer | string format: int32
/^-?(?:0|[1-9]\d*)$/
since
null | string format: date-time
until
null | string format: date-time
skippedForAbsence

Members the plan would otherwise have named, who were passed over because they were absent.

Array<object>

A member the rotation passed over because they were away.

object
userId

The Slack user id of the member who was passed over.

string
from
null | string format: date-time
until
null | string format: date-time
source

Where the absence came from: googleCalendar or internalAvailability. Null on older records, which predate the field, so treat it as optional.

null | string
unmappedExternalDuty
One of:
null
users

Every Slack user this body names, resolved, when the caller asked for ?expand=users. Null otherwise, so a client can tell “not asked for” from “asked for and nobody matched”.

Array<object> | null

A Slack user, as Round Robin knows them.

object
id

The Slack user id.

string
teamId

The workspace that owns this person’s directory entry, which under Enterprise Grid can differ from the calling key’s workspace.

string
displayName
null | string
realName
null | string
email

Null when the install was never granted users:read.email, not only when unknown.

null | string
isGuest
boolean
isExternal

A Slack Connect person: visible through a shared channel, never a member of the workspace.

boolean
isDeleted

Deactivated in Slack, or gone from the workspace. These people are still answered rather than hidden, so an id a rotation still names resolves to something you can render.

boolean
avatarUrl

The person’s Slack avatar, at 192px.

null | string
timeZone

The person’s Slack time zone, as an IANA identifier (Europe/Rome). Absent when Round Robin has not seen one for them.

null | string
createdAt
string format: date-time
updatedAt
string format: date-time
Examplegenerated
{
"id": "example",
"name": "example",
"description": "example",
"code": "example",
"enabled": true,
"public": true,
"mode": "example",
"dutySize": 1,
"owners": [
"example"
],
"channels": [
"example"
],
"members": {
"users": [
{
"userId": "example",
"sequenceNumber": 1
}
],
"userGroups": [
{
"userGroupId": "example",
"users": [
{
"userId": "example",
"sequenceNumber": 1
}
]
}
]
},
"plan": {
"items": [
{
"userId": "example",
"userGroupId": "example",
"turn": 1
}
]
},
"schedule": {
"type": "example",
"timeZone": "example",
"startsAt": "2026-04-15T12:00:00Z",
"period": 1,
"skipWeekends": true,
"skipNationalHolidays": true,
"countryCodes": [
"example"
],
"nextEvents": [
"2026-04-15T12:00:00Z"
],
"dayOfWeek": "example",
"daysOfWeek": [
"example"
],
"dayOfMonth": 1,
"weeksInMonth": [
1
],
"times": [
"example"
],
"partner": "example"
},
"businessHours": {
"enabled": true,
"inEffect": true,
"timeZone": "example",
"coverage": [
{
"startDay": "example",
"startTime": "example",
"endDay": "example",
"endTime": "example"
}
],
"considerScheduleSkipsAsOffTime": true,
"isInBusinessHours": true,
"currentBusinessHours": {
"startUtc": "2026-04-15T12:00:00Z",
"endUtc": "2026-04-15T12:00:00Z"
},
"nextBusinessHour": {
"startUtc": "2026-04-15T12:00:00Z",
"endUtc": "2026-04-15T12:00:00Z"
}
},
"onDutyUserGroupId": "example",
"onCall": {
"rotationId": "example",
"onCall": [
{
"userId": "example",
"turn": 1,
"since": "2026-04-15T12:00:00Z",
"until": "2026-04-15T12:00:00Z",
"skippedForAbsence": [
{
"userId": "example",
"from": "2026-04-15T12:00:00Z",
"until": "2026-04-15T12:00:00Z",
"source": "example"
}
]
}
],
"unmappedExternalDuty": {
"email": "example",
"name": "example",
"until": "2026-04-15T12:00:00Z"
},
"users": [
{
"id": "example",
"teamId": "example",
"displayName": "example",
"realName": "example",
"email": "example",
"isGuest": true,
"isExternal": true,
"isDeleted": true,
"avatarUrl": "example",
"timeZone": "example"
}
]
},
"createdAt": "2026-04-15T12:00:00Z",
"updatedAt": "2026-04-15T12:00:00Z"
}

Bad Request

Media typeapplication/json
object
type
null | string
title
null | string
status
null | integer | string format: int32
/^-?(?:0|[1-9]\d*)$/
detail
null | string
instance
null | string
Examplegenerated
{
"type": "example",
"title": "example",
"status": 1,
"detail": "example",
"instance": "example"
}

Unauthorized

Media typeapplication/json
object
type
null | string
title
null | string
status
null | integer | string format: int32
/^-?(?:0|[1-9]\d*)$/
detail
null | string
instance
null | string
Examplegenerated
{
"type": "example",
"title": "example",
"status": 1,
"detail": "example",
"instance": "example"
}

Forbidden

Media typeapplication/json
object
type
null | string
title
null | string
status
null | integer | string format: int32
/^-?(?:0|[1-9]\d*)$/
detail
null | string
instance
null | string
Examplegenerated
{
"type": "example",
"title": "example",
"status": 1,
"detail": "example",
"instance": "example"
}

Not Found

Media typeapplication/json
object
type
null | string
title
null | string
status
null | integer | string format: int32
/^-?(?:0|[1-9]\d*)$/
detail
null | string
instance
null | string
Examplegenerated
{
"type": "example",
"title": "example",
"status": 1,
"detail": "example",
"instance": "example"
}

Precondition Failed

Media typeapplication/json
object
type
null | string
title
null | string
status
null | integer | string format: int32
/^-?(?:0|[1-9]\d*)$/
detail
null | string
instance
null | string
Examplegenerated
{
"type": "example",
"title": "example",
"status": 1,
"detail": "example",
"instance": "example"
}

Precondition Required

Media typeapplication/json
object
type
null | string
title
null | string
status
null | integer | string format: int32
/^-?(?:0|[1-9]\d*)$/
detail
null | string
instance
null | string
Examplegenerated
{
"type": "example",
"title": "example",
"status": 1,
"detail": "example",
"instance": "example"
}