Get a rotation's recorded history
using System.Net.Http.Headers;var client = new HttpClient();var request = new HttpRequestMessage{ Method = HttpMethod.Get, RequestUri = new Uri("https://api.roundrobinbot.eu/v1/rotations/example/history?from=2026-04-15T12%3A00%3A00Z&to=2026-04-15T12%3A00%3A00Z"), Headers = { { "Authorization", "Bearer <token>" }, },};using (var response = await client.SendAsync(request)){ response.EnsureSuccessStatusCode(); var body = await response.Content.ReadAsStringAsync(); Console.WriteLine(body);}package main
import ( "fmt" "net/http" "io")
func main() {
url := "https://api.roundrobinbot.eu/v1/rotations/example/history?from=2026-04-15T12%3A00%3A00Z&to=2026-04-15T12%3A00%3A00Z"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
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();
Request request = new Request.Builder() .url("https://api.roundrobinbot.eu/v1/rotations/example/history?from=2026-04-15T12%3A00%3A00Z&to=2026-04-15T12%3A00%3A00Z") .get() .addHeader("Authorization", "Bearer <token>") .build();
Response response = client.newCall(request).execute();import axios from 'axios';
const options = { method: 'GET', url: 'https://api.roundrobinbot.eu/v1/rotations/example/history', params: {from: '2026-04-15T12:00:00Z', to: '2026-04-15T12:00:00Z'}, headers: {Authorization: 'Bearer <token>'}};
try { const { data } = await axios.request(options); console.log(data);} catch (error) { console.error(error);}const url = 'https://api.roundrobinbot.eu/v1/rotations/example/history?from=2026-04-15T12%3A00%3A00Z&to=2026-04-15T12%3A00%3A00Z';const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request GET \ --url 'https://api.roundrobinbot.eu/v1/rotations/example/history?from=2026-04-15T12%3A00%3A00Z&to=2026-04-15T12%3A00%3A00Z' \ --header 'Authorization: Bearer <token>'The turns this rotation actually recorded inside a window you name, one entry per turn held: who held it, the lane and coverage window it was held on, when it started, when it ended, and the hours the window covered at the time. This is the record, not the plan. Read GET /v1/rotations/{rotationId}/schedule for what the rules say should happen; the two disagree whenever a hand-over was skipped for absence, a window shut early, or somebody was put on duty by hand, and the record is the one that answers “who was on call when this fired”. A stretch nobody covered is an entry of its own with nobodyWasOnCall true and no userId, so a gap is a fact you can read rather than a hole between two entries you have to infer. And an entry keeps the hours it was held under: widening a window does not rewrite what last month looked like. from and to are both required and no more than 90 days apart. An entry that began before the window or ended after it is included, because the turn in progress when the window opened is usually the one you wanted. No ETag: a page is assembled from many entries, so no single version stands behind it.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”Query Parameters
Section titled “Query Parameters”Responses
Section titled “Responses”OK
The envelope every /v1 collection comes back in.
object
One turn a rotation recorded: who held it, where, and between which instants.
object
When the turn started.
When it ended.
The Slack user id of whoever held the turn, absent on a stretch nobody covered.
True when this entry is a stretch nobody was on call for.
The holder’s position in the turn order of the list the turn was dealt from. Absent on a stretch nobody covered, which has no position.
Which lane and which window the turn was held on, named as they were called at the time.
Why the turn ended, as a stable code: one of next-on-scheduled-duty (the cadence came round), rotate-to-next-on-duty, explicit-change, duty-cleared, removal-of-active-duty, coverage-changed, business-hours-offline, business-hours-back-online, change-in-external-schedule or after-mention.
The hours the window covered while this turn was held, which is not necessarily what it covers now. Absent when there is no record of that window’s hours to read.
object
The one zone these hours are expressed in, as its own identifier (Europe/Rome). Empty on a rotation with no cadence, which has no zone to express one in.
Whether the window restricted nothing: open the whole week, in which case coverage is empty.
The repeating week the window was open for. Empty means the whole week.
One stretch of the coverage week, which may run past midnight into the next day.
object
A weekday name, monday through sunday.
A time of day, HH:mm.
Examplegenerated
{ "data": [ { "start": "2026-04-15T12:00:00Z", "end": "2026-04-15T12:00:00Z", "userId": "example", "nobodyWasOnCall": true, "turn": 1, "laneId": "example", "laneName": "example", "windowId": "example", "windowName": "example", "reason": "example", "hoursAsHeld": { "timeZone": "example", "alwaysOpen": true, "coverage": [ { "startDay": "example", "startTime": "example", "endDay": "example", "endTime": "example" } ] } } ], "page": 1, "pageSize": 1, "totalItems": 1, "totalPages": 1}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"}