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:

PassFilterReturns
PublishednoneFinalized shifts. The schedule as staff see it.
Deleteddeleted=trueShifts that were published and later removed.
Draftdraft=trueUnpublished 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:

Casepublish_statusOther fields
New unpublished shiftdraftdraft: true
Published shift edited, not republishedpublisheddraft: true
Published shift deleted in an unpublished editpublisheddeleted: 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

StatusMeaningWhat to do
400Malformed date filter or invalid parameterConfirm dates are URL-encoded ISO 8601 and gte precedes lte.
401Token missing, expired, or revokedRe-authenticate. See Authentication.
403Token cannot access this companyConfirm the token was issued for {COMPANY_ID}.
429Rate limitedBack 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


Did this page help you?