Features Pricing
Start My Free Trial

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
x
Keep Your API Key Secret Never expose your API key in client-side code. Use it only in server-to-server requests.

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.

POST /public/waitlists/{waitlist_id}/signups

Add a person to a waitlist. If the email already exists, the existing signup data is returned without creating a duplicate.

FieldTypeRequiredDescription
emailstringYesEmail address
first_namestringNoFirst name
last_namestringNoLast name
phonestringNoPhone number
referred_by_tokenstringNoReferral token of the person who referred them
utm_sourcestringNoUTM source
utm_mediumstringNoUTM medium
utm_campaignstringNoUTM campaign
utm_termstringNoUTM term
utm_contentstringNoUTM content
custom_answersobjectNoAnswers 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 /public/waitlists/{waitlist_id}/signups/{email}

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"
}
POST /public/waitlists/{waitlist_id}/signups/{email}/verify

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 /public/waitlists/{waitlist_id}/config

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 /public/waitlists/{waitlist_id}/count

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 /public/waitlists/{waitlist_id}/leaderboard

Get the top referrers for a waitlist. Fully anonymized — returns only rank and referral count, never names or emails.

ParameterTypeDefaultDescription
limitinteger10Results to return (1-50)

Response (200):

{
  "waitlist_id": "a1b2c3d4",
  "leaderboard": [
    {
      "rank": 1,
      "referral_count": 12
    }
  ]
}
POST /public/waitlists/{waitlist_id}/unsubscribe

Unsubscribe a signup from marketing emails (blasts). Requires an HMAC-SHA256 token for verification.

Request Body:

{ "email": "jane@example.com", "token": "hmac_token_here" }
POST /public/waitlists/{waitlist_id}/delete-my-data

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" }
POST /public/waitlists/{waitlist_id}/signups/{email}/actions/{action_id} Pro

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"
}
GET /public/waitlists/{waitlist_id}/signups/{email}/actions Pro

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 /public/waitlists/{waitlist_id}/page

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

POST /auth/register

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"
}
!
The API key is only returned in full at registration and when regenerated. Store it securely — it cannot be retrieved again.
GET /auth/me

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"
}
DELETE /auth/me

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

POST /auth/regenerate-api-key

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..." }
POST /auth/verify-email

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" }
POST /auth/resend-verification

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

POST /billing/checkout

Create a Stripe Checkout session to subscribe to a plan. Redirect the user to the returned URL.

FieldTypeValues
tierstringbasic, advanced, pro
intervalstringmonthly, yearly

Response (200):

{ "url": "https://checkout.stripe.com/c/pay/..." }
POST /billing/portal

Create a Stripe Customer Portal session for plan changes, payment method updates, and invoice history.

Response (200):

{ "url": "https://billing.stripe.com/p/session/..." }
POST /billing/preview

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"
}
POST /billing/start-trial

Start a 7-day free trial of the Pro plan. Can only be used once per account.

POST /billing/cancel

Cancel the current subscription. The subscription remains active until the end of the billing period.

GET /billing/info

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).

POST /waitlists

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"
}
GET /waitlists

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 /waitlists/{waitlist_id}

Get a single waitlist with full details including all settings.

PATCH /waitlists/{waitlist_id}

Partial update — only provided fields are changed. Supports updating name and any settings fields.

Request Body:

{
  "name": "Updated Name",
  "settings": { "headline": "New Headline" }
}
DELETE /waitlists/{waitlist_id}

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.

GET /waitlists/{waitlist_id}/signups

List all signups for a waitlist with pagination. Includes email, name, phone, position, referral count, UTM data, custom answers, and verification status.

ParameterTypeDefaultDescription
limitinteger50Results per page (1-100)
last_keystringPagination cursor from previous response
GET /waitlists/{waitlist_id}/stats

Get waitlist statistics including total signups and referral stats.

DELETE /waitlists/{waitlist_id}/signups/{email}

Remove a signup from the waitlist. Position numbers for remaining signups stay stable.

Response: 204 No Content

PATCH /waitlists/{waitlist_id}/signups/{email}/position Advanced+

Move a signup to a specific position in the waitlist.

Request Body:

{ "position": 1 }
POST /waitlists/{waitlist_id}/signups/bulk-delete

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 }
POST /waitlists/{waitlist_id}/signups/bulk-move Advanced+

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 }
POST /waitlists/{waitlist_id}/import Basic+

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.

POST /waitlists/{waitlist_id}/codes Advanced+

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" }
GET /waitlists/{waitlist_id}/codes Advanced+

List all invitation codes for a waitlist with usage counts.

DELETE /waitlists/{waitlist_id}/codes/{code} Advanced+

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.

POST /waitlists/{waitlist_id}/variants Pro

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"
}
GET /waitlists/{waitlist_id}/variants Pro

List all variants with view/signup statistics and conversion rates.

PATCH /waitlists/{waitlist_id}/variants/{variant_id} Pro

Update a variant's headline, subheadline, hero image, or weight.

DELETE /waitlists/{waitlist_id}/variants/{variant_id} Pro

Delete an A/B test variant.

Response: 204 No Content

Waitlist Presets

Pre-built waitlist configurations for common use cases. Requires Pro tier.

GET /templates/presets Pro

List available presets: viral-giveaway, product-launch, beta-access, referral-contest.

POST /waitlists/from-preset Pro

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

GET /waitlists/{waitlist_id}/export Basic+

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 /waitlists/{waitlist_id}/analytics Pro

Get daily traffic analytics — page views and signups over time.

ParameterTypeDefaultDescription
rangestring30dTime range: 7d, 30d, 90d
GET /waitlists/{waitlist_id}/analytics/utm Pro

Get UTM attribution data — breakdown of signups by source, medium, and campaign.

Email Blast Endpoints

POST /waitlists/{waitlist_id}/blasts Pro

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."
}
GET /waitlists/{waitlist_id}/blasts Pro

List all email blasts sent to a waitlist with delivery stats.

Template Endpoints

POST /templates Pro

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>"
}
GET /templates Pro

List all email templates.

GET /templates/{template_id} Pro

Get a single template with full HTML body.

PATCH /templates/{template_id} Pro

Update a template. Partial update — only provided fields are changed.

DELETE /templates/{template_id} Pro

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.

POST /domains/verify Pro

Start domain verification. Returns DNS records (DKIM, SPF) to add to your domain.

Request Body:

{ "domain": "mail.yourdomain.com" }
GET /domains Pro

List all verified and pending domains.

GET /domains/{domain}/status Pro

Check the verification status and get the required DNS records.

DELETE /domains/{domain} Pro

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.

POST /team/invite Pro

Invite a team member by email.

Request Body:

{ "email": "teammate@example.com" }
GET /team Pro

List all team members with their email, role, and when they were added.

DELETE /team/{email} Pro

Remove a team member. They immediately lose access to your waitlists.

Response: 204 No Content

Webhook Events

POST /webhooks/stripe

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 created
  • invoice.paid — subscription renewed or upgraded
  • invoice.payment_failed — payment failed
  • customer.subscription.updated — subscription plan changed via Customer Portal
  • customer.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"
}
StatusMeaning
400Bad request — missing or invalid parameters
401Unauthorized — missing or invalid authentication
403Forbidden — insufficient subscription tier or wrong auth method
404Not found — resource does not exist or you don't own it
409Conflict — resource already exists (duplicate email, etc.)
500Server 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.