A Blueprint is a design until it is built. Building it means making each block real with the app that already does that job, then linking the block to the record it became (blueprint_link). Nothing on the canvas is a second version of an app.
Video tutorials for this topic
Ask first
- Which blueprint — its id (the person has it open, or
find_recordsonblueprint). - Blank blocks — each holds only an instruction. If an instruction does not say enough to build from (which pipeline, which stage, what to send), ask; don't guess.
- Nothing else: the design says what to build.
Steps
1. CRM Blueprints: read the design
blueprint_view { id }. Note every block whose state is design or changed, every blank block, and every wire with no record.
Fill blank blocks first: for each, choose the block type from blueprint_catalogue that does what its instruction says, then blueprint_edit → update_block with its type, label and settings. Tell the person what each blank became.
2. Get the build plan
blueprint_build_plan { id } lists every block still to build, in order: what it becomes, its app, its skills page, and for each wire the exact setting that makes it real, with the ids already filled in. Work from it — don't search the API or read schemas for what the plan already says. Load a block's skills page only for what the plan leaves open (e.g. the wording of an email).
2. Build the blocks — in this order, each with its own app
Build what others point at first, so their ids exist when wires need them:
- Pipeline — the Lead acquisition page, steps 1–3: the pipeline with the design's stages (won and lost), scoring and assignment rules.
- Customer group — as in Newsletter setup.
- Website form — the collection and the form, Lead acquisition steps 4–5.
- Lead Finder list — a
prospect_listwith the design's steps, Find leads. Do not run it. - Follow-up — an automation with
status: "draft", New-lead follow-up. - Broadcast — a draft, Email campaign draft.
After each: blueprint_link { id, blockId, datatype, recordId } — the record must be the block's kind (a follow-up is an automation). If the answer says it cannot run until fixed (a call step with no script, a missing sender…), fix that record with its own tools and link it again — never leave a block that cannot run. A block whose settings hold copyOf was copied from a built blueprint: make the same thing again (read that record for what it was). A changed block: update its existing record (the same app's update), then link again. If a block cannot be built, blueprint_link { …, state: "failed", note: "<why>" } and carry on with the others. If a person must act first (connect an account, verify a sender), state: "design" with that in note.
Check: blueprint_view again — every block you built is built and shows its record's content.
3. Make the wires real
Each wire becomes the link its two apps already support:
| Wire | What makes it real |
|---|---|
| Website form → a pipeline stage | an automation (draft) whose trigger is the form's answers — a form bound to a collection: database_change on that collection, operation: "created"; a form without one: form_submitted with the form's formId — and whose step create_lead has that pipelineId and stageId. Then blueprint_link { wireId, datatype: "automation", recordId } |
| Lead Finder list → a pipeline stage | its move step's pipelineId / stageId |
| A pipeline stage → Follow-up | the follow-up automation's trigger: pipeline_stage_changed with { pipelineId, stageId } — it runs each time a lead enters that stage (new leads arriving there included) |
| Customer group → Broadcast | the broadcast's selectedLists: [<group id>] |
| Email template → Broadcast | the broadcast's emailConfig.templateId: <template sk or name> |
| Email template → Follow-up | the follow-up's send_email step templateIds: [<template sk>] |
| Broadcast "clicked through" → Web page | the page's link in the email; to act on the click, a follow-up with trigger email_clicked { emailId: <broadcast sk>, linkUrl: <part of the page's address> } |
| Broadcast "Opened" / "Clicked a link" → Follow-up | the follow-up's trigger: email_opened or email_clicked with emailId: <broadcast sk> (and linkUrl: <part of the link> for one link) |
| Web page "A known visitor viewed it" → Follow-up | the follow-up's trigger: page_visited with pageUrl: "/<the page's address>" |
| Follow-up → a pipeline stage | a move_pipeline_stage step in the follow-up with stageName: <the stage's id or name> (and pipelineId) — e.g. after a call answered and agreed |
| Campaign → Web page | the campaign's landingPage: the page's full address on the site |
| Retargeting audience → Campaign | the campaign's audienceIds: [<audience sk>] |
| Follow-up → Reservation definition | the follow-up's make_call step with reservationDefinitionId: <definition sk> and a reason that says to offer a time and book it |
| Reservation definition "Booked" → Follow-up | trigger database_change { datatype: "reservation", operation: "created", whereField: "data.reservationDefinitionId", whereValue: <definition sk> } |
| Reservation definition "Attended" / "Missed" / "Cancelled" → Follow-up | trigger database_change { datatype: "reservation", operation: "updated", changedField: "data.status", changedTo: "used" / "no_show" / "cancelled", whereField: "data.reservationDefinitionId", whereValue: <definition sk> } |
| "…unless they booked again" inside a follow-up | a record_exists condition { datatype: "reservation", match: { "data.customer.email": "{{data.customer.email}}", "data.reservationDefinitionId": <definition sk> }, createdAfter: "{{record.createdate}}", thenStepId, elseStepId } — a booking keeps the person under data.customer ({{data.customer.phone}}, {{data.customer.email}}, {{data.customer.name}}) |
| "Ask them to confirm they'll be there" after Booked | send_sms asking them to reply CONFIRM (not YES — YES, START and STOP are carrier keywords that subscribe or unsubscribe the number) → wait_for_reply { messageType: "sms", maxWait, maxWaitUnit, replyKeywords: "confirm", timeoutAction: "continue" } → if_then { condition: { field: "reply.text", operator: "exists" } }: then update_reservation { reservationId: "{{record.sk}}", status: "confirmed" }; else wait_until_date { targetDate: "{{data.startTime}}", offset: -2, offsetUnit: "hours" } → record_exists { datatype: "reservation", match: { "sk": "{{record.sk}}", "data.status": "new" } } (still unconfirmed) → make_call whose reason asks if they will be there and gives the booking number {{data.name}} and email, so the AI can confirm it |
| Web page → Retargeting audience | create_website_audience { name, include: ["<the page's address, e.g. /pricing>"], exclude: ["<the page that means they converted, e.g. /thank-you>"], retentionDays, platforms }, then blueprint_link { blockId, datatype: "custom_audience", recordId }. The answer says what was made on Meta and on Google Ads; anything not made (not connected, no ad account, no Meta Pixel on the site) goes in the block's note — the person fixes it in Ads › Connections or Site settings › Tracking. |
| Lead Finder list → Broadcast | the broadcast's selectedLists: [<list id>] |
Check: blueprint_view — the links it reads from the records match the wires drawn.
4. Show it
Open the blueprint (CRM Blueprints, the blueprint) — every block shows its record and numbers.
Stop: list what is switched off or a draft and waits for a yes — each follow-up automation (it sends, calls or texts real people once on), each broadcast (a person sends it). Turn an automation on (call_appengine POST /automation/start/<sk>; set_automation_status alone doesn't connect its trigger) only after a yes for that automation.
When it's done
Tell the person: what was built (block by block, with its app), which wires are real, what failed and why, and what waits for their yes.
Don'ts
Don't invent what the business offers. Pages, emails, texts and ads state only what the person wrote in the blueprint or what the business's own content says: its pages, products, services and knowledge base. No made-up offers, prices, discounts, guarantees or numbers. Where something is missing, leave a clear placeholder (
[Your offer]) and tell the person what to add.Don't guess a form's address. Link a form only with its link from
blueprint_build_plan(site.forms); link a page with the site's base address plus the page's address."Book a call", "Book now" and any other booking button opens the booking calendar, not a form: use the booking block's link from
blueprint_build_plan(site.bookings). Visitors pick a time there and are booked straight away.Don't build a block any way but its own app's actions; never write a pipeline, a lead or a form's answers as raw records.
Don't link a record that was not really created.
Don't send, publish, run a Lead Finder search or switch anything on without a yes.
