[{"data":1,"prerenderedAt":41},["ShallowReactive",2],{"doc-onboarding":3},{"html":4,"headings":5},"\u003Cp>Show new users a few screens that explain your app, and change them from the\npanel without shipping a release. You write the screens in HelpBites, publish,\nand your web or Swift app shows them once to each person.\u003C\u002Fp>\n\u003Cp>Onboarding runs on the first launch, which is exactly when a failing network\nhurts most. So a flow is plain JSON your app can keep: the web script and the\nSwift snippet below show the last copy they got, and you can ship a copy inside\nyour app for the very first launch. If HelpBites is down, onboarding still\nshows.\u003C\u002Fp>\n\u003Ch2 id=\"a-flow\">A flow\u003Ca class=\"heading-anchor\" href=\"#a-flow\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>A flow is a list of steps under a \u003Cstrong>key\u003C\u002Fstrong>, the name your app asks for it by\n(\u003Ccode>welcome\u003C\u002Fcode>, \u003Ccode>pro-upgrade\u003C\u002Fcode>). Each step has:\u003C\u002Fp>\n\u003Cdiv class=\"table-wrap\">\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Meaning\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>title\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Up to 120 characters.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>body\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Up to 600 characters. Line breaks are kept.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>imageUrl\u003C\u002Fcode>, \u003Ccode>imageAlt\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>An optional picture (any https URL; uploads and brand kit files work too) and its description for screen readers.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>buttonLabel\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The button text. Empty uses the client's own: \u003Cstrong>Continue\u003C\u002Fstrong>, and \u003Cstrong>Get started\u003C\u002Fstrong> on the last step.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>pricingTable\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The key of one of the project's \u003Ca href=\"\u002Fdocs\u002Fpricing\">pricing tables\u003C\u002Fa> to show under the text, such as a paywall at the end of the tour; choosing a plan finishes the tour. The public flow carries the published table as the step's \u003Ccode>pricing\u003C\u002Fcode> (null while it is a draft). \u003Ccode>onboarding.js\u003C\u002Fcode> draws it; the Swift snippet below shows the step's text only.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\u003C\u002Fdiv>\n\u003Cp>A flow has up to 12 steps, and a project up to 50 flows. The flow also has a\n\u003Ccode>style\u003C\u002Fcode> and \u003Ccode>options\u003C\u002Fcode>:\u003C\u002Fp>\n\u003Cdiv class=\"table-wrap\">\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>Meaning\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>style.accent\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>#rrggbb\u003C\u002Fcode>. Empty uses the project's accent.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>style.background\u003C\u002Fcode>, \u003Ccode>style.text\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>#rrggbb\u003C\u002Fcode>. Empty follows the app's light or dark theme.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>style.radius\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The card's corner radius, 0 to 32. Default 20.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>style.font\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>system\u003C\u002Fcode>, \u003Ccode>rounded\u003C\u002Fcode>, \u003Ccode>serif\u003C\u002Fcode> or \u003Ccode>mono\u003C\u002Fcode>.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>options.skippable\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Show \u003Cstrong>Skip\u003C\u002Fstrong> (and let Escape close it). Default \u003Ccode>true\u003C\u002Fcode>.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>options.progress\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>bar\u003C\u002Fcode>, \u003Ccode>dots\u003C\u002Fcode> or \u003Ccode>none\u003C\u002Fcode>.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\u003C\u002Fdiv>\n\u003Cp>Create and edit flows under \u003Cstrong>Project → Onboarding\u003C\u002Fstrong>, in the web panel or the\nnative app.\u003C\u002Fp>\n\u003Ch2 id=\"drafts-and-versions\">Drafts and versions\u003Ca class=\"heading-anchor\" href=\"#drafts-and-versions\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>Editing changes the working copy only. Your apps keep getting the published\nversion until you \u003Cstrong>Publish\u003C\u002Fstrong> again, which freezes the working copy into the\nnext version (1, 2, 3…). The panel tells you when there are changes you have\nnot published.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>Unpublish\u003C\u002Fstrong> makes the public URL answer \u003Ccode>404\u003C\u002Fcode>. The web script forgets its\ncached copy when it sees that, and the Swift snippet deletes its own, so the flow\nstops showing. Changing a published flow's key moves its URL in the same way.\u003C\u002Fp>\n\u003Ch2 id=\"plug-and-play-web\">Plug and play: web\u003Ca class=\"heading-anchor\" href=\"#plug-and-play-web\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>One script tag. It shows the flow \u003Ccode>welcome\u003C\u002Fcode> as a dialog, once per browser:\u003C\u002Fp>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-html\">\u003Ccode>\u003Cspan class=\"shj-syn-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">script\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">src\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"https:\u002F\u002FYOUR-SUPPORT-HOST\u002Fonboarding.js\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">data-project\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"pk_…\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">data-flow\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"welcome\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">defer\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">script\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&gt;\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>It is MIT-licensed, has no dependencies, and draws the flow from JSON in your\npage (in a shadow root, so your CSS and its CSS never collide). It never waits\nlong: when HelpBites does not answer within four seconds it uses the last copy\nit saved, and the dialog never blocks your page from loading.\u003C\u002Fp>\n\u003Ch3 id=\"options\">Options\u003Ca class=\"heading-anchor\" href=\"#options\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh3>\n\u003Cp>Set them as \u003Ccode>data-*\u003C\u002Fcode> attributes or in \u003Ccode>init\u003C\u002Fcode>:\u003C\u002Fp>\n\u003Cdiv class=\"table-wrap\">\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Option\u003C\u002Fth>\n\u003Cth>Meaning\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>flow\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The flow's key. Default \u003Ccode>welcome\u003C\u002Fcode>.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>show\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>once\u003C\u002Fcode> (default) shows it one time per browser. \u003Ccode>update\u003C\u002Fcode> shows it again after each publish. \u003Ccode>always\u003C\u002Fcode> shows it on every load (handy while you test). \u003Ccode>manual\u003C\u002Fcode> only shows it when you call \u003Ccode>open\u003C\u002Fcode>.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>target\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A selector. Draws the flow inline in that element, as a card on your own welcome page, instead of a dialog.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>theme\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>system\u003C\u002Fcode>, \u003Ccode>light\u003C\u002Fcode> or \u003Ccode>dark\u003C\u002Fcode>. Defaults to a \u003Ccode>dark\u003C\u002Fcode> or \u003Ccode>light\u003C\u002Fcode> class on your \u003Ccode>&lt;html&gt;\u003C\u002Fcode>, then the visitor's system setting.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>accent\u003C\u002Fcode>, \u003Ccode>radius\u003C\u002Fcode>, \u003Ccode>font\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Override the flow's style.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>fallback\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A selector for a \u003Ccode>&lt;script type=&quot;application\u002Fjson&quot;&gt;\u003C\u002Fcode> holding an offline copy (see below), or the copy itself in \u003Ccode>init\u003C\u002Fcode>.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>locale\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The translation to show, a language tag such as \u003Ccode>es\u003C\u002Fcode> or \u003Ccode>pt-BR\u003C\u002Fcode>. Defaults to your \u003Ccode>&lt;html lang&gt;\u003C\u002Fcode>, then the browser's language. A flow without that translation shows in its own language. See \u003Ca href=\"\u002Fdocs\u002Flanguages\">Languages\u003C\u002Fa>.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>host\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Where HelpBites is, when you serve \u003Ccode>onboarding.js\u003C\u002Fcode> from your own site.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>token\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The person's identity token, to report who finished the flow (see \u003Ca href=\"#who-finished-it\">Who finished it\u003C\u002Fa>). A string, or a function returning one or a promise of one.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\u003C\u002Fdiv>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-html\">\u003Ccode>\u003Cspan class=\"shj-syn-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">script\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&gt;\u003C\u002Fspan>\n  window\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-syn-class\">HelpBitesOnboarding\u003C\u002Fspan> \u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan> window\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-syn-class\">HelpBitesOnboarding\u003C\u002Fspan> \u003Cspan class=\"shj-syn-oper\">||\u003C\u002Fspan> []\n  \u003Cspan class=\"shj-syn-class\">HelpBitesOnboarding\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-syn-func\">push\u003C\u002Fspan>([\u003Cspan class=\"shj-syn-str\">'init'\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">,\u003C\u002Fspan> {\n    show\u003Cspan class=\"shj-syn-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">'update'\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">,\u003C\u002Fspan>\n    labels\u003Cspan class=\"shj-syn-oper\">:\u003C\u002Fspan> { next\u003Cspan class=\"shj-syn-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">'Next'\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">,\u003C\u002Fspan> done\u003Cspan class=\"shj-syn-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">'Start'\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">,\u003C\u002Fspan> back\u003Cspan class=\"shj-syn-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">'Back'\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">,\u003C\u002Fspan> skip\u003Cspan class=\"shj-syn-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">'Not now'\u003C\u002Fspan> }\u003Cspan class=\"shj-syn-oper\">,\u003C\u002Fspan>\n    onComplete\u003Cspan class=\"shj-syn-oper\">:\u003C\u002Fspan> (flow) \u003Cspan class=\"shj-syn-kwd\">=&gt;\u003C\u002Fspan> analytics\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-syn-func\">track\u003C\u002Fspan>(\u003Cspan class=\"shj-syn-str\">'onboarding_done'\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">,\u003C\u002Fspan> { version\u003Cspan class=\"shj-syn-oper\">:\u003C\u002Fspan> flow\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>version })\u003Cspan class=\"shj-syn-oper\">,\u003C\u002Fspan>\n    onSkip\u003Cspan class=\"shj-syn-oper\">:\u003C\u002Fspan> (flow\u003Cspan class=\"shj-syn-oper\">,\u003C\u002Fspan> step) \u003Cspan class=\"shj-syn-kwd\">=&gt;\u003C\u002Fspan> analytics\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-syn-func\">track\u003C\u002Fspan>(\u003Cspan class=\"shj-syn-str\">'onboarding_skipped'\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">,\u003C\u002Fspan> { step })\u003Cspan class=\"shj-syn-oper\">,\u003C\u002Fspan>\n    onStep\u003Cspan class=\"shj-syn-oper\">:\u003C\u002Fspan> (step\u003Cspan class=\"shj-syn-oper\">,\u003C\u002Fspan> flow) \u003Cspan class=\"shj-syn-kwd\">=&gt;\u003C\u002Fspan> {}\n  }])\n\u003Cspan class=\"shj-syn-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">script\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-syn-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">script\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">src\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"https:\u002F\u002FYOUR-SUPPORT-HOST\u002Fonboarding.js\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">data-project\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"pk_…\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">data-flow\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"welcome\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">defer\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">script\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&gt;\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>Other calls: \u003Ccode>HelpBitesOnboarding.push(['open'])\u003C\u002Fcode> shows it now whatever the\nvisitor saw before (a &quot;Take the tour&quot; link), \u003Ccode>['close']\u003C\u002Fcode>, and \u003Ccode>['reset']\u003C\u002Fcode>\nforgets that this browser saw it. Every outcome is also a\n\u003Ccode>helpbites:onboarding\u003C\u002Fcode> event on \u003Ccode>window\u003C\u002Fcode>, with \u003Ccode>detail.type\u003C\u002Fcode> set to \u003Ccode>show\u003C\u002Fcode>,\n\u003Ccode>step\u003C\u002Fcode>, \u003Ccode>complete\u003C\u002Fcode> or \u003Ccode>skip\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch3 id=\"restyle-it\">Restyle it\u003Ca class=\"heading-anchor\" href=\"#restyle-it\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh3>\n\u003Cp>The flow's style is the starting point; your CSS can change any of it. Set these\nvariables on \u003Ccode>.help-bites-onboarding\u003C\u002Fcode>:\u003C\u002Fp>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-css\">\u003Ccode>\u003Cspan class=\"shj-syn-var\">.help-bites-onboarding\u003C\u002Fspan> {\n  --\u003Cspan class=\"shj-syn-var\">hb-onboarding-accent\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">#0ea5e9\u003C\u002Fspan>;\n  --\u003Cspan class=\"shj-syn-var\">hb-onboarding-on-accent\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">#fff\u003C\u002Fspan>;     \u003Cspan class=\"shj-syn-cmnt\">\u002F* text on the button *\u002F\u003C\u002Fspan>\n  --\u003Cspan class=\"shj-syn-var\">hb-onboarding-bg\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">#fff\u003C\u002Fspan>;\n  --\u003Cspan class=\"shj-syn-var\">hb-onboarding-text\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">#0f172a\u003C\u002Fspan>;\n  --\u003Cspan class=\"shj-syn-var\">hb-onboarding-muted\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">#64748b\u003C\u002Fspan>;\n  --\u003Cspan class=\"shj-syn-var\">hb-onboarding-radius\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">24\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">px\u003C\u002Fspan>\u003Cspan class=\"shj-syn-num\">\u003C\u002Fspan>;\n  --\u003Cspan class=\"shj-syn-var\">hb-onboarding-font\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"Inter\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-num\">sans-serif\u003C\u002Fspan>;\n  --\u003Cspan class=\"shj-syn-var\">hb-onboarding-backdrop\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-func\">rgb\u003C\u002Fspan>(\u003Cspan class=\"shj-syn-num\">15\u003C\u002Fspan> \u003Cspan class=\"shj-syn-num\">23\u003C\u002Fspan> \u003Cspan class=\"shj-syn-num\">42\u003C\u002Fspan> \u002F \u003Cspan class=\"shj-syn-num\">0.6\u003C\u002Fspan>);\n  --\u003Cspan class=\"shj-syn-var\">hb-onboarding-width\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">440\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">px\u003C\u002Fspan>\u003Cspan class=\"shj-syn-num\">\u003C\u002Fspan>;\n}\n\u003Cspan class=\"shj-syn-var\">.help-bites-onboarding\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">::part\u003C\u002Fspan>(\u003Cspan class=\"shj-syn-kwd\">title\u003C\u002Fspan>) { \u003Cspan class=\"shj-syn-var\">letter-spacing\u003C\u002Fspan>: -\u003Cspan class=\"shj-syn-num\">0.02\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">em\u003C\u002Fspan>\u003Cspan class=\"shj-syn-num\">\u003C\u002Fspan>; }\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>Parts you can style: \u003Ccode>overlay\u003C\u002Fcode>, \u003Ccode>card\u003C\u002Fcode>, \u003Ccode>progress\u003C\u002Fcode>, \u003Ccode>skip\u003C\u002Fcode>, \u003Ccode>media\u003C\u002Fcode>, \u003Ccode>image\u003C\u002Fcode>,\n\u003Ccode>title\u003C\u002Fcode>, \u003Ccode>body\u003C\u002Fcode>, \u003Ccode>actions\u003C\u002Fcode>, \u003Ccode>back\u003C\u002Fcode> and \u003Ccode>next\u003C\u002Fcode>. The layout is HelpBites'.\u003C\u002Fp>\n\u003Ch3 id=\"offline-copy\">Offline copy\u003Ca class=\"heading-anchor\" href=\"#offline-copy\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh3>\n\u003Cp>Each flow has \u003Cstrong>Download offline copy\u003C\u002Fstrong> in the panel. It saves\n\u003Ccode>onboarding-&lt;key&gt;.json\u003C\u002Fcode>, the same JSON the API returns. Put it in your page for\nvisitors who arrive while HelpBites is down and have no saved copy yet:\u003C\u002Fp>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-html\">\u003Ccode>\u003Cspan class=\"shj-syn-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">script\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">type\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"application\u002Fjson\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">id\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"welcome-flow\"\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&gt;\u003C\u002Fspan>{ \"flow\"\u003Cspan class=\"shj-syn-oper\">:\u003C\u002Fspan> { \"key\"\u003Cspan class=\"shj-syn-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"welcome\"\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">,\u003C\u002Fspan> … } }\u003Cspan class=\"shj-syn-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">script\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-syn-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">script\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">src\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"https:\u002F\u002FYOUR-SUPPORT-HOST\u002Fonboarding.js\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">data-project\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"pk_…\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">data-flow\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"welcome\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">data-fallback\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"#welcome-flow\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">defer\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">script\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&gt;\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>The script itself is cached by browsers for five minutes and may be served\nstale for a week if HelpBites cannot be reached. For a first visit that works\nwith HelpBites entirely down, serve \u003Ccode>onboarding.js\u003C\u002Fcode> from your own site as well\nand tell it where HelpBites is:\u003C\u002Fp>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-html\">\u003Ccode>\u003Cspan class=\"shj-syn-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">script\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">src\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"\u002Fjs\u002Fonboarding.js\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">data-host\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"https:\u002F\u002FYOUR-SUPPORT-HOST\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">data-project\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"pk_…\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">data-flow\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"welcome\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">data-fallback\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"#welcome-flow\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">defer\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">script\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&gt;\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>\u003Cstrong>What it tries, in order:\u003C\u002Fstrong> a copy it saved in the last ten minutes, then\nHelpBites (four seconds), then any copy it saved, then your offline copy. With\nnone of them, it shows nothing. A step whose image does not load shows without\nit.\u003C\u002Fp>\n\u003Ch2 id=\"who-finished-it\">Who finished it\u003Ca class=\"heading-anchor\" href=\"#who-finished-it\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>Hand the script the person's \u003Ca href=\"\u002Fdocs\u002Fconnect\">identity token\u003C\u002Fa> (the one the chat and\nthe board use) and it tells HelpBites when they finish or skip the flow. HelpBites\nkeeps the latest outcome on their contact, so you can build the segment\n\u003Cstrong>Finished onboarding\u003C\u002Fstrong> (Contacts → Segments → \u003Cem>Finished onboarding is yes\u003C\u002Fem>) and\nsend them a different sequence than the people who skipped it.\u003C\u002Fp>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-html\">\u003Ccode>\u003Cspan class=\"shj-syn-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">script\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">src\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"https:\u002F\u002FYOUR-SUPPORT-HOST\u002Fonboarding.js\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">data-project\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"pk_…\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">data-flow\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"welcome\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">data-token\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"eyJ…\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">defer\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">script\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&gt;\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>When the token is minted after the page loads, pass a function. It runs when the\nperson finishes, not before:\u003C\u002Fp>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-js\">\u003Ccode>\u003Cspan class=\"shj-syn-class\">HelpBitesOnboarding\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-syn-func\">push\u003C\u002Fspan>([\u003Cspan class=\"shj-syn-str\">'init'\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">,\u003C\u002Fspan> {\n  token\u003Cspan class=\"shj-syn-oper\">:\u003C\u002Fspan> () \u003Cspan class=\"shj-syn-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-syn-func\">fetch\u003C\u002Fspan>(\u003Cspan class=\"shj-syn-str\">'\u002Fapi\u002Fsupport\u002Ftoken'\u003C\u002Fspan>)\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-syn-func\">then\u003C\u002Fspan>(r \u003Cspan class=\"shj-syn-kwd\">=&gt;\u003C\u002Fspan> r\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-syn-func\">json\u003C\u002Fspan>())\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-syn-func\">then\u003C\u002Fspan>(r \u003Cspan class=\"shj-syn-kwd\">=&gt;\u003C\u002Fspan> r\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>token)\n}])\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>The report is best effort: with no token, a signed-out visitor, or a network\nerror, the script does nothing and the dialog behaves as before. Only a \u003Cstrong>Get\nstarted\u003C\u002Fstrong> on the last step counts as \u003Ccode>completed\u003C\u002Fcode>; \u003Cstrong>Skip\u003C\u002Fstrong> and Escape are \u003Ccode>skipped\u003C\u002Fcode>\nand do not match the segment. The latest outcome replaces the one before, so\nsomeone who skipped and later takes the tour again becomes a finisher.\u003C\u002Fp>\n\u003Cp>Without the script, send it yourself. The route is public (it takes the identity\ntoken, not a key), so it works from a browser, a server or a phone:\u003C\u002Fp>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-bash\">\u003Ccode>\u003Cspan class=\"shj-syn-func\">curl\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -s\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -X\u003C\u002Fspan> POST https:\u002F\u002FYOUR-SUPPORT-HOST\u002Fv1\u002Fonboarding\u002FPROJECT_PUBLIC_KEY\u002Fwelcome\u002Fcomplete \\\n \u003Cspan class=\"shj-syn-kwd\"> -H\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"authorization: Bearer \u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$IDENTITY_TOKEN\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -H\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">'content-type: application\u002Fjson'\u003C\u002Fspan> \\\n \u003Cspan class=\"shj-syn-kwd\"> -d\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">'{ \"outcome\": \"completed\" }'\u003C\u002Fspan>     \u003Cspan class=\"shj-syn-cmnt\"># or \"skipped\"\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>\u003Ccode>404\u003C\u002Fcode> when the key is not a flow of the token's project, \u003Ccode>401\u003C\u002Fcode> for a missing or\ninvalid token. The person's contact is created if it did not exist yet. Each\nreport also emits the \u003Ccode>onboarding.completed\u003C\u002Fcode> \u003Ca href=\"\u002Fdocs\u002Fevents\">event\u003C\u002Fa> (webhooks can subscribe to it, and a\nsequence can start from it) and shows up in the contact's activity (\u003Cstrong>Finished onboarding “welcome”\u003C\u002Fstrong>).\u003C\u002Fp>\n\u003Cp>From Swift, add this next to the snippet below and call it from \u003Ccode>onFinish\u003C\u002Fcode>:\u003C\u002Fp>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-plain\">\u003Ccode>extension HelpBitesOnboarding {\n  \u002F\u002F\u002F Tells HelpBites the person finished (true) or skipped (false) the flow. Best effort: it never throws.\n  static func report(host: URL, projectKey: String, flow key: String, token: String, completed: Bool) async {\n    var request = URLRequest(url: host.appending(path: \u003Cspan class=\"shj-syn-str\">\"v1\u002Fonboarding\u002F\\(projectKey)\u002F\\(key)\u002Fcomplete\"\u003C\u002Fspan>))\n    request.httpMethod = \u003Cspan class=\"shj-syn-str\">\"POST\"\u003C\u002Fspan>\n    request.setValue(\u003Cspan class=\"shj-syn-str\">\"Bearer \\(token)\"\u003C\u002Fspan>, forHTTPHeaderField: \u003Cspan class=\"shj-syn-str\">\"Authorization\"\u003C\u002Fspan>)\n    request.setValue(\u003Cspan class=\"shj-syn-str\">\"application\u002Fjson\"\u003C\u002Fspan>, forHTTPHeaderField: \u003Cspan class=\"shj-syn-str\">\"Content-Type\"\u003C\u002Fspan>)\n    request.httpBody = try? JSONEncoder().encode([\u003Cspan class=\"shj-syn-str\">\"outcome\"\u003C\u002Fspan>: completed ? \u003Cspan class=\"shj-syn-str\">\"completed\"\u003C\u002Fspan> : \u003Cspan class=\"shj-syn-str\">\"skipped\"\u003C\u002Fspan>])\n    _ = try? await URLSession.shared.data(for: request)\n  }\n}\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-plain\">\u003Ccode>ContentView()\n  .helpBitesOnboarding(host: host, projectKey: \u003Cspan class=\"shj-syn-str\">\"pk_…\"\u003C\u002Fspan>, flow: \u003Cspan class=\"shj-syn-str\">\"welcome\"\u003C\u002Fspan>) { completed in\n    Task { await HelpBitesOnboarding.report(host: host, projectKey: \u003Cspan class=\"shj-syn-str\">\"pk_…\"\u003C\u002Fspan>, flow: \u003Cspan class=\"shj-syn-str\">\"welcome\"\u003C\u002Fspan>, token: session.helpBitesToken, completed: completed) }\n  }\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Ch2 id=\"plug-and-play-swift\">Plug and play: Swift\u003Ca class=\"heading-anchor\" href=\"#plug-and-play-swift\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>Paste this file into your app. It needs no package: plain \u003Ccode>URLSession\u003C\u002Fcode> against\nthe public API.\u003C\u002Fp>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-plain\">\u003Ccode>\u002F\u002F HelpBitesOnboarding.swift. MIT. Shows a HelpBites onboarding flow once. iOS 17, macOS 14.\nimport SwiftUI\n\nstruct HelpBitesFlow: Codable, Identifiable, Equatable {\n  struct Step: Codable, Identifiable, Equatable { let id, title, body, imageUrl, imageAlt, buttonLabel: String }\n  struct Style: Codable, Equatable { let accent, background, text, font: String; let radius: Double }\n  struct Options: Codable, Equatable { let skippable: Bool; let progress: String }\n  let key, name, locale: String\n  let version: Int\n  let style: Style\n  let options: Options\n  let steps: [Step]\n  var id: String { key }\n}\n\nenum HelpBitesOnboarding {\n  \u002F\u002F\u002F The freshest copy that answers: HelpBites (4 s), then the last good copy, then `onboarding-&lt;key&gt;.json` in your bundle.\n  \u002F\u002F\u002F `locale` is the translation to ask for: the user's first language unless you say otherwise.\n  static func load(host: URL, projectKey: String, flow key: String, locale: String = Locale.preferredLanguages.first ?? \u003Cspan class=\"shj-syn-str\">\"en\"\u003C\u002Fspan>) async -&gt; HelpBitesFlow? {\n    let saved = URL.cachesDirectory.appending(path: \u003Cspan class=\"shj-syn-str\">\"helpbites-onboarding-\\(key)-\\(locale).json\"\u003C\u002Fspan>)\n    var url = URLComponents(url: host.appending(path: \u003Cspan class=\"shj-syn-str\">\"v1\u002Fonboarding\u002F\\(projectKey)\u002F\\(key)\"\u003C\u002Fspan>), resolvingAgainstBaseURL: false)!\n    url.queryItems = [URLQueryItem(name: \u003Cspan class=\"shj-syn-str\">\"locale\"\u003C\u002Fspan>, value: locale)]\n    let request = URLRequest(url: url.url!, cachePolicy: .reloadRevalidatingCacheData, timeoutInterval: 4)\n    if let answer = try? await URLSession.shared.data(for: request), let status = (answer.1 as? HTTPURLResponse)?.statusCode {\n      if status == 404 { try? FileManager.default.removeItem(at: saved); return nil }\n      if status == 200, let flow = decode(answer.0) { try? answer.0.write(to: saved); return flow }\n    }\n    if let data = try? Data(contentsOf: saved), let flow = decode(data) { return flow }\n    if let url = Bundle.main.url(forResource: \u003Cspan class=\"shj-syn-str\">\"onboarding-\\(key)\"\u003C\u002Fspan>, withExtension: \u003Cspan class=\"shj-syn-str\">\"json\"\u003C\u002Fspan>), let data = try? Data(contentsOf: url) { return decode(data) }\n    return nil\n  }\n\n  private struct Envelope: Decodable { let flow: HelpBitesFlow }\n  static func decode(_ data: Data) -&gt; HelpBitesFlow? { try? JSONDecoder().decode(Envelope.self, from: data).flow }\n}\n\nextension View {\n  \u002F\u002F\u002F Shows the flow once, or again after each publish with `everyVersion`. `onFinish(true)` when completed, `false` when skipped.\n  func helpBitesOnboarding(host: URL, projectKey: String, flow key: String, locale: String? = nil, everyVersion: Bool = false,\n                           accent: Color? = nil, cornerRadius: CGFloat? = nil, design: Font.Design? = nil,\n                           onFinish: @escaping (Bool) -&gt; Void = { _ in }) -&gt; some View {\n    modifier(HelpBitesOnboardingPresenter(host: host, projectKey: projectKey, key: key, locale: locale, everyVersion: everyVersion,\n                                          accent: accent, cornerRadius: cornerRadius, design: design, onFinish: onFinish))\n  }\n}\n\nprivate struct HelpBitesOnboardingPresenter: ViewModifier {\n  let host: URL, projectKey: String, key: String, locale: String?, everyVersion: Bool\n  let accent: Color?, cornerRadius: CGFloat?, design: Font.Design?\n  let onFinish: (Bool) -&gt; Void\n  @State private var flow: HelpBitesFlow?\n\n  func body(content: Content) -&gt; some View {\n    content\n      .task {\n        guard let loaded = await HelpBitesOnboarding.load(host: host, projectKey: projectKey, flow: key, locale: locale ?? Locale.preferredLanguages.first ?? \u003Cspan class=\"shj-syn-str\">\"en\"\u003C\u002Fspan>), !loaded.steps.isEmpty else { return }\n        let seen = UserDefaults.standard.integer(forKey: \u003Cspan class=\"shj-syn-str\">\"helpbites.onboarding.\\(key)\"\u003C\u002Fspan>)\n        if seen == 0 || (everyVersion &#38;&#38; seen &lt; loaded.version) { flow = loaded }\n      }\n      .sheet(item: $flow) { flow in\n        HelpBitesOnboardingView(flow: flow, accent: accent, cornerRadius: cornerRadius, design: design) { completed in\n          UserDefaults.standard.set(flow.version, forKey: \u003Cspan class=\"shj-syn-str\">\"helpbites.onboarding.\\(key)\"\u003C\u002Fspan>)\n          self.flow = nil\n          onFinish(completed)\n        }\n        .interactiveDismissDisabled(!flow.options.skippable)\n      }\n  }\n}\n\nstruct HelpBitesOnboardingView: View {\n  let flow: HelpBitesFlow\n  var accent: Color?\n  var cornerRadius: CGFloat?\n  var design: Font.Design?\n  let onFinish: (Bool) -&gt; Void\n  @State private var index = 0\n\n  var body: some View {\n    let step = flow.steps[index], last = index == flow.steps.count - 1\n    let tint = accent ?? Color(hex: flow.style.accent) ?? .accentColor\n    let onTint = accent == nil &#38;&#38; Color(hex: flow.style.accent) != nil ? Color.readable(on: flow.style.accent) : .white\n    let radius = cornerRadius ?? flow.style.radius\n    VStack(alignment: .leading, spacing: 16) {\n      HStack(spacing: 5) {\n        if flow.options.progress != \u003Cspan class=\"shj-syn-str\">\"none\"\u003C\u002Fspan> &#38;&#38; flow.steps.count &gt; 1 {\n          ForEach(flow.steps.indices, id: \\.self) { i in\n            let dots = flow.options.progress == \u003Cspan class=\"shj-syn-str\">\"dots\"\u003C\u002Fspan>\n            Capsule().fill(i == index || (!dots &#38;&#38; i &lt; index) ? tint : Color.secondary.opacity(0.2))\n              .frame(width: dots ? (i == index ? 18 : 6) : nil, height: dots ? 6 : 3)\n              .frame(maxWidth: dots ? nil : .infinity)\n          }\n        }\n        Spacer(minLength: 12)\n        if flow.options.skippable &#38;&#38; !last { Button(\u003Cspan class=\"shj-syn-str\">\"Skip\"\u003C\u002Fspan>) { onFinish(false) }.buttonStyle(.plain).foregroundStyle(.secondary) }\n      }\n      .frame(minHeight: 28)\n      if let url = URL(string: step.imageUrl), !step.imageUrl.isEmpty {\n        AsyncImage(url: url) { $0.resizable().scaledToFit() } placeholder: { Color.clear }\n          .frame(maxWidth: .infinity).aspectRatio(4 \u002F 3, contentMode: .fit)\n          .background(tint.opacity(0.1), in: .rect(cornerRadius: max(0, radius - 8)))\n          .accessibilityLabel(step.imageAlt)\n      }\n      Text(step.title).font(.title2.weight(.semibold))\n      Text(step.body).foregroundStyle(.secondary)\n      Spacer(minLength: 0)\n      HStack(spacing: 10) {\n        if index &gt; 0 { Button(\u003Cspan class=\"shj-syn-str\">\"Back\"\u003C\u002Fspan>) { withAnimation { index -= 1 } }.buttonStyle(.bordered).controlSize(.large) }\n        Button {\n          if last { onFinish(true) } else { withAnimation { index += 1 } }\n        } label: {\n          \u002F\u002F A step's own label is shown as written; the defaults are your app's own strings, so they follow its localisation.\n          (step.buttonLabel.isEmpty ? Text(LocalizedStringKey(last ? \u003Cspan class=\"shj-syn-str\">\"Get started\"\u003C\u002Fspan> : \u003Cspan class=\"shj-syn-str\">\"Continue\"\u003C\u002Fspan>)) : Text(verbatim: step.buttonLabel)).fontWeight(.semibold)\n            .frame(maxWidth: .infinity, minHeight: 46)\n            .background(tint, in: .rect(cornerRadius: max(6, radius * 0.6))).foregroundStyle(onTint)\n        }\n        .buttonStyle(.plain)\n      }\n    }\n    .padding(24)\n    .fontDesign(design ?? Self.designs[flow.style.font] ?? .default)\n    .background(Color(hex: flow.style.background) ?? Color.clear)\n    .foregroundStyle(Color(hex: flow.style.text).map { AnyShapeStyle($0) } ?? AnyShapeStyle(.primary))\n  }\n\n  private static let designs: [String: Font.Design] = [\u003Cspan class=\"shj-syn-str\">\"rounded\"\u003C\u002Fspan>: .rounded, \u003Cspan class=\"shj-syn-str\">\"serif\"\u003C\u002Fspan>: .serif, \u003Cspan class=\"shj-syn-str\">\"mono\"\u003C\u002Fspan>: .monospaced]\n}\n\nextension Color {\n  init?(hex: String) {\n    guard hex.count == 7, hex.hasPrefix(\u003Cspan class=\"shj-syn-str\">\"#\"\u003C\u002Fspan>), let v = Int(hex.dropFirst(), radix: 16) else { return nil }\n    self.init(red: Double(v &gt;&gt; 16 &#38; 255) \u002F 255, green: Double(v &gt;&gt; 8 &#38; 255) \u002F 255, blue: Double(v &#38; 255) \u002F 255)\n  }\n\n  \u002F\u002F\u002F Black or white, whichever reads better on `hex`.\n  static func readable(on hex: String) -&gt; Color {\n    let v = Int(hex.dropFirst(), radix: 16) ?? 0\n    let lin = { (c: Int) -&gt; Double in let s = Double(c) \u002F 255; return s &lt;= 0.03928 ? s \u002F 12.92 : pow((s + 0.055) \u002F 1.055, 2.4) }\n    return 0.2126 * lin(v &gt;&gt; 16 &#38; 255) + 0.7152 * lin(v &gt;&gt; 8 &#38; 255) + 0.0722 * lin(v &#38; 255) &gt; 0.179 ? .black : .white\n  }\n}\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>Then, on your first screen:\u003C\u002Fp>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-plain\">\u003Ccode>ContentView()\n  .helpBitesOnboarding(host: URL(string: \u003Cspan class=\"shj-syn-str\">\"https:\u002F\u002FYOUR-SUPPORT-HOST\"\u003C\u002Fspan>)!, projectKey: \u003Cspan class=\"shj-syn-str\">\"pk_…\"\u003C\u002Fspan>, flow: \u003Cspan class=\"shj-syn-str\">\"welcome\"\u003C\u002Fspan>)\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>\u003Cstrong>Languages.\u003C\u002Fstrong> The flow is requested in the user's first language\n(\u003Ccode>Locale.preferredLanguages\u003C\u002Fcode>), or the one you pass as \u003Ccode>locale:\u003C\u002Fcode>. HelpBites answers\nwith that translation when the flow has it and with the flow's own language\notherwise (see \u003Ca href=\"\u002Fdocs\u002Flanguages\">Languages\u003C\u002Fa>). The words the snippet itself adds\n(&quot;Continue&quot;, &quot;Get started&quot;, &quot;Skip&quot;, &quot;Back&quot;) are your app's strings: add them to\nyour \u003Ccode>Localizable.xcstrings\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>Customise it\u003C\u002Fstrong> with \u003Ccode>accent\u003C\u002Fcode>, \u003Ccode>cornerRadius\u003C\u002Fcode> and \u003Ccode>design\u003C\u002Fcode> (\u003Ccode>.rounded\u003C\u002Fcode>,\n\u003Ccode>.serif\u003C\u002Fcode>…), or skip the presenter and put \u003Ccode>HelpBitesOnboardingView\u003C\u002Fcode> wherever you\nlike: in a \u003Ccode>fullScreenCover\u003C\u002Fcode>, a page of your own, or a \u003Ccode>TabView\u003C\u002Fcode>. It is your\nfile, so change anything.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>Offline.\u003C\u002Fstrong> Add the panel's \u003Cstrong>Download offline copy\u003C\u002Fstrong> file\n(\u003Ccode>onboarding-welcome.json\u003C\u002Fcode>) to your app target. A first launch without a\nnetwork then shows that copy; later launches show the last copy they saved in\nthe Caches folder. Images load from their URLs and are left out when they\ncannot load; for a fully offline first launch, use flows without images or point\nthem at files your app can reach.\u003C\u002Fp>\n\u003Ch2 id=\"your-own-ui\">Your own UI\u003Ca class=\"heading-anchor\" href=\"#your-own-ui\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>The flow is one public request, open to any origin:\u003C\u002Fp>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-bash\">\u003Ccode>\u003Cspan class=\"shj-syn-func\">curl\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -s\u003C\u002Fspan> https:\u002F\u002FYOUR-SUPPORT-HOST\u002Fv1\u002Fonboarding\u002FPROJECT_PUBLIC_KEY\u002Fwelcome\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-json\">\u003Ccode>{\n  \u003Cspan class=\"shj-syn-var\">\"flow\"\u003C\u002Fspan>: {\n    \u003Cspan class=\"shj-syn-var\">\"key\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"welcome\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"name\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"Welcome\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"locale\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"en\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"locales\"\u003C\u002Fspan>: [\u003Cspan class=\"shj-syn-str\">\"en\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-str\">\"es\"\u003C\u002Fspan>], \u003Cspan class=\"shj-syn-var\">\"version\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">3\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"publishedAt\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"2026-10-21T09:00:00.000Z\"\u003C\u002Fspan>,\n    \u003Cspan class=\"shj-syn-var\">\"project\"\u003C\u002Fspan>: { \u003Cspan class=\"shj-syn-var\">\"name\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"Acme\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"logoUrl\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"https:\u002F\u002Fcdn.acme.test\u002Ficon.png\"\u003C\u002Fspan> },\n    \u003Cspan class=\"shj-syn-var\">\"style\"\u003C\u002Fspan>: { \u003Cspan class=\"shj-syn-var\">\"accent\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"#0ea5e9\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"background\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"text\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"radius\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">20\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"font\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"system\"\u003C\u002Fspan> },\n    \u003Cspan class=\"shj-syn-var\">\"options\"\u003C\u002Fspan>: { \u003Cspan class=\"shj-syn-var\">\"skippable\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-bool\">true\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"progress\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"bar\"\u003C\u002Fspan> },\n    \u003Cspan class=\"shj-syn-var\">\"steps\"\u003C\u002Fspan>: [{ \u003Cspan class=\"shj-syn-var\">\"id\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"st_1\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"title\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"Welcome to Acme\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"body\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"Track every order in one place.\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"imageUrl\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"imageAlt\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"buttonLabel\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"\"\u003C\u002Fspan> }]\n  }\n}\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cul>\n\u003Cli>\u003Ccode>404\u003C\u002Fcode> for an unknown project, an unknown key, or a flow that is not published.\nTreat it as &quot;show nothing&quot;, and drop any copy you saved.\u003C\u002Fli>\n\u003Cli>\u003Ccode>?locale=es\u003C\u002Fcode> (or an \u003Ccode>Accept-Language\u003C\u002Fcode> header) asks for a translation. You get\nthe first language the flow has, matching \u003Ccode>es-MX\u003C\u002Fcode> to \u003Ccode>es\u003C\u002Fcode>, and the flow's own\nlanguage when it has none; \u003Ccode>locale\u003C\u002Fcode> says which one you got and \u003Ccode>locales\u003C\u002Fcode> lists\nevery language the flow has. Answers \u003Ccode>Vary\u003C\u002Fcode> on \u003Ccode>Accept-Language\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>Answers carry an \u003Ccode>ETag\u003C\u002Fcode> (send it back as \u003Ccode>If-None-Match\u003C\u002Fcode> for a \u003Ccode>304\u003C\u002Fcode>) and may be\ncached for 60 seconds. A CDN in front of HelpBites may serve a stale answer for\na day while it refreshes, and for a week if HelpBites is down\n(\u003Ccode>stale-if-error\u003C\u002Fcode>).\u003C\u002Fli>\n\u003Cli>Reads are limited to 1,200 a minute per IP address (\u003Ccode>429\u003C\u002Fcode> past that).\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Remember yourself whether a user has seen a version: store the \u003Ccode>version\u003C\u002Fcode> they\nfinished, and show the flow again when the API returns a higher one if you want\nthem to see updates.\u003C\u002Fp>\n\u003Ch2 id=\"managing-flows-over-the-api\">Managing flows over the API\u003Ca class=\"heading-anchor\" href=\"#managing-flows-over-the-api\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>Use an admin API key (\u003Cstrong>API keys\u003C\u002Fstrong>) or an operator session. Every route is\nscoped to your workspace; a key limited to some projects only reaches those.\u003C\u002Fp>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-bash\">\u003Ccode>\u003Cspan class=\"shj-syn-var\">HOST\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>https:\u002F\u002FYOUR-SUPPORT-HOST\n\u003Cspan class=\"shj-syn-var\">KEY\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>ak_...\n\n\u003Cspan class=\"shj-syn-cmnt\"># Create a flow (it starts with three starter steps unless you send `steps`)\u003C\u002Fspan>\n\u003Cspan class=\"shj-syn-func\">curl\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -s\u003C\u002Fspan> \u003Cspan class=\"shj-syn-var\">$HOST\u003C\u002Fspan>\u002Fv1\u002Fadmin\u002Fonboarding\u002Fflows\u003Cspan class=\"shj-syn-kwd\"> -H\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"authorization: Bearer \u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$KEY\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -H\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">'content-type: application\u002Fjson'\u003C\u002Fspan> \\\n \u003Cspan class=\"shj-syn-kwd\"> -d\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">'{ \"project\": \"PROJECT_ID\", \"name\": \"Welcome\", \"key\": \"welcome\" }'\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-syn-cmnt\"># Replace its steps and change its look\u003C\u002Fspan>\n\u003Cspan class=\"shj-syn-func\">curl\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -s\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -X\u003C\u002Fspan> PATCH \u003Cspan class=\"shj-syn-var\">$HOST\u003C\u002Fspan>\u002Fv1\u002Fadmin\u002Fonboarding\u002Fflows\u002FFLOW_ID\u003Cspan class=\"shj-syn-kwd\"> -H\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"authorization: Bearer \u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$KEY\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -H\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">'content-type: application\u002Fjson'\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -d\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">'{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-syn-str\">\"steps\"\u003C\u002Fspan>: [\n    \u003Cspan class=\"shj-syn-oper\">{\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"title\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"Welcome to Acme\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-str\">\"body\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"Track every order in one place.\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-oper\">}\u003C\u002Fspan>,\n    \u003Cspan class=\"shj-syn-oper\">{\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"title\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"Get notified\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-str\">\"body\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"We tell you when an order ships.\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-str\">\"buttonLabel\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"Turn on alerts\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-oper\">}\u003C\u002Fspan>\n  ],\n  \u003Cspan class=\"shj-syn-str\">\"style\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-oper\">{\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"accent\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"#0ea5e9\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-str\">\"font\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"rounded\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-oper\">}\u003C\u002Fspan>,\n  \u003Cspan class=\"shj-syn-str\">\"options\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-oper\">{\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"progress\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"dots\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-oper\">}\u003C\u002Fspan>\n\u003Cspan class=\"shj-syn-oper\">}\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">'\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-syn-cmnt\"># Publish it (version 1)\u003C\u002Fspan>\n\u003Cspan class=\"shj-syn-func\">curl\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -s\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -X\u003C\u002Fspan> POST \u003Cspan class=\"shj-syn-var\">$HOST\u003C\u002Fspan>\u002Fv1\u002Fadmin\u002Fonboarding\u002Fflows\u002FFLOW_ID\u002Fpublish\u003Cspan class=\"shj-syn-kwd\"> -H\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"authorization: Bearer \u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">$KEY\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cdiv class=\"table-wrap\">\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Route\u003C\u002Fth>\n\u003Cth>What it does\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>GET \u002Fv1\u002Fadmin\u002Fonboarding\u002Fflows?project=\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Every flow, with its working copy, \u003Ccode>published\u003C\u002Fcode> (what apps get, or \u003Ccode>null\u003C\u002Fcode>) and \u003Ccode>changed\u003C\u002Fcode>.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>POST \u002Fv1\u002Fadmin\u002Fonboarding\u002Fflows\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Create a draft. \u003Ccode>key\u003C\u002Fcode> must be unused in the project (\u003Ccode>409\u003C\u002Fcode>).\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>GET \u002Fv1\u002Fadmin\u002Fonboarding\u002Fflows\u002F{id}\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>One flow.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>PATCH \u002Fv1\u002Fadmin\u002Fonboarding\u002Fflows\u002F{id}\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Change \u003Ccode>name\u003C\u002Fcode>, \u003Ccode>key\u003C\u002Fcode>, \u003Ccode>steps\u003C\u002Fcode> (the full list), \u003Ccode>style\u003C\u002Fcode> or \u003Ccode>options\u003C\u002Fcode> (merged), \u003Ccode>locale\u003C\u002Fcode> (the language it is written in) or \u003Ccode>translations\u003C\u002Fcode> (below).\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>POST \u002Fv1\u002Fadmin\u002Fonboarding\u002Fflows\u002F{id}\u002Ftranslate\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Translate the working copy with AI into \u003Ccode>locales\u003C\u002Fcode>, e.g. \u003Ccode>[&quot;es&quot;,&quot;fr&quot;]\u003C\u002Fcode>; \u003Ccode>overwrite: true\u003C\u002Fcode> redoes strings that already have a translation.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>POST \u002Fv1\u002Fadmin\u002Fonboarding\u002Fflows\u002F{id}\u002Fpublish\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Publish the working copy as the next version. \u003Ccode>400\u003C\u002Fcode> with no steps.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>POST \u002Fv1\u002Fadmin\u002Fonboarding\u002Fflows\u002F{id}\u002Funpublish\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Back to a draft; the public URL answers \u003Ccode>404\u003C\u002Fcode>.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>DELETE \u002Fv1\u002Fadmin\u002Fonboarding\u002Fflows\u002F{id}\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Delete it.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>GET \u002Fv1\u002Fonboarding\u002F{pk}\u002F{key}\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Public: the published flow.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\u003C\u002Fdiv>\n\u003Cp>Invalid values answer \u003Ccode>400\u003C\u002Fcode> with every problem listed. The full schema is in the\nOpenAPI docs at \u003Ccode>\u002F_docs\u002Fscalar\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch2 id=\"languages\">Languages\u003Ca class=\"heading-anchor\" href=\"#languages\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>A flow is written in one language (\u003Ccode>locale\u003C\u002Fcode>, default \u003Ccode>en\u003C\u002Fcode>) and can carry a\ntranslation per language: its name and, for each step, the \u003Ccode>title\u003C\u002Fcode>, \u003Ccode>body\u003C\u002Fcode>,\n\u003Ccode>imageAlt\u003C\u002Fcode> and \u003Ccode>buttonLabel\u003C\u002Fcode>. A word a translation leaves out is shown in the\nflow's own language, so a half-translated flow never shows a blank.\u003C\u002Fp>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-json\">\u003Ccode>\u003Cspan class=\"shj-syn-var\">\"locale\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"en\"\u003C\u002Fspan>,\n\u003Cspan class=\"shj-syn-var\">\"translations\"\u003C\u002Fspan>: {\n  \u003Cspan class=\"shj-syn-var\">\"es\"\u003C\u002Fspan>: { \u003Cspan class=\"shj-syn-var\">\"name\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"Bienvenida\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"steps\"\u003C\u002Fspan>: { \u003Cspan class=\"shj-syn-var\">\"st_1\"\u003C\u002Fspan>: { \u003Cspan class=\"shj-syn-var\">\"title\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"Te damos la bienvenida a Acme\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"body\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"Sigue todos tus pedidos en un solo lugar.\"\u003C\u002Fspan> } } }\n}\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>Send \u003Ccode>translations\u003C\u002Fcode> to \u003Ccode>PATCH\u003C\u002Fcode> to write them yourself: a language you send\nreplaces that language, and \u003Ccode>null\u003C\u002Fcode> removes it. \u003Cstrong>Translate with AI\u003C\u002Fstrong> (in the\npanel, or \u003Ccode>POST …\u002Ftranslate\u003C\u002Fcode>) fills the missing words and keeps what you wrote.\nTranslations are part of the version you publish, so they reach apps with it.\nSee \u003Ca href=\"\u002Fdocs\u002Flanguages\">Languages\u003C\u002Fa> for how the AI translation works.\u003C\u002Fp>\n",[6,10,13,16,20,23,26,29,32,35,38],{"id":7,"text":8,"level":9},"a-flow","A flow",2,{"id":11,"text":12,"level":9},"drafts-and-versions","Drafts and versions",{"id":14,"text":15,"level":9},"plug-and-play-web","Plug and play: web",{"id":17,"text":18,"level":19},"options","Options",3,{"id":21,"text":22,"level":19},"restyle-it","Restyle it",{"id":24,"text":25,"level":19},"offline-copy","Offline copy",{"id":27,"text":28,"level":9},"who-finished-it","Who finished it",{"id":30,"text":31,"level":9},"plug-and-play-swift","Plug and play: Swift",{"id":33,"text":34,"level":9},"your-own-ui","Your own UI",{"id":36,"text":37,"level":9},"managing-flows-over-the-api","Managing flows over the API",{"id":39,"text":40,"level":9},"languages","Languages",1792677543076]