Signup API
Accept signups from your server with a scoped API key.
Use this endpoint when you collect emails on your own backend and want Waitlyze to own verification, referrals, and storage. Browsers should use the hosted page or embed instead - never expose an API key client-side.
See the API Reference for the complete list of supported public endpoints and the boundary between public API keys and dashboard sessions.
Create an API key first
Go to Settings → Integrations → API keys, choose a waitlist, name the key, and set expiry. Copy the secret before closing: only its hash is stored. See API keys.
Keep keys on your server
A key grants signup-write access to one waitlist. Store it in a server environment variable such as WAITLYZE_API_KEY. Never put it in a browser bundle or public repository.
Create a signup
POST /api/waitlist accepts JSON. Authentication uses the apiKey body field. This endpoint does not use a Bearer header.
If Turnstile is enabled for the waitlist in the form builder, include the visitor's cf-turnstile-response token. Waitlyze checks it with Cloudflare before creating the signup; missing, expired, replayed, or unverifiable tokens are rejected. Tokens are single-use. Browser forms should use the hosted page or embed so the verification widget is rendered for visitors.
const response = await fetch("https://waitlyze.com/api/waitlist", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
apiKey: process.env.WAITLYZE_API_KEY,
waitlistId: "YOUR_WAITLIST_ID",
email: "person@example.com",
// referralCode: "OPTIONAL_REFERRAL_CODE"
})
});
const result = await response.json();
if (!response.ok) throw new Error(result.error || "Signup failed");Prop
Type
Handle responses
{
"success": true,
"message": "Successfully joined the waitlist!",
"data": {
"rank": 9,
"position": 9,
"display_position": 9,
"points": 50,
"referral_count": 0,
"next_reward": { "referrals": 3, "reward": "Beta access", "remaining": 3 },
"unlocked_rewards": [],
"spots_to_advance": 2,
"counts_hidden": false,
"referral_code": "7f3a…",
"referral_link": "https://waitlyze.com/w/your-slug?r=7f3a…",
"verified": false
}
}position is the canonical queue position (rank is its legacy alias). referral_code is this signup's personal code and referral_link is the ready-to-share URL - render them in your confirmation UI when referral sharing is on. Both are null/"" when sharing is disabled or the waitlist has no public slug. New signups start verified: false until they click the 24-hour confirmation link - except campaigns with verification turned off, which return verified: true immediately.
Rewards progress rides along in the same payload: points is the signup's balance (50 per verified signup, 30 per completed referral by default - founders can retune both per waitlist), referral_count counts verified referrals only, and next_reward is the nearest locked tier as { referrals, reward, remaining } (null when everything is unlocked). spots_to_advance is the verified referrals needed to take the spot above (0 at the top) - render it as “N more to pass #8”. display_position applies the founder's position inflation (position stays raw); counts_hidden is true for stealth launches, where your UI should hide every number and show the link alone.
Duplicate emails return 409 with the existing position, so a retry never loses state:
{
"success": false,
"error": "Email already registered for this waitlist",
"data": { "position": 9, "referral_code": "7f3a…", "referral_link": "https://…" }
}| Status | Meaning | Fix |
|---|---|---|
400 | Missing or invalid input | Validate email, IDs, and JSON shape. |
401 | Bad key, expiry, or wrong linked waitlist | Check API keys and waitlistId. |
409 | Duplicate email, key reuse, or not accepting signups | Read data.position for duplicates; mint a new Idempotency-Key on reuse; distinguish state reasons. |
404 | Status lookup found no signup | The email never joined this waitlist (lookup only). |
429 | Rate limited | Wait for Retry-After; back off, do not loop. |
503 | Rate-limit service unavailable | Retry after the provided interval. |
500 | Temporary server failure | Retry with exponential backoff. |
New API signups follow the same verification flow: they receive a 24-hour confirmation link and stay unverified until they click it.
Dashboard routes need a session, not a signup key
/api/v1/api-keys and /api/v1/waitlists are browser-session routes. A waitlist:write signup key cannot manage keys, list subscribers, or access them.
Retry safely with Idempotency-Key
Network failures leave you unsure whether a signup landed. Send a client-generated Idempotency-Key header (1–128 chars: A-Z a-z 0-9 _ -) and retry with the same key and body:
curl --request POST 'https://waitlyze.com/api/waitlist' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: 01J9Z8QK7M5N3P2R4T6V8X0Z2C4' \
--data '{ "apiKey": "YOUR_SERVER_SIDE_API_KEY", "waitlistId": "YOUR_WAITLIST_ID", "email": "person@example.com" }'- Replays return the original signup with
"idempotentReplay": trueindata- no duplicate is created. - Reusing a key with a different email returns
409(Idempotency-Key was already used…). Mint a fresh key per distinct signup. - The key is also accepted alongside
x-api-key/ Bearer auth, keeping secrets out of the JSON body if you prefer.
Look up a signup's status
GET /api/waitlist?waitlistId=…&email=… answers “where am I?” for custom frontends without consuming signup intake budget. The key travels in headers only - never in the query string:
curl 'https://waitlyze.com/api/waitlist?waitlistId=YOUR_WAITLIST_ID&email=person%40example.com' \
--header 'X-Api-Key: YOUR_SERVER_SIDE_API_KEY'{ "success": true, "data": { "position": 9, "referral_code": "7f3a…", "referral_link": "https://…", "verified": true } }Unknown emails return 404. Responses are private, no-store - do not cache them in a CDN.
Award custom points
POST /api/waitlist/points credits off-site actions the join flow never sees - a survey completed, a Discord joined. Configure named events with default points under Rewards in the builder, then call with your server-side key:
curl --request POST 'https://waitlyze.com/api/waitlist/points' \
--header 'Content-Type: application/json' \
--header 'X-Api-Key: YOUR_SERVER_SIDE_API_KEY' \
--data '{ "waitlistId": "YOUR_WAITLIST_ID", "email": "person@example.com", "event": "survey", "idempotencyKey": "srv-2026-001" }'{ "success": true, "data": { "awarded": true, "event": "survey", "label": "Survey completed", "points": 20, "balance": 100 } }eventmust be 1–40 chars (a-z 0-9 -). Unknown events return400unless you pass explicitpoints(0–100000) to override the configured default.- The API key travels in the
X-Api-KeyorAuthorizationheader only; keys in the request body are rejected with400. - Only verified signups earn; unverified signups return
409and farm-suspect signups returnawarded: false. Retries with the sameidempotencyKeyreturnawarded: falsewithout double-paying; omit it for once-per-signup-per-event semantics.
Rate limits
Two budgets protect intake; both surface Retry-After on 429/503:
- Shared intake budget - per campaign, client, and recipient windows (see Troubleshooting).
- Per-key daily budget (5,000 signups + lookups per key per day). Successful
POSTsignups andGETlookups (hits and404s) carryX-RateLimit-Limit,X-RateLimit-Remaining, andX-RateLimit-Reset(Unix seconds); validation (400), auth (401), rate-limit (429/503), and server (500) errors - as well as409duplicate/key-reuse responses - do not. Graph remaining per key to spot a noisy integration before it caps.