Skip to content

The Round Robin API

The Round Robin API is an HTTP interface to the same data the dashboard and the Slack bot work with: your rotations, their schedules, and who is on call right now. It exists for the things Slack cannot do for you: putting the on-call person on a status page, paging them from your own incident tooling, driving a deployment gate, or feeding a dashboard that already shows everything else about your service.

It is a separate surface from the one the dashboard uses, and it is the only one covered by a promise: what is documented here keeps working. Anything you find by watching the dashboard’s network tab is not part of it and will change without notice.

You need: a team API key. Create one in the dashboard under Settings → API keys: choose what it may do and which rotations it may reach, and copy the secret, which is shown once. See Creating and managing API keys.

Everything lives under one host, and the team is implicit: a key belongs to exactly one Slack workspace, so no workspace id ever appears in a path.

Terminal window
curl https://api.roundrobinbot.eu/v1/keys/self \
-H "Authorization: Bearer rr_live_..."

GET /v1/keys/self is the call to start with, and the one to come back to when something is refused: it answers with the key’s name, its scopes, and the rotations it may reach.

{
"id": "6a2f…",
"name": "Status page",
"teamId": "T0123ABCD",
"scopes": ["read:oncall"],
"selector": "Rotations",
"rotationIds": ["664f…"],
"apiVersion": "2026-08-01",
"createdAt": "2026-08-14T09:12:44+00:00"
}

The question most integrations exist to answer takes one request:

Terminal window
curl "https://api.roundrobinbot.eu/v1/rotations/{rotationId}/on-call?expand=users" \
-H "Authorization: Bearer rr_live_..."

?expand=users side-loads the full directory record for every person the answer names, so you get Slack ids and display names without a second round trip. Leave it off and you get ids alone, plus an ETag you can use to make the next poll free (see Conventions).

What Endpoint
Your rotations, a page at a time GET /v1/rotations
One rotation, with its schedule and business hours GET /v1/rotations/{rotationId}
Who is on call right now GET /v1/rotations/{rotationId}/on-call
Projected shifts in a window you name GET /v1/rotations/{rotationId}/schedule?from&to
A person, group or channel from the synced directory GET /v1/users/{id}, /v1/groups/{id}, /v1/channels/{id}
The key’s own workspace GET /v1/team
The key itself GET /v1/keys/self

Every write against something that already exists requires an If-Match carrying the ETag from your last read. A write without one is refused: see Conditional writes.

What Endpoint Scope
Hand the shift to the next person POST /v1/rotations/{rotationId}/rotate write:duty
Put named people on duty PUT /v1/rotations/{rotationId}/on-call write:duty
Change a rotation’s configuration PATCH /v1/rotations/{rotationId} write:rotation
Replace a rotation’s schedule PUT /v1/rotations/{rotationId}/schedule write:rotation
Create a rotation POST /v1/rotations write:rotation

The two duty writes answer with the rotation’s new on-call state and a fresh ETag, so a deployment gate that rotates and then reports who now holds the shift needs one request, not two. The other three answer with the whole rotation.

PATCH changes only the fields you send, so renaming a rotation means sending the name and nothing else. The schedule is a replacement rather than a patch, because the schedule kinds do not share a field set and a field left out of one kind means nothing in another.

Creating takes an Idempotency-Key instead of an If-Match: there is no version to match on yet, and rotation names are not unique, so nothing else could tell a retry from a second rotation. See Creating things.

The roster is read-only over /v1: who is in a rotation is changed from the dashboard or from Slack.

The full request and response shapes are in the API reference, which is generated from the same OpenAPI document the service publishes at https://api.roundrobinbot.eu/openapi/public.json. Point an SDK generator or Postman at that URL directly.

A key is pinned to a dated contract version (2026-08-01 for every key issued today), and GET /v1/keys/self reports it. Fields get added to responses within a version, so parse leniently and ignore what you do not recognise. Nothing is removed or renamed within a version; that needs a new one, and your key keeps answering on the old one until you move it.

/v1 is the path, not the version. It changes only if the shape of the API changes so fundamentally that a dated version cannot carry it.

  • Authentication: how to present a key, and what each refusal means
  • Scopes and access: what a key may do, and which rotations it may reach
  • Conventions: paging, sorting, timestamps, conditional reads and conditional writes
  • Errors: the codes to branch on