The full design-thread loop: brief → questions → concepts → pick.

The canonical conversational loop uses one append-only design thread:

  1. start_design_thread with the brief → get a sessionId.

  2. Poll get_design_thread. When its latest agent_message has asking: true, answer it with send_design_message. Repeat through discovery and intake. If discovery lands on a domain the account does not own, the next agent_message asks the merchant about it (askingDomain is set) instead of moving to concepts — answer it the same way, then continue.

  3. When concept cards are ready, call select_variant with one or more card ids. Send the complete ordered selection; several ids create an A/B set. This is the agent’s Build it: the selection is picked AND built in one call, the draft campaign is created right away, and the answer carries a jobId to wait on with watch_job. To SEE the cards, call get_concept_board: the same cards as one fixed 2×2 image with each card’s letter and name rendered into it.

  4. Continue with send_design_message to edit the picked popup. To request another concept round instead, set newVariant: true; optionally pass a picked run as newVariantFrom. Keep polling get_design_thread for the complete ordered log.

  5. The draft campaign is normally created right away by select_variant (its id is in campaignId): read and save its targeting with get_campaign_settings / set_campaign_settings, then publish_campaign. The call answers within about 40 seconds even when the build is still starting up — a pending: true response with a null campaignId means exactly that; watch the jobId and read the campaign from get_design_thread shortly after. create_campaign stays available for a completed run that has no campaign yet, and returns the existing draft otherwise.

  6. select_variant and create_campaign both refuse with 409 domain_not_on_account if the campaign would land on a domain the account does not own — its agentMessage asks the merchant to add the domain or name one of their own. Repeat the same call with the merchant's answer as the optional domain param.

start_design_thread({ text: "A 10% welcome popup for shop.com" })
  → { sessionId }

# poll
get_design_thread({ sessionId })
  → { events: [{ type: "agent_message", payload: { asking: true, ... } }] }

send_design_message({ sessionId, message: "New visitors" })
  → { routedTo: "board" }

# after concept cards arrive — picks, builds and creates the draft campaign
select_variant({ sessionId, conceptIds: ["0-a", "0-b"] })
  → { runId, runIds, campaignId, jobId: "run:<runId>", next }

watch_job({ jobId })
  → { status: "completed", hasHtml: true }

send_design_message({ sessionId, message: "Make the CTA more prominent" })
  → { routedTo: "run", runId }

# explicitly start another concept round instead of editing the picked popup
send_design_message({ sessionId, message: "Try a bolder direction", newVariant: true, newVariantFrom: runId })
  → { routedTo: "board" }

# the draft campaign already exists: agree targeting, then publish
get_campaign_settings({ campaignId })
set_campaign_settings({ campaignId, ... })
publish_campaign({ campaignId })

For direct screenshot reconstruction without a design conversation, use upload_reference_image (if needed), build_popup, watch_job, and get_popup_html. Use get_job instead when you need one status read or the client cannot hold a request open; either way the runId is the jobId. A standalone build_popup run has no designer session, so it cannot be passed to create_campaign.

get_popup_html returns the markup; get_popup_preview returns the picture — server-rendered screenshots of a finished build at real browser proportions (desktop 1440×900 and mobile 390×844), one image per viewport.