On-Call API
On-Call gives you the compute primitives behind incident paging — who is on call right now, and in what order an incident pages people — without locking you into a separate alerting vendor. Every endpoint is pure compute over the request body: you send a rotation or policy definition, Orbit returns the resolved answer. Nothing is paged and no state is persisted by these endpoints; when you’re ready to actually notify someone, you send through the Messaging API (SMS, voice, WhatsApp, push, email) using the targets and channels this API worked out for you. Base path:/api/v1/oncall
Authentication: API key (X-API-Key) or session JWT.
Resolve who’s on call
Given a rotation — an ordered list of member identifiers and a cadence (daily, weekly, or a custom shift length in seconds), anchored to the ISO instant member one’s first shift starts — returns the member on call at a given instant (defaults to now), the current shift window, and when the next hand-off happens. Members are opaque identifiers the engine never interprets — E.164 numbers, email addresses, push tokens, or your own user ids.
Pass an at in the past to answer “who was on call when the alert fired” — the window math is deterministic, so a backdated resolve returns the historically correct shift.
atechoes the instant you resolved at, normalized to ISO — a backdatedatproduces the same answer a live resolve at that moment would have returned.shift_indexcounts completed shifts since the anchor. Here the anchor (2026-08-17) is earlier thanat(2026-08-20), and fewer than seven days have elapsed, so shift zero is still running. Onceatpassesshift_end,shift_indexadvances by one per cadence and membership wraps with modulo.on_callismembers[shift_index mod len(members)]— shift zero picks member one, the E.164-to-email mix is fine because member strings are opaque.shift_start/shift_endgive the exact window the member holds the pager;next_on_callis the member who takes over atnext_handoff_at.
shift_index: 0 — pager-less requests early in a schedule’s life still reach a human, and next_handoff_at equals the anchor itself.
Plan an escalation
Given an escalation policy — an ordered list of steps, each with a target (a live rotation or a fixed member list), the channels to page, and how long to wait for an acknowledgement before escalating — returns the full page timeline an incident driver walks: which targets get paged, on which channels, at which offset from incident start, and how many rounds the policy repeats. A step targets either a rotation (resolved live at each page’s fire instant, so a hand-off mid-escalation still reaches the correct human) or a fixed users list. Mixing the two is the common shape: rotation first, a fallback human second.pages[] timeline, row by row:
- Round 0, step 0 — fires immediately at offset 0. The rotation target resolves live:
anchor2026-08-24 is two days beforestart_at, so shift zero is still running and member one (+15551234567) is the target. - Round 0, step 1 — fires 300 seconds in, once step 0’s acknowledgement window elapses. A
userstarget pages a fixed member list — here an email address over the email channel. - Round 0, step 2 — the fallback human gets a voice call at offset 600.
- Rounds 1 —
repeat: 1loops the whole policy one extra time, so the same three steps fire again at offsets 900 / 1200 / 1500 if nobody has acknowledged by then.total_pagescounts six rows: three steps × two rounds.
escalate_after_seconds values before it, and rotation targets re-resolve at each page’s fire_at — a hand-off that lands mid-escalation pages whoever holds the pager at that instant.
Drive an incident
incident/tick and incident/transition let you drive the full incident lifecycle against your own state store (you own persistence; these endpoints are stateless compute over the incident snapshot you pass in).
Tick — which pages are due right now
Pass the incident snapshot and optionally the instant to evaluate (defaults to now). The response tells you which pages became due, the newpages_fired high-water mark to persist on your side, when to wake up and tick again (next_tick_at), and whether the incident is still paging, acknowledged, resolved, or has exhausted its escalation policy.
due_pagesholds every page whosefire_athas passed and that your persistedpages_firedmark had not covered — here pages 0 and 1 both became due because the first tick ran 5.5 minutes after the incident opened.pages_fired: 2is the new mark; persist it so the next tick resumes exactly where this one left off.total_pages: 4counts the two steps across two rounds (repeat: 1).status: "paging"withexhausted: falsebecause two of four pages remain.next_tick_atis the only row your scheduler needs: wake up at 09:10 and tick again. It is the fire instant of the next unfired page — round 1, step 0.
pages_fired: 2 between ticks and a tick that lands before the next page’s fire instant returns an empty due_pages and a pages_fired unchanged — nothing is paged twice under replay:
status: "exhausted", exhausted: true, and next_tick_at: null — hand the incident to a human rather than keep a scheduler awake.
Transition — acknowledge, resolve, or reassign
Apply a lifecycle action.ack is valid only from open and halts the escalation; resolve closes the incident from open or acknowledged; reassign swaps in a new policy and restarts its timeline (status: "open", pages_fired: 0, started_at reset to the action instant).
The sequence below acknowledges, then resolves. Pass the snapshot you persisted — after step one that means status: "acknowledged" plus acknowledged_at:
due_pages: [] and next_tick_at: null, and reassign is the only path back to paging — it swaps the policy, resets pages_fired to 0, and restarts the timeline at the started_at you pass.
The
meta.request_id echoes the X-Request-Id header when you set one — send a stable id per step of your incident driver so the ack → resolve → rejected re-ack chain reads as one line in your logs.Recipe — resolve, then page the on-call member
The typical loop: resolve the rotation, then send the page through Messaging. Four steps, none of which require Orbit to hold any state — your service owns the rotation definition and the incident snapshot.1
Resolve the on-call member
Call
POST /api/v1/oncall/resolve with your rotation definition. Read on_call from the response — that is the target to page (here, an E.164 number).2
Send the page through Messaging
Pass the resolved member as See Send an SMS for the full request and response contract.
to in a POST /api/v1/messages/sms send. Every send exits through the same Messaging pipeline as the rest of your traffic, so your sender ids, quiet-hours policy, and delivery webhooks all apply.cURL
Python
3
Tick on a schedule you control
While the incident stays open, call
POST /api/v1/oncall/incident/tick whenever next_tick_at rolls around, persist the returned pages_fired mark on your side, and send each entry in due_pages through Messaging on the channels it names.4
Stop the clock with a transition
When a human acks or resolves in your console, call
POST /api/v1/oncall/incident/transition and store the returned incident snapshot. A halted incident returns due_pages: [] and next_tick_at: null on the next tick, so your scheduler stands down.These endpoints never persist anything and never send anything themselves — they are stateless compute over the request body. Rotation definitions, incident snapshots, and the
pages_fired high-water mark live in your state store; every actual page exits through Messaging, where delivery status arrives on your configured webhooks.See also
- On-call alerting guide — the end-to-end walkthrough from rotation to resolved incident
- Messaging API — the send surface that actually pages a resolved target
- Webhooks — subscribe to delivery status for a page you sent