The endpoints in this guide live under the Availability Schedule API and require a token with
schedules:write for mutations and schedules:read for reads. Mapping HRIS users to Zeeg additionally needs users:read (or admin:full).Concepts
Three different objects affect a user’s availability. Pick the right one for the job:
This guide focuses on time-off periods. Holiday subscriptions and weekly schedules are covered briefly at the end.
Time-off shape
A time-off period is all-day by default and identified by date, not datetime — there is nostartTime/endTime/timeZone on the resource. Half-day flags (startHalfDay, endHalfDay) with HH:MM cutoffs let the first or last day be partial; the cutoff is interpreted in the user’s schedule timezone.
Prerequisites
- A Zeeg admin or owner account with API access enabled.
- An API token with the following scopes:
users:read(oradmin:full) — list workspace users.schedules:read— list existing time-off.schedules:write— create, update, delete time-off.
- The token owner must have edit rights on each target user’s schedules (i.e. be the user themselves, an admin/owner, or the team manager). Targets the token can’t edit are reported as
failed— they don’t fail the whole request.
End-to-end sync flow
Map HRIS users to Zeeg users
Time-off endpoints accept any of three identifiers per user: HRIS users with no Zeeg account. This will happen — contractors, new hires not yet provisioned, deactivated employees still in the HRIS. Log them and skip; do not try to invite them implicitly.
email, slug, or workspace uuid. Email is the most stable join key against most HRIS systems, so cache an email → Zeeg uuid map at the start of every sync.Create time-off periods
POST /time-off is bulk-only. Each request creates the same period for one set of users (up to 10) or every active member of one team. There is no single-user create endpoint — to create one period for one person, send a users.emails array with one entry.Provide exactly one of users or team. Inside users, provide exactly one of emails, slugs, or uuids.Example: Alice is on vacation Mon–Fri next week.status is created, replaced, or failed — handle all three:200 if at least one entry succeeded; 422 only when every target failed. Always inspect each entry’s status rather than trusting the HTTP code alone.Half-day example. Alice leaves at 13:00 on Friday afternoon:Update or delete when the HRIS record changes
PATCH /time-off/{uuid} partially updates a period. Only supplied fields change; omitted fields are left alone.DELETE /time-off/{uuid} removes a period:Solve the dedup / idempotency problem
The 2. An external-id → Zeeg-uuid map on your side. Overlap-replace handles re-syncs of the same vacation, but it doesn’t help when:
/time-off resource has no native external-id lookup, so re-running a sync needs care to avoid duplicates. Use the two mechanisms Zeeg gives you:1. Built-in overlap-replace on POST. When you POST /time-off for a user and the new range overlaps an existing period, Zeeg deletes the old period(s), creates the new one, and returns status: "replaced" with the deleted UUIDs in replaced[]. This makes re-posting the same vacation safe-by-default — the second call replaces the first instead of duplicating.- The HRIS shifts a vacation to a non-overlapping range (old: May 11–15, new: May 18–22). POST creates a second period instead of moving the first. You need the original UUID to PATCH or DELETE.
- The HRIS deletes a vacation. Zeeg doesn’t know about the source-of-truth deletion; you must DELETE explicitly.
- You want to detect drift between HRIS and Zeeg (e.g. someone manually created a Zeeg-side period that has no HRIS counterpart).
hris_record_id → zeeg_period_uuid, plus the user identifier and the last-seen range) on the integration side. The map is your primary lookup. The note field is a fine forensic breadcrumb (e.g. [hris-id:bamboo-12345]) — it shows up in the dashboard and helps humans audit — but note is not query-filterable, so it’s a complement to the map, not a substitute.Reconcile: a worked sync loop
Putting it together. The sync runs against three sources of state — your HRIS, your local map, and Zeeg — and produces three sets of operations: create, update, delete.A few things this loop intentionally handles:
Python
- Idempotency on first run. If your map is empty but Zeeg already has matching periods, the POST overlap-replace logic absorbs them — you don’t end up with double-booked time-off.
- Range moves. PATCH first (preserves the UUID), POST as fallback when PATCH hits a
422overlap. - Upstream deletions. Anything in the map but not in this HRIS pull is treated as deleted.
- Failed entries.
upsertraises onfailedstatus (often: token can’t edit that user’s schedules) so you can log and continue.
Adjacent topics
Public holidays
For country- or region-wide closures (Christmas, Bavarian regional holidays, US Thanksgiving), use holiday subscriptions instead of creating time-off for every user every year:Recurring weekly availability
For permanent working-hour changes (someone moves to a 4-day week, or starts an hour later on Wednesdays), update the user’s availability schedule withPATCH /schedules/{uuid}. Use weeklyHours for the recurring pattern and specialHours for date-specific overrides. See PATCH /schedules/{uuid}.
Don’t use specialHours to model vacations — that’s what /time-off is for. specialHours is for altering the working day on a date (e.g. “I’m only available 09:00–11:00 on this date”); time-off is for removing it.
Rate limits and error handling
- The bulk POST endpoint accepts up to 10 users per request or one team of up to 200 active members. Chunk larger HRIS pulls into multiple requests.
- For
429 Too Many Requestsresponses, follow the exponential backoff guidance — 1s, 2s, 4s, … capped at 60s, with a small jitter. - See Errors for the standard error envelope. Common time-off-specific cases:
200withstatus: "failed"— token can’t edit that user’s schedules. Don’t retry; surface to a human.422 No updatable fields were providedon PATCH — the request body was empty.422 This time-off period overlaps with an existing oneon PATCH — fall back to POST or pick a non-overlapping range.404 Time-off period not foundon PATCH/DELETE — the UUID was already deleted, or the token can’t see it. Treat DELETE 404 as success; treat PATCH 404 as a stale map entry to be cleaned up.