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 the product REST API for external integrations (API Gateway {stage}/v1/*, authenticated with x-api-key).
Schedule-result completion webhooks are outbound HTTPS notifications configured in API management (not REST CRUD). See Completion webhooks on this page for payload and signature details.
In-app browser routes (for example payment callbacks or analytics paths) are not a substitute for this product API. For external systems, use /v1/* on this page and completion webhooks.
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.
Paid features (emergency shift, labor details, some schedule defaults) require an active paid subscription. You may receive **503** (e.g. code **STRIPE_NOT_CONFIGURED**) when billing is not configured for your tenant. Company profile, audit logs, and user (account) management are handled in the web app and are not part of the public API.
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/staff"
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).
Lists SCHEDULE_RESULT parent rows: conditionId, status, candidateCount, createdAt/updatedAt. DynamoDB parent-row pagination.
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.
Gets one SCHEDULE_RESULT parent row by conditionId (JSON attributes; PK/SK omitted).
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.
Review payload for manual confirmation (candidates, requiredByCellKey, staff allowedCellKeys, etc.).
Emergency-shift context (calendar dates, staff, slots). Paid subscription required; null when condition missing.
Lists SCHEDULE_CONDITION parent rows (conditionId, dates, optional publicApiCancelledAt).
Condition parent row plus reqDays and staffSnapshots (PK/SK stripped).
Tenant schedule defaults row (SCHEDULE_DEFAULTS) or null if not yet saved.
SCHEDULE_ACTUAL row for edited/actual assignments, or null if none.
Weekly shift wish row for the staff member, or null if none.
Lists active staff members for the tenant (display name sorted).
Gets one staff member by staffId, including labor details (laborLawCompliance, laborFlsaExempt, personal caps, laborConstraintMask). Soft-deleted rows return 404.
| 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/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/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/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). POST /v1/staff returns 409 STAFF_LIMIT when the roster would exceed the tenant cap. User (account) management, company profile, and audit logs are handled in the web app and are not part of the public API.
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.
Saves tenant schedule defaults (JSON body matches saveSchema in schedule-defaults-actions). Paid-tier fields follow your subscription.
Creates a staff member (displayName). Returns 201 with staff JSON.
Clones a completed source schedule with absences applied; returns 201 with new conditionId. Paid subscription required.
Validates and saves manually edited assignments (200 with { ok: true }).
Updates displayName and labor details (laborLawCompliance, laborFlsaExempt, personal caps, laborConstraintMask) for one staff member.
Soft-deletes a staff member (sets deletedAt).
Marks the condition as cancelled (publicApiCancelledAt); does not delete child rows.
Saves actual shift assignments JSON { "assignments": [ ... ] } (validated against the condition grid).
Replaces weekly wish for the staff member: full grid (1–7 days), wishByCellKey per cell (NONE|LOW|HIGH); granularity from tenant schedule defaults.
| Method | Path | Description |
|---|---|---|
| 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/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. |
When a schedule result reaches a terminal status, we POST a signed JSON notification to your HTTPS URL. The body does not include the full result; fetch details with GET /v1/schedule-results/{conditionId}.
Playground results are not delivered. If the webhook is disabled or no URL is saved, nothing is sent.
Event type and when it is sent.
schedule.result.terminal
Schedule result status becomes completed, no_solution, failed, error, or canceled
| type | When |
|---|---|
| schedule.result.terminal | Schedule result status becomes completed, no_solution, failed, error, or canceled |
status is terminal only. customerId is omitted from the body.
{
"type": "schedule.result.terminal",
"conditionId": "019ee481-e855-7000-abd8-977cbfd64ffc",
"status": "completed",
"occurredAt": "2026-07-16T13:00:54.694Z",
"links": {
"result": "{baseUrl}/v1/schedule-results/019ee481-e855-7000-abd8-977cbfd64ffc"
}
}Content-Type
application/json
User-Agent
AutoScheduler-Webhook/1.0
X-AutoScheduler-Timestamp
Unix seconds as a string
X-AutoScheduler-Signature
v1=<hex> (see verification below)
| Header | Description |
|---|---|
| Content-Type | application/json |
| User-Agent | AutoScheduler-Webhook/1.0 |
| X-AutoScheduler-Timestamp | Unix seconds as a string |
| X-AutoScheduler-Signature | v1=<hex> (see verification below) |
Compute a digest from the raw body (before JSON parse) and the Timestamp, then compare to Signature. Reject requests when |now − timestamp| exceeds 300 seconds (5 minutes) unless you intentionally allow wider clock skew.
HMAC-SHA256(signingSecret, `${timestamp}.${rawBody}`) → hex; header value is v1=<hex>On 5xx or timeout we retry automatically. Persistent failures go to a dead-letter queue. Browser push notifications use a separate path.
Register & enable: API management
x-api-key
API key tied to your paid subscription (identifies your tenant and plan limits).
Content-Type
application/json
| 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).
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
| 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.
timeZone
IANA time zone for interpreting dates and slots.
scheduleStartDate / scheduleEndDate
Inclusive calendar range; start ≤ end.
timeRangeStart / timeRangeEnd
Wall-clock range that defines the slot column; must yield at least one slot.
slotGranularityMinutes
Paid API: 60 (hourly) or 15 (15-minute slots). Must be in your allowedGranularities.
requirementsByCell
Keys = cellKey in the computed grid; values = integer 0–15.
staff
Non-empty; max size per limits; each item: staffId, displayName, optional availableCells.
| 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.
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.
| 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).