REST API

Base URL: https://waitloop.dev/api/v1 (self-hosted: <your-host>/api/v1). Auth: Authorization: Bearer wl_YOUR_KEY on every request. All bodies are JSON.

Waitlists can be referenced by UUID or slug in path parameters.

Endpoints

GET /me

Returns the account the key belongs to.

GET /waitlists

List waitlists with signupCount and pageUrl.

POST /waitlists

curl -X POST https://waitloop.dev/api/v1/waitlists \
  -H "Authorization: Bearer wl_YOUR_KEY" -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Robots",
    "slug": "acme-robots",
    "theme": {
      "headline": "Robots for everyone",
      "description": "The waitlist for our launch.",
      "buttonText": "Join the waitlist",
      "successMessage": "You are in.",
      "accentColor": "#ff6b3d",
      "background": "dark"
    },
    "referralsEnabled": true,
    "avatarsEnabled": true,
    "webhookUrl": "https://example.com/hooks/waitloop"
  }'

Returns 201 with {"waitlist": {..., "pageUrl"}}. Omit slug to derive it from the name. avatarsEnabled (default true) shows the queue of signup avatars on the hosted page and lets signups customize theirs.

GET /waitlists/:id · PATCH /waitlists/:id · DELETE /waitlists/:id

Read, patch (theme fields merge), or delete. PATCH accepts the same fields as create; set "webhookUrl": null to remove.

GET /waitlists/:id/signups?limit=100&offset=0

Signups ordered by position (referral count desc, then signup time). limit ≤ 1000.

POST /waitlists/:id/signups

{
  "email": "friend@example.com",
  "referredByCode": "abc123",
  "avatar": { "expression": "wink", "accessory": "cap", "color": "#9ecbf5" },
  "metadata": { "source": "import" }
}

Owner-side add (imports, manual). Dedupes by email — returns 200 with the existing signup instead of 201. avatar is optional; omitted fields are dealt deterministically from the signup's referral code. Expressions: smile grin wink joy starry. Accessories: none party cap bow glasses. Color: any hex.

GET /waitlists/:id/signups/export?format=csv

Full export. format=json (default) or format=csv (returns text/csv).

GET /waitlists/:id/stats

{"stats": {"total", "last24h", "last7d", "referred", "topReferrers", "pageUrl"}}

Public endpoints (no auth — used by hosted pages, embeds, and integrations)

GET /api/public/w/:slug

Public read of a waitlist for third-party embeds (e.g. a link-in-bio waitlist block). CORS-open, cached 30s. No emails or owner data — only what the hosted page already shows.

{
  "slug": "moonbase-espresso",
  "name": "Moonbase Espresso",
  "pageUrl": "https://waitloop.dev/w/moonbase-espresso",
  "theme": { "headline": "…", "description": "…", "buttonText": "…", "accentColor": "#7c5cff", "background": "light" },
  "referralsEnabled": true,
  "avatarsEnabled": true,
  "total": 41,
  "front": [ { "position": 1, "avatar": { "expression": "grin", "accessory": "none", "color": "#ffd166" }, "earned": ["crown", "glow"] } ]
}

front is the first 8 in line and is omitted when avatarsEnabled is false.

POST /api/public/w/:slug/signups

{ "email": "visitor@example.com", "ref": "abc123", "avatar": { "expression": "grin" }, "metadata": { "source": "indielinks" } }

Returns {"created", "position", "referralUrl", "referralCode", "avatar", "earned", "successMessage"}. CORS-open. earned lists items the queue has handed out: crown (holding #1), balloon/pennant/glow (1/5/10 referrals). metadata is stored with the signup — use it for attribution when embedding the form elsewhere.

PATCH /api/public/w/:slug/signups

{ "code": "k2m4p6q8", "avatar": { "accessory": "party", "color": "#ffd166" } }

Update your queue avatar after joining — code is your referral code, no account needed. Returns {"position", "avatar", "earned"}. CORS-open.

Errors

Non-2xx responses are {"error": "message"}. 401 missing/bad key, 404 not found (or not yours), 409 slug taken, 400 validation (includes issues).