The full design-thread loop: brief → questions → concepts → pick.
The canonical conversational loop uses one append-only design thread:
-
start_design_threadwith the brief → get asessionId. -
Poll
get_design_thread. When its latestagent_messagehasasking: true, answer it withsend_design_message. Repeat through discovery and intake. If discovery lands on a domain the account does not own, the nextagent_messageasks the merchant about it (askingDomainis set) instead of moving to concepts — answer it the same way, then continue. -
When concept cards are ready, call
select_variantwith 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 ajobIdto wait on withwatch_job. To SEE the cards, callget_concept_board: the same cards as one fixed 2×2 image with each card’s letter and name rendered into it. -
Continue with
send_design_messageto edit the picked popup. To request another concept round instead, setnewVariant: true; optionally pass a picked run asnewVariantFrom. Keep pollingget_design_threadfor the complete ordered log. -
The draft campaign is normally created right away by
select_variant(its id is incampaignId): read and save its targeting withget_campaign_settings/set_campaign_settings, thenpublish_campaign. The call answers within about 40 seconds even when the build is still starting up — apending: trueresponse with a nullcampaignIdmeans exactly that; watch thejobIdand read the campaign fromget_design_threadshortly after.create_campaignstays available for a completed run that has no campaign yet, and returns the existing draft otherwise. -
select_variantandcreate_campaignboth refuse with409 domain_not_on_accountif the campaign would land on a domain the account does not own — itsagentMessageasks the merchant to add the domain or name one of their own. Repeat the same call with the merchant's answer as the optionaldomainparam.
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.