Skip to content

API Reference

Base URL

http://localhost:3000          # Development
https://your-domain.com        # Production

Endpoints

MethodEndpointDescription
POST/api/signupEmail signup
POST/api/signup/extendedSignup with name, source, tags
POST/api/signup/bulkBulk signup (up to 100)
POST/api/signup/formHTML form submission
GET/api/statsSignup statistics
GET/api/healthHealth check
GET/api/configPublic configuration
GET/metricsPrometheus metrics
GET/Built-in HTML signup form
GET/embed.jsEmbeddable JavaScript widget

POST /api/signup

Basic email signup.

Request:

json
{
  "email": "user@example.com",
  "sheetTab": "Sheet1"
}
FieldTypeRequiredDescription
emailstringYesValid email address
sheetTabstringNoTarget sheet tab (default: DEFAULT_SHEET_TAB)
sitestringNoSite name for multi-site support
metadataobjectNoArbitrary key-value metadata
turnstileTokenstringNoRequired if Turnstile is configured

Success (200):

json
{
  "success": true,
  "statusCode": 200,
  "message": "Successfully signed up!"
}

Duplicate (409):

json
{
  "success": false,
  "statusCode": 409,
  "error": "Email already registered"
}

Validation error (400):

json
{
  "success": false,
  "statusCode": 400,
  "error": "Validation failed",
  "details": ["email: Invalid email format"]
}
bash
curl -X POST http://localhost:3000/api/signup \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com", "sheetTab": "Sheet1"}'

POST /api/signup/extended

Signup with additional fields.

Request:

json
{
  "email": "user@example.com",
  "name": "John Doe",
  "source": "website",
  "tags": ["newsletter", "beta"],
  "sheetTab": "Newsletter"
}
FieldTypeRequiredDescription
emailstringYesValid email address
namestringNoUser's name
sourcestringNoSignup source (default: "api")
tagsstring[]NoTags (default: [], max 50)
sheetTabstringNoTarget sheet tab
sitestringNoSite name for multi-site support
metadataobjectNoArbitrary key-value metadata
turnstileTokenstringNoRequired if Turnstile is configured

Success (200):

json
{
  "success": true,
  "statusCode": 200,
  "message": "Successfully signed up!"
}
bash
curl -X POST http://localhost:3000/api/signup/extended \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com", "name": "John Doe", "source": "website", "tags": ["newsletter"], "sheetTab": "Newsletter"}'

POST /api/signup/bulk

Bulk signup for multiple emails (1-100 per request).

Request:

json
{
  "turnstileToken": "token-if-turnstile-is-enabled",
  "signups": [
    {"email": "user1@example.com"},
    {"email": "user2@example.com"}
  ]
}
FieldTypeRequiredDescription
signupsarrayYesArray of signup objects (max 100)
turnstileTokenstringNoRequired at the request level if Turnstile is configured

Each signup object accepts the same fields as /api/signup except turnstileToken.

Success (200):

json
{
  "success": true,
  "statusCode": 200,
  "message": "Processed 2 signups",
  "data": {
    "success": 2,
    "failed": 0,
    "duplicates": 0,
    "errors": []
  }
}

Partial success / duplicates (207):

json
{
  "success": false,
  "statusCode": 207,
  "message": "Processed signups (1 created, 1 duplicates, 1 failed)",
  "data": {
    "success": 1,
    "failed": 1,
    "duplicates": 1,
    "errors": ["bad@example: Error: Failed to check existing signups"]
  }
}
bash
curl -X POST http://localhost:3000/api/signup/bulk \
  -H "Content-Type: application/json" \
  -d '{"turnstileToken": "token-if-needed", "signups": [{"email": "user1@example.com"}, {"email": "user2@example.com"}]}'

POST /api/signup/form

HTML form submission endpoint. Accepts application/x-www-form-urlencoded or multipart/form-data.

FieldTypeRequiredDescription
emailstringYesValid email address
namestringNoUser's name
sheetTabstringNoTarget sheet tab
sitestringNoSite name
sourcestringNoSignup source (default: "form")
tagsstringNoComma-separated tags (default: "form-submit")
turnstileTokenstringNoDirect Turnstile token
cf-turnstile-responsestringNoCloudflare's default form field name
bash
curl -X POST http://localhost:3000/api/signup/form \
  -d "email=user@example.com&name=John+Doe&sheetTab=Newsletter"

GET /api/stats

Signup statistics for a sheet tab.

ParameterTypeRequiredDescription
sheetTabstringYesSheet tab to query

Success (200):

json
{
  "success": true,
  "data": {
    "total": 150,
    "sheetTab": "Sheet1",
    "lastSignup": "2025-01-12T10:30:00.000Z"
  }
}
bash
curl "http://localhost:3000/api/stats?sheetTab=Sheet1"

GET /api/health

Success (200):

json
{"status": "ok", "timestamp": "2025-01-12T10:30:00.000Z"}

GET /api/config

Public configuration for frontend clients. Does not expose secrets.

Success (200):

json
{
  "turnstileSiteKey": "0x4AAAAAAAxxxxxxxx",
  "turnstileEnabled": true,
  "defaultSheetTab": "Sheet1",
  "sheetTabs": ["Sheet1"]
}
FieldTypeDescription
turnstileSiteKeystring | nullTurnstile site key (null if not configured)
turnstileEnabledbooleanWhether Turnstile is enabled
defaultSheetTabstringDefault sheet tab name
sheetTabsstring[]Available sheet tabs

Validation

Email

Emails are trimmed, lowercased, and validated with Zod's email validator.

  • Max 254 characters (RFC 5321)
  • Leading and trailing whitespace is removed before validation
  • Invalid or malformed addresses are rejected with Validation failed

Field Limits

FieldMax Length
email254 characters
name100 characters
source50 characters
sheetTab100 characters
tags50 items, 50 chars each
signups (bulk)100 items

Status Codes

CodeDescription
200Success
207Bulk request completed with duplicates or per-row failures
400Validation error
409Duplicate email
415Unsupported media type (form endpoint)
500Internal server error

Error Format

All errors follow this structure:

json
{
  "success": false,
  "statusCode": 400,
  "error": "Error type",
  "details": ["field: explanation"]
}

CORS

Configure allowed origins via ALLOWED_ORIGINS environment variable:

bash
ALLOWED_ORIGINS=https://yoursite.com,https://www.yoursite.com

Rate Limiting

No built-in rate limiting. Use Cloudflare's rate limiting for Workers deployments, or a reverse proxy (Nginx, Caddy) for Docker/VPS.

TypeScript Usage

typescript
import {
  signupSchema,
  extendedSignupSchema,
  bulkSignupSchema,
  type SignupInput,
  type ExtendedSignupInput,
  type BulkSignupInput,
} from './schemas/signup.js';

// Validate data
const result = signupSchema.safeParse({
  email: 'user@example.com',
  sheetTab: 'Sheet1',
});

if (result.success) {
  console.log(result.data.email);  // typed
} else {
  console.error(result.error);
}

Extending Schemas

typescript
import { z } from 'zod';

const customSchema = signupSchema.extend({
  email: z.string()
    .email()
    .refine(
      (email) => email.endsWith('@yourdomain.com'),
      'Email must be from yourdomain.com'
    ),
});

Exports

From src/schemas/signup.ts:

Schemas: signupSchema, extendedSignupSchema, bulkSignupSchema

Types: SignupInput, ExtendedSignupInput, BulkSignupInput, SheetRowData

Next Steps

Released under the MIT License.