{"openapi":"3.0.3","info":{"title":"SchedKit API","description":"**SchedKit** is an API-first white-label scheduling platform.\n\nAll endpoints require authentication via **`x-api-key`** (user API key) or **`x-admin-secret`** (admin) unless tagged **Public**.\n\n### Quick start\n1. Get your API key from the dashboard → Settings\n2. Pass it as `x-api-key: YOUR_KEY` on every request\n3. Create an assignment type, set your standby windows, share your manifest link\n\n### Tags\n- **Assignment Types** — Define assignable manifest types (duration, buffer, confirmation requirements)\n- **Availability** — Set weekly recurring availability windows\n- **Assignments** — View, reschedule, cancel, confirm/decline assignments (API: `/v1/bookings`)\n- **Auth** — Magic link login, session management\n- **Public** — Unauthenticated endpoints (availability slots, manifest page data)","version":"1.0.0"},"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"x-api-key"},"adminSecret":{"type":"apiKey","in":"header","name":"x-admin-secret"},"cookieAuth":{"type":"apiKey","in":"cookie","name":"sk_session"}},"schemas":{}},"paths":{"/v1/users":{"post":{"operationId":"postV1Users","summary":"Create a user (admin)","tags":["Users"],"description":"Create a new SchedKit user account. Requires the `x-admin-secret` header. Returns the new user including their generated `api_key`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","email","slug"],"properties":{"name":{"type":"string"},"email":{"type":"string","format":"email"},"slug":{"type":"string","description":"URL-safe username used in public manifest links (`/assign/:slug/:type`)"},"timezone":{"type":"string","default":"UTC"}}}}}},"security":[{"adminSecret":[]}],"responses":{"200":{"description":"Default Response"}}},"get":{"operationId":"getV1Users","summary":"List all users (admin)","tags":["Users"],"description":"Returns all users. Requires the `x-admin-secret` header.","security":[{"adminSecret":[]}],"responses":{"200":{"description":"Default Response"}}}},"/v1/users/{id}":{"patch":{"operationId":"patchV1Users_:id","summary":"Update a user (admin)","tags":["Users"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"plan":{"type":"string","enum":["free","starter","agency","enterprise"]},"active":{"type":"boolean"},"name":{"type":"string"},"slug":{"type":"string"},"timezone":{"type":"string"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"adminSecret":[]}],"responses":{"200":{"description":"Default Response"}}}},"/v1/u/{slug}":{"get":{"operationId":"getV1U_:slug","summary":"Get a public user profile","tags":["Users"],"description":"Returns the public profile for a user by slug. Used by the manifest page to display the host name and timezone.","parameters":[{"schema":{"type":"string"},"in":"path","name":"slug","required":true,"description":"User's URL slug"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"slug":{"type":"string"},"timezone":{"type":"string"}}}}}}}}},"/v1/event-types":{"get":{"operationId":"getV1Event-types","summary":"List event types","tags":["Event Types"],"description":"Returns all event types for the authenticated user. Each event type has a public manifest link at `/assign/:username/:slug`.","security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"event_types":{"type":"array","items":{"type":"object","properties":{"Id":{"type":"integer"},"title":{"type":"string"},"slug":{"type":"string"},"description":{"type":"string"},"appointment_label":{"type":"string","description":"Custom noun shown to attendees — \"appointment\", \"inspection\", \"session\", etc."},"duration_minutes":{"type":"integer"},"buffer_before":{"type":"integer"},"buffer_after":{"type":"integer"},"min_notice_minutes":{"type":"integer","description":"Minimum advance notice required to book (minutes)"},"max_bookings_per_day":{"type":"integer"},"location":{"type":"string"},"location_type":{"type":"string","enum":["video","phone","in_person","other"]},"webhook_url":{"type":"string"},"custom_fields":{"type":"string","description":"JSON array of custom field definitions"},"requires_confirmation":{"type":"boolean","description":"Require host to confirm each booking before it is finalized"},"active":{"type":"boolean"}}}}},"example":{"event_types":[{"Id":14,"title":"Incident Triage","slug":"incident-triage","description":"Quick ops intake for new incidents.","appointment_label":"assignment","duration_minutes":30,"buffer_before":0,"buffer_after":15,"min_notice_minutes":60,"max_bookings_per_day":8,"location":"Google Meet","location_type":"video","webhook_url":"https://example.com/hooks/bookings","custom_fields":"[{\"id\":\"site\",\"label\":\"Site\",\"type\":\"text\"}]","requires_confirmation":false,"active":true}]}}}}}}},"post":{"operationId":"postV1Event-types","summary":"Create event type","tags":["Event Types"],"description":"Create a new assignable assignment type. The `slug` is used in the public manifest link (`/assign/:username/:slug`). Set `requires_confirmation: true` to put new bookings in a **pending** state — you will receive an email with one-click confirm/decline links, and the attendee is notified their request is under review.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","duration_minutes"],"properties":{"title":{"type":"string"},"slug":{"type":"string"},"description":{"type":"string"},"appointment_label":{"type":"string","default":"assignment"},"duration_minutes":{"type":"integer"},"buffer_before":{"type":"integer","default":0},"buffer_after":{"type":"integer","default":0},"min_notice_minutes":{"type":"integer","default":0},"max_bookings_per_day":{"type":"integer"},"location":{"type":"string"},"location_type":{"type":"string","enum":["video","phone","in_person","other"]},"webhook_url":{"type":"string","description":"POST this URL when a booking is created for this event type"},"custom_fields":{"type":"string","description":"JSON array of custom field definitions shown on the booking form"},"requires_confirmation":{"type":"boolean","description":"If true, new bookings are held as `pending` until the host confirms or declines via email link or dashboard"}}},"example":{"Id":14,"title":"Incident Triage","slug":"incident-triage","description":"Quick ops intake for new incidents.","appointment_label":"assignment","duration_minutes":30,"buffer_before":0,"buffer_after":15,"min_notice_minutes":60,"max_bookings_per_day":8,"location":"Google Meet","location_type":"video","webhook_url":"https://example.com/hooks/bookings","custom_fields":"[{\"id\":\"site\",\"label\":\"Site\",\"type\":\"text\"}]","requires_confirmation":false,"active":true}}}},"security":[{"apiKey":[]}],"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"Id":{"type":"integer"},"title":{"type":"string"},"slug":{"type":"string"},"description":{"type":"string"},"appointment_label":{"type":"string","description":"Custom noun shown to attendees — \"appointment\", \"inspection\", \"session\", etc."},"duration_minutes":{"type":"integer"},"buffer_before":{"type":"integer"},"buffer_after":{"type":"integer"},"min_notice_minutes":{"type":"integer","description":"Minimum advance notice required to book (minutes)"},"max_bookings_per_day":{"type":"integer"},"location":{"type":"string"},"location_type":{"type":"string","enum":["video","phone","in_person","other"]},"webhook_url":{"type":"string"},"custom_fields":{"type":"string","description":"JSON array of custom field definitions"},"requires_confirmation":{"type":"boolean","description":"Require host to confirm each booking before it is finalized"},"active":{"type":"boolean"}},"example":{"Id":14,"title":"Incident Triage","slug":"incident-triage","description":"Quick ops intake for new incidents.","appointment_label":"assignment","duration_minutes":30,"buffer_before":0,"buffer_after":15,"min_notice_minutes":60,"max_bookings_per_day":8,"location":"Google Meet","location_type":"video","webhook_url":"https://example.com/hooks/bookings","custom_fields":"[{\"id\":\"site\",\"label\":\"Site\",\"type\":\"text\"}]","requires_confirmation":false,"active":true}}}}}}}},"/v1/event-types/{id}":{"get":{"operationId":"getV1Event-types_:id","summary":"Get event type","tags":["Event Types"],"description":"Return a single event type definition by ID for the authenticated user.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"Id":{"type":"integer"},"title":{"type":"string"},"slug":{"type":"string"},"description":{"type":"string"},"appointment_label":{"type":"string","description":"Custom noun shown to attendees — \"appointment\", \"inspection\", \"session\", etc."},"duration_minutes":{"type":"integer"},"buffer_before":{"type":"integer"},"buffer_after":{"type":"integer"},"min_notice_minutes":{"type":"integer","description":"Minimum advance notice required to book (minutes)"},"max_bookings_per_day":{"type":"integer"},"location":{"type":"string"},"location_type":{"type":"string","enum":["video","phone","in_person","other"]},"webhook_url":{"type":"string"},"custom_fields":{"type":"string","description":"JSON array of custom field definitions"},"requires_confirmation":{"type":"boolean","description":"Require host to confirm each booking before it is finalized"},"active":{"type":"boolean"}},"example":{"Id":14,"title":"Incident Triage","slug":"incident-triage","description":"Quick ops intake for new incidents.","appointment_label":"assignment","duration_minutes":30,"buffer_before":0,"buffer_after":15,"min_notice_minutes":60,"max_bookings_per_day":8,"location":"Google Meet","location_type":"video","webhook_url":"https://example.com/hooks/bookings","custom_fields":"[{\"id\":\"site\",\"label\":\"Site\",\"type\":\"text\"}]","requires_confirmation":false,"active":true}}}}}}},"patch":{"operationId":"patchV1Event-types_:id","summary":"Update event type","tags":["Event Types"],"description":"Partially update an event type. Only fields included in the request body are changed. Toggle `requires_confirmation` here to switch between instant-confirm and host-approval flows.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"Id":{"type":"integer"},"title":{"type":"string"},"slug":{"type":"string"},"description":{"type":"string"},"appointment_label":{"type":"string","description":"Custom noun shown to attendees — \"appointment\", \"inspection\", \"session\", etc."},"duration_minutes":{"type":"integer"},"buffer_before":{"type":"integer"},"buffer_after":{"type":"integer"},"min_notice_minutes":{"type":"integer","description":"Minimum advance notice required to book (minutes)"},"max_bookings_per_day":{"type":"integer"},"location":{"type":"string"},"location_type":{"type":"string","enum":["video","phone","in_person","other"]},"webhook_url":{"type":"string"},"custom_fields":{"type":"string","description":"JSON array of custom field definitions"},"requires_confirmation":{"type":"boolean","description":"Require host to confirm each booking before it is finalized"},"active":{"type":"boolean"}}},"example":{"title":"Urgent Dispatch","duration_minutes":45,"requires_confirmation":true}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"Id":{"type":"integer"},"title":{"type":"string"},"slug":{"type":"string"},"description":{"type":"string"},"appointment_label":{"type":"string","description":"Custom noun shown to attendees — \"appointment\", \"inspection\", \"session\", etc."},"duration_minutes":{"type":"integer"},"buffer_before":{"type":"integer"},"buffer_after":{"type":"integer"},"min_notice_minutes":{"type":"integer","description":"Minimum advance notice required to book (minutes)"},"max_bookings_per_day":{"type":"integer"},"location":{"type":"string"},"location_type":{"type":"string","enum":["video","phone","in_person","other"]},"webhook_url":{"type":"string"},"custom_fields":{"type":"string","description":"JSON array of custom field definitions"},"requires_confirmation":{"type":"boolean","description":"Require host to confirm each booking before it is finalized"},"active":{"type":"boolean"}},"example":{"Id":14,"title":"Urgent Dispatch","slug":"incident-triage","description":"Quick ops intake for new incidents.","appointment_label":"assignment","duration_minutes":45,"buffer_before":0,"buffer_after":15,"min_notice_minutes":60,"max_bookings_per_day":8,"location":"Google Meet","location_type":"video","webhook_url":"https://example.com/hooks/bookings","custom_fields":"[{\"id\":\"site\",\"label\":\"Site\",\"type\":\"text\"}]","requires_confirmation":true,"active":true}}}}}}},"delete":{"operationId":"deleteV1Event-types_:id","summary":"Delete event type","tags":["Event Types"],"description":"Delete an event type owned by the authenticated user.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean"}},"example":{"deleted":true}}}}}}}},"/v1/availability":{"get":{"operationId":"getV1Availability","summary":"List availability rules","tags":["Availability"],"description":"Return the weekly availability windows for the authenticated user. These rules drive public slot generation.","security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"availability":{"type":"array","items":{"type":"object","additionalProperties":true}}},"example":{"availability":[{"Id":12,"user_id":"7","day_of_week":1,"start_time":"09:00","end_time":"17:00","timezone":"America/Chicago"}]}}}}}}},"post":{"operationId":"postV1Availability","summary":"Create availability rule","tags":["Availability"],"description":"Create a single weekly availability window for the authenticated user.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["day_of_week","start_time","end_time"],"properties":{"day_of_week":{"type":"integer","description":"0=Sunday, 6=Saturday"},"start_time":{"type":"string","description":"HH:MM"},"end_time":{"type":"string","description":"HH:MM"},"timezone":{"type":"string"}}},"example":{"day_of_week":1,"start_time":"09:00","end_time":"17:00","timezone":"America/Chicago"}}}},"security":[{"apiKey":[]}],"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"Id":12,"user_id":"7","day_of_week":1,"start_time":"09:00","end_time":"17:00","timezone":"America/Chicago"}}}}}}},"put":{"operationId":"putV1Availability","summary":"Set availability for a day (replaces existing)","tags":["Availability"],"description":"Replace all availability rules for a given day with a single new window.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["day_of_week","start_time","end_time"],"properties":{"day_of_week":{"type":"integer"},"start_time":{"type":"string"},"end_time":{"type":"string"},"timezone":{"type":"string"}}},"example":{"day_of_week":2,"start_time":"10:00","end_time":"16:00","timezone":"America/Chicago"}}}},"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"Id":12,"user_id":"7","day_of_week":2,"start_time":"10:00","end_time":"16:00","timezone":"America/Chicago"}}}}}}}},"/v1/availability/{id}":{"patch":{"operationId":"patchV1Availability_:id","summary":"Update an availability rule by ID","tags":["Availability"],"description":"Patch a single availability rule without replacing other rules for the same day.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"day_of_week":{"type":"integer"},"start_time":{"type":"string"},"end_time":{"type":"string"},"timezone":{"type":"string"}}},"example":{"start_time":"08:30","end_time":"15:30"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"Id":12,"user_id":"7","day_of_week":1,"start_time":"08:30","end_time":"15:30","timezone":"America/Chicago"}}}}}}},"delete":{"operationId":"deleteV1Availability_:id","summary":"Delete an availability rule by ID","tags":["Availability"],"description":"Delete a single availability rule for the authenticated user.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean"}},"example":{"deleted":true}}}}}}}},"/v1/slots/{username}/{event_slug}":{"get":{"operationId":"getV1Slots_:username_:eventSlug","summary":"Get available slots for a date","tags":["Public"],"description":"Return bookable slots for a public event type on a specific date in the requested timezone.","parameters":[{"schema":{"type":"string"},"in":"query","name":"date","required":true,"description":"YYYY-MM-DD"},{"schema":{"type":"string"},"in":"query","name":"timezone","required":false},{"schema":{"type":"string"},"in":"path","name":"username","required":true},{"schema":{"type":"string"},"in":"path","name":"event_slug","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"date":"2026-03-20","timezone":"America/Chicago","event_type":{"title":"Incident Triage","duration_minutes":30,"appointment_label":"assignment","location_type":"video","location":"Google Meet","custom_fields":"[]","description":"Quick ops intake call"},"slots":["2026-03-20T14:00:00.000Z","2026-03-20T14:30:00.000Z"]}}}}}}}},"/v1/bookings":{"get":{"operationId":"getV1Bookings","summary":"List bookings","tags":["Bookings"],"description":"Returns bookings for the authenticated user. Filter by `status`: `confirmed`, `pending` (awaiting host confirmation), `cancelled`, or `rescheduled`. Sorted by start time descending.","parameters":[{"schema":{"type":"string","enum":["confirmed","pending","cancelled","rescheduled","declined"]},"in":"query","name":"status","required":false,"description":"Filter by booking status"},{"schema":{"type":"integer","default":50},"in":"query","name":"limit","required":false},{"schema":{"type":"integer","default":1},"in":"query","name":"page","required":false}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"bookings":[{"Id":88,"uid":"bk_abc123def456","event_type_id":"14","user_id":"7","attendee_name":"Taylor Brooks","attendee_email":"taylor@example.com","attendee_timezone":"America/Chicago","start_time":"2026-03-20T15:00:00.000Z","end_time":"2026-03-20T15:30:00.000Z","status":"confirmed","cancel_url":"/cancel/cancel_tok_123","reschedule_url":"/reschedule/res_tok_123"}],"total":1}}}}}}}},"/v1/bookings/{id}":{"get":{"operationId":"getV1Bookings_:id","summary":"Get booking","tags":["Bookings"],"description":"Return a single booking by ID for the authenticated owner.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"Id":88,"uid":"bk_abc123def456","event_type_id":"14","user_id":"7","attendee_name":"Taylor Brooks","attendee_email":"taylor@example.com","attendee_timezone":"America/Chicago","start_time":"2026-03-20T15:00:00.000Z","end_time":"2026-03-20T15:30:00.000Z","status":"confirmed","cancel_url":"/cancel/cancel_tok_123","reschedule_url":"/reschedule/res_tok_123"}}}}}}}},"/v1/bookings/{id}/reschedule":{"post":{"operationId":"postV1Bookings_:idReschedule","summary":"Admin reschedule a booking","tags":["Bookings"],"description":"Moves a confirmed booking to a new start time. Sends a reschedule notification email to the attendee.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["start_time"],"properties":{"start_time":{"type":"string","description":"New ISO start time"},"override":{"type":"boolean","description":"If true, bypasses availability/slot validation — allows any date/time including weekends"}}},"example":{"start_time":"2026-03-21T16:00:00.000Z","override":false}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"ok":true,"start_time":"2026-03-21T16:00:00.000Z","end_time":"2026-03-21T16:30:00.000Z","override":false}}}}}}}},"/v1/assign/{username}/{event_slug}":{"post":{"operationId":"postV1Assign_:username_:eventSlug","summary":"Create an assignment","tags":["Public"],"description":"Submit an assignment request for an individual host's assignment type. Returns confirmation including `cancel_url` and `reschedule_url`. If `requires_confirmation: true`, status will be `pending` until the host confirms.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["start_time","attendee_name","attendee_email"],"properties":{"start_time":{"type":"string","description":"ISO 8601 datetime from the `/v1/slots` response"},"attendee_name":{"type":"string"},"attendee_email":{"type":"string","format":"email"},"attendee_timezone":{"type":"string","default":"UTC"},"notes":{"type":"string"},"custom_responses":{"type":"object","description":"Answers to custom fields defined on the assignment type"}}},"example":{"start_time":"2026-03-20T15:00:00.000Z","attendee_name":"Taylor Brooks","attendee_email":"taylor@example.com","attendee_timezone":"America/Chicago","notes":"Need site intake.","custom_responses":{"site":"Alpha"}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"username","required":true},{"schema":{"type":"string"},"in":"path","name":"event_slug","required":true}],"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"Id":88,"uid":"bk_abc123def456","event_type_id":"14","user_id":"7","attendee_name":"Taylor Brooks","attendee_email":"taylor@example.com","attendee_timezone":"America/Chicago","start_time":"2026-03-20T15:00:00.000Z","end_time":"2026-03-20T15:30:00.000Z","status":"confirmed","cancel_url":"/cancel/cancel_tok_123","reschedule_url":"/reschedule/res_tok_123"}}}}}}}},"/v1/cancel/{token}":{"post":{"operationId":"postV1Cancel_:token","summary":"Cancel a booking (via token)","tags":["Public"],"description":"Cancel a booking using the `cancel_token` from the booking confirmation. Sends cancellation emails to both attendee and host.","parameters":[{"schema":{"type":"string"},"in":"path","name":"token","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"uid":{"type":"string"}},"example":{"status":"cancelled","uid":"bk_abc123def456"}}}}}}},"get":{"operationId":"getV1Cancel_:token","summary":"Cancel confirmation page","tags":["Public"],"description":"Returns an HTML confirmation page for cancelling a booking. Linked from cancellation emails.","parameters":[{"schema":{"type":"string"},"in":"path","name":"token","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"string","example":"<!DOCTYPE html><html><body>Cancel this booking?</body></html>"}}}}}}},"/v1/cancel/{token}/confirm":{"post":{"operationId":"postV1Cancel_:tokenConfirm","summary":"Confirm cancellation","tags":["Public"],"description":"Confirms the cancellation after the attendee clicks \"Yes, cancel\" on the cancel confirmation page.","parameters":[{"schema":{"type":"string"},"in":"path","name":"token","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"string","example":"<!DOCTYPE html><html><body>Your booking has been cancelled.</body></html>"}}}}}}},"/v1/reschedule/{token}":{"get":{"operationId":"getV1Reschedule_:token","summary":"Reschedule page","tags":["Public"],"description":"Returns an HTML page for rescheduling a booking. Linked from booking confirmation and cancellation emails.","parameters":[{"schema":{"type":"string"},"in":"path","name":"token","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"string","example":"<!DOCTYPE html><html><body>Reschedule your booking</body></html>"}}}}}},"post":{"operationId":"postV1Reschedule_:token","summary":"Submit reschedule","tags":["Public"],"description":"Cancels the existing booking and creates a new one at the requested time.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["start_time"],"properties":{"start_time":{"type":"string","description":"New ISO 8601 start time"},"attendee_timezone":{"type":"string","default":"UTC"}}},"example":{"start_time":"2026-03-22T16:00:00.000Z","attendee_timezone":"America/Chicago"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"token","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"status":"rescheduled","uid":"bk_new123","start_time":"2026-03-22T16:00:00.000Z","end_time":"2026-03-22T16:30:00.000Z","cancel_url":"/v1/cancel/cancel_tok_new","reschedule_url":"/v1/reschedule/res_tok_new"}}}}}}}},"/v1/bookings/{confirm_token}/confirm":{"get":{"operationId":"getV1Bookings_:confirmTokenConfirm","summary":"Confirm a pending booking (host)","tags":["Bookings"],"description":"One-click confirm link sent to the host when a booking requires confirmation. Sets status to `confirmed` and notifies the attendee.","parameters":[{"schema":{"type":"string"},"in":"path","name":"confirm_token","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/bookings/{confirm_token}/decline":{"get":{"operationId":"getV1Bookings_:confirmTokenDecline","summary":"Decline a pending booking (host)","tags":["Bookings"],"description":"One-click decline link sent to the host when a booking requires confirmation. Sets status to `declined` and notifies the attendee.","parameters":[{"schema":{"type":"string"},"in":"path","name":"confirm_token","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/blackout":{"get":{"operationId":"getV1Blackout","summary":"List blackout dates","tags":["Blackout Dates"],"description":"Returns all blocked dates/ranges for the authenticated user.","security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response"}}},"post":{"operationId":"postV1Blackout","summary":"Add a blackout date or range","tags":["Blackout Dates"],"description":"Block out a full day or a specific time window. Bookings will not be available during blackout periods.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["date"],"properties":{"date":{"type":"string","format":"date","description":"The date to block out (YYYY-MM-DD)"},"end_date":{"type":"string","format":"date","description":"Optional end date for a multi-day range (YYYY-MM-DD). If omitted, blocks the single `date` only."},"start_time":{"type":"string","description":"Optional start time for partial-day blocks (HH:MM). Omit for full-day block."},"end_time":{"type":"string","description":"Optional end time for partial-day blocks (HH:MM)."},"reason":{"type":"string","description":"Internal note (not shown to attendees)"}}}}}},"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response"}}}},"/v1/blackout/{id}":{"delete":{"operationId":"deleteV1Blackout_:id","summary":"Remove a blackout date","tags":["Blackout Dates"],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response"}}}},"/v1/auth/capabilities":{"get":{"operationId":"getV1AuthCapabilities","summary":"Auth capabilities for clients","tags":["Auth"],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"passwordLoginEnabled":{"type":"boolean"},"magicLinkEnabled":{"type":"boolean"}}}}}}}}},"/v1/auth/login":{"post":{"operationId":"postV1AuthLogin","summary":"Email + password login","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","format":"email"},"password":{"type":"string"}}}}}},"responses":{"200":{"description":"Default Response"}}}},"/v1/auth/set-password":{"post":{"operationId":"postV1AuthSet-password","summary":"Set or update account password","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["password"],"properties":{"password":{"type":"string","minLength":8}}}}}},"responses":{"200":{"description":"Default Response"}}}},"/v1/auth/magic":{"post":{"operationId":"postV1AuthMagic","summary":"Request a magic link login email","tags":["Auth"],"description":"Send a one-time magic link and 6-digit login code to a registered user. Always returns `{ ok: true }` so the endpoint does not leak whether an email exists.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"},"next":{"type":"string"}}},"example":{"email":"ops@schedkit.net"}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"example":{"ok":true}}}}}}}},"/v1/auth/verify-code":{"post":{"operationId":"postV1AuthVerify-code","summary":"Verify short login code","tags":["Auth"],"description":"Verify the 6-digit email code inside the web app or PWA and issue a dashboard session cookie for the current browser context.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","code"],"properties":{"email":{"type":"string","format":"email"},"code":{"type":"string"},"next":{"type":"string"}}},"example":{"email":"ops@schedkit.net","code":"482193"}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"destination":{"type":"string"}},"example":{"ok":true,"destination":"/dashboard"}}}}}}}},"/v1/auth/verify":{"get":{"operationId":"getV1AuthVerify","summary":"Verify magic link token","tags":["Auth"],"description":"Verify a one-time magic link token from email. On success this issues a session cookie and redirects the browser to onboarding or dashboard.","parameters":[{"schema":{"type":"string"},"in":"query","name":"token","required":false}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"string","example":"<html>Redirecting to dashboard...</html>"}}}}}}},"/v1/auth/logout":{"post":{"operationId":"postV1AuthLogout","summary":"Log out","tags":["Auth"],"description":"Clear the session cookie and delete the current server-side session record.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"example":{"ok":true}}}}}}}},"/v1/auth/me":{"get":{"operationId":"getV1AuthMe","summary":"Get current user","tags":["Auth"],"description":"Return the authenticated user profile for the active browser session, including API key, plan, and ntfy topic settings.","security":[{"cookieAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"Id":{"type":"number"},"name":{"type":"string"},"email":{"type":"string"},"slug":{"type":"string"},"timezone":{"type":"string"},"api_key":{"type":"string"},"enterprise":{"type":"boolean"},"ntfy_topic":{"type":"string"},"plan":{"type":"string"},"is_platform_admin":{"type":"boolean"}}}}}}}},"patch":{"operationId":"patchV1AuthMe","summary":"Update profile","tags":["Auth"],"description":"Update the current session user profile. This is used for onboarding and profile maintenance.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"email":{"type":"string","format":"email"},"timezone":{"type":"string"},"ntfy_topic":{"type":"string"}}},"example":{"name":"Olson Ops","timezone":"America/Chicago","ntfy_topic":"schedkit-ops"}}}},"security":[{"cookieAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","example":{"Id":7,"name":"Olson Ops","email":"ops@schedkit.net","slug":"olson-ops","timezone":"America/Chicago","ntfy_topic":"schedkit-ops"}}}}}}}},"/v1/clients":{"get":{"operationId":"getV1Clients","summary":"List flagged clients","tags":["Clients"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response"}}}},"/v1/clients/flag":{"post":{"operationId":"postV1ClientsFlag","summary":"Flag a client (create or update)","tags":["Clients"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"},"risk_level":{"type":"string","enum":["ok","caution","high","blocked"]},"notes":{"type":"string"},"discount_flag":{"type":"string"}}}}}},"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response"}}},"delete":{"operationId":"deleteV1ClientsFlag","summary":"Remove a client flag","tags":["Clients"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}}}}},"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response"}}}},"/v1/clients/{email}/flag":{"get":{"operationId":"getV1Clients_:emailFlag","summary":"Get flag for a client email","tags":["Clients"],"parameters":[{"schema":{"type":"string"},"in":"path","name":"email","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response"}}},"post":{"operationId":"postV1Clients_:emailFlag","summary":"Flag a client (create or update)","tags":["Clients"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"risk_level":{"type":"string","enum":["ok","caution","high","blocked"],"description":"ok=clear, caution=watch, high=get payment first, blocked=refuse service"},"notes":{"type":"string","description":"Free-form notes about this client"},"discount_flag":{"type":"string","description":"Discount tier: vet, realtor, partner"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"email","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response"}}},"delete":{"operationId":"deleteV1Clients_:emailFlag","summary":"Remove a client flag","tags":["Clients"],"parameters":[{"schema":{"type":"string"},"in":"path","name":"email","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response"}}}},"/v1/orgs":{"post":{"operationId":"postV1Orgs","summary":"Create an organization","tags":["Organizations"],"description":"Create a new organization. Requires an Enterprise account. The creator is automatically added as `owner`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Display name for the org"},"slug":{"type":"string","description":"URL-safe slug (auto-generated from name if omitted)"}}},"example":{"name":"Alpha Response Team","slug":"alpha-response"}}}},"security":[{"apiKey":[]}],"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"Id":4,"name":"Alpha Response Team","slug":"alpha-response","owner_user_id":"7","api_key":"p7s_org_abc123"}}}}}}},"get":{"operationId":"getV1Orgs","summary":"List your organizations","tags":["Organizations"],"description":"Returns all organizations the authenticated user is a member of.","security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"orgs":[{"Id":4,"name":"Alpha Response Team","slug":"alpha-response"}]}}}}}}}},"/v1/orgs/{org_slug}":{"get":{"operationId":"getV1Orgs_:orgSlug","summary":"Get an organization","tags":["Organizations"],"description":"Returns org details, members, and teams. `api_key` is only included for the org owner.","parameters":[{"schema":{"type":"string"},"in":"path","name":"org_slug","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"org":{"Id":4,"name":"Alpha Response Team","slug":"alpha-response"},"members":[{"role":"owner","user":{"email":"ops@schedkit.net","name":"Olson Ops"}}],"teams":[]}}}}}}},"patch":{"operationId":"patchV1Orgs_:orgSlug","summary":"Update an organization","tags":["Organizations"],"description":"Update the org name or slug. Requires `admin` role.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"slug":{"type":"string"}}},"example":{"name":"Alpha Response HQ","slug":"alpha-response-hq"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"org_slug","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"Id":4,"name":"Alpha Response HQ","slug":"alpha-response-hq"}}}}}}},"delete":{"operationId":"deleteV1Orgs_:orgSlug","summary":"Delete an organization","tags":["Organizations"],"description":"Permanently deletes the org. Requires `owner` role.","parameters":[{"schema":{"type":"string"},"in":"path","name":"org_slug","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"example":{"ok":true}}}}}}}},"/v1/orgs/{org_slug}/settings":{"patch":{"operationId":"patchV1Orgs_:orgSlugSettings","summary":"Update org email settings","tags":["Organizations"],"description":"Update email branding for the org. Requires `admin` role. `ticket_subject_template` supports tokens: `%ticket_id%`, `%title%`, `%priority%`, `%org_name%`. `ticket_from_prefix` sets the sender name prefix (e.g. `SchedKit-INC` → `SchedKit-INC9`).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ticket_subject_template":{"type":"string","description":"Email subject template. Tokens: %ticket_id%, %title%, %priority%, %org_name%"},"ticket_from_prefix":{"type":"string","description":"From name prefix for ticket emails (e.g. \"SchedKit-INC\" → \"SchedKit-INC9\")"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"org_slug","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response"}}}},"/v1/orgs/{org_slug}/members":{"post":{"operationId":"postV1Orgs_:orgSlugMembers","summary":"Add a member to an org","tags":["Organizations"],"description":"Add an existing SchedKit user to the org by email. Requires `admin` role.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","role"],"properties":{"email":{"type":"string"},"role":{"type":"string","enum":["admin","member"]}}},"example":{"email":"alex@example.com","role":"member"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"org_slug","required":true}],"security":[{"apiKey":[]}],"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"Id":19,"org_id":"4","user_id":"12","role":"member"}}}}}}}},"/v1/orgs/{org_slug}/members/{user_id}":{"patch":{"operationId":"patchV1Orgs_:orgSlugMembers_:userId","summary":"Update a member's role","tags":["Organizations"],"description":"Change the role of an org member. Requires `admin`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"role":{"type":"string","enum":["admin","member"]},"can_manage_work_orders":{"type":"boolean","description":"Dispatcher: view and assign all unit work orders"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"org_slug","required":true},{"schema":{"type":"string"},"in":"path","name":"user_id","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"Id":19,"org_id":"4","user_id":"12","role":"admin"}}}}}}},"delete":{"operationId":"deleteV1Orgs_:orgSlugMembers_:userId","summary":"Remove a member from an org","tags":["Organizations"],"description":"Removes the user from the org. Requires `admin`.","parameters":[{"schema":{"type":"string"},"in":"path","name":"org_slug","required":true},{"schema":{"type":"string"},"in":"path","name":"user_id","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"example":{"ok":true}}}}}}}},"/v1/orgs/{org_slug}/teams":{"post":{"operationId":"postV1Orgs_:orgSlugTeams","summary":"Create a team","tags":["Organizations"],"description":"Create a team within an org. `routing` controls how bookings are assigned to members: `round_robin` rotates in order, `random` picks randomly.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","routing"],"properties":{"name":{"type":"string"},"slug":{"type":"string","description":"URL-safe slug (auto-generated from name if omitted)"},"routing":{"type":"string","enum":["round_robin","random"],"description":"`round_robin` rotates assignments; `random` picks any available member"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"org_slug","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response"}}},"get":{"operationId":"getV1Orgs_:orgSlugTeams","summary":"List teams in an org","tags":["Organizations"],"parameters":[{"schema":{"type":"string"},"in":"path","name":"org_slug","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response"}}}},"/v1/orgs/{org_slug}/teams/{team_slug}":{"patch":{"operationId":"patchV1Orgs_:orgSlugTeams_:teamSlug","summary":"Update a team","tags":["Organizations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"slug":{"type":"string"},"routing":{"type":"string","enum":["round_robin","random"]}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"org_slug","required":true},{"schema":{"type":"string"},"in":"path","name":"team_slug","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response"}}},"delete":{"operationId":"deleteV1Orgs_:orgSlugTeams_:teamSlug","summary":"Delete a team","tags":["Organizations"],"parameters":[{"schema":{"type":"string"},"in":"path","name":"org_slug","required":true},{"schema":{"type":"string"},"in":"path","name":"team_slug","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response"}}}},"/v1/orgs/{org_slug}/teams/{team_slug}/members":{"get":{"operationId":"getV1Orgs_:orgSlugTeams_:teamSlugMembers","summary":"List team members","tags":["Organizations"],"description":"Returns team members enriched with user name and email.","parameters":[{"schema":{"type":"string"},"in":"path","name":"org_slug","required":true},{"schema":{"type":"string"},"in":"path","name":"team_slug","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response"}}},"post":{"operationId":"postV1Orgs_:orgSlugTeams_:teamSlugMembers","summary":"Add a member to a team","tags":["Organizations"],"description":"Add an org member to a team. Provide `email` (preferred) or `user_id`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","description":"Email of the user to add (preferred)"},"user_id":{"type":"string","description":"User ID (fallback)"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"org_slug","required":true},{"schema":{"type":"string"},"in":"path","name":"team_slug","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response"}}}},"/v1/orgs/{org_slug}/teams/{team_slug}/members/{user_id}":{"patch":{"operationId":"patchV1Orgs_:orgSlugTeams_:teamSlugMembers_:userId","summary":"Enable or disable a team member","tags":["Organizations"],"description":"Toggle a team member's `active` status. Inactive members are excluded from assignment routing.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["active"],"properties":{"active":{"type":"boolean"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"org_slug","required":true},{"schema":{"type":"string"},"in":"path","name":"team_slug","required":true},{"schema":{"type":"string"},"in":"path","name":"user_id","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response"}}},"delete":{"operationId":"deleteV1Orgs_:orgSlugTeams_:teamSlugMembers_:userId","summary":"Remove a member from a team","tags":["Organizations"],"parameters":[{"schema":{"type":"string"},"in":"path","name":"org_slug","required":true},{"schema":{"type":"string"},"in":"path","name":"team_slug","required":true},{"schema":{"type":"string"},"in":"path","name":"user_id","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response"}}}},"/v1/orgs/{org_slug}/teams/{team_slug}/event-types":{"post":{"operationId":"postV1Orgs_:orgSlugTeams_:teamSlugEvent-types","summary":"Create a team event type","tags":["Organizations"],"description":"Create a assignable assignment type for a team. The public manifest link is `/assign/:org_slug/:team_slug/:slug`. Bookings are auto-assigned to a team member based on the team's `routing` setting.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","duration_minutes"],"properties":{"title":{"type":"string"},"slug":{"type":"string","description":"URL slug (auto-generated from title if omitted)"},"duration_minutes":{"type":"integer"},"buffer_before":{"type":"integer","description":"Buffer before event (minutes)"},"buffer_after":{"type":"integer","description":"Buffer after event (minutes)"},"location":{"type":"string"},"location_type":{"type":"string","enum":["video","phone","in_person","other"]},"description":{"type":"string"},"appointment_label":{"type":"string","default":"assignment"},"min_notice_minutes":{"type":"integer","description":"Minimum advance notice required to book"},"webhook_url":{"type":"string"},"custom_fields":{"type":"string","description":"JSON array of custom field definitions"},"requires_confirmation":{"type":"boolean"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"org_slug","required":true},{"schema":{"type":"string"},"in":"path","name":"team_slug","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response"}}},"get":{"operationId":"getV1Orgs_:orgSlugTeams_:teamSlugEvent-types","summary":"List team event types","tags":["Organizations"],"parameters":[{"schema":{"type":"string"},"in":"path","name":"org_slug","required":true},{"schema":{"type":"string"},"in":"path","name":"team_slug","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response"}}}},"/v1/orgs/{org_slug}/teams/{team_slug}/event-types/{et_slug}":{"patch":{"operationId":"patchV1Orgs_:orgSlugTeams_:teamSlugEvent-types_:etSlug","summary":"Update a team event type","tags":["Organizations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"slug":{"type":"string"},"duration_minutes":{"type":"integer"},"buffer_before":{"type":"integer"},"buffer_after":{"type":"integer"},"location":{"type":"string"},"location_type":{"type":"string","enum":["video","phone","in_person","other"]},"description":{"type":"string"},"appointment_label":{"type":"string"},"min_notice_minutes":{"type":"integer"},"webhook_url":{"type":"string"},"custom_fields":{"type":"string"},"requires_confirmation":{"type":"boolean"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"org_slug","required":true},{"schema":{"type":"string"},"in":"path","name":"team_slug","required":true},{"schema":{"type":"string"},"in":"path","name":"et_slug","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response"}}},"delete":{"operationId":"deleteV1Orgs_:orgSlugTeams_:teamSlugEvent-types_:etSlug","summary":"Delete a team event type","tags":["Organizations"],"parameters":[{"schema":{"type":"string"},"in":"path","name":"org_slug","required":true},{"schema":{"type":"string"},"in":"path","name":"team_slug","required":true},{"schema":{"type":"string"},"in":"path","name":"et_slug","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response"}}}},"/v1/orgs/{org_slug}/invite":{"post":{"operationId":"postV1Orgs_:orgSlugInvite","summary":"Invite a user to an org","tags":["Organizations"],"description":"Send an email invite to join the org. If the email is not yet registered, a stub user is created and a 24-hour magic-link login is included in the invite email.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","role"],"properties":{"email":{"type":"string"},"role":{"type":"string","enum":["admin","member"]}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"org_slug","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response"}}}},"/v1/slots/{org_slug}/{team_slug}/{event_slug}":{"get":{"operationId":"getV1Slots_:orgSlug_:teamSlug_:eventSlug","summary":"Get available slots for a team event","tags":["Public"],"description":"Returns available time slots for a team event type on a given date. Used by the team booking page. Slots respect each team member's availability; at least one member must be free for a slot to appear.","parameters":[{"schema":{"type":"string"},"in":"query","name":"date","required":false,"description":"YYYY-MM-DD"},{"schema":{"type":"string"},"in":"query","name":"timezone","required":false,"description":"IANA timezone (e.g. America/Chicago)"},{"schema":{"type":"string"},"in":"path","name":"org_slug","required":true},{"schema":{"type":"string"},"in":"path","name":"team_slug","required":true},{"schema":{"type":"string"},"in":"path","name":"event_slug","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/assign/{username}/{event_slug}":{"get":{"operationId":"getAssign_:username_:eventSlug","summary":"Individual manifest page","tags":["Public"],"description":"Returns the HTML assignment request page for an individual host's assignment type. Open in a browser — not a JSON API endpoint.","parameters":[{"schema":{"type":"string"},"in":"path","name":"username","required":true},{"schema":{"type":"string"},"in":"path","name":"event_slug","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"string","example":"<!DOCTYPE html><html><head><title>Request Assignment</title></head><body>...</body></html>"}}}}}}},"/assign/{org_slug}/{team_slug}/{event_slug}":{"get":{"operationId":"getAssign_:orgSlug_:teamSlug_:eventSlug","summary":"Team manifest page","tags":["Public"],"description":"Returns the HTML assignment request page for a team assignment type. Open in a browser — not a JSON API endpoint.","parameters":[{"schema":{"type":"string"},"in":"path","name":"org_slug","required":true},{"schema":{"type":"string"},"in":"path","name":"team_slug","required":true},{"schema":{"type":"string"},"in":"path","name":"event_slug","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"string","example":"<!DOCTYPE html><html><head><title>Request Assignment</title></head><body>...</body></html>"}}}}}}},"/v1/assign/{org_slug}/{team_slug}/{event_slug}":{"post":{"operationId":"postV1Assign_:orgSlug_:teamSlug_:eventSlug","summary":"Create a team assignment","tags":["Public"],"description":"Submit an assignment for a team assignment type. A team member is auto-assigned based on the team's routing setting (`round_robin` or `random`). Returns confirmation including `cancel_url` and `assigned_to`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["start_time","attendee_name","attendee_email"],"properties":{"start_time":{"type":"string","description":"ISO 8601 datetime from the `/v1/slots` response"},"attendee_name":{"type":"string"},"attendee_email":{"type":"string","format":"email"},"attendee_timezone":{"type":"string","default":"UTC"},"notes":{"type":"string"}}},"example":{"start_time":"2026-03-20T15:00:00.000Z","attendee_name":"Taylor Brooks","attendee_email":"taylor@example.com","attendee_timezone":"America/Chicago","notes":"Need a field response lead."}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"org_slug","required":true},{"schema":{"type":"string"},"in":"path","name":"team_slug","required":true},{"schema":{"type":"string"},"in":"path","name":"event_slug","required":true}],"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"uid":"bk_abc123def456","status":"confirmed","start_time":"2026-03-20T15:00:00.000Z","end_time":"2026-03-20T15:30:00.000Z","assigned_to":"Alex Response","cancel_url":"/v1/cancel/cancel_tok_123","reschedule_url":"/v1/reschedule/res_tok_123"}}}}}}}},"/v1/auth/google/connect":{"get":{"operationId":"getV1AuthGoogleConnect","summary":"Connect Google Calendar","tags":["Calendar"],"description":"Redirects to Google OAuth to authorize calendar access. After authorization, Google redirects back to `/v1/auth/google/callback` and the connection is saved. Visit this URL in the browser — do not call it as an API endpoint.","security":[{"cookieAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/v1/auth/google/callback":{"get":{"operationId":"getV1AuthGoogleCallback","summary":"Google OAuth callback (internal)","tags":["Calendar"],"description":"Internal callback URL used by Google after OAuth authorization. Not called directly — Google redirects here after the user grants calendar access.","parameters":[{"schema":{"type":"string"},"in":"query","name":"code","required":false},{"schema":{"type":"string"},"in":"query","name":"state","required":false},{"schema":{"type":"string"},"in":"query","name":"error","required":false}],"responses":{"200":{"description":"Default Response"}}}},"/v1/calendar/status":{"get":{"operationId":"getV1CalendarStatus","summary":"Get calendar connection status","tags":["Calendar"],"description":"Returns whether the authenticated user has a Google Calendar connected, and if so, which Google account email is linked.","security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"connected":{"type":"boolean"},"provider":{"type":"string","description":"Always `google` when connected"},"calendar_email":{"type":"string","description":"Google account email linked to the calendar"}}}}}}}}},"/v1/calendar/disconnect":{"delete":{"operationId":"deleteV1CalendarDisconnect","summary":"Disconnect Google Calendar","tags":["Calendar"],"description":"Removes the Google Calendar connection for the authenticated user. New bookings will no longer be checked against or synced to Google Calendar.","security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}}}}},"/v1/notifications/test":{"post":{"operationId":"postV1NotificationsTest","summary":"Send a test notification","tags":["Notifications"],"description":"Sends a test push notification to the specified ntfy.sh topic. Use this to verify your ntfy topic is configured correctly.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["topic"],"properties":{"topic":{"type":"string","description":"ntfy.sh topic name or full URL (e.g. `my-topic` or `https://ntfy.sh/my-topic`)"}}}}}},"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}}}}},"/v1/push/vapid-public":{"get":{"operationId":"getV1PushVapid-public","summary":"Get VAPID public key","tags":["Push"],"description":"Return the public VAPID key the browser needs to create a Web Push subscription.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"publicKey":{"type":["null","string"]}},"example":{"publicKey":"BIa_HKGuR_doeyRcqmP3qSRqznWQin-oPIJlSk1Mk08-yBSqeB8w932fH-f66YTFLMhUzeHzP_VHNEwMXIFmG4k"}}}}}}}},"/v1/push/subscribe":{"post":{"operationId":"postV1PushSubscribe","summary":"Register a Web Push subscription","tags":["Push"],"description":"Store or update a browser PushSubscription for the current user so SchedKit can send native notifications.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["subscription"],"properties":{"subscription":{"type":"object","description":"PushSubscription JSON from browser"}}},"example":{"subscription":{"endpoint":"https://fcm.googleapis.com/fcm/send/example-subscription-id","expirationTime":null,"keys":{"p256dh":"BExamplePublicKey","auth":"exampleAuthSecret"}}}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"example":{"ok":true}}}}}}},"delete":{"operationId":"deleteV1PushSubscribe","summary":"Remove a Web Push subscription","tags":["Push"],"description":"Delete a stored Web Push subscription for the current user, usually during logout or opt-out.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["endpoint"],"properties":{"endpoint":{"type":"string"}}},"example":{"endpoint":"https://fcm.googleapis.com/fcm/send/example-subscription-id"}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"example":{"ok":true}}}}}}}},"/v1/push/test":{"post":{"operationId":"postV1PushTest","summary":"Send a test push notification to yourself","tags":["Push"],"description":"Send a test push to all saved subscriptions for the current user. Useful for validating permission and delivery.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"sent":{"type":"integer"}},"example":{"ok":true,"sent":2}}}}}}}},"/v1/signals":{"post":{"operationId":"postV1Signals","summary":"Create a signal (beacon ping, capture, note, alert)","tags":["Signals"],"description":"Create a new org-scoped signal. Beacon pings are broadcast live and only persist a `beacon_on` record on first activation; other signal types are written to storage and optionally trigger push notifications.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["beacon","capture","note","checkin","alert"]},"lat":{"type":"number"},"lng":{"type":"number"},"accuracy":{"type":"number"},"image_url":{"type":"string"},"note":{"type":"string"},"ticket_id":{"type":"number"},"org_id":{"type":"number","description":"Override org — defaults to user primary org"},"meta":{"type":"object","additionalProperties":true}}},"example":{"type":"beacon","lat":35.4676,"lng":-97.5164,"accuracy":10,"meta":{"device_id":"dev-abc123-xyz","battery":81}}}}},"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"Id":301,"user_id":7,"org_id":4,"type":"beacon","lat":35.4676,"lng":-97.5164,"accuracy":10,"meta":"{\"device_id\":\"dev-abc123-xyz\",\"battery\":81}","created_at":"2026-03-15T18:00:00Z","user_name":"Olson Ops"}}}}}}},"get":{"operationId":"getV1Signals","summary":"List signals for the authenticated user's orgs","tags":["Signals"],"description":"Return persisted signals for the orgs the current user belongs to. Beacon pings themselves are live-only, so use this for captures, notes, alerts, and audit records.","parameters":[{"schema":{"type":"string"},"in":"query","name":"type","required":false},{"schema":{"type":"integer"},"in":"query","name":"org_id","required":false},{"schema":{"type":"integer","default":100},"in":"query","name":"limit","required":false},{"schema":{"type":"string"},"in":"query","name":"since","required":false},{"schema":{"type":"string"},"in":"query","name":"before","required":false}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"signals":[{"Id":301,"user_id":7,"org_id":4,"type":"beacon","lat":35.4676,"lng":-97.5164,"accuracy":10,"meta":"{\"device_id\":\"dev-abc123-xyz\",\"battery\":81}","created_at":"2026-03-15T18:00:00Z","user_name":"Olson Ops"}],"total":1}}}}}}}},"/v1/signals/beacon":{"delete":{"operationId":"deleteV1SignalsBeacon","summary":"Stop beacon and broadcast beacon_off","tags":["Signals"],"description":"Stop the current device beacon, clear it from the active tracker, write a `beacon_off` audit record, and broadcast the stop event to the org stream.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"device_id":{"type":"string"}}},"example":{"device_id":"dev-abc123-xyz"}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"example":{"ok":true}}}}}}}},"/v1/signals/log":{"get":{"operationId":"getV1SignalsLog","summary":"List signal audit log entries","tags":["Signals"],"description":"Return persisted signal log records such as `beacon_off`, `capture`, `alert`, or other audit-worthy signal entries for the current org.","parameters":[{"schema":{"type":"integer","default":50,"maximum":500},"in":"query","name":"limit","required":false},{"schema":{"type":"integer","default":0},"in":"query","name":"offset","required":false},{"schema":{"type":"string"},"in":"query","name":"type","required":false,"description":"Filter by signal type (e.g. beacon_off, alert, capture)"},{"schema":{"type":"string"},"in":"query","name":"device_id","required":false}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"entries":[{"Id":301,"user_id":7,"org_id":4,"type":"alert","lat":35.4676,"lng":-97.5164,"accuracy":10,"meta":"{\"device_id\":\"dev-abc123-xyz\",\"battery\":81}","created_at":"2026-03-15T18:00:00Z","user_name":"Olson Ops","note":"Pipe pressure drop"}],"total":1,"limit":50,"offset":0}}}}}}}},"/v1/signals/stream":{"get":{"operationId":"getV1SignalsStream","summary":"Open SSE stream for live signals","tags":["Signals"],"description":"Open a server-sent events stream that emits live signal events for the orgs the authenticated user belongs to.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"string","example":"data: {\"type\":\"connected\",\"org_ids\":[\"4\"]}\n\ndata: {\"type\":\"signal.beacon\",\"payload\":{\"device_id\":\"dev-abc123-xyz\"}}\n\n"}}}}}}},"/v1/tickets":{"get":{"operationId":"getV1Tickets","summary":"List tickets / incidents","tags":["Tickets"],"description":"\n**Tickets and incidents are the same object.** Every record in this table is accessible via both `/v1/tickets` and the real-time `/v1/incidents` layer — same NocoDB row, same ID, same fields. No data is duplicated.\n\n- Use `/v1/tickets` for helpdesk, ITSM, or async workflows\n- Use `/v1/incidents` + SSE for real-time dispatch, ops war rooms, or alert routing\n- The `source` field (`api`, `email`, `webhook`, `alert`) and `priority` together imply context — neither endpoint enforces a use case on the caller\n\nReturns records for the authenticated user. Filter by `status` or `priority`. Paginated.","parameters":[{"schema":{"type":"string","enum":["open","in_progress","resolved","closed"]},"in":"query","name":"status","required":false,"description":"Filter by status"},{"schema":{"type":"string","enum":["low","normal","high","urgent"]},"in":"query","name":"priority","required":false,"description":"Filter by priority"},{"schema":{"type":"integer","default":50},"in":"query","name":"limit","required":false},{"schema":{"type":"integer","default":1},"in":"query","name":"page","required":false}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"tickets":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"sla_due_at":{"type":"string","nullable":true},"sla_breached":{"type":"boolean"},"sla_status":{"type":"string","enum":["ok","warning","breached"]},"customer_token":{"type":"string","description":"Magic token for public customer status page"}}}},"total":{"type":"integer"}},"example":{"tickets":[{"Id":42,"title":"Water main pressure drop","description":"Pressure dropped below threshold at Site Alpha.","status":"open","priority":"urgent","source":"alert","source_ref":"sensor-ops-991","user_id":7,"sla_due_at":"2026-03-15T19:00:00Z","sla_breached":false,"sla_status":"warning","customer_token":"cus_tok_abc123xyz","customer_status_url":"https://schedkit.net/incidents/status/cus_tok_abc123xyz","lat":35.4676,"lng":-97.5164,"location_name":"Site Alpha"}],"total":1}}}}}}},"post":{"operationId":"postV1Tickets","summary":"Create ticket / incident","tags":["Tickets"],"description":"\n**Tickets and incidents are the same object.** Every record in this table is accessible via both `/v1/tickets` and the real-time `/v1/incidents` layer — same NocoDB row, same ID, same fields. No data is duplicated.\n\n- Use `/v1/tickets` for helpdesk, ITSM, or async workflows\n- Use `/v1/incidents` + SSE for real-time dispatch, ops war rooms, or alert routing\n- The `source` field (`api`, `email`, `webhook`, `alert`) and `priority` together imply context — neither endpoint enforces a use case on the caller\n\nCreates a new record. `title` is required. Defaults: `status=open`, `priority=normal`, `source=api`. A `customer_token` is generated automatically — use it to share the public status page at `https://schedkit.net/incidents/status/:token`. A real-time SSE event (`incident.created`) is broadcast to all connected staff.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string"},"description":{"type":"string"},"priority":{"type":"string","enum":["low","normal","high","urgent"],"default":"normal"},"source":{"type":"string","enum":["api","email","webhook","alert"],"default":"api","description":"Origin of the record. Use `alert` for automated incident creation, `email` for inbound support, `api` for programmatic creation."},"source_ref":{"type":"string","description":"External reference ID (e.g. email message ID, alert ID)"},"lat":{"type":"number","nullable":true,"description":"Incident latitude"},"lng":{"type":"number","nullable":true,"description":"Incident longitude"},"location_name":{"type":"string","nullable":true,"description":"Human-readable location name"},"customer_email":{"type":"string","nullable":true,"description":"Email to notify — if set, confirmation goes here instead of the creator"},"customer_name":{"type":"string","nullable":true,"description":"Customer display name for the email"},"org_id":{"type":"integer","nullable":true,"description":"Org this ticket belongs to — determines email branding"}}},"example":{"title":"Water main pressure drop","description":"Pressure dropped below threshold at Site Alpha.","priority":"urgent","source":"alert","source_ref":"sensor-ops-991","lat":35.4676,"lng":-97.5164,"location_name":"Site Alpha"}}}},"security":[{"apiKey":[]}],"responses":{"201":{"description":"Created ticket/incident","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Created ticket/incident","properties":{"Id":{"type":"integer"},"title":{"type":"string"},"status":{"type":"string"},"priority":{"type":"string"},"sla_due_at":{"type":"string","nullable":true},"sla_breached":{"type":"boolean"},"sla_status":{"type":"string","enum":["ok","warning","breached"]},"customer_token":{"type":"string","description":"Magic token for the public customer status page"},"customer_status_url":{"type":"string","description":"Full URL to public customer status page"},"lat":{"type":"number","nullable":true},"lng":{"type":"number","nullable":true},"location_name":{"type":"string","nullable":true}},"example":{"Id":42,"title":"Water main pressure drop","description":"Pressure dropped below threshold at Site Alpha.","status":"open","priority":"urgent","source":"alert","source_ref":"sensor-ops-991","user_id":7,"sla_due_at":"2026-03-15T19:00:00Z","sla_breached":false,"sla_status":"warning","customer_token":"cus_tok_abc123xyz","customer_status_url":"https://schedkit.net/incidents/status/cus_tok_abc123xyz","lat":35.4676,"lng":-97.5164,"location_name":"Site Alpha"}}}}}}}},"/v1/tickets/{id}":{"get":{"operationId":"getV1Tickets_:id","summary":"Get ticket / incident","tags":["Tickets"],"description":"\n**Tickets and incidents are the same object.** Every record in this table is accessible via both `/v1/tickets` and the real-time `/v1/incidents` layer — same NocoDB row, same ID, same fields. No data is duplicated.\n\n- Use `/v1/tickets` for helpdesk, ITSM, or async workflows\n- Use `/v1/incidents` + SSE for real-time dispatch, ops war rooms, or alert routing\n- The `source` field (`api`, `email`, `webhook`, `alert`) and `priority` together imply context — neither endpoint enforces a use case on the caller\n\nReturns a single record by ID.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"sla_due_at":{"type":"string","nullable":true},"sla_breached":{"type":"boolean"},"sla_status":{"type":"string","enum":["ok","warning","breached"]},"customer_token":{"type":"string"},"customer_status_url":{"type":"string"}},"example":{"Id":42,"title":"Water main pressure drop","description":"Pressure dropped below threshold at Site Alpha.","status":"open","priority":"urgent","source":"alert","source_ref":"sensor-ops-991","user_id":7,"sla_due_at":"2026-03-15T19:00:00Z","sla_breached":false,"sla_status":"warning","customer_token":"cus_tok_abc123xyz","customer_status_url":"https://schedkit.net/incidents/status/cus_tok_abc123xyz","lat":35.4676,"lng":-97.5164,"location_name":"Site Alpha"}}}}}}},"patch":{"operationId":"patchV1Tickets_:id","summary":"Update ticket / incident","tags":["Tickets"],"description":"\n**Tickets and incidents are the same object.** Every record in this table is accessible via both `/v1/tickets` and the real-time `/v1/incidents` layer — same NocoDB row, same ID, same fields. No data is duplicated.\n\n- Use `/v1/tickets` for helpdesk, ITSM, or async workflows\n- Use `/v1/incidents` + SSE for real-time dispatch, ops war rooms, or alert routing\n- The `source` field (`api`, `email`, `webhook`, `alert`) and `priority` together imply context — neither endpoint enforces a use case on the caller\n\nUpdate status, priority, assignee, title, or description. Broadcasts `incident.updated` (or `incident.resolved`) to all connected SSE clients.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":"string"},"status":{"type":"string","enum":["open","in_progress","resolved","closed"]},"priority":{"type":"string","enum":["low","normal","high","urgent"]},"assignee_id":{"type":"integer","nullable":true},"lat":{"type":"number","nullable":true},"lng":{"type":"number","nullable":true},"location_name":{"type":"string","nullable":true}}},"example":{"status":"in_progress","priority":"high","assignee_id":12,"location_name":"Site Alpha North Gate"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"Id":42,"title":"Water main pressure drop","description":"Pressure dropped below threshold at Site Alpha.","status":"in_progress","priority":"high","source":"alert","source_ref":"sensor-ops-991","user_id":7,"sla_due_at":"2026-03-15T19:00:00Z","sla_breached":false,"sla_status":"warning","customer_token":"cus_tok_abc123xyz","customer_status_url":"https://schedkit.net/incidents/status/cus_tok_abc123xyz","lat":35.4676,"lng":-97.5164,"location_name":"Site Alpha North Gate","assignee_id":12}}}}}}},"delete":{"operationId":"deleteV1Tickets_:id","summary":"Close ticket / incident","tags":["Tickets"],"description":"Sets status to `closed`. Does not delete the record. Broadcasts `incident.resolved` to SSE clients.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"status":{"type":"string"},"id":{"type":"integer"}},"example":{"ok":true,"status":"closed","id":42}}}}}}}},"/v1/incidents/stream":{"get":{"operationId":"getV1IncidentsStream","summary":"SSE event stream","tags":["Incidents"],"description":"Server-Sent Events stream for real-time incident updates. Requires auth. Keep connection open — events delivered as `data: {...}\\n\\n`.","security":[{"apiKey":[]},{"cookieAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/v1/incidents/{token}/public-stream":{"get":{"operationId":"getV1Incidents_:tokenPublic-stream","summary":"Public SSE stream for customer status page","tags":["Incidents"],"description":"Filtered SSE stream for a specific ticket identified by its customer_token. No auth required.","parameters":[{"schema":{"type":"string"},"in":"path","name":"token","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/incidents/{id}/join":{"post":{"operationId":"postV1Incidents_:idJoin","summary":"Join incident as responder","tags":["Incidents"],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"cookieAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/v1/incidents/{id}/leave":{"post":{"operationId":"postV1Incidents_:idLeave","summary":"Leave incident","tags":["Incidents"],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"cookieAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/v1/incidents/{id}/replies":{"post":{"operationId":"postV1Incidents_:idReplies","summary":"Add reply to incident","tags":["Incidents"],"description":"Staff: authenticate with API key or session. Customer: pass `customer_token` query param.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["body"],"properties":{"body":{"type":"string"},"author_name":{"type":"string"},"author_email":{"type":"string"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"query","name":"customer_token","required":false},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}},"get":{"operationId":"getV1Incidents_:idReplies","summary":"Get reply thread","tags":["Incidents"],"description":"Returns all replies for an incident. Auth required or customer_token query param.","parameters":[{"schema":{"type":"string"},"in":"query","name":"customer_token","required":false},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/incidents/{id}/responders/location":{"patch":{"operationId":"patchV1Incidents_:idRespondersLocation","summary":"Update responder location","tags":["Incidents"],"description":"Broadcast responder position update. Auth required. Emits `responder.moved` SSE event.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["lat","lng"],"properties":{"lat":{"type":"number"},"lng":{"type":"number"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]},{"cookieAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/v1/work-orders":{"get":{"operationId":"getV1Work-orders","summary":"List work orders","tags":["Work Orders"],"parameters":[{"schema":{"type":"string"},"in":"query","name":"status","required":false},{"schema":{"type":"string"},"in":"query","name":"priority","required":false},{"schema":{"type":"string"},"in":"query","name":"search","required":false},{"schema":{"type":"string","enum":["me","unassigned","all"]},"in":"query","name":"assignee","required":false,"description":"Filter by assignment"},{"schema":{"type":"integer","default":50},"in":"query","name":"limit","required":false},{"schema":{"type":"integer","default":1},"in":"query","name":"page","required":false}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response"}}},"post":{"operationId":"postV1Work-orders","summary":"Create work order","tags":["Work Orders"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string"},"description":{"type":"string"},"site_address":{"type":"string"},"site_notes":{"type":"string"},"status":{"type":"string","enum":["draft","scheduled","in_progress","on_hold","completed","signed_off","closed"]},"priority":{"type":"string","enum":["low","normal","high","urgent"]},"scheduled_start":{"type":"string"},"scheduled_end":{"type":"string"},"booking_id":{"type":"string"},"customer_name":{"type":"string"},"customer_email":{"type":"string"},"org_id":{"type":"string"},"assignee_id":{"type":"string","nullable":true},"lat":{"type":"number"},"lng":{"type":"number"},"location_name":{"type":"string"},"checklist":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"required":{"type":"boolean"},"sort_order":{"type":"integer"}}}}}}}}},"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response"}}}},"/v1/work-orders/assignees":{"get":{"operationId":"getV1Work-ordersAssignees","summary":"List assignable users for an org","tags":["Work Orders"],"parameters":[{"schema":{"type":"string"},"in":"query","name":"org_id","required":false},{"schema":{"type":"string"},"in":"query","name":"work_order_id","required":false,"description":"Authorizes via can_manage on this work order"}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response"}}}},"/v1/work-orders/{id}":{"get":{"operationId":"getV1Work-orders_:id","summary":"Get work order detail","tags":["Work Orders"],"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}},"patch":{"operationId":"patchV1Work-orders_:id","summary":"Update work order","tags":["Work Orders"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}},"delete":{"operationId":"deleteV1Work-orders_:id","summary":"Delete work order","tags":["Work Orders"],"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/work-orders/{id}/start":{"post":{"operationId":"postV1Work-orders_:idStart","summary":"Start work order","tags":["Work Orders"],"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/work-orders/{id}/pause":{"post":{"operationId":"postV1Work-orders_:idPause","summary":"Pause work order","tags":["Work Orders"],"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/work-orders/{id}/complete":{"post":{"operationId":"postV1Work-orders_:idComplete","summary":"Complete work order","tags":["Work Orders"],"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/work-orders/{id}/en-route":{"post":{"operationId":"postV1Work-orders_:idEn-route","summary":"Mark technician en route (shows on customer portal when beacon active)","tags":["Work Orders"],"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/work-orders/{id}/ack":{"post":{"operationId":"postV1Work-orders_:idAck","summary":"Radio/dispatch ack — assignee or dispatcher confirms receipt","tags":["Work Orders"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["role"],"properties":{"role":{"type":"string","enum":["assignee","dispatch"]}}}}}},"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/work-orders/{id}/incidents":{"post":{"operationId":"postV1Work-orders_:idIncidents","summary":"Link incident to work order","tags":["Work Orders"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ticket_id"],"properties":{"ticket_id":{"type":"string"}}}}}},"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/work-orders/{id}/incidents/{ticketId}":{"delete":{"operationId":"deleteV1Work-orders_:idIncidents_:ticketId","summary":"Unlink incident","tags":["Work Orders"],"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"ticketId","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/work-orders/{id}/booking":{"patch":{"operationId":"patchV1Work-orders_:idBooking","summary":"Link or unlink manifest assignment","tags":["Work Orders"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"booking_id":{"type":"string","nullable":true}}}}}},"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/work-orders/{id}/time":{"get":{"operationId":"getV1Work-orders_:idTime","summary":"List time entries","tags":["Work Orders"],"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}},"post":{"operationId":"postV1Work-orders_:idTime","summary":"Add manual time entry","tags":["Work Orders"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["started_at"],"properties":{"entry_type":{"type":"string","enum":["travel","on_site","admin","other"]},"started_at":{"type":"string"},"ended_at":{"type":"string"},"notes":{"type":"string"}}}}}},"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/work-orders/{id}/time/{entryId}":{"patch":{"operationId":"patchV1Work-orders_:idTime_:entryId","summary":"Update time entry","tags":["Work Orders"],"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"entryId","required":true}],"responses":{"200":{"description":"Default Response"}}},"delete":{"operationId":"deleteV1Work-orders_:idTime_:entryId","summary":"Delete time entry","tags":["Work Orders"],"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"entryId","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/work-orders/{id}/time/clock-in":{"post":{"operationId":"postV1Work-orders_:idTimeClock-in","summary":"Clock in on work order","tags":["Work Orders"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"entry_type":{"type":"string","enum":["travel","on_site","admin","other"]},"notes":{"type":"string"}}}}}},"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/work-orders/{id}/time/clock-out":{"post":{"operationId":"postV1Work-orders_:idTimeClock-out","summary":"Clock out on work order","tags":["Work Orders"],"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/work-orders/{id}/checklist":{"get":{"operationId":"getV1Work-orders_:idChecklist","summary":"List checklist items","tags":["Work Orders"],"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}},"post":{"operationId":"postV1Work-orders_:idChecklist","summary":"Add checklist item","tags":["Work Orders"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["label"],"properties":{"label":{"type":"string"},"required":{"type":"boolean"},"sort_order":{"type":"integer"}}}}}},"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/work-orders/{id}/checklist/{itemId}":{"patch":{"operationId":"patchV1Work-orders_:idChecklist_:itemId","summary":"Update checklist item","tags":["Work Orders"],"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"itemId","required":true}],"responses":{"200":{"description":"Default Response"}}},"delete":{"operationId":"deleteV1Work-orders_:idChecklist_:itemId","summary":"Delete checklist item","tags":["Work Orders"],"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"itemId","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/work-orders/{id}/line-items":{"get":{"operationId":"getV1Work-orders_:idLine-items","summary":"List parts/materials","tags":["Work Orders"],"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}},"post":{"operationId":"postV1Work-orders_:idLine-items","summary":"Add line item","tags":["Work Orders"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["description"],"properties":{"description":{"type":"string"},"sku":{"type":"string"},"quantity":{"type":"number"},"unit":{"type":"string"},"unit_cost":{"type":"number"},"notes":{"type":"string"}}}}}},"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/work-orders/{id}/line-items/{lineId}":{"patch":{"operationId":"patchV1Work-orders_:idLine-items_:lineId","summary":"Update line item","tags":["Work Orders"],"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"lineId","required":true}],"responses":{"200":{"description":"Default Response"}}},"delete":{"operationId":"deleteV1Work-orders_:idLine-items_:lineId","summary":"Delete line item","tags":["Work Orders"],"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"lineId","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/work-orders/{id}/attachments":{"get":{"operationId":"getV1Work-orders_:idAttachments","summary":"List attachments","tags":["Work Orders"],"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}},"post":{"operationId":"postV1Work-orders_:idAttachments","summary":"Register attachment metadata after upload","tags":["Work Orders"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string"},"filename":{"type":"string"},"mime_type":{"type":"string"},"caption":{"type":"string"},"category":{"type":"string","enum":["before","during","after","inspection","other"]}}}}}},"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/work-orders/{id}/attachments/{attId}":{"patch":{"operationId":"patchV1Work-orders_:idAttachments_:attId","summary":"Update attachment caption or photo markup","tags":["Work Orders"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"caption":{"type":"string"},"category":{"type":"string","enum":["before","during","after","inspection","other"]},"annotations":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["arrow","line"]},"x1":{"type":"number"},"y1":{"type":"number"},"x2":{"type":"number"},"y2":{"type":"number"},"color":{"type":"string"}}}}}}}}},"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"attId","required":true}],"responses":{"200":{"description":"Default Response"}}},"delete":{"operationId":"deleteV1Work-orders_:idAttachments_:attId","summary":"Delete attachment record","tags":["Work Orders"],"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"attId","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/work-orders/{id}/signatures":{"post":{"operationId":"postV1Work-orders_:idSignatures","summary":"Add signature (technician)","tags":["Work Orders"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["signer_name","image_url"],"properties":{"role":{"type":"string","enum":["technician","customer"]},"signer_name":{"type":"string"},"image_url":{"type":"string"}}}}}},"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/work-orders/{id}/report.pdf":{"get":{"operationId":"getV1Work-orders_:idReport.pdf","summary":"Download evidence pack PDF","tags":["Work Orders"],"security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/incidents/war-room":{"get":{"operationId":"getIncidentsWar-room","summary":"War Room — real-time incident command center","tags":["Incidents"],"description":"Full-screen terminal-aesthetic incident dashboard. Requires session auth.","responses":{"200":{"description":"Default Response"}}}},"/incidents/status/{token}":{"get":{"operationId":"getIncidentsStatus_:token","summary":"Customer status page","tags":["Incidents"],"description":"Public status page for a ticket/incident, accessed via magic link token. No auth required. Customers can view status, reply thread, and post replies.","parameters":[{"schema":{"type":"string"},"in":"path","name":"token","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/work-orders/status/{token}/live.json":{"get":{"operationId":"getWork-ordersStatus_:tokenLive.json","summary":"Customer portal live status (en route beacon)","tags":["Work Orders"],"parameters":[{"schema":{"type":"string"},"in":"path","name":"token","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/work-orders/status/{token}":{"get":{"operationId":"getWork-ordersStatus_:token","summary":"Customer work order status page","tags":["Work Orders"],"description":"Public portal — no auth. Shows progress, checklist, photos, customer sign-off.","parameters":[{"schema":{"type":"string"},"in":"path","name":"token","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/work-orders/status/{token}/sign":{"post":{"operationId":"postWork-ordersStatus_:tokenSign","summary":"Customer signature (public)","tags":["Work Orders"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["signer_name","image_url"],"properties":{"signer_name":{"type":"string"},"image_url":{"type":"string"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"token","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/work-orders/status/{token}/report.pdf":{"get":{"operationId":"getWork-ordersStatus_:tokenReport.pdf","summary":"Customer PDF download (public)","tags":["Work Orders"],"parameters":[{"schema":{"type":"string"},"in":"path","name":"token","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/settings":{"get":{"operationId":"getV1Settings","summary":"Get user settings","tags":["Settings"],"description":"Returns the authenticated user's settings: `ntfy_topic`, `plan`, and `email`. Auth via `x-api-key` or session cookie.","security":[{"apiKey":[]},{"cookieAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","description":"User email address"},"plan":{"type":"string","description":"Subscription plan (free, pro, enterprise)"},"ntfy_topic":{"type":"string","description":"ntfy.sh topic slug for push notifications"}}}}}}}},"patch":{"operationId":"patchV1Settings","summary":"Update user settings","tags":["Settings"],"description":"Update the authenticated user's settings. Currently supports `ntfy_topic` — a valid ntfy.sh slug (alphanumeric, hyphens, underscores, max 64 chars). Set to empty string `\"\"` to clear.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ntfy_topic":{"type":"string","description":"ntfy.sh topic slug. Must match `[a-zA-Z0-9_-]{1,64}` or be empty to clear."}}}}}},"security":[{"apiKey":[]},{"cookieAuth":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string"},"plan":{"type":"string"},"ntfy_topic":{"type":"string"}}}}}},"400":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}}}}},"/v1/upload/capture":{"post":{"operationId":"postV1UploadCapture","summary":"Upload a capture image","tags":["Signals"],"description":"Accept a raw JPEG, PNG, or WebP image upload from Beacon Mode or another authenticated client, save it to `public/captures`, and return a permanent static URL.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"},"filename":{"type":"string"},"size":{"type":"integer"}},"example":{"url":"/captures/cap_1742061600000_ab12c.jpg","filename":"cap_1742061600000_ab12c.jpg","size":184233}}}}}}}},"/v1/upload/work-order/{uid}":{"post":{"operationId":"postV1UploadWork-order_:uid","summary":"Upload work order attachment image","tags":["Work Orders"],"parameters":[{"schema":{"type":"string"},"in":"path","name":"uid","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/upload/work-order-public/{token}":{"post":{"operationId":"postV1UploadWork-order-public_:token","summary":"Upload signature image (customer portal)","tags":["Work Orders"],"parameters":[{"schema":{"type":"string"},"in":"path","name":"token","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/billing/subscribe":{"post":{"operationId":"postV1BillingSubscribe","summary":"Start subscription","tags":["Billing"],"description":"Creates a Stripe customer and subscription. Returns a PaymentIntent clientSecret for use with Stripe Elements.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["plan"],"properties":{"plan":{"type":"string","enum":["starter","agency"]}}}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"}}}}}}}}},"/v1/billing/portal":{"get":{"operationId":"getV1BillingPortal","summary":"Open Stripe Customer Portal","tags":["Billing"],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"}}}}}}}}},"/v1/billing/webhook":{"post":{"operationId":"postV1BillingWebhook","summary":"Stripe webhook receiver","tags":["Billing"],"responses":{"200":{"description":"Default Response"}}}},"/v1/alerts":{"post":{"operationId":"postV1Alerts","summary":"Fire an alert","tags":["Alerts"],"description":"Create a new alert. `critical` severity automatically creates an incident ticket with a 1h SLA and broadcasts to all SSE clients. All alerts trigger push + ntfy notifications if the user has a topic configured.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string"},"body":{"type":"string"},"severity":{"type":"string","enum":["info","warning","critical"],"default":"warning"},"source":{"type":"string","enum":["api","webhook","sensor","noaa","booking","scheduled"],"default":"api"},"source_ref":{"type":"string","description":"External reference (sensor ID, webhook ID, etc.)"},"org_id":{"type":"integer","nullable":true},"lat":{"type":"number","nullable":true},"lng":{"type":"number","nullable":true},"location_name":{"type":"string","nullable":true},"meta":{"type":"object","additionalProperties":true,"nullable":true}}},"example":{"title":"Pressure sensor offline","body":"Sensor unit-7 at Site Alpha has not reported in 5 minutes.","severity":"critical","source":"sensor","source_ref":"sensor-unit-7","lat":35.4676,"lng":-97.5164,"location_name":"Site Alpha"}}}},"security":[{"apiKey":[]}],"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"Id":1,"title":"Pressure sensor offline","body":"Sensor unit-7 at Site Alpha has not reported in 5 minutes.","severity":"critical","source":"sensor","source_ref":"sensor-unit-7","status":"firing","user_id":1,"org_id":3,"ticket_id":null,"lat":35.4676,"lng":-97.5164,"location_name":"Site Alpha","meta":null,"fired_at":"2026-03-24T12:00:00Z","acked_at":null,"resolved_at":null}}}}}}},"get":{"operationId":"getV1Alerts","summary":"List alerts","tags":["Alerts"],"description":"Returns alerts for the authenticated user, newest first. Filter by `status` or `severity`.","parameters":[{"schema":{"type":"string","enum":["firing","acked","resolved"]},"in":"query","name":"status","required":false},{"schema":{"type":"string","enum":["info","warning","critical"]},"in":"query","name":"severity","required":false},{"schema":{"type":"integer","default":50},"in":"query","name":"limit","required":false},{"schema":{"type":"integer","default":1},"in":"query","name":"page","required":false}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"alerts":{"type":"array","items":{"type":"object","additionalProperties":true}},"total":{"type":"integer"}},"example":{"alerts":[{"Id":1,"title":"Pressure sensor offline","body":"Sensor unit-7 at Site Alpha has not reported in 5 minutes.","severity":"critical","source":"sensor","source_ref":"sensor-unit-7","status":"firing","user_id":1,"org_id":3,"ticket_id":null,"lat":35.4676,"lng":-97.5164,"location_name":"Site Alpha","meta":null,"fired_at":"2026-03-24T12:00:00Z","acked_at":null,"resolved_at":null}],"total":1}}}}}}}},"/v1/alerts/{id}":{"get":{"operationId":"getV1Alerts_:id","summary":"Get alert","tags":["Alerts"],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"Id":1,"title":"Pressure sensor offline","body":"Sensor unit-7 at Site Alpha has not reported in 5 minutes.","severity":"critical","source":"sensor","source_ref":"sensor-unit-7","status":"firing","user_id":1,"org_id":3,"ticket_id":null,"lat":35.4676,"lng":-97.5164,"location_name":"Site Alpha","meta":null,"fired_at":"2026-03-24T12:00:00Z","acked_at":null,"resolved_at":null}}}}}}},"patch":{"operationId":"patchV1Alerts_:id","summary":"Acknowledge or resolve an alert","tags":["Alerts"],"description":"Set status to `acked` or `resolved`. Timestamps `acked_at` / `resolved_at` automatically. Broadcasts `alert.acked` or `alert.resolved` via SSE.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["acked","resolved"]}}},"examples":{"example1":{"value":{"status":"acked"}},"example2":{"value":{"status":"resolved"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}},"delete":{"operationId":"deleteV1Alerts_:id","summary":"Delete an alert","tags":["Alerts"],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"example":{"ok":true}}}}}}}},"/v1/alerts/stream":{"get":{"operationId":"getV1AlertsStream","summary":"Open SSE stream for live alert events","tags":["Alerts"],"description":"Emits `alert.fired`, `alert.acked`, and `alert.resolved` events in real time.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"string","example":"data: {\"type\":\"connected\"}\n\ndata: {\"type\":\"alert.fired\",\"payload\":{...}}\n\n"}}}}}}},"/v1/admin/users":{"get":{"operationId":"getV1AdminUsers","summary":"List users (platform admin)","tags":["Admin"],"security":[{"cookieAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/v1/admin/users/{id}":{"patch":{"operationId":"patchV1AdminUsers_:id","summary":"Update a user (platform admin)","tags":["Admin"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"plan":{"type":"string","enum":["free","starter","agency","enterprise"]},"active":{"type":"boolean"},"name":{"type":"string"},"slug":{"type":"string"},"timezone":{"type":"string"},"ntfy_topic":{"type":"string"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"cookieAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/v1/portal/auth/capabilities":{"get":{"operationId":"getV1PortalAuthCapabilities","summary":"Portal auth capabilities","tags":["Portal"],"responses":{"200":{"description":"Default Response"}}}},"/v1/portal/auth/login":{"post":{"operationId":"postV1PortalAuthLogin","summary":"Client portal login","tags":["Portal"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["password"],"properties":{"username":{"type":"string"},"email":{"type":"string"},"password":{"type":"string"}}}}}},"responses":{"200":{"description":"Default Response"}}}},"/v1/portal/auth/me":{"get":{"operationId":"getV1PortalAuthMe","summary":"Portal session check","tags":["Portal"],"responses":{"200":{"description":"Default Response"}}}},"/v1/portal/auth/logout":{"post":{"operationId":"postV1PortalAuthLogout","summary":"Portal logout","tags":["Portal"],"responses":{"200":{"description":"Default Response"}}}},"/v1/portal/departments":{"get":{"operationId":"getV1PortalDepartments","summary":"List ticket departments for portal","tags":["Portal"],"responses":{"200":{"description":"Default Response"}}}},"/v1/portal/tickets":{"get":{"operationId":"getV1PortalTickets","summary":"List client tickets","tags":["Portal"],"parameters":[{"schema":{"type":"string"},"in":"query","name":"status","required":false},{"schema":{"type":"integer"},"in":"query","name":"page","required":false}],"responses":{"200":{"description":"Default Response"}}},"post":{"operationId":"postV1PortalTickets","summary":"Open a new support ticket","tags":["Portal"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["subject","message"],"properties":{"department_id":{"type":"integer"},"subject":{"type":"string"},"message":{"type":"string"},"priority":{"type":"string"}}}}}},"responses":{"200":{"description":"Default Response"}}}},"/v1/portal/tickets/summary":{"get":{"operationId":"getV1PortalTicketsSummary","summary":"Recent open tickets for dashboard","tags":["Portal"],"responses":{"200":{"description":"Default Response"}}}},"/v1/portal/tickets/{id}":{"get":{"operationId":"getV1PortalTickets_:id","summary":"Get ticket with replies","tags":["Portal"],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}},"patch":{"operationId":"patchV1PortalTickets_:id","summary":"Update ticket (close)","tags":["Portal"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/portal/tickets/{id}/replies":{"post":{"operationId":"postV1PortalTickets_:idReplies","summary":"Reply to a ticket","tags":["Portal"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["message"],"properties":{"message":{"type":"string"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/v1/portal/inbound/ticket":{"post":{"operationId":"postV1PortalInboundTicket","summary":"Create ticket from contact form or inbound email (shared secret)","tags":["Portal"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","subject","message"],"properties":{"org_slug":{"type":"string"},"name":{"type":"string"},"email":{"type":"string"},"subject":{"type":"string"},"message":{"type":"string"},"source":{"type":"string","enum":["api","email","webhook"]},"source_ref":{"type":"string"},"department_slug":{"type":"string"},"priority":{"type":"string","enum":["low","normal","high","urgent"]}}}}}},"responses":{"200":{"description":"Default Response"}}}},"/health":{"get":{"operationId":"getHealth","summary":"Health check","tags":["System"],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"service":{"type":"string"}}}}}}}}},"/version":{"get":{"operationId":"getVersion","summary":"Service version","tags":["System"],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"version":{"type":"string"},"commit":{"type":"string"}}}}}}}}},"/v1/request-access":{"post":{"operationId":"postV1Request-access","summary":"Request early access","tags":["System"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","email"],"properties":{"name":{"type":"string"},"email":{"type":"string","format":"email"},"company":{"type":"string"},"message":{"type":"string"},"plan":{"type":"string","enum":["free","starter","agency","enterprise"]}}}}}},"responses":{"200":{"description":"Default Response"}}}}},"servers":[{"url":"https://schedkit.net","description":"Production"}],"tags":[{"name":"Event Types","description":"Create and manage bookable event types. Each event type has a slug used in the public manifest link (`/assign/:username/:slug`). Set `requires_confirmation: true` to hold assignments as pending until you manually accept or decline."},{"name":"Availability","description":"Define your weekly recurring availability. Rules are per day-of-week with a start/end time in HH:MM format. The slots engine uses these rules minus existing assignments and blocked times."},{"name":"Bookings","description":"List, retrieve, reschedule, cancel, confirm, or decline bookings. Bookings with `status=pending` are awaiting host confirmation (requires_confirmation flow)."},{"name":"Auth","description":"Magic link authentication. Request a login link via email, verify it to get a session, or use API keys for programmatic access."},{"name":"Public","description":"Unauthenticated endpoints used by the booking page — available slots, booking submission, cancellation/reschedule via token."},{"name":"Blackout Dates","description":"Block specific dates or date ranges from accepting assignments."},{"name":"Clients","description":"Client risk flags — mark clients as caution, high-risk, or blocked. Flags appear in host notification emails."},{"name":"Notifications","description":"Configure ntfy.sh push notification topics for new assignments."},{"name":"Organizations","description":"Multi-user org and team management. Invite members, create shared team event types."},{"name":"Calendar","description":"Google Calendar OAuth connection for two-way sync."},{"name":"Users","description":"User profile and API key management."},{"name":"Tickets","description":"Ticket and incident management. **Tickets and incidents are the same object** — every record is accessible via both `/v1/tickets` (async/helpdesk) and `/v1/incidents` (real-time SSE/dispatch). The `source` field (`api`, `email`, `webhook`, `alert`) and `priority` together imply context. Neither endpoint enforces a use case."},{"name":"Incidents","description":"Real-time incident coordination layer. **Same underlying records as `/v1/tickets`** — no separate table. Adds SSE streaming, responder management, and reply threads on top of the ticket object. Use `/v1/incidents` for ops war rooms, dispatch systems, or alert pipelines. Use `/v1/tickets` for helpdesk or ITSM flows. Both share identical data."},{"name":"Settings","description":"User settings management. Get or update per-user configuration — currently `ntfy_topic` for push notifications via ntfy.sh."},{"name":"Alerts","description":"Inbound signal alerts — fire, acknowledge, and resolve alerts from any source (sensor, webhook, NOAA, API). Critical alerts auto-create incident tickets. Live events via SSE stream."},{"name":"Work Orders","description":"Field job documentation — timelog, inspection photos, checklists, parts/materials, signatures, customer portal, and PDF evidence packs. Optional links to incidents and manifest assignments."},{"name":"Portal","description":"Project Seven client portal — auth and support tickets via SchedKit."}]}