{"openapi":"3.0.0","paths":{"/v1/workspaces/{workspace_id}/waitlist/settings":{"get":{"description":"The settings plus `email_setup` (whether Waitlist emails can go out and what is missing; the sender domain and template names are checked against Mail, cached for up to 60 seconds) and `skipped_sends` (emails not sent in the last 7 days because the setup was incomplete). Waitlist never falls back to a ProductCraft sender: an email that cannot go out is recorded, not sent.","operationId":"SettingsController_get","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Workspace Waitlist settings with the email setup state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaitlistSettingsResponseDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"401":{"description":"No valid session or API key.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["UNAUTHORIZED"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"403":{"description":"Caller lacks `waitlist.settings.read` on the workspace.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["FORBIDDEN"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"404":{"description":"Workspace does not exist or is not Waitlist-enabled.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Read workspace-level Waitlist defaults","tags":["waitlist-settings"]},"put":{"operationId":"SettingsController_update","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSettingsDto"}}}},"responses":{"200":{"description":"Updated workspace Waitlist settings with the email setup state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaitlistSettingsResponseDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"Validation failure on the patch body (per `UpdateSettingsDto`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["BAD_REQUEST","VALIDATION_ERROR"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"401":{"description":"No valid session or API key.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["UNAUTHORIZED"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"403":{"description":"Caller lacks `waitlist.settings.update` on the workspace.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["FORBIDDEN"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"404":{"description":"Workspace does not exist or is not Waitlist-enabled.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Update workspace-level Waitlist defaults","tags":["waitlist-settings"]}},"/v1/workspaces/{workspace_id}/waitlist/settings/notifications/{slot}/test":{"post":{"description":"Sends the slot's real Mail template, with the workspace's real Waitlist sender (the workspace default or the custom address) and example values, to your own verified account address. It cannot be sent anywhere else. It uses your own Mail access, goes through the same send path as real emails, and shows up in Mail > Emails. At most 10 tests per person per hour. Not available to API keys (a key has no inbox).","operationId":"SettingsController_sendTest","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"Stripe-style idempotency key. A retried request with the same key returns the first response instead of sending a second email. 24h TTL.","required":false,"schema":{"type":"string"}},{"name":"slot","required":true,"in":"path","description":"The email to test.","schema":{"enum":["welcome","approved","rejected"],"type":"string"}}],"responses":{"202":{"description":"Accepted by Mail and queued for delivery.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationTestResponseDto"}}},"headers":{"Idempotent-Replay":{"description":"`true` when this response is a replay of an earlier request with the same `Idempotency-Key`.","schema":{"type":"string","enum":["true"]}},"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"Unknown `slot`, or `workspaceId` is not a UUID.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["BAD_REQUEST","VALIDATION_ERROR"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"401":{"description":"No valid session or API key.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["UNAUTHORIZED"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"403":{"description":"Caller lacks `waitlist.settings.update`, or Mail refused the send for your role (you need Mail send access in this workspace).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["FORBIDDEN"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"404":{"description":"Workspace not Waitlist-enabled, or the slot's template no longer exists in Mail.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"409":{"description":"Idempotency-Key reused with a different body (`IDEMPOTENCY_KEY_REUSE`). Idempotency: `IDEMPOTENCY_KEY_REUSE` when this `Idempotency-Key` was already used with a different body or route; `IDEMPOTENCY_IN_PROGRESS` while another request with the same key is still running (retry after `Retry-After` seconds, with the same key).","headers":{"Retry-After":{"description":"Seconds to wait before retrying with the same key (sent with `IDEMPOTENCY_IN_PROGRESS`).","schema":{"type":"integer"}},"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["CONFLICT","IDEMPOTENCY_KEY_REUSE","IDEMPOTENCY_IN_PROGRESS"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"412":{"description":"`no_template`: the slot has no template. `no_sender_address`: custom sender mode with no address.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["PRECONDITION_FAILED"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"422":{"description":"`operator_email_unavailable`: the caller has no verified account email (API keys included). From Mail: `no_default_sender` (workspace mode and no default sender set in Mail), `from_domain_not_verified`, `domain_not_verified_by_provider`, a suppressed address, or a render failure.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["UNPROCESSABLE_ENTITY","no_default_sender","from_domain_not_verified","domain_not_verified_by_provider"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"429":{"description":"More than 10 tests by you in the last hour (`RATE_LIMITED`), or over a Mail send limit.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["RATE_LIMITED"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"503":{"description":"`MAIL_UNREACHABLE`: Mail did not answer.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["SERVICE_UNAVAILABLE","MAIL_UNREACHABLE"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Send me a test of one Waitlist email","tags":["waitlist-settings"]}},"/v1/workspaces/{workspace_id}/waitlists":{"post":{"description":"Pass `status: \"active\"` to accept public sign-ups straight away. Without it the waitlist starts as a draft, and the response says so in `accepting_signups: false` and `signups_note`.","operationId":"WaitlistController_create","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","description":"Stripe-style idempotency key. Waitlist creation provisions a non-trivial sub-tree (variant, default referral settings, etc.); a retry without this header creates a second waitlist sharing the same name slug. 24h TTL.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWaitlistDto"}}}},"responses":{"201":{"description":"Waitlist created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaitlistResponseDto"}}},"headers":{"Idempotent-Replay":{"description":"`true` when this response is a replay of an earlier request with the same `Idempotency-Key`.","schema":{"type":"string","enum":["true"]}},"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"Validation failure, including an `allowed_origins` entry that is not a plain origin.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["BAD_REQUEST","VALIDATION_ERROR"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"403":{"description":"Caller lacks `waitlist.create`, or workspace has not enabled the `waitlist` service.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["FORBIDDEN"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"409":{"description":"Slug already taken in this workspace. Idempotency: `IDEMPOTENCY_KEY_REUSE` when this `Idempotency-Key` was already used with a different body or route; `IDEMPOTENCY_IN_PROGRESS` while another request with the same key is still running (retry after `Retry-After` seconds, with the same key).","headers":{"Retry-After":{"description":"Seconds to wait before retrying with the same key (sent with `IDEMPOTENCY_IN_PROGRESS`).","schema":{"type":"integer"}},"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["CONFLICT","IDEMPOTENCY_KEY_REUSE","IDEMPOTENCY_IN_PROGRESS"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Create a waitlist (workspace-scoped)","tags":["waitlists"]},"get":{"operationId":"WaitlistController_list","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string","format":"uuid"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"cursor","required":false,"in":"query","description":"Opaque cursor for pagination","schema":{"type":"string"}},{"name":"status","required":false,"in":"query","description":"Filter by status","schema":{"type":"string"}}],"responses":{"200":{"description":"Page of waitlists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaitlistListResponseDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}}},"security":[{"bearer":[]}],"summary":"List waitlists in this workspace","tags":["waitlists"]}},"/v1/workspaces/{workspace_id}/waitlists/{id}":{"get":{"operationId":"WaitlistController_get","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Waitlist row.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaitlistResponseDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"404":{"description":"Waitlist not found.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Get waitlist details","tags":["waitlists"]},"patch":{"operationId":"WaitlistController_update","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWaitlistDto"}}}},"responses":{"200":{"description":"Waitlist updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaitlistResponseDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"Validation failure, including an `allowed_origins` entry that is not a plain origin.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["BAD_REQUEST","VALIDATION_ERROR"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"404":{"description":"Waitlist not found.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Update waitlist metadata and settings","tags":["waitlists"]},"delete":{"operationId":"WaitlistController_remove","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Waitlist deleted.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"403":{"description":"Caller lacks `waitlist.delete`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["FORBIDDEN"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"404":{"description":"Waitlist not found.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Delete a waitlist (cascades to entries)","tags":["waitlists"]}},"/v1/workspaces/{workspace_id}/waitlists/{id}/status":{"patch":{"operationId":"WaitlistController_updateStatus","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWaitlistStatusDto"}}}},"responses":{"200":{"description":"Status updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaitlistResponseDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"Invalid status transition.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["BAD_REQUEST","VALIDATION_ERROR"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"404":{"description":"Waitlist not found.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Change waitlist status","tags":["waitlists"]}},"/v1/workspaces/{workspace_id}/waitlists/{waitlist_id}/entries":{"get":{"operationId":"EntryController_list","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"waitlist_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"cursor","required":false,"in":"query","description":"Opaque cursor for pagination","schema":{"type":"string"}},{"name":"status","required":false,"in":"query","description":"Filter by status","schema":{"type":"string"}},{"name":"search","required":false,"in":"query","description":"Case-insensitive substring match across the entry `email` OR `name`. Trimmed; blank means no filter. Composes (AND) with `status`, `since` and `until`.","schema":{"type":"string","maxLength":200}},{"name":"since","required":false,"in":"query","description":"ISO-8601 inclusive lower bound on `created_at`. Malformed values are rejected with a 400.","schema":{"type":"string","format":"date-time"}},{"name":"until","required":false,"in":"query","description":"ISO-8601 exclusive upper bound on `created_at`. Must be after `since`. Malformed values are rejected with a 400.","schema":{"type":"string","format":"date-time"}},{"name":"is_test","required":false,"in":"query","description":"`true` lists only test entries (\"Add test signup\"), `false` only real ones. Omitted lists both; test entries come first (negative positions).","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Page of entries with pagination cursor.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntryListResponseDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"Invalid cursor, overlong `search`, malformed `since` / `until`, or `until <= since`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["BAD_REQUEST","VALIDATION_ERROR"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"403":{"description":"Caller lacks `entry.read`, or workspace has not enabled the `waitlist` service.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["FORBIDDEN"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"404":{"description":"Waitlist not found in this workspace.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"List entries on a waitlist (queue-position order, cursor-paginated). Filterable by status, free-text search across email/name, and a created_at window.","tags":["entries"]}},"/v1/workspaces/{workspace_id}/waitlists/{waitlist_id}/entries/count":{"get":{"operationId":"EntryController_count","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"waitlist_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"include_test","required":false,"in":"query","description":"Count test entries (\"Add test signup\") too. Default `false`: they are left out.","schema":{"default":false,"type":"boolean"}}],"responses":{"200":{"description":"Entry count.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntryCountResponseDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"404":{"description":"Waitlist not found in this workspace.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Entry count for a waitlist (test entries left out unless include_test=true)","tags":["entries"]}},"/v1/workspaces/{workspace_id}/waitlists/{waitlist_id}/entries/export.csv":{"get":{"operationId":"EntryController_exportCsv","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"waitlist_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"include_test","required":false,"in":"query","description":"Count test entries (\"Add test signup\") too. Default `false`: they are left out.","schema":{"default":false,"type":"boolean"}}],"responses":{"200":{"description":"text/csv stream — one header row + one row per entry. Content-Disposition: attachment.","content":{"text/csv":{"schema":{"type":"string"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"403":{"description":"Caller lacks `waitlist.export`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["FORBIDDEN"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"404":{"description":"Waitlist not found in this workspace.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Stream every entry as CSV (admin export; test entries left out unless include_test=true)","tags":["entries"]}},"/v1/workspaces/{workspace_id}/waitlists/{waitlist_id}/entries/{entry_id}":{"get":{"operationId":"EntryController_get","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"waitlist_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"entry_id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Entry row.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntryResponseDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"404":{"description":"Entry not found.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Get a single entry","tags":["entries"]},"patch":{"operationId":"EntryController_updateStatus","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"waitlist_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"entry_id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateEntryStatusDto"}}}},"responses":{"200":{"description":"Entry status updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntryResponseDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"Invalid status transition.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["BAD_REQUEST","VALIDATION_ERROR"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"403":{"description":"Caller lacks `entry.update`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["FORBIDDEN"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"404":{"description":"Entry not found in this waitlist.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Approve or reject an entry","tags":["entries"]},"delete":{"operationId":"EntryController_remove","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"waitlist_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"entry_id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":"Entry deleted.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"403":{"description":"Caller lacks `entry.update`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["FORBIDDEN"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"404":{"description":"Entry not found.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Delete an entry (positions are not renumbered)","tags":["entries"]}},"/v1/workspaces/{workspace_id}/waitlists/{waitlist_id}/entries/{entry_id}/invite-to-app":{"post":{"operationId":"EntryController_inviteToApp","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"waitlist_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"entry_id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteToAppDto"}}}},"responses":{"200":{"description":"Invite created in Auth","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteToAppResponseDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"422":{"description":"No linked Auth app for this workspace","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["UNPROCESSABLE_ENTITY"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Invite an approved entry into a linked Auth consumer app (one-click cross-product handoff)","tags":["entries"]}},"/v1/workspaces/{workspace_id}/waitlists/{waitlist_id}/entries/bulk":{"post":{"operationId":"EntryController_bulk","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"waitlist_id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkEntryActionDto"}}}},"responses":{"200":{"description":"`{ requested, succeeded, failed, results }`. Each row in `results` carries `{ id, status: \"ok\" | \"error\", error?: { code, message } }` so a partial failure (one bad id) doesn't fail the whole batch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkEntryActionResponseDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"Empty `ids`, more than 100 ids, or unknown action.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["BAD_REQUEST","VALIDATION_ERROR"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"403":{"description":"Caller lacks `entry.update`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["FORBIDDEN"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"404":{"description":"Waitlist not found.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Bulk-apply approve / reject / invite-to-app to multiple entries at once. Capped at 100 ids per call. Processed serially server-side; per-row results are returned so the UI can render partial successes.","tags":["entries"]}},"/v1/workspaces/{workspace_id}/waitlists/{waitlist_id}/test-entries":{"post":{"description":"Creates an entry marked `is_test` with a generated Amazon SES mailbox-simulator address (`success+test-…@simulator.amazonses.com`). Works on draft and paused waitlists and ignores `max_entries`. The entry shows in the entries list, takes a negative position, and is left out of counts, analytics, exports and webhooks unless asked. No email is sent unless `send_welcome_email` is true. Delete it like any other entry.","operationId":"TestEntryController_create","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"waitlist_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"Idempotency key (24 h). A retry with the same key returns the first test entry instead of adding a second one.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTestEntryDto"}}}},"responses":{"201":{"description":"Test entry created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntryResponseDto"}}},"headers":{"Idempotent-Replay":{"description":"`true` when this response is a replay of an earlier request with the same `Idempotency-Key`.","schema":{"type":"string","enum":["true"]}},"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"Validation failure.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["BAD_REQUEST","VALIDATION_ERROR"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"403":{"description":"Caller lacks `waitlist.update`, or workspace has not enabled the `waitlist` service.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["FORBIDDEN"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"404":{"description":"Waitlist not found in this workspace.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"409":{"description":"Idempotency-Key reused with a different body. Idempotency: `IDEMPOTENCY_KEY_REUSE` when this `Idempotency-Key` was already used with a different body or route; `IDEMPOTENCY_IN_PROGRESS` while another request with the same key is still running (retry after `Retry-After` seconds, with the same key).","headers":{"Retry-After":{"description":"Seconds to wait before retrying with the same key (sent with `IDEMPOTENCY_IN_PROGRESS`).","schema":{"type":"integer"}},"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["CONFLICT","IDEMPOTENCY_KEY_REUSE","IDEMPOTENCY_IN_PROGRESS"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"422":{"description":"The waitlist is closed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["UNPROCESSABLE_ENTITY"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Add a test signup","tags":["entries"]}},"/v1/waitlists/{workspace_slug}/{waitlist_slug}":{"get":{"operationId":"PublicWaitlistController_getPublic","parameters":[{"name":"workspace_slug","required":true,"in":"path","schema":{"type":"string"}},{"name":"waitlist_slug","required":true,"in":"path","schema":{"type":"string"}},{"name":"lang","required":false,"in":"query","description":"BCP-47 locale tag (e.g. `en`, `pt-BR`). Wins over Accept-Language when picking the variant.","schema":{"type":"string"}}],"responses":{"200":{"description":"Public waitlist info","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicWaitlistInfoDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"404":{"description":"Code `WAITLIST_NOT_FOUND`: no such waitlist, not active, or its workspace was deleted. The answer is the same in every case.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND","WAITLIST_NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"summary":"Public waitlist info (display name, description, custom-field schema, branding hints, active variant)","tags":["public-waitlist"]}},"/v1/waitlists/{workspace_slug}/{waitlist_slug}/leaderboard":{"get":{"operationId":"PublicWaitlistController_leaderboard","parameters":[{"name":"workspace_slug","required":true,"in":"path","schema":{"type":"string"}},{"name":"waitlist_slug","required":true,"in":"path","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Number of rows (1-50, default 50)","schema":{"type":"string"}}],"responses":{"200":{"description":"Array of `{ position, referral_count, masked_email }`","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PublicLeaderboardEntryDto"}}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"404":{"description":"Code `WAITLIST_NOT_FOUND`: no such waitlist, not active, or its workspace was deleted. The answer is the same in every case.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND","WAITLIST_NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"summary":"Public top-50 referral leaderboard. Emails are masked. Anonymous read; same access policy as the public waitlist info endpoint.","tags":["public-waitlist"]}},"/v1/waitlists/{workspace_slug}/{waitlist_slug}/entries":{"post":{"operationId":"PublicWaitlistController_submit","parameters":[{"name":"workspace_slug","required":true,"in":"path","schema":{"type":"string"}},{"name":"waitlist_slug","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEntryDto"}}}},"responses":{"201":{"description":"Entry created. Persist `referral_code` client-side to build the referral URL; `status` is `approved` when the waitlist auto-approves. A sign-up from an Amazon SES mailbox-simulator address (`…@simulator.amazonses.com`) is stored as a test entry (`is_test: true`, negative `position`), so test runs never land on the real list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicEntryCreatedDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"Validation or spam check failed","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["BAD_REQUEST","VALIDATION_ERROR"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"403":{"description":"The request came from a web origin (`Origin` header) that is not on the waitlist's `allowed_origins` list. Requests without an `Origin` header (server-side calls) are not checked. A CORS preflight from such an origin gets the same 403.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedErrorDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["FORBIDDEN"]},"404":{"description":"Code `WAITLIST_NOT_FOUND`: no such waitlist, or its workspace was deleted. The answer is the same in both cases.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND","WAITLIST_NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"409":{"description":"Email already on this waitlist","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["CONFLICT"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"422":{"description":"Waitlist is not active or is full","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["UNPROCESSABLE_ENTITY"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"429":{"description":"Too many requests from this IP","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["RATE_LIMITED"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"summary":"Submit a new entry to a public waitlist","tags":["public-waitlist"]}},"/v1/workspaces/{workspace_id}/waitlists/{waitlist_id}/analytics":{"get":{"description":"Test entries (\"Add test signup\") are left out unless `include_test=true`.","operationId":"AnalyticsController_get","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"waitlist_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"include_test","required":false,"in":"query","description":"Count test entries (\"Add test signup\") too. Default `false`: they are left out.","schema":{"default":false,"type":"boolean"}}],"responses":{"200":{"description":"Aggregated analytics payload for the waitlist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaitlistAnalyticsResponseDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"403":{"description":"Caller lacks `waitlist.read` on the workspace.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["FORBIDDEN"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"404":{"description":"Waitlist does not exist or is not in the path workspace.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Aggregate counts, viral coefficient, top referrers + referrals","tags":["analytics"]}},"/v1/workspaces/{workspace_id}/waitlists/{waitlist_id}/analytics/timeline":{"get":{"operationId":"AnalyticsController_timeline","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"waitlist_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"since","required":true,"in":"query","description":"ISO-8601 inclusive start","schema":{"type":"string"}},{"name":"until","required":true,"in":"query","description":"ISO-8601 exclusive end","schema":{"type":"string"}},{"name":"include_test","required":false,"in":"query","schema":{"type":"boolean","default":false},"description":"Count test entries (\"Add test signup\") too. Default `false`: they are left out."}],"responses":{"200":{"description":"Daily timeline array, oldest first, zero-count days included.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyticsTimelineResponseDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"`since` / `until` missing, malformed, or `until <= since`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["BAD_REQUEST","VALIDATION_ERROR"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"403":{"description":"Caller lacks `waitlist.read` on the workspace.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["FORBIDDEN"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"404":{"description":"Waitlist does not exist or is not in the path workspace.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Daily-bucketed signup timeline (UTC, dense — zero-count days included)","tags":["analytics"]}},"/v1/workspaces/{workspace_id}/waitlists/{waitlist_id}/variants":{"get":{"operationId":"VariantController_list","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"waitlist_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20},"description":"Page size. Defaults to 20; values above 100 are clamped to 100."},{"name":"cursor","required":false,"in":"query","schema":{"type":"string"},"description":"Opaque cursor from the previous page's `pagination.next_cursor`. Omit for the first page."}],"responses":{"200":{"description":"Page of variants in (kind, locale, created_at) order with a pagination cursor.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VariantListResponseDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"Invalid cursor.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["BAD_REQUEST","VALIDATION_ERROR"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"404":{"description":"Waitlist not found in this workspace","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"List variants on a waitlist (cursor-paginated). BREAKING: this endpoint used to return a bare JSON array; it now returns the standard `{ data, pagination }` envelope.","tags":["waitlist-variants"]},"post":{"operationId":"VariantController_create","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"waitlist_id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateVariantDto"}}}},"responses":{"201":{"description":"Variant created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VariantResponseDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"Validation failure","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["BAD_REQUEST","VALIDATION_ERROR"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"404":{"description":"Waitlist not found in this workspace","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Create a variant","tags":["waitlist-variants"]}},"/v1/workspaces/{workspace_id}/waitlists/{waitlist_id}/variants/{id}":{"patch":{"operationId":"VariantController_update","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"waitlist_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateVariantDto"}}}},"responses":{"200":{"description":"Variant updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VariantResponseDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"404":{"description":"Variant not found","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Update a variant (locale, copy, weight, active)","tags":["waitlist-variants"]},"delete":{"operationId":"VariantController_remove","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"waitlist_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":"Variant deleted","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"404":{"description":"Variant not found","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Delete a variant","tags":["waitlist-variants"]}},"/v1/workspaces/{workspace_id}/webhooks/event-types":{"get":{"description":"Each type with a plain description, its group and an example `data` object (what \"Send test\" delivers).","operationId":"WebhooksController_waitlist_workspace_listEventTypes","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListWebhookEventTypesResponseDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"401":{"description":"Missing or invalid bearer token.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["UNAUTHORIZED"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"403":{"description":"Caller lacks `waitlist.webhook.read` (reads) or `waitlist.webhook.manage` (changes, Send test, replay), or Waitlist is not enabled for the workspace (`SERVICE_NOT_ENABLED`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["FORBIDDEN","SERVICE_NOT_ENABLED"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"List the events an endpoint can subscribe to.","tags":["webhooks"]}},"/v1/workspaces/{workspace_id}/webhooks/endpoints":{"get":{"description":"At most 10, so this is always one page. Signing secrets are never returned here, only their last 4 characters.","operationId":"WebhooksController_waitlist_workspace_listEndpoints","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListWebhookEndpointsResponseDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"The scope id in the path is malformed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["BAD_REQUEST","VALIDATION_ERROR"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"401":{"description":"Missing or invalid bearer token.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["UNAUTHORIZED"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"403":{"description":"Caller lacks `waitlist.webhook.read` (reads) or `waitlist.webhook.manage` (changes, Send test, replay), or Waitlist is not enabled for the workspace (`SERVICE_NOT_ENABLED`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["FORBIDDEN","SERVICE_NOT_ENABLED"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"List webhook endpoints, oldest first.","tags":["webhooks"]},"post":{"description":"Deliveries are signed with the Standard Webhooks headers `webhook-id`, `webhook-timestamp` and `webhook-signature` (`v1,<base64 HMAC-SHA256 of \"<id>.<timestamp>.<body>\">`); the returned `whsec_…` secret works unchanged with any Standard Webhooks library. Failed deliveries are retried after 1 min, 5 min, 30 min, 2 h, 6 h and 24 h.","operationId":"WebhooksController_waitlist_workspace_createEndpoint","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"Opaque client-generated string (1-256 chars, `[A-Za-z0-9_-:]`) that makes a retry of this request safe for 24 hours. The same key with a different body returns 409 `IDEMPOTENCY_KEY_REUSE`.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookEndpointDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointWithSecretDto"}}},"headers":{"Idempotent-Replay":{"description":"`true` when this response is a replay of an earlier request with the same `Idempotency-Key`.","schema":{"type":"string","enum":["true"]}},"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"Validation failure: URL not https with a public host, or an unknown event type (`WEBHOOK_EVENT_TYPE_UNKNOWN`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["BAD_REQUEST","VALIDATION_ERROR","WEBHOOK_EVENT_TYPE_UNKNOWN"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"401":{"description":"Missing or invalid bearer token.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["UNAUTHORIZED"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"403":{"description":"Caller lacks `waitlist.webhook.read` (reads) or `waitlist.webhook.manage` (changes, Send test, replay), or Waitlist is not enabled for the workspace (`SERVICE_NOT_ENABLED`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["FORBIDDEN","SERVICE_NOT_ENABLED"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"409":{"description":"Already 10 endpoints (`WEBHOOK_ENDPOINT_LIMIT`), or Idempotency-Key reused with a different body (`IDEMPOTENCY_KEY_REUSE`). Idempotency: `IDEMPOTENCY_KEY_REUSE` when this `Idempotency-Key` was already used with a different body or route; `IDEMPOTENCY_IN_PROGRESS` while another request with the same key is still running (retry after `Retry-After` seconds, with the same key).","headers":{"Retry-After":{"description":"Seconds to wait before retrying with the same key (sent with `IDEMPOTENCY_IN_PROGRESS`).","schema":{"type":"integer"}},"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["CONFLICT","WEBHOOK_ENDPOINT_LIMIT","IDEMPOTENCY_KEY_REUSE","IDEMPOTENCY_IN_PROGRESS"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"422":{"description":"The URL points at a private, loopback or internal address.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["UNPROCESSABLE_ENTITY"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Add a webhook endpoint. Returns its signing secret once.","tags":["webhooks"]}},"/v1/workspaces/{workspace_id}/webhooks/endpoints/{endpoint_id}":{"get":{"operationId":"WebhooksController_waitlist_workspace_getEndpoint","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"endpoint_id","required":true,"in":"path","schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"`endpoint_id` is not a UUID.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["BAD_REQUEST","VALIDATION_ERROR"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"401":{"description":"Missing or invalid bearer token.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["UNAUTHORIZED"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"403":{"description":"Caller lacks `waitlist.webhook.read` (reads) or `waitlist.webhook.manage` (changes, Send test, replay), or Waitlist is not enabled for the workspace (`SERVICE_NOT_ENABLED`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["FORBIDDEN","SERVICE_NOT_ENABLED"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"404":{"description":"No such endpoint here (`WEBHOOK_ENDPOINT_NOT_FOUND`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND","WEBHOOK_ENDPOINT_NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Get one webhook endpoint (without its secret).","tags":["webhooks"]},"patch":{"description":"Only the fields sent change. `enabled: true` (or a new URL) also clears an automatic disable. Switching off cancels queued retries. The secret is unchanged; rotate it with `POST …/rotate-secret`.","operationId":"WebhooksController_waitlist_workspace_updateEndpoint","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"endpoint_id","required":true,"in":"path","schema":{"format":"uuid","type":"string"}},{"name":"Idempotency-Key","in":"header","description":"Opaque client-generated string (1-256 chars, `[A-Za-z0-9_-:]`) that makes a retry of this request safe for 24 hours. The same key with a different body returns 409 `IDEMPOTENCY_KEY_REUSE`.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWebhookEndpointDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointDto"}}},"headers":{"Idempotent-Replay":{"description":"`true` when this response is a replay of an earlier request with the same `Idempotency-Key`.","schema":{"type":"string","enum":["true"]}},"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"Validation failure, or `endpoint_id` is not a UUID.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["BAD_REQUEST","VALIDATION_ERROR"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"401":{"description":"Missing or invalid bearer token.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["UNAUTHORIZED"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"403":{"description":"Caller lacks `waitlist.webhook.read` (reads) or `waitlist.webhook.manage` (changes, Send test, replay), or Waitlist is not enabled for the workspace (`SERVICE_NOT_ENABLED`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["FORBIDDEN","SERVICE_NOT_ENABLED"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"404":{"description":"No such endpoint here (`WEBHOOK_ENDPOINT_NOT_FOUND`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND","WEBHOOK_ENDPOINT_NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"409":{"description":"Idempotency-Key reused with a different body (`IDEMPOTENCY_KEY_REUSE`). Idempotency: `IDEMPOTENCY_KEY_REUSE` when this `Idempotency-Key` was already used with a different body or route; `IDEMPOTENCY_IN_PROGRESS` while another request with the same key is still running (retry after `Retry-After` seconds, with the same key).","headers":{"Retry-After":{"description":"Seconds to wait before retrying with the same key (sent with `IDEMPOTENCY_IN_PROGRESS`).","schema":{"type":"integer"}},"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["CONFLICT","IDEMPOTENCY_KEY_REUSE","IDEMPOTENCY_IN_PROGRESS"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"422":{"description":"The new URL points at a private, loopback or internal address.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["UNPROCESSABLE_ENTITY"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Change a webhook endpoint: URL, description, events, on/off.","tags":["webhooks"]},"delete":{"description":"Queued retries are dropped. Its past attempts stay in the event log until the events expire.","operationId":"WebhooksController_waitlist_workspace_deleteEndpoint","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"endpoint_id","required":true,"in":"path","schema":{"format":"uuid","type":"string"}}],"responses":{"204":{"description":"Deleted.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"`endpoint_id` is not a UUID.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["BAD_REQUEST","VALIDATION_ERROR"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"401":{"description":"Missing or invalid bearer token.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["UNAUTHORIZED"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"403":{"description":"Caller lacks `waitlist.webhook.read` (reads) or `waitlist.webhook.manage` (changes, Send test, replay), or Waitlist is not enabled for the workspace (`SERVICE_NOT_ENABLED`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["FORBIDDEN","SERVICE_NOT_ENABLED"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"404":{"description":"No such endpoint here (`WEBHOOK_ENDPOINT_NOT_FOUND`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND","WEBHOOK_ENDPOINT_NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Delete a webhook endpoint.","tags":["webhooks"]}},"/v1/workspaces/{workspace_id}/webhooks/endpoints/{endpoint_id}/rotate-secret":{"post":{"description":"For `grace_hours` (default 24) the previous secret keeps signing too: `webhook-signature` then lists one `v1,` signature per secret, so receivers can switch over without dropping a delivery.","operationId":"WebhooksController_waitlist_workspace_rotateSecret","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"endpoint_id","required":true,"in":"path","schema":{"format":"uuid","type":"string"}},{"name":"Idempotency-Key","in":"header","description":"Opaque client-generated string (1-256 chars, `[A-Za-z0-9_-:]`) that makes a retry of this request safe for 24 hours. The same key with a different body returns 409 `IDEMPOTENCY_KEY_REUSE`.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RotateWebhookSecretDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointWithSecretDto"}}},"headers":{"Idempotent-Replay":{"description":"`true` when this response is a replay of an earlier request with the same `Idempotency-Key`.","schema":{"type":"string","enum":["true"]}},"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"`grace_hours` out of range, or `endpoint_id` is not a UUID.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["BAD_REQUEST","VALIDATION_ERROR"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"401":{"description":"Missing or invalid bearer token.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["UNAUTHORIZED"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"403":{"description":"Caller lacks `waitlist.webhook.read` (reads) or `waitlist.webhook.manage` (changes, Send test, replay), or Waitlist is not enabled for the workspace (`SERVICE_NOT_ENABLED`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["FORBIDDEN","SERVICE_NOT_ENABLED"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"404":{"description":"No such endpoint here (`WEBHOOK_ENDPOINT_NOT_FOUND`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND","WEBHOOK_ENDPOINT_NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"409":{"description":"Idempotency-Key reused with a different body (`IDEMPOTENCY_KEY_REUSE`). Idempotency: `IDEMPOTENCY_KEY_REUSE` when this `Idempotency-Key` was already used with a different body or route; `IDEMPOTENCY_IN_PROGRESS` while another request with the same key is still running (retry after `Retry-After` seconds, with the same key).","headers":{"Retry-After":{"description":"Seconds to wait before retrying with the same key (sent with `IDEMPOTENCY_IN_PROGRESS`).","schema":{"type":"integer"}},"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["CONFLICT","IDEMPOTENCY_KEY_REUSE","IDEMPOTENCY_IN_PROGRESS"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Rotate the signing secret. The new one is returned once.","tags":["webhooks"]}},"/v1/workspaces/{workspace_id}/webhooks/endpoints/{endpoint_id}/test":{"post":{"description":"Delivers the catalogue example of `event_type`, signed like any delivery, with `\"test\": true` in the body. Works while the endpoint is switched off or not subscribed to that type, is logged in the event log, and is never retried or counted toward an automatic disable. Waits up to 15 s for your receiver.","operationId":"WebhooksController_waitlist_workspace_sendTest","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"endpoint_id","required":true,"in":"path","schema":{"format":"uuid","type":"string"}},{"name":"Idempotency-Key","in":"header","description":"Opaque client-generated string (1-256 chars, `[A-Za-z0-9_-:]`) that makes a retry of this request safe for 24 hours. The same key with a different body returns 409 `IDEMPOTENCY_KEY_REUSE`.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendWebhookTestDto"}}}},"responses":{"200":{"description":"The attempt, successful or not.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeliveryAttemptDto"}}},"headers":{"Idempotent-Replay":{"description":"`true` when this response is a replay of an earlier request with the same `Idempotency-Key`.","schema":{"type":"string","enum":["true"]}},"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"Unknown `event_type`, or `endpoint_id` is not a UUID.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["BAD_REQUEST","VALIDATION_ERROR"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"401":{"description":"Missing or invalid bearer token.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["UNAUTHORIZED"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"403":{"description":"Caller lacks `waitlist.webhook.read` (reads) or `waitlist.webhook.manage` (changes, Send test, replay), or Waitlist is not enabled for the workspace (`SERVICE_NOT_ENABLED`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["FORBIDDEN","SERVICE_NOT_ENABLED"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"404":{"description":"No such endpoint here (`WEBHOOK_ENDPOINT_NOT_FOUND`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND","WEBHOOK_ENDPOINT_NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"409":{"description":"Idempotency-Key reused with a different body (`IDEMPOTENCY_KEY_REUSE`). Idempotency: `IDEMPOTENCY_KEY_REUSE` when this `Idempotency-Key` was already used with a different body or route; `IDEMPOTENCY_IN_PROGRESS` while another request with the same key is still running (retry after `Retry-After` seconds, with the same key).","headers":{"Retry-After":{"description":"Seconds to wait before retrying with the same key (sent with `IDEMPOTENCY_IN_PROGRESS`).","schema":{"type":"integer"}},"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["CONFLICT","IDEMPOTENCY_KEY_REUSE","IDEMPOTENCY_IN_PROGRESS"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Send a test event to this endpoint now and return the result.","tags":["webhooks"]}},"/v1/workspaces/{workspace_id}/webhooks/events":{"get":{"description":"Every event logged while at least one endpoint existed, with the latest state per endpoint and when the next retry is due.","operationId":"WebhooksController_waitlist_workspace_listEvents","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"type","required":false,"in":"query","description":"Only these types. Repeat the parameter or comma-separate.","schema":{"type":"array","items":{"type":"string","enum":["entry.created","entry.approved","entry.rejected"]}}},{"name":"endpoint_id","required":false,"in":"query","description":"Only events sent (or queued) to this endpoint.","schema":{"format":"uuid","type":"string"}},{"name":"status","required":false,"in":"query","description":"Only events in this delivery status.","schema":{"$ref":"#/components/schemas/WebhookEventStatus"}},{"name":"created_after","required":false,"in":"query","description":"Only events created at or after this instant.","schema":{"format":"date-time","type":"string"}},{"name":"created_before","required":false,"in":"query","description":"Only events created before this instant.","schema":{"format":"date-time","type":"string"}},{"name":"limit","required":false,"in":"query","description":"Page size.","schema":{"minimum":1,"maximum":100,"default":20,"type":"integer"}},{"name":"cursor","required":false,"in":"query","description":"The previous page's `pagination.next_cursor`.","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListWebhookEventsResponseDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"Invalid filter value or cursor.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["BAD_REQUEST","VALIDATION_ERROR"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"401":{"description":"Missing or invalid bearer token.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["UNAUTHORIZED"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"403":{"description":"Caller lacks `waitlist.webhook.read` (reads) or `waitlist.webhook.manage` (changes, Send test, replay), or Waitlist is not enabled for the workspace (`SERVICE_NOT_ENABLED`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["FORBIDDEN","SERVICE_NOT_ENABLED"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"List webhook events, newest first (kept for 30 days).","tags":["webhooks"]}},"/v1/workspaces/{workspace_id}/webhooks/events/{event_id}":{"get":{"operationId":"WebhooksController_waitlist_workspace_getEvent","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"event_id","required":true,"in":"path","description":"The event id, `evt_<uuid>`.","schema":{"type":"string","pattern":"^evt_[0-9a-f-]{36}$"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEventDetailDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"`event_id` is not `evt_<uuid>`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["BAD_REQUEST","VALIDATION_ERROR"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"401":{"description":"Missing or invalid bearer token.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["UNAUTHORIZED"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"403":{"description":"Caller lacks `waitlist.webhook.read` (reads) or `waitlist.webhook.manage` (changes, Send test, replay), or Waitlist is not enabled for the workspace (`SERVICE_NOT_ENABLED`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["FORBIDDEN","SERVICE_NOT_ENABLED"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"404":{"description":"No such event here (`WEBHOOK_EVENT_NOT_FOUND`); events are kept for 30 days.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND","WEBHOOK_EVENT_NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Get one webhook event with every delivery attempt.","tags":["webhooks"]}},"/v1/workspaces/{workspace_id}/webhooks/events/{event_id}/replay":{"post":{"description":"Same body and `webhook-id`, freshly signed, to every switched-on endpoint that receives this event type now, or only to `endpoint_id`. Waits for the receivers (up to 15 s each) and returns the attempts. A success cancels a retry still queued for that endpoint.","operationId":"WebhooksController_waitlist_workspace_replay","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"event_id","required":true,"in":"path","description":"The event id, `evt_<uuid>`.","schema":{"type":"string","pattern":"^evt_[0-9a-f-]{36}$"}},{"name":"endpoint_id","required":false,"in":"query","description":"Send to this endpoint only.","schema":{"format":"uuid","type":"string"}},{"name":"Idempotency-Key","in":"header","description":"Opaque client-generated string (1-256 chars, `[A-Za-z0-9_-:]`) that makes a retry of this request safe for 24 hours. The same key with a different body returns 409 `IDEMPOTENCY_KEY_REUSE`.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplayWebhookEventResponseDto"}}},"headers":{"Idempotent-Replay":{"description":"`true` when this response is a replay of an earlier request with the same `Idempotency-Key`.","schema":{"type":"string","enum":["true"]}},"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"`event_id` is not `evt_<uuid>`, or `endpoint_id` is not a UUID.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["BAD_REQUEST","VALIDATION_ERROR"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"401":{"description":"Missing or invalid bearer token.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["UNAUTHORIZED"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"403":{"description":"Caller lacks `waitlist.webhook.read` (reads) or `waitlist.webhook.manage` (changes, Send test, replay), or Waitlist is not enabled for the workspace (`SERVICE_NOT_ENABLED`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["FORBIDDEN","SERVICE_NOT_ENABLED"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"404":{"description":"No such event here (`WEBHOOK_EVENT_NOT_FOUND`); events are kept for 30 days. Or No such endpoint here (`WEBHOOK_ENDPOINT_NOT_FOUND`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND","WEBHOOK_EVENT_NOT_FOUND","WEBHOOK_ENDPOINT_NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"409":{"description":"`endpoint_id` is switched off or does not receive this event type (`WEBHOOK_ENDPOINT_NOT_SUBSCRIBED`), or Idempotency-Key reused with a different body (`IDEMPOTENCY_KEY_REUSE`). Idempotency: `IDEMPOTENCY_KEY_REUSE` when this `Idempotency-Key` was already used with a different body or route; `IDEMPOTENCY_IN_PROGRESS` while another request with the same key is still running (retry after `Retry-After` seconds, with the same key).","headers":{"Retry-After":{"description":"Seconds to wait before retrying with the same key (sent with `IDEMPOTENCY_IN_PROGRESS`).","schema":{"type":"integer"}},"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["CONFLICT","WEBHOOK_ENDPOINT_NOT_SUBSCRIBED","IDEMPOTENCY_KEY_REUSE","IDEMPOTENCY_IN_PROGRESS"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Send a logged event again, to its endpoints or to one endpoint.","tags":["webhooks"]}},"/v1/workspaces/{workspace_id}/webhooks":{"post":{"deprecated":true,"description":"Deprecated: use `/v1/workspaces/{workspace_id}/webhooks/endpoints` (event descriptions, Send test that reports the result, event log, replay, rotation with a grace period). Responses carry `Deprecation: true` and `Sunset: Fri, 15 Jan 2027 00:00:00 GMT`.","operationId":"WebhookController_create","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"Stripe-style idempotency key. The signing secret is returned exactly once; a retry without this header creates a second webhook + secret you never see the plaintext for. 24h TTL.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookDto"}}}},"responses":{"201":{"description":"Webhook created. Includes plaintext `signing_secret` exactly once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCreateResponseDto"}}},"headers":{"Idempotent-Replay":{"description":"`true` when this response is a replay of an earlier request with the same `Idempotency-Key`.","schema":{"type":"string","enum":["true"]}},"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"409":{"description":"The workspace already has 10 webhooks. Idempotency: `IDEMPOTENCY_KEY_REUSE` when this `Idempotency-Key` was already used with a different body or route; `IDEMPOTENCY_IN_PROGRESS` while another request with the same key is still running (retry after `Retry-After` seconds, with the same key).","headers":{"Retry-After":{"description":"Seconds to wait before retrying with the same key (sent with `IDEMPOTENCY_IN_PROGRESS`).","schema":{"type":"integer"}},"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["CONFLICT","IDEMPOTENCY_KEY_REUSE","IDEMPOTENCY_IN_PROGRESS"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Create a workspace webhook. The response includes the signing secret in clear EXACTLY ONCE — copy it now.","tags":["webhooks"]},"get":{"deprecated":true,"description":"Deprecated: use `/v1/workspaces/{workspace_id}/webhooks/endpoints` (event descriptions, Send test that reports the result, event log, replay, rotation with a grace period). Responses carry `Deprecation: true` and `Sunset: Fri, 15 Jan 2027 00:00:00 GMT`.","operationId":"WebhookController_list","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20},"description":"Page size. Defaults to 20; values above 100 are clamped to 100."},{"name":"cursor","required":false,"in":"query","schema":{"type":"string"},"description":"Opaque cursor from the previous page's `pagination.next_cursor`. Omit for the first page."}],"responses":{"200":{"description":"Page of webhooks for the workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookListResponseDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"Invalid cursor.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["BAD_REQUEST","VALIDATION_ERROR"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"List webhooks for the workspace, newest first, cursor-paginated (secrets masked)","tags":["webhooks"]}},"/v1/workspaces/{workspace_id}/webhooks/{id}":{"get":{"deprecated":true,"description":"Deprecated: use `/v1/workspaces/{workspace_id}/webhooks/endpoints` (event descriptions, Send test that reports the result, event log, replay, rotation with a grace period). Responses carry `Deprecation: true` and `Sunset: Fri, 15 Jan 2027 00:00:00 GMT`.","operationId":"WebhookController_get","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Webhook.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookResponseDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"`id` is not a UUID.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["BAD_REQUEST","VALIDATION_ERROR"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"404":{"description":"Webhook not found.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Get a webhook (secret masked)","tags":["webhooks"]},"patch":{"deprecated":true,"description":"Deprecated: use `/v1/workspaces/{workspace_id}/webhooks/endpoints` (event descriptions, Send test that reports the result, event log, replay, rotation with a grace period). Responses carry `Deprecation: true` and `Sunset: Fri, 15 Jan 2027 00:00:00 GMT`.","operationId":"WebhookController_update","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWebhookDto"}}}},"responses":{"200":{"description":"Updated webhook (secret masked).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookResponseDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"404":{"description":"Webhook not found.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Update url / events / active flag. `active: true` also clears an automatic switch-off.","tags":["webhooks"]},"delete":{"deprecated":true,"description":"Deprecated: use `/v1/workspaces/{workspace_id}/webhooks/endpoints` (event descriptions, Send test that reports the result, event log, replay, rotation with a grace period). Responses carry `Deprecation: true` and `Sunset: Fri, 15 Jan 2027 00:00:00 GMT`.","operationId":"WebhookController_remove","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"404":{"description":"Webhook not found.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Delete a webhook","tags":["webhooks"]}},"/v1/workspaces/{workspace_id}/webhooks/{id}/deliveries":{"get":{"deprecated":true,"description":"Deprecated: use `/v1/workspaces/{workspace_id}/webhooks/endpoints` (event descriptions, Send test that reports the result, event log, replay, rotation with a grace period). Responses carry `Deprecation: true` and `Sunset: Fri, 15 Jan 2027 00:00:00 GMT`.","operationId":"WebhookController_recentDeliveries","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"description":"Page size. Defaults to 50; values above 200 are clamped to 200."},{"name":"cursor","required":false,"in":"query","schema":{"type":"string"},"description":"Opaque cursor from the previous page's `pagination.next_cursor`. Omit for the first page."},{"name":"status","required":false,"in":"query","schema":{"type":"string","enum":["success","failed"]},"description":"`success` is a 2xx response; `failed` is everything else, including attempts that got no response at all (null `status_code`). Unrecognised values are rejected with a 400."}],"responses":{"200":{"description":"Delivery attempts, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeliveryListResponseDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"Invalid cursor or unknown `status` filter.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["BAD_REQUEST","VALIDATION_ERROR"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"404":{"description":"Webhook not found.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Delivery attempts for a webhook, newest first, cursor-paginated (default 50, max 200)","tags":["webhooks"]}},"/v1/workspaces/{workspace_id}/webhooks/{id}/rotate-secret":{"post":{"deprecated":true,"description":"Deprecated: use `/v1/workspaces/{workspace_id}/webhooks/endpoints` (event descriptions, Send test that reports the result, event log, replay, rotation with a grace period). Responses carry `Deprecation: true` and `Sunset: Fri, 15 Jan 2027 00:00:00 GMT`.","operationId":"WebhookController_rotateSecret","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"Secret rotated. Includes the new plaintext secret exactly once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookRotateSecretResponseDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"404":{"description":"Webhook not found.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Rotate the signing secret. Returns the new secret in clear EXACTLY ONCE; the old one stops at once.","tags":["webhooks"]}},"/v1/workspaces/{workspace_id}/webhooks/{id}/test":{"post":{"deprecated":true,"description":"Deprecated: use `/v1/workspaces/{workspace_id}/webhooks/endpoints` (event descriptions, Send test that reports the result, event log, replay, rotation with a grace period). Responses carry `Deprecation: true` and `Sunset: Fri, 15 Jan 2027 00:00:00 GMT`.","operationId":"WebhookController_fireTest","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestWebhookDto"}}}},"responses":{"202":{"description":"Sent. Read the outcome from `/deliveries`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"404":{"description":"Webhook not found.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}},"422":{"description":"The webhook is switched off.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["UNPROCESSABLE_ENTITY"],"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"Send the example of an event to this webhook; the attempt shows in `/deliveries`","tags":["webhooks"]}},"/v1/workspaces/{workspace_id}/stats":{"get":{"description":"Waitlist count, total entries, signups in the window, the most recent signup and a daily signup series, across every waitlist in the workspace.\n\n**Active** means a signup: a new waitlist entry.\n\nDays are UTC. Counts only: no entrant emails, names, referrers or IPs. Cached for up to 60 seconds (`generated_at`).","operationId":"WorkspaceStatsController_get","parameters":[{"name":"workspace_id","required":true,"in":"path","description":"Workspace id.","schema":{"format":"uuid","type":"string"}},{"name":"window","required":false,"in":"query","description":"Window for `in_window.signups` and the length of `series`, in UTC days ending today.","schema":{"default":"30d","example":"30d","enum":["1d","7d","30d"],"type":"string"}}],"responses":{"200":{"description":"Waitlist stats.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaitlistWorkspaceStatsDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}}},"400":{"description":"`window` is not one of 1d, 7d, 30d.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["BAD_REQUEST","VALIDATION_ERROR"]},"403":{"description":"Caller lacks `waitlist.read` on the workspace, or Waitlist is not enabled on it (`SERVICE_NOT_ENABLED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["FORBIDDEN","SERVICE_NOT_ENABLED"]},"404":{"description":"Workspace not found: the id is unknown, is not a UUID, or is not visible to the caller. Same answer as the service's other workspace routes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseDto"}}},"headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"x-pc-error-codes":["NOT_FOUND"]}},"security":[{"bearer":[]}],"summary":"Waitlist stats for a workspace","tags":["stats"]}}},"info":{"title":"Waitlist API","description":"Waitlist management for the ProductCraft platform","version":"0.1.0","contact":{}},"tags":[],"servers":[],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"SenderDomainStatus":{"type":"string","enum":["not_started","pending","partially_verified","verified","temporary_failure","failed","disabled","deleted","not_found"]},"EffectiveSenderDto":{"type":"object","properties":{"source":{"type":"string","enum":["workspace","custom"],"description":"`workspace`: the workspace default sender set in Mail. `custom`: the Waitlist sender domain + address.","example":"workspace"},"address":{"type":"string","nullable":true,"description":"The From Waitlist emails go out with (a bare address or `Name <address>`); null when none is set, or when the workspace default could not be read (`verified: null`).","example":"Acme <hello@acme.com>"},"verified":{"type":"boolean","nullable":true,"description":"Whether mail from this sender goes out right now, checked live in Mail (cached up to 60 seconds). null: not checked (your role cannot read Mail, or Mail did not answer).","example":true},"domain_status":{"nullable":true,"description":"The sender domain's live `verification_status` in Mail (`verified`, `pending`, `temporary_failure`, `failed`, …) in both modes, `deleted` when the domain behind the workspace default was removed, `not_found` for a custom sender domain that is gone (or receive-only), or null when unknown or there is no address. The shared `SenderDomainStatus` enum, the same in Mail and Auth.","example":"verified","allOf":[{"$ref":"#/components/schemas/SenderDomainStatus"}]}},"required":["source","address","verified","domain_status"]},"EmailSetupDto":{"type":"object","properties":{"state":{"type":"string","enum":["ready","incomplete","off"],"description":"`ready`: every piece is in place and emails go out. `incomplete`: notifications are on but something in `missing` stops some or all emails. `off`: notifications are off (`notifications_via_envoi = false`), so Waitlist sends no email and webhooks are the way to reach signups.","example":"incomplete"},"missing":{"type":"array","description":"What is missing, in a fixed order. `not_enabled`: notifications are off. `no_sender_domain`: no sender domain is chosen, or the chosen one no longer exists in Mail (or is receive-only). `sender_domain_unverified`: the sender domain is not verified in Mail yet. `no_sender_address`: no From address is set. `no_template`: at least one email has no template, or its template was deleted in Mail; `missing_templates` says which. Empty when `state` is `ready`.","example":["no_template"],"items":{"type":"string","enum":["not_enabled","no_sender_domain","sender_domain_unverified","no_sender_address","no_template"]}},"missing_templates":{"type":"array","description":"The emails whose template is not set or no longer exists in Mail: `welcome` (on signup), `approved`, `rejected`.","example":["welcome"],"items":{"type":"string","enum":["welcome","approved","rejected"]}},"mail_checked":{"type":"boolean","description":"Whether the sender domain and template names were checked against Mail on this read (results are cached for up to 60 seconds). `false` when Mail could not be asked, for example because the caller cannot read Mail domains or templates; nothing is reported missing on an unchecked item.","example":true}},"required":["state","missing","missing_templates","mail_checked"]},"WaitlistSkippedEmailsDto":{"type":"object","properties":{"count":{"type":"integer","minimum":0,"description":"Emails not sent in the last 7 days because the setup was incomplete (`email_setup.missing`). Emails not sent because notifications are off are not counted.","example":3},"last_at":{"type":"string","nullable":true,"format":"date-time","description":"When the most recent counted email was skipped; null when none in the last 7 days."},"last_7_days":{"type":"integer","minimum":0,"deprecated":true,"description":"The same number as `count`, under its old name. Read `count`.","example":3}},"required":["count","last_at","last_7_days"]},"WaitlistSettingsResponseDto":{"type":"object","properties":{"workspace_id":{"type":"string","format":"uuid"},"default_sender_domain_id":{"type":"string","nullable":true,"format":"uuid","description":"Mail domain UUID used as default sender for Waitlist notifications."},"default_heimdall_app_slug":{"type":"string","nullable":true,"description":"Auth consumer-app slug approved entries are invited into."},"brand_name":{"type":"string","nullable":true},"brand_logo_url":{"type":"string","nullable":true},"primary_color":{"type":"string","nullable":true,"description":"Primary brand colour as #RRGGBB"},"notifications_via_envoi":{"type":"boolean","description":"Whether Waitlist notifications are routed through the Mail service."},"sender_mode":{"type":"string","enum":["workspace","custom"],"description":"Where the From comes from: `workspace` (the workspace default sender set in Mail; the default) or `custom` (`default_sender_domain_id` + `default_sender_address`). Workspaces that had an address before this setting existed are `custom`.","example":"workspace"},"effective_sender":{"description":"The sender Waitlist emails actually go out from, with its live status in Mail.","allOf":[{"$ref":"#/components/schemas/EffectiveSenderDto"}]},"default_sender_address":{"type":"string","nullable":true,"description":"The From used when `sender_mode` is `custom`."},"welcome_template_name":{"type":"string","nullable":true},"approved_template_name":{"type":"string","nullable":true},"rejected_template_name":{"type":"string","nullable":true},"updated_at":{"type":"string","nullable":true,"format":"date-time","description":"null when the workspace has no explicit settings row yet."},"email_setup":{"description":"Whether Waitlist emails can go out and, if not, what is missing. Computed on the server on every read.","allOf":[{"$ref":"#/components/schemas/EmailSetupDto"}]},"skipped_sends":{"description":"Emails Waitlist did not send in the last 7 days because the setup was incomplete.","allOf":[{"$ref":"#/components/schemas/WaitlistSkippedEmailsDto"}]}},"required":["workspace_id","default_sender_domain_id","default_heimdall_app_slug","brand_name","brand_logo_url","primary_color","notifications_via_envoi","sender_mode","effective_sender","default_sender_address","welcome_template_name","approved_template_name","rejected_template_name","updated_at","email_setup","skipped_sends"]},"UpdateSettingsDto":{"type":"object","properties":{"default_sender_domain_id":{"type":"string","description":"Mail domain UUID to use as default sender for Waitlist notifications. null clears the override.","nullable":true},"default_heimdall_app_slug":{"type":"string","description":"Auth consumer-app slug to invite approved entries into. null clears the link.","nullable":true},"brand_name":{"type":"string","description":"Brand name returned in the public waitlist payload. Customer frontends render it on their own signup form."},"brand_logo_url":{"type":"string","description":"Logo URL returned in the public waitlist payload. Customer frontends render it on their own signup form."},"primary_color":{"type":"string","description":"Primary brand colour as #RRGGBB"},"notifications_via_envoi":{"type":"boolean","description":"Route Waitlist notifications through the Mail service instead of staying silent. Needs a sender (the workspace default in `workspace` mode, or the custom domain + address) and a template for every event. Turning it on (from off) creates the default templates `waitlist-welcome`, `waitlist-approved` and `waitlist-rejected` in Mail for every empty slot, reusing a template that already has that name, with your own Mail access. Default false; when off, Waitlist sends no notification emails and the customer is expected to handle email via the webhook lane."},"sender_mode":{"type":"string","enum":["workspace","custom"],"description":"Where the From comes from. `workspace` (the default): the workspace default sender set in Mail, resolved at send time, so changing it in Mail changes it for every product. `custom`: `default_sender_domain_id` + `default_sender_address`. Sending `default_sender_address` without `sender_mode` sets `custom`; sending it as null sets `workspace`.","example":"workspace"},"default_sender_address":{"type":"string","description":"The From used when `sender_mode` is `custom` (kept, but unused, in `workspace` mode). Must belong to default_sender_domain_id. null clears it.","nullable":true,"format":"email"},"welcome_template_name":{"type":"string","description":"Mail template name used for entry.created notifications. Must already exist in the workspace template store.","nullable":true},"approved_template_name":{"type":"string","description":"Mail template name used for entry.approved notifications. Must already exist in the workspace template store.","nullable":true},"rejected_template_name":{"type":"string","description":"Mail template name used for entry.rejected notifications. Must already exist in the workspace template store.","nullable":true}}},"NotificationTestResponseDto":{"type":"object","properties":{"to":{"type":"string","format":"email","description":"Your own verified account address: tests only ever go to the person who asked.","example":"you@acme.com"},"template_name":{"type":"string","description":"The Mail template that was sent.","example":"waitlist-welcome"},"from":{"type":"string","nullable":true,"description":"The From it went out with.","example":"Acme <hello@acme.com>"},"message_id":{"type":"string","nullable":true,"format":"uuid","description":"The Mail message id; look it up in Mail > Emails to follow delivery.","example":"0d6c1c3e-5f2a-4b8e-9d17-3a4b5c6d7e8f"}},"required":["to","template_name","from","message_id"]},"CreateWaitlistDto":{"type":"object","properties":{"slug":{"type":"string","description":"URL-safe slug","example":"early-access"},"display_name":{"type":"string","description":"Display name","example":"Early Access Program"},"description":{"type":"string","description":"Description returned in the public waitlist payload — customer frontends typically render it under the display name on their signup form."},"settings":{"type":"object","description":"Waitlist settings (max_entries, approval_required, custom_fields, confirmation_message, referral_boost, referral_rewards, recaptcha_site_key). To turn on reCAPTCHA v3, send recaptcha_site_key together with recaptcha_secret_key (both from your reCAPTCHA admin console); the secret is stored encrypted and is never returned. Send recaptcha_site_key: null to turn it off."},"status":{"type":"string","description":"Start state. `active` accepts public sign-ups straight away; `draft` (the default) refuses them with 422 until the status is changed to `active`.","enum":["draft","active"],"default":"draft"},"allowed_origins":{"description":"Web origins (scheme and host, for example `https://acme.com` or `http://localhost:3000`) that browsers may submit public sign-ups from. `https://*.acme.com` matches any subdomain. An empty list means any origin. A browser request from another origin gets 403 `ORIGIN_NOT_ALLOWED`; server-side calls without an `Origin` header are not checked. Entries are normalised (lower case, no trailing slash, default port dropped).","maxItems":50,"example":["https://acme.com","https://*.acme.com"],"default":[],"type":"array","items":{"type":"string"}}},"required":["slug","display_name"]},"WaitlistResponseDto":{"type":"object","properties":{"id":{"type":"string","example":"b1a4..."},"workspace_id":{"type":"string","example":"b1a4..."},"workspace_slug":{"type":"string","example":"acme"},"slug":{"type":"string","example":"early-access"},"display_name":{"type":"string","example":"Early Access Program"},"description":{"type":"string","nullable":true},"status":{"type":"string","enum":["draft","active","paused","closed"]},"accepting_signups":{"type":"boolean","description":"Whether the public sign-up endpoint accepts entries right now (only an `active` waitlist does).","example":false},"signups_note":{"type":"string","nullable":true,"description":"Plain explanation when public sign-ups are refused, and how to open them; null when the waitlist is active.","example":"This waitlist is a draft, so public sign-ups are refused (422) until you change its status to active."},"allowed_origins":{"description":"Web origins (scheme and host, for example `https://acme.com` or `http://localhost:3000`) that browsers may submit public sign-ups from. `https://*.acme.com` matches any subdomain. An empty list means any origin. A browser request from another origin gets 403 `ORIGIN_NOT_ALLOWED`; server-side calls without an `Origin` header are not checked. Entries are normalised (lower case, no trailing slash, default port dropped).","example":[],"type":"array","items":{"type":"string"}},"settings":{"type":"object","description":"Waitlist settings (max_entries, approval_required, custom_fields, confirmation_message, referral_boost, referral_rewards, recaptcha_site_key). The reCAPTCHA secret key is never returned; recaptcha_secret_configured says whether one is saved."},"created_by":{"type":"string"},"previous_workspace_slug":{"type":"string","nullable":true},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","workspace_id","workspace_slug","slug","display_name","description","status","accepting_signups","signups_note","allowed_origins","settings","created_by","previous_workspace_slug","created_at","updated_at"]},"WaitlistListPaginationDto":{"type":"object","properties":{"next_cursor":{"type":"string","nullable":true},"has_more":{"type":"boolean"}},"required":["next_cursor","has_more"]},"WaitlistListResponseDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WaitlistResponseDto"}},"pagination":{"$ref":"#/components/schemas/WaitlistListPaginationDto"}},"required":["data","pagination"]},"UpdateWaitlistDto":{"type":"object","properties":{"display_name":{"type":"string","description":"New display name"},"description":{"type":"string","description":"New description"},"settings":{"type":"object","description":"Updated settings, merged shallowly into the saved ones. Keys: max_entries, approval_required, custom_fields, confirmation_message, referral_boost, referral_rewards, recaptcha_site_key. To turn on reCAPTCHA v3, send recaptcha_site_key together with recaptcha_secret_key (both from your reCAPTCHA admin console); the secret is stored encrypted and is never returned. Send recaptcha_site_key: null to turn it off."},"allowed_origins":{"description":"Web origins (scheme and host, for example `https://acme.com` or `http://localhost:3000`) that browsers may submit public sign-ups from. `https://*.acme.com` matches any subdomain. An empty list means any origin. A browser request from another origin gets 403 `ORIGIN_NOT_ALLOWED`; server-side calls without an `Origin` header are not checked. Entries are normalised (lower case, no trailing slash, default port dropped). Replaces the whole list; send `[]` to allow any origin again.","maxItems":50,"example":["https://acme.com"],"type":"array","items":{"type":"string"}}}},"UpdateWaitlistStatusDto":{"type":"object","properties":{"status":{"type":"string","description":"New status","enum":["draft","active","paused","closed"]}},"required":["status"]},"EntryResponseDto":{"type":"object","properties":{"id":{"type":"string"},"waitlist_id":{"type":"string"},"workspace_id":{"type":"string"},"email":{"type":"string"},"name":{"type":"string"},"interest":{"type":"string","nullable":true},"referrer":{"type":"string","nullable":true},"referral_code":{"type":"string"},"referral_count":{"type":"number"},"referred_by_entry_id":{"type":"string","nullable":true},"position":{"type":"number","description":"Place in the queue, from 1. Test entries have negative positions (-1, -2…) so they never take a real place."},"is_test":{"type":"boolean","description":"Made with \"Add test signup\". Test entries use a mailbox-simulator address and are left out of counts, analytics, exports and webhooks unless asked.","example":false},"status":{"type":"string","enum":["pending","approved","rejected"]},"invited_at":{"type":"string","nullable":true},"invited_to_app_slug":{"type":"string","nullable":true},"metadata":{"type":"object"},"created_at":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","waitlist_id","workspace_id","email","name","interest","referrer","referral_code","referral_count","referred_by_entry_id","position","is_test","status","invited_at","invited_to_app_slug","metadata","created_at","updated_at"]},"EntryListPaginationDto":{"type":"object","properties":{"next_cursor":{"type":"string","nullable":true},"has_more":{"type":"boolean"}},"required":["next_cursor","has_more"]},"EntryListResponseDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/EntryResponseDto"}},"pagination":{"$ref":"#/components/schemas/EntryListPaginationDto"}},"required":["data","pagination"]},"EntryCountResponseDto":{"type":"object","properties":{"count":{"type":"number","description":"Number of entries on the waitlist. Test entries are left out unless `include_test=true`."}},"required":["count"]},"UpdateEntryStatusDto":{"type":"object","properties":{"status":{"type":"string","description":"Target status","enum":["approved","rejected"]}},"required":["status"]},"InviteToAppDto":{"type":"object","properties":{"app_slug":{"type":"string","description":"Auth app slug to invite into; defaults to the workspace Waitlist settings default (`default_heimdall_app_slug`)"},"send_email":{"type":"boolean","description":"Send the approval email with the Auth invite link baked in","default":true}}},"InviteToAppResponseDto":{"type":"object","properties":{"entry":{"description":"The entry row after `invited_at` / `invited_to_app_slug` were stamped.","allOf":[{"$ref":"#/components/schemas/EntryResponseDto"}]},"invite_id":{"type":"string","description":"Auth invite id created in the linked app."},"invite_url":{"type":"string","description":"Invite acceptance URL, when the Auth app returned one. Baked into the approval email when send_email is enabled."}},"required":["entry","invite_id"]},"BulkEntryActionDto":{"type":"object","properties":{"ids":{"description":"Entry UUIDs to act on in this batch. Capped at 100 per call; the request is processed serially and per-row results are returned. De-duped server-side.","minItems":1,"maxItems":100,"example":["11111111-1111-1111-1111-111111111111","22222222-2222-2222-2222-222222222222"],"type":"array","items":{"type":"string"}},"action":{"type":"string","description":"Action to apply to every id.","enum":["approve","reject","invite-to-app"],"example":"approve"},"app_slug":{"type":"string","description":"Auth app slug override for `action: invite-to-app`. When omitted, falls back to the workspace Waitlist settings default (`default_heimdall_app_slug`). Ignored for approve/reject.","example":"acme-product","maxLength":64},"send_email":{"type":"boolean","description":"Whether to send the approval email with the invite link baked in. Defaults to `true`. Ignored for approve/reject.","default":true}},"required":["ids","action"]},"BulkEntryErrorDto":{"type":"object","properties":{"code":{"type":"number","description":"HTTP-style status code for the per-row failure."},"message":{"type":"string"}},"required":["code","message"]},"BulkEntryResultDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["ok","error"]},"error":{"$ref":"#/components/schemas/BulkEntryErrorDto"}},"required":["id","status"]},"BulkEntryActionResponseDto":{"type":"object","properties":{"requested":{"type":"number","description":"Number of unique ids processed (after de-dupe)."},"succeeded":{"type":"number"},"failed":{"type":"number"},"results":{"type":"array","items":{"$ref":"#/components/schemas/BulkEntryResultDto"}}},"required":["requested","succeeded","failed","results"]},"CreateTestEntryDto":{"type":"object","properties":{"name":{"type":"string","description":"Name on the test entry. Defaults to \"Test signup\".","example":"Test signup","maxLength":200},"trigger_webhooks":{"type":"boolean","description":"Send `entry.created` (and `entry.approved` when the waitlist auto-approves) to your webhook endpoints for this entry. The payload carries `is_test: true`. Default `false`.","default":false},"send_welcome_email":{"type":"boolean","description":"Send the welcome email for this entry. It goes to the entry's mailbox-simulator address, which accepts it without delivering to a real inbox. Default `false`.","default":false}}},"PublicWaitlistCustomFieldDto":{"type":"object","properties":{"key":{"type":"string","description":"Stable field key. Submit values under this key in the entry `metadata` object."},"label":{"type":"string","description":"Human-readable label for the field."},"type":{"type":"string","description":"Field type hint (e.g. `text`, `select`)."},"required":{"type":"boolean"},"options":{"description":"Choices for select-style fields.","type":"array","items":{"type":"string"}}},"required":["key"]},"PublicReferralRewardMilestoneDto":{"type":"object","properties":{"count":{"type":"number","description":"Referral count that unlocks the reward."},"reward":{"type":"string","description":"Reward description shown to the signer-up."}},"required":["count","reward"]},"PublicReferralRewardsDto":{"type":"object","properties":{"milestones":{"description":"Milestones sorted by count, ascending. Empty when the waitlist has no referral rewards configured.","type":"array","items":{"$ref":"#/components/schemas/PublicReferralRewardMilestoneDto"}}},"required":["milestones"]},"PublicWaitlistVariantDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Active variant id. Round-trip it as `variant_id` on POST .../entries to attribute the signup."},"locale":{"type":"string","description":"BCP-47 locale of the served copy."},"kind":{"type":"string","enum":["ab","locale"]}},"required":["id","locale","kind"]},"PublicWaitlistBrandDto":{"type":"object","properties":{"brand_name":{"type":"string","nullable":true},"brand_logo_url":{"type":"string","nullable":true},"primary_color":{"type":"string","nullable":true,"description":"Primary brand colour as #RRGGBB."}},"required":["brand_name","brand_logo_url","primary_color"]},"PublicWaitlistInfoDto":{"type":"object","properties":{"slug":{"type":"string","description":"Waitlist slug."},"workspace_slug":{"type":"string","description":"Workspace slug the request resolved against."},"canonical_workspace_slug":{"type":"string","description":"Canonical workspace slug. When the request URL used a stale slug, compare with the URL slug and 301-redirect."},"display_name":{"type":"string","description":"Display name (active variant copy wins over the stored name)."},"description":{"type":"string","nullable":true},"status":{"type":"string","description":"Always `active` — non-active waitlists return 404."},"custom_fields":{"type":"array","items":{"$ref":"#/components/schemas/PublicWaitlistCustomFieldDto"}},"confirmation_message":{"type":"string","description":"Message to show after a successful signup."},"recaptcha_site_key":{"type":"string","nullable":true,"description":"reCAPTCHA v3 site key. When non-null, POST .../entries requires a `recaptcha_token` created with the action `waitlist_signup`."},"referral_rewards":{"$ref":"#/components/schemas/PublicReferralRewardsDto"},"variant":{"nullable":true,"description":"Active A/B or locale variant served for this request; null when none configured.","allOf":[{"$ref":"#/components/schemas/PublicWaitlistVariantDto"}]},"brand":{"$ref":"#/components/schemas/PublicWaitlistBrandDto"}},"required":["slug","workspace_slug","canonical_workspace_slug","display_name","description","status","custom_fields","confirmation_message","recaptcha_site_key","referral_rewards","variant","brand"]},"PublicLeaderboardEntryDto":{"type":"object","properties":{"position":{"type":"number","description":"Signup position on the waitlist."},"referral_count":{"type":"number","description":"Number of honoured referrals."},"masked_email":{"type":"string","description":"Masked email, e.g. `a***@example.com`.","example":"a***@example.com"}},"required":["position","referral_count","masked_email"]},"CreateEntryDto":{"type":"object","properties":{"email":{"type":"string","description":"Email address","example":"ada@example.com"},"name":{"type":"string","description":"Full name","example":"Ada Lovelace"},"interest":{"type":"string","description":"Free-text product interest"},"referrer":{"type":"string","description":"How they heard about it"},"referral_code":{"type":"string","description":"Referral code from a prior signup; bumps that signup’s position when honoured"},"metadata":{"type":"object","description":"Custom field values"},"recaptcha_token":{"type":"string","description":"reCAPTCHA v3 token, created with the action `waitlist_signup`. Required when the waitlist has a reCAPTCHA site key; verified server-side with the secret key saved on the waitlist."},"variant_id":{"type":"string","description":"Variant id this submission was attributed to. Customer frontends fetch the active variant from `GET /v1/waitlists/:workspaceSlug/:waitlistSlug` and round-trip it here on POST so per-variant conversion can be computed without a separate impressions table.","format":"uuid"}},"required":["email","name"]},"PublicEntryCreatedDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Entry id."},"email":{"type":"string","description":"Normalised (lower-cased) email."},"name":{"type":"string"},"position":{"type":"number","description":"Immutable signup position (1-based order of signup). Negative for a test sign-up (`is_test: true`), which takes no place in line."},"is_test":{"type":"boolean","description":"`true` when the email is an Amazon SES mailbox-simulator address (`…@simulator.amazonses.com`). Such a sign-up is stored as a test entry: it is left out of counts, analytics, exports and webhooks, and only the welcome email (to the simulator) goes out."},"status":{"type":"string","enum":["pending","approved"],"description":"`approved` when the waitlist auto-approves; `pending` when admin approval is required."},"referral_code":{"type":"string","description":"Referral code for this signup. Build your referral URL from it and pass it as `referral_code` on subsequent signups."},"created_at":{"type":"string","format":"date-time"},"effective_position":{"type":"number","description":"Visible position after applying the referral boost — \"you would be #N if approved now\". Equals `position` at signup time (no referrals yet), and always for a test sign-up."}},"required":["id","email","name","position","is_test","status","referral_code","created_at","effective_position"]},"OriginNotAllowedErrorDto":{"type":"object","properties":{"status_code":{"type":"number","example":403},"statusCode":{"type":"number","example":403,"description":"Deprecated alias of `status_code`, same value. Kept so existing clients keep working; read `status_code`. Error responses carry `Deprecation: true` while it is sent.","deprecated":true},"code":{"type":"string","enum":["ORIGIN_NOT_ALLOWED"],"example":"ORIGIN_NOT_ALLOWED"},"message":{"type":"string","example":"Sign-ups from https://evil.test are not allowed for this waitlist. Add the origin to the waitlist's allowed origins."},"origin":{"type":"string","description":"The `Origin` header the request carried.","example":"https://evil.test"}},"required":["status_code","code","message","origin"]},"AnalyticsTopReferrerDto":{"type":"object","properties":{"referrer":{"type":"string","nullable":true,"description":"Free-text referrer value; null groups entries without one."},"count":{"type":"number"}},"required":["referrer","count"]},"AnalyticsTopReferralDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string"},"name":{"type":"string"},"referral_code":{"type":"string"},"referral_count":{"type":"number"},"position":{"type":"number","description":"Immutable signup position."},"effective_position":{"type":"number","description":"`position - referral_count * referral_boost`, floored at 1."}},"required":["id","email","name","referral_code","referral_count","position","effective_position"]},"WaitlistAnalyticsResponseDto":{"type":"object","properties":{"waitlist_id":{"type":"string","format":"uuid"},"total_entries":{"type":"number"},"by_status":{"type":"object","additionalProperties":{"type":"number"},"description":"Entry counts keyed by status (`pending` / `approved` / `rejected`).","example":{"pending":12,"approved":40,"rejected":3}},"signups_today":{"type":"number"},"signups_this_week":{"type":"number"},"signups_this_month":{"type":"number"},"viral_coefficient":{"type":"number","description":"referrals / total_entries — the K-factor proxy. 0 when no entries."},"referred_count":{"type":"number"},"top_referrers":{"type":"array","items":{"$ref":"#/components/schemas/AnalyticsTopReferrerDto"}},"top_referrals":{"type":"array","items":{"$ref":"#/components/schemas/AnalyticsTopReferralDto"}}},"required":["waitlist_id","total_entries","by_status","signups_today","signups_this_week","signups_this_month","viral_coefficient","referred_count","top_referrers","top_referrals"]},"AnalyticsTimelinePointDto":{"type":"object","properties":{"date":{"type":"string","description":"UTC day, `YYYY-MM-DD`.","example":"2026-08-01"},"count":{"type":"number","description":"Signups on that day (0 for gap days)."}},"required":["date","count"]},"AnalyticsTimelineResponseDto":{"type":"object","properties":{"granularity":{"type":"string","enum":["day"]},"points":{"description":"Oldest first; every day in [since, until) is present.","type":"array","items":{"$ref":"#/components/schemas/AnalyticsTimelinePointDto"}}},"required":["granularity","points"]},"VariantResponseDto":{"type":"object","properties":{"id":{"type":"string"},"waitlist_id":{"type":"string"},"kind":{"type":"string","enum":["ab","locale"]},"locale":{"type":"string"},"copy":{"type":"object"},"weight":{"type":"number"},"active":{"type":"boolean"},"created_at":{"type":"string"}},"required":["id","waitlist_id","kind","locale","copy","weight","active","created_at"]},"PaginationDto":{"type":"object","properties":{"next_cursor":{"type":"string","description":"Opaque cursor for the next page. `null` when no more pages are available.","nullable":true,"example":"eyJwb3NpdGlvbiI6MjB9"},"has_more":{"type":"boolean","description":"True iff another page exists; if true, pass `next_cursor` on the next request.","example":false}},"required":["next_cursor","has_more"]},"VariantListResponseDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/VariantResponseDto"}},"pagination":{"$ref":"#/components/schemas/PaginationDto"}},"required":["data","pagination"]},"VariantCopyDto":{"type":"object","properties":{"display_name":{"type":"string","maxLength":200},"description":{"type":"string","maxLength":1000},"confirmation_message":{"type":"string","maxLength":500}}},"CreateVariantDto":{"type":"object","properties":{"kind":{"type":"string","enum":["ab","locale"]},"locale":{"type":"string","description":"BCP-47 locale tag. For ab variants this is the locale they target; for locale variants this is the locale they serve. Case is normalised on the public side.","example":"en"},"copy":{"description":"Copy overrides. Recognised keys: display_name, description, confirmation_message. Unknown keys are dropped on the public render.","allOf":[{"$ref":"#/components/schemas/VariantCopyDto"}]},"weight":{"type":"number","description":"Relative weight inside the ab pool. Ignored when kind=locale. Default 1.","minimum":1,"maximum":100,"default":1},"active":{"type":"boolean","default":true}},"required":["kind","locale","copy"]},"UpdateVariantDto":{"type":"object","properties":{"locale":{"type":"string"},"copy":{"$ref":"#/components/schemas/VariantCopyDto"},"weight":{"type":"number","minimum":1,"maximum":100},"active":{"type":"boolean"}}},"WebhookEventType":{"type":"string","enum":["entry.created","entry.approved","entry.rejected"]},"WebhookEventTypeDto":{"type":"object","properties":{"type":{"example":"entry.created","description":"The event type (one of this product's catalogue).","allOf":[{"$ref":"#/components/schemas/WebhookEventType"}]},"description":{"type":"string","example":"A user signed up or was created by an admin.","description":"When this event is sent."},"category":{"type":"string","example":"Users","description":"Group shown in the event picker."},"example":{"type":"object","additionalProperties":true,"description":"An example `data` object; \"Send test\" delivers exactly this."}},"x-pc-sdk-model":"WebhookEventTypeInfo","required":["type","description","category","example"]},"ListWebhookEventTypesResponseDto":{"type":"object","properties":{"data":{"description":"Every event type this product sends.","type":"array","items":{"$ref":"#/components/schemas/WebhookEventTypeDto"}}},"required":["data"]},"WebhookEndpointStatus":{"type":"string","enum":["enabled","disabled","auto_disabled"]},"WebhookEndpointDisabledReason":{"type":"string","enum":["manual","failing"]},"WebhookEndpointDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"The endpoint's id."},"scope_type":{"type":"string","example":"workspace","description":"What owns the endpoint: `workspace`, `app` or `community`."},"scope_id":{"type":"string","description":"Id of the owning workspace, app or community."},"url":{"type":"string","format":"uri","example":"https://api.acme.com/webhooks","description":"Where events are sent."},"description":{"type":"string","nullable":true,"example":"Production receiver","description":"A label for people; null when unset."},"event_types":{"description":"Subscribed event types. Empty means every event, including types added later.","example":[],"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean","description":"The on/off switch (`PATCH … { enabled }`)."},"status":{"description":"`enabled` receives events. `disabled` was switched off. `auto_disabled` was switched off after 20 failed deliveries in a row or 24 hours of failures; `PATCH { enabled: true }` turns it back on.","allOf":[{"$ref":"#/components/schemas/WebhookEndpointStatus"}]},"disabled_at":{"type":"string","format":"date-time","nullable":true,"description":"When it was switched off; null while enabled."},"disabled_reason":{"nullable":true,"description":"`manual`: switched off by a caller. `failing`: switched off automatically after failed deliveries. Null while enabled.","allOf":[{"$ref":"#/components/schemas/WebhookEndpointDisabledReason"}]},"consecutive_failures":{"type":"number","description":"Failed automatic deliveries in a row; a success resets it."},"first_failure_at":{"type":"string","format":"date-time","nullable":true,"description":"Start of the current failure streak."},"signing_secret_hint":{"type":"string","description":"Last 4 characters of the signing secret.","example":"x9Q="},"prev_signing_secret_active":{"type":"boolean","description":"True while the previous secret still signs too (after a rotation, for its grace period)."},"prev_signing_secret_expires_at":{"type":"string","format":"date-time","nullable":true,"description":"Until then the previous secret signs too (after a rotation)."},"legacy_signature":{"type":"boolean","description":"True for endpoints carried over from the product's previous webhook system: they also receive the old signature header until its sunset date."},"created_at":{"type":"string","format":"date-time","description":"When the endpoint was created."},"updated_at":{"type":"string","format":"date-time","description":"When the endpoint last changed."}},"required":["id","scope_type","scope_id","url","description","event_types","enabled","status","disabled_at","disabled_reason","consecutive_failures","first_failure_at","signing_secret_hint","prev_signing_secret_active","prev_signing_secret_expires_at","legacy_signature","created_at","updated_at"]},"WebhookPaginationDto":{"type":"object","properties":{"next_cursor":{"type":"string","nullable":true,"description":"Pass as `cursor` to get the next page; null on the last page."},"has_more":{"type":"boolean","description":"Whether another page follows."}},"required":["next_cursor","has_more"]},"ListWebhookEndpointsResponseDto":{"type":"object","properties":{"data":{"description":"The endpoints, newest first.","type":"array","items":{"$ref":"#/components/schemas/WebhookEndpointDto"}},"pagination":{"description":"Always one page (an endpoint limit applies).","allOf":[{"$ref":"#/components/schemas/WebhookPaginationDto"}]}},"required":["data","pagination"]},"CreateWebhookEndpointDto":{"type":"object","properties":{"url":{"type":"string","description":"Where to send events: an absolute https:// URL on a public host.","example":"https://api.acme.com/webhooks","maxLength":2048,"format":"uri"},"description":{"type":"string","nullable":true,"maxLength":200,"example":"Production receiver","description":"A label for people."},"event_types":{"type":"array","description":"Event types from `GET …/webhooks/event-types`. Empty or omitted means every event, including types added later.","example":["entry.created","entry.approved"],"items":{"type":"string","enum":["entry.created","entry.approved","entry.rejected"]}},"enabled":{"type":"boolean","default":true,"description":"Create it switched off."}},"required":["url"]},"WebhookEndpointWithSecretDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"The endpoint's id."},"scope_type":{"type":"string","example":"workspace","description":"What owns the endpoint: `workspace`, `app` or `community`."},"scope_id":{"type":"string","description":"Id of the owning workspace, app or community."},"url":{"type":"string","format":"uri","example":"https://api.acme.com/webhooks","description":"Where events are sent."},"description":{"type":"string","nullable":true,"example":"Production receiver","description":"A label for people; null when unset."},"event_types":{"description":"Subscribed event types. Empty means every event, including types added later.","example":[],"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean","description":"The on/off switch (`PATCH … { enabled }`)."},"status":{"description":"`enabled` receives events. `disabled` was switched off. `auto_disabled` was switched off after 20 failed deliveries in a row or 24 hours of failures; `PATCH { enabled: true }` turns it back on.","allOf":[{"$ref":"#/components/schemas/WebhookEndpointStatus"}]},"disabled_at":{"type":"string","format":"date-time","nullable":true,"description":"When it was switched off; null while enabled."},"disabled_reason":{"nullable":true,"description":"`manual`: switched off by a caller. `failing`: switched off automatically after failed deliveries. Null while enabled.","allOf":[{"$ref":"#/components/schemas/WebhookEndpointDisabledReason"}]},"consecutive_failures":{"type":"number","description":"Failed automatic deliveries in a row; a success resets it."},"first_failure_at":{"type":"string","format":"date-time","nullable":true,"description":"Start of the current failure streak."},"signing_secret_hint":{"type":"string","description":"Last 4 characters of the signing secret.","example":"x9Q="},"prev_signing_secret_active":{"type":"boolean","description":"True while the previous secret still signs too (after a rotation, for its grace period)."},"prev_signing_secret_expires_at":{"type":"string","format":"date-time","nullable":true,"description":"Until then the previous secret signs too (after a rotation)."},"legacy_signature":{"type":"boolean","description":"True for endpoints carried over from the product's previous webhook system: they also receive the old signature header until its sunset date."},"created_at":{"type":"string","format":"date-time","description":"When the endpoint was created."},"updated_at":{"type":"string","format":"date-time","description":"When the endpoint last changed."},"signing_secret":{"type":"string","description":"The signing secret, shown once: pass it unchanged to any Standard Webhooks verifier. Store it now; it cannot be shown again.","example":"whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"}},"required":["id","scope_type","scope_id","url","description","event_types","enabled","status","disabled_at","disabled_reason","consecutive_failures","first_failure_at","signing_secret_hint","prev_signing_secret_active","prev_signing_secret_expires_at","legacy_signature","created_at","updated_at","signing_secret"]},"UpdateWebhookEndpointDto":{"type":"object","properties":{"url":{"type":"string","maxLength":2048,"format":"uri","example":"https://api.acme.com/webhooks","description":"A new absolute https:// URL on a public host."},"description":{"type":"string","nullable":true,"maxLength":200,"description":"A new label; null clears it."},"event_types":{"type":"array","description":"Replaces the subscription. Event types from `GET …/webhooks/event-types`. Empty or omitted means every event, including types added later.","items":{"type":"string","enum":["entry.created","entry.approved","entry.rejected"]}},"enabled":{"type":"boolean","description":"Switch it on or off. Switching on also clears an automatic disable."}}},"RotateWebhookSecretDto":{"type":"object","properties":{"grace_hours":{"type":"number","default":24,"minimum":0,"maximum":168,"description":"Hours the previous secret keeps signing too (0 ends it now)."}}},"SendWebhookTestDto":{"type":"object","properties":{"event_type":{"type":"string","enum":["entry.created","entry.approved","entry.rejected"],"description":"Which event to send; its catalogue example is the `data`.","example":"entry.created"}},"required":["event_type"]},"WebhookAttemptTrigger":{"type":"string","enum":["automatic","replay","test"]},"WebhookDeliveryAttemptDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"The attempt's id."},"event_id":{"type":"string","pattern":"^evt_[0-9a-f-]{36}$","example":"evt_3f0c3c1e-8a2b-4c55-9d3f-0a1b2c3d4e5f","description":"The event that was delivered."},"endpoint_id":{"type":"string","format":"uuid","description":"The endpoint it was delivered to."},"event_type":{"type":"string","example":"user.created","description":"The event's type."},"attempt_number":{"type":"number","description":"1 for the first delivery; retries and replays count up."},"trigger":{"description":"`automatic` (first delivery or a retry), `replay` or `test`.","allOf":[{"$ref":"#/components/schemas/WebhookAttemptTrigger"}]},"url":{"type":"string","format":"uri","description":"Where it was sent."},"status_code":{"type":"number","nullable":true,"description":"HTTP status; null when no response came back."},"succeeded":{"type":"boolean","description":"True for a 2xx response."},"error_message":{"type":"string","nullable":true,"example":"request timed out","description":"Why the attempt failed when no 2xx came back; null on success."},"response_body":{"type":"string","nullable":true,"description":"The first 1 KiB of the response body."},"latency_ms":{"type":"number","nullable":true,"description":"Milliseconds until the response; null when none came back."},"attempted_at":{"type":"string","format":"date-time","description":"When the attempt was made."}},"required":["id","event_id","endpoint_id","event_type","attempt_number","trigger","url","status_code","succeeded","error_message","response_body","latency_ms","attempted_at"]},"WebhookEventStatus":{"type":"string","enum":["pending","delivered","failed","not_sent"]},"WebhookEventDeliveryDto":{"type":"object","properties":{"endpoint_id":{"type":"string","format":"uuid","description":"The endpoint."},"attempt_number":{"type":"number","description":"Number of the latest attempt; retries and replays count up."},"status_code":{"type":"number","nullable":true,"description":"HTTP status of the latest attempt; null when no response came back."},"succeeded":{"type":"boolean","description":"True when the latest attempt got a 2xx."},"attempted_at":{"type":"string","format":"date-time","description":"When the latest attempt was made."},"next_attempt_at":{"type":"string","format":"date-time","nullable":true,"description":"When the next automatic attempt is due; null when none is queued."}},"required":["endpoint_id","attempt_number","status_code","succeeded","attempted_at","next_attempt_at"]},"WebhookEventDto":{"type":"object","properties":{"id":{"type":"string","pattern":"^evt_[0-9a-f-]{36}$","example":"evt_3f0c3c1e-8a2b-4c55-9d3f-0a1b2c3d4e5f","description":"Sent as `webhook-id`; the same on every retry and replay."},"type":{"example":"entry.created","description":"The event type (one of this product's catalogue).","allOf":[{"$ref":"#/components/schemas/WebhookEventType"}]},"created_at":{"type":"string","format":"date-time","description":"When the event happened."},"test":{"type":"boolean","description":"Made by \"Send test\"."},"status":{"description":"`pending`: a delivery is queued. `failed`: the latest attempt to some endpoint failed and none is queued. `delivered`: every endpoint answered 2xx. `not_sent`: no endpoint subscribed.","allOf":[{"$ref":"#/components/schemas/WebhookEventStatus"}]},"payload":{"type":"object","additionalProperties":true,"description":"The JSON body exactly as sent."},"deliveries":{"description":"Latest attempt per endpoint (an endpoint whose first attempt is still queued is not listed yet).","type":"array","items":{"$ref":"#/components/schemas/WebhookEventDeliveryDto"}}},"required":["id","type","created_at","test","status","payload","deliveries"]},"ListWebhookEventsResponseDto":{"type":"object","properties":{"data":{"description":"The events, newest first.","type":"array","items":{"$ref":"#/components/schemas/WebhookEventDto"}},"pagination":{"description":"The cursor for the next page.","allOf":[{"$ref":"#/components/schemas/WebhookPaginationDto"}]}},"required":["data","pagination"]},"WebhookEventDetailDto":{"type":"object","properties":{"id":{"type":"string","pattern":"^evt_[0-9a-f-]{36}$","example":"evt_3f0c3c1e-8a2b-4c55-9d3f-0a1b2c3d4e5f","description":"Sent as `webhook-id`; the same on every retry and replay."},"type":{"example":"entry.created","description":"The event type (one of this product's catalogue).","allOf":[{"$ref":"#/components/schemas/WebhookEventType"}]},"created_at":{"type":"string","format":"date-time","description":"When the event happened."},"test":{"type":"boolean","description":"Made by \"Send test\"."},"status":{"description":"`pending`: a delivery is queued. `failed`: the latest attempt to some endpoint failed and none is queued. `delivered`: every endpoint answered 2xx. `not_sent`: no endpoint subscribed.","allOf":[{"$ref":"#/components/schemas/WebhookEventStatus"}]},"payload":{"type":"object","additionalProperties":true,"description":"The JSON body exactly as sent."},"deliveries":{"description":"Latest attempt per endpoint (an endpoint whose first attempt is still queued is not listed yet).","type":"array","items":{"$ref":"#/components/schemas/WebhookEventDeliveryDto"}},"attempts":{"description":"Every attempt, newest first.","type":"array","items":{"$ref":"#/components/schemas/WebhookDeliveryAttemptDto"}}},"required":["id","type","created_at","test","status","payload","deliveries","attempts"]},"ReplayWebhookEventResponseDto":{"type":"object","properties":{"event_id":{"type":"string","pattern":"^evt_[0-9a-f-]{36}$","description":"The replayed event (the same id as the original)."},"attempts":{"description":"One attempt per endpoint it was sent to.","type":"array","items":{"$ref":"#/components/schemas/WebhookDeliveryAttemptDto"}}},"required":["event_id","attempts"]},"CreateWebhookDto":{"type":"object","properties":{"url":{"type":"string","description":"Destination URL (must be https in prod)"},"events":{"type":"array","description":"Events to subscribe to","example":["entry.created","entry.approved"],"items":{"type":"string","enum":["entry.created","entry.approved","entry.rejected"]}},"active":{"type":"boolean","description":"Disabled webhooks store events but never deliver"}},"required":["url","events"]},"WebhookCreateResponseDto":{"type":"object","properties":{"id":{"type":"string"},"workspace_id":{"type":"string"},"url":{"type":"string"},"events":{"type":"array","description":"Subscribed events (`event_types` on the new routes).","items":{"type":"string","enum":["entry.created","entry.approved","entry.rejected"]}},"active":{"type":"boolean","description":"Whether it receives events (`enabled` on the new routes)."},"failure_count":{"type":"number","description":"Failed deliveries in a row (`consecutive_failures` on the new routes)."},"last_status_code":{"type":"number","nullable":true,"description":"HTTP status of the latest attempt."},"last_attempt_at":{"type":"string","nullable":true,"description":"When the latest attempt was made."},"auto_disabled_at":{"type":"string","nullable":true,"description":"Set when it was switched off automatically after sustained failures."},"first_failure_at":{"type":"string","nullable":true},"created_at":{"type":"string"},"secret_set":{"type":"boolean","description":"Always true: every webhook has a signing secret."},"signing_secret":{"type":"string","description":"Plaintext signing secret (`whsec_…`), returned EXACTLY ONCE. Persist immediately; it does not appear on later reads."},"secret_one_time_reveal":{"type":"string","description":"The same secret as `signing_secret`, under the name older clients read."}},"required":["id","workspace_id","url","events","active","failure_count","last_status_code","last_attempt_at","auto_disabled_at","first_failure_at","created_at","signing_secret","secret_one_time_reveal"]},"WebhookResponseDto":{"type":"object","properties":{"id":{"type":"string"},"workspace_id":{"type":"string"},"url":{"type":"string"},"events":{"type":"array","description":"Subscribed events (`event_types` on the new routes).","items":{"type":"string","enum":["entry.created","entry.approved","entry.rejected"]}},"active":{"type":"boolean","description":"Whether it receives events (`enabled` on the new routes)."},"failure_count":{"type":"number","description":"Failed deliveries in a row (`consecutive_failures` on the new routes)."},"last_status_code":{"type":"number","nullable":true,"description":"HTTP status of the latest attempt."},"last_attempt_at":{"type":"string","nullable":true,"description":"When the latest attempt was made."},"auto_disabled_at":{"type":"string","nullable":true,"description":"Set when it was switched off automatically after sustained failures."},"first_failure_at":{"type":"string","nullable":true},"created_at":{"type":"string"},"secret_set":{"type":"boolean","description":"Always true: every webhook has a signing secret."}},"required":["id","workspace_id","url","events","active","failure_count","last_status_code","last_attempt_at","auto_disabled_at","first_failure_at","created_at"]},"WebhookListResponseDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookResponseDto"}},"pagination":{"$ref":"#/components/schemas/PaginationDto"}},"required":["data","pagination"]},"LegacyWebhookDeliveryAttemptDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"webhook_id":{"type":"string","format":"uuid"},"event_id":{"type":"string","description":"Id of the event (`evt_…`), the same across retries."},"event_type":{"type":"string","enum":["entry.created","entry.approved","entry.rejected"]},"attempt_number":{"type":"number","description":"Which attempt this was for the event: 1 for the first delivery, then 2 to 7 for the automatic retries (1m, 5m, 30m, 2h, 6h and 24h after each failure). Retries keep the same `event_id`. A test or a replay is numbered in the same sequence.","minimum":1},"status_code":{"type":"number","nullable":true,"description":"HTTP status from the destination; null when the request errored before a response."},"response_body_truncated":{"type":"string","nullable":true},"error_message":{"type":"string","nullable":true},"latency_ms":{"type":"number","nullable":true},"attempted_at":{"type":"string","format":"date-time"}},"required":["id","webhook_id","event_id","event_type","attempt_number","status_code","response_body_truncated","error_message","latency_ms","attempted_at"]},"WebhookDeliveryListResponseDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/LegacyWebhookDeliveryAttemptDto"}},"pagination":{"$ref":"#/components/schemas/PaginationDto"}},"required":["data","pagination"]},"UpdateWebhookDto":{"type":"object","properties":{"url":{"type":"string","description":"New URL"},"events":{"type":"array","description":"New event subscription","items":{"type":"string","enum":["entry.created","entry.approved","entry.rejected"]}},"active":{"type":"boolean","description":"Pause/resume the webhook"}}},"WebhookRotateSecretResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"secret":{"type":"string","description":"New plaintext signing secret, returned EXACTLY ONCE. Persist immediately — it does not appear on subsequent GETs."}},"required":["id","secret"]},"TestWebhookDto":{"type":"object","properties":{"event":{"type":"string","description":"Event type to fire as a synthetic test","enum":["entry.created","entry.approved","entry.rejected"]},"waitlist_id":{"type":"string","description":"Accepted for older clients and ignored: the test delivers the event's example payload from `GET …/webhooks/event-types`."}},"required":["event"]},"WaitlistStatsTotalsDto":{"type":"object","properties":{"waitlists":{"type":"number","description":"Waitlists in the workspace, in any status.","example":2},"entries":{"type":"number","description":"Entries across those waitlists, all time, in any status.","example":1380}},"required":["waitlists","entries"]},"WaitlistStatsInWindowDto":{"type":"object","properties":{"signups":{"type":"number","description":"Entries created in the requested window.","example":212}},"required":["signups"]},"StatsSeriesPointDto":{"type":"object","properties":{"date":{"type":"string","description":"UTC calendar day.","format":"date","example":"2026-10-07"},"value":{"type":"number","description":"Value for that day.","example":4}},"required":["date","value"]},"WaitlistStatsSeriesDto":{"type":"object","properties":{"metric":{"type":"string","enum":["signups"],"description":"New waitlist entries per UTC day across every waitlist in the workspace.","example":"signups"},"granularity":{"type":"string","enum":["day"],"description":"Bucket width.","example":"day"},"points":{"description":"Dense and zero-filled: one point per UTC day in the window, oldest first, ending today.","type":"array","items":{"$ref":"#/components/schemas/StatsSeriesPointDto"}}},"required":["metric","granularity","points"]},"WaitlistWorkspaceStatsDto":{"type":"object","properties":{"window":{"type":"string","enum":["1d","7d","30d"],"description":"Window the counts cover.","example":"30d"},"generated_at":{"type":"string","format":"date-time","description":"When these numbers were computed. Responses are cached for up to 60 seconds.","example":"2026-10-07T09:00:00.000Z"},"last_activity_at":{"type":"string","format":"date-time","nullable":true,"description":"Most recent signup on any of the workspace's waitlists. Null when there has never been one.","example":"2026-10-07T08:41:12.000Z"},"totals":{"description":"All-time counts.","allOf":[{"$ref":"#/components/schemas/WaitlistStatsTotalsDto"}]},"in_window":{"description":"Counts for the window.","allOf":[{"$ref":"#/components/schemas/WaitlistStatsInWindowDto"}]},"series":{"description":"Daily signups.","allOf":[{"$ref":"#/components/schemas/WaitlistStatsSeriesDto"}]}},"required":["window","generated_at","last_activity_at","totals","in_window","series"]},"ErrorParamDto":{"type":"object","properties":{"param":{"type":"string","description":"Wire path of the invalid field: `email`, `items.0.display_name`.","example":"email"},"message":{"type":"string","description":"What is wrong with it; the same text as the field's entry in an array `message`.","example":"email must be an email"},"code":{"type":"string","description":"The failed rule (class-validator constraint name, e.g. `isEmail`, `isUuid`, `whitelistValidation` for a field the operation does not declare), or `invalid`.","example":"isEmail"}},"required":["param","message","code"]},"ErrorResponseDto":{"type":"object","properties":{"status_code":{"type":"number","description":"HTTP status code, same as the response status.","example":404},"statusCode":{"type":"number","description":"Deprecated alias of `status_code`, same value. Kept so existing clients keep working; read `status_code`. Error responses carry `Deprecation: true` while it is sent.","example":404,"deprecated":true},"message":{"description":"Human-readable error message. A string, or on a `VALIDATION_ERROR` an array with one message per invalid field (the same texts as `errors[].message`). Branch on `code`, not on this.","oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"example":"Domain not found"},"code":{"description":"Stable, machine-readable error code; branch on this, not on `message`. Always present. A route-specific code when the route defines one, otherwise the generic code for the status (`NOT_FOUND`, `VALIDATION_ERROR`, `RATE_LIMITED`, ...). Open set: accept codes you do not know. Each code is documented at https://productcraft.co/docs/errors#<code>.","example":"NOT_FOUND","allOf":[{"$ref":"#/components/schemas/WaitlistErrorCode"}]},"request_id":{"type":"string","description":"The request's id, same as the `X-Request-Id` response header. Quote it when contacting support.","pattern":"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$","example":"req_6f1c2a9d4b7e4f0a8c3d2e1f0a9b8c7d"},"errors":{"description":"One entry per invalid request field on a `VALIDATION_ERROR`; empty on every other error.","type":"array","items":{"$ref":"#/components/schemas/ErrorParamDto"}},"details":{"type":"object","description":"Structured context for errors a client has to act on, keyed in snake_case and specific to the code (e.g. `{ fqdn, reason }` on `from_domain_not_verified`). Absent on most errors.","additionalProperties":true,"example":{"fqdn":"mail.example.com","reason":"not_verified"}}},"required":["status_code","message","code","request_id","errors"]},"WaitlistErrorCode":{"description":"Error codes this API returns (open set: a client must accept codes it does not know; new codes are not a breaking change). Each is documented at https://productcraft.co/docs/errors#<code>.","anyOf":[{"type":"string","enum":["BAD_REQUEST","VALIDATION_ERROR","UNAUTHORIZED","PAYMENT_REQUIRED","FORBIDDEN","NOT_FOUND","METHOD_NOT_ALLOWED","NOT_ACCEPTABLE","REQUEST_TIMEOUT","CONFLICT","GONE","PRECONDITION_FAILED","PAYLOAD_TOO_LARGE","UNSUPPORTED_MEDIA_TYPE","UNPROCESSABLE_ENTITY","PRECONDITION_REQUIRED","RATE_LIMITED","INTERNAL_SERVER_ERROR","NOT_IMPLEMENTED","BAD_GATEWAY","SERVICE_UNAVAILABLE","GATEWAY_TIMEOUT","SERVICE_NOT_ENABLED","INVALID_CREDENTIALS","SESSION_REVOKED","MCP_NOT_PERMITTED","MCP_WORKSPACE_MISMATCH","PAK_NOT_PERMITTED","ACTIONS_NOT_GRANTABLE","IDEMPOTENCY_KEY_REUSE","IDEMPOTENCY_KEY_REQUIRED","IDEMPOTENCY_IN_PROGRESS","WEBHOOK_ENDPOINT_LIMIT","WEBHOOK_ENDPOINT_NOT_FOUND","WEBHOOK_ENDPOINT_NOT_SUBSCRIBED","WEBHOOK_EVENT_NOT_FOUND","WEBHOOK_EVENT_TYPE_UNKNOWN","MAIL_UNREACHABLE","ORIGIN_NOT_ALLOWED","WAITLIST_NOT_FOUND","domain_not_verified_by_provider","from_domain_not_verified","no_default_sender"]},{"type":"string","description":"A code added after this document was published."}],"x-pc-open-enum":true,"x-pc-error-docs":"https://productcraft.co/docs/errors#{code}","x-enum-descriptions":{"BAD_REQUEST":"The request is malformed (not valid JSON, a bad header, an unusable parameter).","VALIDATION_ERROR":"One or more request fields are invalid; `errors` lists each field (`param`, `message`, `code`).","UNAUTHORIZED":"No credentials, or the token or API key is invalid, expired or revoked.","PAYMENT_REQUIRED":"The workspace has no credit or plan for this operation.","FORBIDDEN":"The caller is authenticated but not allowed to do this.","NOT_FOUND":"The resource does not exist, or belongs to another workspace or app.","METHOD_NOT_ALLOWED":"The route does not accept this HTTP method.","NOT_ACCEPTABLE":"The route cannot produce the requested content type.","REQUEST_TIMEOUT":"The request took too long to arrive.","CONFLICT":"The request conflicts with the current state of the resource.","GONE":"The resource existed but has been removed for good.","PRECONDITION_FAILED":"A precondition header did not match.","PAYLOAD_TOO_LARGE":"The request body is over the size limit.","UNSUPPORTED_MEDIA_TYPE":"The request body is not in a content type the route accepts.","UNPROCESSABLE_ENTITY":"The request is well-formed but cannot be carried out.","PRECONDITION_REQUIRED":"The route requires a precondition header.","RATE_LIMITED":"Too many requests; retry after the `Retry-After` header.","INTERNAL_SERVER_ERROR":"An unexpected server error. Retrying is safe for idempotent requests; quote `request_id` to support.","NOT_IMPLEMENTED":"The server does not support this operation.","BAD_GATEWAY":"An upstream service returned an invalid response.","SERVICE_UNAVAILABLE":"The service or one of its dependencies is temporarily unavailable; retry.","GATEWAY_TIMEOUT":"An upstream service did not answer in time; retry.","SERVICE_NOT_ENABLED":"The workspace has not enabled this product.","INVALID_CREDENTIALS":"The credentials were not accepted.","SESSION_REVOKED":"The session behind this token was signed out or revoked.","MCP_NOT_PERMITTED":"An MCP (agent) token cannot call this route.","MCP_WORKSPACE_MISMATCH":"The MCP token was issued for a different workspace.","PAK_NOT_PERMITTED":"A workspace API key cannot call this route.","ACTIONS_NOT_GRANTABLE":"The policy grants actions the caller does not hold.","IDEMPOTENCY_KEY_REUSE":"The `Idempotency-Key` was already used with a different body or route.","IDEMPOTENCY_KEY_REQUIRED":"This operation requires an `Idempotency-Key` header.","IDEMPOTENCY_IN_PROGRESS":"A request with the same `Idempotency-Key` is still running. Retry after `Retry-After` seconds with the same key to get its response.","WEBHOOK_ENDPOINT_LIMIT":"The workspace has the maximum number of webhook endpoints.","WEBHOOK_ENDPOINT_NOT_FOUND":"No webhook endpoint with this id in this workspace.","WEBHOOK_ENDPOINT_NOT_SUBSCRIBED":"The endpoint is not subscribed to this event type.","WEBHOOK_EVENT_NOT_FOUND":"No webhook event with this id in this workspace.","WEBHOOK_EVENT_TYPE_UNKNOWN":"The event type is not one this product sends.","MAIL_UNREACHABLE":"The Mail service did not answer; try again in a minute.","ORIGIN_NOT_ALLOWED":"Origin not allowed.","WAITLIST_NOT_FOUND":"No waitlist with this id or slug in this workspace.","domain_not_verified_by_provider":"The From domain's DNS records have been broken for over 72 h, so it no longer sends.","from_domain_not_verified":"The From domain is not an active sending domain of this workspace (`details: { fqdn, reason }`).","no_default_sender":"No `from` and the workspace has no default sender."}}},"headers":{"RequestId":{"description":"The request's id: the inbound `X-Request-Id` when it is 1 to 128 characters of `A-Z a-z 0-9 . _ : -`, otherwise a fresh `req_...` id. Error bodies repeat it as `request_id`. Quote it when contacting support.","schema":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$","example":"req_6f1c2a9d4b7e4f0a8c3d2e1f0a9b8c7d"}}}}}