Features Pricing
Start My Free Trial

Error Handling

How the MakeEmWait API reports errors, and how to handle them in your code.

Error Response Format

Every error response from the API includes three fields:

{
  "error": "This waitlist is currently closed for new signups",
  "error_code": "WAITLIST_CLOSED",
  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
Field Description
error Human-readable message suitable for displaying to end users
error_code Machine-readable string for programmatic error handling
request_id Unique API Gateway request ID for debugging and support
i
Always use error_code for programmatic handling — not the error message. Error codes are stable across API versions while messages may be refined.

Field-Level Validation Errors

When a request fails validation on multiple fields, the API returns a 400 response with a VALIDATION_ERROR error code and an additional errors array containing per-field details:

{
  "error": "A valid email address is required. Password must be at least 8 characters",
  "error_code": "VALIDATION_ERROR",
  "errors": [
    { "field": "email", "message": "A valid email address is required" },
    { "field": "password", "message": "Password must be at least 8 characters" }
  ],
  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}

The errors array contains objects with:

Field Description
field The name of the field that failed validation
message A human-readable description of what’s wrong

The top-level error field contains all field messages joined together with periods.

Which Endpoints Return Field Errors

Endpoint Fields Validated
POST /auth/register email, password
PATCH /waitlists/{id} All waitlist settings (e.g., headline, webhook_url, custom_questions)

Handling Field Errors in JavaScript

const response = await fetch(`${API_BASE}/auth/register`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ email, password }),
});

if (!response.ok) {
  const data = await response.json();

  if (data.errors && data.errors.length > 0) {
    // Show per-field errors next to their inputs
    data.errors.forEach(({ field, message }) => {
      const input = document.querySelector(`[name="${field}"]`);
      if (input) {
        const errorEl = input.parentElement.querySelector(".field-error");
        if (errorEl) errorEl.textContent = message;
      }
    });
  } else {
    // Show the general error message
    showMessage(data.error);
  }
}

Error Codes Reference

Validation Errors (400)

Error Code When It Occurs
VALIDATION_ERROR Missing or invalid request parameters (email, password, position, etc.). Consent failures also return VALIDATION_ERROR with a consent field error in the errors array.
INVALID_BODY Request body is not valid JSON
INVALID_TOKEN Verification, unsubscribe, or delete token is invalid or expired
(no DUPLICATE_EMAIL code) Duplicate email during registration returns 409 CONFLICT, not 400 DUPLICATE_EMAIL
PAYMENT_REQUIRED Stripe payment failed (card declined, insufficient funds, etc.). Can be returned at 400 (card errors) or 403 (inactive subscription).

Authentication Errors (401)

Error Code When It Occurs
AUTHENTICATION_REQUIRED No authentication provided, or the JWT/API key is invalid

Authorization Errors (403)

Error Code When It Occurs
FORBIDDEN Access denied — e.g., using API key auth where JWT is required
TIER_REQUIRED Feature requires a higher subscription tier (Basic, Advanced, or Pro)
TRIAL_EXPIRED Free trial has ended — a paid subscription is required
EMAIL_NOT_VERIFIED Account email must be verified before using authenticated endpoints
WAITLIST_CLOSED Waitlist is not accepting new signups
DOMAIN_RESTRICTED Email domain is not in the waitlist’s allowed list (returns 403), or personal emails are blocked by exclude_personal_emails (returns 400)

Resource Errors (404, 409)

Error Code When It Occurs
NOT_FOUND Waitlist, signup, template, domain, or team member does not exist
CONFLICT Resource already exists (e.g., duplicate account email)
DUPLICATE Duplicate resource detected

Rate Limiting (429)

Error Code When It Occurs
RATE_LIMITED Too many requests — wait and retry. The response includes a Retry-After header with the number of seconds to wait.

Server Errors (500, 502)

Error Code When It Occurs
EXTERNAL_SERVICE_ERROR A third-party service (Stripe, Resend) failed — retry the request
INTERNAL_ERROR Unexpected server error

Handling Errors in Code

JavaScript / Fetch

async function joinWaitlist(waitlistId, email) {
  const response = await fetch(
    `${API_BASE}/public/waitlists/${waitlistId}/signups`,
    {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email }),
    }
  );

  if (!response.ok) {
    const { error, error_code } = await response.json();

    switch (error_code) {
      case "WAITLIST_CLOSED":
        return { success: false, message: "This waitlist is no longer accepting signups." };
      case "DOMAIN_RESTRICTED":
        return { success: false, message: "Please use a work email address." };
      case "VALIDATION_ERROR":
        return { success: false, message: error }; // Show the specific validation message (includes consent errors)
      default:
        return { success: false, message: "Something went wrong. Please try again." };
    }
  }

  return { success: true, data: await response.json() };
}

Python / Requests

import requests

def join_waitlist(waitlist_id, email):
    response = requests.post(
        f"{API_BASE}/public/waitlists/{waitlist_id}/signups",
        json={"email": email},
    )

    if not response.ok:
        data = response.json()
        error_code = data.get("error_code")

        if error_code == "WAITLIST_CLOSED":
            raise WaitlistClosedError()
        elif error_code == "DOMAIN_RESTRICTED":
            raise DomainRestrictedError()
        elif error_code == "VALIDATION_ERROR":
            raise ValidationError(data["error"])
        else:
            raise ApiError(data["error"])

    return response.json()

cURL

curl -s -X POST "https://api.makeemwait.com/v1/public/waitlists/abc123/signups" \
  -H "Content-Type: application/json" \
  -d '{"email": "jane@example.com"}' | jq .

# On error, the response includes error_code:
# {
#   "error": "Email is required",
#   "error_code": "VALIDATION_ERROR",
#   "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
# }

Tier Gating

Endpoints and settings that require a specific subscription tier return TIER_REQUIRED when accessed on a lower tier:

{
  "error": "This feature requires the Advanced plan or higher",
  "error_code": "TIER_REQUIRED",
  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}

The tier hierarchy is: free_trial < basic < advanced < pro

Feature Minimum Tier
CSV Export, CSV Import Basic
Hide branding, hide position, custom redirect URL, domain restrictions, move position Advanced
Analytics, email templates, email blasts, custom domains, webhooks, team management Pro

Related error codes for subscription issues:

Error Code Meaning
TIER_REQUIRED Specific feature needs a higher plan
TRIAL_EXPIRED Free trial ended — must subscribe
FORBIDDEN Subscription is inactive or canceled

External Service Errors

The API integrates with Stripe (billing) and Resend (email). When these services fail, the API returns EXTERNAL_SERVICE_ERROR with a generic message instead of leaking internal details:

{
  "error": "Unable to create checkout session. Please try again.",
  "error_code": "EXTERNAL_SERVICE_ERROR",
  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}

Exception: Stripe card errors (declined card, insufficient funds) return PAYMENT_REQUIRED with the Stripe-provided user-facing message, since these are designed for end users:

{
  "error": "Your card was declined.",
  "error_code": "PAYMENT_REQUIRED",
  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}

Request ID for Debugging

Every error includes a request_id that corresponds to the API Gateway request ID. When contacting support or investigating issues, include this ID for quick correlation with server logs.


  • Rate Limits — rate limit numbers and retry strategies
  • API Reference — complete endpoint documentation with error codes per endpoint
  • Security — authentication, bot protection, and other security measures