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_ENABLEDis not"false"and category isspam: lead saved to KV withstatus: "spam", Pipedrive and email skipped. - If
VENDOR_FILTER_ENABLEDis not"false"and category isvendor: lead saved withstatus: "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 andstepsvalidation, 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 textdone—{ "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 |