Integrate with the production schedule submission API using an API key tied to your paid subscription. Billing follows your plan and contract (not per-API-call usage pricing). You can also read and update tenant schedule defaults (PATCH /v1/schedule-defaults), and read or replace per-staff weekly shift wishes (GET/PUT /v1/staff/{staffId}/weekly-shift-wish). This page documents paid-tier entitlements, validation rules, and error codes — use the sidebar search to jump to a field.
This page documents only the product REST API on API Gateway ({stage}/v1/*, authenticated with x-api-key).
Next.js /api/* routes (Stripe webhooks, OG images, Web Vitals, and similar) are internal to the web application and are not part of this developer API. Do not use them for external integrations.
GET /v1/processing-history is not offered (removed with the legacy UI) and is not listed among the endpoints on this page.
This HTTP API is part of the paid product: access requires an active commercial agreement and keys are issued to your organization after onboarding.
Billing follows your subscription and contract. For paid plans, commercial charges are typically per registered staff seat per month (see the pricing page).
Playground / free-tier limits do not apply to paid API keys. Effective limits such as schedule span, staff count, and whether 15-minute granularity is enabled are determined by your paid entitlements. See Limits & defaults on this page.
Requests must include a valid API key issued under your paid subscription. Create, rotate, and revoke keys from the account area after signing in.
Send the key in the x-api-key header. Keys are tied to your organization for tenant resolution and plan-based access limits.
Manage keys: API management
Submit a schedule condition as JSON. This is the paid integration surface; the exact base URL is provided per environment after your API access is provisioned.
Use the base URL provided when your API access was provisioned or by your operations contact.
Replace BASE_URL with the API base URL (path prefix) provided when your access was provisioned.
Send your API key in the **x-api-key** header. Your organization (tenant) is identified from the key—you do not pass a separate tenant id in the query string.
Company read/update (GET/PATCH /v1/company) requires an active paid subscription. You may receive **503** (e.g. code **STRIPE_NOT_CONFIGUREED**) when billing is not configured for your tenant.
Windows PowerShell: a trailing \ is **not** a line continuation. Use one line, or end each line with a backtick (`) to continue.
curl -sS -D - \ -H "x-api-key: YOUR_API_KEY" \ "BASE_URL/v1/schedule-results?limit=10" curl -sS -D - \ -H "x-api-key: YOUR_API_KEY" \ "BASE_URL/v1/company"
Same base URL and API key as POST /v1/schedule. Optional query: limit (1–100, default 50), cursor (opaque token from the previous response field nextCursor).
GET /v1/company and company updates require an active paid subscription. You may receive **503 / STRIPE_NOT_CONFIGURED** when billing is not configured.
| Method | Path | Description |
|---|---|---|
| GET | {baseUrl}/v1/schedule-results | Lists SCHEDULE_RESULT parent rows: conditionId, status, candidateCount, createdAt/updatedAt. DynamoDB parent-row pagination. |
| GET | {baseUrl}/v1/schedule-results/history | Schedule result history search (same as the browser history view). Optional from / to (ISO 8601; default service start to now), keyword, eventName (comma-separated INSERT,MODIFY,REMOVE), limit, cursor. Merges recent and archive data automatically; returns only items and nextCursor. |
| GET | {baseUrl}/v1/schedule-results/{conditionId} | Gets one SCHEDULE_RESULT parent row by conditionId (JSON attributes; PK/SK omitted). |
| GET | {baseUrl}/v1/schedule-results/{conditionId}/resolution | Resolved optimization output: status, candidates, confirmedAssignments when present. If status is completed and not yet confirmed, rank-1 may be auto-confirmed on this GET (side effect). Use POST …/confirm-assignments for manual confirmation. |
| GET | {baseUrl}/v1/schedule-results/{conditionId}/review | Review payload for manual confirmation (candidates, requiredByCellKey, staff allowedCellKeys, etc.). |
| GET | {baseUrl}/v1/schedule-results/{conditionId}/emergency-shift-context | Emergency-shift context (calendar dates, staff, slots). Paid subscription required; null when condition missing. |
| GET | {baseUrl}/v1/schedule-conditions | Lists SCHEDULE_CONDITION parent rows (conditionId, dates, optional publicApiCancelledAt). |
| GET | {baseUrl}/v1/schedule-conditions/{conditionId} | Condition parent row plus reqDays and staffSnapshots (PK/SK stripped). |
| GET | {baseUrl}/v1/audit-logs | Lists audit log entries (newest first; cursor pagination). |
| GET | {baseUrl}/v1/schedule-defaults | Tenant schedule defaults row (SCHEDULE_DEFAULTS) or null if not yet saved. |
| GET | {baseUrl}/v1/schedule-actual/{conditionId} | SCHEDULE_ACTUAL row for edited/actual assignments, or null if none. |
| GET | {baseUrl}/v1/staff/{staffId}/weekly-shift-wish | Weekly shift wish row for the staff member, or null if none. |
| GET | {baseUrl}/v1/company | Returns Stripe Customer fields (name, email, phone, address) for the customerId on your API key. |
| GET | {baseUrl}/v1/users | Lists tenant users (optional: limit, cursor, userName, permissionProfileId, scope fullAccess|nonFullAccess). |
| GET | {baseUrl}/v1/users/{userId} | Gets one user by userId in the path. |
| GET | {baseUrl}/v1/staff | Lists active staff members for the tenant (display name sorted). |
| GET | {baseUrl}/v1/staff/{staffId} | Gets one staff member by staffId, including labor details (laborLawCompliance, laborFlsaExempt, personal caps, laborConstraintMask). Soft-deleted rows return 404. |
Use the same API key with JSON bodies. PATCH /v1/company rejects empty strings for fields such as company name (aligned with the web app). PATCH /v1/schedule-defaults saves tenant-wide defaults (requirementsByCellKey, planning horizon, etc.); paid-tier fields follow your subscription. PUT /v1/staff/{staffId}/weekly-shift-wish replaces the entire wish grid for that staff member (see validation note). DELETE /v1/users/{userId} returns 409 if the tenant would have zero active users, or when removing the last admin. POST /v1/staff returns 409 STAFF_LIMIT when the roster would exceed the tenant cap.
POST /v1/users requires userName, email, permissionProfileId (UUID from your tenant permission profiles); optional locale, password. The public API skips the verification-code email on create. PATCH user: include only fields to change; empty strings are rejected when a key is present. POST /v1/staff requires JSON { "displayName": "..." }. PATCH /v1/staff/{staffId} accepts any of displayName, laborLawCompliance, laborFlsaExempt, personalMaxWeeklyMinutes / personalMaxMonthlyMinutes / personalMaxThreeMonthMinutes (integer or null to clear), laborConstraintMask (object or null; only when laborLawCompliance is true). Labor fields require a paid subscription. POST /v1/schedule-results/{conditionId}/emergency-shift: absentStaffIds + absentDatesYmd and/or absentSlots (paid; source schedule must be completed). POST …/confirm-assignments: assignments array and optional relaxAvailability. PATCH /v1/schedule-defaults: see PUBLIC_API_SCHEDULE_REQUEST_BODY.md §12. PUT /v1/staff/{staffId}/weekly-shift-wish: scheduleStartDate, scheduleEndDate, timeRangeStart, timeRangeEnd, wishByCellKey.
| Method | Path | Description |
|---|---|---|
| PATCH | {baseUrl}/v1/company | Updates company (billing customer) profile fields. |
| PATCH | {baseUrl}/v1/schedule-defaults | Saves tenant schedule defaults (JSON body matches saveSchema in schedule-defaults-actions). Paid-tier fields follow your subscription. |
| POST | {baseUrl}/v1/users | Creates a user. Returns 201 with JSON user. |
| PATCH | {baseUrl}/v1/users/{userId} | Updates user fields (userName, email, permissionProfileId, locale). |
| DELETE | {baseUrl}/v1/users/{userId} | Deletes a user. 409 CONFLICT if the organization would have no users or last-admin rules apply. |
| POST | {baseUrl}/v1/staff | Creates a staff member (displayName). Returns 201 with staff JSON. |
| POST | {baseUrl}/v1/schedule-results/{conditionId}/emergency-shift | Clones a completed source schedule with absences applied; returns 201 with new conditionId. Paid subscription required. |
| POST | {baseUrl}/v1/schedule-results/{conditionId}/confirm-assignments | Validates and saves manually edited assignments (200 with { ok: true }). |
| PATCH | {baseUrl}/v1/staff/{staffId} | Updates displayName and labor details (laborLawCompliance, laborFlsaExempt, personal caps, laborConstraintMask) for one staff member. |
| DELETE | {baseUrl}/v1/staff/{staffId} | Soft-deletes a staff member (sets deletedAt). |
| DELETE | {baseUrl}/v1/schedule-conditions/{conditionId} | Marks the condition as cancelled (publicApiCancelledAt); does not delete child rows. |
| PUT | {baseUrl}/v1/schedule-actual/{conditionId} | Saves actual shift assignments JSON { "assignments": [ ... ] } (validated against the condition grid). |
| PUT | {baseUrl}/v1/staff/{staffId}/weekly-shift-wish | Replaces weekly wish for the staff member: full grid (1–7 days), wishByCellKey per cell (NONE|LOW|HIGH); granularity from tenant schedule defaults. |
| POST | {baseUrl}/v1/users/{userId}/restore | Restores a soft-deleted user. |
| Header | Description |
|---|---|
| x-api-key | API key tied to your paid subscription (identifies your tenant and plan limits). |
| Content-Type | application/json |
Some response headers (such as request ids) are trace metadata, not your API key. Treat JSON **bodies** as confidential when they contain company or user data.
Typical validation limits for your contract (15-minute granularity, schedule span, registered staff count, and related caps).
| Setting | Paid API (typical) |
|---|---|
| Billing model | Stripe subscription; product charges typically scale with registered staff seats per the pricing page (not pay-per API call). |
| maxScheduleDays | Up to 14 calendar days per submission for standard paid two-week detailed planning (wider spans only if your contract allows). |
| maxStaffPerSchedule | Up to 500 staff rows per POST /v1/schedule (PUBLIC_SCHEDULE_MAX_STAFF; inline staff IDs in the request body). |
| maxRosterStaff | Up to 30 active staff via POST /v1/staff under the current app default (STAFF_ROSTER_MAX_PAID); returns 409 STAFF_LIMIT when exceeded. |
| allowedGranularities | [60, 15] — 15-minute slots are a paid capability; hourly (60) is also supported. |
| maxPayloadBytes | 524288 (512 KiB) UTF-8 unless a higher cap is granted. |
| strictUnknownRootKeys | true — unknown top-level or staff keys are rejected |
Per-cell required headcount remains an integer from 0 through 15. Exceeding rate limits may return an error response.
When strict mode is on, only the following top-level keys are accepted. Any other key returns UNKNOWN_FIELD.
The payload follows the paid public schedule request model: calendar dates, time zone, slot grid (60- or 15-minute on paid API), per-cell requirements, and staff availability. Labor law jurisdiction, paid optimization flags, and staff-level labor fields are not part of the JSON — the server writes defaults on the parent SCHEDULE_CONDITION row when persisting (see Labor law & paid flags). DynamoDB keys and internal entity types must not appear in the body.
| Field | Type | Required | Notes |
|---|---|---|---|
| timeZone | string | Yes | IANA time zone for interpreting dates and slots. |
| scheduleStartDate / scheduleEndDate | string (YYYY-MM-DD) | Yes | Inclusive calendar range; start ≤ end. |
| timeRangeStart / timeRangeEnd | string (HH:mm) | Yes | Wall-clock range that defines the slot column; must yield at least one slot. |
| slotGranularityMinutes | 60 | 15 | Yes | Paid API: 60 (hourly) or 15 (15-minute slots). Must be in your allowedGranularities. |
| requirementsByCell | object | Yes | Keys = cellKey in the computed grid; values = integer 0–15. |
| staff | array | Yes | Non-empty; max size per limits; each item: staffId, displayName, optional availableCells. |
The following sections mirror the validator in `app/lib/public-schedule-api/validate.ts` and sanitizers in `sanitize.ts`, using the paid-tier defaults from `PUBLIC_SCHEDULE_SUBMIT_DEFAULTS` unless options are passed explicitly. Prefer sending JSON numbers as actual numbers (not strings). After validation, `build-schedule-transact-items.ts` adds parent-row fields (paid optimization copy, labor jurisdiction, model version) that are not request keys — see Labor law & paid flags.
string | omitted · Required: No — optional
Allowed values
Behavior
Typical errors
TYPE_ERRORUnsupported apiVersion value.string · Required: Yes
Allowed values
Behavior
Typical errors
INVALID_TIME_ZONEMissing, empty, or not a valid IANA zone.string, string · Required: Yes — both
Allowed values
Behavior
Typical errors
INVALID_DATE_RANGEBad format, invalid dates, or empty range.SCHEDULE_SPAN_TOO_LONGMore than maxScheduleDays days.string, string · Required: Yes — both
Allowed values
Behavior
Typical errors
INVALID_TIME_RANGEMissing values or zero slots generated for the range.number (JSON number) · Required: Yes
Allowed values
Behavior
Typical errors
INVALID_GRANULARITYNot 60/15, or not allowed for the plan.Record<string, number> · Required: Yes
Allowed values
Behavior
Typical errors
INVALID_REQUIREMENTSNot an object, or invalid numeric value for a key.UNKNOWN_CELL_KEYKey not in the computed date × slot grid.array · Required: Yes — non-empty array
Allowed values
Behavior
Typical errors
INVALID_STAFFEmpty array, too many rows, bad object, or failed sanitization.UNKNOWN_FIELDExtra key on a staff object in strict mode.DUPLICATE_STAFF_IDSame staffId twice.string[] | omitted · Required: No — optional
Allowed values
SHORTFALL check
Typical errors
INVALID_AVAILABLE_CELLUnknown cell key or empty entry.SHORTFALL_CELLSNot enough staff cover required headcount for a cell.n/a — persisted server-side · In request: No — never send these keys
What the API stores on the parent row
Docs
If you send disallowed keys
UNKNOWN_FIELDStrict mode rejects unknown root or staff properties.Applied before or during validation as implemented in `sanitize.ts`. These rules explain why a value might be rejected as INVALID_STAFF.
staffId
displayName
Cell keys (requirements / availableCells)
202 Accepted
The request body passed validation and the condition was written; optimization may continue asynchronously. The handler may return a JSON body with conditionId and related fields.
Validation errors return structured error information with a stable `code`. Parsing happens before validation (INVALID_JSON, PAYLOAD_TOO_LARGE). Runtime persistence failures may surface as DYNAMODB_ERROR. Write handlers for schedule defaults may return SCHEDULE_DEFAULTS_SAVE_FAILED (HTTP 400) with details when Zod or business rules fail.
| Code | Meaning |
|---|---|
| INVALID_JSON | Body is not valid JSON. |
| PAYLOAD_TOO_LARGE | UTF-8 byte length exceeds maxPayloadBytes (default 512 KiB). |
| TYPE_ERROR | Wrong JSON type, or unsupported apiVersion string. |
| UNKNOWN_FIELD | Strict mode: disallowed property on root or staff object. |
| INVALID_TIME_ZONE | timeZone missing or not a valid IANA name. |
| INVALID_DATE_RANGE | Dates not YYYY-MM-DD, invalid range, or empty enumeration. |
| SCHEDULE_SPAN_TOO_LONG | Too many days between start and end (see maxScheduleDays). |
| INVALID_TIME_RANGE | Time range missing or yields zero slots. |
| INVALID_GRANULARITY | slotGranularityMinutes not 60/15 or not allowed for plan. |
| INVALID_REQUIREMENTS | requirementsByCell not an object, or count not integer 0–15. |
| UNKNOWN_CELL_KEY | Key not in the computed schedule grid. |
| INVALID_STAFF | staff array empty, too large, bad row, or failed sanitization. |
| DUPLICATE_STAFF_ID | Duplicate staffId values. |
| INVALID_AVAILABLE_CELL | Empty or unknown entry in availableCells. |
| SHORTFALL_CELLS | Insufficient staff availability vs required counts. |
| DYNAMODB_ERROR | Transient or persistence error after validation (handler-dependent). |
| SCHEDULE_DEFAULTS_SAVE_FAILED | PATCH /v1/schedule-defaults: validation or business rules failed (see error.details). |
| LABOR_COMPLIANCE_REQUIRED | PATCH /v1/staff/{staffId}: laborConstraintMask sent while laborLawCompliance is false. |
{
"apiVersion": "2026-04-01",
"timeZone": "Asia/Tokyo",
"scheduleStartDate": "2026-04-07",
"scheduleEndDate": "2026-04-13",
"timeRangeStart": "08:00",
"timeRangeEnd": "20:00",
"slotGranularityMinutes": 60,
"requirementsByCell": {
"2026-04-07__slot-0": 2
},
"staff": [
{
"staffId": "550e8400-e29b-41d4-a716-446655440000",
"displayName": "Example",
"availableCells": ["2026-04-07__slot-0"]
}
]
}Further reading. Product plans & paid features: docs/architecture/NURSE_SCHEDULING_SERVICE_SPECIFICATION.md. POST /v1/schedule request body (and labor defaults on the parent row): docs/data-and-api/PUBLIC_API_SCHEDULE_REQUEST_BODY.md §1–§11. Tenant schedule defaults PATCH body: same file §12. Implementation: app/lib/public-schedule-api/ (submit), app/features/schedule-settings/schedule-defaults-actions.ts (defaults), app/lib/public-api-write/weekly-shift-wish-tenant.ts (weekly wish). Deployment: docs/aws/CDK_DEPLOY.md (public schedule API).