HomeDocsPricingSign in
← All docs

Signup API

POST signups directly from your backend, app, or CLI. JSON in, JSON out.

Base URL
https://waitforge.com
Endpoints
MethodPathDescription
GET /api/public/:slug/config Public waitlist config (title, theme, fields, Turnstile key)
POST /api/public/:slug/signup Create a signup. Returns position + referral code.
GET /api/public/:slug/config

Returns the waitlist's public configuration. Cached at the edge for 60 seconds. Use this to render your own custom form.

{
  "title": "Acme AI",
  "description": "Be the first to know.",
  "theme": {
    "primary": "#4f46e5",
    "bg": "#ffffff",
    "text": "#0f172a",
    "font": "system",
    "radius": "soft",
    "logo_url": "",
    "hero_image": ""
  },
  "fields": {
    "name": true,
    "company": false,
    "twitter": false
  },
  "branding": true,
  "turnstile_site_key": "0x4AAAAAAA..."
}
POST /api/public/:slug/signup

Creates a signup on a waitlist. Returns the new position and a referral code.

Request body

{
  "email":     "alice@example.com",
  "name":      "Alice",
  "company":   "Acme Inc",
  "twitter":   "alice",
  "ref":       "AB3CD4EF",
  "turnstile": "TURNSTILE_TOKEN"
}

Only email and turnstile are required. Other fields are optional and only stored if the waitlist has them enabled.

Success response (200)

{
  "ok": true,
  "position": 47,
  "ref_code": "XY9ZW8V7",
  "share_path": "/w/your-slug?ref=XY9ZW8V7"
}

Duplicate email (200)

Not an error — returns the existing position and ref code so your UI can show "you're already in".

{
  "ok": true,
  "duplicate": true,
  "position": 47,
  "ref_code": "XY9ZW8V7"
}

Error responses

StatusMeaning
400Invalid email, bad JSON, payload too large, or honeypot triggered.
403Turnstile failed, or waitlist is full.
404Slug doesn't exist.
429Rate limited (120 requests per IP per minute).
500Transient server error — safe to retry once.
Examples

curl

curl -X POST https://waitforge.com/api/public/your-slug/signup \
  -H "Content-Type: application/json" \
  -d '{
    "email": "alice@example.com",
    "name": "Alice",
    "turnstile": "TURNSTILE_TOKEN"
  }'

JavaScript

const res = await fetch(
  'https://waitforge.com/api/public/your-slug/signup',
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      email: 'alice@example.com',
      name: 'Alice',
      turnstile: turnstileToken,
    }),
  }
);
const data = await res.json();
if (data.ok) {
  console.log('Position:', data.position);
  console.log('Share URL:', 'https://waitforge.com' + data.share_path);
}

Python

import httpx

res = httpx.post(
    'https://waitforge.com/api/public/your-slug/signup',
    json={
        'email': 'alice@example.com',
        'name': 'Alice',
        'turnstile': turnstile_token,
    },
)
data = res.json()
print('Position:', data['position'])

Node.js

const res = await fetch(
  'https://waitforge.com/api/public/your-slug/signup',
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email: 'alice@example.com', turnstile: token }),
  }
);
console.log(await res.json());
Turnstile tokens

Every signup request must include a valid Cloudflare Turnstile token. Three ways to get one:

  • Embed widget — the widget handles Turnstile automatically. Use this if you're rendering WaitForge's own form.
  • Your own form — render Turnstile client-side with the turnstile_site_key from the config endpoint and pass the resulting token in the signup POST. See the Turnstile docs.
  • Server-to-server — not supported on the public endpoint. The Turnstile check fails closed.
Rate limits

All endpoints share a per-IP rate limit of 120 requests per minute, enforced at the edge before any DB hit. Exceeding returns 429 with a Retry-After: 60 header.

CORS

The public endpoints (/api/public/*) are CORS-enabled with Access-Control-Allow-Origin: * — you can POST from any origin, including localhost during development.

Owner APIs

Listing your waitlists, updating them, viewing signups, exporting CSVs, and upgrading to Pro live under /api/waitlists/*. They require a valid waitforge_session cookie set by the magic-link login flow and are consumed by the dashboard UI. If you need programmatic access, reach out.