BUILD WITH SHIFTFOREST
Connect your team’s work.
Bring schedules into your software with scoped access, predictable updates and a clear recovery path when a request fails.
TypeScript source + JavaScript modules for Node 22.23.2+. Includes read-only API examples, a Python 3.10+ local-replica consumer and a webhook verifier.
01 · GET STARTED
Your first connection
In Company → API, create an independent workspace integration. Choose the minimum scopes and workplaces, a finite expiry, then review the access and verify your authenticator. Save the one-time key in your server’s secret store.
Extract the developer kit, set SHIFTFOREST_API_KEY and SHIFTFOREST_ORGANIZATION_ID in your server environment, then run node examples/read-schedule.mjs. This example checks the organization before fetching a page and prints counts.
Server-side JavaScript
import { ShiftForest } from "./shiftforest.mjs";
const api = new ShiftForest({
apiKey: process.env.SHIFTFOREST_API_KEY,
});
const organization = await api.organization();
if (organization.data.id !==
process.env.SHIFTFOREST_ORGANIZATION_ID) {
throw new Error("Organization mismatch");
}
const page = await api.shifts(
new URLSearchParams({ limit: "25" }),
);
// Store records and checkpoints transactionally.
// Do not log credentials or employee details.02 · PERMISSIONS
Give each integration the access it needs
Integrations belong to the workspace and survive their creator’s offboarding. Permissions are immutable: use a reviewed replacement and rebuild the consumer replica when access changes. Selected-workplace restrictions apply before pagination; pay fields need a separate scope.
For a signed-in person with several workspaces, set the SDK’s workspaceId to the company UUID. The API verifies that membership and applies its role and workplace access. API keys already belong to one workspace and reject the workspace header. Keep each workspace’s cursors, resource versions and reviewed commands separate.
| Capability | Scopes | Behavior |
|---|---|---|
| Workspace roster and participants | Human workspace session | GET /workspace/rota-roster pages workers, including those without shifts, with a separate matching total. POST /workspace/participants resolves up to 500 distinct workforce UUIDs per call. Employee peer fields are redacted. Preserve filters and next_cursor; restart after a scope_hash change. These are live views, not immutable snapshots. Independent integrations use the scoped /users directory instead. |
| Shift cover status | recovery:read | Read workplace-scoped recovery status. Approval and employee consent require their own signed-in sessions. |
| Schedules | shifts:read · shifts:write | Read and update shifts; synchronize changes. |
| People and workplaces | users:read · locations:read · roles:read | Discover public numeric IDs and current assignments. |
| Attendance history | attendance_history:read | Read historical clock records at authorized workplaces. Requires from/to timestamps, up to 93 days. |
| Leave | leave:read | Read dates and status for workers at authorized workplaces. Inclusive overlap windows up to 366 days; no free-text reasons. |
| Credentials | credentials:read | Read recorded review status and expiry dates. Evaluate dates against the intended work date and your policy. |
| Reviewed payroll exports | payroll_exports:read · pay:read | Read immutable exports and paged component lines. Requires all workplaces. Totals and minutes are decimal strings; version 2 captures units, base rates, multipliers and reviewed policy provenance. |
| Reviewed payroll changes | Human administrator + recent MFA | Review effective-dated rates and configured overtime/enhancement/sleep-in policies, record agreed fixed salary/payments, append signed corrections and record administrator-reported reconciliation. Workplace payroll delegates use /payroll-access for scoped reviews, exports and corrections; base manager access alone is insufficient. Use /payroll-changes/exports/{id} for frozen attendance UUIDs. Persist request_id and review_id for retries; original exports remain unchanged. |
| Worker profile commands | users:read · users:write | Review and edit employee names, job role and workplaces. Access to every existing and proposed workplace is required. |
| Worker creation and pay | users:write · pay:read · pay:write | Create workforce records with login disabled; edit rates, weekly hours and payroll codes. Account access and invitations remain separate. |
| Pay fields | pay:read | Add pay fields to authorized people reads. |
| External references | external_ids:write | Maintain your system's identifiers on supported records. |
| Availability | availability:read · availability:write | Manage complete daily availability intervals. |
| Shift requests | shift_requests:read · shift_requests:approve | Review requests after the employee has given consent. |
| Attendance visibility | attendance:read | Read who is currently on shift; clocking requires a human session. |
| Rota publication | rotas:write | Validate and publish with current review details; all-workplace access required. |
Call /organization and /capabilities to verify the current organization, principal, permission boundary and limits. Key rotation can overlap the previous key for up to 60 minutes. Disabling an integration revokes all its credentials.
03 · SYNCHRONIZATION
Keep a complete replica
Follow each opaque next_cursor while has_more is true. Bind the replica to its organization, principal and permissions. Store records and the checkpoint in one transaction.
For cross-resource synchronization, create a checkpoint before reading the initial pages. Then poll /sync/changes: fetch current records for refresh notices and remove cached records for removal notices. Commit each page and its cursor together, even when empty. A 410 response means workplace visibility changed, the checkpoint is invalid, or privacy processing reset the history. Clear the replica and rebuild from a new checkpoint. Privacy resets also invalidate older shift-feed and catalogue cursors. The bundled Python consumer clears its local rows and checkpoint before stopping for a fresh run; copied databases and external systems require separate handling.
User, workplace and role catalogue pages exclude later insertions, including records with imported numeric IDs. Existing records remain live. Keep query parameters unchanged with each cursor. If an older catalogue cursor returns a restart error, discard it and begin again from the first page; use the changes feed for ongoing reconciliation.
Use GET /users?search=Alex&location=123&level=employeeor listUsers() to read filtered worker pages. Name search is a literal substring; level means workspace access, not the job role. Filters never widen workplace permissions. Follow the returned cursor with the same filters, and treat omitted pay fields as unavailable. getUser() reads one worker.
Human administrator and manager sessions can read complete team totals with teamSummary({ week: "2026-10-26" }) or GET /team/summary?week=2026-10-26. Optional name and workplace filters select team members. Here the workplace identifier is a native UUID. Managers receive hours only for managed workplaces; whole-workspace weekly-limit comparison is unavailable to them.
Use setupProgress() for current administrator setup facts. These checks do not establish policy approval or historical activation. Use searchPeople() for minimal human-scoped navigation results. Employees can find themselves and current workplace colleagues; administrators can search the workspace. Results exclude contact and pay fields. Refine the query when has_more is true. Use teamMembers() for bounded human-session directory pages and teamMember(nativeUuid) for independently authorized profiles. Profiles include a native id and the public_id used by numeric workforce API routes. Directory cursors expire after 24 hours and bind to the filters and current management scope. New workers appear on a fresh traversal; existing records remain live.
The shift changes feed includes deletion and access-removal events. Remove records when instructed. Webhooks are hints to reconcile; delivery can repeat or arrive out of order.
For recorded attendance totals, use /attendance/summaryor the SDK attendanceSummary method. The same explicit attendance-history permission and workplace scope apply. Queries cover at most 93 days and 100,000 matching records; larger results require a narrower filter and never return partial totals. Daily groups use the company-local clock-in date. Duration totals exclude open and voided sessions and return exact decimal seconds before payroll rounding or paid-break policies.
Human administrators and managers can use /dashboard/attentionor dashboardAttention() for current company-local review counts. The response includes the selected workspace and member UUIDs, timezone, week and month boundaries, and calculation time. It accepts no filters; API keys and employee sessions are denied. Counts include the complete current month, subject to a 100,000-record limit per section. A 422 means the limit was exceeded; display unavailable rather than zero.
Browser notification devices use /notifications/push/devicesand the SDK pushDevices(), enrollPushDevice()and revokePushDevice() methods. These require the person’s own workspace session. Enrollment requires explicit browser consent and a stable Idempotency-Key; revocation requires the listed revision in If-Match. Device IDs are UUIDs. Endpoint capabilities and encryption secrets are never returned in device lists. Sending stays unavailable until notification delivery is commissioned.
Use ISO timestamps with offsets in v2. Numeric resource IDs, UUID workflow IDs and company-local dates have distinct meanings—follow the schema for each field.
04 · RETRIES & CONFLICTS
Recover without duplicating work
Persist an Idempotency-Key before every logical write. Retry the identical request with that key after a timeout. Receipts last 24 hours; reconcile the current state after expiry.
Send the current version in If-Match. A 412 means fetch and review again. A 409 can mean a reused retry key with different content. Never silently overwrite a newer record.
The SDK retries network errors and 429/502/503/504 up to three attempts. Keep X-Request-ID for support. Current limits are 120 attempts per principal and 600 per organization per minute; edge limits may apply sooner.
05 · WEBHOOKS
Verify, persist, then acknowledge
Verify the original raw request bytes against RotaTree-Signature before parsing JSON. The kit’s verifier supports current and previous signing secrets with a five-minute timestamp tolerance. During signing-key rotation, both signatures remain available for one hour.
Persist the unique event_id and effects durably before acknowledging delivery. Replay keeps the original event identity and receives a fresh signature. Configure webhook subscriptions separately from API-key workplace permissions.
Run node examples/verify-event.mjs raw-body.bin signature.txt with the signing secret in your server environment to verify a captured event. This command does not receive or acknowledge deliveries.
06 · CONTRACTS & LIMITS
Build against the deployed contract
- v2 OpenAPI schema — scoped workforce integrations.
- v1 compatibility schema — existing numeric-ID consumers; optional record ETags and administrator retry receipts. External Auth provisioning is excluded.
- Browser billing schema — session-authenticated live billing for workspace administrators. GET /api/billing/invoices returns paginated GBP invoice history, payment status and private invoice/PDF links for the selected workspace. The downloadable billing SDK provides typed reads; workforce integration keys cannot access billing. GET /api/billing/features and SDK features() expose current premium creation access; the database rechecks it when creating a campaign or pack. Amounts are pence, and invoice totals are not a refund or credit-note ledger. GET /api/billing/payments and /api/billing/credits return separate payment and credit-note pages. GET /api/billing/refunds requires an owned payment_id from the payments list. Use the SDK payments(), credits() and refunds() methods with cursor, limit and cancellation signal. Pending refunds are not successfully returned funds; credit notes may describe the same adjustment as a refund, so do not sum both. These reads move no money and send no messages.
- Account email schema — own preferences, explicit consent, activity, unsubscribe and operator-reviewed campaigns. Human sessions only; campaign operations require recent MFA. The downloadable email SDK includes typed preferences and revision handling.
- Reviewed login recovery schema — human-session Supabase RPCs for administrator MFA review, immediate suspension and verified replacement-account acceptance. The workforce ID and history stay unchanged. Keep command IDs for ambiguous retries; do not automatically accept or resend a changed review. No email is sent by this workflow. The downloadable login-recovery client uses your configured Supabase URL, publishable key and user access token.
- SCIM provisioning schema — configurable company identity at /scim/v2/{connectionId}. Use a dedicated expiring provisioning key. Users and Groups support bounded CRUD, equality filters and atomic PATCH. Preserve externalId exactly and map it to the registered SAML provider’s stable subject. Groups do not grant permissions. The kit’s ShiftForestScim client preserves weak ETags and never retries writes automatically. Reconcile an ambiguous write by resource ID or externalId before retrying. Real provider commissioning is required before enabling customer access.
- Developer kit instructions and file hashes — validate the files you downloaded.
The kit is a downloadable source distribution; npm publication is not yet available. Workspace administrators can create a hosted sandbox in Settings → Developer sandbox after MFA verification. Use its displayed base URL and sandbox key with this same SDK. Sandboxes contain synthetic data, expire within 24 hours, and support reset and deletion. Each reset replaces the key and all record IDs; discard previous cursors, cached IDs and idempotency keys. Limits are 120 requests per minute, 64 KiB per request and 100 MiB per database, with one allocation per workspace subject to capacity. Provider sign-in, private file storage, payments, email delivery, Realtime and outbound webhooks are not connected in the sandbox. Independent integrations cannot attest employee consent or forge attendance. Statutory payroll calculations, payroll-provider certification, and document signing are outside the currently advertised contract.
07 · WORKER CHANGES
Review worker changes before applying them
Prepare a creation review with POST /users/creation-context, then POST /users with the same command UUID, profile and review token. Supply an explicit hourly rate in currency minor units, weekly hours, numeric job role and workplace IDs. The new workforce record has login disabled; no invitation or email is sent.
For an existing employee, GET /users/{id} for its version, then POST /users/{id}/profile-context and PATCH /users/{id}/profile with that If-Match. All mutations need an Idempotency-Key. Preserve the original UUID, body, review token and version for retries, including after a timeout. A changed review requires a new command and explicit review.
Use the SDK workerCreationContext, createWorker, workerProfileContext and updateWorkerProfile methods. Email, login and administrator access changes use the workspace workflow. Pay edits require their own permission and do not verify a rate for payroll. Human API writes require a recently verified authenticator; independent integrations use their reviewed permissions.
08 · SHIFT COVER
Offer cover with manager approval and employee consent
Integrations with recovery:read can poll GET /recovery/status for up to 93 days of shift rota dates, restricted to their authorized workplaces. Responses include campaign status and counts without the manager’s reason or recipient details. Recovery is not yet a change-feed or webhook topic. Use the read-recovery example in the developer kit.
A signed-in manager reviews candidates and prepares internal, bank or agency offers through /recovery/candidates and /recovery/prepare. Launching or cancelling needs recent MFA, a current review token and a persisted command UUID and Idempotency-Key. Offers reserve an open shift; only an eligible employee’s explicit acceptance assigns it. Integrations cannot approve or accept on someone’s behalf.
Signed-in workplace managers can compare up to five workers for one open shift with POST /recovery/simulate and the SDK simulateStaffing method. Supply numeric shift and workers IDs. The response compares current coverage with each independent assignment, explains eligibility and shows improved or worsened blocking findings. It makes no changes or offers. Use a fresh request without Idempotency-Key or If-Match; the result is not an approval. Shifts are limited to 25 elapsed hours.
The SDK includes recoveryCandidates, prepareRecovery, launchRecovery, recoveryCampaign, cancelRecovery, recoveryOffers, recoveryOffer and respondRecovery. Keep the exact reviewed command for uncertain retries. A conflict requires a new review. Shift and worker IDs are numeric; campaign and offer IDs are UUIDs. Eligibility checks current access, availability and configured staffing rules; filling one shift may still leave other coverage gaps.
09 · LEAVE ENTITLEMENTS
Integrate contractual leave balances
GET /leave-balances and /leave-ledger/{account_id} expose approved hours or policy days, signed ledger entries and captured daily calculations. Independent integrations need leave:read and remain within their workplace scope. Free-text evidence and approver identities are omitted. A missing account means the policy has not been configured; it does not mean zero entitlement.
Policy setup and adjustments require another administrator’s human session with recent MFA. Review opening entitlement and effective work patterns before creating an account. Record approved accrual, proration, TOIL or expiry calculations as immutable adjustments. Carryover debits and credits commit together. No statutory allowance or automatic accrual rule is assumed.
Contract changes use /leave-ledger/{account_id}/policy/review and /policy, with explicit confirmation of the corrected charges. The original policy and prior charges remain in history. Read /policies to page through amendments; ledger entries identify their captured policy version. A changed balance, leave request or worker profile requires a fresh review.
POST /leave-requests accepts integer start_minute (0–1439) and end_minute (1–1440) boundaries in the captured organisation timezone. 1440 means midnight after the final date. Times skipped or repeated during a clock change are rejected. Another authorised manager reviews the entitlement impact through /leave/{id}/review before applying /leave/{id}/decisions. Cancellation reverses the captured charges and corrections. Preserve the exact request_id and reviewed body across retries; omit Idempotency-Key and If-Match. Signed live pages default to 25 records, maximum 50. Poll entitlement records separately from the workforce change feed.
Employees can withdraw their own pending request through /leave/{id}/withdraw/review and /withdraw using their human session. No balance changes. The retained record has status denied with withdrawn_at identifying a withdrawal. An intervening approval requires a fresh manager-reviewed cancellation instead. GET /leave/{id} lets the requester refresh their own record; collection and other-worker access keep their existing restrictions. API keys cannot perform withdrawals. Legacy v1 reads include exact minutes; an unrepresentable am/pm boundary is null, and v1 writes cannot rewrite a custom-time record.
10 · DATA & PRIVACY
Integrate reviewed privacy requests
The privacy API uses the person’s own access token and selected workspace. API keys cannot impersonate requesters. GET and POST /privacy/requests list and submit your requests. Verified administrators can record requests received through another channel, including from former workers, using /privacy/intake/review and /privacy/intake. This does not restore login access or send an email.
Review replies and decisions before applying them. Persist the request_id, review token and exact body for retries. Privacy commands reject Idempotency-Key and If-Match; their durable command records handle retries. A conflict needs a fresh review. Worker IDs are public numbers; case, hold and export IDs are UUIDs. Cursor pages are bound to the caller, workspace and filters. Privacy records are not in the general change feed.
The current owner can record approved retention policies. Verified administrators can place and release preservation holds. No legal period is supplied automatically, and recording a policy does not enable deletion. The governance response reports the separate purge controls.
An active access or portability request can produce a reviewed personal export after an export-retention policy is approved. Recent MFA is required for creation and download. Choose up to 366 days; each section is limited to 1,000 records, with 10,000 records and 8 MiB overall. The review explains included records and omissions. This limited copy leaves the request open. Revocation stops future downloads; it cannot recall downloaded copies.
Use the SDK reviewPersonalExport, createPersonalExport and verifiedPersonalExport methods. The verification helper checks the exact UTF-8 bytes, checksum, export ID, workspace and access expiry before returning bytes to save. The read-privacy example prints only your request count. Privacy operations and their response types are in the OpenAPI document and downloadable developer kit.
Primary-profile removal is a separate partial action for an open erasure request. Use reviewProfileRedaction to inspect blockers and retained records, then applyProfileRedaction with explicit confirmation and the unchanged review token. It requires the current owner, recent MFA, reviewed offboarding, elapsed approved retention and no active holds or pending dependencies. The deployment must commission it first. Administrators can read profileRedactionStatus after an uncertain response; preserve the exact command ID and body when retrying.
This removes primary contact details and current API profile mappings. Worker identifiers, financial fields, historical records, shared authentication, files, backups and external copies remain. It does not close the request or report full erasure. Existing sync cursors return 410 after removal: clear the old replica and restart checkpoint and snapshot before processing further changes.
Review delegated workplace access
Use the seven /access endpoints to discover active members and workplaces, configure workplace groups and named staffing roles, review changes, apply a durable command, and read the immutable change history. These operations require an active human workspace session; administration also requires recent authenticator verification.
Staffing readers can list and download evidence in their assigned scope. Staffing managers can also prepare, create and revoke packs. Grants expire, include descendant workplaces when a group is selected, and never change the recipient’s base role. SCIM grants follow current active group membership and connection status.
Access identifiers are opaque UUIDs from the access discovery endpoints, separate from numeric workforce resource IDs. Retain the exact request_id and review_id after an interrupted apply; a conflict requires a new review. The OpenAPI document and TypeScript SDK include all request, response, paging and retry details.
Share verified staffing evidence
Workplace managers can review and freeze up to 31 local days of staffing evidence. The pack separates current records from recorded historical requirements and credentials, and makes missing history explicit. Names, contact details, pay and private notes are omitted; worker labels remain pseudonymous personal data.
Five documented operations support review, creation, listing, verified download and revocation. Use a human session and selected workspace; handling pack contents requires recent MFA. The SDK verifiedStaffingPack helper checks bytes, checksum, identity, workspace and expiry. Internal links require the recipient’s own current permission. Printable reports retain the source JSON checksum.
The owner must record an approved retention policy first. Access expires within 24 hours; expiry does not delete the retained copy. Reports explain their limits and do not certify regulatory compliance or staffing adequacy. Exact schemas and retry rules are included in the developer kit.
Private workforce documents
Eight /files operations support reservation, upload credentials, completion, metadata lists, version history, verified content, acknowledgement and retirement. Use an active human session and selected workspace. Managers need current worker access and recent MFA; API keys cannot upload or download private documents. Browser clients use the application’s same-origin /v2/ endpoint.
Reserve an immutable version with its SHA-256, exact byte count and matching PDF, PNG or JPEG filename. Files are limited to 4 MiB. PUT the bytes using the returned upload credential, then complete within the 15-minute reservation. Provider upload credentials last two hours; late uploads remain quarantined. Persist command UUIDs for exact retries. Poll version history until scanning finishes. Encrypted PDFs, embedded files, malformed documents and exceeded scan limits are rejected.
Download through verifiedPrivateFile with the reviewed descriptor. Downloads recheck authorization and verify the stored bytes; no public download links are issued. Acknowledgement binds a version and checksum and is not an electronic signature. Retirement prevents subsequent downloads; it cannot recall saved copies. An owner-approved document retention policy is required. Four additional privacy operations at /privacy/documents/{version_id}/deletion support owner review, authorisation, administrator status and explicit retry. Use /review first and retain the exact request_id and body on retry. Retention deadlines and active holds block authorisation. Authorisation immediately revokes access; only verified absence from primary storage completes deletion. Metadata, backups and downloaded copies remain separate. The worker must be commissioned before authorisation is enabled. The developer kit describes capacity limits and recovery boundaries.