{"openapi":"3.1.0","info":{"title":"NovelThread website API","version":"1.0.0","description":"The public launch waitlist API. Submission requires user consent, a same-origin browser request and a valid Cloudflare Turnstile token. Discovery does not grant unattended registration or access to private application APIs."},"servers":[{"url":"https://novelthread.app"}],"externalDocs":{"url":"https://novelthread.app/api-docs","description":"Website API documentation"},"paths":{"/public/v1/waitlist":{"post":{"operationId":"joinWaitlist","summary":"Request a launch notification","description":"Use the waitlist form on novelthread.app with the user’s consent. Requests must originate on the website and include Origin: https://novelthread.app. Turnstile tokens must verify for the website hostname and action waitlist. Requests are limited to 16 KiB and five attempts per minute per IP. All responses have an empty body. Duplicate emails receive the same 202 response and never replace the first submission.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["email","consent","policyVersion","turnstileToken"],"properties":{"email":{"type":"string","format":"email","description":"Trimmed email, at most 320 UTF-8 bytes, without whitespace or control characters. The domain is lowercased; the local part is preserved."},"reason":{"type":"string","maxLength":1000,"description":"Optional reason for interest; trimmed before validation. Paragraph breaks are allowed, other control characters are rejected."},"workField":{"type":"string","maxLength":120,"description":"Optional field of work; trimmed before validation. Control characters are rejected."},"consent":{"type":"boolean","const":true,"description":"The user explicitly agrees to the waitlist privacy terms."},"policyVersion":{"type":"string","const":"waitlist-2026-09-05"},"website":{"type":"string","description":"Anti-spam honeypot. Omit or leave empty. Non-empty values are silently discarded."},"turnstileToken":{"type":"string","minLength":1,"maxLength":2048,"description":"A valid Cloudflare Turnstile token generated by the website for action waitlist."}}},"example":{"email":"writer@example.com","consent":true,"policyVersion":"waitlist-2026-09-05","turnstileToken":"REPLACE_WITH_TOKEN_FROM_WEBSITE"}}}},"responses":{"202":{"description":"Accepted, already registered, or silently discarded by the anti-spam honeypot. Empty body; does not disclose whether an email is registered."},"400":{"description":"Malformed JSON, invalid fields, missing consent, or outdated policy version."},"403":{"description":"Origin, client IP, or Turnstile verification failed."},"405":{"description":"Method not allowed. Use POST.","headers":{"Allow":{"schema":{"type":"string","const":"POST"}}}},"410":{"description":"The waitlist is closed."},"413":{"description":"Request body exceeds 16 KiB."},"415":{"description":"Content-Type must be application/json."},"429":{"description":"Too many attempts. Retry after 60 seconds.","headers":{"Retry-After":{"schema":{"type":"string","const":"60"}}}},"503":{"description":"Configuration, verification, or storage is temporarily unavailable."}}}}}}