Skip to content

Participant duty operations

Participant operations apply decisions a Slack member makes: asking for cover, proposing a swap, and answering either. Duty does not change until the person asked accepts.

You need: a Round Robin access token for a signed-in Slack member, with access to the rotation or workspace. Team API keys cannot perform these actions.

  1. Send the proposed terms to the cover or swap preview operation.
  2. Review the returned intervals and any availability conflicts. Keep the response’s ETag.
  3. Submit with that value in If-Match and an operationId of your own.
  4. To answer an existing request, include the revision you reviewed in expectedRevision.

Use a new operation identifier for each new action. If a response is lost, retry the same body with the same identifier. Never reuse an identifier for changed terms.

Preview the acceptance first, with accept/preview on the request. The preview shows the person accepting the duty they would take on. A swap requester cannot acknowledge the recipient’s conflicts for them.

Read oneClick on the acceptance preview to decide how much review to show:

oneClick What it means What to do
allowed The acceptance can be committed as previewed. Submit it.
unavailable The caller cannot accept this request. Read capability.reasonCode on the request for why.
conflictReview There are conflicts to review before accepting. Show the conflicts, collect the acknowledgements, then submit.
remainingTimeReview A swap leg has already started (swaps only). Get consent for the remaining time first, as below.

oneClick is null on every other preview, including a proposal and a direct assignment.

Accepting a swap changes both intervals together. Duty already served stays as it was.

On a pending swap, a preview leg with hasStarted: true holds only the remaining interval in from and to. Review that interval and include the leg’s key in acknowledgedStartedLegKeys when accepting. Include every started leg, even the one the requester receives. These keys record consent to the remaining time, separately from conflict acknowledgements.

If another leg starts before you submit, preview again and get consent for it too.

A proposal exchanges two whole shifts that have not started. Take both from GET /v1/rotations/{rotationId}/swap-requests/candidates and send their start and end unchanged. Both must still be in the future when you submit, and both must end within six calendar months.

A pending proposal expires when its first leg ends.

Availability checks include manual absences, applicable Google Calendar events and unknown availability. A conflicting assignment for the same duty period blocks the write. Check canAcknowledge before you send a conflict identifier.

The GET operations on /v1/rotations/{rotationId}/cover-requests and /v1/rotations/{rotationId}/swap-requests need read:oncall. The key’s workspace and rotation access still apply. Capabilities read with an API key do not authorize participant writes.

Cover lists return at most 100 requests a page. Pass nextCursor as cursor, with the same from and to, until complete is true. A malformed cursor or a changed range returns invalidCoverCursor: start again without the cursor.

Ask for a UTC range of at most 31 days that ends after the current time and within six months. The list holds only whole shifts that had not started at asOfUtc, so offer every item as it comes.

horizonUtc is the furthest a swap can reach: six calendar months after asOfUtc, with the day moved back to the month’s last day where needed. A shift that ends after it is left out, and limitationCodes includes candidateHorizon. Do not page past horizonUtc.

changesAtUtc is the start of the earliest candidate: from then on, the list is out of date. Read it again at that instant. It is null when the list is empty.

Check complete and limitationCodes before treating the list as everything there is. The codes are listed in conventions.

Every answer is worked out on the server at one instant, and it says which. Show what it says. Do not compare its times with your own clock.

Field On What it says
asOfUtc A cover or swap request read, the duty-arrangement list, swap candidates, each rotation in a person’s shifts The instant the state, phase and capabilities were evaluated.
state A cover or swap request Already evaluated at asOfUtc. A pending cover request reads expired once its to is reached, and a pending swap once its first leg’s to is reached. An accepted request stays accepted after its interval ends.
phase A cover request, a swap request, each swap leg, a duty arrangement, a shift upcoming before the start, running from the start up to but not including the end, and ended from the end. A shift with no end never ends.
active A cover or swap request read true while the request is pending or accepted.
actionable A cover or swap request read true when the caller may act on the request: accept, decline, cancel or end a cover request, or accept, decline or cancel a swap.
changesAtUtc A cover or swap request read, a duty arrangement, a shift, swap candidates The next instant something above changes by the clock alone. On an arrangement or a shift, the next instant its phase changes. On swap candidates, the start of the earliest candidate. Null when nothing will.

A swap request’s phase runs from its first leg’s start to its last leg’s end, so the gap between two legs reads as running. Each leg’s own phase tells a started leg from one still ahead.

Fetch the record again at changesAtUtc. Until then, only another write can change it.

Each capability carries reasonCode beside reason, with the same word in both. reasonCode says why the can… flag next to it is false, and is null while that flag is true.

The values are readOnly, humanActorRequired, externalRotation, rotationDisabled, planLimit, laneStopped, nominalDutyUnknown, invalidCell, notAssignedToInterval, laneUnavailable, repairNotSupported, arrangementEnded, coverRequestExpired and swapRequestExpired. Treat a value you do not know as “not available”.

A duty-arrangement capability for a cell also carries actionable and actionableReason. actionable says whether to offer an assignment on that cell now. When it is false, actionableReason says why:

  • the same word as reasonCode, when canAssign is false;
  • existingCommitment, when an accepted cover or a swap leg holds the cell now. canAssign stays true, because an assignment after that commitment ends is still accepted.

actionableReason is null exactly when actionable is true.

Send the proposal to POST /v1/teams/{teamId}/availability/impact-preview. It saves no availability and changes no duty.

Ask for a UTC range of at most 31 days that ends after the current time and within six months. rotationLimit accepts 1 to 5. Pass nextAfterRotationId as afterRotationId to read the next page.

The response’s complete is about paging. Each rotation also has its own complete, sourceStatuses and limitationCodes, so check both levels. An empty interval list on an incomplete rotation does not mean the absence has no effect. The codes are listed in conventions.

Pass expectedPreviewRevision to compare a preview you reviewed with a fresh one. A false matchesExpectedRevision means the result changed: review it again before saving.

Result What to do
428 Include If-Match from a current preview.
412 Preview again and review the current terms before retrying.
operationIdConflict Use a new identifier for changed terms. Keep the original for an identical retry.
coverRequestChanged or swapRequestChanged Read the request again and preview again.
coverRequestExpired The cover request’s interval has ended, so nothing more can be done with it: no answer, cancellation or end. Ask for cover again with a future interval.
swapRequestExpired The swap’s first leg has ended, so it can no longer be accepted, declined or cancelled. Propose a new swap with future shifts.
swapRequesterConsentChanged Create a new proposal so the requester can review the changed availability.
swapShiftStarted Choose a future shift and preview again.
swapFullShiftRequired Use the full start and end from the candidate list.
swapRemainingTimeConsentRequired Preview again and get consent for every started leg before sending its key.
invalidSwapRange or invalidImpactRange Check the UTC offsets, the order of the times, the range length and the six-month limit.

From the instant a request reads expired, every one of these writes refuses it.