{"openapi":"3.0.0","paths":{"/v1/workspaces/{workspace_id}/rally/settings":{"get":{"operationId":"SettingsController_get","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Workspace Rally settings."},"403":{"description":"Caller lacks `rally.settings.read` on the workspace."},"404":{"description":"Workspace does not exist or is not Rally-enabled."}},"security":[{"bearer":[]}],"summary":"Read workspace-level Rally defaults","tags":["rally-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 Rally settings."},"400":{"description":"Validation failure on the patch body (per `UpdateSettingsDto`)."},"403":{"description":"Caller lacks `rally.settings.update` on the workspace."},"404":{"description":"Workspace does not exist or is not Rally-enabled."}},"security":[{"bearer":[]}],"summary":"Update workspace-level Rally defaults","tags":["rally-settings"]}},"/v1/workspaces/{workspace_id}/waitlists":{"post":{"operationId":"WaitlistController_create","parameters":[{"name":"workspace_id","required":true,"in":"path","schema":{"type":"string"}},{"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 `rally` 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"}},{"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"}}],"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"}}],"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"}}],"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"}}],"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 `rally` 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":""}},"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":"CSV stream"}},"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 Heimdall"},"422":{"description":"No linked Heimdall app for this workspace"}},"security":[{"bearer":[]}],"summary":"Invite an approved entry into a linked Heimdall 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."},"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"},"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 }`"},"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"},"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."},"403":{"description":"Caller lacks `rally.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."},"400":{"description":"`since` / `until` missing, malformed, or `until <= since`."},"403":{"description":"Caller lacks `rally.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":""}},"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, event_id, event_type, attempt_number, status_code, response_body, error_message, latency_ms, created_at }> }`"}},"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":""}},"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":"Rally","description":"Waitlist management for the ProductCraft platform","version":"0.1.0","contact":{}},"tags":[],"servers":[],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"UpdateSettingsDto":{"type":"object","properties":{"default_sender_domain_id":{"type":"string","description":"Envoi (mailbox-api) domain UUID to use as default sender for Rally notifications. null clears the override.","nullable":true},"default_heimdall_app_slug":{"type":"string","description":"Heimdall 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 Rally notifications via Envoi (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, Rally 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 Rally notifications. Must belong to default_sender_domain_id. null clears the override.","nullable":true},"welcome_template_name":{"type":"string","description":"Envoi template name used for entry.created notifications. Must already exist in the workspace template store.","nullable":true},"approved_template_name":{"type":"string","description":"Envoi template name used for entry.approved notifications. Must already exist in the workspace template store.","nullable":true},"rejected_template_name":{"type":"string","description":"Envoi 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"]},"UpdateEntryStatusDto":{"type":"object","properties":{"status":{"type":"string","description":"Target status","enum":["approved","rejected"]}},"required":["status"]},"InviteToAppDto":{"type":"object","properties":{"app_slug":{"type":"string","description":"Heimdall app slug to invite into; defaults to rally_settings.default_heimdall_app_slug"},"send_email":{"type":"boolean","description":"Send the rally/approved email with the Heimdall invite link baked in","default":true}}},"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":"Heimdall app slug override for `action: invite-to-app`. When omitted, falls back to `rally_settings.default_heimdall_app_slug`. Ignored for approve/reject.","example":"acme-product","maxLength":64},"send_email":{"type":"boolean","description":"Whether to send the rally/approved email with the invite link baked in. Defaults to `true`. Ignored for approve/reject.","default":true}},"required":["ids","action"]},"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"]},"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"]},"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"}}},"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"]}}}}