API reference

All routes are mounted in src/index.ts. Errors are returned in a standardized shape (see Development → Error handling). CORS, request-id, and dependency injection run on every request.

Base URL (production): https://swapps-worker.swapps.workers.dev

Health & status

Method Path Purpose
GET / API name, version, timestamp
GET /health Liveness check ({ status: "OK" })

Leads — /api/leads

Method Path Auth Purpose
POST /api/leads reCAPTCHA (or X-Chat-Api-Key) Create a lead: validate, classify, persist to KV, sync to Pipedrive, route side effects
GET /api/leads/lookup X-Chat-Api-Key Look up a lead by email + report rate-limit status
GET /api/leads (none in code) List leads (paginated or ?all=true)
GET /api/leads/:id (none in code) Get a single lead by id
GET /api/leads/:id/preview Render the prototype preview/progress HTML page
GET /api/leads/:id/generate-stream SSE stream of prototype generation progress
POST /api/leads/:id/generate Trigger prototype generation (blocking create)
POST /api/leads/:id/trigger-generation Trigger prototype generation (async, returns immediately)

POST /api/leads

The checkPrototypeRateLimit middleware runs first; for prototype-eligible submissions it enforces the dual-bucket monthly quota and stashes the resolved identifiers in context.

Request (JSON, abbreviated):

{
  "personal": {
    "name": "Jane Doe",
    "email_address": "jane@example.com",
    "phone_number": "+1-555-0000",
    "company": "Acme",
    "position": "CTO"
  },
  "steps": {
    "initial_choice": "create",
    "service": "build-website",
    "prototype": "landing",
    "goal": "...",
    "industry": "...",
    "features": ["..."],
    "style": "...",
    "audience": "..."
  },
  "recaptchaToken": "...",
  "source_url": "https://swapps.com/...",
  "language": "en"
}

Behavior:

  • Every submission is classified by the LLM into lead / spam / vendor (result is always stored on the lead).
  • If SPAM_FILTER_ENABLED is not "false" and category is spam: lead saved to KV with status: "spam", Pipedrive and email skipped.
  • If VENDOR_FILTER_ENABLED is not "false" and category is vendor: lead saved with status: "vendor", a polite decline email is sent (EN/ES), Pipedrive skipped.
  • Otherwise: lead is created (KV + Pipedrive), and post-creation side effects run in the background via notifyLeadCreated.
  • Trusted chat requests (valid X-Chat-Api-Key) skip reCAPTCHA and steps validation, and for prototype-eligible leads create the V0 chat synchronously (response reflects the real outcome).

Response (200):

{
  "success": true,
  "message": "Lead created successfully. Your prototype is being generated.",
  "leadId": "uuid",
  "previewUrl": "https://.../api/leads/uuid/preview",
  "pipedrive": { "success": true, "leadId": 123, "message": "Lead also created in Pipedrive" }
}

A failed synchronous V0 creation on the chat path returns 502 with success: false.

GET /api/leads/lookup

Requires X-Chat-Api-Key. Resolves the lead via the leadByEmail: KV index and reports per-month rate-limit usage across the IP + email buckets.

Response:

{
  "exists": true,
  "name": "Jane Doe",
  "company": "Acme",
  "service": "build-website",
  "rateLimit": {
    "landing": { "used": 1, "limit": 2, "remaining": 1 },
    "application": { "used": 0, "limit": 2, "remaining": 2 },
    "inProgress": null,
    "inProgressType": null,
    "resetDate": "2026-07-01"
  }
}

GET /api/leads/:id/generate-stream (SSE)

Emits progress, complete, and error events as text/event-stream. On reconnection it resumes from existing state; on a stale/failed prior attempt it restarts. On stream error it enqueues the lead to the PrototypeQueue DO as a safety net. See Key flows.

POST /api/leads/:id/trigger-generation

Re-checks rate limits, marks the buckets in-progress, sets the lead to generating, then runs V0 creation in the background (createAsync). Returns { success, status: "started", generatedAt, previewUrl }. If a non-completed prototype remains, it is enqueued to the PrototypeQueue DO.

Chat — /api/chat

All chat routes require a valid X-Chat-Api-Key (CHAT_API_SECRET).

Method Path Purpose
POST /api/chat/stream SSE stream of an Agno sales-agent response
GET /api/chat/:sessionId/history Return the persisted conversation transcript

POST /api/chat/stream

Body: { "sessionId": string (≤128), "query": string (≤8000), "language"?: string (≤16) }.

