You render the form. We take the signup.
Waitlist is API-only — there is no hosted page and no embed script. Two anonymous endpoints: read the waitlist, post an entry.
1
Read the waitlist (optional)
Fetch copy and branding at render time so marketing can change the headline without a redeploy. Anonymous — no token, no CORS credentials.
curl 'https://api.waitlist.productcraft.co/v1/waitlists/acme/acme-pro?lang=pt-BR'{
"slug": "acme-pro",
"workspace_slug": "acme",
"canonical_workspace_slug": "acme",
"display_name": "Acme Pro early access",
"description": "Sign up to be among the first to try Acme Pro.",
"status": "active",
"custom_fields": [],
"confirmation_message": "You're on the list. We'll be in touch.",
"recaptcha_site_key": null,
"referral_rewards": { "milestones": [] },
"variant": null,
"brand": {
"brand_name": "Acme",
"brand_logo_url": "https://cdn.acme.com/logo.png",
"primary_color": "#1E40AF"
}
}404 means “not found or not active” — a draft, paused or closed waitlist looks identical to a missing one from the outside.
?lang= (BCP-47) beats the Accept-Language header when picking which variant's copy to serve. When a variant is active, variant carries { id, locale, kind } — echo that id back as variant_id on submit so the entry is attributed. See chapter 5.
canonical_workspace_slug is there so a link built against a renamed workspace slug can 301 itself: compare it to the slug in your URL.
2
Submit an entry
curl https://api.waitlist.productcraft.co/v1/waitlists/acme/acme-pro/entries \
-H 'content-type: application/json' \
-d '{
"email": "ada@example.com",
"name": "Ada Lovelace",
"interest": "The realtime collaboration bits",
"referrer": "Twitter",
"referral_code": "C67AB5HS",
"metadata": { "company": "Acme Industries", "role": "engineer" }
}'email and name are both required. Everything else is optional: interest (free text), referrer (how they heard about you), referral_code (another entry's code — credits that entry), variant_id, recaptcha_token, and metadata — a free-form object for whatever else your form collects. There is no server-side field schema to register first; put your extra inputs in metadata and they come back on the entry.
Response:
{
"id": "150489b4-558d-4c2a-93b2-dec1b657558c",
"email": "ada@example.com",
"name": "Ada Lovelace",
"position": 147,
"status": "approved",
"referral_code": "C67AB5HS",
"created_at": "2026-07-28T20:09:16.626Z",
"effective_position": 147
}referral_code is this signup's code to share. Waitlist doesn't host a page, so build the share link yourself against your own form URL — https://acme.com/join?ref=C67AB5HS — and pass the value straight back as referral_code on the next submission.
status is approved on a waitlist that doesn't require manual review, otherwise pending. position is the immutable join order — it is never renumbered, including when earlier entries are deleted. Render position; effective_position is a forward-compatible mirror of it.
3
Errors to render
400— validation failed (missingname, malformed email), a disposable-email domain, or a rejected reCAPTCHA token. The body'smessageis an array of per-field messages you can map onto inputs.404— waitlist not found or not active.409 This email is already on this waitlist— the friendliest handling is “you're already in”, since positions are never disclosed anonymously for an existing email.422 Waitlist is not accepting entries— the list is paused, closed or full.429— per-IP, per-waitlist rate limit. The message carries the retry window in seconds.
4
Leaderboard
Anonymous top-referrer ranking — the “skip the line” mechanic from chapter 5. Same access rule as the public info endpoint (active waitlists only).
curl 'https://api.waitlist.productcraft.co/v1/waitlists/acme/acme-pro/leaderboard?limit=10'[
{ "position": 1, "referral_count": 42, "masked_email": "a***@example.com" },
{ "position": 2, "referral_count": 31, "masked_email": "b***@acme.org" }
]A bare array, not an envelope. Emails are masked and only entries with at least one referral appear. limit is 1–50, default 50. Unmasked referrer data is admin-only, on GET .../analytics.