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.

Server-side JavaScript
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 response
{
  "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:

Duplicate response (409)
{
  "success": false,
  "error": "Email already registered for this waitlist",
  "data": { "position": 9, "referral_code": "7f3a…", "referral_link": "https://…" }
}
StatusMeaningFix
400Missing or invalid inputValidate email, IDs, and JSON shape.
401Bad key, expiry, or wrong linked waitlistCheck API keys and waitlistId.
409Duplicate email, key reuse, or not accepting signupsRead data.position for duplicates; mint a new Idempotency-Key on reuse; distinguish state reasons.
404Status lookup found no signupThe email never joined this waitlist (lookup only).
429Rate limitedWait for Retry-After; back off, do not loop.
503Rate-limit service unavailableRetry after the provided interval.
500Temporary server failureRetry 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:

Idempotent signup
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": true in data - 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:

Status lookup
curl 'https://waitlyze.com/api/waitlist?waitlistId=YOUR_WAITLIST_ID&email=person%40example.com' \
  --header 'X-Api-Key: YOUR_SERVER_SIDE_API_KEY'
Status response
{ "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:

Custom point award
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" }'
Award response
{ "success": true, "data": { "awarded": true, "event": "survey", "label": "Survey completed", "points": 20, "balance": 100 } }
  • event must be 1–40 chars (a-z 0-9 -). Unknown events return 400 unless you pass explicit points (0–100000) to override the configured default.
  • The API key travels in the X-Api-Key or Authorization header only; keys in the request body are rejected with 400.
  • Only verified signups earn; unverified signups return 409 and farm-suspect signups return awarded: false. Retries with the same idempotencyKey return awarded: false without 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 POST signups and GET lookups (hits and 404s) carry X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (Unix seconds); validation (400), auth (401), rate-limit (429/503), and server (500) errors - as well as 409 duplicate/key-reuse responses - do not. Graph remaining per key to spot a noisy integration before it caps.

Next

On this page