# Build a popup end-to-end

Canonical URL: https://support.optimonk.com/en/articles/build-a-popup-end-to-end

# Build a popup end-to-end

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.
