{"openapi":"3.1.0","info":{"title":"Waitlist Service API","description":"Create and manage waitlists. Authenticated endpoints accept session cookie or Authorization: Bearer <api_key>. Public signup endpoint requires no auth. API keys are generated automatically on first login.","version":"1.0.0","contact":{"url":"https://waitlist.claudlabs.com"}},"servers":[{"url":"https://waitlist.claudlabs.com","description":"Production"}],"tags":[{"name":"Auth","description":"Authentication flows — OAuth and magic link"},{"name":"Waitlists","description":"Manage waitlists (operator-only)"},{"name":"Entries","description":"Waitlist entries — public signup and operator management"}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"API key generated on first login. Prefix: wl_"}},"schemas":{},"parameters":{}},"paths":{"/auth/magic-link":{"post":{"tags":["Auth"],"summary":"Request a magic link","description":"Sends a one-time sign-in link to the provided email address. The link expires in 15 minutes.","operationId":"post_PostMagicLink","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","description":"Email address to send the magic link to","example":"founder@example.com"}},"required":["email"]}}}},"responses":{"200":{"description":"Magic link sent successfully","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"message":{"type":"string","example":"Check your inbox"}},"required":["message"]}},"required":["data"]}}}},"400":{"description":"Invalid email address","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}}}}},"/auth/magic-link/verify":{"get":{"tags":["Auth"],"summary":"Verify a magic link token","description":"Validates the one-time token from the magic link email. On success, creates a session and redirects to the dashboard. Returns a redirect URL in the response body for API clients.","operationId":"get_GetMagicLinkVerify","parameters":[{"schema":{"type":"string","description":"One-time magic link token from the email","example":"abc123xyz..."},"required":true,"description":"One-time magic link token from the email","name":"token","in":"query"}],"responses":{"200":{"description":"Token valid — session created. Browser clients receive a 302 redirect.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"redirect":{"type":"string","example":"https://waitlist.claudlabs.com/dashboard"}},"required":["redirect"]}},"required":["data"]}}}},"400":{"description":"Missing token parameter","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}},"401":{"description":"Invalid or expired token","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}}}}},"/auth/sign-out":{"post":{"tags":["Auth"],"summary":"Sign out","description":"Clears the session cookie and redirects to the home page.","operationId":"post_PostSignOut","responses":{"200":{"description":"Signed out successfully","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"message":{"type":"string","example":"Signed out"}},"required":["message"]}},"required":["data"]}}}}}}},"/auth/me":{"get":{"tags":["Auth"],"summary":"Get current operator","description":"Returns the currently authenticated operator. Returns 401 if not authenticated.","security":[{"BearerAuth":[]}],"operationId":"get_GetAuthMe","responses":{"200":{"description":"Current operator","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string","format":"email"},"name":{"type":["string","null"]},"avatar_url":{"type":["string","null"]},"created_at":{"type":"string"}},"required":["id","email","name","avatar_url","created_at"]}},"required":["data"]}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}}}}},"/auth/rotate-key":{"post":{"tags":["Auth"],"summary":"Rotate API key","description":"Deletes all existing API keys for the operator and generates a new one. The full key is returned only once — store it immediately.","security":[{"BearerAuth":[]}],"operationId":"post_PostRotateKey","responses":{"200":{"description":"New API key generated","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"key":{"type":"string","description":"Full API key — shown only once","example":"wl_abc123..."},"prefix":{"type":"string","description":"Key prefix for display","example":"wl_abc123"}},"required":["key","prefix"]}},"required":["data"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}}}}},"/auth/api-keys":{"get":{"tags":["Auth"],"summary":"List API keys","description":"Returns the API keys for the authenticated operator (prefix and metadata only — never the full key).","security":[{"BearerAuth":[]}],"operationId":"get_GetApiKeys","responses":{"200":{"description":"List of API keys","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"prefix":{"type":"string"},"label":{"type":"string"},"created_at":{"type":"string"},"last_used_at":{"type":["string","null"]}},"required":["id","prefix","label","created_at","last_used_at"]}}},"required":["data"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}}}}},"/entries/status":{"get":{"tags":["Entries"],"summary":"Check entry status by token","description":"Public endpoint. Looks up an entry by its unique token across all waitlists. Use this for the applicant status page.","operationId":"get_GetEntryStatusByToken","parameters":[{"schema":{"type":"string","description":"Entry token returned at signup","example":"abc123xyz..."},"required":true,"description":"Entry token returned at signup","name":"token","in":"query"}],"responses":{"200":{"description":"Entry status","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"waitlist_id":{"type":"string"},"waitlist_name":{"type":"string"},"email":{"type":"string","format":"email"},"position":{"type":"integer"},"status":{"type":"string","enum":["awaiting_confirmation","confirmed","rejected"]},"created_at":{"type":"string"}},"required":["waitlist_id","waitlist_name","email","position","status","created_at"]}},"required":["data"]}}}},"400":{"description":"Missing token","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}},"404":{"description":"Entry not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}}}}},"/waitlists":{"post":{"tags":["Waitlists"],"summary":"Create a waitlist","description":"Creates a new waitlist for the authenticated operator.","security":[{"BearerAuth":[]}],"operationId":"post_PostWaitlist","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"Waitlist name","example":"My Product Beta"},"description":{"type":["string","null"],"maxLength":500,"description":"Optional description","example":"Early access to our product"},"capacity":{"type":["integer","null"],"minimum":1,"description":"Maximum number of entries (null = unlimited)","example":500},"display_mode":{"type":"string","enum":["position_only","total_count","hidden"],"description":"Controls what position info subscribers see after signing up. Defaults to position_only.","example":"position_only"}},"required":["name"]}}}},"responses":{"201":{"description":"Waitlist created","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string","example":"wl_abc123"},"operator_id":{"type":"string","example":"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"},"name":{"type":"string","example":"My Product Beta"},"description":{"type":["string","null"],"example":"Early access to our product"},"capacity":{"type":["integer","null"],"example":500},"display_mode":{"type":"string","enum":["position_only","total_count","hidden"],"example":"position_only","description":"Controls what position info subscribers see after signing up"},"entry_count":{"type":"integer","example":42},"pending_count":{"type":"integer","example":10,"description":"Number of entries in \"awaiting_confirmation\" state (i.e. signed up but not yet email-verified)"},"created_at":{"type":"string","example":"2026-01-01T00:00:00.000Z"},"updated_at":{"type":"string","example":"2026-01-01T00:00:00.000Z"}},"required":["id","operator_id","name","description","capacity","display_mode","created_at","updated_at"]}},"required":["data"]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}}}},"get":{"tags":["Waitlists"],"summary":"List waitlists","description":"Returns all waitlists belonging to the authenticated operator, with live entry counts.","security":[{"BearerAuth":[]}],"operationId":"get_GetWaitlists","responses":{"200":{"description":"List of waitlists","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"wl_abc123"},"operator_id":{"type":"string","example":"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"},"name":{"type":"string","example":"My Product Beta"},"description":{"type":["string","null"],"example":"Early access to our product"},"capacity":{"type":["integer","null"],"example":500},"display_mode":{"type":"string","enum":["position_only","total_count","hidden"],"example":"position_only","description":"Controls what position info subscribers see after signing up"},"entry_count":{"type":"integer","example":42},"pending_count":{"type":"integer","example":10,"description":"Number of entries in \"awaiting_confirmation\" state (i.e. signed up but not yet email-verified)"},"created_at":{"type":"string","example":"2026-01-01T00:00:00.000Z"},"updated_at":{"type":"string","example":"2026-01-01T00:00:00.000Z"}},"required":["id","operator_id","name","description","capacity","display_mode","created_at","updated_at"]}},"meta":{"type":"object","properties":{"page":{"type":"integer","example":1},"per_page":{"type":"integer","example":50},"total":{"type":"integer","example":123}},"required":["page","per_page","total"]}},"required":["data","meta"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}}}}},"/waitlists/{id}":{"get":{"tags":["Waitlists"],"summary":"Get a waitlist","description":"Returns a single waitlist with live entry stats.","security":[{"BearerAuth":[]}],"operationId":"get_GetWaitlist","parameters":[{"schema":{"type":"string","description":"Waitlist ID","example":"wl_abc123"},"required":true,"description":"Waitlist ID","name":"id","in":"path"}],"responses":{"200":{"description":"Waitlist details","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string","example":"wl_abc123"},"operator_id":{"type":"string","example":"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"},"name":{"type":"string","example":"My Product Beta"},"description":{"type":["string","null"],"example":"Early access to our product"},"capacity":{"type":["integer","null"],"example":500},"display_mode":{"type":"string","enum":["position_only","total_count","hidden"],"example":"position_only","description":"Controls what position info subscribers see after signing up"},"entry_count":{"type":"integer","example":42},"pending_count":{"type":"integer","example":10,"description":"Number of entries in \"awaiting_confirmation\" state (i.e. signed up but not yet email-verified)"},"created_at":{"type":"string","example":"2026-01-01T00:00:00.000Z"},"updated_at":{"type":"string","example":"2026-01-01T00:00:00.000Z"}},"required":["id","operator_id","name","description","capacity","display_mode","created_at","updated_at"]}},"required":["data"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}},"403":{"description":"Forbidden — waitlist belongs to another operator","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}},"404":{"description":"Waitlist not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}}}},"patch":{"tags":["Waitlists"],"summary":"Update a waitlist","description":"Updates one or more fields of an existing waitlist. Only provided fields are updated.","security":[{"BearerAuth":[]}],"operationId":"patch_PatchWaitlist","parameters":[{"schema":{"type":"string","description":"Waitlist ID","example":"wl_abc123"},"required":true,"description":"Waitlist ID","name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":["string","null"],"minLength":1,"maxLength":100,"description":"Waitlist name","example":"My Product Beta"},"description":{"type":["string","null"],"maxLength":500,"description":"Optional description","example":"Early access to our product"},"capacity":{"type":["integer","null"],"minimum":1,"description":"Maximum number of entries (null = unlimited)","example":500},"display_mode":{"type":["string","null"],"enum":["position_only","total_count","hidden",null],"description":"Controls what position info subscribers see after signing up","example":"position_only"}}}}}},"responses":{"200":{"description":"Updated waitlist","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string","example":"wl_abc123"},"operator_id":{"type":"string","example":"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"},"name":{"type":"string","example":"My Product Beta"},"description":{"type":["string","null"],"example":"Early access to our product"},"capacity":{"type":["integer","null"],"example":500},"display_mode":{"type":"string","enum":["position_only","total_count","hidden"],"example":"position_only","description":"Controls what position info subscribers see after signing up"},"entry_count":{"type":"integer","example":42},"pending_count":{"type":"integer","example":10,"description":"Number of entries in \"awaiting_confirmation\" state (i.e. signed up but not yet email-verified)"},"created_at":{"type":"string","example":"2026-01-01T00:00:00.000Z"},"updated_at":{"type":"string","example":"2026-01-01T00:00:00.000Z"}},"required":["id","operator_id","name","description","capacity","display_mode","created_at","updated_at"]}},"required":["data"]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}},"403":{"description":"Forbidden — waitlist belongs to another operator","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}},"404":{"description":"Waitlist not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}}}}},"/waitlists/{id}/entries":{"post":{"tags":["Entries"],"summary":"Join a waitlist","description":"Public endpoint — no authentication required. Adds an applicant to the waitlist. Returns a token the applicant can use to check their status.","operationId":"post_PostEntry","parameters":[{"schema":{"type":"string","description":"Waitlist ID","example":"wl_abc123"},"required":true,"description":"Waitlist ID","name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","description":"Applicant email address","example":"user@example.com"},"name":{"type":["string","null"],"maxLength":100,"description":"Applicant display name","example":"Alice Smith"},"metadata":{"type":["object","null"],"additionalProperties":{},"description":"Arbitrary key-value metadata to store with the entry","example":{"referral":"twitter","plan":"pro"}}},"required":["email"]}}}},"responses":{"201":{"description":"Successfully joined the waitlist","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string","format":"email"},"name":{"type":["string","null"]},"position":{"type":"integer"},"status":{"type":"string","enum":["awaiting_confirmation","confirmed","rejected"]},"token":{"type":"string","description":"Use this token with GET /waitlists/:id/entries/status to check your position"},"created_at":{"type":"string"},"display_mode":{"type":"string","enum":["position_only","total_count","hidden"],"description":"Controls what position info the widget displays after signup"},"entry_count":{"type":"integer","description":"Total number of entries in the waitlist after this signup"}},"required":["id","email","name","position","status","token","created_at","display_mode","entry_count"]}},"required":["data"]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}},"404":{"description":"Waitlist not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}},"409":{"description":"Conflict — email already registered (`ALREADY_REGISTERED`) or waitlist is at capacity (`WAITLIST_FULL`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}}}},"get":{"tags":["Entries"],"summary":"List entries","description":"Returns paginated entries for a waitlist. Supports filtering by status.","security":[{"BearerAuth":[]}],"operationId":"get_GetEntries","parameters":[{"schema":{"type":"string","description":"Waitlist ID","example":"wl_abc123"},"required":true,"description":"Waitlist ID","name":"id","in":"path"},{"schema":{"type":"string","enum":["awaiting_confirmation","confirmed","rejected"],"description":"Filter by entry status"},"required":false,"description":"Filter by entry status","name":"status","in":"query"},{"schema":{"type":"integer","exclusiveMinimum":0,"default":1,"description":"Page number (1-indexed)","example":1},"required":false,"description":"Page number (1-indexed)","name":"page","in":"query"},{"schema":{"type":"integer","exclusiveMinimum":0,"maximum":100,"default":50,"description":"Results per page (max 100)","example":50},"required":false,"description":"Results per page (max 100)","name":"per_page","in":"query"}],"responses":{"200":{"description":"Paginated list of entries","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"e1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"},"waitlist_id":{"type":"string","example":"wl_abc123"},"email":{"type":"string","format":"email","example":"user@example.com"},"name":{"type":["string","null"],"example":"Alice Smith"},"status":{"type":"string","enum":["awaiting_confirmation","confirmed","rejected"],"example":"awaiting_confirmation","description":"Current status of the entry. \"awaiting_confirmation\" is the initial state on signup. \"confirmed\" is set automatically when the subscriber clicks the email confirmation link. Operators may only set \"rejected\"."},"position":{"type":"integer","example":7},"token":{"type":"string","example":"abc123xyz..."},"created_at":{"type":"string","example":"2026-01-01T00:00:00.000Z"},"updated_at":{"type":"string","example":"2026-01-01T00:00:00.000Z"}},"required":["id","waitlist_id","email","name","status","position","token","created_at","updated_at"]}},"meta":{"type":"object","properties":{"page":{"type":"integer","example":1},"per_page":{"type":"integer","example":50},"total":{"type":"integer","example":123}},"required":["page","per_page","total"]}},"required":["data","meta"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}},"404":{"description":"Waitlist not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}}}}},"/waitlists/{id}/entries/status":{"get":{"tags":["Entries"],"summary":"Check waitlist position","description":"Public endpoint — no authentication required. Allows an applicant to check their position and status using the token returned at signup.","operationId":"get_GetEntryStatus","parameters":[{"schema":{"type":"string","description":"Waitlist ID","example":"wl_abc123"},"required":true,"description":"Waitlist ID","name":"id","in":"path"},{"schema":{"type":"string","description":"Entry token returned at signup","example":"abc123xyz..."},"required":true,"description":"Entry token returned at signup","name":"token","in":"query"}],"responses":{"200":{"description":"Entry status","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"waitlist_name":{"type":"string","example":"My Product Beta"},"email":{"type":"string","format":"email","example":"user@example.com"},"position":{"type":"integer","example":7},"status":{"type":"string","enum":["awaiting_confirmation","confirmed","rejected"],"example":"awaiting_confirmation"},"created_at":{"type":"string","example":"2026-01-01T00:00:00.000Z"}},"required":["waitlist_name","email","position","status","created_at"]}},"required":["data"]}}}},"400":{"description":"Missing token parameter","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}},"404":{"description":"Entry not found for this token","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}}}}},"/waitlists/{id}/entries/{entry_id}":{"patch":{"tags":["Entries"],"summary":"Update an entry","description":"Updates the status or name of a single entry. Idempotent — setting the same status twice is safe.","security":[{"BearerAuth":[]}],"operationId":"patch_PatchEntry","parameters":[{"schema":{"type":"string","description":"Waitlist ID","example":"wl_abc123"},"required":true,"description":"Waitlist ID","name":"id","in":"path"},{"schema":{"type":"string","description":"Entry ID","example":"e1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"},"required":true,"description":"Entry ID","name":"entry_id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["rejected"],"description":"New status for the entry. Only 'rejected' is operator-settable; confirmation is automatic via email link."},"name":{"type":["string","null"],"maxLength":255,"description":"Updated display name"}}}}}},"responses":{"200":{"description":"Updated entry","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string","example":"e1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"},"waitlist_id":{"type":"string","example":"wl_abc123"},"email":{"type":"string","format":"email","example":"user@example.com"},"name":{"type":["string","null"],"example":"Alice Smith"},"status":{"type":"string","enum":["awaiting_confirmation","confirmed","rejected"],"example":"awaiting_confirmation","description":"Current status of the entry. \"awaiting_confirmation\" is the initial state on signup. \"confirmed\" is set automatically when the subscriber clicks the email confirmation link. Operators may only set \"rejected\"."},"position":{"type":"integer","example":7},"token":{"type":"string","example":"abc123xyz..."},"created_at":{"type":"string","example":"2026-01-01T00:00:00.000Z"},"updated_at":{"type":"string","example":"2026-01-01T00:00:00.000Z"}},"required":["id","waitlist_id","email","name","status","position","token","created_at","updated_at"]}},"required":["data"]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}},"403":{"description":"Forbidden — entry does not belong to operator's waitlist","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}},"404":{"description":"Waitlist or entry not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}}}}},"/waitlists/{id}/entries/batch":{"post":{"tags":["Entries"],"summary":"Bulk update entries","description":"Approves or rejects multiple entries in a single request. Idempotent — entries already in the target state are silently skipped. Only entries belonging to the operator's waitlist are updated.","security":[{"BearerAuth":[]}],"operationId":"post_PostBatchEntries","parameters":[{"schema":{"type":"string","description":"Waitlist ID","example":"wl_abc123"},"required":true,"description":"Waitlist ID","name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ids":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":500,"description":"Array of entry IDs to update","example":["e1b2c3d4","e5f6a7b8"]},"action":{"type":"string","enum":["reject"],"description":"Action to apply to all specified entries"}},"required":["ids","action"]}}}},"responses":{"200":{"description":"Batch update result","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"updated":{"type":"integer","description":"Number of entries actually updated","example":3},"ids":{"type":"array","items":{"type":"string"},"description":"IDs of entries that were updated"}},"required":["updated","ids"]}},"required":["data"]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}},"404":{"description":"Waitlist not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}}}}},"/waitlists/{id}/notify":{"post":{"tags":["Waitlists"],"summary":"Send launch notification","description":"Sends a launch notification email to all confirmed subscribers of the waitlist. Tracks sent/failed counts and stamps `notify_sent_at` on the waitlist row.","security":[{"BearerAuth":[]}],"operationId":"post_PostNotifyWaitlist","parameters":[{"schema":{"type":"string","description":"Waitlist ID","example":"wl_abc123"},"required":true,"description":"Waitlist ID","name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"subject":{"type":"string","minLength":1,"description":"Email subject line","example":"We're live! 🚀"},"message":{"type":"string","minLength":1,"description":"Email body message sent to all confirmed subscribers","example":"We're thrilled to announce that our product is now live. Thank you for your patience!"}},"required":["subject","message"]}}}},"responses":{"200":{"description":"Notification batch dispatched","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"sent":{"type":"integer","description":"Number of emails successfully sent","example":42},"failed":{"type":"integer","description":"Number of emails that failed to send","example":0},"notify_sent_at":{"type":"string","description":"ISO timestamp when the notification batch was dispatched","example":"2026-04-13T12:00:00.000Z"}},"required":["sent","failed","notify_sent_at"]}},"required":["data"]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}},"403":{"description":"Forbidden — waitlist belongs to another operator","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}},"404":{"description":"Waitlist not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]}}}}}}}},"webhooks":{}}