Synchronizing Shifts for Data Warehousing
To sync shifts into a warehouse, page through GET /v2/company/{COMPANY_ID}/shifts on a date range. The endpoint returns published, non-deleted shifts by default. Deleted and draft shifts require explicit filters, and each filter is a separate request.
Run three passes per window to capture the full picture:
| Pass | Filter | Returns |
|---|---|---|
| Published | none | Finalized shifts. The schedule as staff see it. |
| Deleted | deleted=true | Shifts that were published and later removed. |
| Draft | draft=true | Unpublished creates, edits, and deletions. |
Do not combine deleted=true and draft=true in one request. The results are not reliable.
Ignore include_draft. It is internal and not part of the supported API.
Shift states
A shift moves between four states, and which filter surfaces it depends on where it sits.
stateDiagram-v2
[*] --> Draft: created in an unpublished schedule
Draft --> Published: schedule published
Published --> EditedDraft: edited, not yet republished
EditedDraft --> Published: republished
Published --> Deleted: removed after publishing
Draft --> [*]: discarded
note right of Draft
draft=true
publish_status: draft
end note
note right of EditedDraft
draft=true
publish_status: published
end note
note right of Published
default response
end note
note right of Deleted
deleted=true
end note
The field combination is what tells the two draft cases apart. A brand-new unpublished shift has publish_status of draft; an unpublished edit to an existing shift keeps publish_status of published and carries draft: true.
Sync published shifts
This is the pass most warehouses need. It reflects the final schedule.
curl --request GET \
--url 'https://api.7shifts.com/v2/company/{COMPANY_ID}/shifts?limit=250&start%5Bgte%5D=2026-08-01T00%3A00%3A00Z&start%5Blte%5D=2026-08-08T00%3A00%3A00Z' \
--header 'Authorization: Bearer {ACCESS_TOKEN}' \
--header 'x-api-version: 2026-01-01'start[gte] is the beginning of the window and start[lte] is the end. Set gte earlier than lte or the range is empty and the response contains no shifts.
Sync deleted shifts
Deleted shifts are excluded from the default response. Request them explicitly to keep your warehouse from holding shifts that no longer exist.
curl --request GET \
--url 'https://api.7shifts.com/v2/company/{COMPANY_ID}/shifts?limit=250&start%5Bgte%5D=2026-08-01T00%3A00%3A00Z&start%5Blte%5D=2026-08-08T00%3A00%3A00Z&deleted=true' \
--header 'Authorization: Bearer {ACCESS_TOKEN}' \
--header 'x-api-version: 2026-01-01'Each returned shift carries deleted: true. Mark the matching row in your warehouse as deleted rather than dropping it, so downstream labor reporting keeps its history.
Sync unpublished shifts
Draft shifts are provisional. A manager building next week's schedule generates them, and they can change or disappear before anyone works them.
curl --request GET \
--url 'https://api.7shifts.com/v2/company/{COMPANY_ID}/shifts?limit=250&start%5Bgte%5D=2026-08-01T00%3A00%3A00Z&start%5Blte%5D=2026-08-08T00%3A00%3A00Z&draft=true' \
--header 'Authorization: Bearer {ACCESS_TOKEN}' \
--header 'x-api-version: 2026-01-01'Three cases come back together:
| Case | publish_status | Other fields |
|---|---|---|
| New unpublished shift | draft | draft: true |
| Published shift edited, not republished | published | draft: true |
| Published shift deleted in an unpublished edit | published | deleted: true |
Store draft shifts in a separate table or behind a status column. Feeding them into the same table as published shifts inflates scheduled-hours figures with work nobody has committed to.
Sync loop
graph TD
A[Pick a date window] --> B[Pass 1: no filter]
B --> C[Pass 2: deleted=true]
C --> D[Pass 3: draft=true, optional]
D --> E[Page each pass<br/>until meta.cursor.next is null]
E --> F[Upsert on shift id]
F --> G[Advance the window]
G --> A
Each pass paginates independently. Use limit up to 250 and follow meta.cursor.next until it is null. See Pagination.
Upsert on the shift id. Shifts get edited after publication, so a append-only load produces duplicates.
Re-sync a trailing window rather than only new dates. Managers edit and delete shifts in the recent past, and a forward-only sync never sees those changes. A rolling window covering the current and previous pay period is a reasonable starting point; widen it if you see drift.
Subscribe to the schedule.published webhook to sync on change instead of on a fixed schedule. See Webhooks.
Error responses
| Status | Meaning | What to do |
|---|---|---|
400 | Malformed date filter or invalid parameter | Confirm dates are URL-encoded ISO 8601 and gte precedes lte. |
401 | Token missing, expired, or revoked | Re-authenticate. See Authentication. |
403 | Token cannot access this company | Confirm the token was issued for {COMPANY_ID}. |
429 | Rate limited | Back off and retry. A full historical backfill will hit this; add a delay between pages. |
An empty data array is a 200, not an error. If you get one unexpectedly, check the date range direction before anything else.
Related
Updated 14 days ago
