Set who is on duty
using System.Net.Http.Headers;var client = new HttpClient();var request = new HttpRequestMessage{ Method = HttpMethod.Put, RequestUri = new Uri("https://api.roundrobinbot.eu/v1/rotations/example/on-call"), Headers = { { "If-Match", "example" }, { "Authorization", "Bearer <token>" }, }, Content = new StringContent("{ \"userIds\": [ \"example\" ] }") { Headers = { ContentType = new MediaTypeHeaderValue("application/json-patch+json") } }};using (var response = await client.SendAsync(request)){ response.EnsureSuccessStatusCode(); var body = await response.Content.ReadAsStringAsync(); Console.WriteLine(body);}package main
import ( "fmt" "strings" "net/http" "io")
func main() {
url := "https://api.roundrobinbot.eu/v1/rotations/example/on-call"
payload := strings.NewReader("{ \"userIds\": [ \"example\" ] }")
req, _ := http.NewRequest("PUT", url, payload)
req.Header.Add("If-Match", "example") req.Header.Add("Authorization", "Bearer <token>") req.Header.Add("Content-Type", "application/json-patch+json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close() body, _ := io.ReadAll(res.Body)
fmt.Println(res) fmt.Println(string(body))
}OkHttpClient client = new OkHttpClient();
MediaType mediaType = MediaType.parse("application/json-patch+json");RequestBody body = RequestBody.create(mediaType, "{ \"userIds\": [ \"example\" ] }");Request request = new Request.Builder() .url("https://api.roundrobinbot.eu/v1/rotations/example/on-call") .put(body) .addHeader("If-Match", "example") .addHeader("Authorization", "Bearer <token>") .addHeader("Content-Type", "application/json-patch+json") .build();
Response response = client.newCall(request).execute();import axios from 'axios';
const options = { method: 'PUT', url: 'https://api.roundrobinbot.eu/v1/rotations/example/on-call', headers: { 'If-Match': 'example', Authorization: 'Bearer <token>', 'Content-Type': 'application/json-patch+json' }, data: '{ "userIds": [ "example" ] }'};
try { const { data } = await axios.request(options); console.log(data);} catch (error) { console.error(error);}const url = 'https://api.roundrobinbot.eu/v1/rotations/example/on-call';const options = { method: 'PUT', headers: { 'If-Match': 'example', Authorization: 'Bearer <token>', 'Content-Type': 'application/json-patch+json' }, body: '{ "userIds": [ "example" ] }'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request PUT \ --url https://api.roundrobinbot.eu/v1/rotations/example/on-call \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json-patch+json' \ --header 'If-Match: example' \ --data '{ "userIds": [ "example" ] }'Puts the people you name on duty, replacing whoever holds the shift now. They must be members of the rotation, and userIds must name at least one: an empty list is refused rather than read as “take everybody off duty”. Answers with the new on-call state and a fresh ETag.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”Header Parameters
Section titled “Header Parameters”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
Request Bodyrequired
Section titled “Request Bodyrequired”The body of PUT /v1/rotations/{rotationId}/on-call: who should be on duty.
object
The Slack user ids to put on duty. At least one, and each must be a member of the rotation.
Examplegenerated
{ "userIds": [ "example" ]}The body of PUT /v1/rotations/{rotationId}/on-call: who should be on duty.
object
The Slack user ids to put on duty. At least one, and each must be a member of the rotation.
Examplegenerated
{ "userIds": [ "example" ]}The body of PUT /v1/rotations/{rotationId}/on-call: who should be on duty.
object
The Slack user ids to put on duty. At least one, and each must be a member of the rotation.
Examplegenerated
{ "userIds": [ "example" ]}The body of PUT /v1/rotations/{rotationId}/on-call: who should be on duty.
object
The Slack user ids to put on duty. At least one, and each must be a member of the rotation.
Examplegenerated
{ "userIds": [ "example" ]}Responses
Section titled “Responses”OK
Who is on call for a rotation right now.
object
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”.
One person holding the shift, and when they hold it until.
object
The Slack user id of the person actually on duty.
Their position in the rotation’s turn order.
Members the plan would otherwise have named, who were passed over because they were absent.
A member the rotation passed over because they were away.
object
The Slack user id of the member who was passed over.
Where the absence came from: googleCalendar or internalAvailability. Null on older records, which predate the field, so treat it as optional.
Set when an external schedule put somebody on duty who does not map to a Slack user. For those rotations this is the only answer there is, so a client that reads onCall alone reports “nobody” for every one of them.
object
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”.
A Slack user, as Round Robin knows them.
object
The Slack user id.
The workspace that owns this person’s directory entry, which under Enterprise Grid can differ from the calling key’s workspace.
Null when the install was never granted users:read.email, not only when unknown.
A Slack Connect person: visible through a shared channel, never a member of the workspace.
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.
The person’s Slack avatar, at 192px.
The person’s Slack time zone, as an IANA identifier (Europe/Rome). Absent when Round Robin has not seen one for them.
Examplegenerated
{ "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" } ]}Bad Request
object
Examplegenerated
{ "type": "example", "title": "example", "status": 1, "detail": "example", "instance": "example"}Unauthorized
object
Examplegenerated
{ "type": "example", "title": "example", "status": 1, "detail": "example", "instance": "example"}Forbidden
object
Examplegenerated
{ "type": "example", "title": "example", "status": 1, "detail": "example", "instance": "example"}Not Found
object
Examplegenerated
{ "type": "example", "title": "example", "status": 1, "detail": "example", "instance": "example"}Precondition Failed
object
Examplegenerated
{ "type": "example", "title": "example", "status": 1, "detail": "example", "instance": "example"}Precondition Required
object
Examplegenerated
{ "type": "example", "title": "example", "status": 1, "detail": "example", "instance": "example"}