Capture signups
Add one or more signups to a waitlist. Use this from your backend to funnel signups from your own landing page, product, or import job into Lucerna.
POST /sdk/v1/waitlist/signup
- Auth: Server key —
Authorization: Bearer <key> - Batch size: 1–100 entries per request
- Identity: each entry needs an email address, a phone number, or both
- Duplicates: an identifier already on the waitlist is skipped, never duplicated
Request body
| Field | Type | Required | Description |
|---|---|---|---|
waitlistId | string | Yes | The waitlist's short id (1–64 chars), e.g. wl_launch. |
entries | Entry[] | Yes | 1–100 signups to create. |
Entry object
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Either | The signup's email address. Required unless phone is given. |
phone | string | Either | The signup's phone number in E.164 (+15550100199). Required unless email is given. Any other format is rejected — see below. |
metadata | object | No | Free-form string→string map stored on the entry for later segmentation. Values are trimmed. Defaults to {}. |
source | string | No | Where the signup came from. Recognized values: widget, landing_page, api, import, manual. Any other value is stored under metadata.source and the entry is recorded as api. Defaults to api. |
referredBy | string | No | The referrer's email or referral identifier (≤320 chars), for referral attribution. |
Response
Returns the counts of what happened. created + skipped always equals the number of entries you submitted.
| Field | Type | Description |
|---|---|---|
success | true | Always true on a 2xx. |
created | number | Entries newly added to the waitlist. |
skipped | number | Entries whose identifier was already present. |
201 Created— at least one entry was created.200 OK— every submitted entry was already on the waitlist.
{ "success": true, "created": 2, "skipped": 1 }Email, phone, or both
An entry is identified by whichever channels you send. Supply one:
{ "email": "ada@example.com" }
{ "phone": "+15550100199" }…or both, which links the two to a single person:
{ "email": "ada@example.com", "phone": "+15550100199" }Matching runs against both identifiers. If either one is already on the waitlist, the entry is skipped rather than duplicated — and if the request carries an identifier that person did not have yet, it is attached to their existing row. Someone who signed up by phone and comes back with an address keeps one queue position, not two.
Phone numbers must be E.164 — a leading +, country code, digits, no spaces or punctuation. The API does not infer a country, because +1 (555) 010-0199 and +15550100199 would otherwise be stored as two different people. Format client-side and send the canonical form.
Lucerna does not send SMS. A phone-identified entry receives no lifecycle emails; subscribe to the signup.created and invite.sent webhooks — both carry the number — and send those messages yourself.
Example
curl -X POST https://api.uselucerna.app/sdk/v1/waitlist/signup \
-H "Authorization: Bearer $LUCERNA_SERVER_KEY" \
-H "Content-Type: application/json" \
-d '{
"waitlistId": "wl_launch",
"entries": [
{
"email": "ada@example.com",
"metadata": { "plan": "pro", "utm_source": "producthunt" },
"referredBy": "grace@example.com"
}
]
}'const res = await fetch("https://api.uselucerna.app/sdk/v1/waitlist/signup", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.LUCERNA_SERVER_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
waitlistId: "wl_launch",
entries: [
{
email: "ada@example.com",
metadata: { plan: "pro", utm_source: "producthunt" },
referredBy: "grace@example.com",
},
],
}),
});
if (!res.ok) {
const error = await res.json();
throw new Error(`${error.code}: ${error.message}`);
}
const { created, skipped } = await res.json();Errors
| Status | code | When |
|---|---|---|
400 | invalid_request | Missing/invalid field, empty entries, or more than 100 entries. |
401 | unauthorized | Missing or invalid server key. |
403 | forbidden | The waitlist is closed or rejects an email by policy. |
404 | not_found | No waitlist matches waitlistId. |
See Errors for the response shape and retry guidance.