The worker opens a WebSocket to the Agno swapps_leads agent, forwards the query (and the trusted end-user IP), and re-streams events to the browser as SSE:

  • token{ "content": "..." } incremental text
  • done{ "content": "...", "metadata": {...} }
  • error{ "message": "..." }

User and assistant messages are persisted to KV (best-effort) for history.

GET /api/chat/:sessionId/history

Returns { sessionId, messages, total }. Reads persisted KV history first; falls back to Agno's own session history. ?limit= (1–200, default 50).

Contact — /api/contact

Method Path Purpose
POST /api/contact Submit a simple contact form (name, email, subject, message) → stored in KV
GET /api/contact List submissions (paginated or ?all=true)

Analytics — /api/analytics

Method Path Purpose
POST /api/analytics/track Record an event/page view (captures CF IP + country)
GET /api/analytics/stats Aggregate stats (total events, unique pages, top pages/countries, event types)
GET /api/analytics/events List recorded events (paginated)

Support — /api/support

Method Path Purpose
POST /api/support/send Contact-us request: reCAPTCHA → classify → email (lead) / decline (vendor) / drop (spam) → Pipedrive
POST /api/support/tickets Multipart support ticket with attachments → KV + customer & support emails

POST /api/support/send

reCAPTCHA-verified. Classifies the submission; spam is stored and silently dropped, vendor gets a decline email, a genuine lead sends a contact email (BCC PIPEDRIVE_BCC) and creates a Pipedrive lead.

POST /api/support/tickets

multipart/form-data with fields fullName, email, issueType, companyName, urgency, productUrl, description, recaptchaToken, and up to 5 attachments. Generates a TICKET-YYYYMMDD-XXXXXX id, stores the ticket, and emails both the customer (confirmation) and the support team (with attachments). Returns { success, ticketId }.

Diagnostic — /api/diagnostic

Method Path Purpose
POST /api/diagnostic/analyze Analyze a URL with Google PageSpeed Insights (cached 24h)
GET /api/diagnostic/health Health check (reports whether API keys are configured)

Body: { "url": string, "strategy"?: "mobile" | "desktop" }. URL is validated (SSRF-safe) and normalized; results cached per URL+strategy.

SEO — /api/seo

Method Path Purpose
POST /api/seo/analyze Analyze a URL's SEO metrics (cached)
GET /api/seo/health Health check

Posts — /api/posts (WordPress proxy)

Method Path Auth Purpose
GET /api/posts List posts (filter, paginate, search)
GET /api/posts/search Search posts
GET /api/posts/tags List tags with counts
GET /api/posts/categories List categories
GET /api/posts/author Search authors
GET /api/posts/author/:id Author details
GET /api/posts/:id Single post with full content
GET /api/posts/cache/status Cache health/size
DELETE /api/posts/cache X-Cache-Api-Key Purge all posts_cache: entries
POST /api/posts/cache/clear X-Cache-Api-Key Purge all posts_cache: entries

Query params are mapped through to the WordPress REST API. Supported on /api/posts include: page, per_page, offset, search, author, author_exclude, before, after, exclude, include, order, orderby, slug, status, categories, categories_exclude, tags, tags_exclude, sticky, lang, tag, category. Cache can be bypassed with ?cache=false.

# 9 recent posts
GET /api/posts?per_page=9&orderby=date&order=desc
# Search
GET /api/posts?search=javascript&per_page=5
# Top tags
GET /api/posts/tags?per_page=6&orderby=count&order=desc

Warning

swapps.com is fronted by Cloudflare. A bot/WAF challenge on /wp-json/ returns 403 (cf-mitigated: challenge) to the server-to-server fetch, surfacing as EXTERNAL_SERVICE_ERROR (HTTP 502). The fix is a zone-level WAF Skip rule for Worker subrequests — no Worker code change. See the integrations page and the repo README for the exact expression.

Admin — /admin

Method Path Auth Purpose
GET /admin/leads — (HTML shell only) Serve the leads dashboard page (no data embedded)
GET /admin/api/leads X-Admin-Api-Key Protected JSON data the dashboard fetches

Note

ADMIN_API_SECRET is a dedicated server-only secret. It must never reuse CHAT_API_SECRET, which ships to the browser bundle.

Durable Object internal endpoints

Not part of the public API — called only by the worker. Included for completeness.

DO Method Path
PrototypeQueue POST /enqueue
PrototypeQueue GET /status
PrototypeQueue POST /clear
ConfirmationEmailQueue POST /enqueue
ConfirmationEmailQueue GET /status