Skip to main content

On-Call History

Fetch the record of who was on call for one of your schedules, span by span, after all overrides have been applied. Use it to calculate hours for payroll, audits, or any system that tracks who was responsible during an incident window.

Request​

GET https://api.oppex.ai/client/oncall/public/history

Send your API key in the X-API-KEY header.

curl -G "https://api.oppex.ai/client/oncall/public/history" \
-H "X-API-KEY: <your-api-key>" \
--data-urlencode "scheduleId=42" \
--data-urlencode "startTime=2026-10-01T00:00:00Z" \
--data-urlencode "endTime=2026-10-31T00:00:00Z"

To get history for a single user only, add the userId parameter:

curl -G "https://api.oppex.ai/client/oncall/public/history" \
-H "X-API-KEY: <your-api-key>" \
--data-urlencode "scheduleId=42" \
--data-urlencode "startTime=2026-10-01T00:00:00Z" \
--data-urlencode "endTime=2026-10-31T00:00:00Z" \
--data-urlencode "userId=1001"

Query parameters​

ParameterRequiredDescription
scheduleIdYesThe numeric id of the schedule. Use List On-Call Schedules to find it.
startTimeYesStart of the range (inclusive), as an ISO-8601 timestamp with a timezone offset, such as 2026-10-01T00:00:00Z or 2026-10-01T05:30:00+05:30. A space is read as +, so URL-encode the offset.
endTimeYesEnd of the range (exclusive), same format as startTime. Must be after startTime.
userIdNoNumeric user id. When given, only spans where that user is on call are returned. Omit to get all spans.

The range must not exceed 93 days. Request a shorter window and repeat if you need more.

Response​

{
"success": true,
"statusCode": 200,
"message": "On-call history retrieved successfully",
"data": {
"items": [
{
"startTime": "2026-10-01T00:00:00Z",
"endTime": "2026-10-08T00:00:00Z",
"userId": 1001,
"userName": "Alice Chen",
"onCallUsers": [
{ "userId": 1001, "userName": "Alice Chen" }
],
"assignmentMode": "SCHEDULE_ROTATION",
"overrideActive": false,
"shortSpan": false
},
{
"startTime": "2026-10-08T00:00:00Z",
"endTime": "2026-10-15T00:00:00Z",
"userId": 1002,
"userName": "Raj Patel",
"onCallUsers": [
{ "userId": 1002, "userName": "Raj Patel" }
],
"assignmentMode": "SCHEDULE_ROTATION",
"overrideActive": false,
"shortSpan": false
}
],
"truncated": false
}
}

Response fields​

FieldDescription
itemsOrdered list of on-call spans from earliest to latest.
items[].startTimeStart of the span, UTC, whole seconds.
items[].endTimeEnd of the span, UTC, exclusive. One span ends exactly where the next begins.
items[].userIdThe nominal on-call user. Null when nobody is on call (a gap between shifts).
items[].userNameDisplay name of the nominal on-call user.
items[].onCallUsersEveryone on call during this span. Contains more than one entry only when the schedule layer uses Notify All mode, where the entire roster is on call at once.
items[].assignmentModeSCHEDULE_ROTATION, NOTIFY_ALL, or DISTRIBUTED_EVENLY.
items[].overrideActivetrue when an override changed who is on call for this span.
items[].shortSpantrue when the span is under a minute. This is real on-call time — flagged rather than dropped, typically caused by a schedule edit at a handover boundary.
truncatedtrue when the server stopped before the end of the range. Split the request into smaller windows and repeat.

Calculating on-call hours​

Sum endTime − startTime per userId across onCallUsers. When a span has truncated: true (i.e. the parent response is truncated), that span's duration is incomplete — request the remainder of the range to complete the calculation.

# Total seconds on call for each user in October 2026
curl -s -G "https://api.oppex.ai/client/oncall/public/history" \
-H "X-API-KEY: <your-api-key>" \
--data-urlencode "scheduleId=42" \
--data-urlencode "startTime=2026-10-01T00:00:00Z" \
--data-urlencode "endTime=2026-11-01T00:00:00Z" \
| jq '[
.data.items[] | . as $span | .onCallUsers[]
| {userId, userName, start: $span.startTime, end: $span.endTime}
]
| group_by(.userId)
| map({
userId: .[0].userId,
userName: .[0].userName,
totalSeconds: (map(
((.end | fromdateiso8601) - (.start | fromdateiso8601))
) | add)
})'

Because onCallUsers at the span level doesn't carry startTime/endTime, use the parent span's times when computing per-user totals for Notify All schedules — every user in onCallUsers was on call for the whole span.

Errors​

StatusWhen
400A parameter is invalid — scheduleId missing, times missing or malformed, range exceeds 93 days, or startTime is not before endTime. The message says which.
401The API key is missing, wrong, or disabled. See API Keys.
404The schedule doesn't exist, or the API key doesn't have access to it.
500Something went wrong in Oppex. Retry. If it keeps happening, contact support with the message.

FAQ

How do I find the scheduleId?

Use List On-Call Schedules to search your workspace's schedules. The response includes a scheduleId for each one.

My timezone offset is being rejected — why?

The + in +05:30 must be URL-encoded as %2B, or use curl --data-urlencode which handles this automatically. Alternatively, express the time in UTC (Z) and convert locally.

Why does a span appear twice for the same user?

Spans are not merged across days by default — a seven-day rotation is split at schedule layer boundaries, override windows, and other inflection points. Sum all spans for the user to get total hours.

What does truncated: true mean?

The schedule has many configuration changes (versions) in the requested range, and the server stopped after processing 500 spans within one version window to avoid very large responses. Request the same range broken into smaller windows to collect all spans.

Can I filter by service instead of schedule?

Not directly. Use List On-Call Schedules with a search query to find the schedules attached to a service, then call this endpoint for each one.

A Notify All span shows multiple users — how do I count hours?

Each user in onCallUsers was on call for the entire span. Add the span's duration to every user's total. For example, if Alice and Bob are both in onCallUsers for a 4-hour span, both accrue 4 hours.

In this article

FAQ

How do I find the scheduleId?

Use List On-Call Schedules to search your workspace's schedules. The response includes a scheduleId for each one.

My timezone offset is being rejected — why?

The + in +05:30 must be URL-encoded as %2B, or use curl --data-urlencode which handles this automatically. Alternatively, express the time in UTC (Z) and convert locally.

Why does a span appear twice for the same user?

Spans are not merged across days by default — a seven-day rotation is split at schedule layer boundaries, override windows, and other inflection points. Sum all spans for the user to get total hours.

What does truncated: true mean?

The schedule has many configuration changes (versions) in the requested range, and the server stopped after processing 500 spans within one version window to avoid very large responses. Request the same range broken into smaller windows to collect all spans.

Can I filter by service instead of schedule?

Not directly. Use List On-Call Schedules with a search query to find the schedules attached to a service, then call this endpoint for each one.

A Notify All span shows multiple users — how do I count hours?

Each user in onCallUsers was on call for the entire span. Add the span's duration to every user's total. For example, if Alice and Bob are both in onCallUsers for a 4-hour span, both accrue 4 hours.