# Waitlist API

The Waitlist area publishes 7 REST endpoints. Paths are relative to `https://api.lessly.com`; see [Authentication](/reference/openapi#authentication) for the required header.

### `GET` `/waitlist/config`

Get the waitlist configuration (consent text, policy url, policy label, policy version, sender domain, invite template, invite subject) for the current product. Returns null if not configured yet.

MCP tool: `waitlist_config_get`

**Parameters**

No parameters.

### `PUT` `/waitlist/config`

Create or replace the waitlist configuration for the current product (full-document upsert). consent_text is plain text (may contain a {policy} placeholder); policy_url/policy_label render a structured policy link; invite_subject is the invite email subject line. Stored as-is; content correctness is the controller responsibility.

MCP tool: `waitlist_config_upsert`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| policyVersion | body | string | • | Policy version label, controller-owned free string. Stored as-is, not validated for monotonicity. |
| consentText | body | string | • | Plain-text consent statement shown at signup. May contain a {policy} placeholder marking the policy link. Stored as-is (no HTML sanitization). |
| policyUrl | body | string | — | Policy link URL (http/https/mailto). Rendered as a structured link in the form. Null to clear. |
| policyLabel | body | string | — | Visible text for the policy link. Falls back to the URL itself if absent. |
| senderDomain | body | string | — | Sender domain for invite emails (bare hostname). Verification happens at send time, not here. Null to clear. |
| senderName | body | string | — | Controller display name shown as the invite From name and in the default email body (A7§64 controller identity). Stored as-is. Null to clear (falls back to a generic sender name). |
| inviteTemplate | body | string | — | Invite email template ({{vars}}). Stored as-is; rendered/validated at send time. Null to clear. |
| inviteSubject | body | string | — | Invite email subject line. Plain text, stored as-is; CR/LF stripped at send time. Null to clear (falls back to a default subject). |
| siteBaseUrl | body | string | — | Per-product public base URL of the tenant site; invite (/signup) and opt-out (/withdraw) links are built from it. http/https only, path allowed, trailing slash stripped. Stored as-is (DP-013); not content-verified. Null to clear. |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| createdAt | string | • | — |
| policyUrl | string | • | — |
| productId | string | • | — |
| updatedAt | string | • | — |
| senderName | string | • | — |
| consentText | string | • | — |
| policyLabel | string | • | — |
| siteBaseUrl | string | • | — |
| senderDomain | string | • | — |
| inviteSubject | string | • | — |
| policyVersion | string | • | — |
| inviteTemplate | string | • | — |

### `GET` `/waitlist/embed`

Returns the waitlist embed <script> tag for the current product.

MCP tool: `waitlist_embed_get`

**Parameters**

No parameters.

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| product_id | string | • | — |
| script_tag | string | • | — |
| public_edge_url | string | • | — |

### `POST` `/waitlist/erasure`

GDPR Art.17 erasure by email within the current product. Hard-deletes the signup and its invite tokens, reduces the consent-ledger rows to the post-erasure minimum (raw email replaced by a keyed hash), and adds the email to the do-not-contact suppression list. Idempotent. Returns counts of what was affected; never returns PII.

MCP tool: `waitlist_signups_erase`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| email | body | string | • | Email address to erase (GDPR Art.17) within the current product. Normalized (trimmed + lowercased) server-side. The operation is idempotent. |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| suppressed | boolean | • | — |
| deletedTokens | number | • | — |
| deletedSignups | number | • | — |
| reducedConsents | number | • | — |

### `POST` `/waitlist/invites/send`

Send invite emails to waitlist signups. Provide an explicit email list and/or a filter; set force=true to resend when a live invite already exists.

MCP tool: `waitlist_invites_send`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| emails | body | array | — | Explicit recipient emails (e.g. from waitlist_signups_list). Combined with filter if both given. |
| filter | body | object | — | Selection filter over signups; resolved server-side with pagination. |
| force | body | boolean | — | Resend even when a live invite token already exists (mints a fresh token). |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| sent | array | • | — |
| failed | array | • | — |
| skipped | array | • | — |

### `GET` `/waitlist/signups`

List waitlist signups for the current product. Supports status filtering, keyset pagination, and field selection.

MCP tool: `waitlist_signups_list`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| status | query | string | — | Filter by status. A single value or an array. Omit to return all statuses. |
| fields | query | array | — | Fields to include per row. Default ["status","created_at"]; id is always included. email and ip_hash are PII and audited when requested. |
| cursor | query | string | — | Keyset cursor from a previous response next_cursor. Omit to start from the beginning. |
| limit | query | integer | — | Page size, 1–200. Default 50. |
| order | query | string | — | Sort by created_at. newest = most recent first. Default newest. |
| q | query | string | — | Prefix search on email (case-insensitive). Returns signups whose email starts with this value. Audited as PII access (DP-009). |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| items | array | • | — |
| has_more | boolean | • | — |
| next_cursor | string | • | — |

### `GET` `/waitlist/signups/stats`

Funnel counts (signup, invited, registered) for the current product over a time window, plus non_monotonic. Each stage is counted by its own transition timestamp, so a window can be non-monotonic (registered > signup) — transition events within the period, not a fixed cohort.

MCP tool: `waitlist_signups_funnel`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| period | query | string | — | Time window. "7d"/"30d"/"90d" = trailing N days ending now; "all" = all time. Default "30d". |

**Response**

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| to | string | • | — |
| from | string | • | — |
| period | string | • | — |
| signup | number | • | — |
| invited | number | • | — |
| registered | number | • | — |
| non_monotonic | boolean | • | — |
