Quickstart
From signup to deployed site in five minutes. Every step is a copy-paste.
1. Get an API key
- Sign up at app.warpweb.ai.
- Confirm your email by clicking the link we send you. New accounts get 520 free credits — enough for a first build plus a few revisions.
- Open API Keys in the dashboard and create a key. Keys are prefixed
wwk_and the plaintext value is shown once — copy it into your secret manager immediately.
export WARPWEB_KEY="wwk_<your-key>"Verify your key
Before spending any credits, confirm the key + header chain works. GET /v1/sites is free and returns an empty list for a brand-new account:
curl -i https://api.warpweb.ai/v1/sites \
-H "Authorization: Bearer $WARPWEB_KEY"HTTP/1.1 200 OK
Content-Type: application/json
{ "sites": [] }If you see 401 Unauthorized, your Authorization header or WARPWEB_KEY is misconfigured — fix it here before moving on. A 200 means you’re ready to build.
2. Build your first site (one field)
Open the business in Google Maps, copy the URL, POST it. That’s the build.
curl -X POST https://api.warpweb.ai/v1/sites \
-H "Authorization: Bearer $WARPWEB_KEY" \
-H "Content-Type: application/json" \
-d '{"mapsUrl":"<paste URL>"}'Response:
{
"siteId": "8f3c2a1b-5d47-4c9e-b820-1f8a3e7d9c4f",
"status": "generating",
"slug": "brookside-plumbing-a1b2c3"
}The Maps URL already encodes the business identity (Place ID via the URL itself or via /place/Name/@lat,lng/ triangulation). All three formats work — the long google.com/maps/place/... URL, the short maps.app.goo.gl/... share link, and maps.google.com/?cid=....
The build runs in the background and typically lands within 3–8 minutes. Step 3 covers how to find out when it’s done.
Email is optional now
contactEmail is no longer required at create time. When you omit it, Warpweb tries to scrape one from the business’s website (mailto: links + contact pages). If the scrape lands an address, the build auto-advances; if not, the site pauses at research_review and fires site.research_ready with email_status: "missing" so you can collect one and POST it on /v1/sites/:id/approve-research. Full cascade in Email resolution.
If you’re building sites for end-clients and intend to deliver leads via webhook only — never via email — set "allowMissingEmail": true on create to skip the safety-net pause.
Stock photos are opt-in
By default, sections that don’t have an operator-uploaded or Google-Places photo render with Lucide icons over the site’s brand colors — every photo on the deployed site is real. If you’d rather have generic Pexels stock photos fill those gaps, pass "allowStockPhotos": true at create time:
curl -X POST https://api.warpweb.ai/v1/sites \
-H "Authorization: Bearer $WARPWEB_KEY" \
-H "Content-Type: application/json" \
-d '{
"mapsUrl": "https://maps.app.goo.gl/abc123",
"allowStockPhotos": true
}'You can also toggle this later from the build-plan sheet or on POST /v1/sites/:id/approve-research.
Optional: review research before generation
For production builds — or anywhere you want to verify services, hours, and photos before generation spends credits — pass "review": "manual" on the create call. The site pauses after research and waits for you to approve.
curl -X POST https://api.warpweb.ai/v1/sites \
-H "Authorization: Bearer $WARPWEB_KEY" \
-H "Content-Type: application/json" \
-d '{
"mapsUrl": "https://maps.app.goo.gl/abc123",
"review": "manual"
}'When research completes, the lifecycle webhook fires site.research_ready and the site sits at generation_phase: "research_review" until you advance it. (No webhook receiver yet? Poll GET /v1/sites/:id until generation_phase is research_review.)
Fetch the assembled research payload — Google Places business data, services, FAQs, testimonials, hours, photos, brand colors, plus the pre-filled contact_email:
curl https://api.warpweb.ai/v1/sites/$SITE_ID/research \
-H "Authorization: Bearer $WARPWEB_KEY"Save in-progress edits without advancing (optional — useful for multi-session review):
curl -X PATCH https://api.warpweb.ai/v1/sites/$SITE_ID/research-draft \
-H "Authorization: Bearer $WARPWEB_KEY" \
-H "Content-Type: application/json" \
-d '{
"services": [
{ "name": "Drain cleaning", "description": "Hydro-jetting and cabling.", "price": "from $189" },
{ "name": "Water-heater install" }
]
}'When you’re ready, approve and start the build. Any final edits go in this call’s body and override the research payload — including contactEmail if the scrape didn’t find one:
curl -X POST https://api.warpweb.ai/v1/sites/$SITE_ID/approve-research \
-H "Authorization: Bearer $WARPWEB_KEY" \
-H "Content-Type: application/json" \
-d '{
"contactEmail": "owner@brooksideplumbing.com",
"ownerPrompt": "Emphasize 24/7 emergency service",
"excludeImages": ["research/photo-3.jpg"]
}'{ "success": true, "phase": "generating" }From here it’s the same as auto mode — wait for site.complete on the lifecycle webhook (or poll until status flips to complete). See /research, /approve-research, and /research-draft for the full field list.
Alternative entry points
When a Maps URL isn’t what you have on hand:
- Place ID — pass
placeIdinstead. Best when you’ve already disambiguated viaPOST /v1/businesses/search. LLM-agent flows that receive a bare business name from the user should call search first, then build with the chosenplaceId. - Business name + location — pass
businessNameplusbusinessLocation. Warpweb auto-resolves to a Place via Google. Only safe when the name is unambiguous in the given city — common names (“Acme Plumbing”) are a coin flip without disambiguation.
Resolution priority on the server is placeId > mapsUrl > businessName + businessLocation. See POST /v1/sites for the full body.
3. Get notified when the build completes
Warpweb supports two webhook surfaces: a customer-level lifecycle webhook (one URL per account, receives every build/revision event for every site) and per-site form webhooks (one URL per site, receives only form submissions — covered in Step 4). The lifecycle webhook is what you want here. Configure it once per account and Warpweb pushes site.complete / site.failed events to your URL as builds finish — no polling, no race conditions, no rate-limit headaches at scale.
Heads up: in auto mode, a site.research_ready event may arrive before site.complete if the email-missing safety net kicks in (cascade returned null, allowMissingEmail not set) — the payload’s email_status: "missing" tells you the site is paused waiting for an email. See Email resolution for the cascade and Lifecycle Events for the full payload.
curl -X PUT https://api.warpweb.ai/v1/customer/webhook \
-H "Authorization: Bearer $WARPWEB_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.yourapp.com/webhooks/warpweb-lifecycle"
}'First call returns the signing secret in secret_plaintext once — capture it immediately. Your receiver will then get a site.complete event when the build finishes (typically 3–8 minutes after POST /v1/sites):
{
"event_id": "...",
"type": "site.complete",
"site_id": "8f3c2a1b-5d47-4c9e-b820-1f8a3e7d9c4f",
"occurred_at": "2026-05-17T14:27:54Z",
"payload": {
"deployment_url": "https://brookside-plumbing-a1b2c3.warpweb.app",
"slug": "brookside-plumbing-a1b2c3",
"business_name": "Brookside Plumbing"
}
}Test your receiver before real traffic fires with POST /v1/customer/webhook/test-ping. See Webhooks → Lifecycle Events for the full event catalog, payload shapes, and signature verification.
Fallback: poll the status endpoint
No receiver running yet? You can poll GET /v1/sites/:id directly — fine for a hello-world check, but switch to the webhook once you’re past the test phase.
curl https://api.warpweb.ai/v1/sites/8f3c2a1b-5d47-4c9e-b820-1f8a3e7d9c4f \
-H "Authorization: Bearer $WARPWEB_KEY"(The GET endpoint mirrors the DB row, so it returns id, not siteId — same UUID, different key. See Field-naming conventions below for the broader pattern.)
{
"id": "8f3c2a1b-5d47-4c9e-b820-1f8a3e7d9c4f",
"status": "complete",
"deployment_url": "https://brookside-plumbing-a1b2c3.warpweb.app",
"business_name": "Brookside Plumbing",
"slug": "brookside-plumbing-a1b2c3",
"hosting_tier": "free_subdomain",
"created_at": "2026-05-17T14:23:11Z",
"updated_at": "2026-05-17T14:27:54Z"
}Poll every 2–5 seconds while status is generating. Stop when it flips to complete or failed.
Either way, open the deployment_url in your browser. That’s a real, indexable, working website. (Search engines pick it up on their own crawl schedule — usually within a few days; faster if you submit the sitemap to Google Search Console.)
4. Configure a webhook for form submissions
The generated site has a contact form. You probably want submissions in your own system.
curl -X POST https://api.warpweb.ai/v1/sites/8f3c2a1b-5d47-4c9e-b820-1f8a3e7d9c4f/webhooks/forms \
-H "Authorization: Bearer $WARPWEB_KEY" \
-H "Content-Type: application/json" \
-d '{
"webhook_url": "https://api.yourapp.com/webhooks/warpweb-leads"
}'Response (first time you configure):
{
"ok": true,
"site_id": "8f3c2a1b-5d47-4c9e-b820-1f8a3e7d9c4f",
"webhook_url": "https://api.yourapp.com/webhooks/warpweb-leads",
"secret_issued": "whsec_…64-char-hex…",
"secret_note": "Copy this signing secret now — it will not be shown again."
}Capture secret_issued immediately into your secret manager — it’s the HMAC key you’ll use to verify deliveries, and it’s only returned once. (To rotate later, call the same endpoint with "rotate_secret": true.)
Every form submission now POSTs to your URL with an HMAC-SHA256 signature in X-Warpweb-Signature. See Webhooks → Form Submissions for the payload shape and signature verification.
5. Submit a test form
Once Step 3 reported status: complete (via webhook or polling), open the deployed URL in your browser. Visit the site’s contact form, fill it in, submit. Your webhook fires within seconds. If the receiver returns 5xx or times out, Warpweb retries on a schedule: immediate → +30s → +5min → +30min, then dead-letters.
Or fire a synthetic test payload without leaving the API:
curl -X POST https://api.warpweb.ai/v1/sites/8f3c2a1b-5d47-4c9e-b820-1f8a3e7d9c4f/webhooks/forms/test \
-H "Authorization: Bearer $WARPWEB_KEY"What’s next
- Request a revision — change copy, tweak design, regenerate sections in plain English. Usually the very next thing you’ll do.
- Verify your form-webhook receiver in production — payload shape, retry behavior, dead-letter recovery.
- Attach a custom domain when you’re ready to go live — $10/mo for always-on hosting, no refresh required.
- Browse the full API reference.
Field-naming conventions
The naming style flips by surface — once you know the pattern, the per-endpoint pages are unambiguous:
- Resource fetches (
GET /v1/sites,GET /v1/sites/:id) mirror the DB row in snake_case (id,business_name,deployment_url,last_refreshed_at,pause_state). - Action endpoints (
POST /v1/sites, revision/domain ops) return a small bespoke shape with camelCase IDs (siteId,revisionId,zoneId). - Webhook payloads use snake_case throughout (
site_id,deployment_url,business_name).
In practice: pin the UUID returned from the create call and reuse it; don’t try to read siteId off a GET. The per-endpoint Response section is always authoritative.