API Reference
Complete REST API reference for creating waitlists, managing signups, and building integrations.
The MakeEmWait API lets you programmatically create waitlists, manage signups, send email blasts, and build custom integrations. All endpoints return JSON and use standard HTTP status codes.
Interactive API Explorer Try out endpoints directly with the Swagger UI documentation →Base URL
https://6jt3tqoac6.execute-api.us-west-2.amazonaws.com/v1
All endpoint paths below are relative to this base URL.
Authentication
The API supports two authentication methods. Both work interchangeably on all authenticated endpoints.
Bearer Token (JWT)
Send your ID token in the Authorization header. Tokens are obtained by logging in through the authentication SDK.
Authorization: Bearer YOUR_ID_TOKEN
API Key
Send your API key in the x-api-key header. Your key is generated at registration and can be regenerated from your profile.
x-api-key: mew_live_YOUR_API_KEY
Public Endpoints
These endpoints require no authentication and are used by the signup form and embed widget. All /public/* routes return Access-Control-Allow-Origin: * for cross-origin requests.
Add a person to a waitlist. If the email already exists, the existing signup data is returned without creating a duplicate.
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Email address |
first_name | string | No | First name |
last_name | string | No | Last name |
phone | string | No | Phone number |
referred_by_token | string | No | Referral token of the person who referred them |
utm_source | string | No | UTM source |
utm_medium | string | No | UTM medium |
utm_campaign | string | No | UTM campaign |
utm_term | string | No | UTM term |
utm_content | string | No | UTM content |
custom_answers | object | No | Answers to custom questions (keyed by question ID) |
Response (201):
{
"email": "jane@example.com",
"first_name": "Jane",
"last_name": "Doe",
"referral_token": "f7a3b2c1",
"position": 42,
"referral_count": 0,
"total_signups": 150,
"created_at": "2025-03-15T10:30:00.000Z"
}
Get a signup's current position, referral count, and details. If the waitlist owner has "Hide position" enabled (Advanced+), the position and total_signups fields are omitted.
Response (200):
{
"email": "jane@example.com",
"first_name": "Jane",
"referral_token": "f7a3b2c1",
"position": 42,
"referral_count": 5,
"total_signups": 150,
"created_at": "2025-03-15T10:30:00.000Z"
}
Verify a signup's email address using the verification token sent by email. Also supports GET with ?token=TOKEN as a query parameter for clickable email links.
Request Body:
{ "token": "a3b2c1d4e5f67890..." }
Response (200):
{ "verified": true, "message": "Email verified successfully" }
Get the public configuration needed to render a signup form — name, headline, field visibility, custom questions, and current signup count. Also increments the daily page view counter for analytics.
Response (200):
{
"waitlist_id": "a1b2c3d4",
"name": "Beta Access",
"headline": "Join the Beta",
"subheadline": "Be the first to try our new product",
"hero_image_url": "https://example.com/hero.png",
"name_field": "optional",
"phone_field": "hidden",
"signup_count": 150,
"custom_questions": [
{
"id": "q1",
"label": "What's your role?",
"type": "dropdown",
"required": true,
"options": ["Founder", "Developer", "Designer", "Other"]
}
],
"hide_branding": false,
"hide_position": false
}
Get the total number of signups on a waitlist. Returns null for count if the waitlist has 10 or fewer signups or if the owner has "Hide position" enabled.
Response (200):
{ "waitlist_id": "a1b2c3d4", "count": 150 }
Get the top referrers for a waitlist. Fully anonymized — returns only rank and referral count, never names or emails.
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 10 | Results to return (1-50) |
Response (200):
{
"waitlist_id": "a1b2c3d4",
"leaderboard": [
{
"rank": 1,
"referral_count": 12
}
]
}
Unsubscribe a signup from marketing emails (blasts). Requires an HMAC-SHA256 token for verification.
Request Body:
{ "email": "jane@example.com", "token": "hmac_token_here" }
Permanently delete a signup's data from the waitlist. Requires a separate HMAC-SHA256 delete token (different from the unsubscribe token).
Request Body:
{ "email": "jane@example.com", "token": "delete_token_here" }
Complete a reward action. Each action can only be completed once per user. Boosts the signup's position by the action's points value. Requires the signup's referral token as ?token= query parameter.
Response (200):
{
"action_id": "follow-twitter",
"points_earned": 3,
"new_position": 12,
"completed_at": "2025-03-15T10:30:00.000Z"
}
List all reward actions a signup has completed. Requires the signup's referral token as ?token= query parameter.
Response (200):
{
"completed_actions": [
{ "action_id": "follow-twitter", "action_label": "Follow us on X", "points": 3, "completed_at": "2025-03-15T10:30:00.000Z" }
]
}
Get a hosted landing page with Open Graph meta tags and an embedded signup widget. Returns text/html, not JSON. Designed for social media sharing — crawlers see proper og:title, og:description, and og:image tags.
Auth Endpoints
Create a new account. Generates an API key and starts a 7-day free trial. Password must be at least 8 characters.
Request Body:
{
"email": "jane@example.com",
"password": "securePassword123"
}
Response (201):
{
"email": "jane@example.com",
"api_key": "mew_live_abc123...",
"subscription_tier": "pro",
"subscription_status": "trialing"
}
Get the authenticated user's profile including subscription status.
Response (200):
{
"email": "jane@example.com",
"has_api_key": true,
"subscription_tier": "basic",
"subscription_status": "active",
"trial_ends_at": "2025-03-22T10:30:00.000Z",
"stripe_customer_id": "cus_abc123"
}
Permanently delete your account and all associated data — waitlists, signups, templates, domains, team members, and analytics. Cancels any active Stripe subscription. This action cannot be undone.
Response: 204 No Content
Generate a new API key and invalidate the old one. Requires Bearer token authentication — cannot be called with an API key (returns 403).
Response (200):
{ "api_key": "mew_live_new_key_here..." }
Verify the user's email address using a token sent via email. No authentication required — the 256-bit token is proof of ownership. Accepts email and token from the request body or query parameters.
Request Body:
{ "email": "jane@example.com", "token": "a3b2c1d4e5f67890..." }
Response (200):
{ "verified": true, "message": "Email verified successfully" }
Resend the email verification link. Rate limited to one link per 60 seconds, maximum 10 resends total.
Response (200):
{ "message": "Verification link sent" }
Billing Endpoints
Create a Stripe Checkout session to subscribe to a plan. Redirect the user to the returned URL.
| Field | Type | Values |
|---|---|---|
tier | string | basic, advanced, pro |
interval | string | monthly, yearly |
Response (200):
{ "url": "https://checkout.stripe.com/c/pay/..." }
Create a Stripe Customer Portal session for plan changes, payment method updates, and invoice history.
Response (200):
{ "url": "https://billing.stripe.com/p/session/..." }
Preview what a plan change would cost, including proration details. Used by the pricing page to show a confirmation dialog before switching plans.
Request Body:
{
"tier": "pro",
"interval": "monthly"
}
Start a 7-day free trial of the Pro plan. Can only be used once per account.
Cancel the current subscription. The subscription remains active until the end of the billing period.
Get the current subscription details including tier, status, billing interval, and renewal date.
Waitlist Endpoints
All waitlist endpoints require authentication (Bearer token or API key).
Create a new waitlist with optional settings for the signup form.
Request Body:
{
"name": "Beta Access",
"settings": {
"headline": "Join the Beta",
"subheadline": "Be the first to try our new product",
"hero_image_url": "https://example.com/hero.png",
"name_field": "optional",
"phone_field": "hidden",
"hide_branding": false,
"hide_position": false,
"custom_questions": [],
"webhook_url": "https://your-server.com/webhook",
"slack_webhook_url": "https://hooks.slack.com/...",
"discord_webhook_url": "https://discord.com/api/webhooks/...",
"allowed_email_domains": [],
"exclude_personal_emails": false
}
}
Only name is required. All settings fields are optional.
Response (201):
{
"waitlist_id": "a1b2c3d4",
"name": "Beta Access",
"signup_count": 0,
"created_at": "2025-03-15T10:30:00.000Z"
}
List all waitlists owned by the authenticated user.
Response (200):
{
"waitlists": [
{
"waitlist_id": "a1b2c3d4",
"name": "Beta Access",
"signup_count": 150,
"created_at": "2025-03-15T10:30:00.000Z"
}
]
}
Get a single waitlist with full details including all settings.
Partial update — only provided fields are changed. Supports updating name and any settings fields.
Request Body:
{
"name": "Updated Name",
"settings": { "headline": "New Headline" }
}
Delete a waitlist and its metadata. Individual signup records are retained but become inaccessible.
Response: 204 No Content
Admin Endpoints
Manage signups within your waitlists.
List all signups for a waitlist with pagination. Includes email, name, phone, position, referral count, UTM data, custom answers, and verification status.
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 50 | Results per page (1-100) |
last_key | string | — | Pagination cursor from previous response |
Get waitlist statistics including total signups and referral stats.
Remove a signup from the waitlist. Position numbers for remaining signups stay stable.
Response: 204 No Content
Move a signup to a specific position in the waitlist.
Request Body:
{ "position": 1 }
Delete multiple signups in a single request. Maximum 100 emails per call.
Request Body:
{ "emails": ["alice@example.com", "bob@example.com"] }
Response (200):
{ "deleted": 2 }
Update positions for multiple signups in a single request. Maximum 100 updates per call.
Request Body:
{ "updates": [{ "email": "alice@example.com", "position": 1 }, { "email": "bob@example.com", "position": 2 }] }
Response (200):
{ "moved": 2 }
Import up to 10,000 signups from a JSON array. Deduplicates against existing signups.
Request Body:
{
"signups": [
{ "email": "alice@example.com", "first_name": "Alice" },
{ "email": "bob@example.com", "first_name": "Bob", "phone": "+1234567890" }
]
}
Response (200):
{ "imported": 2, "skipped": 0, "errors": [] }
Invitation Codes
Manage invitation codes for gated waitlists. Requires Advanced tier or higher.
Create an invitation code. If no code is provided, a random 12-character hex code is generated. Codes must be 3–50 characters, alphanumeric with hyphens and underscores.
Request Body:
{ "code": "EARLY-ACCESS", "max_uses": 100 }
Response (201):
{ "code": "EARLY-ACCESS", "max_uses": 100, "uses": 0, "created_at": "2025-03-15T10:30:00.000Z" }
List all invitation codes for a waitlist with usage counts.
Delete an invitation code.
Response: 204 No Content
A/B Testing Variants
Test different headlines, subheadlines, and hero images on your signup forms. Requires Pro tier.
Create a new A/B test variant. The weight controls how often this variant is shown relative to others.
Request Body:
{
"headline": "Join the Revolution",
"subheadline": "Early adopters get exclusive perks",
"hero_image_url": "https://example.com/hero-b.png",
"weight": 50
}
Response (201):
{
"variant_id": "a1b2c3d4-...",
"headline": "Join the Revolution",
"subheadline": "Early adopters get exclusive perks",
"hero_image_url": "https://example.com/hero-b.png",
"weight": 50,
"views": 0,
"signups": 0,
"created_at": "2025-03-15T10:30:00.000Z"
}
List all variants with view/signup statistics and conversion rates.
Update a variant's headline, subheadline, hero image, or weight.
Delete an A/B test variant.
Response: 204 No Content
Waitlist Presets
Pre-built waitlist configurations for common use cases. Requires Pro tier.
List available presets: viral-giveaway, product-launch, beta-access, referral-contest.
Create a new waitlist from a preset. Pre-configures settings and creates associated email templates.
Request Body:
{ "preset_id": "viral-giveaway", "name": "Summer Giveaway" }
Response (201):
{
"waitlist_id": "a1b2c3d4-...",
"name": "Summer Giveaway",
"preset_id": "viral-giveaway",
"preset_name": "Viral Giveaway",
"templates_created": 2,
"created_at": "2025-03-15T10:30:00.000Z"
}
Export
Download all signups as a CSV file. Includes email, name, phone, position, referral count, UTM data, verification status, and timestamps. Supports ?token=YOUR_JWT query parameter as an alternative to the Authorization header (useful for browser download links).
Analytics Endpoints
Get daily traffic analytics — page views and signups over time.
| Parameter | Type | Default | Description |
|---|---|---|---|
range | string | 30d | Time range: 7d, 30d, 90d |
Get UTM attribution data — breakdown of signups by source, medium, and campaign.
Email Blast Endpoints
Send an email blast to all signups on a waitlist.
Request Body:
{
"subject": "We're launching next week!",
"html_body": "<h1>Big news!</h1><p>We're launching next Tuesday.</p>",
"text_body": "Big news! We're launching next Tuesday."
}
List all email blasts sent to a waitlist with delivery stats.
Template Endpoints
Create a reusable email template. Supports HTML with placeholder variables like {{first_name}}, {{position}}, and {{waitlist_name}}.
Request Body:
{
"template_type": "signup_confirmation",
"name": "Welcome Email",
"subject_template": "You're In, !",
"html_body_template": "<h1>Welcome!</h1><p>Your position is #.</p>"
}
List all email templates.
Get a single template with full HTML body.
Update a template. Partial update — only provided fields are changed.
Delete an email template.
Response: 204 No Content
Domain Endpoints
Verify custom email domains so blasts come from your own address instead of the default sending domain.
Start domain verification. Returns DNS records (DKIM, SPF) to add to your domain.
Request Body:
{ "domain": "mail.yourdomain.com" }
List all verified and pending domains.
Check the verification status and get the required DNS records.
Remove a verified domain.
Response: 204 No Content
Team Endpoints
Invite team members to manage your waitlists. Team members get full access to all waitlists owned by the account.
Invite a team member by email.
Request Body:
{ "email": "teammate@example.com" }
List all team members with their email, role, and when they were added.
Remove a team member. They immediately lose access to your waitlists.
Response: 204 No Content
Webhook Events
Receives Stripe webhook events for subscription lifecycle management. This endpoint is called by Stripe, not by your application.
Events handled:
checkout.session.completed— new subscription createdinvoice.paid— subscription renewed or upgradedinvoice.payment_failed— payment failedcustomer.subscription.updated— subscription plan changed via Customer Portalcustomer.subscription.deleted— subscription cancelled
Error Responses
All errors return a JSON object with error, error_code, and request_id fields:
{
"error": "Waitlist not found",
"error_code": "NOT_FOUND",
"request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
| Status | Meaning |
|---|---|
400 | Bad request — missing or invalid parameters |
401 | Unauthorized — missing or invalid authentication |
403 | Forbidden — insufficient subscription tier or wrong auth method |
404 | Not found — resource does not exist or you don't own it |
409 | Conflict — resource already exists (duplicate email, etc.) |
500 | Server error |
Tier Gating (403)
When a feature requires a higher tier, the error tells you what's needed:
{ "error": "This feature requires an Advanced plan or higher. Current tier: basic" }
Tier hierarchy: free_trial < basic < advanced < pro
CORS
All /public/* endpoints return Access-Control-Allow-Origin: * for cross-origin requests from the embed widget. Authenticated endpoints return CORS headers matching the request origin. All routes respond to OPTIONS preflight requests.