If the embed widget doesn’t fit your stack (you have a custom form, a non-browser client, or a server-side flow that runs in the browser), you can call the public subscribe endpoint directly. It’s the same endpoint the widget uses.
The endpoint is unauthenticated. Submissions are protected by the anti-abuse pipeline instead.
For server-to-server flows where your own backend is doing the signup, use the authenticated POST subscribers endpoint instead. The Bearer key proves tenant identity, so the Origin / Challenge / Proof of Work gates don’t apply.
Subscribe
POST https://api.getqueueup.com/v1/public/waitlists/{waitlist_id}/subscribers
Find the waitlist_id (wl_...) at the top of your waitlist’s setup page in the dashboard.
Headers
| Header | Required | Description |
|---|---|---|
Content-Type | yes | application/json. |
Origin | yes | Browser-supplied for cross-origin requests. Must match the waitlist’s origin allowlist. |
Request body
{
"email": "[email protected]"
}
| Field | Type | Required | Notes |
|---|---|---|---|
email | string | yes | RFC 5322 syntax. Normalised server-side; see Anti-abuse → Per-email throttle. |
pow | object | only on retry | Present when retrying after a 428 with gate: "pow". |
challenge_token | string | only on retry | Present when retrying after a 428 with gate: "challenge". |
You don’t need to construct pow or challenge_token yourself unless you’re writing a custom client. The official Web Component and SDK both handle the retry transparently. If your waitlist has Proof of Work or Challenge enabled, you’ll receive a 428 and need to solve and retry. See Challenge required below.
Success response
202 Accepted
{
"status": "accepted",
"status_token": "0123abcd...",
"referral_code": "Q7M3KX",
"status_url": "https://app.getqueueup.com/s/0123abcd..."
}
The subscriber row was created (or matched an existing row; duplicates are silently de-duped).
status_token, referral_code, and status_url are only populated when the waitlist has referrals enabled. They are returned identically for a fresh signup or a re-signup of the same email (no double-credit on the referrer).
Challenge required
The first request returns 428 Precondition Required if the waitlist requires Challenge or Proof of Work. The body tells you which gate is active.
Challenge mode:
{
"status": "challenge_required",
"gate": "challenge",
"challenge": {
"token": "base64.signed.token",
"min_age_ms": 250
}
}
Wait at least min_age_ms, then retry the original request with challenge_token set to the value from challenge.token.
Proof of Work mode:
{
"status": "challenge_required",
"gate": "pow",
"pow": {
"nonce": "...",
"difficulty": 50000
}
}
Solve the Proof of Work (find an integer solution such that the hash of (nonce, solution) clears the difficulty target), then retry with pow: { nonce, solution }.
The widget SDK ships solvers for both gates. You only need to implement these yourself if you’re building a custom client.
Errors
| Status | When |
|---|---|
400 Bad Request | Email syntactically invalid or request body malformed. |
404 Not Found | Waitlist doesn’t exist, is archived, the Origin header is missing, or origin isn’t on the allowlist. (Same status across these cases, by design, to avoid leaking which waitlists exist.) |
428 Precondition Required | Challenge or Proof of Work required (see above). |
429 Too Many Requests | Per-IP per-waitlist throttle exceeded. Includes Retry-After: 60. |
500 Internal Server Error | Transient. Retry with backoff. |
CORS preflight
OPTIONS https://api.getqueueup.com/v1/public/waitlists/{waitlist_id}/subscribers
Returns 204 No Content with the standard CORS response headers. Browsers issue this automatically on POST requests with Content-Type: application/json.
The Access-Control-Allow-Origin header is set to the request’s Origin value if it matches the waitlist’s allowlist; otherwise the preflight rejects.