Skip to content

Get a rotation's recorded history

GET
/v1/rotations/{rotationId}/history
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 duty 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.

rotationId
required
string
from
required
string format: date-time
to
required
string format: date-time
page
integer format: int32
pageSize
integer format: int32
sort
string

OK

Media typeapplication/json

The envelope every /v1 collection comes back in.

object
data
Array<object>

One turn a rotation recorded: who held it, where, and between which instants.

object
identityKind

Recorded identity: slackUser, externalPerson, nobody or unknown. An external person held duty without a Slack mapping. Unknown means the record cannot identify the holder; it does not mean nobody was on duty.

string
externalName

Recorded external participant name, when available. Absent when nobody was on duty.

null | string
externalEmail

Recorded external participant email address, when available. Absent when nobody was on duty.

null | string
cellId

Identity of the lane in a window where duty was held, when recorded.

null | string
localAssignmentId

Local identity of the external assignment, when recorded. Separate history entries can share this identity.

null | string
externalAssignment
One of:
null
start

When the turn started.

string format: date-time
end

When it ended.

string format: date-time
userId

Slack user ID of the recorded holder. Absent for external participants without a Slack mapping, unidentified holders and stretches nobody covered. Read identityKind to distinguish them.

null | string
nobodyWasOnCall

True when nobody was on duty, including an explicitly unassigned provider slot. False for an external participant who has no Slack mapping.

boolean
turn

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.

null | integer format: int32
laneId

Which lane and which window the turn was held on, named as they were called at the time.

null | string
laneName
null | string
windowId
null | string
windowName
null | string
reason

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, after-mention, interval-reconciliation (an automatic change that no other code names), direct-assignment-changed or direct-assignment-ended.

string
hoursAsHeld
One of:
null
page
integer format: int32
pageSize
integer format: int32
totalItems
integer format: int64
totalPages
integer format: int32
Examplegenerated
{
"data": [
{
"identityKind": "example",
"externalName": "example",
"externalEmail": "example",
"cellId": "example",
"localAssignmentId": "example",
"externalAssignment": {
"bindingId": "example",
"memberKind": "example",
"participantId": "example",
"sourceType": "example",
"rotationId": "example",
"shiftId": "example",
"overrideId": "example",
"originalStartUtc": "2026-04-15T12:00:00Z",
"originalEndUtc": "2026-04-15T12:00:00Z"
},
"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

Media typeapplication/json
object
type
null | string
title
null | string
status
null | integer format: int32
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 format: int32
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 format: int32
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 format: int32
detail
null | string
instance
null | string
Examplegenerated
{
"type": "example",
"title": "example",
"status": 1,
"detail": "example",
"instance": "example"
}