API ReferencePOST /v1/sites/:id/approve-research

POST /v1/sites/:id/approve-research

Approve a site that’s paused in research_review and advance it into generation. Optionally merge final edits — services, hours, FAQs, photos, brand colors — into the research payload before the build runs. Only relevant for sites created with "review": "manual"; auto-mode sites advance themselves and never sit in research_review.

Cost: Free (the generation itself still costs the usual ~200–500 credits — see POST /v1/sites).

Request

curl -X POST https://api.warpweb.ai/v1/sites/8f3c2a1b/approve-research \
  -H "Authorization: Bearer wwk_<your-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "services": [
      { "name": "Drain cleaning", "description": "Hydro-jetting and cabling.", "price": "from $189" },
      { "name": "Water-heater install" }
    ],
    "hours": {
      "Monday": "8:00 AM - 6:00 PM",
      "Sunday": "Closed"
    },
    "excludeImages": ["research/photo-3.jpg"],
    "ownerPrompt": "Emphasize 24/7 emergency service"
  }'

All body fields are optional. An empty {} is valid — it accepts the research payload as-is and starts the build.

Body

FieldTypeDescription
servicesarrayReplace the services list. { name, description?, price? }. Empty name entries are dropped.
testimonialsarrayReplace the testimonials list. { quote, author?, rating?, source? }. Empty quote entries are dropped.
faqsarrayReplace the FAQ list. { question, answer }. Empty question entries are dropped.
hoursobjectWeekday → hour string (e.g. "Monday": "9:00 AM - 5:00 PM" or "Closed"). Replaces the hours map wholesale.
brandColorsobject{ primary?, secondary?, accent?, personality? }. Hex strings. Omitted slots preserve research-extracted values.
existingSiteUrlstringOverride the existing-site URL recorded at create time.
ownerPromptstringVoice / tone direction merged into the build prompt. Same field as on create.
businessDescriptionstringOne-sentence pitch the generator uses for the hero / meta-description.
designStylestringOverride the auto-selected design style. Unknown values are silently ignored — the existing style stays.
allowStockPhotosbooleanToggle the stock-photo (Pexels) layer of the photo cascade. Default false as of 2026-05-24 — sites with empty upload + Google Places slots render Lucide icons over brand colors instead of generic stock imagery. Set true to opt in to stock fallback when you’d rather have generic photo coverage than icon-and-color sections. Also accepted at create time on POST /v1/sites.
excludeImagesstring[]Drop photos from generation. Each entry can be either the localPath (for existing_site / upload / facebook photos: e.g. "images/existing-3.jpg") or the full storage_url (for places / stock photos, which only live in the photo catalog and not on disk: e.g. "https://...supabase.../places-XYZ.jpg"). Use whatever the photos[] entry from GET /v1/sites/:id/research exposed — both forms are accepted. Excluded photos are tombstoned in the photo catalog (metadata.excluded=true), filtered out of the build picker, AND removed from the on-disk staging dir when local. Surfaces on subsequent GET /research calls with exclude: true so your UI can pre-check the operator’s prior exclusions.
uploadedPhotosstring[]URLs to download and add to the photo library. Same field as on create.
contentPlanobjectPartial overrides to the AI-generated content plan (e.g. servicesToHighlight, competitiveAdvantages). Merged shallow over the existing plan.
contactEmailstringOperator-confirmed contact email. Written to sites.contact_email before the build kicks off. Use this when the email cascade returned null at research time (email_status: "missing") or when the operator wants to swap the placeholder for the customer’s real address. After the site is built, use POST /v1/sites/:id/contact instead. See Email resolution.

services, testimonials, faqs, and hours write into the priority-1 content source — they beat anything the extractor finds during research.

Response

{
  "success": true,
  "phase": "generating"
}

The build runs asynchronously from here. Listen for site.complete (or site.failed) on the customer lifecycle webhook, or poll GET /v1/sites/:id until status flips to complete.

Errors

StatusBodyCause
400{ "error": "Site is not in research review phase" }The site has already advanced (or never entered) research_review. Check generation_phase on GET /v1/sites/:id. Idempotent — repeated calls after a successful approve also land here.
400{ "error": "No research data found" }The site exists but research_data is empty. Should not happen for sites that reached research_review; usually indicates a corrupted row.
404{ "error": "Site not found" }No site with that ID belongs to your account.