{"openapi":"3.0.0","paths":{"/v1/workspaces/{workspace_id}/waitlist/settings":{"get":{"operationId":"SettingsController_get","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Workspace Waitlist settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaitlistSettingsResponseDto"}}}},"403":{"description":"Caller lacks `waitlist.settings.read` on the workspace."},"404":{"description":"Workspace does not exist or is not Waitlist-enabled."}},"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaitlistSettingsResponseDto"}}}},"400":{"description":"Validation failure on the patch body (per `UpdateSettingsDto`)."},"403":{"description":"Caller lacks `waitlist.settings.update` on the workspace."},"404":{"description":"Workspace does not exist or is not Waitlist-enabled."}},"security":[{"bearer":[]}],"summary":"Update workspace-level Waitlist defaults","tags":["waitlist-settings"]}},"/v1/workspaces/{workspace_id}/waitlists":{"post":{"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"}}}},"400":{"description":"Validation failure."},"403":{"description":"Caller lacks `waitlist.create`, or workspace has not enabled the `waitlist` service."},"409":{"description":"Slug already taken in this workspace."}},"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"}}}}},"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"}}}},"404":{"description":"Waitlist not found."}},"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"}}}},"400":{"description":"Validation failure."},"404":{"description":"Waitlist not found."}},"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."},"403":{"description":"Caller lacks `waitlist.delete`."},"404":{"description":"Waitlist not found."}},"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"}}}},"400":{"description":"Invalid status transition."},"404":{"description":"Waitlist not found."}},"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"}}],"responses":{"200":{"description":"Page of entries with pagination cursor.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntryListResponseDto"}}}},"403":{"description":"Caller lacks `entry.read`, or workspace has not enabled the `waitlist` service."},"404":{"description":"Waitlist not found in this workspace."}},"security":[{"bearer":[]}],"summary":"List entries on a waitlist (paginated)","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"}}],"responses":{"200":{"description":"Total entry count.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntryCountResponseDto"}}}},"404":{"description":"Waitlist not found in this workspace."}},"security":[{"bearer":[]}],"summary":"Total entry count for a waitlist","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"}}],"responses":{"200":{"description":"text/csv stream — one header row + one row per entry. Content-Disposition: attachment.","content":{"text/csv":{"schema":{"type":"string"}}}}},"security":[{"bearer":[]}],"summary":"Stream every entry as CSV (admin export)","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"}}}},"404":{"description":"Entry not found."}},"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"}}}},"400":{"description":"Invalid status transition."},"403":{"description":"Caller lacks `entry.update`."},"404":{"description":"Entry not found in this waitlist."}},"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."},"403":{"description":"Caller lacks `entry.update`."},"404":{"description":"Entry not found."}},"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"}}}},"422":{"description":"No linked Auth app for this workspace"}},"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"}}}},"400":{"description":"Empty `ids`, more than 100 ids, or unknown action."},"403":{"description":"Caller lacks `entry.update`."},"404":{"description":"Waitlist not found."}},"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/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"}}}},"404":{"description":"Waitlist not found or not active"}},"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"}}}}},"404":{"description":"Waitlist not found or not active"}},"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicEntryCreatedDto"}}}},"400":{"description":"Validation or spam check failed"},"404":{"description":"Waitlist not found"},"409":{"description":"Email already on this waitlist"},"422":{"description":"Waitlist is not active or is full"},"429":{"description":"Too many requests from this IP"}},"summary":"Submit a new entry to a public waitlist","tags":["public-waitlist"]}},"/v1/workspaces/{workspace_id}/waitlists/{waitlist_id}/analytics":{"get":{"operationId":"AnalyticsController_get","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"waitlist_id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Aggregated analytics payload for the waitlist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaitlistAnalyticsResponseDto"}}}},"403":{"description":"Caller lacks `waitlist.read` on the workspace."},"404":{"description":"Waitlist does not exist or is not in the path workspace."}},"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"}}],"responses":{"200":{"description":"Daily timeline array, oldest first, zero-count days included.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyticsTimelineResponseDto"}}}},"400":{"description":"`since` / `until` missing, malformed, or `until <= since`."},"403":{"description":"Caller lacks `waitlist.read` on the workspace."},"404":{"description":"Waitlist does not exist or is not in the path workspace."}},"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"}}],"responses":{"200":{"description":"Bare array of variants","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VariantListResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"List all variants on a waitlist","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"}}}},"400":{"description":"Validation failure"},"404":{"description":"Waitlist not found in this workspace"}},"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"}}}},"404":{"description":"Variant not found"}},"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"},"404":{"description":"Variant not found"}},"security":[{"bearer":[]}],"summary":"Delete a variant","tags":["waitlist-variants"]}},"/v1/workspaces/{workspace_id}/webhooks":{"post":{"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"}}}}},"security":[{"bearer":[]}],"summary":"Create a workspace webhook. The response includes the per-row signing secret in clear EXACTLY ONCE — copy it now.","tags":["webhooks"]},"get":{"operationId":"WebhookController_list","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Webhooks for the workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookListResponseDto"}}}}},"security":[{"bearer":[]}],"summary":"List webhooks for the workspace (secrets masked)","tags":["webhooks"]}},"/v1/workspaces/{workspace_id}/webhooks/{id}":{"get":{"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 row.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookResponseDto"}}}},"404":{"description":"Webhook not found."}},"security":[{"bearer":[]}],"summary":"Get a webhook (secret masked)","tags":["webhooks"]},"patch":{"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 row (secret masked).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookResponseDto"}}}},"404":{"description":"Webhook not found."}},"security":[{"bearer":[]}],"summary":"Update url / events / active flag","tags":["webhooks"]},"delete":{"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":""}},"security":[{"bearer":[]}],"summary":"Delete a webhook","tags":["webhooks"]}},"/v1/workspaces/{workspace_id}/webhooks/{id}/deliveries":{"get":{"operationId":"WebhookController_recentDeliveries","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"`{ data: Array<{ id, webhook_id, event_id, event_type, attempt_number, status_code, response_body_truncated, error_message, latency_ms, attempted_at }> }`, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeliveryListResponseDto"}}}},"404":{"description":"Webhook not found."}},"security":[{"bearer":[]}],"summary":"Recent delivery attempts for a webhook (default 50, max 200, newest first)","tags":["webhooks"]}},"/v1/workspaces/{workspace_id}/webhooks/{id}/rotate-secret":{"post":{"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"}}}},"404":{"description":"Webhook not found."}},"security":[{"bearer":[]}],"summary":"Rotate the signing secret. Returns the new secret in clear EXACTLY ONCE.","tags":["webhooks"]}},"/v1/workspaces/{workspace_id}/webhooks/{id}/test":{"post":{"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":""}},"security":[{"bearer":[]}],"summary":"Fire a synthetic test event to this webhook (asynchronous; check the destination logs)","tags":["webhooks"]}}},"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":{"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."},"default_sender_address":{"type":"string","nullable":true},"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."}},"required":["workspace_id","default_sender_domain_id","default_heimdall_app_slug","brand_name","brand_logo_url","primary_color","notifications_via_envoi","default_sender_address","welcome_template_name","approved_template_name","rejected_template_name","updated_at"]},"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 (workspace sender domain + per-event template selection) instead of staying silent. Requires default_sender_domain_id, default_sender_address, and a template name for every supported event. Default false; when off, Waitlist sends no notification emails and the customer is expected to handle email via the webhook lane."},"default_sender_address":{"type":"string","description":"Verified sender address used as the From envelope on Waitlist notifications. Must belong to default_sender_domain_id. null clears the override.","nullable":true},"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}}},"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)"}},"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"]},"settings":{"type":"object","description":"Waitlist settings (max_entries, approval_required, custom_fields, confirmation_message, referral_boost)."},"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","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 shallow)"}}},"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"},"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","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":"Total number of entries on the waitlist."}},"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"]},"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`."},"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; verified server-side when the waitlist has settings.recaptcha_site_key"},"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)."},"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)."}},"required":["id","email","name","position","status","referral_code","created_at","effective_position"]},"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"]},"VariantListResponseDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/VariantResponseDto"}}},"required":["data"]},"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"}}},"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","items":{"type":"string","enum":["entry.created","entry.approved","entry.rejected"]}},"active":{"type":"boolean"},"failure_count":{"type":"number"},"last_status_code":{"type":"number","nullable":true},"last_attempt_at":{"type":"string","nullable":true},"auto_disabled_at":{"type":"string","nullable":true},"first_failure_at":{"type":"string","nullable":true},"created_at":{"type":"string"},"signing_secret":{"type":"string","description":"Plaintext signing secret returned EXACTLY ONCE on create + rotate. Persist immediately — it does not appear on subsequent GETs."}},"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"]},"WebhookResponseDto":{"type":"object","properties":{"id":{"type":"string"},"workspace_id":{"type":"string"},"url":{"type":"string"},"events":{"type":"array","items":{"type":"string","enum":["entry.created","entry.approved","entry.rejected"]}},"active":{"type":"boolean"},"failure_count":{"type":"number"},"last_status_code":{"type":"number","nullable":true},"last_attempt_at":{"type":"string","nullable":true},"auto_disabled_at":{"type":"string","nullable":true},"first_failure_at":{"type":"string","nullable":true},"created_at":{"type":"string"}},"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"}}},"required":["data"]},"WebhookDeliveryAttemptDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"webhook_id":{"type":"string","format":"uuid"},"event_id":{"type":"string","description":"Idempotency id of the dispatched event."},"event_type":{"type":"string","enum":["entry.created","entry.approved","entry.rejected"]},"attempt_number":{"type":"number","description":"1-based retry counter for the event."},"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/WebhookDeliveryAttemptDto"}}},"required":["data"]},"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":"Waitlist UUID to reference in the synthetic payload. Defaults to a placeholder UUID when omitted."}},"required":["event"]}}}}