The main way to build and change a funnel: send a list of ops, applied in order, all or nothing (a failing op saves nothing and says what to fix). Returns the ids given to new steps (refs), warnings, the updated funnel with its forecast, and the canvas link.
Ops (each an object with "op"; name steps by id, or by the "ref" you gave them earlier in the SAME call, or by their exact label when it's unique):
- add_step: a new step. Fields: ref (your temp id, required), element (a step type key from list_step_types, required), label, after (step id/ref: placed one column to its right), phase (1-5, growth phase), visitors (people entering here; for ads/posts: people who SEE it), money, note, content (the type's fields).
{"op":"add_step","ref":"ad","element":"meta-ads","label":"Meta Ads","phase":1,"visitors":50000,"money":{"costModel":"cpm","cost":40}}
{"op":"add_step","ref":"lp","element":"landing-page","label":"Landing page","after":"ad"}
{"op":"add_step","ref":"buy","element":"purchase","label":"Purchase","after":"lp","money":{"price":499}}
Put prices on a purchase step (or an offer: order-bump, upsell, downsell; "priceable" in list_step_types): reaching it is what makes a customer.
- update_step: change a step; money and content are merged, phase/visitors accept null to clear. Fields: step, label, subtitle, element, phase, visitors, money, note, content, stage (tofu|mofu|bofu|retain|none).
{"op":"update_step","step":"lp","label":"Webinar page","money":{"cost":300,"costModel":"fixed"}}
- remove_step: removes the step and its links. {"op":"remove_step","step":"<step id>"}
- connect: a link between two steps. rate = % of the people at "from" who go to "to" (for an ad: its click-through rate). Links from one step should add up to 100% at most; a link without a rate shares what the rated ones leave.
{"op":"connect","from":"ad","to":"lp","rate":1.5}
{"op":"connect","from":"lp","to":"buy","rate":4,"label":"Buy now"}
- update_link: name the link by "link" (its id) or by from + to. {"op":"update_link","from":"lp","to":"buy","rate":6}
- disconnect: {"op":"disconnect","from":"lp","to":"buy"} or {"op":"disconnect","link":"<link id>"}
- add_sticky: a sticky note on the canvas, optionally next to a step. {"op":"add_sticky","text":"Test 3 hooks first","near":"ad"}
- update_task: fix a task (or a subtask) by id or exact text: new text, and for a task its subtasks as a whole list (ticks kept on the ones that stay).
{"op":"update_task","task":"Write 3 ad hooks","text":"Write 5 ad hooks","items":["Pain","Proof","Offer","Story","Objection"]}
- remove_task: delete a task or a subtask by id or exact text. {"op":"remove_task","task":"<task id>"}
- add_frame: a stage of the funnel, drawn as a frame around its steps (a step sits in one frame). A funnel usually has 2-4 stages, named by what each part does: Traffic, Capture / Convert, Sales, Follow-up, After the sale. The funnel is laid out again so each stage's steps sit together.
{"op":"add_frame","ref":"fe","label":"Front end","steps":["ad","lp","buy"]}
- update_frame: rename a frame, or give it its steps again (a list replaces its steps). Name it by id, ref or exact name. {"op":"update_frame","frame":"Front end","label":"Get the lead","steps":["ad","lp"]}
- remove_frame: removes the frame; its steps stay. {"op":"remove_frame","frame":"Front end"}
- tidy: lays the whole funnel out left to right by flow (like Tidy up on the canvas): a step's branches either side of it, each frame's steps together, lines back around underneath. A new funnel, or a call adding 3+ steps, is laid out on its own; use it after small changes that left things crowded.
{"op":"tidy"}
- set_currency: the one currency every amount in the funnel is in (default USD): plans, real numbers and reports. Real numbers in other currencies (ad accounts, stores) always convert to it at the funnel's own rate or today's. With "convert": true, the planned prices and costs move from the old currency too, at the same rate; without it they keep their numbers. One of EGP, AED, SAR, KWD, QAR, BHD, OMR, JOD, MAD, TND, DZD, IQD, LBP, USD, CAD, MXN, BRL, COP, ARS, CLP, PEN, EUR, GBP, CHF, SEK, NOK, DKK, PLN, CZK, HUF, RON, TRY, INR, PKR, BDT, IDR, PHP, THB, VND, MYR, SGD, KES, NGN, GHS, ZAR, XOF, XAF, JPY, KRW, CNY, TWD, HKD, AUD, NZD.
{"op":"set_currency","currency":"SAR","convert":true}
- set_rate: the funnel's own exchange rate, when the person gives the one that's true for them (their bank's, the market's): 1 base = rate quote. It wins over the converter's in both directions; rate null goes back to the converter's.
{"op":"set_rate","base":"USD","quote":"EGP","rate":50.2}
- set_brief: what you learnt about the business (the interview's memory), as a merge patch: fields left out stay, null clears one, objects merge one level deep, lists are replaced. Fields: businessType (ecommerce|high_ticket|course|local_service|saas|app|b2b_service|event|real_estate|restaurant_retail|membership|marketplace|nonprofit|other), businessDetail, offer {name, outcome}, pricePoints [{label, amount, role: lead_magnet|tripwire|core|upsell|downsell|continuity|high_ticket, period: once|week|month|year}], currency, revenueModel (one_time|subscription|repeat|installments|commission), subscription {monthsKept, monthlyChurn %}, salesMotion (self_checkout|book_appointment|application_call|demo_sales|quote|in_store|marketplace|messaging_chat|webinar_launch|trial_freemium|cod_order), customer {who, b2b, awareness: unaware|problem|solution|product|most}, markets [{country, cities, radiusKm}], languages {chat, ads, dialect, site}, paymentMethods, goal {metric: revenue|customers|leads|bookings|signups|installs, target, deadline}, budget {monthlyAds, other}, channels [{key, current, monthlyVolume}], baseline {key: {value, source}} (rates in %), assets, capacity {unit, perWeek}, proof {testimonials, guarantee}, cycleDays, seasons, approved (the person agreed to the plan), sources {field path: user|site|default|ads}. Facts the person gave or confirmed; source "ads" is for real numbers read with get_actuals, copied in only when the person agrees. ticketBand and verticalFlags are worked out by the server.
{"op":"set_brief","brief":{"businessType":"local_service","businessDetail":"dental clinic, 3 branches","pricePoints":[{"label":"Whitening","amount":600,"role":"core"}],"currency":"SAR","salesMotion":"messaging_chat","markets":[{"country":"Saudi Arabia","cities":["Riyadh"]}],"languages":{"ads":"Arabic","dialect":"Najdi"}}}
- set_notes: the funnel's notes (Markdown: headings, lists, bold, links). mode "replace" or "append".
{"op":"set_notes","markdown":"## Offer\n- 499 EGP course\n- 7-day refund","mode":"append"}
- set_strategy: the funnel's strategy, shown beside the notes (replace whole; null clears): size (starter | focused | growth | scale, from propose_funnels), where (budget, team, tools today), why (this funnel at this size), notNow (what waits and the number that says it's time to grow), split [{label, percent}] of the ad budget, first [the first moves, at most 5], watch [{label, target}] (the weekly numbers with targets from the forecast), risks [risks and typical numbers that aren't theirs yet], tools {have, add}.
{"op":"set_strategy","strategy":{"size":"focused","where":"6,000 SAR a month, WhatsApp, a website, no list; you run the ads","why":"One channel gets enough bookings each week to learn.","notNow":"Meta waits until a booking costs under 130 SAR for 3 weeks.","split":[{"label":"Snapchat","percent":80},{"label":"Retargeting","percent":20}],"first":["Reply on WhatsApp within 5 minutes"],"watch":[{"label":"Cost per booking","target":"≤ 150 SAR"}],"risks":["Show-up 70% is typical, not yours yet"],"tools":{"have":["WhatsApp Business"],"add":["Snapchat Ads Manager"]}}}
- add_task: a checklist task, optional subtasks, optional step it's about. {"op":"add_task","text":"Write 3 ad hooks","items":["Pain","Proof","Offer"],"step":"ad"}
- complete_task: tick (or untick) a task by id or exact text. {"op":"complete_task","task":"Write 3 ad hooks","done":true}
money fields: costModel (fixed | cpc: per click | cpm: per 1,000 people reached | per_person), cost (in the funnel's currency), price (per payment, on purchase/offer steps), revenueType (one_time | subscription | repeat), months (subscription: months kept), purchases (repeat: purchases per customer), buyRate (% who pay, e.g. an upsell's take rate), costPerSale.
Pass "revision" (from get_funnel) only to refuse changes if someone edited the funnel since; without it, the ops apply to the latest version.
update_funnel