{"openapi":"3.1.0","info":{"title":"Schedultary","version":"1.0.0","description":"HTTP API for Schedultary. AI clients authenticate with an API key: create one in Settings > API Keys and send Authorization: Bearer sk_live_... (or x-api-key). Admin keys have the administrator's access; Read Only keys read what an employee can see and every change returns 403. A signed-in session access token also works. Own Actions: a staff member's Read Only key or connected app with Can File My Requests and Swaps turned on may also call POST, PATCH and DELETE /api/v1/me/requests, POST, PATCH and DELETE /api/v1/program/requests, POST /api/v1/me/swaps, POST /api/v1/me/swaps/{id}/answer and /cancel, POST and DELETE /api/v1/me/offers and POST and DELETE /api/v1/open-shifts/{id}/claim, for that person only. The same windows, deadlines, rules and approvals apply as in the app; nothing is approved by the call. Per person across all keys: 10 request changes and 5 swap, offer or pickup proposals per 24 hours, 3 open proposals at a time and 30 changes per 7 days by default (the workplace sets them), plus 20 changes per 10 minutes per key. A repeated pending item returns 409, a limit 429, anything else 403, each with a plain error. Every write is in Activity under the key's name, marked as an assistant. Access is read from the person's account on every request: employee or admin. Employee tokens see own schedule, requests, swaps, calendar, and profile. Admin tokens run the roster, rules, requests, swaps, people, settings, and the residency program (grid, call, generate, QGenda). Demo: GET /api/v1/key returns the org admin token. Mint a person token with POST /api/v1/tokens. Employees can exchange a calendar subscribe token at POST /api/v1/tokens/calendar. Every successful JSON GET carries an ETag; send it back as If-None-Match and an unchanged answer is a bodiless 304. The big lists (people, assignments, requests, issues) take fields, limit and cursor. Safe writes: every write that changes the workplace schedule takes ?dryRun=1 (or \"dryRun\": true in the body). A dry run runs the same checks, saves nothing and answers {dryRun, planId, expiresAt, baseRevision, changes (plain lines: who moves where, on which dates or blocks), issues {added, cleared, mustFix {before, after}, worthALook {before, after}}, result}. POST /api/v1/plans/{planId}/apply makes exactly that change once within 15 minutes, only while the workplace is still at baseRevision; otherwise 409 revision_conflict. Writes to records outside the schedule (timesheets, hour logs, onboarding, messages, devices) answer 422 dry_run_unsupported. Every write takes an Idempotency-Key header (or \"idempotencyKey\" in the body): the same key and request from the same caller within 24 hours returns the first answer with Idempotent-Replayed: true. The same key with a different request returns 422 idempotency_key_reused. POST /api/v1/batch saves up to 25 writes together or none; POST /api/v1/simulate reports their effect without saving. Errors: {error (plain sentence), code, hint?, field?} with codes validation_failed, unauthorized, forbidden, not_found, conflict, revision_conflict, publication_conflict, coverage_gap, hard_rule_blocked, plan_used, plan_expired, idempotency_key_reused, idempotency_in_progress, dry_run_unsupported, rate_limited, unavailable and internal_error."},"servers":[{"url":"https://schedultary.com"}],"tags":[{"name":"meta"},{"name":"tokens"},{"name":"me"},{"name":"admin"},{"name":"legacy"},{"name":"program"},{"name":"time-off"},{"name":"communications"},{"name":"reports"},{"name":"safe-writes"}],"paths":{"/api/v1":{"get":{"summary":"Catalog, scopes, and how to get a token","security":[],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["meta"]}},"/api/v1/duty-timeline":{"get":{"summary":"Review combined duty clocks and overlapping assignments","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"Duty timeline with source provenance and elapsed hours","content":{"application/json":{"schema":{"type":"object","required":["start","end","scenario","duties","issues","totals"],"properties":{"start":{"type":"string","format":"date"},"end":{"type":"string","format":"date"},"scenario":{"type":"string","enum":["published","all"]},"duties":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"sourceId":{"type":"string"},"personId":{"type":"string"},"label":{"type":"string"},"source":{"type":"string","enum":["assignment","rotation","call","signup","moonlight","backup","leave"]},"date":{"type":"string","format":"date"},"start":{"type":"number","description":"UTC epoch milliseconds"},"end":{"type":"number","description":"UTC epoch milliseconds"},"timeZone":{"type":"string"},"status":{"type":"string","enum":["draft","published"]},"countsTowardHours":{"type":"boolean"}}}},"issues":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string","enum":["overlap","missing-clock","missing-timezone"]},"personId":{"type":"string"},"message":{"type":"string"},"dutyIds":{"type":"array","items":{"type":"string"}}}}},"totals":{"type":"array","items":{"type":"object","properties":{"personId":{"type":"string"},"hours":{"type":"number"}}}}}}}}},"400":{"description":"Invalid dates, scenario or person"},"403":{"description":"Administrator access required"}},"description":"Administrator-only read scenario. Includes general assignments, residency rotations, call, sign-ups, moonlighting, backup and leave. Reports clock conflicts and incomplete clock/timezone settings; does not certify coverage, eligibility, fatigue rules or publication readiness. Hours are elapsed interval unions clipped to selected local dates; inactive backup and leave do not add duty hours. Missing or invalid duty clocks conservatively reserve the whole date and emit a diagnostic, but contribute zero assumed work hours.","parameters":[{"name":"start","in":"query","required":true,"schema":{"type":"string","format":"date"}},{"name":"end","in":"query","required":true,"description":"Inclusive, at most 32 days including start.","schema":{"type":"string","format":"date"}},{"name":"personId","in":"query","schema":{"type":"string"}},{"name":"scenario","in":"query","schema":{"type":"string","enum":["published","all"],"default":"published"},"description":"all also selects draft duties without changing publication state."}]}},"/api/v1/openapi":{"get":{"summary":"This OpenAPI document","security":[],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["meta"]}},"/api/v1/key":{"get":{"summary":"Demo org admin token","security":[],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["tokens"]},"post":{"summary":"Rotate the org admin token","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","tags":["tokens"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/tokens/calendar":{"post":{"summary":"Exchange a calendar subscribe token for an API token","security":[],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["calendarToken"],"properties":{"calendarToken":{"type":"string"}}}}}},"tags":["tokens"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/tokens":{"get":{"summary":"List tokens for this actor","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["tokens"]},"post":{"summary":"Mint a person token. Employees may only mint for themselves.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"personId":{"type":"string"},"label":{"type":"string"}}}}}},"tags":["tokens"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/tokens/{id}":{"delete":{"summary":"Revoke a token","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["tokens"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/me":{"get":{"summary":"Current actor, access, and scopes. person.avatarUrl is a signed profile picture link (one hour) or null.","operationId":"getMe","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["me"]}},"/api/v1/me/avatar":{"get":{"summary":"Own profile picture: {avatarUrl, path}. avatarUrl is a signed link that works for one hour, or null. Needs a signed-in session, not a workplace.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["me"]},"post":{"summary":"Start a picture upload. Crop to a square and downscale to 512x512 first. Returns {upload: {bucket, path, token, signedUrl}}; PUT the bytes to signedUrl (or Supabase uploadToSignedUrl), then POST /api/v1/me/avatar/commit.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["contentType","size"],"properties":{"contentType":{"type":"string","enum":["image/jpeg","image/png","image/webp","image/heic"]},"size":{"type":"integer","minimum":1,"maximum":5242880,"description":"Bytes. 5 MB at most."}}}}}},"tags":["me"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]},"delete":{"summary":"Remove own profile picture. Coworkers see initials again.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["me"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/me/avatar/commit":{"post":{"summary":"Finish an upload: the server reads the stored file back, checks its type and size, makes it the current picture and deletes the one it replaces. Returns {avatarUrl, path}.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["path"],"properties":{"path":{"type":"string","description":"upload.path from POST /api/v1/me/avatar"}}}}}},"tags":["me"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/avatars":{"get":{"summary":"Pictures in your selected workplace: {avatars: {personId: signed URL}, sources: {personId: account|roster}, rosterPhotos: {personId: signed URL}, expiresIn: 3600}. avatars follows the display rule: the person's own profile picture, else the roster photo an admin set, else nothing (show initials). Admins get every roster photo in rosterPhotos, including ones behind a profile picture.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["me"]}},"/api/v1/reports":{"post":{"summary":"Report someone's profile photo or name, or a note they wrote. Any member; not yourself. The workplace admins get an in-app notice and an email by their own choices, and Schedultary support is emailed. The reporter is never shown to the person reported. 201 {id, message}; 400 bad input; 429 too many reports.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["kind","personId","reason"],"properties":{"kind":{"type":"string","enum":["profile","note"]},"personId":{"type":"string"},"reason":{"type":"string","enum":["inappropriate","harassment","spam","other"]},"details":{"type":"string","maxLength":500},"note":{"type":"object","description":"Required when kind is note: the note as it was shown.","required":["type","id","text"],"properties":{"type":{"type":"string","enum":["request","swap","release","offer","announcement","other"]},"id":{"type":"string"},"text":{"type":"string","maxLength":2000}}}}}}}},"tags":["reports"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]},"get":{"summary":"Reports in this workplace, newest first: {reports:[{id, kind, personId, personName, reporterName, reason, details, note, status: open|dismissed|photo_removed|note_removed, createdAt, resolvedAt, resolvedByName}]}. A report about the admin asking is never listed.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["reports"]}},"/api/v1/reports/{id}/resolve":{"post":{"summary":"Resolve a report. dismiss keeps everything; remove-photo (profile reports) clears the person's roster photo and account picture; remove-note (note reports) blanks the note and sets noteRemovedAt on the request, swap or offered shift. Recorded in Activity. 200 {report}.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["dismiss","remove-photo","remove-note"]}}}}}},"tags":["reports"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/blocks":{"get":{"summary":"People you blocked in this workplace: {blocked:[{personId, name, blockedAt}]}. Blocking hides their photo and notes for you only; it never changes the schedule.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["reports"]},"post":{"summary":"Block someone in this workplace (not yourself). Repeating it changes nothing. 201 {blocked}.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["personId"],"properties":{"personId":{"type":"string"}}}}}},"tags":["reports"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/blocks/{personId}":{"delete":{"summary":"Unblock someone. 200 {blocked}.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["reports"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/roster":{"get":{"summary":"Photo Roster: everyone active in your workplace, grouped by class or role, with name, title and a signed picture link (one hour) or null: {workplace, subtitle, date, count, groups:[{id, label, people:[{name, title, badge?, avatarUrl}]}]}.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["me"]}},"/api/v1/me/days":{"get":{"summary":"Your own days off over a range, for shading your calendar: {from, to, timeZone, days: [{date, off, holiday}]}. off is null or {kind, label}: kind vacation (approved vacation), off (other approved leave, an admin day or outside your active dates), leave-rotation (a leave rotation in a published year) or holiday (a holiday you have off with nothing else on); label is your own wording. holiday names a program holiday or the workplace's closed day. Pending requests never show.","operationId":"getMyDays","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"parameters":[{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date","description":"At most 119 days after from."}}],"tags":["me"]}},"/api/v1/team/off":{"get":{"summary":"Who is off: {from, to, off: [{personId, name, start, end, label}]} where label is Vacation or Off. Approved time off, profile leave and recorded days off overlapping the range; pending or declined requests, reasons and notes never show.","operationId":"getTeamOff","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"parameters":[{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date","description":"At most 400 days after from."}}],"tags":["me"]}},"/api/v1/program/signups/mine":{"get":{"summary":"Your Call Sign-Up in one read: {today, timeZone, admin, manageUrl, sheets, swaps}. Each sheet has its rule (name, code, time, rotations), block dates, opensOn, dueOn, state (upcoming, open, closed), seatsPerDay, required, picked, days [{date, open, mine, locked, eligible, past, people [{id, name, you}]}] and swapTargets {date: [{id, name, days}]} for your picked days. swaps are your sign-up swaps [{id, direction incoming|outgoing, status open|approved|denied, accepted, year, ruleId, name, date, withDate, otherId, otherName, reason, createdAt, canAnswer, canCancel}]. Pick or drop with POST /api/v1/program/signups/mine, ask with POST /api/v1/program/signups/swaps, answer with POST /api/v1/me/swaps/{id}/answer, cancel with POST /api/v1/me/swaps/{id}/cancel.","operationId":"getMyCallSignup","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"parameters":[{"name":"year","in":"query","schema":{"type":"integer","description":"Defaults to the year holding today and the next one."}}],"tags":["program"]},"post":{"summary":"Pick, unpick or move your own sign-up day {ruleId, add? (YYYY-MM-DD), remove?, year?}. Only while the block's sign-up is open, on days you are on the rotation, first come per seat, up to your required days; a day an admin locked stays","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"own","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/me/day":{"get":{"summary":"The phone's Today in one read: me (what you are on that day, with a timeline of rotation hours, call, Call Sign-Up and moonlighting, who works with you and the next day on file), upcoming (tomorrow, next call, sign-up day, moonlighting, rotation change or shift), needs (today only: hours to confirm, released moonlighting offered to you, open sign-ups, swaps waiting for you) and whosOn {published, holiday, onCall, groups, off, count}. whosOn.off lists who is off with detail Vacation, Off or No Rotation and off {label, start, end} for time off: approved time off, leave and a program holiday only, one neutral word, never a reason. Published schedules only; residents see the team Team already shows them.","operationId":"getMyDay","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"parameters":[{"name":"date","in":"query","schema":{"type":"string","format":"date","description":"Defaults to today at the workplace."}}],"tags":["me"]}},"/api/v1/me/schedule":{"get":{"summary":"Own published shifts for a date range (defaults to the current month)","operationId":"getMySchedule","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"parameters":[{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}}],"tags":["me"]}},"/api/v1/me/calendar":{"get":{"summary":"Own ICS subscribe URL, plus team: the calendar for everyone (url, subscribe, name, title, text, empty, wholeTeamCall) with the web's wording","operationId":"getMyCalendar","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["me"]}},"/api/v1/me/profile":{"get":{"summary":"Own profile","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["me"]},"patch":{"summary":"Update own name, birthday, or preferences","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"birthday":{"type":"string"},"notes":{"type":"string"},"preferences":{"type":"object"}}}}}},"tags":["me"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/me/requests":{"get":{"summary":"Own requests","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["me"]},"post":{"summary":"Create a request for yourself","operationId":"createMyRequest","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["startDate","endDate"],"properties":{"startDate":{"type":"string","format":"date"},"endDate":{"type":"string","format":"date"},"kind":{"type":"string","enum":["time-off","prefer-off","prefer-on","heads-up","half-day","leave-early","arrive-late"]},"reason":{"type":"string"},"standing":{"type":"boolean"},"recurrence":{"type":"object","description":"once, weekly (intervalWeeks, anchorDate), yearly, cycle (anchorDate, lengthDays, days, preset), monthly (ordinals, weekdays) or dates (dates). Any kind may carry from and until."},"window":{"type":"object","description":"{start, end, endNextDay?}: HH:MM or sunset-relative edges such as sunset-18"},"overrides":{"type":"array","items":{"type":"object"},"description":"[{date, available}] dated exceptions"},"periodId":{"type":"string"}}}}}},"tags":["me"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/me/requests/{id}":{"patch":{"summary":"Edit your pending or standing request","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"startDate":{"type":"string","format":"date"},"endDate":{"type":"string","format":"date"},"kind":{"type":"string"},"reason":{"type":"string"}}}}}},"tags":["me"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]},"delete":{"summary":"Withdraw your own pending request before its due date. 409 when it was already decided or is locked","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["me"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/me/swaps":{"get":{"summary":"Own swap asks","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["me"]},"post":{"summary":"Propose a swap of one of your shifts","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["assignmentId","targetPersonId"],"properties":{"assignmentId":{"type":"string"},"targetPersonId":{"type":"string"},"offeredAssignmentId":{"type":"string"},"reason":{"type":"string"}}}}}},"tags":["me"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/me/swaps/{id}/decide":{"post":{"summary":"Accept or decline an incoming swap","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["approved","denied"]}}}}}},"tags":["me"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/me/time-off":{"get":{"summary":"Paid time off balances per leave type (feature must be on)","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"parameters":[{"name":"date","in":"query","schema":{"type":"string","format":"date"}},{"name":"ledger","in":"query","schema":{"type":"string","enum":["1"]}}],"tags":["time-off"]}},"/api/v1/me/time-off/check":{"get":{"summary":"Balance before and after a request, counting other waiting requests","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"parameters":[{"name":"bankId","in":"query","schema":{"type":"string"}},{"name":"startDate","in":"query","schema":{"type":"string","format":"date"}},{"name":"endDate","in":"query","schema":{"type":"string","format":"date"}},{"name":"kind","in":"query","schema":{"type":"string"}}],"tags":["time-off"]}},"/api/v1/time-off/balances":{"get":{"summary":"Every active person's balances","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","parameters":[{"name":"date","in":"query","schema":{"type":"string","format":"date"}}],"tags":["time-off"]}},"/api/v1/time-off/people/{id}":{"get":{"summary":"One person's ledger per leave type","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","parameters":[{"name":"bankId","in":"query","schema":{"type":"string"}},{"name":"date","in":"query","schema":{"type":"string","format":"date"}}],"tags":["time-off"]}},"/api/v1/time-off/adjustments":{"get":{"summary":"Manual adjustments","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","parameters":[{"name":"personId","in":"query","schema":{"type":"string"}}],"tags":["time-off"]},"post":{"summary":"Add a dated adjustment with a reason, or reverse one","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["personId","bankId","date","reason"],"properties":{"personId":{"type":"string"},"bankId":{"type":"string"},"date":{"type":"string","format":"date"},"amount":{"type":"number"},"reason":{"type":"string","maxLength":500},"reverses":{"type":"string","format":"uuid"}}}}}},"tags":["time-off"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/staffing/intervals":{"get":{"summary":"Staffing by time of day; 404 while the feature is off. Returns slotMinutes, maxBreakShare and days [{date, underSlots, overSlots, shortPersonMinutes, lines [{group {key,label,roles,skill}, required[], scheduled[]}]}] with one array entry per interval from midnight","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","parameters":[{"name":"start","in":"query","schema":{"type":"string","format":"date"}},{"name":"end","in":"query","schema":{"type":"string","format":"date"}}],"tags":["admin"]}},"/api/v1/credentials":{"get":{"summary":"Credentials needing action (expired, not yet valid or expiring within 30 days), or every credential with ?all=1; 404 while the Credentials feature is off","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","parameters":[{"name":"all","in":"query","schema":{"type":"string","enum":["1"]}}],"tags":["admin"]}},"/api/v1/me/credentials":{"get":{"summary":"Your own credentials {id, name, number, issued, expires, status valid|expiring|expired|not-yet}; 404 while the feature is off","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["me"]}},"/api/v1/locations":{"get":{"summary":"Locations for the Multiple locations feature; 404 while the feature is off. Returns locations (id, name, address, timezone, hours, holidays, coordinates {lat,lng,radiusMeters}), travelMinutes, shiftLocations and me {homeLocationId, eligibleLocationIds}","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["me"]}},"/api/v1/me/reminders":{"get":{"summary":"Your reminder choices with defaults filled in, your daily schedule message and the workplace settings. workplace.texts: texts can go out now (Allow Text Messages plus the workplace's texts); workplace.textsAllowed: the admin switch alone","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["me"]},"patch":{"summary":"Change reminder switches, lead time or the daily schedule message","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"updates":{"type":"boolean"},"deadlines":{"type":"boolean"},"shifts":{"type":"boolean"},"email":{"type":"boolean"},"sms":{"type":"boolean"},"coworkers":{"type":"boolean"},"published":{"type":"boolean"},"leadMinutes":{"type":"integer","enum":[60,120,240,720,1440,2880]},"daily":{"type":"object","properties":{"sms":{"type":"boolean"},"email":{"type":"boolean"},"push":{"type":"boolean"},"time":{"type":"string"},"weekdaysOnly":{"type":"boolean"}}}}}}}},"tags":["me"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/me/swaps/{id}/answer":{"post":{"summary":"The asked coworker accepts or declines a swap","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["answer"],"properties":{"answer":{"type":"string","enum":["accept","decline"]}}}}}},"tags":["me"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/me/unavailable":{"get":{"summary":"Coworkers who cannot take one of your shifts because they asked for that day off or are on leave. Returns assignmentId, date and personIds; reasons stay private","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"parameters":[{"name":"assignmentId","in":"query","schema":{"type":"string"}}],"tags":["me"]}},"/api/v1/crews":{"get":{"summary":"Crews and rotations (404 while the feature is off). Admins: crews with members and weeklyHours, crewsPerDuty, coverage {checkedDays, complete, messages}, reliefPool person IDs and openSeats for the next eight weeks. Members: crew names and me {crewId, days[{date, shiftTypeIds, kelly, optional?}], layer?: {crewId, days}} (layer only for on-call layer members)","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["me"]},"put":{"summary":"Replace crews; members without a personal work pattern follow their crew cycle","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["crews"],"properties":{"crews":{"type":"array","maxItems":50,"items":{"type":"object","required":["id","label","cycle","anchorDate"],"properties":{"id":{"type":"string"},"label":{"type":"string","maxLength":100},"cycle":{"type":"array","minItems":1,"maxItems":366,"items":{"type":"object","required":["shiftTypeIds"],"properties":{"shiftTypeIds":{"type":"array","items":{"type":"string"},"description":"Empty is a crew day off; otherwise one of these duties is required"},"optional":{"type":"boolean","enum":[true],"description":"The duties are allowed but not required; the build decides"}}}},"anchorDate":{"type":"string","format":"date","description":"Cycle day 1"},"offsetDays":{"type":"integer"},"memberIds":{"type":"array","items":{"type":"string"}},"kelly":{"type":"object","required":["everyTours","staggerTours"],"properties":{"everyTours":{"type":"integer","minimum":2,"maximum":366},"staggerTours":{"type":"integer","minimum":0,"maximum":366}}},"overlay":{"type":"boolean","enum":[true],"description":"On-call layer: duty days are required but other work stays open; a person may be in one crew and one layer"}}}},"crewsPerDuty":{"type":"object","additionalProperties":{"type":"integer","minimum":0,"maximum":50},"description":"Intended crews per duty per day; missing means one"}}}}}},"tags":["admin"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/me/clock":{"get":{"summary":"Time clock status, today's and this week's hours, own punches, approvals and correction requests for a pay period","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"parameters":[{"name":"start","in":"query","schema":{"type":"string","format":"date"}},{"name":"end","in":"query","schema":{"type":"string","format":"date"}}],"tags":["time-clock"]},"post":{"summary":"Punch at server time. Location is optional and only used when the workplace turns the check on","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["kind"],"properties":{"kind":{"type":"string","enum":["in","out","break_start","break_end"]},"note":{"type":"string","maxLength":500},"lat":{"type":"number"},"lng":{"type":"number"}}}}}},"tags":["time-clock"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/me/timesheet/corrections":{"post":{"summary":"Ask for a timesheet correction","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["date","message"],"properties":{"date":{"type":"string","format":"date"},"message":{"type":"string","maxLength":500},"punchId":{"type":"string"}}}}}},"tags":["time-clock"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/compliance/fair-workweek":{"get":{"summary":"Fair Workweek report (Beta): posting notice, rest between shifts, published-shift changes with premiums owed and access to hours","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","parameters":[{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}}],"tags":["admin"]}},"/api/v1/timesheets":{"get":{"summary":"Timesheets for a pay period with punches, approvals and correction requests","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","parameters":[{"name":"start","in":"query","schema":{"type":"string","format":"date"}},{"name":"end","in":"query","schema":{"type":"string","format":"date"}}],"tags":["time-clock"]}},"/api/v1/timesheets/punches":{"post":{"summary":"Add a missed punch","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["personId","kind","at","reason"],"properties":{"personId":{"type":"string"},"kind":{"type":"string","enum":["in","out","break_start","break_end"]},"at":{"type":"string","format":"date-time"},"reason":{"type":"string","maxLength":500},"assignmentId":{"type":"string"}}}}}},"tags":["time-clock"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/timesheets/punches/{id}":{"patch":{"summary":"Edit or remove a punch; the original is kept","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string","maxLength":500},"at":{"type":"string","format":"date-time"},"kind":{"type":"string","enum":["in","out","break_start","break_end"]},"void":{"type":"boolean"}}}}}},"tags":["time-clock"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/timesheets/approvals":{"post":{"summary":"Approve a person's pay period","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["personId","start","end"],"properties":{"personId":{"type":"string"},"start":{"type":"string","format":"date"},"end":{"type":"string","format":"date"}}}}}},"tags":["time-clock"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/timesheets/approvals/reopen":{"post":{"summary":"Reopen an approved pay period","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["personId","start","reason"],"properties":{"personId":{"type":"string"},"start":{"type":"string","format":"date"},"reason":{"type":"string"}}}}}},"tags":["time-clock"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/timesheets/corrections/{id}":{"post":{"summary":"Answer a correction request","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["resolved","declined"]},"response":{"type":"string","maxLength":500}}}}}},"tags":["time-clock"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/timesheets/approved-hours":{"get":{"summary":"Approved timesheet hours per person per date, for paid time off earned per hour worked","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin only, time clock on. Up to 400 days. Returns {range,periods:[{personId,start,end}],hours:{personId:{date:hours}}}: paid worked hours inside approved pay periods, the same hours Timesheets shows.","parameters":[{"name":"start","in":"query","schema":{"type":"string","format":"date"}},{"name":"end","in":"query","schema":{"type":"string","format":"date"}}],"tags":["time-clock"]}},"/api/v1/payroll":{"get":{"summary":"Payroll export for approved people in a pay period","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","parameters":[{"name":"start","in":"query","schema":{"type":"string","format":"date"}},{"name":"end","in":"query","schema":{"type":"string","format":"date"}},{"name":"format","in":"query","schema":{"type":"string","enum":["json","csv","days-csv"]}}],"tags":["time-clock"]}},"/api/v1/open-shifts":{"get":{"summary":"Open shifts. Employees see shifts they qualify for, their claims and offers; admins see every post with claims. ?view=mine returns the employee view","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"parameters":[{"name":"view","in":"query","schema":{"type":"string","enum":["mine"]}}],"tags":["open-shifts"]},"post":{"summary":"Post an open shift","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["shiftTypeId","date"],"properties":{"shiftTypeId":{"type":"string"},"date":{"type":"string","format":"date"},"seats":{"type":"integer","minimum":1,"maximum":50},"note":{"type":"string","maxLength":500},"requiredSkill":{"type":"string"},"requiredRole":{"type":"string"}}}}}},"tags":["open-shifts"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/open-shifts/gaps":{"post":{"summary":"Post every unfilled seat from today on in a published period","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["periodId"],"properties":{"periodId":{"type":"string"}}}}}},"tags":["open-shifts"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/open-shifts/{id}":{"delete":{"summary":"Cancel a post (admin) or withdraw your own offer","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["open-shifts"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/open-shifts/{id}/claim":{"post":{"summary":"Ask to take an open or offered shift","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["open-shifts"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]},"delete":{"summary":"Withdraw your pending claim","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["open-shifts"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/open-shifts/{id}/claims/{claimId}/decide":{"post":{"summary":"Approve or decline a claim; approval re-checks every rule","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["approved","declined"]}}}}}},"tags":["open-shifts"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/comms/preferences":{"get":{"summary":"Your push choices","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["communications"]},"put":{"summary":"Save push choices","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pushReminders":{"type":"boolean"},"pushUpdates":{"type":"boolean"}}}}}},"tags":["communications"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/comms/push/native":{"get":{"summary":"Your iPhones registered for notifications in this workplace, without tokens","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Returns {configured, devices:[{id,platform,provider,environment,device,createdAt,lastSuccessAt}]}. configured is false until the server has its APNs key.","tags":["communications"]},"post":{"summary":"Register this iPhone for notifications","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"provider":{"type":"string","enum":["apns"]},"token":{"type":"string","pattern":"^[0-9a-fA-F]{32,200}$","description":"APNs device token as hex"},"environment":{"type":"string","enum":["production","sandbox"],"description":"sandbox for development builds, production for TestFlight and the App Store"},"device":{"type":"string","maxLength":120}}}}}},"description":"The token is registered for the selected workplace (x-schedultary-workplace header); register once per workplace to get notifications from each. It is removed from any other account. Returns {devices}.","tags":["communications"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]},"delete":{"summary":"Remove an iPhone from this workplace","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"Omit to remove every iPhone of yours in this workplace"}}}}}},"description":"Returns {removed}.","tags":["communications"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/comms/push/native/test":{"post":{"summary":"Send a test notification to your registered iPhones","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"503 until the server has its APNs key. Returns {sent}.","tags":["communications"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/me/offers":{"post":{"summary":"Offer one of your future published shifts to anyone qualified or to named coworkers","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["assignmentId"],"properties":{"assignmentId":{"type":"string"},"offeredTo":{"type":"array","items":{"type":"string"},"maxItems":50},"note":{"type":"string","maxLength":500}}}}}},"tags":["me"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/me/offers/{id}":{"delete":{"summary":"Withdraw your shift offer","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["me"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/me/fairness":{"get":{"summary":"Own fairness row, fair-share score and team range","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["me"]}},"/api/v1/shift-tasks":{"get":{"summary":"Shift checklists and notes with completion, overdue items and handoff notes","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"parameters":[{"name":"date","in":"query","schema":{"type":"string","format":"date"}},{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}}],"tags":["shift-tasks"]}},"/api/v1/shift-tasks/complete":{"post":{"summary":"Check off or clear a checklist item; only people on the shift or admins","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["date","shiftTypeId","checklistId","itemId"],"properties":{"date":{"type":"string","format":"date"},"shiftTypeId":{"type":"string"},"checklistId":{"type":"string"},"itemId":{"type":"string"},"done":{"type":"boolean"}}}}}},"tags":["shift-tasks"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/shift-tasks/verify":{"post":{"summary":"Verify a completed item that needs a manager","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["date","shiftTypeId","checklistId","itemId"],"properties":{"date":{"type":"string","format":"date"},"shiftTypeId":{"type":"string"},"checklistId":{"type":"string"},"itemId":{"type":"string"},"verified":{"type":"boolean"}}}}}},"tags":["shift-tasks"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/shift-notes":{"post":{"summary":"Add a shift note, optionally as a handoff to later shifts","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["date","shiftTypeId","body"],"properties":{"date":{"type":"string","format":"date"},"shiftTypeId":{"type":"string"},"body":{"type":"string","maxLength":2000},"handoff":{"type":"boolean"}}}}}},"tags":["shift-tasks"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/shift-notes/{id}":{"patch":{"summary":"Edit your own note within 15 minutes","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["body"],"properties":{"body":{"type":"string","maxLength":2000},"handoff":{"type":"boolean"}}}}}},"tags":["shift-tasks"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]},"delete":{"summary":"Remove your own note within 15 minutes, or any note as an admin","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["shift-tasks"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/me/onboarding":{"get":{"summary":"Your onboarding documents, due dates and acknowledgements","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["onboarding"]}},"/api/v1/me/onboarding/{documentId}/acknowledge":{"post":{"summary":"Acknowledge the current version of an assigned document","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["version"],"properties":{"version":{"type":"integer","minimum":1},"typedName":{"type":"string","minLength":2,"maxLength":200}}}}}},"tags":["onboarding"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/onboarding":{"get":{"summary":"Admins: documents, packets, assignments and acknowledgements. Employees or ?view=mine: own list","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"parameters":[{"name":"view","in":"query","schema":{"type":"string","enum":["mine"]}}],"tags":["onboarding"]}},"/api/v1/onboarding/documents":{"post":{"summary":"Add a text document, or start a file upload and receive a one-time upload link","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string","maxLength":200},"description":{"type":"string","maxLength":2000},"mode":{"type":"string","enum":["acknowledge","sign"]},"text":{"type":"string","maxLength":100000},"contentType":{"type":"string","enum":["application/pdf","image/png","image/jpeg","image/webp"]},"size":{"type":"integer","minimum":1,"maximum":10485760}}}}}},"tags":["onboarding"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/onboarding/documents/{id}":{"patch":{"summary":"Change a document's title, description, mode or archived flag","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":"string"},"mode":{"type":"string","enum":["acknowledge","sign"]},"archived":{"type":"boolean"}}}}}},"tags":["onboarding"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/onboarding/documents/{id}/versions":{"post":{"summary":"Add a new version; everyone assigned must acknowledge again","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string","maxLength":100000},"contentType":{"type":"string"},"size":{"type":"integer","maximum":10485760}}}}}},"tags":["onboarding"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/onboarding/documents/{id}/versions/{version}/publish":{"post":{"summary":"Check the uploaded file on the server and make it current","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","tags":["onboarding"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/onboarding/documents/{id}/file":{"get":{"summary":"Signed link to the file, valid for five minutes","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"parameters":[{"name":"version","in":"query","schema":{"type":"integer"}}],"tags":["onboarding"]}},"/api/v1/onboarding/assignments":{"post":{"summary":"Assign documents or a packet to people","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["personIds"],"properties":{"personIds":{"type":"array","items":{"type":"string"},"maxItems":500},"documentIds":{"type":"array","items":{"type":"string"},"maxItems":100},"packetId":{"type":"string"},"dueDate":{"type":"string","format":"date"}}}}}},"tags":["onboarding"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/onboarding/assignments/{id}":{"delete":{"summary":"Remove a document from someone's list","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","tags":["onboarding"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/onboarding/packets":{"post":{"summary":"Create a packet with an optional assignment rule","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"documentIds":{"type":"array","items":{"type":"string"}},"dueDays":{"type":"integer","minimum":0,"maximum":365},"auto":{"type":"object","properties":{"enabled":{"type":"boolean"},"onlyNewPeople":{"type":"boolean"},"roles":{"type":"array","items":{"type":"string"}},"locations":{"type":"array","items":{"type":"string"}}}}}}}}},"tags":["onboarding"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/onboarding/packets/{id}":{"patch":{"summary":"Change a packet","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","tags":["onboarding"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]},"delete":{"summary":"Delete a packet; assignments stay","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","tags":["onboarding"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/schedule-templates":{"get":{"summary":"List private workplace templates and starters","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["admin"]},"post":{"summary":"Save, preview, or explicitly apply a workplace template","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["save","preview","apply"]},"name":{"type":"string","maxLength":100},"id":{"type":"string"},"preview":{"type":"object","description":"Exact reviewed template; required for apply"},"expectedConfiguration":{"type":"object","description":"Configuration reviewed before replacement; required for apply"}}}}}},"tags":["admin"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/periods":{"get":{"summary":"List scheduling periods and request windows","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["me"]},"post":{"summary":"Create a scheduling period","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["cadence","start"],"properties":{"cadence":{"type":"string","enum":["weekly","fortnightly","monthly","quarterly","annually","custom"]},"start":{"type":"string","format":"date"},"end":{"type":"string","format":"date","description":"Required for custom cadence"},"requestOpen":{"type":"string","format":"date"},"requestClose":{"type":"string","format":"date"},"name":{"type":"string"}}}}}},"tags":["admin"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/periods/{id}":{"patch":{"summary":"Change an unpublished period request deadline","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["requestClose"],"properties":{"requestClose":{"type":"string","format":"date"}}}}}},"tags":["admin"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/shift-types":{"get":{"summary":"Shift types for this org","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["me"]},"post":{"summary":"Create a shift type","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code","label"],"properties":{"code":{"type":"string"},"label":{"type":"string"},"start":{"type":"string","description":"Local HH:MM"},"end":{"type":"string","description":"Local HH:MM; at or before start ends the next day"},"location":{"type":"string"},"minStaff":{"type":"integer","minimum":0},"maxStaff":{"type":"integer","minimum":0},"weekdays":{"type":"array","items":{"type":"integer","minimum":0,"maximum":6}},"requiredSkills":{"type":"array","items":{"type":"string"}},"eligibleRoles":{"type":"array","items":{"type":"string"}},"workMode":{"type":"string","enum":["remote","in-person","hybrid"]},"dutyKind":{"type":"string","enum":["work","call","standby"]},"allowConcurrent":{"type":"boolean"},"supervisorRequired":{"type":"boolean"},"supervisorRoles":{"type":"array","items":{"type":"string"}},"burdenWeight":{"type":"number","minimum":0,"maximum":100},"minimumAge":{"type":["integer","null"],"minimum":1,"maximum":99,"description":"Minor labor rules (Beta): youngest age allowed on this duty"},"breaks":{"type":"array","description":"Explicit off-floor intervals. All breaks remove active coverage; unpaid breaks also reduce recorded hours. Intervals must not overlap or exceed the duty.","items":{"type":"object","required":["startMinute","durationMinutes","paid"],"properties":{"startMinute":{"type":"integer","minimum":0,"description":"Civil minutes after duty start, including overnight"},"durationMinutes":{"type":"integer","minimum":1},"paid":{"type":"boolean"}}}},"reliefForShiftTypeIds":{"type":"array","maxItems":1,"items":{"type":"string"},"description":"One existing other duty this named custom shift may cover. Relief must have zero own minimum staffing, including coverage overrides, and requires the target location, role, skills, and supervisor/lead qualifications."},"splitGroup":{"type":["string","null"],"maxLength":60,"description":"Hours rules feature: segments with the same group on one date are one workday (split shift); rest is not required between them."},"contiguousTour":{"type":"boolean","description":"Hours rules feature: back-to-back duties of this kind join into one tour; rest applies after the tour."},"weekendDays":{"type":["array","null"],"items":{"type":"integer","minimum":0,"maximum":6},"description":"Hours rules feature: start weekdays that count as a weekend for this duty. Default Saturday and Sunday."}},"additionalProperties":true}}}},"tags":["admin"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/shift-types/{id}":{"patch":{"summary":"Update a shift type","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string"},"label":{"type":"string"},"start":{"type":"string","description":"Local HH:MM"},"end":{"type":"string","description":"Local HH:MM; at or before start ends the next day"},"location":{"type":"string"},"minStaff":{"type":"integer","minimum":0},"maxStaff":{"type":"integer","minimum":0},"weekdays":{"type":"array","items":{"type":"integer","minimum":0,"maximum":6}},"requiredSkills":{"type":"array","items":{"type":"string"}},"eligibleRoles":{"type":"array","items":{"type":"string"}},"workMode":{"type":"string","enum":["remote","in-person","hybrid"]},"dutyKind":{"type":"string","enum":["work","call","standby"]},"allowConcurrent":{"type":"boolean"},"supervisorRequired":{"type":"boolean"},"supervisorRoles":{"type":"array","items":{"type":"string"}},"burdenWeight":{"type":"number","minimum":0,"maximum":100},"minimumAge":{"type":["integer","null"],"minimum":1,"maximum":99,"description":"Minor labor rules (Beta): youngest age allowed on this duty"},"breaks":{"type":"array","description":"Explicit off-floor intervals. All breaks remove active coverage; unpaid breaks also reduce recorded hours. Intervals must not overlap or exceed the duty.","items":{"type":"object","required":["startMinute","durationMinutes","paid"],"properties":{"startMinute":{"type":"integer","minimum":0,"description":"Civil minutes after duty start, including overnight"},"durationMinutes":{"type":"integer","minimum":1},"paid":{"type":"boolean"}}}},"reliefForShiftTypeIds":{"type":"array","maxItems":1,"items":{"type":"string"},"description":"One existing other duty this named custom shift may cover. Relief must have zero own minimum staffing, including coverage overrides, and requires the target location, role, skills, and supervisor/lead qualifications."},"splitGroup":{"type":["string","null"],"maxLength":60,"description":"Hours rules feature: segments with the same group on one date are one workday (split shift); rest is not required between them."},"contiguousTour":{"type":"boolean","description":"Hours rules feature: back-to-back duties of this kind join into one tour; rest applies after the tour."},"weekendDays":{"type":["array","null"],"items":{"type":"integer","minimum":0,"maximum":6},"description":"Hours rules feature: start weekdays that count as a weekend for this duty. Default Saturday and Sunday."}}}}}},"tags":["admin"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/schedule-builder":{"get":{"summary":"Schedule readiness, conflicts, and the request window for one period","operationId":"getScheduleBuilder","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","parameters":[{"name":"periodId","in":"query","schema":{"type":"string"}},{"name":"month","in":"query","schema":{"type":"string","description":"YYYY-MM. periodId wins when both are sent."}}],"tags":["admin"]},"post":{"summary":"Generate, publish, or change the request window for one period","operationId":"generateSchedule","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin only. generate fills a draft and keeps requests open until the deadline. publish is refused while a coverage or mandatory-rule conflict remains. close-requests stops intake early. reopen-requests works only before the deadline.","parameters":[{"name":"periodId","in":"query","schema":{"type":"string"}},{"name":"month","in":"query","schema":{"type":"string","description":"YYYY-MM. periodId wins when both are sent."}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["generate","publish","close-requests","reopen-requests","exception"]},"replace":{"type":"boolean","description":"Required true when generate would replace an existing draft."},"key":{"type":"string","description":"Exception key when action is exception."},"approved":{"type":"boolean","description":"Approve or reject the exception when action is exception."}}}}}},"tags":["admin"]}},"/api/v1/org":{"get":{"summary":"Org settings. Employees see public fields only.","operationId":"getWorkplace","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Employees get features: the optional feature ids that are on. Admins get the whole org, where features is the saved map (it can hold retired flags), plus enabledFeatures: the ids the server serves.","tags":["me"]},"patch":{"summary":"Patch org settings","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"schedulePolicy":{"type":"object","properties":{"cadence":{"type":"string","enum":["weekly","fortnightly","monthly","quarterly","annually","custom"]},"requestLeadDays":{"type":"integer","minimum":1,"maximum":366}}},"workweekStart":{"type":["object","null"],"required":["weekday","time"],"properties":{"weekday":{"type":"integer","minimum":0,"maximum":6},"time":{"type":"string","description":"Local HH:MM"}},"description":"Hours rules feature: start of the fixed 168-hour workweek for weekly limits and overtime. Null restores Monday 00:00."}}}}}},"tags":["admin"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/settings":{"get":{"summary":"Alias of /org","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["admin"]},"patch":{"summary":"Alias of PATCH /org","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"schedulePolicy":{"type":"object","properties":{"cadence":{"type":"string","enum":["weekly","fortnightly","monthly","quarterly","annually","custom"]},"requestLeadDays":{"type":"integer","minimum":1,"maximum":366}}}}}}}},"tags":["admin"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/people":{"get":{"summary":"Full roster. Each person has avatarUrl: a signed link (one hour) to their own profile picture, else their roster photo, else null.","operationId":"listPeople","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["admin"],"parameters":[{"name":"fields","in":"query","schema":{"type":"string"},"description":"Comma-separated top-level fields to keep, such as id,name. Ids are always kept."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":500},"description":"Page size. A paged answer is {items, total, nextCursor} ({year, hits, total, nextCursor} for program issues)."},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"nextCursor from the previous page."}]},"post":{"summary":"Create a person. Role is an org-defined string.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","email"],"properties":{"name":{"type":"string"},"email":{"type":"string"},"role":{"type":"string"},"access":{"type":"string","enum":["admin","employee"]},"fte":{"type":"number"},"sharePhoneWithTeam":{"type":"boolean","description":"Mobile number sharing with the team (Team, Call and Text buttons and the team contact card). On unless false. The person can also change it in Profile; the last change wins."},"preferences":{"type":"object","description":"Usual days and how firm they are. Notes stay the person's own.","properties":{"workWeekdays":{"type":"array","items":{"type":"integer","minimum":0,"maximum":6},"description":"Usual working days, 0 Sunday to 6 Saturday. An empty list means any day."},"workWeekdaysFirmness":{"type":["string","null"],"enum":["always","usually",null],"description":"How firm workWeekdays are. always: never works other days (the generator never places them there, swaps and covers never offer them, a shift there is Must Fix). usually: prefers these days (other days are avoided, allowed in swaps with a Not a Usual Day label, and a shift there is Worth a Look). Unset or null behaves as always."}}},"clinicalFte":{"type":["number","null"],"minimum":0,"description":"Clinical effort, no greater than employment FTE. Null clears the override."},"maxWeeklyHours":{"type":["number","null"],"minimum":0,"maximum":168,"description":"Monday-to-Sunday full duty-span limit, including paid and unpaid breaks. Overnight spans are split across weeks. Null clears the limit."},"workPattern":{"type":["object","null"],"required":["anchorDate","days"],"properties":{"anchorDate":{"type":"string","format":"date"},"offsetDays":{"type":"integer"},"days":{"type":"array","minItems":1,"maxItems":366,"items":{"type":"object","required":["shiftTypeIds"],"properties":{"shiftTypeIds":{"type":"array","items":{"type":"string"}},"required":{"type":"boolean","description":"Requires one permitted duty unless blocking leave excuses all alternatives"}}}}}},"protectedTime":{"type":["array","null"],"items":{"type":"object","required":["id","label","weekdays","start","end"],"properties":{"id":{"type":"string"},"label":{"type":"string"},"weekdays":{"type":"array","minItems":1,"items":{"type":"integer","minimum":0,"maximum":6}},"start":{"type":"string"},"end":{"type":"string"}}}},"weekendFrequency":{"type":["object","null"],"required":["maxWorked","window"],"properties":{"maxWorked":{"type":"integer","minimum":0},"window":{"type":"integer","minimum":1,"maximum":52}},"description":"Hours rules feature: this person's own weekend frequency, overriding the weekend rule. Null clears it."}}}}}},"tags":["admin"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/people/phones":{"post":{"summary":"Add mobile numbers for people already on the roster. Preview matches each line by email, then full name, then last name plus first initial, and saves nothing. Apply saves only phone for the listed people whose row is new or change.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"mode":{"type":"string","enum":["preview","apply"],"default":"preview"},"text":{"type":"string","maxLength":20000,"description":"One person per line: a name or email and a mobile number. A header row is optional."},"personIds":{"type":"array","items":{"type":"string"},"description":"Apply only: the people to save, from the preview rows."}}}}}},"tags":["admin"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/contacts/card":{"get":{"summary":"Your current team contact card as a vCard 3.0 file (text/vcard): everyone active in your own workplace who shares a mobile number.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["me"]}},"/api/v1/contacts/batches":{"get":{"summary":"Sharing counts, what a full or new-only contact update would send, and the history of contact links.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["admin"]},"post":{"summary":"Make a contact card link without sending anything. all holds everyone who shares a mobile number; new holds only people who were not on an earlier sent card. The link works for 30 days.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"scope":{"type":"string","enum":["all","new"],"default":"all"}}}}}},"tags":["admin"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/contacts/batches/send":{"post":{"summary":"Send a contact update: one email each (and a text for people who turned texts on) with a link to the card. new sends only new contacts to people who got an earlier update and the full card to people who never did. Sends real messages.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"scope":{"type":"string","enum":["all","new"],"default":"all"}}}}}},"tags":["admin"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/contacts/batches/{id}":{"delete":{"summary":"Turn off a contact card link.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","tags":["admin"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/program/moonlighting":{"get":{"summary":"Moonlighting: optional paid shifts on top of the schedule. Residents get their board (MoonlightBoard); admins get the settings and one month of coverage.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"parameters":[{"name":"month","in":"query","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}$","description":"Admins: the month of coverage, YYYY-MM. Default this month."}}],"description":"Read only for residents in the mobile app. Returns MoonlightBoard for a resident token: {config: {types}, notices, configured, open, released, mine, upcoming, totals, jeopardy}. Pay is in cents; null means not set.","tags":["program"]}},"/api/v1/program/moonlighting/payroll":{"get":{"summary":"One month of moonlighting payroll as CSV, or JSON with format=json.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","parameters":[{"name":"month","in":"query","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}$"}},{"name":"format","in":"query","schema":{"type":"string","enum":["csv","json"]}}],"tags":["program"]}},"/api/v1/program/moonlighting/mine":{"post":{"summary":"Change your own moonlighting: sign up, request, cancel, release, keep, take a released shift or log overtime.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["signup","request","cancel","release","unrelease","take","pass","want","pick","overtime","hours","opt-out","opt-in"]},"typeId":{"type":"string","description":"signup and request"},"date":{"type":"string","format":"date","description":"signup and request"},"shiftId":{"type":"string","description":"cancel, release, unrelease, take, pass, want, pick, overtime and hours"},"toPersonId":{"type":"string","description":"pick (pick mode only): someone who said I Want It"},"poolId":{"type":"string","description":"opt-out and opt-in for a jeopardy pool"},"minutes":{"type":"integer","minimum":0,"maximum":1440,"multipleOf":15,"description":"overtime: minutes past the end (at most 600); hours: minutes worked on a shift type whose hours vary. Both once the shift ends; both wait for an admin to approve them."}}}}}},"description":"Returns {ok, shift, warnings, flags}. A seat that overlaps your call, rotation hours, leave or jeopardy on the published schedule is refused with 409 and the reason in error, such as \"You are on short call 5 PM to 9 PM on Mon, Oct 5.\". warnings never block: \"You are on nights this week.\" (a night float rotation that day or the morning after a night) and hours near 80 a week. flags is [\"nights\"] then; only an overlap with the night's own hours is refused. Refusals return {error, problems?} with 400, 403, 404 or 409.","tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/moonlighting/hours":{"post":{"summary":"Approve a resident's overtime or worked hours on a moonlighting seat, or set them (an admin's entry is approved at once).","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["shiftId"],"properties":{"shiftId":{"type":"string"},"approve":{"type":"boolean"},"overtimeMinutes":{"type":"integer","minimum":0,"maximum":600,"multipleOf":15},"workedMinutes":{"type":"integer","minimum":0,"maximum":1440,"multipleOf":15}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/moonlighting/starts":{"get":{"summary":"Starting counts for an academic year: shifts of each type a resident worked before moonlighting was tracked here. They count toward yearly limits and fewest shifts first.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","parameters":[{"name":"year","in":"query","schema":{"type":"integer"}}],"tags":["program"]},"put":{"summary":"Set one starting count. count 0 removes it.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["personId","typeId","count"],"properties":{"personId":{"type":"string"},"typeId":{"type":"string"},"count":{"type":"integer","minimum":0,"maximum":366},"year":{"type":"integer"}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/moonlighting/assign":{"post":{"summary":"Place a resident on a moonlighting shift. override saves past a class, limit, overlap or full date, with the reasons; past the regular schedule (call, rotation hours, leave or jeopardy) only with reason. Returns {ok, shift, warnings, flags}; flags is [\"nights\"] during the resident's nights (never blocking).","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["personId","typeId","date"],"properties":{"personId":{"type":"string"},"typeId":{"type":"string"},"date":{"type":"string","format":"date"},"override":{"type":"boolean"},"reason":{"type":"string","maxLength":200}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]},"delete":{"summary":"Remove a moonlighting seat or request.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["personId","shiftId"],"properties":{"personId":{"type":"string"},"shiftId":{"type":"string"}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/moonlighting/released":{"get":{"summary":"Released moonlighting shifts: the ones you released (admins: every one) with who was told, passed, wants it or has not answered, and the released shifts offered to you (offers) with canTake and mode.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"A push or in-app notice about a released shift opens /mine/moonlighting?shift=<shiftId>. Answer with POST /api/v1/program/moonlighting/mine {action: take | pass | want, shiftId}.","tags":["program"]}},"/api/v1/program/moonlighting/released/pick":{"post":{"summary":"Give a released moonlighting shift to someone. override saves past a class, rotation, limit, overlap or Not Interested, with the reasons; past the regular schedule only with reason.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["shiftId","personId"],"properties":{"shiftId":{"type":"string"},"personId":{"type":"string"},"override":{"type":"boolean"},"reason":{"type":"string","maxLength":200}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/moonlighting/settings":{"get":{"summary":"Moonlighting settings: shift types, the sign-up window, release rules and jeopardy pools, plus the program's classes and one plain line per shift type. Pay is in cents.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["program"]},"patch":{"summary":"Change the sign-up window and release rules. Shift types and pools change through their own routes.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"window":{"type":"object","properties":{"period":{"type":"string","enum":["month","block"]},"opensDaysBefore":{"type":"integer","minimum":0,"maximum":180},"closesDaysBefore":{"type":"integer","minimum":0,"maximum":180}},"additionalProperties":false},"windowOverrides":{"type":["object","null"],"description":"Keys \"YYYY-MM\" (a month) or \"YYYY:bN\" (a block); values {opensOn, closesOn}. null clears them."},"releaseClassHours":{"type":"integer","minimum":0,"maximum":336},"releaseTakeMode":{"type":"string","enum":["first-come","pick"]},"releasePickHours":{"type":"integer","minimum":1,"maximum":336},"releaseStillOpenHours":{"type":"integer","minimum":0,"maximum":336},"releaseOpenHoursBefore":{"type":"integer","minimum":0,"maximum":336,"description":"A released shift opens to everyone this many hours before it starts when that comes before the class-first hours end (default 72)."},"cancelGraceHours":{"type":"integer","minimum":0,"maximum":168,"description":"Hours after sign-up closes that a resident may still drop a seat they took (default 24)."},"lateReleaseDays":{"type":"integer","minimum":0,"maximum":60}},"additionalProperties":false}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/moonlighting/templates":{"get":{"summary":"Common moonlighting setups to start from, with the classes each picks in this program by place. Pay is blank.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["program"]}},"/api/v1/program/moonlighting/types":{"post":{"summary":"Add a moonlighting shift type. Classes are ids or labels; days are 0 (Sunday) to 6 or words; times are HH:MM or 7 AM; pay is dollars (or payCents).","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":60},"code":{"type":"string","maxLength":3},"levelIds":{"type":"array","items":{"type":"string"},"description":"Class ids or labels. Empty means every class."},"days":{"type":"array","items":{"type":"integer","minimum":0,"maximum":6},"description":"0 Sunday to 6 Saturday. Words such as \"Sat and Sun\" are also read."},"holidays":{"type":"string","enum":["normal","add","skip","only"]},"start":{"type":"string","description":"HH:MM or a time such as 7 AM."},"end":{"type":"string","description":"An end at or before the start ends the next day."},"seats":{"type":"integer","minimum":1,"maximum":20},"pay":{"type":["number","null"],"description":"Dollars per shift."},"payCents":{"type":["integer","null"]},"overtimePerHour":{"type":["number","null"],"description":"Dollars per hour."},"maxPerYear":{"type":["integer","null"],"minimum":1,"maximum":366},"maxPerMonth":{"type":["integer","null"],"minimum":1,"maximum":31},"duties":{"type":["string","null"],"maxLength":500},"note":{"type":["string","null"],"maxLength":200},"mode":{"type":"string","enum":["request","first-come","release","lottery","snake","straight","seniority","ranked","bidding","round-robin","admin"],"description":"request is Assign Fairly, the default and recommended. The others run as sign-up rounds."},"pickOrder":{"type":["array","null"],"items":{"type":"string"},"description":"Snake, straight, seniority and round-robin: the pick order set by hand (person ids or full names, first pick first). null is the automatic order. People no longer eligible drop out; newly eligible people go to the end."},"openLevelIds":{"type":["array","null"],"items":{"type":"string"}},"countsAsMoonlighting":{"type":"boolean"},"sendMessages":{"type":"boolean"},"hoursVary":{"type":"boolean","description":"The hours change from shift to shift: people enter the hours worked, approved like overtime."},"enabled":{"type":"boolean"},"id":{"type":"string"},"classes":{"type":"array","items":{"type":"string"},"description":"Same as levelIds."},"payByDate":{"type":["object","null"],"additionalProperties":{"type":"integer"},"description":"{\"YYYY-MM-DD\": cents} for dates that pay differently."},"overtimeCentsPerHour":{"type":["integer","null"]},"worksAlongsideCallKinds":{"type":["array","null"],"items":{"type":"string","enum":["short","weekend","holiday","baby"]},"description":"Call a person may hold on the same day."},"unlockRotationIds":{"type":["array","null"],"items":{"type":"string"}},"unlockMode":{"type":["string","null"],"enum":["any","all",null]},"includePeople":{"type":["array","null"],"items":{"type":"object","properties":{"personId":{"type":"string"},"reason":{"type":"string"}}}},"excludePeople":{"type":["array","null"],"items":{"type":"object","properties":{"personId":{"type":"string"},"reason":{"type":"string"}}}}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/moonlighting/types/{id}":{"patch":{"summary":"Change a moonlighting shift type. Send only the fields to change; enabled false turns it off and keeps its shifts; null clears pay, limits, duties, note or openLevelIds.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":60},"code":{"type":"string","maxLength":3},"levelIds":{"type":"array","items":{"type":"string"},"description":"Class ids or labels. Empty means every class."},"days":{"type":"array","items":{"type":"integer","minimum":0,"maximum":6},"description":"0 Sunday to 6 Saturday. Words such as \"Sat and Sun\" are also read."},"holidays":{"type":"string","enum":["normal","add","skip","only"]},"start":{"type":"string","description":"HH:MM or a time such as 7 AM."},"end":{"type":"string","description":"An end at or before the start ends the next day."},"seats":{"type":"integer","minimum":1,"maximum":20},"pay":{"type":["number","null"],"description":"Dollars per shift."},"payCents":{"type":["integer","null"]},"overtimePerHour":{"type":["number","null"],"description":"Dollars per hour."},"maxPerYear":{"type":["integer","null"],"minimum":1,"maximum":366},"maxPerMonth":{"type":["integer","null"],"minimum":1,"maximum":31},"duties":{"type":["string","null"],"maxLength":500},"note":{"type":["string","null"],"maxLength":200},"mode":{"type":"string","enum":["request","first-come","release","lottery","snake","straight","seniority","ranked","bidding","round-robin","admin"],"description":"request is Assign Fairly, the default and recommended. The others run as sign-up rounds."},"pickOrder":{"type":["array","null"],"items":{"type":"string"},"description":"Snake, straight, seniority and round-robin: the pick order set by hand (person ids or full names, first pick first). null is the automatic order. People no longer eligible drop out; newly eligible people go to the end."},"openLevelIds":{"type":["array","null"],"items":{"type":"string"}},"countsAsMoonlighting":{"type":"boolean"},"sendMessages":{"type":"boolean"},"hoursVary":{"type":"boolean","description":"The hours change from shift to shift: people enter the hours worked, approved like overtime."},"enabled":{"type":"boolean"},"id":{"type":"string"},"classes":{"type":"array","items":{"type":"string"},"description":"Same as levelIds."},"payByDate":{"type":["object","null"],"additionalProperties":{"type":"integer"},"description":"{\"YYYY-MM-DD\": cents} for dates that pay differently."},"overtimeCentsPerHour":{"type":["integer","null"]},"worksAlongsideCallKinds":{"type":["array","null"],"items":{"type":"string","enum":["short","weekend","holiday","baby"]},"description":"Call a person may hold on the same day."},"unlockRotationIds":{"type":["array","null"],"items":{"type":"string"}},"unlockMode":{"type":["string","null"],"enum":["any","all",null]},"includePeople":{"type":["array","null"],"items":{"type":"object","properties":{"personId":{"type":"string"},"reason":{"type":"string"}}}},"excludePeople":{"type":["array","null"],"items":{"type":"object","properties":{"personId":{"type":"string"},"reason":{"type":"string"}}}}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]},"delete":{"summary":"Remove a moonlighting shift type with no seats or requests on file. One with seats answers 409: turn it off with enabled false instead.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","tags":["program"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/moonlighting/pools":{"post":{"summary":"Add a jeopardy (backup) pool.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":60},"levelIds":{"type":"array","items":{"type":"string"}},"covers":{"type":"string","enum":["weekend","weekday","custom"]},"startDays":{"type":"array","uniqueItems":true,"minItems":1,"maxItems":7,"items":{"type":"integer","minimum":0,"maximum":6},"description":"Custom units start on these weekdays (Sunday=0). Units must not overlap."},"durationDays":{"type":"integer","minimum":1,"maximum":7,"description":"Required with startDays for custom pools. Inclusive days per recurring unit."},"hours":{"type":["object","null"],"additionalProperties":false,"required":["start","end"],"properties":{"start":{"type":"string","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$"},"end":{"type":"string","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$"}},"description":"Daily backup window in program time zone. End at or before start crosses midnight; equal times mean 24 hours. null restores shift-derived hours. Assigned pools cannot change clocks."},"typeIds":{"type":["array","null"],"items":{"type":"string"},"description":"Shift types it backs up (ids or names). null or left out: every type whose classes overlap the pool's."}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/moonlighting/pools/{id}":{"patch":{"summary":"Change a jeopardy pool.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":60},"levelIds":{"type":"array","items":{"type":"string"}},"covers":{"type":"string","enum":["weekend","weekday","custom"]},"startDays":{"type":"array","uniqueItems":true,"minItems":1,"maxItems":7,"items":{"type":"integer","minimum":0,"maximum":6},"description":"Custom units start on these weekdays (Sunday=0). Units must not overlap."},"durationDays":{"type":"integer","minimum":1,"maximum":7,"description":"Required with startDays for custom pools. Inclusive days per recurring unit."},"hours":{"type":["object","null"],"additionalProperties":false,"required":["start","end"],"properties":{"start":{"type":"string","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$"},"end":{"type":"string","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$"}},"description":"Daily backup window in program time zone. End at or before start crosses midnight; equal times mean 24 hours. null restores shift-derived hours. Assigned pools cannot change clocks."},"typeIds":{"type":["array","null"],"items":{"type":"string"},"description":"Shift types it backs up (ids or names). null or left out: every type whose classes overlap the pool's."}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]},"delete":{"summary":"Remove a jeopardy pool.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","tags":["program"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/moonlighting/jeopardy":{"get":{"summary":"The jeopardy roster for an academic year: every unit of each pool with who has it and each member's count.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","parameters":[{"name":"year","in":"query","schema":{"type":"integer"}},{"name":"poolId","in":"query","schema":{"type":"string"}}],"tags":["program"]},"put":{"summary":"Set or clear one jeopardy unit. override places someone outside the pool's classes.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["poolId","start"],"properties":{"poolId":{"type":"string"},"start":{"type":"string","format":"date"},"personId":{"type":["string","null"]},"year":{"type":"integer"},"override":{"type":"boolean"}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/moonlighting/jeopardy/fill":{"post":{"summary":"Fill Evenly: every open unit of the pool goes to the member with the fewest so far, skipping leave and time off.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["poolId"],"properties":{"poolId":{"type":"string"},"year":{"type":"integer"}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/moonlighting/jeopardy/import":{"post":{"summary":"Import a jeopardy roster CSV for one pool. A preview unless apply is true: each row with the resident it matched or why not. Dates fall in the program's own academic year.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["poolId","csv"],"properties":{"poolId":{"type":"string"},"csv":{"type":"string","description":"Columns Category, Dates, Jeopardy Resident."},"year":{"type":"integer"},"apply":{"type":"boolean","default":false}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/moonlighting/rounds":{"get":{"summary":"Moonlighting sign-up rounds touching a date range. Admins get every round (automatic ones included); residents get the rounds they are in, with their own ranking or bids.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"parameters":[{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}}],"tags":["program"]},"post":{"summary":"Set up a round. Assign Fairly (request) is the default method; use another only when asked. A timed release takes seats, all or next and opensAt.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"method":{"type":"string","enum":["request","first-come","release","lottery","snake","straight","seniority","ranked","bidding","round-robin","admin"]},"typeIds":{"type":"array","items":{"type":"string"},"description":"Shift type ids or names."},"month":{"type":"string","pattern":"^\\d{4}-\\d{2}$"},"start":{"type":"string","format":"date"},"end":{"type":"string","format":"date"},"opensOn":{"type":"string","format":"date"},"closesOn":{"type":"string","format":"date"},"participants":{"type":["array","null"],"items":{"type":"string"}},"seats":{"type":"array","items":{"type":"string","description":"typeId|YYYY-MM-DD"}},"all":{"type":"boolean"},"next":{"type":"integer","minimum":1},"opensAt":{"type":"string","description":"YYYY-MM-DDTHH:MM in the program's time zone, ISO or now."},"closesAt":{"type":"string"},"settings":{"type":"object","properties":{"maxPicks":{"type":"integer","minimum":1},"balance":{"type":"boolean"},"live":{"type":"boolean"},"order":{"type":"string","enum":["random","fewest","admin","seniority"]},"pattern":{"type":"string","enum":["snake","straight"]},"rotateFirst":{"type":"boolean"},"rounds":{"type":"integer","minimum":1},"clock":{"type":"array","items":{"type":"object","properties":{"fromRound":{"type":"integer"},"seconds":{"type":"integer"}}}},"onTimeout":{"type":"string","enum":["auto-pick","skip"]},"budget":{"type":"integer","minimum":1},"carryover":{"type":"boolean"},"leftovers":{"type":"string","enum":["first-come","admin"]},"early":{"type":"object","properties":{"picks":{"type":"integer"},"minutes":{"type":"integer"}}}}}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/moonlighting/rounds/{id}":{"get":{"summary":"One round. Expired live draft clocks move on first. Admins see the settings, seed, preview and audit log.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["program"]},"patch":{"summary":"Change a round before it runs. After publishing or once a live draft starts only the name changes.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"method":{"type":"string","enum":["request","first-come","release","lottery","snake","straight","seniority","ranked","bidding","round-robin","admin"]},"typeIds":{"type":"array","items":{"type":"string"},"description":"Shift type ids or names."},"month":{"type":"string","pattern":"^\\d{4}-\\d{2}$"},"start":{"type":"string","format":"date"},"end":{"type":"string","format":"date"},"opensOn":{"type":"string","format":"date"},"closesOn":{"type":"string","format":"date"},"participants":{"type":["array","null"],"items":{"type":"string"}},"seats":{"type":"array","items":{"type":"string","description":"typeId|YYYY-MM-DD"}},"all":{"type":"boolean"},"next":{"type":"integer","minimum":1},"opensAt":{"type":"string","description":"YYYY-MM-DDTHH:MM in the program's time zone, ISO or now."},"closesAt":{"type":"string"},"settings":{"type":"object","properties":{"maxPicks":{"type":"integer","minimum":1},"balance":{"type":"boolean"},"live":{"type":"boolean"},"order":{"type":"string","enum":["random","fewest","admin","seniority"]},"pattern":{"type":"string","enum":["snake","straight"]},"rotateFirst":{"type":"boolean"},"rounds":{"type":"integer","minimum":1},"clock":{"type":"array","items":{"type":"object","properties":{"fromRound":{"type":"integer"},"seconds":{"type":"integer"}}}},"onTimeout":{"type":"string","enum":["auto-pick","skip"]},"budget":{"type":"integer","minimum":1},"carryover":{"type":"boolean"},"leftovers":{"type":"string","enum":["first-come","admin"]},"early":{"type":"object","properties":{"picks":{"type":"integer"},"minutes":{"type":"integer"}}}}}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]},"delete":{"summary":"Remove a round that has not started or published. Seats it gave stay.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","tags":["program"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/moonlighting/rounds/{id}/run":{"post":{"summary":"Run the round's method as a preview. Places nothing until publish; a new seed each run unless one is given.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"seed":{"type":"integer","minimum":0}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/moonlighting/rounds/{id}/publish":{"post":{"summary":"Publish the preview: checked again, placed as ordinary seats, unmet requests cleared, residents told, round locked.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","tags":["program"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/moonlighting/rounds/{id}/discard":{"post":{"summary":"Discard the unpublished preview.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","tags":["program"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/moonlighting/rounds/{id}/order":{"post":{"summary":"Shuffle (seeded), set by hand, reset or lock a draft's order or a Round Robin rotation. reset goes back to the shift type's order set by hand, else automatic.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["shuffle","set","reset","lock"]},"ids":{"type":"array","items":{"type":"string"}},"seed":{"type":"integer","minimum":0}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/moonlighting/rounds/{id}/draft":{"post":{"summary":"Control a live draft. pick records a seat for the person on the clock.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["start","pause","resume","skip","undo","end","pick"]},"slot":{"type":"string","description":"typeId|YYYY-MM-DD"},"personId":{"type":"string"}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/moonlighting/windows":{"get":{"summary":"Sign-up windows by period with sign-ups on file and Timed Release batches.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","parameters":[{"name":"typeId","in":"query","schema":{"type":"string"}},{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}}],"tags":["program"]}},"/api/v1/program/moonlighting/windows/{key}":{"post":{"summary":"Set, reset, copy from the previous period or apply to all periods one period's sign-up window. Moving a closed or busy window into the past needs confirm.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["set","reset","copy-previous","apply-to-all"]},"opensOn":{"type":"string","format":"date"},"closesOn":{"type":"string","format":"date"},"opensTime":{"type":["string","null"],"description":"HH:MM"},"closesTime":{"type":["string","null"],"description":"HH:MM"},"confirm":{"type":"boolean"}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/moonlighting/windows/{key}/batches":{"post":{"summary":"Add, change, remove, copy or apply Timed Release batches in one period.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["add","update","remove","copy-previous","apply-to-all"]},"typeId":{"type":"string"},"id":{"type":"string"},"opensAt":{"type":"string"},"closesAt":{"type":["string","null"]},"cap":{"type":["integer","null"],"minimum":1},"early":{"type":["object","null"],"properties":{"picks":{"type":"integer"},"minutes":{"type":"integer"}}}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/moonlighting/rounds/{id}/release":{"post":{"summary":"Control a timed release: open now, close, extend or add seats.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["open-now","close","extend","add-seats"]},"closesAt":{"type":["string","null"]},"seats":{"type":"array","items":{"type":"string"}}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/moonlighting/rounds/{id}/results":{"get":{"summary":"A round's results by person with seats left open. CSV for admins; residents read JSON once published.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"parameters":[{"name":"format","in":"query","schema":{"type":"string","enum":["json","csv"]}}],"tags":["program"]}},"/api/v1/program/moonlighting/rounds/{id}/ranking":{"put":{"summary":"Your ranking for a draft or Best Overall Fit round, best first.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ranking"],"properties":{"ranking":{"type":"array","items":{"type":"string","description":"typeId|YYYY-MM-DD"}}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/moonlighting/rounds/{id}/bids":{"put":{"summary":"Your bids for a Points Bidding round, whole points within your budget.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["bids"],"properties":{"bids":{"type":"object","additionalProperties":{"type":"integer","minimum":0}}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/moonlighting/rounds/{id}/pick":{"post":{"summary":"Take a seat: in a live draft on your turn, or in an open timed release (first confirmed wins).","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["slot"],"properties":{"slot":{"type":"string","description":"typeId|YYYY-MM-DD"}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/moonlighting/rounds/{id}/pass":{"post":{"summary":"Sit out the rest of a live draft.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["program"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/hours":{"get":{"summary":"Work hour periods (ACGME sampling periods). Admins get every period with its state and counts {included, submitted, notYet, reopened}; residents get the periods that include them with myStatus.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["program"]},"post":{"summary":"Open a work hour period. Classes resolve to the residents in them on the first day. Sends nothing.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","start","end"],"properties":{"name":{"type":"string","maxLength":80},"start":{"type":"string","format":"date"},"end":{"type":"string","format":"date","description":"At most 62 days after start."},"due":{"type":"string","format":"date","description":"Defaults to a week after end."},"levelIds":{"type":"array","items":{"type":"string"},"description":"Class ids. Empty or left out means all residents."}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/program/hours/{id}":{"get":{"summary":"Review one period: each resident's status, total hours, weekly average, ACGME issues, edited days and signedAt. With signatures=true each submitted resident's signaturePng (a PNG data URL) is added.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","parameters":[{"name":"signatures","in":"query","schema":{"type":"string","enum":["true"]}}],"tags":["program"]},"patch":{"summary":"Close a period {closed: true} or open it again {closed: false}.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["closed"],"properties":{"closed":{"type":"boolean"}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]},"delete":{"summary":"Delete a period made by mistake. Refused once any resident has submitted.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","tags":["program"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/program/hours/{id}/compliance":{"get":{"summary":"ACGME compliance summary: per resident status, total hours, weekly average, edited days, violations and cautions in plain words.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["program"]}},"/api/v1/program/hours/{id}/export":{"get":{"summary":"CSV for the GME office (text/csv): Resident, Class, Status, Date, In, Out, Type, Hours, Edited, Note.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["program"]}},"/api/v1/program/hours/{id}/remind":{"post":{"summary":"Email every included resident who has not submitted, and text those who turned texts on. Sends real messages.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","tags":["program"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/program/hours/{id}/residents/{personId}":{"get":{"summary":"One resident's days in a period, their status, the ACGME checks and log {submittedAt, reopenedAt, signedAt, signaturePng, adminEdits, changedAfterSigningAt}.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["program"]},"put":{"summary":"Edit a resident's days as an admin with a required reason. Logged in Activity and on the log. Submitted hours stay submitted, marked changed after signing, until the resident confirms again. Emails the resident the change.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["days","reason"],"properties":{"days":{"type":"array","items":{"type":"object"},"description":"Same shape as PUT /api/v1/program/hours/{id}/mine."},"reason":{"type":"string","minLength":3,"maxLength":500}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/program/hours/{id}/residents/{personId}/reopen":{"post":{"summary":"Reopen a resident's submitted hours so they can edit and submit again. Clears their signature; the re-submit is signed again.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","tags":["program"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/program/hours/{id}/gme":{"get":{"summary":"GME Report: program, period, counts, a count for each ACGME rule, one row per resident (total, weekly average, busiest week, days off per 7, longest continuous shift, short rests, moonlighting, status, submitted date) and every issue. With signatures=true each submitted signaturePng is added. With logs=true, report.logs has each resident's days, Monday to Sunday week totals, period total, admin edits and changedAfterSigningAt.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","parameters":[{"name":"signatures","in":"query","schema":{"type":"string","enum":["true"]}},{"name":"logs","in":"query","schema":{"type":"string","enum":["true"]}}],"tags":["program"]}},"/api/v1/program/hours/{id}/gme/export":{"get":{"summary":"CSV for the GME office (text/csv), one row per resident per week: Resident, Class, Status, Week Start, Week End, Days in Period, Total Hours, In-House Hours, Moonlighting Hours, Home Call Hours, Work From Home Hours, Days Off, Flags.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["program"]}},"/api/v1/program/hours/{id}/mine":{"get":{"summary":"Your own days for a period, pre-filled from the published schedule, with weekly totals, ACGME issues and your log {submittedAt, signedAt, signaturePng}.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["me"]},"put":{"summary":"Save your days without submitting. Days left out keep the schedule; changed days are marked edited.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["days"],"properties":{"days":{"type":"array","items":{"type":"object","required":["date","entries"],"properties":{"date":{"type":"string","format":"date"},"note":{"type":"string","maxLength":500},"entries":{"type":"array","maxItems":8,"items":{"type":"object","required":["type","start","end"],"properties":{"type":{"type":"string","enum":["clinical","call","moonlight-internal","moonlight-external","home-call","home-work"]},"start":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$"},"end":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$","description":"An end at or before the start ends the next day."},"nextDay":{"type":"boolean","description":"Set for a stretch longer than 24 hours."}}}}}}}}}}}},"tags":["me"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/program/hours/{id}/mine/export":{"get":{"summary":"Your own days for a period as CSV (text/csv): Date, In, Out, Type, Hours, Edited, Note.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["me"]}},"/api/v1/program/hours/{id}/mine/submit":{"post":{"summary":"Submit your hours with {attest: true, signature}. The signature is the resident's own drawn signature as a PNG data URL (data:image/png;base64,...), at most 200 KB; saveSignature (true unless sent false) keeps it as their saved signature. In the app, {attest: true, useSaved: true} applies the saved signature instead; API keys and connected apps cannot. attest is required every time. Without days it submits what you saved, or the schedule as is. Read-only until an admin reopens it.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["attest"],"properties":{"attest":{"type":"boolean","const":true},"signature":{"type":"string","maxLength":204800,"pattern":"^data:image/png;base64,"},"saveSignature":{"type":"boolean"},"useSaved":{"type":"boolean"},"days":{"type":"array","items":{"type":"object"}}}}}}},"tags":["me"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/people/{id}/roster-photo":{"get":{"summary":"The person's roster photo: {rosterPhotoUrl, path}. A signed link that works for one hour, or null. Their own profile picture still shows in its place when they have one.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["admin"]},"post":{"summary":"Start a roster photo upload for someone on the roster, with or without an account. Crop to a square and downscale to 512x512 first. Returns {upload: {bucket, path, token, signedUrl}}; PUT the bytes to signedUrl, then POST /api/v1/people/{id}/roster-photo/commit.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["contentType","size"],"properties":{"contentType":{"type":"string","enum":["image/webp","image/jpeg"]},"size":{"type":"integer","minimum":1,"maximum":5242880,"description":"Bytes. 5 MB at most."}}}}}},"tags":["admin"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]},"delete":{"summary":"Remove the person's roster photo. Their own profile picture or initials show instead.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","tags":["admin"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/people/{id}/roster-photo/commit":{"post":{"summary":"Finish a roster photo upload: the server reads the stored file back, checks its type and size, makes it current and deletes the one it replaces. Returns {rosterPhotoUrl, path}.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["path"],"properties":{"path":{"type":"string","description":"upload.path from POST /api/v1/people/{id}/roster-photo"}}}}}},"tags":["admin"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/people/{id}":{"get":{"summary":"One person, with avatarUrl: a signed link to their own profile picture, else their roster photo, else null","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["admin"]},"patch":{"summary":"Update a person","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sharePhoneWithTeam":{"type":"boolean","description":"Mobile number sharing with the team (Team, Call and Text buttons and the team contact card). On unless false. The person can also change it in Profile; the last change wins."},"preferences":{"type":"object","description":"Usual days and how firm they are. Notes stay the person's own.","properties":{"workWeekdays":{"type":"array","items":{"type":"integer","minimum":0,"maximum":6},"description":"Usual working days, 0 Sunday to 6 Saturday. An empty list means any day."},"workWeekdaysFirmness":{"type":["string","null"],"enum":["always","usually",null],"description":"How firm workWeekdays are. always: never works other days (the generator never places them there, swaps and covers never offer them, a shift there is Must Fix). usually: prefers these days (other days are avoided, allowed in swaps with a Not a Usual Day label, and a shift there is Worth a Look). Unset or null behaves as always."}}},"clinicalFte":{"type":["number","null"],"minimum":0,"description":"Clinical effort, no greater than employment FTE. Null clears the override."},"maxWeeklyHours":{"type":["number","null"],"minimum":0,"maximum":168,"description":"Monday-to-Sunday full duty-span limit, including paid and unpaid breaks. Overnight spans are split across weeks. Null clears the limit."},"workPattern":{"type":["object","null"],"required":["anchorDate","days"],"properties":{"anchorDate":{"type":"string","format":"date"},"offsetDays":{"type":"integer"},"days":{"type":"array","minItems":1,"maxItems":366,"items":{"type":"object","required":["shiftTypeIds"],"properties":{"shiftTypeIds":{"type":"array","items":{"type":"string"}},"required":{"type":"boolean","description":"Requires one permitted duty unless blocking leave excuses all alternatives"}}}}}},"protectedTime":{"type":["array","null"],"items":{"type":"object","required":["id","label","weekdays","start","end"],"properties":{"id":{"type":"string"},"label":{"type":"string"},"weekdays":{"type":"array","minItems":1,"items":{"type":"integer","minimum":0,"maximum":6}},"start":{"type":"string"},"end":{"type":"string"}}}},"weekendFrequency":{"type":["object","null"],"required":["maxWorked","window"],"properties":{"maxWorked":{"type":"integer","minimum":0},"window":{"type":"integer","minimum":1,"maximum":52}},"description":"Hours rules feature: this person's own weekend frequency, overriding the weekend rule. Null clears it."}}}}}},"tags":["admin"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]},"delete":{"summary":"Deactivate a person","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","tags":["admin"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/people/{id}/access":{"patch":{"summary":"Make Admin or Remove Admin. Takes effect at once, is logged and emails the person unless they changed their own access. Refused with reason owner_locked for the Owner (transfer ownership first) and last_admin for the last admin","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["access"],"properties":{"access":{"type":"string","enum":["admin","member"]}}}}}},"tags":["admin"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/ownership":{"get":{"summary":"Who owns the workplace: owner {personId, name, email for admins}, ownerPresent, isOwner, isAdmin, level, and for admins the pending transfer","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["own","admin"]}},"/api/v1/assignments":{"get":{"summary":"Published assignments","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","parameters":[{"name":"personId","in":"query","schema":{"type":"string"}},{"name":"date","in":"query","schema":{"type":"string","format":"date"}},{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}},{"name":"fields","in":"query","schema":{"type":"string"},"description":"Comma-separated top-level fields to keep, such as id,name. Ids are always kept."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":500},"description":"Page size. A paged answer is {items, total, nextCursor} ({year, hits, total, nextCursor} for program issues)."},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"nextCursor from the previous page."}],"tags":["admin"]},"post":{"summary":"Put someone on a shift","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["personId","date"],"properties":{"personId":{"type":"string"},"date":{"type":"string","format":"date"},"shiftTypeId":{"type":"string"},"status":{"type":"string","enum":["draft"],"description":"Explicitly create an unpublished draft assignment."},"locked":{"type":"boolean","description":"Preserve this assignment during automatic generation and balancing. Manual admin edits remain available; validity checks still apply."}}}}}},"tags":["admin"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/assignments/{id}":{"patch":{"summary":"Edit an assignment or its automatic-generation lock","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","minProperties":1,"properties":{"personId":{"type":"string"},"date":{"type":"string","format":"date"},"shiftTypeId":{"type":"string"},"locked":{"type":"boolean","description":"Send {locked:true} or {locked:false} alone to change the lock. Assignment ID and publication status are preserved."}}}}}},"tags":["admin"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]},"delete":{"summary":"Remove an assignment","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","tags":["admin"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/rules":{"get":{"summary":"Placement and swap rules","operationId":"listRules","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["admin"]},"post":{"summary":"Add a rule","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","code"],"properties":{"code":{"type":"string","enum":["no-overlap","max-consecutive","supervisor-required","time-off","min-staff","weekend-balance","am-swing-balance","shift-lead","hours-in-window","hours-per-week","hours-average","work-period-hours","min-time-off","rest-after-duty","rest-after-run","max-run","run-gap","no-transition","forward-rotation","max-span","split-premium","weekend-frequency"]},"severity":{"type":"string","enum":["hard","soft"]},"params":{"type":"object","description":"Hours and rest rules (feature hoursRules) take the params listed in docs/SCHEDULING_API.md and are validated; unknown keys are rejected."}}}}}},"tags":["admin"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/rules/{id}":{"patch":{"summary":"Edit a rule","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"tags":["admin"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]},"delete":{"summary":"Remove a rule","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","tags":["admin"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/requests":{"get":{"summary":"All requests","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","parameters":[{"name":"personId","in":"query","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string","enum":["pending","approved","denied"]}},{"name":"standing","in":"query","schema":{"type":"string","enum":["true","false"]}},{"name":"fields","in":"query","schema":{"type":"string"},"description":"Comma-separated top-level fields to keep, such as id,name. Ids are always kept."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":500},"description":"Page size. A paged answer is {items, total, nextCursor} ({year, hits, total, nextCursor} for program issues)."},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"nextCursor from the previous page."}],"tags":["admin"]},"post":{"summary":"Create a request for anyone","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["personId","startDate","endDate"],"additionalProperties":true}}}},"tags":["admin"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/requests/{id}/decide":{"post":{"summary":"Approve or deny a request","operationId":"decideRequest","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["approved","denied"]}}}}}},"tags":["admin"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/swaps":{"get":{"summary":"All swaps","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["admin"]},"post":{"summary":"Propose a swap","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["assignmentId","targetPersonId"],"additionalProperties":true}}}},"tags":["admin"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/swaps/{id}/decide":{"post":{"summary":"Approve or deny a swap","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["approved","denied"]}}}}}},"tags":["admin"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/coverage":{"get":{"summary":"Staffing requirements","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["admin"]},"patch":{"summary":"Set global weekday staffing with {weekday:0..6,minStaff}; date override with {date:YYYY-MM-DD,minStaff}; reset with {date,reset:true}. Zero closes the day and removes its assignments.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["admin"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/coverage/infer":{"post":{"summary":"Rebuild coverage from the roster","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","tags":["admin"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/fairness":{"get":{"summary":"Fairness for the team","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["admin"]}},"/api/v1/hits":{"get":{"summary":"Current rule hits","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["admin"],"parameters":[{"name":"fields","in":"query","schema":{"type":"string"},"description":"Comma-separated top-level fields to keep, such as id,name. Ids are always kept."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":500},"description":"Page size. A paged answer is {items, total, nextCursor} ({year, hits, total, nextCursor} for program issues)."},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"nextCursor from the previous page."}]}},"/api/v1/program":{"get":{"summary":"Residency snapshot with callTimes (dutyTimes and slotTimes, start and end only). Employees receive only their own rows.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["program"]}},"/api/v1/program/rotations":{"get":{"summary":"Rotation catalog","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["program"]},"post":{"summary":"Add a rotation {name, section?, levels? (level ids; every level when left out), capacityPerBlock? (null for no limit), elective?, callExempt? (true keeps it off call), offsite?, nightFloat?, callKinds? (short, weekend, holiday, baby), series? ({name, step} or null)}. Returns 201 {rotation: {id, name, section, levels, capacityPerBlock}}; change other fields with PATCH /program/rotations/:id","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/team":{"get":{"summary":"Published schedule for the whole program: everyone's rotations and call for one year, with callTimes. Drafts return published: false.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"parameters":[{"name":"year","in":"query","schema":{"type":"integer"}}],"tags":["program"]}},"/api/v1/program/grid":{"get":{"summary":"Year rotation grid","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"parameters":[{"name":"year","in":"query","schema":{"type":"integer"}}],"tags":["program"]},"put":{"summary":"Set a rotation cell","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["personId","blockIndex"],"properties":{"personId":{"type":"string"},"blockIndex":{"type":"integer"},"rotationId":{"type":"string"},"parts":{"type":"array"},"year":{"type":"integer"}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]},"patch":{"summary":"Set a rotation cell","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]},"delete":{"summary":"Clear a rotation cell","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["personId","blockIndex"]}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/call":{"post":{"summary":"Add a call day","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["personId","date","kind"]}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]},"delete":{"summary":"Clear one call day {personId, date, year?}","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/requests":{"post":{"summary":"Add a block, days-off, or elective plan request","operationId":"createProgramRequest","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"year","in":"query","schema":{"type":"integer"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProgramRequestInput"}}}},"description":"kind electives sends electivePlan for a request section open to the resident that year; its note may be empty and each resident has one plan per section. Plans wait for an admin; Generate uses approved plans.","tags":["program"]}},"/api/v1/program/requests/{id}":{"patch":{"summary":"Edit an own pending request before its deadline","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"year","in":"query","schema":{"type":"integer"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProgramRequestInput"}}}},"tags":["program"]},"delete":{"summary":"Withdraw an own pending request before its deadline","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"year","in":"query","schema":{"type":"integer"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}],"tags":["program"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}},"/api/v1/program/requests/{id}/decide":{"post":{"summary":"Approve, deny, or reopen one resident request","operationId":"decideProgramRequest","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","parameters":[{"name":"year","in":"query","schema":{"type":"integer"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["approved","denied","pending"]}}}}}},"tags":["program"]}},"/api/v1/program/requests/{id}/suggest":{"post":{"summary":"Suggest dates for a flexible vacation request","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin only. Picks windowDays weekdays inside the window on the resident's best vacation rotation (Preferred first, never Not Allowed) with the fewest call days, and saves them as startDate and endDate on the waiting request. Returns {ok,request,suggestion}; 400 when no run of days fits.","parameters":[{"name":"year","in":"query","schema":{"type":"integer"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}],"tags":["program"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}},"/api/v1/program/leave":{"post":{"summary":"Place parental leave or a leave of absence","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin only. 400 when the dates are invalid, the person has no residency profile or kind is not parental or absence. Returns {ok:true}.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["personId","start","end"],"properties":{"personId":{"type":"string"},"start":{"type":"string","format":"date"},"end":{"type":"string","format":"date"},"note":{"type":"string"},"kind":{"type":"string","enum":["parental","absence"],"default":"parental","description":"absence records a leave of absence (profile leave kind other). Both lock leave cells and earn no training credit"}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/active":{"post":{"summary":"Set a resident's active dates","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin only. Dates before activeFrom and after activeUntil are treated as a leave of absence. 400 when the person has no residency profile, a date is invalid or activeUntil is before activeFrom. Returns {ok:true,personId,activeFrom,activeUntil} with null for cleared dates.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["personId"],"properties":{"personId":{"type":"string"},"activeFrom":{"type":["string","null"],"format":"date","description":"First day in the program (mid-year start). null clears"},"activeUntil":{"type":["string","null"],"format":"date","description":"Last day in the program (early exit). null clears"}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/generate":{"post":{"summary":"Generate 4-week rotations and overlay short and weekend call for an unpublished year","operationId":"generateYear","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin only. A published year is never regenerated: the request returns 400 with {error,published:true,year,nextYear}. Every generation step (rotations, gap filling, repair and call) stops by 45 seconds; the draft so far is saved and incomplete says so. Success returns {ok:true,year,residents,complete,incomplete?}.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"year":{"type":"integer","minimum":1900,"maximum":2200}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/repair":{"post":{"summary":"Preview or apply bounded repairs to an existing unpublished residency draft","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin only. Preview does not save changes and returns DraftRepairPreview. Review its proposed changes, before/after readiness, limits and reasons, then send the complete unchanged preview to apply. Apply recomputes and validates the proposal; changed inputs or a stale preview produce HTTP 400. Success returns {ok:true,year}. Repairs preserve protected leave, manual locks and published years. Remaining conflicts can leave the draft incomplete; applying does not publish it.","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","required":["action","year"],"properties":{"action":{"const":"preview"},"year":{"type":"integer","minimum":1900,"maximum":2200}}},{"type":"object","required":["action","preview"],"properties":{"action":{"const":"apply"},"year":{"type":"integer","minimum":1900,"maximum":2200},"preview":{"$ref":"#/components/schemas/DraftRepairPreview"}}}]}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/qgenda":{"post":{"summary":"Scrape the live QGenda HTML board","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/hits":{"get":{"summary":"Residency rule hits","operationId":"listProgramIssues","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","parameters":[{"name":"year","in":"query","schema":{"type":"integer"}},{"name":"fields","in":"query","schema":{"type":"string"},"description":"Comma-separated top-level fields to keep, such as id,name. Ids are always kept."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":500},"description":"Page size. A paged answer is {items, total, nextCursor} ({year, hits, total, nextCursor} for program issues)."},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"nextCursor from the previous page."}],"tags":["program"]}},"/api/v1/program/rules":{"get":{"summary":"Every residency rule and the call settings","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin only. Returns requirements, gates, totals, items (every rule with type and id), levels, call, xyPattern, openBlockRotation and requestSections.","tags":["program"]},"patch":{"summary":"Apply residency rule patches","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin only. POST is accepted as well. Returns {ok,applied,errors}; a bad call setting returns 400 and saves nothing.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"patches":{"type":"array","items":{"type":"object"}},"call":{"$ref":"#/components/schemas/CallSettingsPatch"}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]},"post":{"summary":"Apply residency rule patches (same as PATCH)","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/rules/items":{"get":{"summary":"List every rule with its type and id","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["program"]},"post":{"summary":"Add a requirement, residency total, or gate","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin only. New rules are enabled and marked manual; a missing id or label is generated. Returns 201 {ok,rule}. 400 returns {error,errors} naming every problem; 409 when the id is taken. The next generate uses the rule.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RuleInput"}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/rules/items/{id}":{"get":{"summary":"One rule","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["program"]},"patch":{"summary":"Change some fields of a rule","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin only. Send only the fields to change; null clears an optional field (pathway, fromYear, untilYear, fromCohort, min, exceptBlocks, levelIds, byLevelId, softForPathways, softNote). A gate may change kind. The rule is checked against the program as on the Rules page. Returns {ok,rule}.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RuleInput"}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]},"delete":{"summary":"Delete a rule","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin only. Returns {ok,deleted:{type,id}}; 404 when no rule has the id.","tags":["program"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/rules/call":{"get":{"summary":"Call settings","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["program"]},"patch":{"summary":"Change call settings","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin only. Unknown keys and bad values return 400 with every error and save nothing. Returns {ok,changed,call}.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CallSettingsPatch"}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/rules/open-blocks":{"get":{"summary":"Open block rotation per class","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin only. Returns {openBlockRotation, levels}. A class with an open block rotation gets it in every block with nothing required or requested; other classes get electives chosen by Generate.","tags":["program"]},"patch":{"summary":"Set open block rotations","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin only. Returns {ok,openBlockRotation}; unknown classes or rotations return 400 and save nothing.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":["string","null"]},"description":"Class id to rotation id, such as {\"R4\":\"elective\"}; null clears a class"}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/rules/request-sections":{"get":{"summary":"Class request sections","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["program"]},"post":{"summary":"Add a class request section","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin only. A missing id comes from the title. Returns 201 {ok,section}; 400 lists every problem; 409 when the id is taken.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestSection"}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]},"put":{"summary":"Replace every class request section","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin only. Returns {ok,sections}.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["sections"],"properties":{"sections":{"type":"array","items":{"$ref":"#/components/schemas/RequestSection"}}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/rules/request-sections/{id}":{"get":{"summary":"One class request section","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["program"]},"patch":{"summary":"Change a class request section","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin only. Send only the fields to change. Returns {ok,section}.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestSection"}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]},"delete":{"summary":"Remove a class request section","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin only. Plans residents already sent stay on Requests. Returns {ok,deleted}.","tags":["program"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/rotations/{id}":{"get":{"summary":"One rotation","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["program"]},"patch":{"summary":"Change a rotation","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin only. Returns {ok,changed,rotation,shortCallAvoided}.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string"},"section":{"type":"string"},"levels":{"type":"array","items":{"type":"string"},"description":"Class ids; empty means any class"},"capacityPerBlock":{"type":["integer","null"],"minimum":0},"callKinds":{"type":"array","items":{"type":"string","enum":["short","weekend","holiday","baby"]},"description":"Empty keeps anyone on the rotation off every call"},"fixedDates":{"type":"boolean"},"study":{"type":"boolean"},"offsite":{"type":"boolean"},"leave":{"type":"boolean"},"callExempt":{"type":"boolean"},"shortCode":{"type":["string","null"],"maxLength":8},"ownCall":{"type":["object","null"],"properties":{"callDays":{"type":"integer","minimum":0,"maximum":366},"maxShorts":{"type":"integer","minimum":0,"maximum":31,"description":"Regular weekday shorts in one block on this rotation before a note (default 2)"}},"required":["callDays"],"additionalProperties":false},"elective":{"type":"boolean"},"nightFloat":{"type":"boolean"},"repeatable":{"type":"boolean"},"aliases":{"type":"array","items":{"type":"string"}},"shortCallAvoided":{"type":"boolean","description":"Weekday short call only when nobody else can take the seat"},"vacation":{"type":["string","null"],"enum":["preferred","allowed","discouraged","blocked",null],"description":"How well vacation fits this rotation. blocked is shown as Not Allowed; null clears to allowed"},"workday":{"$ref":"#/components/schemas/Workday"},"series":{"type":["object","null"],"required":["name","step"],"properties":{"name":{"type":"string"},"step":{"type":"number","description":"Order in the series; 4.5 is a variant between 4 and 5"}},"description":"A numbered sequence run in order, each step once; null takes the rotation out of a series"}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]},"delete":{"summary":"Remove a rotation nothing uses (a starter rotation the program does not run). Refused with 409 naming each use (rules, open blocks, request sections, sign-ups, moonlighting, windows, any year's grid or requests, carry-overs, early pulls) until those change","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}],"tags":["program"],"x-scope":"admin"}},"/api/v1/program/rules/talk":{"post":{"summary":"Describe a rule change in plain words","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin only. Runs the same model as Describe a Change on the Rules page. Returns {ok,applied,reply,patches,changes,errors}; changes lists what the patches do (a dry run unless apply is true). 503 when the server has no AI key; 502 when the model fails.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"type":"string","maxLength":2000},"year":{"type":"integer","description":"Academic start year the change begins"},"classes":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"},"maxItems":1}],"description":"Limit new rules to one class id"},"apply":{"type":"boolean","default":false,"description":"Save the proposed patches; otherwise nothing is saved"}}}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/year":{"patch":{"summary":"Select the current academic year","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["year"]}}}},"tags":["program"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/call":{"post":{"summary":"Legacy tool runner. Tools are scoped by account access.","operationId":"callSchedultaryTool","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["tool"],"properties":{"tool":{"type":"string","enum":["get_snapshot","get_org","update_org","list_people","upsert_person","list_shift_types","list_assignments","assign","unassign","list_requests","create_request","decide_request","list_coverage","infer_coverage","get_fairness","get_rule_hits","list_swaps","propose_swap","decide_swap","get_program","set_rotation","clear_rotation","set_call","generate_month","generate_year","pull_qgenda","place_leave"]},"input":{"type":"object","additionalProperties":true}}}}}},"tags":["legacy"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/explain/cell":{"get":{"operationId":"explainCell","summary":"Why a person holds a shift","description":"The generator's decision (stage, eligible count, cost) or hand placement, the person's requests that day, rules and fairness numbers. Send assignmentId, or personId and date. Employees may ask only about themselves (personId defaults to the caller). Reads only; nothing is saved.","tags":["admin"],"security":[{"bearerAuth":[]},{"apiKey":[]}],"parameters":[{"name":"assignmentId","in":"query","schema":{"type":"string"}},{"name":"personId","in":"query","schema":{"type":"string","description":"Id or exact name"}},{"name":"date","in":"query","schema":{"type":"string","description":"YYYY-MM-DD"}},{"name":"shiftTypeId","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The answer first, then the facts behind it","content":{"application/json":{"schema":{"type":"object","required":["answer"],"properties":{"answer":{"type":"string","description":"One plain sentence to repeat to a person."}},"additionalProperties":true}}}},"304":{"description":"Not modified: If-None-Match matched the ETag"},"400":{"description":"Bad input, with a plain reason"},"403":{"description":"Admin only, or a question about someone else"},"404":{"description":"No such person, cell, issue or year"}}}},"/api/v1/explain/issue":{"get":{"operationId":"explainIssue","summary":"Explain one issue","description":"The rule in one sentence, expected vs actual numbers, who, when and why it matters. Admin only. Reads only; nothing is saved.","tags":["admin"],"security":[{"bearerAuth":[]},{"apiKey":[]}],"parameters":[{"name":"id","in":"query","schema":{"type":"string","description":"Issue id from /api/v1/hits"},"required":true},{"name":"month","in":"query","schema":{"type":"string","description":"YYYY-MM"}},{"name":"periodId","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The answer first, then the facts behind it","content":{"application/json":{"schema":{"type":"object","required":["answer"],"properties":{"answer":{"type":"string","description":"One plain sentence to repeat to a person."}},"additionalProperties":true}}}},"304":{"description":"Not modified: If-None-Match matched the ETag"},"400":{"description":"Bad input, with a plain reason"},"403":{"description":"Admin only, or a question about someone else"},"404":{"description":"No such person, cell, issue or year"}}}},"/api/v1/why-not":{"get":{"operationId":"whyNot","summary":"Why someone cannot work a date","description":"Blocking reasons in order (capacity, time off, requests, usual days, availability, qualifications, hours, consecutive days, rest), each with its rule and numbers, and whether an admin can override. Without personId (admins), everyone who can work that date. Employees may ask only about themselves (personId defaults to the caller). Reads only; nothing is saved.","tags":["admin"],"security":[{"bearerAuth":[]},{"apiKey":[]}],"parameters":[{"name":"date","in":"query","schema":{"type":"string","description":"YYYY-MM-DD"},"required":true},{"name":"personId","in":"query","schema":{"type":"string","description":"Id or exact name"}},{"name":"shiftTypeId","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The answer first, then the facts behind it","content":{"application/json":{"schema":{"type":"object","required":["answer"],"properties":{"answer":{"type":"string","description":"One plain sentence to repeat to a person."}},"additionalProperties":true}}}},"304":{"description":"Not modified: If-None-Match matched the ETag"},"400":{"description":"Bad input, with a plain reason"},"403":{"description":"Admin only, or a question about someone else"},"404":{"description":"No such person, cell, issue or year"}}}},"/api/v1/fixes":{"get":{"operationId":"suggestFixes","summary":"Ranked fixes for one issue","description":"Up to 5 fixes (reassign, swap, add, move or remove a shift), each checked with the same rules on a copy of the schedule: issues cleared and added, fairness change, people moved and the calls that apply it. Ranked by net issues cleared, then fairness, then fewest people moved. Bounded to about 2 seconds. Admin only. Reads only; nothing is saved.","tags":["admin"],"security":[{"bearerAuth":[]},{"apiKey":[]}],"parameters":[{"name":"id","in":"query","schema":{"type":"string","description":"Issue id"},"required":true},{"name":"limit","in":"query","schema":{"type":"integer","description":"1 to 5"}},{"name":"month","in":"query","schema":{"type":"string","description":"YYYY-MM"}},{"name":"periodId","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The answer first, then the facts behind it","content":{"application/json":{"schema":{"type":"object","required":["answer"],"properties":{"answer":{"type":"string","description":"One plain sentence to repeat to a person."}},"additionalProperties":true}}}},"304":{"description":"Not modified: If-None-Match matched the ETag"},"400":{"description":"Bad input, with a plain reason"},"403":{"description":"Admin only, or a question about someone else"},"404":{"description":"No such person, cell, issue or year"}}}},"/api/v1/coverage-gaps":{"get":{"operationId":"coverageGaps","summary":"Every short seat","description":"Each required seat nobody fills from from to to (default the next 14 days): shift seats, leads, supervisors and qualified minimums; call seats and rotation minimums in residency programs. Each gap has a key, how many are short, why (no-one-eligible, at-limits or not-assigned) in one sentence and who can cover. Publishing refuses with coverage_gap until each gap is filled or passed in overrides[]. Admin only. Reads only; nothing is saved.","tags":["admin"],"security":[{"bearerAuth":[]},{"apiKey":[]}],"parameters":[{"name":"from","in":"query","schema":{"type":"string","description":"YYYY-MM-DD, default today"}},{"name":"to","in":"query","schema":{"type":"string","description":"YYYY-MM-DD, default 13 days after from"}}],"responses":{"200":{"description":"The answer first, then the facts behind it","content":{"application/json":{"schema":{"type":"object","required":["answer"],"properties":{"answer":{"type":"string","description":"One plain sentence to repeat to a person."}},"additionalProperties":true}}}},"304":{"description":"Not modified: If-None-Match matched the ETag"},"400":{"description":"Bad input, with a plain reason"},"403":{"description":"Admin only, or a question about someone else"},"404":{"description":"No such person, cell, issue or year"}}}},"/api/v1/summary/schedule":{"get":{"operationId":"scheduleSummary","summary":"Compact schedule","description":"One character per person per day (weeks start Monday) with the legend once. Admin only. Reads only; nothing is saved.","tags":["admin"],"security":[{"bearerAuth":[]},{"apiKey":[]}],"parameters":[{"name":"month","in":"query","schema":{"type":"string","description":"YYYY-MM"}},{"name":"periodId","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The answer first, then the facts behind it","content":{"application/json":{"schema":{"type":"object","required":["answer"],"properties":{"answer":{"type":"string","description":"One plain sentence to repeat to a person."}},"additionalProperties":true}}}},"304":{"description":"Not modified: If-None-Match matched the ETag"},"400":{"description":"Bad input, with a plain reason"},"403":{"description":"Admin only, or a question about someone else"},"404":{"description":"No such person, cell, issue or year"}}}},"/api/v1/summary/issues":{"get":{"operationId":"issuesSummary","summary":"Issue counts","description":"Counts by rule and severity plus the top 10 issues with ids. Admin only. Reads only; nothing is saved.","tags":["admin"],"security":[{"bearerAuth":[]},{"apiKey":[]}],"parameters":[{"name":"month","in":"query","schema":{"type":"string","description":"YYYY-MM"}},{"name":"periodId","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The answer first, then the facts behind it","content":{"application/json":{"schema":{"type":"object","required":["answer"],"properties":{"answer":{"type":"string","description":"One plain sentence to repeat to a person."}},"additionalProperties":true}}}},"304":{"description":"Not modified: If-None-Match matched the ETag"},"400":{"description":"Bad input, with a plain reason"},"403":{"description":"Admin only, or a question about someone else"},"404":{"description":"No such person, cell, issue or year"}}}},"/api/v1/summary/person":{"get":{"operationId":"personSummary","summary":"One person's month in a few lines","description":"Shifts vs target, weekends, hours, requests and issues. Employees may ask only about themselves (personId defaults to the caller). Reads only; nothing is saved.","tags":["admin"],"security":[{"bearerAuth":[]},{"apiKey":[]}],"parameters":[{"name":"personId","in":"query","schema":{"type":"string","description":"Id or exact name"}},{"name":"month","in":"query","schema":{"type":"string","description":"YYYY-MM"}},{"name":"periodId","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The answer first, then the facts behind it","content":{"application/json":{"schema":{"type":"object","required":["answer"],"properties":{"answer":{"type":"string","description":"One plain sentence to repeat to a person."}},"additionalProperties":true}}}},"304":{"description":"Not modified: If-None-Match matched the ETag"},"400":{"description":"Bad input, with a plain reason"},"403":{"description":"Admin only, or a question about someone else"},"404":{"description":"No such person, cell, issue or year"}}}},"/api/v1/program/explain/cell":{"get":{"operationId":"explainProgramCell","summary":"Why a resident holds a rotation or a call day","description":"For a block: the generator's pass and reason, alternatives, or the admin edit and its date; requests; rules; fairness. For a date: the call day, how it was placed, counts vs target and class average. Employees may ask only about themselves (personId defaults to the caller). Reads only; nothing is saved.","tags":["program"],"security":[{"bearerAuth":[]},{"apiKey":[]}],"parameters":[{"name":"personId","in":"query","schema":{"type":"string","description":"Id or exact name"}},{"name":"blockIndex","in":"query","schema":{"type":"integer"}},{"name":"date","in":"query","schema":{"type":"string","description":"YYYY-MM-DD for a call day"}},{"name":"year","in":"query","schema":{"type":"integer","description":"Academic year (July start year); defaults to the current year"}}],"responses":{"200":{"description":"The answer first, then the facts behind it","content":{"application/json":{"schema":{"type":"object","required":["answer"],"properties":{"answer":{"type":"string","description":"One plain sentence to repeat to a person."}},"additionalProperties":true}}}},"304":{"description":"Not modified: If-None-Match matched the ETag"},"400":{"description":"Bad input, with a plain reason"},"403":{"description":"Admin only, or a question about someone else"},"404":{"description":"No such person, cell, issue or year"}}}},"/api/v1/program/explain/issue":{"get":{"operationId":"explainProgramIssue","summary":"Explain one residency issue","description":"The rule in one sentence with its evidence, expected vs actual numbers, who, the block or date, the cell decision and why it matters. Admin only. Reads only; nothing is saved.","tags":["program"],"security":[{"bearerAuth":[]},{"apiKey":[]}],"parameters":[{"name":"key","in":"query","schema":{"type":"string","description":"Issue key from /api/v1/program/hits"}},{"name":"ruleId","in":"query","schema":{"type":"string"}},{"name":"personId","in":"query","schema":{"type":"string"}},{"name":"blockIndex","in":"query","schema":{"type":"integer"}},{"name":"date","in":"query","schema":{"type":"string"}},{"name":"year","in":"query","schema":{"type":"integer","description":"Academic year (July start year); defaults to the current year"}}],"responses":{"200":{"description":"The answer first, then the facts behind it","content":{"application/json":{"schema":{"type":"object","required":["answer"],"properties":{"answer":{"type":"string","description":"One plain sentence to repeat to a person."}},"additionalProperties":true}}}},"304":{"description":"Not modified: If-None-Match matched the ETag"},"400":{"description":"Bad input, with a plain reason"},"403":{"description":"Admin only, or a question about someone else"},"404":{"description":"No such person, cell, issue or year"}}}},"/api/v1/program/why-not":{"get":{"operationId":"programWhyNot","summary":"Why a resident is not on a rotation, or cannot take call","description":"Rotation: send personId, blockIndex and rotationId (ids or names). Reasons come in order (class, pathway, prerequisites, capacity, leave, requests, then the rest), each with numbers; a reason caused by moving them off their current rotation says so. Call: send personId and date (kind is inferred). Without personId, who could take the rotation in that block. Employees may ask only about themselves (personId defaults to the caller). Reads only; nothing is saved.","tags":["program"],"security":[{"bearerAuth":[]},{"apiKey":[]}],"parameters":[{"name":"personId","in":"query","schema":{"type":"string"}},{"name":"blockIndex","in":"query","schema":{"type":"integer"}},{"name":"rotationId","in":"query","schema":{"type":"string"}},{"name":"date","in":"query","schema":{"type":"string"}},{"name":"kind","in":"query","schema":{"type":"string","enum":["short","weekend","holiday","baby"]}},{"name":"year","in":"query","schema":{"type":"integer","description":"Academic year (July start year); defaults to the current year"}}],"responses":{"200":{"description":"The answer first, then the facts behind it","content":{"application/json":{"schema":{"type":"object","required":["answer"],"properties":{"answer":{"type":"string","description":"One plain sentence to repeat to a person."}},"additionalProperties":true}}}},"304":{"description":"Not modified: If-None-Match matched the ETag"},"400":{"description":"Bad input, with a plain reason"},"403":{"description":"Admin only, or a question about someone else"},"404":{"description":"No such person, cell, issue or year"}}}},"/api/v1/program/fixes":{"get":{"operationId":"suggestProgramFixes","summary":"Ranked fixes for one residency issue","description":"Up to 5 fixes (trade blocks with another resident, swap within a block, move within the year, a different rotation, or call given to someone else), each rule-checked on a copy of the year, with issues cleared and added and the set_cell or call calls that apply it. Draft years only. Admin only. Reads only; nothing is saved.","tags":["program"],"security":[{"bearerAuth":[]},{"apiKey":[]}],"parameters":[{"name":"key","in":"query","schema":{"type":"string"}},{"name":"ruleId","in":"query","schema":{"type":"string"}},{"name":"personId","in":"query","schema":{"type":"string"}},{"name":"blockIndex","in":"query","schema":{"type":"integer"}},{"name":"date","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","description":"1 to 5"}},{"name":"year","in":"query","schema":{"type":"integer","description":"Academic year (July start year); defaults to the current year"}}],"responses":{"200":{"description":"The answer first, then the facts behind it","content":{"application/json":{"schema":{"type":"object","required":["answer"],"properties":{"answer":{"type":"string","description":"One plain sentence to repeat to a person."}},"additionalProperties":true}}}},"304":{"description":"Not modified: If-None-Match matched the ETag"},"400":{"description":"Bad input, with a plain reason"},"403":{"description":"Admin only, or a question about someone else"},"404":{"description":"No such person, cell, issue or year"}}}},"/api/v1/program/summary/schedule":{"get":{"operationId":"programScheduleSummary","summary":"Compact grid","description":"One code per resident per block, call counts and the legend once. Admin only. Reads only; nothing is saved.","tags":["program"],"security":[{"bearerAuth":[]},{"apiKey":[]}],"parameters":[{"name":"year","in":"query","schema":{"type":"integer","description":"Academic year (July start year); defaults to the current year"}}],"responses":{"200":{"description":"The answer first, then the facts behind it","content":{"application/json":{"schema":{"type":"object","required":["answer"],"properties":{"answer":{"type":"string","description":"One plain sentence to repeat to a person."}},"additionalProperties":true}}}},"304":{"description":"Not modified: If-None-Match matched the ETag"},"400":{"description":"Bad input, with a plain reason"},"403":{"description":"Admin only, or a question about someone else"},"404":{"description":"No such person, cell, issue or year"}}}},"/api/v1/program/summary/issues":{"get":{"operationId":"programIssuesSummary","summary":"Residency issue counts","description":"Counts by rule and severity plus the top 10 with keys. Admin only. Reads only; nothing is saved.","tags":["program"],"security":[{"bearerAuth":[]},{"apiKey":[]}],"parameters":[{"name":"year","in":"query","schema":{"type":"integer","description":"Academic year (July start year); defaults to the current year"}}],"responses":{"200":{"description":"The answer first, then the facts behind it","content":{"application/json":{"schema":{"type":"object","required":["answer"],"properties":{"answer":{"type":"string","description":"One plain sentence to repeat to a person."}},"additionalProperties":true}}}},"304":{"description":"Not modified: If-None-Match matched the ETag"},"400":{"description":"Bad input, with a plain reason"},"403":{"description":"Admin only, or a question about someone else"},"404":{"description":"No such person, cell, issue or year"}}}},"/api/v1/program/summary/person":{"get":{"operationId":"programPersonSummary","summary":"One resident's year in a few lines","description":"Block codes, call vs target, requests and issues. Employees may ask only about themselves (personId defaults to the caller). Reads only; nothing is saved.","tags":["program"],"security":[{"bearerAuth":[]},{"apiKey":[]}],"parameters":[{"name":"personId","in":"query","schema":{"type":"string"}},{"name":"year","in":"query","schema":{"type":"integer","description":"Academic year (July start year); defaults to the current year"}}],"responses":{"200":{"description":"The answer first, then the facts behind it","content":{"application/json":{"schema":{"type":"object","required":["answer"],"properties":{"answer":{"type":"string","description":"One plain sentence to repeat to a person."}},"additionalProperties":true}}}},"304":{"description":"Not modified: If-None-Match matched the ETag"},"400":{"description":"Bad input, with a plain reason"},"403":{"description":"Admin only, or a question about someone else"},"404":{"description":"No such person, cell, issue or year"}}}},"/api/v1/batch":{"post":{"summary":"Run up to 25 writes together against one revision: all saved or none. dryRun returns one plan for the whole list. Admin access; Read Only keys cannot batch.","operationId":"applyBatch","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["operations"],"properties":{"operations":{"type":"array","minItems":1,"maxItems":25,"items":{"$ref":"#/components/schemas/WriteOperation"}},"dryRun":{"type":"boolean"},"idempotencyKey":{"type":"string","maxLength":255}}}}}},"tags":["safe-writes"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/simulate":{"post":{"summary":"What If: run writes without saving or making a plan. Returns the changes, the issues added or cleared and metrics {coverage {from, to, unfilledBefore, unfilledAfter}, fairness [{personId, shifts, weekends, score}], call [{year, personId, calls}]}.","operationId":"simulateWrites","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["operations"],"properties":{"operations":{"type":"array","minItems":1,"maxItems":25,"items":{"$ref":"#/components/schemas/WriteOperation"}}}}}}},"tags":["safe-writes"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/plans/{id}/apply":{"post":{"summary":"Apply a dry run's plan once. 409 revision_conflict when the workplace changed since, 409 plan_used after the first apply, 410 plan_expired after 15 minutes.","operationId":"applyPlan","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}],"tags":["safe-writes"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}},"/api/v1/plans/{id}":{"get":{"summary":"Read one of your plans: its call, changes, issues, base revision, expiry and state (open, used or expired).","operationId":"getPlan","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"tags":["safe-writes"]}},"/api/v1/planning/options":{"get":{"summary":"Workplace planning roster identities and date presets","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["admin"],"x-scope":"own|admin"}},"/api/v1/planning/together":{"get":{"summary":"Shared whole days off: personIds comma-separated (2–8), from/to ISO dates (max 400 days), minDays (1–30). Published duties, jeopardy and recovery excluded; unknown schedules reported separately. No private leave details.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["admin"],"x-scope":"own|admin"}},"/api/v1/planning/remaining":{"get":{"summary":"Own unfinished published assigned duties grouped by kind within from/to ISO dates. In-progress duties included; drafts and target quotas excluded.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["admin"],"x-scope":"own"}},"/api/v1/program/rest-rules":{"get":{"summary":"Long duty recovery configuration, call clocks, and existing violations","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["program"],"x-scope":"admin"},"put":{"summary":"Set config {enabled,minimumDutyHours,restHours}, dutyTimes and slotTimes. Clocks accept endDayOffset 0 or 1 for duties over 24 hours. Default inactive; existing violations remain for review.","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/shift-presets":{"get":{"summary":"Editable duty presets and whether they can be applied","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["admin"],"x-scope":"admin"}},"/api/v1/shift-presets/{id}/apply":{"post":{"summary":"Apply a preset before any assignments exist; body {includeWeekendCall?: boolean}","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}],"tags":["admin"],"x-scope":"admin"}},"/api/v1/holidays":{"post":{"summary":"Add a custom annual or one-time holiday event","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["admin"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]},"get":{"summary":"Working holiday catalog and fairness policy","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["admin"],"x-scope":"own|admin"}},"/api/v1/holidays/{id}":{"patch":{"summary":"Edit a custom holiday event without changing work assignments","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}],"tags":["admin"],"x-scope":"admin"},"delete":{"summary":"Remove a custom event from future tracking, preserving past credit","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}],"tags":["admin"],"x-scope":"admin"}},"/api/v1/me/holiday-preferences":{"get":{"summary":"Own holiday rankings by year: {holidays, ranks (effective ranks for editableYear), editableYear, lockDate (YYYY-MM-DD or null), years:[{year,ranks,locked,lockDate?}], note}. Add month=YYYY-MM for month:[{id,name,date,closed,rank}], that month's holidays including closed ones. The ranking for year Y locks on the workplace holidayRankLock date (default 12-01) of year Y-1","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["me"],"x-scope":"own"},"patch":{"summary":"Save {ranks:{holidayId:rank}, year?}; year defaults to editableYear. Rank 1 is most valued off and unranked is neutral. A locked year returns 409 {error}. Returns {ranks, year}","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["me"],"x-scope":"own","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/people/{id}/holiday-ranking":{"patch":{"summary":"Reopen one person's locked holiday ranking for one save with {reopenYear: current or next year}. Cancel with {reopenYear:null}","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}],"tags":["admin"],"x-scope":"admin"}},"/api/v1/holiday-fairness":{"get":{"summary":"Holiday assignment history and eligible workload shares across years","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["admin"],"x-scope":"admin"}},"/api/v1/setup-checks":{"get":{"summary":"Live setup checks for hours, people, shift leads, and schedule conflicts","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["admin"],"x-scope":"admin"}},"/api/v1/coworkers":{"get":{"summary":"Active coworkers available for shift coverage or trades: {id, name, avatarUrl}; avatarUrl is a signed profile picture link (one hour) or null","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["admin"],"x-scope":"own"}},"/api/v1/team/schedule":{"get":{"summary":"Published team schedule; filter with month=YYYY-MM. Rows carry person.id; days carry location {id, name} or null","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["admin"],"x-scope":"own|admin"}},"/api/v1/me/preferences":{"get":{"summary":"Own notes","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["me"],"x-scope":"own"},"patch":{"summary":"Save own notes with {notes}","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["me"],"x-scope":"own","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/feature-requests":{"post":{"summary":"Send an idea for Schedultary (body: text, up to 2,000 characters); admins see them on the Suggest page","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["admin"],"x-scope":"own","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/me/cant-make-it":{"post":{"summary":"Tell the managers you cannot work a date (body: date, optional note); notifies admins in the app and by email, files no request","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["me"],"x-scope":"own","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/me/swaps/{id}/cancel":{"post":{"summary":"Cancel a swap you asked for while it is still open","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}],"tags":["me"],"x-scope":"own"}},"/api/v1/coverage-gaps/offer":{"post":{"summary":"Offer to These People: post one short shift {key, personIds?} as an open shift only they can take; without personIds, the top 3 who can cover","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["admin"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/qgenda/link":{"get":{"summary":"QGenda mappings: {link: {tasks (rotation id or va, nights, ir: {am?, pm?, full?, days: weekdays|all}), calls (short-first ... baby-pm: task), staff (person id: staff name), layout?, taskAliases?, viewUrl?, companyKey?}, callKeys, taskFamilies, rotations}. A task or staff entry is {name, key?, abbreviation?}","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["program"],"x-scope":"admin"},"patch":{"summary":"Change QGenda mappings {tasks?, calls?, staff?, layout?, taskAliases?, viewUrl?, companyKey?}. Each section merges; an entry set to null is removed. A task or staff entry is a QGenda name (\"Body CT AM\" or \"Body CT AM [key]\") or {name, key?, abbreviation?}; a saved key stays while the name is unchanged. Use am and pm tasks or one full-day task. Unknown rotations, people or call keys save nothing. Returns {link}","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/qgenda/feed":{"get":{"summary":"QGenda calendar feed: {feedUrl, viewUrl, syncedAt, recordedDays (days recorded from QGenda)}","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["program"],"x-scope":"admin"},"put":{"summary":"Save the QGenda calendar feed {feedUrl}: a webcal:// or https:// iCalendar link on qgenda.com or ending in .ics; null removes it. A public board link is refused: import it with POST /program/qgenda","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/qgenda/feed/import":{"post":{"summary":"Import the QGenda calendar feed {url? (the saved feedUrl when left out)}: vacation, admin days and parental leave become recorded days matched to people by name; parental leave of 7 days or more is placed on the grid. Returns {events, added, unmatched, feedUrl}. Rate limited with the board import","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/progress":{"get":{"summary":"Training progress toward each requirement for every resident; ?year=","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["program"],"x-scope":"admin"}},"/api/v1/program/fairness":{"get":{"summary":"Call and rotation fairness by class; ?year=","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["program"],"x-scope":"admin"}},"/api/v1/program/classes":{"get":{"summary":"Classes (levels), the program's pathways and each resident's class, pathway and start year; ?year=","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["program"],"x-scope":"admin"}},"/api/v1/program/pathways":{"get":{"summary":"The program's pathways [{id, label, base}] and saved (false: read from what residents and rules use). The base pathway is where new residents start; its filter shows everyone off the other pathways","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["program"],"x-scope":"admin"},"put":{"summary":"Replace the pathway list {pathways: [{id (1 to 12 letters, digits, - or _), label?, base?, withBase? (a track inside the base whose residents also show under the base filter)}]}, one base (the first when none is marked). Refused when it drops a pathway a resident or rule still uses","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/review":{"get":{"summary":"Year review: rule hits and the decisions recorded for them; ?year=","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["program"],"x-scope":"admin"}},"/api/v1/program/signups":{"get":{"summary":"Call Sign-Up (rotation call residents pick themselves, such as Peds short call), ?year=. Admins: the rules and one row per block sheet with state (upcoming, open, closed), opensOn, dueOn, members, short, picked and required, plus stale when the sheets need an update. Residents: their own sheets with required, picked and each day's open seats. ?view=mine: an admin's own resident view","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["program"],"x-scope":"own|admin"}},"/api/v1/program/signups/board":{"get":{"summary":"One block's sign-up board ?ruleId=&blockIndex=&year=: every day with who has it and open seats, every resident on the rotation with required, picked and short","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["program"],"x-scope":"admin"}},"/api/v1/program/signups/swaps":{"post":{"summary":"Ask another resident on the rotation to take one of your sign-up days {ruleId, date, targetPersonId, withDate? (one of their days to trade), reason?, year?}. They answer it like any swap; an admin approves it","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"own","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/signups/assign":{"post":{"summary":"Place or move a resident's sign-up day {personId, ruleId, date, from? (the day it moves from), override? (true puts a second person on a full day and marks that Must Fix as OK), year?}. Admins may change a closed sheet","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]},"delete":{"summary":"Remove a resident's sign-up day {personId, ruleId, date, year?}","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/signups/lock":{"post":{"summary":"Lock or unlock a picked sign-up day {personId, ruleId, date, locked, year?}; a locked day cannot be changed by the resident","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/signups/fill":{"post":{"summary":"Fill Automatically {ruleId, blockIndex, year?}: place the days residents did not pick, spread evenly, never on their call days, approved time off or a day that breaks rest rules; returns placed, unfilled and a summary","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/signups/remind":{"post":{"summary":"Email every resident on the block's sheet who has not picked all their days {ruleId, blockIndex, year?}, and text those who turned texts on. Sends real email and texts that cannot be undone: call only when the admin explicitly asks to remind","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/signups/sheets":{"post":{"summary":"Update every sign-up sheet from the rules (after a rule, holiday or calendar change); picked days stay","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/signups/rules":{"put":{"summary":"Replace the Call Sign-Up rules {rules: [{id?, name, code?, rotationIds, days? (0 Sunday..6 Saturday, default Monday to Friday), skipHolidays? (true), seatsPerDay? (1), daysPerBlock? (4), start? (12:00), end? (21:00), replacesWorkday? (true), opensDaysBefore? (21), dueDaysBefore? (7), countsTowardCall? (false), sameDayCall? (avoid|allow), sendMessages? (true), enabled?}]} and update the sheets. An empty list removes every sign-up","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/moonlighting/windows/defaults":{"post":{"summary":"Review default clocks and zone {expectedStoreRevision, window: {opensTime, closesTime, timeZone}, confirm?}. Null clears a clock or inherits the workplace zone. Use dryRun then plan apply. Preview includes configured years and custom periods; zone changes reproject custom clocks. Fixed batches and assignments stay. Ambiguous or missing clocks are refused","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/year/open":{"post":{"summary":"Open a planning year {year}, creating it from the year before when missing","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/publish":{"post":{"summary":"Publish a year {year}. 400 with reasons when it is not ready (empty cells, Must Fix issues, missing call)","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/residents":{"post":{"summary":"Add a resident to any class {name, email?, phone?, class? (a class id or label from get_program_classes, or incoming), pathway? (a pathway id from get_program_pathways; the base pathway when left out), startYear? (instead of class; default next year), activeFrom?, shownOnly?, notes?}. With personId instead of name, places someone already on the roster (staff with no class) in the class. The class is read in the academic year holding today","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/residents/list":{"post":{"summary":"A pasted class list {text, apply?}: one person per line, name, email, class and pathway by comma or tab. Without apply returns the checked rows (class, pathway, the roster entry an email places, and errors); apply:true adds every row without an error and returns added, ids, skipped and errors","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/residents/{id}":{"get":{"summary":"One resident's program profile: class this year, start year, pathway, notes, active dates, shownOnly (on the schedule for reference only: never generated, never on call, never tracked, cells still take room seats), linked (has an account), and carry-overs, early pulls, leaves and rule exceptions with their index, and imported callTallies","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"tags":["program"],"x-scope":"admin"},"patch":{"summary":"Move a resident {class? (class id or label, or incoming; read in the academic year holding today), startYear? (instead of class), pathway?, shownOnly? (true or false; see GET /api/v1/program/residents/:id), notes? (up to 500 characters)}. Schedule history stays where it is","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}],"tags":["program"],"x-scope":"admin"}},"/api/v1/program/residents/{id}/carry-overs":{"post":{"summary":"Record a rotation carried over {rotationId (id or name), fromYear (one of the resident's academic years), blocks? (standard blocks owed, default 1), dueLevelId? (class it is due by), note?}. Next year's plan shows it as a reminder until placed","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}],"tags":["program"],"x-scope":"admin"},"delete":{"summary":"Remove a carry-over {index} (from get_program_residents_by_id)","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}],"tags":["program"],"x-scope":"admin"}},"/api/v1/program/residents/{id}/early-pulls":{"post":{"summary":"Record a rotation done ahead of its class {rotationId (id or name), year (one of the resident's academic years), blockIndex, note?}","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}],"tags":["program"],"x-scope":"admin"},"delete":{"summary":"Remove an early pull {index} (from get_program_residents_by_id)","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}],"tags":["program"],"x-scope":"admin"}},"/api/v1/program/residents/{id}/leaves":{"post":{"summary":"Record leave on file {kind: parental|vacation|absence, start, end (YYYY-MM-DD), note?}. Generate and call keep the resident off those dates; no grid cell changes (POST /program/leave also places it on the grid)","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}],"tags":["program"],"x-scope":"admin"},"delete":{"summary":"Remove leave on file {index} (from get_program_residents_by_id). Grid cells already placed stay","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}],"tags":["program"],"x-scope":"admin"}},"/api/v1/program/residents/{id}/exceptions":{"post":{"summary":"Record a rule exception {ruleId (a program rule, cap-<rotation id>, or an issue's rule id), scope? (text the issue key holds, such as a year; empty means every issue of that rule), reason}. Matching issues for this resident read as accepted","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}],"tags":["program"],"x-scope":"admin"},"delete":{"summary":"Remove a rule exception {index} (from get_program_residents_by_id)","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}],"tags":["program"],"x-scope":"admin"}},"/api/v1/program/residents/{id}/call-tallies":{"put":{"summary":"Set an imported call tally for one of the resident's years {year, short?, weekend?, holiday?, baby? (counts, default 0), majorHolidaysWorked? (names from the program's major holidays)}. Replaces that year's tally","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}],"tags":["program"],"x-scope":"admin"},"delete":{"summary":"Remove an imported call tally {year}","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}],"tags":["program"],"x-scope":"admin"}},"/api/v1/program/residents/{id}/link":{"post":{"summary":"Link to Roster Entry {personId}: fold a staff entry made when someone joined by invite with an email the roster did not have into this resident. The resident takes the account and email, records naming the staff entry move to the resident, and the staff entry leaves the roster. Refused when the resident is linked to another account or the two have different access","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}],"tags":["program"],"x-scope":"admin"}},"/api/v1/program/moonlighting/sheet-sync":{"get":{"summary":"Spreadsheet sync for moonlighting: whether a Google Sheet is connected, its settings, conflicts, history, version and the schedule revision. Needs a signed-in workplace (503 without one)","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["program"],"x-scope":"admin"},"post":{"summary":"Run one spreadsheet sync action {action, version (from GET)}: preview {config}; configure {config, revision, fingerprint (from preview)}; disconnect {revision}; sync; app or sheet {key} to settle one conflict; pause, resume or rollback {historyId?}. Returns the sync view; 409 when the version is stale","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/program/qgenda-sync":{"get":{"summary":"QGenda sync review: connected board, sources, coverage rotations, differences to decide and version. Needs a signed-in workplace (503 without one)","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["program"],"x-scope":"admin"},"post":{"summary":"One QGenda sync action: connect {year, url}; add-source {version, label, url}; coverage-rotations {version, url, rotationIds}; sync {version}; app or source {version, key, stamp} to settle a difference; pause or resume {version}. Returns the sync view; 409 when the version is stale","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."}]}},"/api/v1/program/structure":{"get":{"summary":"Program name, specialty, classes (levels: id, label, order), calendar (mode, anchorMonth, anchorDay, weekday, blockCount, blockWeeks, blockWeeksList, specialBlocks), usual hours (workday), wording, xyPattern, yearlyRequestWindow and seriesOrder (Numbered Series Order: hard Must Follow, soft Prefer, off)","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["program"],"x-scope":"admin"},"patch":{"summary":"Change {name?, specialty?, levels? (every class in order: id, label, order 1..n), calendar? (the whole calendar: anchorMonth, anchorDay, weekday, blockCount, blockWeeks, blockWeeksList?, specialBlocks [{blockIndex, kind physics|study|other, label}], mode?), workday? ({start, end, days, byDay?, holiday?} or null), seriesOrder? (hard default, soft, off)}. Class order and dates lock once people or schedules exist","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/windows":{"get":{"summary":"Fixed-date windows: the calendar dates a Fixed Calendar Dates rotation (AIRP) runs each academic year; ?rotationId=&year=. Also lists the fixed-date rotations","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["program"],"x-scope":"admin"},"post":{"summary":"Add a fixed-date window {rotationId (a rotation with fixedDates), year (academic start year; a year not opened yet works too, and Generate uses its windows once it is opened), start, end (YYYY-MM-DD, both inside that academic year)}. Returns 201 {window} with its id. Without windows in a year the rotation is not scheduled that year","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/windows/{id}":{"delete":{"summary":"Remove a fixed-date window","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}],"tags":["program"],"x-scope":"admin"}},"/api/v1/program/years/past":{"post":{"summary":"Add a past academic year {year} (up to ten years back, not on file) as a record-only history year, then fill it with PUT /program/grid {year, cells} and POST /program/call {calls} with ?year=. Never generated","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/request-window":{"put":{"summary":"Set a year's request window {year, open, close} (YYYY-MM-DD), or {year, action: close|reopen}. {action: yearly, open, close} (MM-DD) sets Every Year, which also opens the year being planned; {action: yearly, open: null} turns it off","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/wording":{"put":{"summary":"Set the program's words {wording: {person, people, level, levels, rotation, rotations, block, blocks, call, program, extraShifts}}, each up to 24 characters. Blank or missing words read the defaults; an empty object restores them all","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/xy-pattern":{"put":{"summary":"Set alternating inpatient and clinic weeks {pattern: {anchorDate (on the calendar's start weekday), inpatientWeeks, ambulatoryWeeks (1..26), ambulatoryRotationIds, protectCall?}, offsets? {residentId: weeks}}. The first save converts an empty draft program to X+Y blocks","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/request-period":{"get":{"summary":"Resident Request Periods: the dates residents send requests for (start, end), when requests open and are due (open, due), status (open, upcoming, closed) and the classes it is for (levelIds, empty for every class), plus the planning year's dates as the default. Residency requests are checked against these periods only; a resident request is refused when its dates fall outside every period or today is before open or after due. Admins may add or change any request","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"tags":["program"],"x-scope":"own|admin"},"put":{"summary":"Set a Resident Request Period {id? (to change one; leave out to add), start, end (the dates requests are for), open, due (YYYY-MM-DD), levelIds? (class ids from get_program, leave out for every class)}, or close or reopen one {id, action: close|reopen}","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/rules/leave":{"patch":{"summary":"Leave settings {vacationDays?, parentalLeaveWeeks?, carryOverLevelId?}","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}},"/api/v1/program/comments":{"get":{"summary":"Comments on the program, newest first; ?open=true for unresolved, ?year=","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"}},"description":"Admin access, taken from the person's account.","tags":["program"],"x-scope":"admin"}},"/api/v1/program/comments/{id}/reply":{"post":{"summary":"Reply to a comment {text}","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}],"tags":["program"],"x-scope":"admin"}},"/api/v1/program/comments/{id}/resolve":{"post":{"summary":"Resolve a comment {resolved?: false to reopen}","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}],"tags":["program"],"x-scope":"admin"}},"/api/v1/program/acknowledgements":{"post":{"summary":"Mark an issue as OK {year?, key (from hits), reason}","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]},"delete":{"summary":"Undo Marked as OK for an issue {year?, key}","security":[{"bearerAuth":[]},{"apiKey":[]}],"responses":{"200":{"description":"OK"},"201":{"description":"Created"},"400":{"description":"validation_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown token"},"403":{"description":"Employee token on an admin route"},"404":{"description":"Not found"},"409":{"description":"revision_conflict, publication_conflict, conflict, plan_used or idempotency_in_progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"dry_run_unsupported or idempotency_key_reused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"description":"Admin access, taken from the person's account.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The fields the summary names."}}}},"tags":["program"],"x-scope":"admin","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255},"description":"Runs this write once per caller and key for 24 hours; a repeat returns the first answer."},{"name":"dryRun","in":"query","required":false,"schema":{"type":"boolean"},"description":"Save nothing; answer with the changes, the issues added or cleared and a planId for POST /api/v1/plans/{planId}/apply."}]}}},"components":{"schemas":{"RuleInput":{"type":"object","description":"A residency rule. type picks the list; gates also need kind. Every field is optional on PATCH.","properties":{"type":{"type":"string","enum":["requirement","total","gate"]},"kind":{"type":"string","enum":["before","notInBlocks","window","beforeFirstCall","maxOfGroupPerBlock","minGap","maxPerYear"]},"id":{"type":"string"},"label":{"type":"string"},"evidence":{"type":"string"},"severity":{"type":"string","enum":["hard","soft"]},"enabled":{"type":"boolean"},"fromYear":{"type":["integer","null"],"description":"First academic year the rule applies"},"untilYear":{"type":["integer","null"],"description":"First academic year it no longer applies"},"levelId":{"type":"string"},"levelIds":{"type":["array","null"],"items":{"type":"string"}},"rotationIds":{"type":"array","items":{"type":"string"}},"count":{"type":"integer","minimum":0},"pathway":{"type":["string","null"]},"scope":{"type":["string","null"],"enum":["year","cumulative",null]},"fromCohort":{"type":["integer","null"]},"byLevelId":{"type":["string","null"]},"perCohort":{"type":"object","additionalProperties":{"type":"integer"},"description":"total: a count by start year that replaces count for that class"},"atLeast":{"type":"boolean","description":"total: extra blocks are allowed"},"softForPathways":{"type":["array","null"],"items":{"type":"string"},"description":"total: pathways that only get Worth a Look when short (up to 12 saved pathway ids); [] or null for none"},"softNote":{"type":["string","null"],"description":"total: shown after the pathway name"},"x":{"type":"string","description":"before: the rotation that comes first"},"y":{"type":"string","description":"before: the rotation that follows"},"blocks":{"description":"notInBlocks: block numbers; minGap: blocks apart","oneOf":[{"type":"array","items":{"type":"integer"}},{"type":"integer"}]},"fromBlock":{"type":"integer"},"toBlock":{"type":"integer"},"max":{"type":"integer","description":"99 means no cap"},"min":{"type":["integer","null"]},"exceptBlocks":{"type":["array","null"],"items":{"type":"integer"}}}},"MoonlightBoard":{"type":"object","description":"A resident's moonlighting. Dates are YYYY-MM-DD, pay is in cents (null when not set).","properties":{"configured":{"type":"boolean"},"notices":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"opensOn":{"type":"string"},"closesOn":{"type":"string"},"names":{"type":"array","items":{"type":"string"}},"request":{"type":"boolean"},"text":{"type":"string"}}}},"open":{"type":"array","items":{"type":"object","properties":{"typeId":{"type":"string"},"name":{"type":"string"},"code":{"type":"string"},"date":{"type":"string"},"time":{"type":"string"},"hours":{"type":"number"},"payCents":{"type":["integer","null"]},"duties":{"type":"string"},"note":{"type":"string"},"phase":{"type":"string","enum":["open","requests"]},"seatsLeft":{"type":"integer"},"requested":{"type":"boolean"},"closesOn":{"type":"string"},"warnings":{"type":"array","items":{"type":"string"}}}}},"released":{"type":"array","items":{"type":"object","properties":{"shiftId":{"type":"string"},"typeId":{"type":"string"},"name":{"type":"string"},"date":{"type":"string"},"time":{"type":"string"},"payCents":{"type":["integer","null"]},"ownerName":{"type":"string"},"stage":{"type":"string","enum":["class","everyone"]},"openToAllAt":{"type":["string","null"]},"canTake":{"type":"boolean"},"reason":{"type":"string"},"warnings":{"type":"array","items":{"type":"string"}}}}},"mine":{"type":"array","items":{"type":"object","properties":{"shiftId":{"type":"string"},"typeId":{"type":"string"},"name":{"type":"string"},"date":{"type":"string"},"time":{"type":"string"},"hours":{"type":"number"},"payCents":{"type":["integer","null"]},"status":{"type":"string","enum":["on","requested"]},"releasedAt":{"type":"string"},"overtimeMinutes":{"type":"integer"},"canCancel":{"type":"boolean"},"canRelease":{"type":"boolean"},"canLogOvertime":{"type":"boolean"}}}},"upcoming":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"opensOn":{"type":"string"},"closesOn":{"type":"string"},"names":{"type":"array","items":{"type":"string"}}}}},"totals":{"type":"object","properties":{"year":{"type":"integer"},"months":{"type":"array","items":{"type":"object"}},"total":{"type":"object","properties":{"shifts":{"type":"integer"},"hours":{"type":"number"},"payCents":{"type":"integer"},"unpaid":{"type":"integer"},"overtimeMinutes":{"type":"integer"},"overtimePayCents":{"type":"integer"}}}}},"jeopardy":{"type":"array","items":{"type":"object","properties":{"pool":{"type":"string"},"start":{"type":"string"},"end":{"type":"string"}}}}}},"ProgramRequestInput":{"type":"object","required":["kind"],"properties":{"kind":{"type":"string","enum":["prefer","avoid","vacation","with","electives"]},"note":{"type":"string","maxLength":4000,"description":"Required except for electives"},"rotationId":{"type":"string"},"withPersonId":{"type":"string"},"blockIndex":{"type":"integer","minimum":1},"startDate":{"type":"string","format":"date"},"endDate":{"type":"string","format":"date"},"windowStart":{"type":"string","format":"date","description":"Flexible vacation: first day of the window"},"windowEnd":{"type":"string","format":"date","description":"Flexible vacation: last day of the window"},"windowDays":{"type":"integer","minimum":1,"maximum":60,"description":"Flexible vacation: weekdays wanted inside the window"},"electivePlan":{"type":"object","required":["sectionId","electiveIds"],"properties":{"sectionId":{"type":"string"},"fellowship":{"type":["object","null"],"required":["rotationId","blocks"],"properties":{"rotationId":{"type":"string"},"blocks":{"type":"integer","minimum":1}}},"electiveIds":{"type":"array","items":{"type":"string"},"description":"Rank order, one block each; an id may repeat"}}}}},"RequestSection":{"type":"object","description":"Extra choices on the request form for one class. Every field is optional on PATCH.","properties":{"id":{"type":"string"},"forLevelId":{"type":"string"},"title":{"type":"string","maxLength":80},"intro":{"type":["string","null"],"maxLength":240},"kind":{"type":"string","enum":["electives"]},"options":{"type":"object","properties":{"fellowships":{"type":"array","items":{"type":"object","required":["rotationId"],"properties":{"rotationId":{"type":"string"},"label":{"type":"string"}}}},"fellowshipBlocks":{"type":"object","properties":{"initial":{"type":"integer"},"min":{"type":"integer"},"max":{"type":"integer"}}},"electiveIds":{"type":"array","items":{"type":"string"}}}}}},"Workday":{"type":["object","null"],"required":["start","end","days"],"additionalProperties":false,"description":"Usual hours for work hour logs (HH:MM, local). An end at or before the start ends the next day. null goes back to the default.","properties":{"start":{"type":"string","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$"},"end":{"type":"string","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$"},"days":{"type":"array","items":{"type":"integer","minimum":0,"maximum":6}},"byDay":{"type":"object","description":"Clocks for single weekdays (keys 0 Sunday to 6 Saturday), such as an earlier weekend start","additionalProperties":{"type":"object","required":["start","end"],"properties":{"start":{"type":"string"},"end":{"type":"string"}}}},"holiday":{"type":"object","required":["start","end"],"properties":{"start":{"type":"string"},"end":{"type":"string"}},"description":"Clocks worked on program holidays"}}},"CallSettingsPatch":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"},"noCall":{"type":"boolean","description":"true confirms the program takes no call"},"perDay":{"type":"integer"},"timeZone":{"type":"string","description":"IANA time zone; must match the workplace time zone"},"weekdaySpread":{"type":["object","null"],"additionalProperties":false,"properties":{"kinds":{"type":"array","items":{"type":"string","enum":["short","weekend","holiday","baby"]}},"levelIds":{"type":"array","items":{"type":"string"}}},"description":"Spread these call kinds across Monday to Friday for these classes; null clears"},"weekendSeats":{"type":"string","enum":["pair","single-day"]},"holidayWeekendTrade":{"type":"boolean"},"weekend":{"type":["object","null"],"description":"{split:{lower,upper},full:{first,second,firstStartBlockByLevel?}}. Seat lists contain class IDs in preference order; null removes class restrictions. Empty full.first uses split call all year. Empty full.second omits the supervisor during split weekends; other empty lists use general eligibility."},"holidays":{"type":"object","required":["major","minor"],"additionalProperties":false,"properties":{"major":{"type":"array","items":{"type":"string"}},"minor":{"type":"array","items":{"type":"string"}},"equalWeight":{"type":"boolean"}}},"oneMajorPerResidency":{"type":"string","enum":["hard","soft","off"]},"weekday":{"type":["object","null"],"description":"{lower,upper} class ids for 1st and 2nd call, or halves {switchMonthDay MM-DD, flexDays, before {first,second}, after {first,second}}; halves null keeps one pairing all year"},"maxHolidaysPerYear":{"type":["integer","null"]},"maxHolidaysFromYear":{"type":["integer","null"]},"targetsByLevel":{"type":"object","additionalProperties":{"type":"object","properties":{"short":{"type":"integer"},"weekend":{"type":"integer"},"holiday":{"type":"integer"},"baby":{"type":"integer"}}}},"startBlockByLevel":{"type":"object","additionalProperties":{"type":"integer"}},"spacing":{"type":"object"},"dutyTimes":{"type":"object"},"slotTimes":{"type":["object","null"],"description":"Clocks for one half of a split call day: {\"first-am\"?: {start, end} | null, \"first-pm\"?: {start, end} | null} (HH:MM). A call day with that slot uses them instead of dutyTimes for its kind; null clears"},"limits":{"type":["object","null"],"description":"{severity, maxCallsInDays {calls,days}, minDaysBetweenCalls, dayOffInSeven strict|averaged}"},"babyWeekend":{"type":["object","null"],"description":"{firstCallHalves (always 2), secondCall (0 or 1: the all-day second call on split weekends), amLevelId, pmLevelId, fromMonthDay, untilMonthDay, season}. season: {start, end, skipHolidayWeekends, reason} or null; start and end are {month, weekend} (weekend 1 to 5, -1 last) or {holiday, weekendsBefore}, inclusive, and replace fromMonthDay and untilMonthDay. skipHolidayWeekends: no baby call on a weekend next to a program or workplace holiday."}}},"DraftRepairPreview":{"type":"object","required":["year","sourceToken","rotationChanges","callChanges","before","after","attempts","limited","notes"],"description":"Opaque round-trip preview returned by POST /program/repair with action preview. Preserve every field when applying; sourceToken detects stale inputs and is not an authorization credential.","properties":{"year":{"type":"integer"},"sourceToken":{"type":"string"},"rotationChanges":{"type":"array","items":{"type":"object","required":["personId","blockIndex","before","after","reason"]}},"callChanges":{"type":"array","items":{"type":"object","required":["before","after","reason"]}},"before":{"type":"object","description":"Publication readiness before repair, including ready, emptyCells, hardHits, missingCallDays and reasons."},"after":{"type":"object","description":"Publication readiness after the proposed repair; ready may remain false."},"attempts":{"type":"integer","minimum":0},"limited":{"type":"boolean"},"notes":{"type":"array","items":{"type":"string"}}}},"Error":{"type":"object","required":["error","code"],"properties":{"error":{"type":"string","description":"One plain sentence to pass on to a person."},"code":{"type":"string","enum":["validation_failed","unauthorized","forbidden","not_found","method_not_allowed","conflict","revision_conflict","publication_conflict","coverage_gap","hard_rule_blocked","plan_used","plan_expired","idempotency_key_reused","idempotency_in_progress","dry_run_unsupported","too_large","rate_limited","unavailable","internal_error"]},"hint":{"type":"string","description":"What to do next."},"field":{"type":"string","description":"The input at fault, such as personId or operations[1].path."}}},"WriteOperation":{"type":"object","required":["method","path"],"properties":{"method":{"type":"string","enum":["POST","PUT","PATCH","DELETE"]},"path":{"type":"string","description":"A write route from the catalog, such as /api/v1/assignments or /api/v1/program/grid"},"body":{"type":"object","additionalProperties":true},"query":{"type":"object","additionalProperties":{"type":["string","number","boolean"]}}}},"DryRun":{"type":"object","required":["dryRun","planId","expiresAt","baseRevision","changes","issues"],"properties":{"dryRun":{"const":true},"planId":{"type":"string","format":"uuid"},"expiresAt":{"type":"string","format":"date-time"},"baseRevision":{"type":"string"},"changes":{"type":"array","items":{"type":"string"},"examples":[["Moved Day on 2027-01-05 from Jordan Lee to Casey Park.","2027-28 Block 4, Avery Quinn: Body to Chest."]]},"changeCount":{"type":"integer"},"issues":{"type":"object","properties":{"added":{"type":"array","items":{"type":"object"}},"cleared":{"type":"array","items":{"type":"object"}},"mustFix":{"type":"object"},"worthALook":{"type":"object"}}},"result":{"description":"What the route would have answered."}}}},"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key (sk_live_...) from Settings > API Keys, or a signed-in session access token."},"apiKey":{"type":"apiKey","in":"header","name":"x-api-key","description":"An API key (sk_live_...) from Settings > API Keys."}}},"x-route-catalog":[{"method":"GET","path":"/api/v1/duty-timeline","auth":"bearer","scope":"admin","summary":"Review combined shift and residency duties with overlap issues and elapsed hours. Query start/end ISO dates (at most 32 days), scenario published or all (includes drafts), optional personId. Read-only review; does not certify coverage or recovery."},{"method":"GET","path":"/api/v1/planning/options","auth":"bearer","scope":"own|admin","summary":"Workplace planning roster identities and date presets"},{"method":"GET","path":"/api/v1/planning/together","auth":"bearer","scope":"own|admin","summary":"Shared whole days off: personIds comma-separated (2–8), from/to ISO dates (max 400 days), minDays (1–30). Published duties, jeopardy and recovery excluded; unknown schedules reported separately. No private leave details."},{"method":"GET","path":"/api/v1/planning/remaining","auth":"bearer","scope":"own","summary":"Own unfinished published assigned duties grouped by kind within from/to ISO dates. In-progress duties included; drafts and target quotas excluded."},{"method":"GET","path":"/api/v1/program/rest-rules","auth":"bearer","scope":"admin","summary":"Long duty recovery configuration, call clocks, and existing violations"},{"method":"PUT","path":"/api/v1/program/rest-rules","auth":"bearer","scope":"admin","summary":"Set config {enabled,minimumDutyHours,restHours}, dutyTimes and slotTimes. Clocks accept endDayOffset 0 or 1 for duties over 24 hours. Default inactive; existing violations remain for review."},{"method":"GET","path":"/api/v1/schedule-templates","auth":"bearer","scope":"admin","summary":"Private workplace templates and editable starters; excludes people and assignment history"},{"method":"POST","path":"/api/v1/schedule-templates","auth":"bearer","scope":"admin","summary":"Save current configuration, preview a template, or apply a reviewed template before assignments exist: {action: save|preview|apply,name?,id?,preview?,expectedConfiguration?}"},{"method":"GET","path":"/api/v1/shift-presets","auth":"bearer","scope":"admin","summary":"Editable duty presets and whether they can be applied"},{"method":"POST","path":"/api/v1/shift-presets/:id/apply","auth":"bearer","scope":"admin","summary":"Apply a preset before any assignments exist; body {includeWeekendCall?: boolean}"},{"method":"POST","path":"/api/v1/holidays","auth":"bearer","scope":"admin","summary":"Add a custom annual or one-time holiday event"},{"method":"PATCH","path":"/api/v1/holidays/:id","auth":"bearer","scope":"admin","summary":"Edit a custom holiday event without changing work assignments"},{"method":"DELETE","path":"/api/v1/holidays/:id","auth":"bearer","scope":"admin","summary":"Remove a custom event from future tracking, preserving past credit"},{"method":"GET","path":"/api/v1/holidays","auth":"bearer","scope":"own|admin","summary":"Working holiday catalog and fairness policy"},{"method":"GET","path":"/api/v1/me/holiday-preferences","auth":"bearer","scope":"own","summary":"Own holiday rankings by year: {holidays, ranks (effective ranks for editableYear), editableYear, lockDate (YYYY-MM-DD or null), years:[{year,ranks,locked,lockDate?}], note}. Add month=YYYY-MM for month:[{id,name,date,closed,rank}], that month's holidays including closed ones. The ranking for year Y locks on the workplace holidayRankLock date (default 12-01) of year Y-1"},{"method":"PATCH","path":"/api/v1/me/holiday-preferences","auth":"bearer","scope":"own","summary":"Save {ranks:{holidayId:rank}, year?}; year defaults to editableYear. Rank 1 is most valued off and unranked is neutral. A locked year returns 409 {error}. Returns {ranks, year}"},{"method":"PATCH","path":"/api/v1/people/:id/holiday-ranking","auth":"bearer","scope":"admin","summary":"Reopen one person's locked holiday ranking for one save with {reopenYear: current or next year}. Cancel with {reopenYear:null}"},{"method":"GET","path":"/api/v1/holiday-fairness","auth":"bearer","scope":"admin","summary":"Holiday assignment history and eligible workload shares across years"},{"method":"PATCH","path":"/api/v1/coverage","auth":"bearer","scope":"admin","summary":"Set global weekday staffing with {weekday:0..6,minStaff}; date override with {date:YYYY-MM-DD,minStaff}; reset with {date,reset:true}. Zero closes the day and removes its assignments."},{"method":"GET","path":"/api/v1/periods","auth":"bearer","scope":"own|admin","summary":"Stored scheduling periods and request windows"},{"method":"POST","path":"/api/v1/periods","auth":"bearer","scope":"admin","summary":"Create a weekly, fortnightly, monthly, quarterly, annual, or custom period with {cadence,start,end?,requestOpen?,requestClose?,name?}"},{"method":"PATCH","path":"/api/v1/periods/:id","auth":"bearer","scope":"admin","summary":"Change an unpublished period's request deadline with {requestClose}"},{"method":"GET","path":"/api/v1/schedule-builder","auth":"bearer","scope":"admin","summary":"Schedule readiness, conflicts and automatic build result, plus requestWindow (open, closesOn, canCloseNow, canReopen, publishWarning), draftStale, draftStaleMessage and changesSinceDraft {requests, otherInputs}; periodId is authoritative and month=YYYY-MM remains supported"},{"method":"POST","path":"/api/v1/schedule-builder","auth":"bearer","scope":"admin","summary":"Build or publish on the server: {action: generate|publish|close-requests|reopen-requests, replace?: boolean}; select with periodId or month=YYYY-MM. Generating keeps requests open until the deadline; close-requests stops them early and notifies staff. AI provider and usage limits are server controlled."},{"method":"GET","path":"/api/v1/setup-checks","auth":"bearer","scope":"admin","summary":"Live setup checks for hours, people, shift leads, and schedule conflicts"},{"method":"GET","path":"/api/v1/coworkers","auth":"bearer","scope":"own","summary":"Active coworkers available for shift coverage or trades: {id, name, avatarUrl}; avatarUrl is a signed profile picture link (one hour) or null"},{"method":"GET","path":"/api/v1/roster","auth":"bearer","scope":"own|admin","summary":"Photo Roster: everyone active in the workplace as {workplace, subtitle, date, count, groups:[{id, label, people:[{name, title, badge?, avatarUrl}]}]}. badge is an admin-set title badge such as Chief Resident. Residency groups are the classes (R1 first), Incoming, then other roles; other workplaces group by home location or role. title matches the team contact card; avatarUrl is a signed picture link (one hour) or null"},{"method":"GET","path":"/api/v1/avatars","auth":"bearer","scope":"own","summary":"Pictures in the selected workplace: {avatars: {personId: signed URL}, sources, rosterPhotos, expiresIn}. avatars shows the person's own picture, else the roster photo an admin set"},{"method":"GET","path":"/api/v1/people/:id/roster-photo","auth":"bearer","scope":"admin","summary":"A person's roster photo: {rosterPhotoUrl, path}"},{"method":"POST","path":"/api/v1/people/:id/roster-photo","auth":"bearer","scope":"admin","summary":"Start a roster photo upload with {contentType: image/webp|jpeg, size <= 5 MB}; returns a one-time upload link"},{"method":"POST","path":"/api/v1/people/:id/roster-photo/commit","auth":"bearer","scope":"admin","summary":"Finish the roster photo upload with {path}: the stored file is checked, becomes the roster photo and replaces the old one"},{"method":"DELETE","path":"/api/v1/people/:id/roster-photo","auth":"bearer","scope":"admin","summary":"Remove a person's roster photo"},{"method":"GET","path":"/api/v1/me/avatar","auth":"bearer","scope":"own","summary":"Own profile picture: {avatarUrl, path}"},{"method":"POST","path":"/api/v1/me/avatar","auth":"bearer","scope":"own","summary":"Start a profile picture upload with {contentType: image/jpeg|png|webp|heic, size <= 5 MB}; returns a one-time upload link"},{"method":"POST","path":"/api/v1/me/avatar/commit","auth":"bearer","scope":"own","summary":"Finish the upload with {path}: the stored file is checked, becomes the picture and replaces the old one"},{"method":"DELETE","path":"/api/v1/me/avatar","auth":"bearer","scope":"own","summary":"Remove own profile picture"},{"method":"GET","path":"/api/v1/team/schedule","auth":"bearer","scope":"own|admin","summary":"Published team schedule; filter with month=YYYY-MM. Rows carry person.id; days carry location {id, name} or null"},{"method":"GET","path":"/api/v1/me/preferences","auth":"bearer","scope":"own","summary":"Own notes"},{"method":"PATCH","path":"/api/v1/me/preferences","auth":"bearer","scope":"own","summary":"Save own notes with {notes}"},{"method":"PATCH","path":"/api/v1/assignments/:id","auth":"bearer","scope":"admin","summary":"Edit a shift while preserving its published status; {locked:true|false} alone sets its automatic-generation lock. A rule break answers with warning, or 409 when adminChangeBreaksRule is refuse"},{"method":"GET","path":"/api/v1","auth":"public","scope":"any","summary":"Catalog, scopes, and how to authenticate: create an API key in Settings > API Keys"},{"method":"GET","path":"/api/v1/openapi","auth":"public","scope":"any","summary":"OpenAPI 3.1 document"},{"method":"GET","path":"/api/v1/key","auth":"public","scope":"demo","summary":"Demo org admin token"},{"method":"POST","path":"/api/v1/key","auth":"admin","scope":"admin","summary":"Rotate the org admin token"},{"method":"POST","path":"/api/v1/tokens/calendar","auth":"public","scope":"any","summary":"Exchange a calendar subscribe token for an API token"},{"method":"GET","path":"/api/v1/tokens","auth":"bearer","scope":"own|admin","summary":"List tokens"},{"method":"POST","path":"/api/v1/tokens","auth":"bearer","scope":"own|admin","summary":"Mint a person token"},{"method":"DELETE","path":"/api/v1/tokens/:id","auth":"bearer","scope":"own|admin","summary":"Revoke a token"},{"method":"GET","path":"/api/v1/me","auth":"bearer","scope":"own","summary":"Current actor and access"},{"method":"GET","path":"/api/v1/team/off","auth":"bearer","scope":"own","summary":"Who is off from ?from= to ?to= (YYYY-MM-DD, up to 400 days): {from, to, off: [{personId, name, start, end, label}]} where label is Vacation or Off. Approved time off, leave and recorded days off only; never pending requests, reasons or notes"},{"method":"GET","path":"/api/v1/me/days","auth":"bearer","scope":"own","summary":"Your own days off from ?from= to ?to= (YYYY-MM-DD, up to 120 days) for your calendar: days [{date, off: null or {kind: vacation, off, leave-rotation or holiday, label}, holiday}]. Approved time off only"},{"method":"GET","path":"/api/v1/me/day","auth":"bearer","scope":"own","summary":"Your day in one read, ?date=YYYY-MM-DD (default today at the workplace): me (rotation with block day or shifts, off or holiday, a timeline of rotation hours, call, sign-up and moonlighting, who works with you), upcoming (tomorrow, next call, rotation change or shift), needs (today: hours to confirm, released shifts for you, open sign-ups, swaps) and whosOn (holiday, on call, everyone by rotation or shift, off with Vacation or Off and dates). Published schedules only"},{"method":"GET","path":"/api/v1/me/schedule","auth":"bearer","scope":"own","summary":"Own published shifts"},{"method":"GET","path":"/api/v1/me/calendar","auth":"bearer","scope":"own","summary":"Own ICS subscribe URL, plus team: the calendar for everyone with the web's wording"},{"method":"GET","path":"/api/v1/me/profile","auth":"bearer","scope":"own","summary":"Own profile"},{"method":"PATCH","path":"/api/v1/me/profile","auth":"bearer","scope":"own","summary":"Update own profile"},{"method":"GET","path":"/api/v1/me/requests","auth":"bearer","scope":"own","summary":"Own time-off and standing requests"},{"method":"POST","path":"/api/v1/me/requests","auth":"bearer","scope":"own","summary":"Create a request for yourself"},{"method":"PATCH","path":"/api/v1/me/requests/:id","auth":"bearer","scope":"own","summary":"Edit your pending or standing request"},{"method":"DELETE","path":"/api/v1/me/requests/:id","auth":"bearer","scope":"own","summary":"Withdraw your own pending request before its due date, the same as Withdraw in the app"},{"method":"GET","path":"/api/v1/me/swaps","auth":"bearer","scope":"own","summary":"Own swap asks. Each open row carries otherPersonId (the coworker on the other side) and noteRemovedAt (set when an admin removed its note after a report)"},{"method":"POST","path":"/api/v1/me/swaps","auth":"bearer","scope":"own","summary":"Propose a swap"},{"method":"POST","path":"/api/v1/me/swaps/:id/decide","auth":"bearer","scope":"own","summary":"Accept or decline an incoming swap"},{"method":"GET","path":"/api/v1/open-shifts","auth":"bearer","scope":"own|admin","summary":"Employees: open shifts they qualify for, their claims and offers, and shifts they can offer. Admins: every post with claims checked against current rules; ?view=mine shows the employee view. Rows carry authorPersonId (who posted it), fromPersonId (offers: who is giving the shift away) and noteRemovedAt"},{"method":"POST","path":"/api/v1/open-shifts","auth":"bearer","scope":"admin","summary":"Post an open shift: {shiftTypeId,date,seats?,note?,requiredSkill?,requiredRole?}"},{"method":"POST","path":"/api/v1/open-shifts/gaps","auth":"bearer","scope":"admin","summary":"Post every unfilled seat from today on in a published period: {periodId}"},{"method":"DELETE","path":"/api/v1/open-shifts/:id","auth":"bearer","scope":"own|admin","summary":"Admins cancel a post; the person who offered a shift withdraws the offer. Waiting claims are declined with a notice"},{"method":"POST","path":"/api/v1/open-shifts/:id/claim","auth":"bearer","scope":"own","summary":"Ask to take an open or offered shift. Rules are checked first; with automatic approval on, the first eligible claim is assigned"},{"method":"DELETE","path":"/api/v1/open-shifts/:id/claim","auth":"bearer","scope":"own","summary":"Withdraw your pending claim"},{"method":"POST","path":"/api/v1/open-shifts/:id/claims/:claimId/decide","auth":"bearer","scope":"admin","summary":"Approve or decline a claim: {status: approved|declined}. Approval re-checks every rule; filling the last seat declines the rest"},{"method":"POST","path":"/api/v1/me/offers","auth":"bearer","scope":"own","summary":"Offer one of your future published shifts: {assignmentId,offeredTo?: personId[],note?}. Empty offeredTo means anyone qualified"},{"method":"DELETE","path":"/api/v1/me/offers/:id","auth":"bearer","scope":"own","summary":"Withdraw your shift offer"},{"method":"GET","path":"/api/v1/me/time-off","auth":"bearer","scope":"own","summary":"Paid time off (when on): balance, waiting, available, earned and used this year per leave type. ?date=YYYY-MM-DD for another day; ?ledger=1 adds every ledger line"},{"method":"GET","path":"/api/v1/me/time-off/check","auth":"bearer","scope":"own","summary":"Balance before and after a request: ?bankId&startDate&endDate&kind. Counts your other waiting requests"},{"method":"GET","path":"/api/v1/time-off/balances","auth":"bearer","scope":"admin","summary":"Every active person's paid time off balances on ?date (default today)"},{"method":"GET","path":"/api/v1/time-off/people/:id","auth":"bearer","scope":"admin","summary":"One person's ledger lines per leave type; ?bankId for one type, ?date for another day"},{"method":"GET","path":"/api/v1/time-off/adjustments","auth":"bearer","scope":"admin","summary":"Manual adjustments, oldest first; ?personId filters"},{"method":"POST","path":"/api/v1/time-off/adjustments","auth":"bearer","scope":"admin","summary":"Add a dated adjustment with a reason: {personId,bankId,date,amount,reason}; or reverse one with {personId,bankId,date,reason,reverses}"},{"method":"POST","path":"/api/v1/reports","auth":"bearer","scope":"own","summary":"Report someone's profile photo or name, or a note they wrote: {kind: profile|note, personId, reason: inappropriate|harassment|spam|other, details? (500), note? {type: request|swap|release|offer|announcement|other, id, text (2000)}} (note required for kind note; not yourself). Tells the workplace admins and Schedultary. 201 {id, message}"},{"method":"GET","path":"/api/v1/reports","auth":"bearer","scope":"admin","summary":"Reports in this workplace, newest first: {reports:[{id, kind, personId, personName, reporterName, reason, details, note, status: open|dismissed|photo_removed|note_removed, createdAt, resolvedAt, resolvedByName}]}. Never lists a report about the admin asking"},{"method":"POST","path":"/api/v1/reports/:id/resolve","auth":"bearer","scope":"admin","summary":"Resolve a report with {action: dismiss|remove-photo|remove-note}: remove-photo (profile reports) clears the person's roster photo and account picture; remove-note (note reports) blanks the note and sets noteRemovedAt on it. 200 {report}"},{"method":"GET","path":"/api/v1/blocks","auth":"bearer","scope":"own","summary":"People you blocked in this workplace: {blocked:[{personId, name, blockedAt}]}. Blocking hides their photo and notes for you only and never changes the schedule"},{"method":"POST","path":"/api/v1/blocks","auth":"bearer","scope":"own","summary":"Block someone in this workplace with {personId} (not yourself); repeating it changes nothing. 201 {blocked}"},{"method":"DELETE","path":"/api/v1/blocks/:personId","auth":"bearer","scope":"own","summary":"Unblock someone. 200 {blocked}"},{"method":"POST","path":"/api/v1/feature-requests","auth":"bearer","scope":"own","summary":"Send an idea for Schedultary (body: text, up to 2,000 characters); admins see them on the Suggest page"},{"method":"POST","path":"/api/v1/me/cant-make-it","auth":"bearer","scope":"own","summary":"Tell the managers you cannot work a date (body: date, optional note); notifies admins in the app and by email, files no request"},{"method":"POST","path":"/api/v1/me/swaps/:id/cancel","auth":"bearer","scope":"own","summary":"Cancel a swap you asked for while it is still open"},{"method":"GET","path":"/api/v1/me/reminders","auth":"bearer","scope":"own","summary":"Your reminder choices with defaults filled in, your daily schedule message and the workplace settings. workplace.texts: texts can go out now (Allow Text Messages plus the workplace's texts); workplace.textsAllowed: the admin switch alone. Save a mobile number with PUT /api/contacts {phone}"},{"method":"PATCH","path":"/api/v1/me/reminders","auth":"bearer","scope":"own","summary":"Change reminder switches, lead time (60, 120, 240, 720, 1440 or 2880 minutes) or daily {sms,email,push,time,weekdaysOnly}"},{"method":"POST","path":"/api/v1/me/swaps/:id/answer","auth":"bearer","scope":"own","summary":"The asked coworker accepts or declines: {answer: accept | decline}"},{"method":"GET","path":"/api/v1/me/unavailable","auth":"bearer","scope":"own","summary":"Coworkers who cannot take one of your shifts (?assignmentId): they asked for that day off or are on leave. Returns personIds only; leave them out of swap and offer pickers"},{"method":"GET","path":"/api/v1/me/clock","auth":"bearer","scope":"own","summary":"Time clock (Beta): your status (off, in or break), today's and this week's hours, your punches with edit history, approvals and correction requests for a pay period (?start&end, default the current one)"},{"method":"POST","path":"/api/v1/me/clock","auth":"bearer","scope":"own","summary":"Punch at server time: {kind: in|out|break_start|break_end, note?, lat?, lng?}. 409 when already clocked in or on a break; 400 with needsNote when a late note is required"},{"method":"POST","path":"/api/v1/me/timesheet/corrections","auth":"bearer","scope":"own","summary":"Ask a manager to fix your timesheet: {date, message, punchId?}"},{"method":"GET","path":"/api/v1/compliance/fair-workweek","auth":"bearer","scope":"admin","summary":"Fair Workweek (Beta): posting notice per period, rest-window pairs with consent and premium, published-shift changes with initiator and premium owed, and open shifts offered to current staff (?from&to)"},{"method":"GET","path":"/api/v1/timesheets","auth":"bearer","scope":"admin","summary":"Timesheets (Beta): scheduled vs worked hours, breaks, overtime and flags per person for a pay period (?start&end), with every punch, approval and correction request"},{"method":"POST","path":"/api/v1/timesheets/punches","auth":"bearer","scope":"admin","summary":"Add a missed punch: {personId, kind, at, reason, assignmentId?}"},{"method":"PATCH","path":"/api/v1/timesheets/punches/:id","auth":"bearer","scope":"admin","summary":"Edit or remove a punch with a reason: {reason, at?, kind?, void?}. The original is kept as history"},{"method":"POST","path":"/api/v1/timesheets/approvals","auth":"bearer","scope":"admin","summary":"Approve a person's pay period: {personId, start, end}. Locks edits until reopened"},{"method":"POST","path":"/api/v1/timesheets/approvals/reopen","auth":"bearer","scope":"admin","summary":"Reopen an approved pay period: {personId, start, reason}"},{"method":"POST","path":"/api/v1/timesheets/corrections/:id","auth":"bearer","scope":"admin","summary":"Answer a correction request: {status: resolved|declined, response?}"},{"method":"GET","path":"/api/v1/timesheets/approved-hours","auth":"bearer","scope":"admin","summary":"Approved timesheet hours per person per date (?start&end, up to 400 days) and the approved pay periods, used for paid time off earned per hour worked"},{"method":"GET","path":"/api/v1/payroll","auth":"bearer","scope":"admin","summary":"Payroll export (Beta) for approved people in a pay period (?start&end). ?format=csv for one row per person, ?format=days-csv for one row per day"},{"method":"GET","path":"/api/v1/comms/preferences","auth":"bearer","scope":"own","summary":"Your push choices"},{"method":"PUT","path":"/api/v1/comms/preferences","auth":"bearer","scope":"own","summary":"Save {pushReminders?,pushUpdates?}"},{"method":"GET","path":"/api/v1/comms/push/native","auth":"bearer","scope":"own","summary":"Your iPhones registered for notifications in this workplace, without tokens, and whether the server can send (configured)"},{"method":"POST","path":"/api/v1/comms/push/native","auth":"bearer","scope":"own","summary":"Register this iPhone: {provider: apns, token (hex), environment: production|sandbox, device?}. Registers it for the selected workplace (x-schedultary-workplace); send once per workplace. Removes the token from any other account"},{"method":"DELETE","path":"/api/v1/comms/push/native","auth":"bearer","scope":"own","summary":"Remove {token} from this workplace, or every iPhone of yours here when token is omitted"},{"method":"POST","path":"/api/v1/comms/push/native/test","auth":"bearer","scope":"own","summary":"Send a test notification to your registered iPhones (503 until APNs is configured)"},{"method":"GET","path":"/api/v1/me/fairness","auth":"bearer","scope":"own","summary":"Own fairness row"},{"method":"GET","path":"/api/v1/crews","auth":"bearer","scope":"any","summary":"Crews and rotations (404 while off): admins get crews, members, weekly hours, the crew coverage check, the relief pool and open seats; members get their own crew days for eight weeks"},{"method":"PUT","path":"/api/v1/crews","auth":"bearer","scope":"admin","summary":"Replace crews: {crews:[{id,label,cycle:[{shiftTypeIds,optional?}],anchorDate,offsetDays?,memberIds?,kelly?:{everyTours,staggerTours},overlay?}],crewsPerDuty?}. optional: the build may fill that day; overlay: an on-call layer that adds to other work (one crew and one layer per person)"},{"method":"GET","path":"/api/v1/locations","auth":"bearer","scope":"any","summary":"Multiple locations (404 while off): locations with hours, closed dates, time zone and optional time clock area, travel minutes, shift type to location map, and your own home and eligible locations"},{"method":"GET","path":"/api/v1/staffing/intervals","auth":"bearer","scope":"admin","summary":"Staffing by time of day (404 while off): needed and scheduled people per interval and group for ?start&end (at most 62 days), with under and over counts"},{"method":"GET","path":"/api/v1/credentials","auth":"bearer","scope":"admin","summary":"Credentials (404 while off): expired, not yet valid or expiring in 30 days; ?all=1 lists every credential"},{"method":"GET","path":"/api/v1/me/credentials","auth":"bearer","scope":"own","summary":"Your own credentials with dates and status (404 while off)"},{"method":"GET","path":"/api/v1/shift-types","auth":"bearer","scope":"any","summary":"Shift types for this org"},{"method":"POST","path":"/api/v1/shift-types","auth":"bearer","scope":"admin","summary":"Create a shift type, including breaks [{startMinute,durationMinutes,paid}] and reliefForShiftTypeIds"},{"method":"PATCH","path":"/api/v1/shift-types/:id","auth":"bearer","scope":"admin","summary":"Update a shift type; explicit break offsets are civil minutes from its start, and relief targets one qualified duty"},{"method":"GET","path":"/api/v1/org","auth":"bearer","scope":"any","summary":"Org settings (full for admin). features lists the optional features that are on (employees); admins get the saved features map plus enabledFeatures, the ids the server serves"},{"method":"PATCH","path":"/api/v1/org","auth":"bearer","scope":"admin","summary":"Patch org settings, including holidayRankLock (MM-DD, default 12-01), branding {primary,accent,background,logo?,fontHeading?,fontBody?} (font family names used on printed rosters), brandingWebsite, reminders {texts (Allow Text Messages; needs a text allowance), quietHours {on, start, end} (HH:MM, workplace time zone; off by default; texts and app notifications wait until quiet hours end, email never waits; GET returns quietHours with defaults)}, features {id: true|false}, prefVerify, shareFairnessWithTeam, askBeforeChanges, adminChangeBreaksRule (warn, the default: an admin change that breaks a rule saves, answers with a warning and stays in Must Fix; refuse: it is refused with the reason), staffAssistants {enabled, requestsPerDay (1-25), proposalsPerDay (1-15), openSwaps (1-10), perWeek (1-75)} for what staff assistants may file, and rotateJoinLink: true for a new join link"},{"method":"GET","path":"/api/v1/settings","auth":"bearer","scope":"admin","summary":"Alias of /org"},{"method":"PATCH","path":"/api/v1/settings","auth":"bearer","scope":"admin","summary":"Alias of PATCH /org"},{"method":"GET","path":"/api/v1/people","auth":"bearer","scope":"admin","summary":"Full roster. sharePhoneWithTeam on each person is true unless the person or an admin turned mobile number sharing off; ?fields=id,name keeps those fields, ?limit=&cursor= pages as {items,total,nextCursor}"},{"method":"POST","path":"/api/v1/people","auth":"bearer","scope":"admin","summary":"Create a person {name,email,phone?,sharePhoneWithTeam? (mobile number sharing, on unless false),role?,title? (a title badge such as Chief Resident, at most 40 characters; shown on the Photo Roster and the contact card, never used for scheduling),preferences?:{workWeekdays?,workWeekdaysFirmness?},...}. workWeekdays are usual days, 0 Sunday to 6 Saturday; workWeekdaysFirmness is always (never works other days) or usually (prefers these days)"},{"method":"POST","path":"/api/v1/people/phones","auth":"bearer","scope":"admin","summary":"Add mobile numbers for people already on the roster from pasted text (one person per line: name or email plus number). {mode: preview, text} matches each line by email, then full name, then last name plus first initial, and saves nothing; rows say new, change, same, bad-number, ambiguous, unmatched, conflict or repeat. {mode: apply, text, personIds} saves only phone for the listed people whose row is new or change"},{"method":"GET","path":"/api/v1/contacts/card","auth":"bearer","scope":"any","summary":"The caller's current team contact card as a vCard 3.0 file (text/vcard): everyone active in their own workplace who shares a mobile number, with first and last name, the workplace as company, class or role as title, the number and the email when shared"},{"method":"GET","path":"/api/v1/contacts/batches","auth":"bearer","scope":"admin","summary":"Team contact updates: how many active people share a mobile number (on unless turned off), have one with sharing turned off, or have none; what a full update and a new-only update would send (contacts, people, emails, texts); and the history of contact links (date, count, sent by, status). Reads only"},{"method":"POST","path":"/api/v1/contacts/batches","auth":"bearer","scope":"admin","summary":"Make a contact card link without sending anything. body {scope: all|new}: all holds everyone who shares a mobile number; new holds only people who were not on an earlier sent card. The link opens a page with one Add Contacts button, works for 30 days and can be turned off"},{"method":"POST","path":"/api/v1/contacts/batches/send","auth":"bearer","scope":"admin","summary":"Send a team contact update: body {scope: all|new}. all emails everyone one link to the full card and texts it to people who turned texts on; new sends the new-only card to people who got an earlier update and the full card to people who never did. Sends real email and texts that cannot be undone: call only when the admin explicitly asks to send"},{"method":"DELETE","path":"/api/v1/contacts/batches/:id","auth":"bearer","scope":"admin","summary":"Turn off a contact card link so it no longer opens"},{"method":"GET","path":"/api/v1/people/:id","auth":"bearer","scope":"admin","summary":"One person"},{"method":"PATCH","path":"/api/v1/people/:id","auth":"bearer","scope":"admin","summary":"Update a person {name?,email?,phone? (null clears),sharePhoneWithTeam? (true or false: show their mobile number to the team and on the contact card; the person can also change it in Profile and the last change wins),role?,title? (title badge, null clears),active?,preferences?:{workWeekdays?,workWeekdaysFirmness? (always: never works other days; usually: prefers these days; null clears)},...}"},{"method":"DELETE","path":"/api/v1/people/:id","auth":"bearer","scope":"admin","summary":"Deactivate a person"},{"method":"PATCH","path":"/api/v1/people/:id/access","auth":"bearer","scope":"admin","summary":"Make Admin or Remove Admin: {access: admin|member}. Takes effect at once, is logged and emails the person (not when you change your own). The Owner cannot be made a member (transfer ownership first) and the last admin cannot be removed. Making someone an admin needs an account or an email. Returns {answer, access, previousAccess, changed, emailed}"},{"method":"GET","path":"/api/v1/ownership","auth":"bearer","scope":"own|admin","summary":"Who owns the workplace: {answer, owner {personId, name, email (admins)}, ownerPresent, isOwner, isAdmin, level (owner|admin|member), pending (admins: a transfer waiting 72 hours, with id, to, requestedBy, completesAt)}. The Owner is an admin who also manages billing and ownership"},{"method":"GET","path":"/api/v1/assignments","auth":"bearer","scope":"admin","summary":"Assignments; ?personId=, ?date=, ?from=&to=; ?fields=id,name keeps those fields, ?limit=&cursor= pages as {items,total,nextCursor}"},{"method":"POST","path":"/api/v1/assignments","auth":"bearer","scope":"admin","summary":"Put someone on a shift; accepts {personId,date,shiftTypeId?,status?:draft,locked?:boolean}. A rule break answers with warning, or 409 when adminChangeBreaksRule is refuse"},{"method":"DELETE","path":"/api/v1/assignments/:id","auth":"bearer","scope":"admin","summary":"Remove an assignment"},{"method":"GET","path":"/api/v1/rules","auth":"bearer","scope":"admin","summary":"Placement and swap rules"},{"method":"POST","path":"/api/v1/rules","auth":"bearer","scope":"admin","summary":"Add a rule"},{"method":"PATCH","path":"/api/v1/rules/:id","auth":"bearer","scope":"admin","summary":"Edit a rule"},{"method":"DELETE","path":"/api/v1/rules/:id","auth":"bearer","scope":"admin","summary":"Remove a rule"},{"method":"GET","path":"/api/v1/requests","auth":"bearer","scope":"admin","summary":"All requests; ?personId=, ?status=; ?fields=id,name keeps those fields, ?limit=&cursor= pages as {items,total,nextCursor}"},{"method":"POST","path":"/api/v1/requests","auth":"bearer","scope":"admin","summary":"Create a request for anyone"},{"method":"POST","path":"/api/v1/requests/:id/decide","auth":"bearer","scope":"admin","summary":"Approve or deny a request"},{"method":"GET","path":"/api/v1/swaps","auth":"bearer","scope":"admin","summary":"All swaps"},{"method":"POST","path":"/api/v1/swaps","auth":"bearer","scope":"admin","summary":"Propose a swap"},{"method":"POST","path":"/api/v1/swaps/:id/decide","auth":"bearer","scope":"admin","summary":"Approve or deny a swap"},{"method":"GET","path":"/api/v1/coverage","auth":"bearer","scope":"admin","summary":"Staffing requirements"},{"method":"POST","path":"/api/v1/coverage/infer","auth":"bearer","scope":"admin","summary":"Rebuild coverage from the roster"},{"method":"GET","path":"/api/v1/fairness","auth":"bearer","scope":"admin","summary":"Fairness for the team"},{"method":"GET","path":"/api/v1/hits","auth":"bearer","scope":"admin","summary":"Open issues (Must Fix and Worth a Look), each with an id for explain_issue and suggest_fixes; ?fields=id,name keeps those fields, ?limit=&cursor= pages as {items,total,nextCursor}"},{"method":"GET","path":"/api/v1/explain/cell","auth":"bearer","scope":"own|admin","summary":"Why a person holds a shift: generator decision or hand placement, requests, rules and fairness. ?assignmentId= or ?personId=&date=&shiftTypeId?; employees ask about their own shifts"},{"method":"GET","path":"/api/v1/explain/issue","auth":"bearer","scope":"admin","summary":"One issue in plain words: the rule, expected vs actual numbers, who, when and why it matters. ?id= from list_issues; ?month= or ?periodId= picks the window"},{"method":"GET","path":"/api/v1/why-not","auth":"bearer","scope":"own|admin","summary":"Why someone cannot work a date: blocking reasons in order with numbers, and whether an admin can override. ?date=&personId?&shiftTypeId?; admins leave out personId to list who can work"},{"method":"GET","path":"/api/v1/coverage-gaps","auth":"bearer","scope":"admin","summary":"Every short shift, call seat and rotation minimum from ?from= to ?to= (default the next 14 days): who is missing, why in one sentence and who can cover; plus overrides still in force"},{"method":"POST","path":"/api/v1/coverage-gaps/offer","auth":"bearer","scope":"admin","summary":"Offer to These People: post one short shift {key, personIds?} as an open shift only they can take; without personIds, the top 3 who can cover"},{"method":"GET","path":"/api/v1/fixes","auth":"bearer","scope":"admin","summary":"Up to 5 ranked fixes for one issue, each rule-checked without saving: issues cleared and added, fairness change and the calls that apply it. ?id=&limit?"},{"method":"GET","path":"/api/v1/summary/schedule","auth":"bearer","scope":"admin","summary":"Compact schedule: one code per person per day for ?month=YYYY-MM or ?periodId=, legend once"},{"method":"GET","path":"/api/v1/summary/issues","auth":"bearer","scope":"admin","summary":"Issue counts by rule and severity plus the top 10 with ids; ?month= or ?periodId="},{"method":"GET","path":"/api/v1/summary/person","auth":"bearer","scope":"own|admin","summary":"One person's month in a few lines: shifts vs target, requests and issues. ?personId=&month?"},{"method":"POST","path":"/api/v1/call","auth":"bearer","scope":"own|admin","summary":"Legacy tool call; scoped by account access"},{"method":"GET","path":"/api/v1/program","auth":"bearer","scope":"own|admin","summary":"Program snapshot: wording, templateId, medical, callSettings, residents (shownOnly), blocks, call, callTimes, requests. Employees see only their rows. ?view=mine: an admin's own resident view"},{"method":"GET","path":"/api/v1/program/rotations","auth":"bearer","scope":"own|admin","summary":"Rotation catalog"},{"method":"GET","path":"/api/v1/program/team","auth":"bearer","scope":"own|admin","summary":"Published schedule for the whole program: rotations and call for everyone, ?year=. Drafts return published: false."},{"method":"GET","path":"/api/v1/program/grid","auth":"bearer","scope":"own|admin","summary":"Year grid; ?year="},{"method":"PUT","path":"/api/v1/program/grid","auth":"bearer","scope":"admin","summary":"Set a cell {personId,blockIndex,rotationId} or {parts}, year?; or many cells at once {year, cells: [{personId,blockIndex,rotationId or parts}]} (up to 2000; one bad cell saves none), such as an imported past year"},{"method":"PATCH","path":"/api/v1/program/grid","auth":"bearer","scope":"admin","summary":"Same as PUT /program/grid"},{"method":"DELETE","path":"/api/v1/program/grid","auth":"bearer","scope":"admin","summary":"Clear a cell {personId,blockIndex}"},{"method":"POST","path":"/api/v1/program/call","auth":"bearer","scope":"admin","summary":"Add call {personId,date,kind,slot?}, ?year=; or many days at once {calls: [{personId,date,kind,slot?}]} (up to 2000; one bad day saves none), such as an imported year"},{"method":"POST","path":"/api/v1/program/requests","auth":"bearer","scope":"own|admin","summary":"Add a prefer/avoid/vacation/with/electives request; ?year=; with uses withPersonId; electives sends electivePlan {sectionId, fellowship?: {rotationId, blocks}, electiveIds (rank order, one block each)} for a request section open to the resident that year (see requestSections on GET /program), note optional, one plan per section; vacation takes startDate and endDate, or a flexible windowStart, windowEnd and windowDays (weekdays)"},{"method":"PATCH","path":"/api/v1/program/requests/:id","auth":"bearer","scope":"own|admin","summary":"Edit a pending request before its deadline; ?year="},{"method":"DELETE","path":"/api/v1/program/requests/:id","auth":"bearer","scope":"own|admin","summary":"Withdraw a pending request before its deadline; ?year="},{"method":"POST","path":"/api/v1/program/leave","auth":"bearer","scope":"admin","summary":"Place leave {personId,start,end,note?,kind?}: kind parental (default) or absence (a leave of absence). Both lock the cells and earn no training credit"},{"method":"POST","path":"/api/v1/program/active","auth":"bearer","scope":"admin","summary":"Set a resident's active dates {personId,activeFrom?,activeUntil?} for a mid-year start or early exit; null clears, a date left out stays. Dates outside count as a leave of absence"},{"method":"POST","path":"/api/v1/program/generate","auth":"bearer","scope":"admin","summary":"Fill an unpublished academic year in one pass {year?, budgetMs? (search effort, 500 to 20000, default 20000: a fixed amount of search work, about that many ms for a mid-size program, so the same input gives the same draft)}. Every step stops by 45 seconds. A published year returns 400 with published:true and nextYear; returns complete and incomplete (set when generation stopped at the time limit; the draft so far is saved)"},{"method":"POST","path":"/api/v1/program/repair","auth":"bearer","scope":"admin","summary":"Preview existing draft repairs with {action:preview,year}; apply the returned preview with {action:apply,year,preview}. Stale previews are rejected; publication is separate."},{"method":"POST","path":"/api/v1/program/qgenda","auth":"bearer","scope":"admin","summary":"Import the live QGenda board into the program {url (the workplace's saved board when left out), startDate?}: matches people, records call and leave days"},{"method":"GET","path":"/api/v1/program/qgenda/link","auth":"bearer","scope":"admin","summary":"QGenda mappings: {link: {tasks (rotation id or va, nights, ir: {am?, pm?, full?, days: weekdays|all}), calls (short-first ... baby-pm: task), staff (person id: staff name), layout?, taskAliases?, viewUrl?, companyKey?}, callKeys, taskFamilies, rotations}. A task or staff entry is {name, key?, abbreviation?}"},{"method":"PATCH","path":"/api/v1/program/qgenda/link","auth":"bearer","scope":"admin","summary":"Change QGenda mappings {tasks?, calls?, staff?, layout?, taskAliases?, viewUrl?, companyKey?}. Each section merges; an entry set to null is removed. A task or staff entry is a QGenda name (\"Body CT AM\" or \"Body CT AM [key]\") or {name, key?, abbreviation?}; a saved key stays while the name is unchanged. Use am and pm tasks or one full-day task. Unknown rotations, people or call keys save nothing. Returns {link}"},{"method":"GET","path":"/api/v1/program/qgenda/feed","auth":"bearer","scope":"admin","summary":"QGenda calendar feed: {feedUrl, viewUrl, syncedAt, recordedDays (days recorded from QGenda)}"},{"method":"PUT","path":"/api/v1/program/qgenda/feed","auth":"bearer","scope":"admin","summary":"Save the QGenda calendar feed {feedUrl}: a webcal:// or https:// iCalendar link on qgenda.com or ending in .ics; null removes it. A public board link is refused: import it with POST /program/qgenda"},{"method":"POST","path":"/api/v1/program/qgenda/feed/import","auth":"bearer","scope":"admin","summary":"Import the QGenda calendar feed {url? (the saved feedUrl when left out)}: vacation, admin days and parental leave become recorded days matched to people by name; parental leave of 7 days or more is placed on the grid. Returns {events, added, unmatched, feedUrl}. Rate limited with the board import"},{"method":"GET","path":"/api/v1/program/hits","auth":"bearer","scope":"admin","summary":"Residency rule hits for ?year=; ?fields=id,name keeps those fields, ?limit=&cursor= pages as {year,hits,total,nextCursor}"},{"method":"GET","path":"/api/v1/program/progress","auth":"bearer","scope":"admin","summary":"Training progress toward each requirement for every resident; ?year="},{"method":"GET","path":"/api/v1/program/fairness","auth":"bearer","scope":"admin","summary":"Call and rotation fairness by class; ?year="},{"method":"GET","path":"/api/v1/program/classes","auth":"bearer","scope":"admin","summary":"Classes (levels), the program's pathways and each resident's class, pathway and start year; ?year="},{"method":"GET","path":"/api/v1/program/pathways","auth":"bearer","scope":"admin","summary":"The program's pathways [{id, label, base}] and saved (false: read from what residents and rules use). The base pathway is where new residents start; its filter shows everyone off the other pathways"},{"method":"PUT","path":"/api/v1/program/pathways","auth":"bearer","scope":"admin","summary":"Replace the pathway list {pathways: [{id (1 to 12 letters, digits, - or _), label?, base?, withBase? (a track inside the base whose residents also show under the base filter)}]}, one base (the first when none is marked). Refused when it drops a pathway a resident or rule still uses"},{"method":"GET","path":"/api/v1/program/review","auth":"bearer","scope":"admin","summary":"Year review: rule hits and the decisions recorded for them; ?year="},{"method":"GET","path":"/api/v1/program/explain/cell","auth":"bearer","scope":"own|admin","summary":"Why a resident holds a rotation in a block (?personId=&blockIndex=) or a call day (?personId=&date=): generator decision or admin edit, requests, rules and fairness; ?year="},{"method":"GET","path":"/api/v1/program/explain/issue","auth":"bearer","scope":"admin","summary":"One issue in plain words: the rule, expected vs actual numbers, who, when and why it matters. ?key= from list_issues, or ?ruleId= with personId, blockIndex or date; ?year="},{"method":"GET","path":"/api/v1/program/why-not","auth":"bearer","scope":"own|admin","summary":"Why a resident is not on a rotation in a block (?personId=&blockIndex=&rotationId=) or cannot take call (?personId=&date=&kind?): reasons in order with numbers, and whether an admin can override. Names work for ids; no personId lists who could take the rotation"},{"method":"GET","path":"/api/v1/program/fixes","auth":"bearer","scope":"admin","summary":"Up to 5 ranked fixes for one issue (?key=), each rule-checked without saving: swaps, moves or call changes with issues cleared and added and the calls that apply it; ?year="},{"method":"GET","path":"/api/v1/program/summary/schedule","auth":"bearer","scope":"admin","summary":"Compact grid: one code per resident per block plus call counts, legend once; ?year="},{"method":"GET","path":"/api/v1/program/summary/issues","auth":"bearer","scope":"admin","summary":"Issue counts by rule and severity plus the top 10 with keys; ?year="},{"method":"GET","path":"/api/v1/program/summary/person","auth":"bearer","scope":"own|admin","summary":"One resident's year in a few lines: blocks, call vs target, requests and issues. ?personId=&year?"},{"method":"GET","path":"/api/v1/program/signups","auth":"bearer","scope":"own|admin","summary":"Call Sign-Up (rotation call residents pick themselves, such as Peds short call), ?year=. Admins: the rules and one row per block sheet with state (upcoming, open, closed), opensOn, dueOn, members, short, picked and required, plus stale when the sheets need an update. Residents: their own sheets with required, picked and each day's open seats. ?view=mine: an admin's own resident view"},{"method":"GET","path":"/api/v1/program/signups/mine","auth":"bearer","scope":"own","summary":"Your Call Sign-Up in one read, ?year= (default: the year holding today and the next): each sheet with its rule, state, required and picked, every day with who has it, its open seats and whether you can pick it, the residents who could take each of your days for a swap, and your sign-up swaps (incoming and outgoing, with status). admin and manageUrl for program admins"},{"method":"GET","path":"/api/v1/program/signups/board","auth":"bearer","scope":"admin","summary":"One block's sign-up board ?ruleId=&blockIndex=&year=: every day with who has it and open seats, every resident on the rotation with required, picked and short"},{"method":"POST","path":"/api/v1/program/signups/mine","auth":"bearer","scope":"own","summary":"Pick, unpick or move your own sign-up day {ruleId, add? (YYYY-MM-DD), remove?, year?}. Only while the block's sign-up is open, on days you are on the rotation, first come per seat, up to your required days; a day an admin locked stays"},{"method":"POST","path":"/api/v1/program/signups/swaps","auth":"bearer","scope":"own","summary":"Ask another resident on the rotation to take one of your sign-up days {ruleId, date, targetPersonId, withDate? (one of their days to trade), reason?, year?}. They answer it like any swap; an admin approves it"},{"method":"POST","path":"/api/v1/program/signups/assign","auth":"bearer","scope":"admin","summary":"Place or move a resident's sign-up day {personId, ruleId, date, from? (the day it moves from), override? (true puts a second person on a full day and marks that Must Fix as OK), year?}. Admins may change a closed sheet"},{"method":"DELETE","path":"/api/v1/program/signups/assign","auth":"bearer","scope":"admin","summary":"Remove a resident's sign-up day {personId, ruleId, date, year?}"},{"method":"POST","path":"/api/v1/program/signups/lock","auth":"bearer","scope":"admin","summary":"Lock or unlock a picked sign-up day {personId, ruleId, date, locked, year?}; a locked day cannot be changed by the resident"},{"method":"POST","path":"/api/v1/program/signups/fill","auth":"bearer","scope":"admin","summary":"Fill Automatically {ruleId, blockIndex, year?}: place the days residents did not pick, spread evenly, never on their call days, approved time off or a day that breaks rest rules; returns placed, unfilled and a summary"},{"method":"POST","path":"/api/v1/program/signups/remind","auth":"bearer","scope":"admin","summary":"Email every resident on the block's sheet who has not picked all their days {ruleId, blockIndex, year?}, and text those who turned texts on. Sends real email and texts that cannot be undone: call only when the admin explicitly asks to remind"},{"method":"POST","path":"/api/v1/program/signups/sheets","auth":"bearer","scope":"admin","summary":"Update every sign-up sheet from the rules (after a rule, holiday or calendar change); picked days stay"},{"method":"PUT","path":"/api/v1/program/signups/rules","auth":"bearer","scope":"admin","summary":"Replace the Call Sign-Up rules {rules: [{id?, name, code?, rotationIds, days? (0 Sunday..6 Saturday, default Monday to Friday), skipHolidays? (true), seatsPerDay? (1), daysPerBlock? (4), start? (12:00), end? (21:00), replacesWorkday? (true), opensDaysBefore? (21), dueDaysBefore? (7), countsTowardCall? (false), sameDayCall? (avoid|allow), sendMessages? (true), enabled?}]} and update the sheets. An empty list removes every sign-up"},{"method":"GET","path":"/api/v1/program/moonlighting","auth":"bearer","scope":"own|admin","summary":"Moonlighting (paid shifts on top of the schedule). Admins: the settings and one month of coverage ?month=YYYY-MM with who is on, open seats, requests, released shifts and jeopardy; seats carry conflict and nights (a flag that never blocks). Residents: open seats they can take (class, limits and overlaps applied, with warnings; nights: true during nights), released shifts, their own shifts and requests, totals by month and their jeopardy weeks. ?view=mine: an admin's own resident view. ?summary=calendar: public shift-type metadata only, without coverage or eligibility calculation"},{"method":"GET","path":"/api/v1/program/moonlighting/payroll","auth":"bearer","scope":"admin","summary":"One month's moonlighting payroll ?month=YYYY-MM as CSV (Resident, Class, Shift, Code, Date, Start, End, Hours, Pay, Overtime Minutes, Overtime Pay, Total Pay, Note); ?format=json returns rows (shiftId, flags: not-worked-yet, released-not-taken, hours-pending; nights: true during nights, Note On nights) and per-resident totals. Pay, clocks and the overtime rate are the ones saved on each seat when it was placed; overtime and worked hours a resident entered count only once approved"},{"method":"POST","path":"/api/v1/program/moonlighting/hours","auth":"bearer","scope":"admin","summary":"Approve or set a moonlighting seat's hours {shiftId, approve?: true, overtimeMinutes? (0-600, 15-minute steps), workedMinutes? (shift types whose hours vary, 0-1440, 15-minute steps; 0 goes back to the usual hours)}. A resident's overtime or worked hours wait for approval before payroll counts them; an admin's entry is approved at once"},{"method":"GET","path":"/api/v1/program/moonlighting/starts","auth":"bearer","scope":"admin","summary":"Starting counts ?year=: shifts of each type a resident worked this academic year before moonlighting was tracked here, which count toward yearly limits and fewest shifts first. Each {personId, typeId, count, name, type}"},{"method":"PUT","path":"/api/v1/program/moonlighting/starts","auth":"bearer","scope":"admin","summary":"Set one starting count {personId, typeId, count (0-366; 0 removes it), year?}: shifts of that type the resident worked this academic year before moonlighting was tracked here, such as July to September"},{"method":"POST","path":"/api/v1/program/moonlighting/mine","auth":"bearer","scope":"own","summary":"Change your own moonlighting {action, typeId?, poolId?, date?, shiftId?, minutes?, toPersonId?}. signup takes an open seat (typeId, date); request asks for a date in a request window; cancel drops a shift or request while sign-up is open; release gives up your shift (your class hears first, then everyone eligible; you stay responsible until someone takes it); unrelease keeps it; take takes a released shift (shiftId), first confirmed wins; pass says Not Interested to a released shift; want says I Want It when the program picks; pick gives your released shift to someone who said I Want It (pick mode only; toPersonId), who is told; overtime logs minutes past the end and hours logs the minutes worked on a shift type whose hours vary (both once the shift ends, and both wait for an admin to approve them); cancel works while sign-up is open and for cancelGraceHours after it closes; opt-out and opt-in say Not Interested to a shift type or pool. A shift counts as over at its end time. Returns warnings that never block (such as \"You are on nights this week.\" or hours near 80 a week) and flags ([\"nights\"] when you are on a night float rotation that day or the morning after a night; only an overlap with the night's hours is refused)"},{"method":"POST","path":"/api/v1/program/moonlighting/assign","auth":"bearer","scope":"admin","summary":"Place a resident on a moonlighting shift {personId, typeId, date, override?, reason?}. Without override a class, a locked rotation, a removal, Not Interested, a limit, an overlap, a full date or the regular schedule (call, rotation hours, leave, jeopardy) is refused; override saves it with the reasons, and past the regular schedule only with reason (max 200 characters). Returns warnings and flags ([\"nights\"]), never blocking"},{"method":"DELETE","path":"/api/v1/program/moonlighting/assign","auth":"bearer","scope":"admin","summary":"Remove a moonlighting seat or request {personId, shiftId}"},{"method":"GET","path":"/api/v1/program/moonlighting/released","auth":"bearer","scope":"own|admin","summary":"Released moonlighting shifts. released: for each one you released (admins: every one), who was told, who said Not Interested, who wants it (pick mode, fewest shifts this year first) and who has not answered. offers: released shifts offered to you with canTake, the reason when you cannot and mode (first-come or pick); take, pass or want them with POST /program/moonlighting/mine. A push or notice opens /mine/moonlighting?shift=<id>"},{"method":"POST","path":"/api/v1/program/moonlighting/released/pick","auth":"bearer","scope":"admin","summary":"Give a released moonlighting shift to someone {shiftId, personId, override?, reason?}: checked like a take (class, rotations, limits, overlaps, the regular schedule, Not Interested); override saves past those with the reasons, and past the regular schedule only with reason. The resident who released it is told"},{"method":"GET","path":"/api/v1/program/moonlighting/settings","auth":"bearer","scope":"admin","summary":"Moonlighting settings: config {types, window, windowOverrides, releaseClassHours, lateReleaseDays, jeopardyPools}, the program's classes (levels, with ids and labels) one plain line per shift type, and for each type and pool the people added or removed and who said Not Interested. Pay is in cents. Read this before changing a shift type or pool"},{"method":"PATCH","path":"/api/v1/program/moonlighting/settings","auth":"bearer","scope":"admin","summary":"Patch window {period,opensDaysBefore,closesDaysBefore}, windowOverrides and release rules. Optional expectedSettings is the original full GET settings.config; changes return 409 without saving. Omitted fields stay. null clears windowOverrides, releasePickHours, releaseStillOpenHours, releaseOpenHoursBefore or cancelGraceHours. Types and pools use their own routes"},{"method":"GET","path":"/api/v1/program/moonlighting/templates","auth":"bearer","scope":"admin","summary":"Common moonlighting setups to start from: shift types (evening ultrasound prelims, evening and weekend neuro reads, weekend ultrasound, contrast coverage, outside site weekend, pediatric weekend, IR weekend) and jeopardy pools (weekend and weekday backup), each with the classes it picks in this program by place (such as the two most senior classes). Pay is blank. Add one with POST /program/moonlighting/types after the admin sets the pay"},{"method":"POST","path":"/api/v1/program/moonlighting/types","auth":"bearer","scope":"admin","summary":"Add a moonlighting (paid extra shift) type {id? (made from the name when left out), name, code? (1-3 letters, made from the name when left out), levelIds or classes (level ids or labels such as [\"R2\"]; empty means every level), days ([0-6], 0 Sunday, or words such as \"Sat\"), holidays? (normal, add, skip or only), start, end (\"07:00\" or \"7 AM\"; an end at or before the start ends the next day), seats? (1), pay? (dollars) or payCents?, payByDate? ({\"YYYY-MM-DD\": cents} for dates that pay differently), overtimePerHour? (dollars) or overtimeCentsPerHour?, worksAlongsideCallKinds? (short, weekend, holiday or baby: call a person may hold the same day), maxPerYear?, maxPerMonth?, duties?, note?, mode? (request is Assign Fairly, the default: fewest shifts so far wins; first-come, release, lottery, snake, straight, seniority, ranked, bidding, round-robin or admin only when asked), pickOrder? (snake, straight, seniority or round-robin only: person ids or full names, first pick first, to set the pick order by hand for every round of this type; null or left out is the automatic order; a snake draft still reverses it each round; people no longer eligible drop out and newly eligible people go to the end), openLevelIds?, unlockRotationIds? (rotation ids or names a resident must finish before the shift date), unlockMode? (any or all), includePeople? and excludePeople? ([{personId, reason?}]: added whatever their class or rotations, or removed whatever else says), countsAsMoonlighting? (true), sendMessages? (false), hoursVary? (false: true when the hours change from shift to shift, so people enter the hours worked), enabled? (true)}. Unknown fields are refused. Backup (jeopardy) rosters are pools: POST /program/moonlighting/pools. Returns the saved type and a plain line"},{"method":"PATCH","path":"/api/v1/program/moonlighting/types/:id","auth":"bearer","scope":"admin","summary":"Patch shift type fields accepted by POST types; id stays fixed. enabled:false retains history. null clears optional pay, limits, notes, pickOrder or access rules. Optional expectedType is the original full type from GET settings.config.types; changes return 409 without saving. Omitted fields stay"},{"method":"DELETE","path":"/api/v1/program/moonlighting/types/:id","auth":"bearer","scope":"admin","summary":"Remove a shift type with no seats or requests; otherwise 409 protects history, so PATCH enabled:false instead. Optional JSON body {expectedType: original full type} refuses changed types with 409. An omitted body keeps legacy behavior"},{"method":"POST","path":"/api/v1/program/moonlighting/pools","auth":"bearer","scope":"admin","summary":"Add backup pool {id? (made from the name when left out), name, levelIds or classes, covers: weekend|weekday|custom, startDays? (0 Sunday to 6 Saturday), durationDays? (1–7), hours? {start,end} (HH:MM), typeIds?, unlockRotationIds?, unlockMode?, includePeople?, excludePeople?}. Custom requires startDays and durationDays; units cannot overlap. Omitted typeIds backs shift types with matching classes."},{"method":"PATCH","path":"/api/v1/program/moonlighting/pools/:id","auth":"bearer","scope":"admin","summary":"Change a jeopardy pool {name?, levelIds?, covers?, startDays?, durationDays?, hours? {start,end}, typeIds?, unlockRotationIds?, unlockMode?, includePeople?, excludePeople?, expectedPool?}; null clears hours (follow shifts), typeIds (matching classes) or a rotation or people rule. Optional expectedPool is the original full pool from GET settings config.jeopardyPools; any changed field returns 409 without saving"},{"method":"DELETE","path":"/api/v1/program/moonlighting/pools/:id","auth":"bearer","scope":"admin","summary":"Remove a jeopardy pool with no assigned roster units. Assigned pools must remain to preserve history. Optional JSON body {expectedPool: original full pool from GET settings config.jeopardyPools} returns 409 without deleting if any pool field changed; an omitted body keeps legacy behavior"},{"method":"GET","path":"/api/v1/program/moonlighting/jeopardy","auth":"bearer","scope":"admin","summary":"The jeopardy roster ?year=&poolId=: for each pool, every configured backup unit in the academic year with who has it, how many are set and open, and each member's count"},{"method":"PUT","path":"/api/v1/program/moonlighting/jeopardy","auth":"bearer","scope":"admin","summary":"Set or clear one jeopardy unit {poolId, start (the Saturday of a weekend or the Monday of a weekday week), personId (null leaves it open), year?, override? (true places someone outside the pool's classes), expectedPersonId? (original person ID or null; a changed assignment returns 409 without saving)}"},{"method":"POST","path":"/api/v1/program/moonlighting/jeopardy/fill","auth":"bearer","scope":"admin","summary":"Fill Evenly {poolId, year?, dryRun?}: fill open units fairly; existing assignments stay. dryRun:true saves nothing and returns planId and result.rows {start,end,personId,previousPersonId,name}, result.placed and result.open. Apply once: POST /api/v1/plans/:id/apply. Changed workplace inputs return 409; preview again. Omit dryRun to fill immediately"},{"method":"POST","path":"/api/v1/program/moonlighting/jeopardy/import","auth":"bearer","scope":"admin","summary":"Import a jeopardy roster CSV for one pool {poolId, csv (columns Category, Dates, Jeopardy Resident; dates such as 7/4 - 7/5 fall in the program's own academic year), year?, apply?}. Without apply true it only previews: every row with the resident it matched, or why it did not match. Names that match nobody or more than one person are never saved"},{"method":"GET","path":"/api/v1/program/moonlighting/windows","auth":"bearer","scope":"admin","summary":"Sign-up windows by period ?typeId=&from=&to= (YYYY-MM-DD; default last month to a year ahead): each period (key such as 2026-10 or 2026:b3) with when sign-up opens and closes (dates, HH:MM clocks, instants), whether it was set by hand, status (upcoming, open or closed), seats and requests on file, and its Timed Release batches (opensAt, closesAt, cap per person, early cap, seats, taken). Times are in the program's time zone"},{"method":"POST","path":"/api/v1/program/moonlighting/windows/defaults","auth":"bearer","scope":"admin","summary":"Review default clocks and zone {expectedStoreRevision, window: {opensTime, closesTime, timeZone}, confirm?}. Null clears a clock or inherits the workplace zone. Use dryRun then plan apply. Preview includes configured years and custom periods; zone changes reproject custom clocks. Fixed batches and assignments stay. Ambiguous or missing clocks are refused"},{"method":"POST","path":"/api/v1/program/moonlighting/windows/:key","auth":"bearer","scope":"admin","summary":"Change one period's sign-up window {action: set (opensOn, closesOn YYYY-MM-DD; opensTime?, closesTime? HH:MM, null for the start and end of the day) | reset (back to the default rule) | copy-previous (same timing as the period before) | apply-to-all (this timing becomes the rule for every period; windows that already opened keep their dates), confirm?}. Moving a window that closed or holds sign-ups into the past is refused with needsConfirm and a sentence to show; send confirm true to go ahead. Nothing on file is ever removed"},{"method":"POST","path":"/api/v1/program/moonlighting/windows/:key/batches","auth":"bearer","scope":"admin","summary":"Timed Release batches in one period {action: add (typeId, opensAt, closesAt?, cap?, early? {picks, minutes}: releases every open seat of the type in the period) | update (id and any of opensAt, closesAt (null: through the last date), cap, early; an opened batch keeps its open time) | remove (id; not once it opened) | copy-previous (typeId: the period before's batches at the same distance from the start) | apply-to-all (typeId: this period's batches into every later period that has none)}. Times \"YYYY-MM-DDTHH:MM\" in the program's time zone or ISO. A seat can be in one open batch at a time"},{"method":"GET","path":"/api/v1/program/moonlighting/rounds","auth":"bearer","scope":"own|admin","summary":"Moonlighting sign-up rounds touching ?from=&to= (YYYY-MM-DD; default the last five weeks to six months ahead). Assign Fairly (mode request: residents ask, fewest shifts so far wins) is the default and recommended for every shift type; a round runs another method only when an admin set one up or set a shift type to it: first-come, release (Timed Release: a batch of seats opens at a set time, first come with a cap each), lottery, snake, straight, seniority (drafts, pre-ranked or live), ranked (Best Overall Fit), bidding (Points Bidding), round-robin (by date) or admin (Admin Assigns). Each round: id, name, method, methodName, stage (upcoming, collecting, ready, previewed, live, paused, racing, published), shift types, dates, window, settings, slots (\"typeId|YYYY-MM-DD\" with seats open), participants (admins), draft board, release feed and result. Residents get only the rounds they are in, with their own ranking or bids"},{"method":"GET","path":"/api/v1/program/moonlighting/rounds/:id","auth":"bearer","scope":"own|admin","summary":"One moonlighting round (an automatic one has an id like auto-lottery-2026-11). Expired live draft clocks move on first. Admins see settings, seed, the preview with per-person counts and pay, unfilled seats and the audit log; residents see the method, window, seats, their own ranking or bids, their draft position, the live board and published results"},{"method":"POST","path":"/api/v1/program/moonlighting/rounds","auth":"bearer","scope":"admin","summary":"Set up a moonlighting round {name?, method (request is Assign Fairly, the default; use another method only when the admin asks for it), typeIds (ids or names), month (YYYY-MM) or start and end (any range up to a year, such as July to January), opensOn?, closesOn? (the window residents send requests, rankings or bids), participants? (person ids; default everyone eligible minus Not Interested), settings? or the same keys at the top: maxPicks (most seats per person in this round), balance (lottery: one win each before seconds, default true), live (drafts: picks in the app on a clock), order (random, fewest, admin or seniority), pattern (seniority: straight or snake), rotateFirst (straight), rounds, clock ([{fromRound, seconds}], default 2 minutes for rounds 1 to 5, then 1 minute), onTimeout (auto-pick or skip), budget and carryover (bidding), leftovers (first-come or admin), early ({picks, minutes}: timed release cap in the first minutes)}. A timed release also takes seats ([\"typeId|YYYY-MM-DD\"]), all (every open seat in the range) or next (the next N open seats) and opensAt (\"YYYY-MM-DDTHH:MM\" in the program's time zone, ISO or now), closesAt?"},{"method":"PATCH","path":"/api/v1/program/moonlighting/rounds/:id","auth":"bearer","scope":"admin","summary":"Change a moonlighting round before it runs: any field from POST /program/moonlighting/rounds, such as {method: \"lottery\"} or {maxPicks: 3} or {participants: null} for everyone eligible. After publishing or once a live draft starts only the name changes (a timed release may also change closesAt and settings)"},{"method":"DELETE","path":"/api/v1/program/moonlighting/rounds/:id","auth":"bearer","scope":"admin","summary":"Remove a moonlighting round that has not started or published. An automatic round goes back to its defaults. Seats it gave stay on the schedule"},{"method":"POST","path":"/api/v1/program/moonlighting/rounds/:id/run","auth":"bearer","scope":"admin","summary":"Run a round's method as a preview {seed?}: a lottery draw, a pre-ranked draft, Best Overall Fit, Points Bidding, Round Robin or Assign Fairly. Nothing is placed until publish; run again for a new seed. Every seat respects the shift's classes and rotations, limits, overlaps, Not Interested and the round's most picks. Returns the preview with per-person counts and pay, unfilled seats and the seed"},{"method":"POST","path":"/api/v1/program/moonlighting/rounds/:id/publish","auth":"bearer","scope":"admin","summary":"Publish a round's preview: every seat is checked again on today's data (a change since the run stops the publish so it can run again), placed as ordinary seats, unmet requests are cleared, residents are told and the round is locked"},{"method":"POST","path":"/api/v1/program/moonlighting/rounds/:id/discard","auth":"bearer","scope":"admin","summary":"Discard a round's unpublished preview so its settings can change or requests reopen"},{"method":"POST","path":"/api/v1/program/moonlighting/rounds/:id/order","auth":"bearer","scope":"admin","summary":"Draft order or Round Robin rotation {action: shuffle (seeded, by the order setting) | set (ids: person ids or names, first pick first; for this round only) | reset (back to the shift type's pickOrder, else automatic) | lock, seed?}. Snake still reverses each round. Newly eligible people go to the end; the ineligible drop out. A locked order cannot change"},{"method":"POST","path":"/api/v1/program/moonlighting/rounds/:id/draft","auth":"bearer","scope":"admin","summary":"Run a live draft {action: start | pause | resume | skip (the person on the clock, for this round) | undo (the last pick; its seat is removed) | end | pick (slot \"typeId|YYYY-MM-DD\" and personId: record the pick for the person on the clock)}. Every pick is a real seat at once"},{"method":"POST","path":"/api/v1/program/moonlighting/rounds/:id/release","auth":"bearer","scope":"admin","summary":"Run a timed release {action: open-now | close (leftover seats go first come or stay for the admin, by the leftovers setting) | extend (closesAt, or null for no close) | add-seats (seats)}"},{"method":"GET","path":"/api/v1/program/moonlighting/rounds/:id/results","auth":"bearer","scope":"own|admin","summary":"A round's results ?format=json|csv: per person seats, count, pay, points spent and choice numbers, seats left open, the seed and log (admins). CSV for admins only. Residents read results once published"},{"method":"PUT","path":"/api/v1/program/moonlighting/rounds/:id/ranking","auth":"bearer","scope":"own","summary":"Your ranking for a draft or Best Overall Fit round {ranking: [\"typeId|YYYY-MM-DD\", ...] best first}, while its window is open (or during a live draft, for auto-picks)"},{"method":"PUT","path":"/api/v1/program/moonlighting/rounds/:id/bids","auth":"bearer","scope":"own","summary":"Your bids for a Points Bidding round {bids: {\"typeId|YYYY-MM-DD\": points}}, whole points within your budget, while its window is open"},{"method":"POST","path":"/api/v1/program/moonlighting/rounds/:id/pick","auth":"bearer","scope":"own","summary":"Take a seat {slot: \"typeId|YYYY-MM-DD\"}: in a live draft when you are on the clock, or in an open timed release (first confirmed wins; a seat someone just took is refused as Just taken)"},{"method":"POST","path":"/api/v1/program/moonlighting/rounds/:id/pass","auth":"bearer","scope":"own","summary":"Sit out the rest of a live draft"},{"method":"POST","path":"/api/v1/program/requests/:id/decide","auth":"bearer","scope":"admin","summary":"Decide a resident request {status: approved|denied|pending}; ?year="},{"method":"POST","path":"/api/v1/program/requests/:id/suggest","auth":"bearer","scope":"admin","summary":"Suggest dates for a flexible vacation request inside its window, on the best vacation rotation with the fewest call days; saves startDate and endDate on the waiting request; ?year="},{"method":"GET","path":"/api/v1/program/rules","auth":"bearer","scope":"admin","summary":"Every rule: requirements, gates, totals, items (each with type and id), levels, call settings, xyPattern (alternating weeks, or undefined), openBlockRotation (per level) and requestSections"},{"method":"PATCH","path":"/api/v1/program/rules","auth":"bearer","scope":"admin","summary":"Apply rule patches {patches: RulePatch[], call? (the fields of PATCH /program/rules/call)}; POST is accepted too. A RulePatch is a flat object with target (requirement, gate, total, capacity, rotationLevels, rotationVacation, callTarget, callStart, addRequirement or addGate) and only the fields it needs: id, rotationId, rotationIds, levelId, levelIds, kind, blocks, x, y, fromBlock, toBlock, count, severity (hard or soft), enabled, max, min, capacityPerBlock, levels, vacation, key (short, weekend, holiday, baby), value, fromYear, untilYear, fromCohort, atLeast, perCohort, softForPathways, softNote, label, evidence. Returns {applied, errors}; prefer the rule item routes for one rule"},{"method":"GET","path":"/api/v1/program/rules/items","auth":"bearer","scope":"admin","summary":"List every rule as {type, id, ...fields}; type is requirement, total, or gate (gates carry kind)"},{"method":"POST","path":"/api/v1/program/rules/items","auth":"bearer","scope":"admin","summary":"Add a rule {type, ...fields}; gates need kind (before, notInBlocks, window, beforeFirstCall, maxOfGroupPerBlock, minGap, maxPerYear). Returns 201 {rule}; 400 lists errors; 409 on a taken id"},{"method":"GET","path":"/api/v1/program/rules/items/:id","auth":"bearer","scope":"admin","summary":"One rule by id"},{"method":"PATCH","path":"/api/v1/program/rules/items/:id","auth":"bearer","scope":"admin","summary":"Change some fields of a rule; null clears an optional field; a gate's kind may change. Checked like the Rules page"},{"method":"DELETE","path":"/api/v1/program/rules/items/:id","auth":"bearer","scope":"admin","summary":"Delete a rule; the next generate stops using it"},{"method":"PATCH","path":"/api/v1/program/rules/call","auth":"bearer","scope":"admin","summary":"Change call settings (read them with GET /program/rules/call): weekend (split and full seat classes, firstStartBlockByLevel), holidays (major, minor, equalWeight), oneMajorPerResidency, holidayWeekendTrade, perDay, weekendSeats, weekday (1st/2nd call classes, halves), maxHolidaysPerYear, maxHolidaysFromYear, targetsByLevel, startBlockByLevel, spacing, weekdaySpread ({kinds, levelIds}), timeZone (must match workplace settings), limits, babyWeekend (secondCall: 0 drops the all-day second call on split weekends, 1 keeps it from the second call classes; firstCallHalves is always 2, one morning and one afternoon half; amLevelId and pmLevelId pick the level for the morning and afternoon halves; fromMonthDay and untilMonthDay (MM-DD); season: {start, end, skipHolidayWeekends, reason}, edges {month, weekend} or {holiday, weekendsBefore}), dutyTimes, slotTimes ({first-am, first-pm}: {start, end} or null each, clocks for the morning and afternoon halves of a split call day, used instead of dutyTimes when a call day has that slot; null clears), enabled, noCall (true confirms the program takes no call while enabled is false). null clears weekday, weekend, weekdaySpread, limits, babyWeekend (and its season), maxHolidaysPerYear, maxHolidaysFromYear and slotTimes. Unknown keys are refused. Returns {changed, call}"},{"method":"GET","path":"/api/v1/program/rules/open-blocks","auth":"bearer","scope":"admin","summary":"Open block rotation per class: the rotation Generate puts in blocks with nothing required or requested; a class left out lets Generate choose electives"},{"method":"PATCH","path":"/api/v1/program/rules/open-blocks","auth":"bearer","scope":"admin","summary":"Set open block rotations {R4: \"elective\"}; null clears a class. Unknown classes or rotations save nothing"},{"method":"GET","path":"/api/v1/program/rules/request-sections","auth":"bearer","scope":"admin","summary":"Class request sections: extra choices on the request form for one class (forLevelId), shown to residents who will be in that class in an unpublished year"},{"method":"POST","path":"/api/v1/program/rules/request-sections","auth":"bearer","scope":"admin","summary":"Add a request section {title, forLevelId, intro?, id?, kind: electives, options: {fellowships: [{rotationId, label}], fellowshipBlocks: {initial, min, max}, electiveIds}}. Returns 201 {section}; 409 on a taken id"},{"method":"PUT","path":"/api/v1/program/rules/request-sections","auth":"bearer","scope":"admin","summary":"Replace every request section {sections: [...]}; an empty list removes them all"},{"method":"GET","path":"/api/v1/program/rules/request-sections/:id","auth":"bearer","scope":"admin","summary":"One request section"},{"method":"PATCH","path":"/api/v1/program/rules/request-sections/:id","auth":"bearer","scope":"admin","summary":"Change some fields of a request section; options fields left out keep their values"},{"method":"DELETE","path":"/api/v1/program/rules/request-sections/:id","auth":"bearer","scope":"admin","summary":"Remove a request section; plans already sent stay on Requests"},{"method":"PATCH","path":"/api/v1/program/rotations/:id","auth":"bearer","scope":"admin","summary":"Change a rotation: fixedDates, study, offsite, leave, callExempt (true keeps the rotation off call), series ({name, step}: a numbered sequence run in order, each step once; step 4.5 is a variant between 4 and 5; null takes the rotation out of a series), shortCode, ownCall ({callDays, maxShorts?} or null; maxShorts: regular weekday shorts a block before a note), levels, capacityPerBlock, callKinds, elective, nightFloat, repeatable, aliases, name, section, shortCallAvoided, vacation (preferred, allowed, discouraged, blocked; blocked shows as Not Allowed; null clears to allowed), workday (usual hours for work hour logs {start: HH:MM, end: HH:MM, days: [0 Sunday..6 Saturday], byDay?: {0..6: {start, end}} for single weekdays, holiday?: {start, end} worked on program holidays}; an end at or before the start ends the next day; null uses the program's workday). Returns {changed, rotation, shortCallAvoided}"},{"method":"GET","path":"/api/v1/program/hours","auth":"bearer","scope":"own|admin","summary":"Work hour periods (ACGME sampling periods). Admins: every period with state (upcoming, open, overdue, closed), counts {included, submitted, notYet, reopened} and setup gaps (call kinds without duty times, night rotations without hours). Residents: the periods that include them with myStatus (submitted, reopened, not-yet). ?view=mine: an admin's own resident view"},{"method":"POST","path":"/api/v1/program/hours","auth":"bearer","scope":"admin","summary":"Open a work hour period {name, start, end (YYYY-MM-DD, at most 62 days), due? (default a week after end), levelIds? (classes; empty means all residents)}. Classes resolve to the residents in them on the first day. Sends nothing"},{"method":"GET","path":"/api/v1/program/hours/:id","auth":"bearer","scope":"admin","summary":"Review one period: each included resident's status, total hours, weekly average, ACGME issues, edited days and signedAt. Residents who have not saved are shown from the published schedule (fromSchedule: true). ?signatures=true adds each submitted signaturePng (PNG data URL)"},{"method":"PATCH","path":"/api/v1/program/hours/:id","auth":"bearer","scope":"admin","summary":"Close a period so residents can no longer save {closed: true}, or open it again {closed: false}"},{"method":"DELETE","path":"/api/v1/program/hours/:id","auth":"bearer","scope":"admin","summary":"Delete a period made by mistake. Refused once any resident has submitted; close it instead"},{"method":"GET","path":"/api/v1/program/hours/:id/compliance","auth":"bearer","scope":"admin","summary":"ACGME compliance summary for one period: per resident status, total hours, weekly average, edited days and the plain-language violations and cautions (80 hours averaged over four weeks, one day off in seven, 24 plus 4 continuous hours, 14 hours off after 24, 8 hours between shifts, in-house call every third night)"},{"method":"GET","path":"/api/v1/program/hours/:id/export","auth":"bearer","scope":"admin","summary":"CSV for the GME office (text/csv): resident, class, status, date, in, out, type, hours, edited, note; one row per entry, a day off is one row typed Off"},{"method":"POST","path":"/api/v1/program/hours/:id/remind","auth":"bearer","scope":"admin","summary":"Email every included resident who has not submitted a link to confirm their hours, and text those who turned texts on. Sends real email and texts that cannot be undone: call only when the admin explicitly asks to remind"},{"method":"GET","path":"/api/v1/program/hours/:id/residents/:personId","auth":"bearer","scope":"admin","summary":"One resident's days in a period (saved or from the schedule), their status, the ACGME checks and their signature (log.signaturePng, log.signedAt)"},{"method":"POST","path":"/api/v1/program/hours/:id/residents/:personId/reopen","auth":"bearer","scope":"admin","summary":"Reopen a resident's submitted hours so they can edit and submit again. Clears their signature; the re-submit is signed again"},{"method":"PUT","path":"/api/v1/program/hours/:id/residents/:personId","auth":"bearer","scope":"admin","summary":"Edit a resident's days as an admin {days (same shape as PUT mine), reason (required, at most 500 characters)}. Kept on the log as \"Edited by <admin> on <date>: <reason>\" and in Activity. Submitted hours stay submitted, marked changed after signing, until the resident confirms again. Emails the resident the change (and texts them if they turned texts on)"},{"method":"GET","path":"/api/v1/program/hours/:id/gme","auth":"bearer","scope":"admin","summary":"GME Report for a period: program, dates, submitted count, a count for each ACGME rule, one row per resident (total, weekly average, busiest week, days off per 7, longest continuous shift, short rests, moonlighting, status, submitted date) and every issue. ?signatures=true adds each submitted signaturePng; ?logs=true adds report.logs, each resident's days, week and period totals and admin edits"},{"method":"GET","path":"/api/v1/program/hours/:id/gme/export","auth":"bearer","scope":"admin","summary":"CSV for the GME office (text/csv), one row per resident per week: resident, class, status, week start and end, days in period, total, in-house, moonlighting, home call and work from home hours, days off and ACGME flags"},{"method":"GET","path":"/api/v1/program/hours/:id/mine","auth":"bearer","scope":"own","summary":"Your own days for a period, pre-filled from the published schedule (rotation workday and call duty times; leave, vacation and holidays off unless on call), with weekly totals and ACGME issues"},{"method":"PUT","path":"/api/v1/program/hours/:id/mine","auth":"bearer","scope":"own","summary":"Save your days without submitting {days: [{date, entries: [{type: clinical|call|moonlight-internal|moonlight-external|home-call|home-work, start: HH:MM, end: HH:MM, nextDay?}], note?}]}. Days left out keep the schedule. Changed days are marked edited"},{"method":"GET","path":"/api/v1/program/hours/:id/mine/export","auth":"bearer","scope":"own","summary":"Your own days for a period as CSV (text/csv): date, in, out, type, hours, edited, note; one row per entry, a day off is one row typed Off"},{"method":"POST","path":"/api/v1/program/hours/:id/mine/submit","auth":"bearer","scope":"own","summary":"Submit your hours {attest: true, signature, days?}. signature is the resident's own drawn signature as a PNG data URL (at most 200 KB); an assistant cannot sign for them or use their saved signature, so send the resident to Confirm Your Hours to sign. Without days it submits what you saved, or the schedule as is (Confirm All As Scheduled). Submitted hours are read-only until an admin reopens them. Opens the day after the period ends (409 before then). On hours an admin changed after they were signed, it confirms the admin's days again with a new signature"},{"method":"POST","path":"/api/v1/program/rules/talk","auth":"bearer","scope":"admin","summary":"Describe a rule change in plain words {text, year?, classes?, apply?}. Returns the proposed patches and what they would change; apply:true saves them. Needs the server's AI key"},{"method":"PATCH","path":"/api/v1/program/year","auth":"bearer","scope":"admin","summary":"Select current academic year {year}"},{"method":"POST","path":"/api/v1/program/year/open","auth":"bearer","scope":"admin","summary":"Open a planning year {year}, creating it from the year before when missing"},{"method":"POST","path":"/api/v1/program/publish","auth":"bearer","scope":"admin","summary":"Publish a year {year}. 400 with reasons when it is not ready (empty cells, Must Fix issues, missing call)"},{"method":"POST","path":"/api/v1/program/residents","auth":"bearer","scope":"admin","summary":"Add a resident to any class {name, email?, phone?, class? (a class id or label from get_program_classes, or incoming), pathway? (a pathway id from get_program_pathways; the base pathway when left out), startYear? (instead of class; default next year), activeFrom?, shownOnly?, notes?}. With personId instead of name, places someone already on the roster (staff with no class) in the class. The class is read in the academic year holding today"},{"method":"POST","path":"/api/v1/program/residents/list","auth":"bearer","scope":"admin","summary":"A pasted class list {text, apply?}: one person per line, name, email, class and pathway by comma or tab. Without apply returns the checked rows (class, pathway, the roster entry an email places, and errors); apply:true adds every row without an error and returns added, ids, skipped and errors"},{"method":"GET","path":"/api/v1/program/residents/:id","auth":"bearer","scope":"admin","summary":"One resident's program profile: class this year, start year, pathway, notes, active dates, shownOnly (on the schedule for reference only: never generated, never on call, never tracked, cells still take room seats), linked (has an account), and carry-overs, early pulls, leaves and rule exceptions with their index, and imported callTallies"},{"method":"PATCH","path":"/api/v1/program/residents/:id","auth":"bearer","scope":"admin","summary":"Move a resident {class? (class id or label, or incoming; read in the academic year holding today), startYear? (instead of class), pathway?, shownOnly? (true or false; see GET /api/v1/program/residents/:id), notes? (up to 500 characters)}. Schedule history stays where it is"},{"method":"POST","path":"/api/v1/program/residents/:id/carry-overs","auth":"bearer","scope":"admin","summary":"Record a rotation carried over {rotationId (id or name), fromYear (one of the resident's academic years), blocks? (standard blocks owed, default 1), dueLevelId? (class it is due by), note?}. Next year's plan shows it as a reminder until placed"},{"method":"DELETE","path":"/api/v1/program/residents/:id/carry-overs","auth":"bearer","scope":"admin","summary":"Remove a carry-over {index} (from get_program_residents_by_id)"},{"method":"POST","path":"/api/v1/program/residents/:id/early-pulls","auth":"bearer","scope":"admin","summary":"Record a rotation done ahead of its class {rotationId (id or name), year (one of the resident's academic years), blockIndex, note?}"},{"method":"DELETE","path":"/api/v1/program/residents/:id/early-pulls","auth":"bearer","scope":"admin","summary":"Remove an early pull {index} (from get_program_residents_by_id)"},{"method":"POST","path":"/api/v1/program/residents/:id/leaves","auth":"bearer","scope":"admin","summary":"Record leave on file {kind: parental|vacation|absence, start, end (YYYY-MM-DD), note?}. Generate and call keep the resident off those dates; no grid cell changes (POST /program/leave also places it on the grid)"},{"method":"DELETE","path":"/api/v1/program/residents/:id/leaves","auth":"bearer","scope":"admin","summary":"Remove leave on file {index} (from get_program_residents_by_id). Grid cells already placed stay"},{"method":"POST","path":"/api/v1/program/residents/:id/exceptions","auth":"bearer","scope":"admin","summary":"Record a rule exception {ruleId (a program rule, cap-<rotation id>, or an issue's rule id), scope? (text the issue key holds, such as a year; empty means every issue of that rule), reason}. Matching issues for this resident read as accepted"},{"method":"DELETE","path":"/api/v1/program/residents/:id/exceptions","auth":"bearer","scope":"admin","summary":"Remove a rule exception {index} (from get_program_residents_by_id)"},{"method":"PUT","path":"/api/v1/program/residents/:id/call-tallies","auth":"bearer","scope":"admin","summary":"Set an imported call tally for one of the resident's years {year, short?, weekend?, holiday?, baby? (counts, default 0), majorHolidaysWorked? (names from the program's major holidays)}. Replaces that year's tally"},{"method":"DELETE","path":"/api/v1/program/residents/:id/call-tallies","auth":"bearer","scope":"admin","summary":"Remove an imported call tally {year}"},{"method":"POST","path":"/api/v1/program/residents/:id/link","auth":"bearer","scope":"admin","summary":"Link to Roster Entry {personId}: fold a staff entry made when someone joined by invite with an email the roster did not have into this resident. The resident takes the account and email, records naming the staff entry move to the resident, and the staff entry leaves the roster. Refused when the resident is linked to another account or the two have different access"},{"method":"GET","path":"/api/v1/program/rotations/:id","auth":"bearer","scope":"admin","summary":"One rotation with every field, plus shortCallAvoided (weekday short call only as a last resort there)"},{"method":"GET","path":"/api/v1/program/rules/call","auth":"bearer","scope":"admin","summary":"Call settings as saved: {call} with enabled, noCall, perDay, weekendSeats, weekday, weekend, targetsByLevel, startBlockByLevel, spacing, limits, holidays, babyWeekend, dutyTimes, slotTimes, weekdaySpread, longDutyRecovery and timeZone"},{"method":"GET","path":"/api/v1/program/moonlighting/sheet-sync","auth":"bearer","scope":"admin","summary":"Spreadsheet sync for moonlighting: whether a Google Sheet is connected, its settings, conflicts, history, version and the schedule revision. Needs a signed-in workplace (503 without one)"},{"method":"POST","path":"/api/v1/program/moonlighting/sheet-sync","auth":"bearer","scope":"admin","summary":"Run one spreadsheet sync action {action, version (from GET)}: preview {config}; configure {config, revision, fingerprint (from preview)}; disconnect {revision}; sync; app or sheet {key} to settle one conflict; pause, resume or rollback {historyId?}. Returns the sync view; 409 when the version is stale"},{"method":"GET","path":"/api/v1/program/qgenda-sync","auth":"bearer","scope":"admin","summary":"QGenda sync review: connected board, sources, coverage rotations, differences to decide and version. Needs a signed-in workplace (503 without one)"},{"method":"POST","path":"/api/v1/program/qgenda-sync","auth":"bearer","scope":"admin","summary":"One QGenda sync action: connect {year, url}; add-source {version, label, url}; coverage-rotations {version, url, rotationIds}; sync {version}; app or source {version, key, stamp} to settle a difference; pause or resume {version}. Returns the sync view; 409 when the version is stale"},{"method":"POST","path":"/api/v1/program/rotations","auth":"bearer","scope":"admin","summary":"Add a rotation {name, section?, levels? (level ids; every level when left out), capacityPerBlock? (null for no limit), elective?, callExempt? (true keeps it off call), offsite?, nightFloat?, callKinds? (short, weekend, holiday, baby), series? ({name, step} or null)}. Returns 201 {rotation: {id, name, section, levels, capacityPerBlock}}; change other fields with PATCH /program/rotations/:id"},{"method":"DELETE","path":"/api/v1/program/rotations/:id","auth":"bearer","scope":"admin","summary":"Remove a rotation nothing uses (a starter rotation the program does not run). Refused with 409 naming each use (rules, open blocks, request sections, sign-ups, moonlighting, windows, any year's grid or requests, carry-overs, early pulls) until those change"},{"method":"GET","path":"/api/v1/program/structure","auth":"bearer","scope":"admin","summary":"Program name, specialty, classes (levels: id, label, order), calendar (mode, anchorMonth, anchorDay, weekday, blockCount, blockWeeks, blockWeeksList, specialBlocks), usual hours (workday), wording, xyPattern, yearlyRequestWindow and seriesOrder (Numbered Series Order: hard Must Follow, soft Prefer, off)"},{"method":"PATCH","path":"/api/v1/program/structure","auth":"bearer","scope":"admin","summary":"Change {name?, specialty?, levels? (every class in order: id, label, order 1..n), calendar? (the whole calendar: anchorMonth, anchorDay, weekday, blockCount, blockWeeks, blockWeeksList?, specialBlocks [{blockIndex, kind physics|study|other, label}], mode?), workday? ({start, end, days, byDay?, holiday?} or null), seriesOrder? (hard default, soft, off)}. Class order and dates lock once people or schedules exist"},{"method":"GET","path":"/api/v1/program/windows","auth":"bearer","scope":"admin","summary":"Fixed-date windows: the calendar dates a Fixed Calendar Dates rotation (AIRP) runs each academic year; ?rotationId=&year=. Also lists the fixed-date rotations"},{"method":"POST","path":"/api/v1/program/windows","auth":"bearer","scope":"admin","summary":"Add a fixed-date window {rotationId (a rotation with fixedDates), year (academic start year; a year not opened yet works too, and Generate uses its windows once it is opened), start, end (YYYY-MM-DD, both inside that academic year)}. Returns 201 {window} with its id. Without windows in a year the rotation is not scheduled that year"},{"method":"DELETE","path":"/api/v1/program/windows/:id","auth":"bearer","scope":"admin","summary":"Remove a fixed-date window"},{"method":"POST","path":"/api/v1/program/years/past","auth":"bearer","scope":"admin","summary":"Add a past academic year {year} (up to ten years back, not on file) as a record-only history year, then fill it with PUT /program/grid {year, cells} and POST /program/call {calls} with ?year=. Never generated"},{"method":"DELETE","path":"/api/v1/program/call","auth":"bearer","scope":"admin","summary":"Clear one call day {personId, date, year?}"},{"method":"PUT","path":"/api/v1/program/request-window","auth":"bearer","scope":"admin","summary":"Set a year's request window {year, open, close} (YYYY-MM-DD), or {year, action: close|reopen}. {action: yearly, open, close} (MM-DD) sets Every Year, which also opens the year being planned; {action: yearly, open: null} turns it off"},{"method":"PUT","path":"/api/v1/program/wording","auth":"bearer","scope":"admin","summary":"Set the program's words {wording: {person, people, level, levels, rotation, rotations, block, blocks, call, program, extraShifts}}, each up to 24 characters. Blank or missing words read the defaults; an empty object restores them all"},{"method":"PUT","path":"/api/v1/program/xy-pattern","auth":"bearer","scope":"admin","summary":"Set alternating inpatient and clinic weeks {pattern: {anchorDate (on the calendar's start weekday), inpatientWeeks, ambulatoryWeeks (1..26), ambulatoryRotationIds, protectCall?}, offsets? {residentId: weeks}}. The first save converts an empty draft program to X+Y blocks"},{"method":"GET","path":"/api/v1/program/request-period","auth":"bearer","scope":"own|admin","summary":"Resident Request Periods: the dates residents send requests for (start, end), when requests open and are due (open, due), status (open, upcoming, closed) and the classes it is for (levelIds, empty for every class), plus the planning year's dates as the default. Residency requests are checked against these periods only; a resident request is refused when its dates fall outside every period or today is before open or after due. Admins may add or change any request"},{"method":"PUT","path":"/api/v1/program/request-period","auth":"bearer","scope":"admin","summary":"Set a Resident Request Period {id? (to change one; leave out to add), start, end (the dates requests are for), open, due (YYYY-MM-DD), levelIds? (class ids from get_program, leave out for every class)}, or close or reopen one {id, action: close|reopen}"},{"method":"PATCH","path":"/api/v1/program/rules/leave","auth":"bearer","scope":"admin","summary":"Leave settings {vacationDays?, parentalLeaveWeeks?, carryOverLevelId?}"},{"method":"GET","path":"/api/v1/program/comments","auth":"bearer","scope":"admin","summary":"Comments on the program, newest first; ?open=true for unresolved, ?year="},{"method":"POST","path":"/api/v1/program/comments/:id/reply","auth":"bearer","scope":"admin","summary":"Reply to a comment {text}"},{"method":"POST","path":"/api/v1/program/comments/:id/resolve","auth":"bearer","scope":"admin","summary":"Resolve a comment {resolved?: false to reopen}"},{"method":"POST","path":"/api/v1/program/acknowledgements","auth":"bearer","scope":"admin","summary":"Mark an issue as OK {year?, key (from hits), reason}"},{"method":"DELETE","path":"/api/v1/program/acknowledgements","auth":"bearer","scope":"admin","summary":"Undo Marked as OK for an issue {year?, key}"},{"method":"GET","path":"/api/v1/shift-tasks","auth":"bearer","scope":"own|admin","summary":"Shift checklists and notes for ?date= or ?from=&to= (31 days at most). Employees see the shifts they work plus handoff notes from earlier shifts; admins see every shift. Each instance lists items with who checked them off and when, overdue items and notes"},{"method":"POST","path":"/api/v1/shift-tasks/complete","auth":"bearer","scope":"own|admin","summary":"Check off or clear a checklist item: {date,shiftTypeId,checklistId,itemId,done?}. Only people on that shift (today or last night) or admins"},{"method":"POST","path":"/api/v1/shift-tasks/verify","auth":"bearer","scope":"admin","summary":"Verify a completed item that needs a manager: {date,shiftTypeId,checklistId,itemId,verified?}"},{"method":"POST","path":"/api/v1/shift-notes","auth":"bearer","scope":"own|admin","summary":"Add a shift note: {date,shiftTypeId,body,handoff?}. Handoff notes reach later shifts that day and the next day"},{"method":"PATCH","path":"/api/v1/shift-notes/:id","auth":"bearer","scope":"own","summary":"Edit your own note within 15 minutes of posting: {body,handoff?}"},{"method":"DELETE","path":"/api/v1/shift-notes/:id","auth":"bearer","scope":"own|admin","summary":"Remove your own note within 15 minutes; admins may remove any note. The text is kept for audit"},{"method":"GET","path":"/api/v1/me/onboarding","auth":"bearer","scope":"own","summary":"Your onboarding documents, assignments with due dates and your own acknowledgements"},{"method":"POST","path":"/api/v1/me/onboarding/:documentId/acknowledge","auth":"bearer","scope":"own","summary":"Acknowledge the current version: {version,typedName?}. typedName is required when the document asks for a typed name"},{"method":"GET","path":"/api/v1/onboarding/documents/:id/file","auth":"bearer","scope":"own|admin","summary":"A signed link to the file that expires in five minutes. Employees get the current version of a document assigned to them; admins may pass ?version="},{"method":"GET","path":"/api/v1/onboarding","auth":"bearer","scope":"own|admin","summary":"Admins: every document, packet, assignment and acknowledgement. ?view=mine or employees: own list"},{"method":"POST","path":"/api/v1/onboarding/documents","auth":"bearer","scope":"admin","summary":"Add a document: {title,description?,mode:acknowledge|sign,text} or {title,description?,mode,contentType,size}. A file returns a one-time upload link; upload the bytes, then publish"},{"method":"PATCH","path":"/api/v1/onboarding/documents/:id","auth":"bearer","scope":"admin","summary":"Change title, description, mode or archived"},{"method":"POST","path":"/api/v1/onboarding/documents/:id/versions","auth":"bearer","scope":"admin","summary":"New version: {text} or {contentType,size}. Everyone assigned must acknowledge it again"},{"method":"POST","path":"/api/v1/onboarding/documents/:id/versions/:version/publish","auth":"bearer","scope":"admin","summary":"After uploading: the server reads the stored file, checks its real type and size, records its SHA-256 and makes it current"},{"method":"POST","path":"/api/v1/onboarding/assignments","auth":"bearer","scope":"admin","summary":"Assign documents or a packet: {personIds,documentIds?,packetId?,dueDate?}"},{"method":"DELETE","path":"/api/v1/onboarding/assignments/:id","auth":"bearer","scope":"admin","summary":"Remove a document from someone's list. Rules do not add it back"},{"method":"POST","path":"/api/v1/onboarding/packets","auth":"bearer","scope":"admin","summary":"Create a packet: {name,documentIds,dueDays?,auto:{enabled,onlyNewPeople,roles,locations}}"},{"method":"PATCH","path":"/api/v1/onboarding/packets/:id","auth":"bearer","scope":"admin","summary":"Change a packet and its assignment rule"},{"method":"DELETE","path":"/api/v1/onboarding/packets/:id","auth":"bearer","scope":"admin","summary":"Delete a packet; existing assignments stay"},{"method":"POST","path":"/api/v1/batch","auth":"bearer","scope":"admin","summary":"Run up to 25 writes together against one workplace revision {operations: [{method, path, body?, query?}], dryRun?}: all are saved or none. Each operation is checked and rate limited as its own call. dryRun: true returns the combined changes, the Must Fix and Worth a Look issues added or cleared and a planId. Takes Idempotency-Key. Admin access; Read Only keys cannot batch"},{"method":"POST","path":"/api/v1/simulate","auth":"bearer","scope":"own|admin","summary":"What If: run proposed writes {operations: [{method, path, body?, query?}]} (up to 25, same shapes as the write routes) without saving or making a plan. Returns the changes, the issues added or cleared and key numbers: unfilled seats on the changed dates, fairness per person and call per resident"},{"method":"POST","path":"/api/v1/plans/:id/apply","auth":"bearer","scope":"own|admin","summary":"Apply a dry run's plan: replays exactly that call once if the workplace is still at the plan's revision, otherwise 409 revision_conflict and nothing changes. Plans last 15 minutes and belong to the key or account that made them. Takes Idempotency-Key"},{"method":"GET","path":"/api/v1/plans/:id","auth":"bearer","scope":"own|admin","summary":"Read one of your plans: its call, changes, issues, base revision, expiry and state (open, used or expired)"}]}