Skip to main content

Workflows

Every call needs Authorization: Bearer <key>. 15 calls:

Start from a template​

Choose New workflow, name it, and pick Start from a template. Each template is a ready-made workflow that follows good texting practice: it uses the contact’s first name, says who is texting, asks one question per message, and its first message ends with “Reply stop to opt out.” The templates are grouped by what they’re for. Pick one to see its steps as a small flow below the list, with any note to read before you use it.

New leads

  • Speed to lead: a new lead gets a text at once, then a follow-up if they don’t reply.
  • Abandoned checkout or partial form: a label starts follow-ups over a few days, and they stop when the label is removed or a label such as “Customer” is added.

Bookings and appointments

  • Book a consultation: someone texts about booking and gets your booking link (Calendly, Acuity and the like), then a reminder and a last nudge.
  • Appointment reminder and no-show: a reminder before the appointment, and a text offering a new time if a no-show label is added.

Follow-up and win-back

  • Review request: a thank-you after a visit or purchase that asks for a review, and the review link once they say yes.
  • Re-engage cold leads: a daily check-in with a segment of contacts who haven’t replied in a while, once each.

The template opens as a draft with a setup checklist: the things only you can choose, such as the label, the segment, your booking or review link and how long each wait lasts. Items tick themselves as you fill the fields in; Use 1 day (or the suggested length) fills a wait in one click. Text you must replace looks like [[your business name]] and is flagged on the step. You can save a draft with placeholders, but publishing it, or starting a live test, is refused until none is left, and the problem names the step. Items marked Required are checked when you publish; Recommended ones, such as the label that means a customer finished, aren’t, so while any item is open Publish and Start test ask first and list them.

Contacts without a first name all get the same text (“Hi there, …”), and a line sends one text to a limited number of people a day. For a big batch of nameless contacts, add a detail that varies, such as {company}, or keep batches small. Speed to lead and Re-engage cold leads say so in a short note when you pick them; Why? opens the full explanation.

From the API, call List the workflow templates and Get a workflow template with its graph and setup checklist, then create the workflow with its trigger, trigger settings, graph, re-entry and exit rules.

Describe it to AI​

Pick Describe it to AI under New workflow and say in plain words what should happen, up to 1,000 characters, for example “When someone abandons checkout, text them, wait a day for a reply, and stop if they buy.” A draft opens on the canvas with a note on anything it assumed. It isn’t saved until you choose Save draft, and it never publishes itself: review it, test it and publish it as usual.

  • The AI is given your description, the steps and triggers workflows support, and your label, segment, event and workflow names. Never your contacts or messages.
  • Its draft must pass the same checks as a saved draft. If it doesn’t after one retry, bubbl3 opens the closest template instead and says so.
  • AI drafting isn’t switched on yet. Until it is, bubbl3 opens the template closest to your description, with the note “Started from the … template. AI drafting isn’t switched on yet.”
  • Each workspace can draft 20 times a day, whichever way the draft is made. AI drafts count toward your organisation’s daily AI budget.

From the API, call Draft a workflow from a description. It returns the draft and saves nothing; test-mode API keys are refused.

Test a workflow​

Before you publish, open the workflow in the dashboard and choose Test. Simulate runs the workflow as it is on the canvas, unsaved changes included, for one of your contacts or a sample contact you type in. Nothing is sent or saved: no runs, messages, labels, events or audit entries.

  • The canvas highlights each step the contact reaches, and the panel lists them: each message as it would read with the contact’s details filled in, each wait with its length, each condition with its answer and why, and what the other steps would do.
  • Where a real run would wait, the panel asks what happened: Replied (with the reply’s text) or No reply at a Wait for reply, and Skip ahead at a Wait. A reply that is a STOP keyword ends the test as an opt-out, as it would end a real run.
  • Flip a condition to try its other path, try one of the workflow’s exit rules, and use Back one step and Reset to try every branch.
  • The panel warns about anything publishing would refuse, a send that would be skipped (the contact opted out, an empty message, no line) and a send that would wait for quiet hours.
  • Simulate uses the same rules as a real run. It doesn’t follow Start another workflow into the other workflow, doesn’t call an AI model, and can’t tell which line a message would go out on or when the sending limits would let it go.

From the API, call Simulate a workflow for one contact, sending nothing with the decisions so far: each call replays them from the start.

Live test​

Live test runs the saved draft for real, even before it is published, for a test contact: a contact an owner or admin marked as one on its page (up to 10 per workspace, such as staff phones). Pick the test contact and the options, then Start test. The panel follows the run as it moves and highlights the step it is on; Cancel test ends it. The draft must pass the publish checks first, and a draft a test ran on is kept as it was: later edits go to a new draft version.

  • Texts go through the normal send path: consent, opt-outs, quiet hours and line limits all apply, and they count against the line like any other.
  • Shorten waits makes every Wait and Wait for reply last 1 minute.
  • Fire event steps are skipped unless Send Fire event steps is on. Then the event goes only to its webhook URL destinations, marked test: true, and never to a CRM or ad platform, so a test can’t record a fake conversion. The old Webhook step follows the same rule.
  • Update CRM field and Start another workflow are always skipped. Label and Assign happen for real, but a label a test changes doesn’t start other workflows or end their runs. Remove from workflow only cancels the contact’s test runs.
  • STOP and the workflow’s exit rules end a test run as they end a real one. A test only texts a contact while they are a test contact: unmarking them, or changing their phone, ends their open tests.
  • While a test runs, the test contact’s replies reach the test but start none of your other workflows, and neither the test’s texts nor those replies are logged to your CRM or counted as conversions.
  • An AI step really hands the conversation to its agent, and the agent keeps it after the test ends: hand it back from the inbox.
  • Test runs show in the run log with a Test tag (choose Test runs to see them) and don’t count in the workflow’s run totals, its daily limit or its re-entry setting. They send no workflow.run.completed event. A contact’s test run and real run don’t block each other; one test run at a time per contact and workflow.

From the API, mark a test contact with test_contact on PATCH /v1/contacts/:id, then call Start a live test of the latest version for one test contact and Cancel a live test run. Test-mode API keys can’t start or cancel one. Listing runs returns real runs unless you pass test=true or test=all.

Archive or delete a workflow​

Pause holds a live workflow; Archive ends one for good. Open the workflow and choose Archive, on a draft, live or paused workflow. No new contacts start it, and contacts in it now stop where they are: the confirmation says how many. If other live or paused workflows start it with a Start another workflow step, the confirmation lists them; from then on they skip that step and go on to the next one. There is no undo.

  • Archived workflows leave the Workflows list. Choose Archived above the list to see them. One opens read-only, with its steps, versions and run history.
  • A draft that was never published and never ran, not even a live test, shows Delete draft instead. Deleting removes it for good. A draft that another workflow’s Start another workflow or Remove from workflow step names, or that an inbound source starts, can only be archived until you change those.

From the API, archive with Edit the draft, or pause, resume or archive a workflow and status: archived, and delete with Delete a draft that was never published or run. Each workflow has can_delete, so you know whether a delete would work; one that wouldn’t is refused with 409 and the reason. Listing workflows returns every status unless you pass status, or exclude_archived=true to leave archived ones out. Deleting sends no webhook event, and test-mode API keys can’t archive or delete.

List workflow runs, newest first​

GET /v1/workflow-runs

Query parameters

NameTypeRequiredDescription
limitintegerNoFrom 1 to 100. Default 50.
cursorstringNoOpaque cursor from next_cursor. Up to 512 characters.
workflow_idstring (uuid)No
statusenumNoOne of: running, waiting, completed, failed, cancelled.
contact_idstringNoContact id (ctc_...).
testenumNoReal runs by default (false). true lists only live test runs, all both. One of: true, false, all.

Headers

NameTypeRequiredDescription
x-workspace-idstringNoWorkspace for session (JWT) callers. API keys carry their own workspace.

Response 200

FieldTypeDescription
dataobject[]
data[].contact_idstring or nullContact id (ctc_...).
data[].error_codestring or null
data[].finished_atstring (date-time) or nullISO 8601 timestamp.
data[].idstring (uuid)
data[].resume_atstring (date-time) or nullISO 8601 timestamp.
data[].started_atstring (date-time)ISO 8601 timestamp.
data[].statusenumOne of: running, waiting, completed, failed, cancelled.
data[].stepsobject[]
data[].steps[].atstring (date-time)ISO 8601 timestamp.
data[].steps[].detailobject
data[].steps[].node_idstring
data[].steps[].statusenumOne of: completed, skipped, failed, waiting, cancelled.
data[].testbooleanA live test started from the editor. Test runs don't count in the workflow's run totals or its limits.
data[].test_optionsobject or null
data[].test_options.send_eventsbooleanFire event and Webhook steps send, marked test: true, to webhook URLs only. Off: they are skipped.
data[].test_options.shorten_waitsbooleanEvery Wait and Wait for reply lasts 1 minute.
data[].versionintegerFrom 1 to 9007199254740991.
data[].workflow_idstring (uuid)
data[].workflow_version_idstring (uuid)
next_cursorstring or nullPass as cursor to get the next page; null at the end.

Errors: 400, 401, 403, 404, 409, 413, 415, 429, 500. See errors for the shape and codes.

Get a workflow run and its steps​

GET /v1/workflow-runs/:id

Path parameters

NameTypeRequiredDescription
idstring (uuid)Yes

Headers

NameTypeRequiredDescription
x-workspace-idstringNoWorkspace for session (JWT) callers. API keys carry their own workspace.

Response 200

FieldTypeDescription
contact_idstring or nullContact id (ctc_...).
error_codestring or null
finished_atstring (date-time) or nullISO 8601 timestamp.
idstring (uuid)
resume_atstring (date-time) or nullISO 8601 timestamp.
started_atstring (date-time)ISO 8601 timestamp.
statusenumOne of: running, waiting, completed, failed, cancelled.
stepsobject[]
steps[].atstring (date-time)ISO 8601 timestamp.
steps[].detailobject
steps[].node_idstring
steps[].statusenumOne of: completed, skipped, failed, waiting, cancelled.
testbooleanA live test started from the editor. Test runs don't count in the workflow's run totals or its limits.
test_optionsobject or null
test_options.send_eventsbooleanFire event and Webhook steps send, marked test: true, to webhook URLs only. Off: they are skipped.
test_options.shorten_waitsbooleanEvery Wait and Wait for reply lasts 1 minute.
versionintegerFrom 1 to 9007199254740991.
workflow_idstring (uuid)
workflow_version_idstring (uuid)

Errors: 400, 401, 403, 404, 409, 413, 415, 429, 500. See errors for the shape and codes.

List the workflow templates​

GET /v1/workflow-templates

The starting points the dashboard offers under New workflow: each with a name, a one-line description, when to use it, its trigger, the gallery group it belongs to (category), its main steps in words (preview) and the same steps with the kind of each (preview_steps), and any tip with a one-sentence version (tip_short). Get one for its graph and setup checklist.

Headers

NameTypeRequiredDescription
x-workspace-idstringNoWorkspace for session (JWT) callers. API keys carry their own workspace.

Response 200

FieldTypeDescription
dataobject[]
data[].categoryenumOne of: new_leads, bookings, follow_up.
data[].descriptionstring
data[].idenumOne of: speed_to_lead, abandoned_checkout, book_consultation, appointment_reminder, review_request, reengage_cold.
data[].namestring
data[].previewstring[]
data[].preview_stepsobject[]
data[].preview_steps[].kindenumOne of: trigger, send, wait, wait_for_reply, branch, label, event, stop.
data[].preview_steps[].textstring
data[].tipstring or null
data[].tip_shortstring or null
data[].triggerenumOne of: message.received, contact.created, schedule, webhook, crm.lead_imported, crm.contact_updated, label.added, label.removed.
data[].use_casestring

Errors: 400, 401, 403, 404, 409, 413, 415, 429, 500. See errors for the shape and codes.

Get a workflow template with its graph and setup checklist​

GET /v1/workflow-templates/:id

The template's trigger, trigger settings, graph, re-entry and exit rules, ready to send to POST /v1/workflows, and its setup checklist: what the business must choose or fill in before it can be published. Message text has [[placeholders]], such as [[your business name]]; publishing refuses a draft that still has one.

Path parameters

NameTypeRequiredDescription
idstringYesUp to 64 characters.

Headers

NameTypeRequiredDescription
x-workspace-idstringNoWorkspace for session (JWT) callers. API keys carry their own workspace.

Response 200

FieldTypeDescription
categoryenumOne of: new_leads, bookings, follow_up.
checklistobject[]
checklist[].hintstring
checklist[].idstring
checklist[].placeholderstring or null
checklist[].requiredboolean
checklist[].suggested_minutesinteger or nullFrom 1 to 9007199254740991.
checklist[].targetsobject[]
checklist[].targets[].fieldstring
checklist[].targets[].kindenumOne of: step.
checklist[].targets[].node_idstring
checklist[].taskenumOne of: fill_in, pick_label, pick_event, pick_segment, set_wait, check_words.
checklist[].titlestring
descriptionstring
graphobject
graph.edgesobject[]Up to 400 items.
graph.edges[].fromstringUp to 64 characters.
graph.edges[].labelstringUp to 40 characters.
graph.edges[].tostringUp to 64 characters.
graph.nodesobject[]Up to 200 items.
graph.nodes[].configobjectDefault {}.
graph.nodes[].idstringUp to 64 characters.
graph.nodes[].positionobject
graph.nodes[].typeenumOne of: trigger, condition, wait, send, assign, label, webhook, ai, crm_update, conversion_event, wait_for_reply, start_workflow, remove_from_workflow.
idenumOne of: speed_to_lead, abandoned_checkout, book_consultation, appointment_reminder, review_request, reengage_cold.
namestring
previewstring[]
preview_stepsobject[]
preview_steps[].kindenumOne of: trigger, send, wait, wait_for_reply, branch, label, event, stop.
preview_steps[].textstring
settingsobject
settings.exit_rulesobject[]
settings.exit_rules[].kindenumOne of: label_removed.
settings.exit_rules[].label_idstring (uuid)
settings.reentryenumOne of: again, once.
tipstring or null
tip_shortstring or null
triggerenumOne of: message.received, contact.created, schedule, webhook, crm.lead_imported, crm.contact_updated, label.added, label.removed.
trigger_configobject
use_casestring

Errors: 400, 401, 403, 404, 409, 413, 415, 429, 500. See errors for the shape and codes.

List workflows, most recently edited first​

GET /v1/workflows

Query parameters

NameTypeRequiredDescription
limitintegerNoFrom 1 to 100. Default 50.
cursorstringNoOpaque cursor from next_cursor. Up to 512 characters.
statusenumNoOne of: draft, live, paused, archived.
exclude_archivedenumNotrue leaves archived workflows out. Without status or this, every status is listed. Not with status=archived. One of: true, false.

Headers

NameTypeRequiredDescription
x-workspace-idstringNoWorkspace for session (JWT) callers. API keys carry their own workspace.

Response 200

FieldTypeDescription
dataobject[]
data[].active_contactsintegerContacts in the workflow now: with a running or waiting run, live tests included. Archiving stops them. From 0 to 9007199254740991.
data[].can_deletebooleanWhether DELETE /v1/workflows/{id} would delete it now: a draft never published, with no runs (live tests included), that no other workflow starts or removes contacts from and no inbound source starts. Otherwise archive it.
data[].created_atstring (date-time)ISO 8601 timestamp.
data[].exit_rulesobject[]
data[].exit_rules[].kindenumOne of: label_removed.
data[].exit_rules[].label_idstring (uuid)
data[].graphobject
data[].graph.edgesobject[]Up to 400 items.
data[].graph.nodesobject[]Up to 200 items.
data[].has_draftboolean
data[].idstring (uuid)
data[].namestring
data[].published_versioninteger or nullFrom 1 to 9007199254740991.
data[].published_version_idstring (uuid) or null
data[].reentryenumagain (default): a contact can go through again once their last run ends. once: a contact who has ever had a run of this workflow never starts another. One of: again, once.
data[].runs_30dobject
data[].runs_30d.completedintegerFrom 0 to 9007199254740991.
data[].runs_30d.failedintegerFrom 0 to 9007199254740991.
data[].runs_30d.totalintegerFrom 0 to 9007199254740991.
data[].started_byobject[]Other live or paused workflows whose published version has a Start another workflow step that starts this one. Once it's archived, those steps are skipped.
data[].started_by[].idstring (uuid)
data[].started_by[].namestring
data[].statusenumOne of: draft, live, paused, archived.
data[].triggerenumOne of: message.received, contact.created, schedule, webhook, crm.lead_imported, crm.contact_updated, label.added, label.removed.
data[].trigger_configobject
data[].updated_atstring (date-time)ISO 8601 timestamp.
data[].warningsobject[]Send steps a contact who hasn't replied yet wouldn't get. None for workflows an inbound message starts: that contact has replied.
data[].warnings[].codestringThe governor code a first message with this text would fail with.
data[].warnings[].messagestring
data[].warnings[].node_idstring
next_cursorstring or nullPass as cursor to get the next page; null at the end.

Errors: 400, 401, 403, 404, 409, 413, 415, 429, 500. See errors for the shape and codes.

Create a draft workflow​

POST /v1/workflows

The workflow starts as a draft; publish it to turn it on. Step types in graph.nodes[].type are the dashboard’s steps: conversion_event is “Fire event”, which fires an event from Settings, Events to its webhook URLs, CRM and ad platforms. webhook is the older “Webhook” step: the dashboard no longer offers it for new steps, but the API still accepts it and existing ones keep running. Test-mode API keys are refused, because workflows send live messages.

Headers

NameTypeRequiredDescription
x-workspace-idstringNoWorkspace for session (JWT) callers. API keys carry their own workspace.
idempotency-keystringNoMakes the write safe to retry for 24 hours. 1 to 255 printable characters. Keys starting with outreach:, workflow: or agent: are reserved for messages bubbl3 sends itself and are refused with 400. Up to 255 characters.

Request body

FieldTypeRequiredDescription
exit_rulesobject[]NoCancel a running or waiting run when one of these happens. A run follows the rules of the version it started on. Up to 20 items.
exit_rules[].kindenumYesOne of: label_removed.
exit_rules[].label_idstring (uuid)Yes
graphobjectNoDefault {"edges":[],"nodes":[]}.
graph.edgesobject[]YesUp to 400 items.
graph.edges[].fromstringYesUp to 64 characters.
graph.edges[].labelstringNoUp to 40 characters.
graph.edges[].tostringYesUp to 64 characters.
graph.nodesobject[]YesUp to 200 items.
graph.nodes[].configobjectNoDefault {}.
graph.nodes[].idstringYesUp to 64 characters.
graph.nodes[].positionobjectNo
graph.nodes[].typeenumYesOne of: trigger, condition, wait, send, assign, label, webhook, ai, crm_update, conversion_event, wait_for_reply, start_workflow, remove_from_workflow.
namestringYesUp to 120 characters.
reentryenumNoagain (default): a contact can go through again once their last run ends. once: a contact who has ever had a run of this workflow never starts another. One of: again, once. Default "again".
triggerenumYesOne of: message.received, contact.created, schedule, webhook, crm.lead_imported, crm.contact_updated, label.added, label.removed.
trigger_configobjectNoDefault {}.

Response 201

FieldTypeDescription
active_contactsintegerContacts in the workflow now: with a running or waiting run, live tests included. Archiving stops them. From 0 to 9007199254740991.
can_deletebooleanWhether DELETE /v1/workflows/{id} would delete it now: a draft never published, with no runs (live tests included), that no other workflow starts or removes contacts from and no inbound source starts. Otherwise archive it.
created_atstring (date-time)ISO 8601 timestamp.
exit_rulesobject[]
exit_rules[].kindenumOne of: label_removed.
exit_rules[].label_idstring (uuid)
graphobject
graph.edgesobject[]Up to 400 items.
graph.edges[].fromstringUp to 64 characters.
graph.edges[].labelstringUp to 40 characters.
graph.edges[].tostringUp to 64 characters.
graph.nodesobject[]Up to 200 items.
graph.nodes[].configobjectDefault {}.
graph.nodes[].idstringUp to 64 characters.
graph.nodes[].positionobject
graph.nodes[].typeenumOne of: trigger, condition, wait, send, assign, label, webhook, ai, crm_update, conversion_event, wait_for_reply, start_workflow, remove_from_workflow.
has_draftboolean
idstring (uuid)
namestring
published_versioninteger or nullFrom 1 to 9007199254740991.
published_version_idstring (uuid) or null
reentryenumagain (default): a contact can go through again once their last run ends. once: a contact who has ever had a run of this workflow never starts another. One of: again, once.
runs_30dobject
runs_30d.completedintegerFrom 0 to 9007199254740991.
runs_30d.failedintegerFrom 0 to 9007199254740991.
runs_30d.totalintegerFrom 0 to 9007199254740991.
started_byobject[]Other live or paused workflows whose published version has a Start another workflow step that starts this one. Once it's archived, those steps are skipped.
started_by[].idstring (uuid)
started_by[].namestring
statusenumOne of: draft, live, paused, archived.
triggerenumOne of: message.received, contact.created, schedule, webhook, crm.lead_imported, crm.contact_updated, label.added, label.removed.
trigger_configobject
updated_atstring (date-time)ISO 8601 timestamp.
warningsobject[]Send steps a contact who hasn't replied yet wouldn't get. None for workflows an inbound message starts: that contact has replied.
warnings[].codestringThe governor code a first message with this text would fail with.
warnings[].messagestring
warnings[].node_idstring

Errors: 400, 401, 403, 404, 409, 413, 415, 429, 500. See errors for the shape and codes.

Draft a workflow from a description​

POST /v1/workflows/draft-from-description

Returns a draft workflow (name, trigger, trigger settings, graph, re-entry, exit rules and notes) for a plain description of up to 1,000 characters. It saves nothing: create the workflow with POST /v1/workflows once it looks right, and it never publishes. When AI drafting is switched on, a model drafts it from the description and the workspace's label, segment, event and workflow names (never contacts or messages), and the draft must pass the same checks as a saved draft; if it doesn't after one retry, or the model fails, the closest template answers instead (source: template) with a note saying so. Until AI drafting is switched on, the closest template always answers. 20 drafts per workspace per usage day, whichever answers; then 429 with details.resets_at. Tokens count toward the organisation's daily AI budget. Test-mode API keys are refused.

Headers

NameTypeRequiredDescription
x-workspace-idstringNoWorkspace for session (JWT) callers. API keys carry their own workspace.
idempotency-keystringNoMakes the write safe to retry for 24 hours. 1 to 255 printable characters. Keys starting with outreach:, workflow: or agent: are reserved for messages bubbl3 sends itself and are refused with 400. Up to 255 characters.

Request body

FieldTypeRequiredDescription
descriptionstringYesWhat the workflow should do, in plain words. Up to 1000 characters.

Response 200

FieldTypeDescription
graphobject
graph.edgesobject[]Up to 400 items.
graph.edges[].fromstringUp to 64 characters.
graph.edges[].labelstringUp to 40 characters.
graph.edges[].tostringUp to 64 characters.
graph.nodesobject[]Up to 200 items.
graph.nodes[].configobjectDefault {}.
graph.nodes[].idstringUp to 64 characters.
graph.nodes[].positionobject
graph.nodes[].typeenumOne of: trigger, condition, wait, send, assign, label, webhook, ai, crm_update, conversion_event, wait_for_reply, start_workflow, remove_from_workflow.
namestring
notesstring[]
settingsobject
settings.exit_rulesobject[]
settings.exit_rules[].kindenumOne of: label_removed.
settings.exit_rules[].label_idstring (uuid)
settings.reentryenumOne of: again, once.
sourceenumOne of: ai, template.
template_idenumOne of: speed_to_lead, abandoned_checkout, book_consultation, appointment_reminder, review_request, reengage_cold.
triggerenumOne of: message.received, contact.created, schedule, webhook, crm.lead_imported, crm.contact_updated, label.added, label.removed.
trigger_configobject

Errors: 400, 401, 403, 404, 409, 413, 415, 429, 500. See errors for the shape and codes.

Get a workflow with its draft graph​

GET /v1/workflows/:id

Path parameters

NameTypeRequiredDescription
idstring (uuid)Yes

Headers

NameTypeRequiredDescription
x-workspace-idstringNoWorkspace for session (JWT) callers. API keys carry their own workspace.

Response 200

FieldTypeDescription
active_contactsintegerContacts in the workflow now: with a running or waiting run, live tests included. Archiving stops them. From 0 to 9007199254740991.
can_deletebooleanWhether DELETE /v1/workflows/{id} would delete it now: a draft never published, with no runs (live tests included), that no other workflow starts or removes contacts from and no inbound source starts. Otherwise archive it.
created_atstring (date-time)ISO 8601 timestamp.
exit_rulesobject[]
exit_rules[].kindenumOne of: label_removed.
exit_rules[].label_idstring (uuid)
graphobject
graph.edgesobject[]Up to 400 items.
graph.edges[].fromstringUp to 64 characters.
graph.edges[].labelstringUp to 40 characters.
graph.edges[].tostringUp to 64 characters.
graph.nodesobject[]Up to 200 items.
graph.nodes[].configobjectDefault {}.
graph.nodes[].idstringUp to 64 characters.
graph.nodes[].positionobject
graph.nodes[].typeenumOne of: trigger, condition, wait, send, assign, label, webhook, ai, crm_update, conversion_event, wait_for_reply, start_workflow, remove_from_workflow.
has_draftboolean
idstring (uuid)
namestring
published_versioninteger or nullFrom 1 to 9007199254740991.
published_version_idstring (uuid) or null
reentryenumagain (default): a contact can go through again once their last run ends. once: a contact who has ever had a run of this workflow never starts another. One of: again, once.
runs_30dobject
runs_30d.completedintegerFrom 0 to 9007199254740991.
runs_30d.failedintegerFrom 0 to 9007199254740991.
runs_30d.totalintegerFrom 0 to 9007199254740991.
started_byobject[]Other live or paused workflows whose published version has a Start another workflow step that starts this one. Once it's archived, those steps are skipped.
started_by[].idstring (uuid)
started_by[].namestring
statusenumOne of: draft, live, paused, archived.
triggerenumOne of: message.received, contact.created, schedule, webhook, crm.lead_imported, crm.contact_updated, label.added, label.removed.
trigger_configobject
updated_atstring (date-time)ISO 8601 timestamp.
warningsobject[]Send steps a contact who hasn't replied yet wouldn't get. None for workflows an inbound message starts: that contact has replied.
warnings[].codestringThe governor code a first message with this text would fail with.
warnings[].messagestring
warnings[].node_idstring

Errors: 400, 401, 403, 404, 409, 413, 415, 429, 500. See errors for the shape and codes.

Edit the draft, or pause, resume or archive a workflow​

PATCH /v1/workflows/:id

Name, trigger, graph, re-entry and exit rule changes go to a draft version; the live version keeps running until the draft is published. A draft a live test ran on is kept as it was, and the changes go to a new draft version. status: paused stops new runs and holds running and waiting ones where they are; status: live resumes a paused workflow that has a published version, and its held runs continue: a run waiting at a Wait for reply step moves on at once if the contact replied in its window, or takes the no-reply path if the window has passed. status: archived is final: it stops new runs and cancels running and waiting ones. Test-mode API keys are refused.

Path parameters

NameTypeRequiredDescription
idstring (uuid)Yes

Headers

NameTypeRequiredDescription
x-workspace-idstringNoWorkspace for session (JWT) callers. API keys carry their own workspace.
idempotency-keystringNoMakes the write safe to retry for 24 hours. 1 to 255 printable characters. Keys starting with outreach:, workflow: or agent: are reserved for messages bubbl3 sends itself and are refused with 400. Up to 255 characters.

Request body

FieldTypeRequiredDescription
exit_rulesobject[]NoCancel a running or waiting run when one of these happens. A run follows the rules of the version it started on. Up to 20 items.
exit_rules[].kindenumYesOne of: label_removed.
exit_rules[].label_idstring (uuid)Yes
graphobjectNo
graph.edgesobject[]YesUp to 400 items.
graph.edges[].fromstringYesUp to 64 characters.
graph.edges[].labelstringNoUp to 40 characters.
graph.edges[].tostringYesUp to 64 characters.
graph.nodesobject[]YesUp to 200 items.
graph.nodes[].configobjectNoDefault {}.
graph.nodes[].idstringYesUp to 64 characters.
graph.nodes[].positionobjectNo
graph.nodes[].typeenumYesOne of: trigger, condition, wait, send, assign, label, webhook, ai, crm_update, conversion_event, wait_for_reply, start_workflow, remove_from_workflow.
namestringNoUp to 120 characters.
reentryenumNoagain (default): a contact can go through again once their last run ends. once: a contact who has ever had a run of this workflow never starts another. One of: again, once.
statusenumNoOne of: paused, archived, live.
triggerenumNoOne of: message.received, contact.created, schedule, webhook, crm.lead_imported, crm.contact_updated, label.added, label.removed.
trigger_configobjectNo

Response 200

FieldTypeDescription
active_contactsintegerContacts in the workflow now: with a running or waiting run, live tests included. Archiving stops them. From 0 to 9007199254740991.
can_deletebooleanWhether DELETE /v1/workflows/{id} would delete it now: a draft never published, with no runs (live tests included), that no other workflow starts or removes contacts from and no inbound source starts. Otherwise archive it.
created_atstring (date-time)ISO 8601 timestamp.
exit_rulesobject[]
exit_rules[].kindenumOne of: label_removed.
exit_rules[].label_idstring (uuid)
graphobject
graph.edgesobject[]Up to 400 items.
graph.edges[].fromstringUp to 64 characters.
graph.edges[].labelstringUp to 40 characters.
graph.edges[].tostringUp to 64 characters.
graph.nodesobject[]Up to 200 items.
graph.nodes[].configobjectDefault {}.
graph.nodes[].idstringUp to 64 characters.
graph.nodes[].positionobject
graph.nodes[].typeenumOne of: trigger, condition, wait, send, assign, label, webhook, ai, crm_update, conversion_event, wait_for_reply, start_workflow, remove_from_workflow.
has_draftboolean
idstring (uuid)
namestring
published_versioninteger or nullFrom 1 to 9007199254740991.
published_version_idstring (uuid) or null
reentryenumagain (default): a contact can go through again once their last run ends. once: a contact who has ever had a run of this workflow never starts another. One of: again, once.
runs_30dobject
runs_30d.completedintegerFrom 0 to 9007199254740991.
runs_30d.failedintegerFrom 0 to 9007199254740991.
runs_30d.totalintegerFrom 0 to 9007199254740991.
started_byobject[]Other live or paused workflows whose published version has a Start another workflow step that starts this one. Once it's archived, those steps are skipped.
started_by[].idstring (uuid)
started_by[].namestring
statusenumOne of: draft, live, paused, archived.
triggerenumOne of: message.received, contact.created, schedule, webhook, crm.lead_imported, crm.contact_updated, label.added, label.removed.
trigger_configobject
updated_atstring (date-time)ISO 8601 timestamp.
warningsobject[]Send steps a contact who hasn't replied yet wouldn't get. None for workflows an inbound message starts: that contact has replied.
warnings[].codestringThe governor code a first message with this text would fail with.
warnings[].messagestring
warnings[].node_idstring

Errors: 400, 401, 403, 404, 409, 413, 415, 429, 500. See errors for the shape and codes.

Delete a draft that was never published or run​

DELETE /v1/workflows/:id

Deletes the workflow and its versions for good. Only a draft that has never been published and has no runs, live tests included, so no run history is lost; and only when no other workflow (any version, unless it's archived) has a Start another workflow or Remove from workflow step that names it, and no inbound source starts it. Otherwise the answer is 409 with the reason: archive it with PATCH and status: archived instead. can_delete on the workflow says whether this would succeed now. No webhook event is sent. Test-mode API keys are refused.

Path parameters

NameTypeRequiredDescription
idstring (uuid)Yes

Headers

NameTypeRequiredDescription
x-workspace-idstringNoWorkspace for session (JWT) callers. API keys carry their own workspace.
idempotency-keystringNoMakes the write safe to retry for 24 hours. 1 to 255 printable characters. Keys starting with outreach:, workflow: or agent: are reserved for messages bubbl3 sends itself and are refused with 400. Up to 255 characters.

Response 204

No body.

Errors: 400, 401, 403, 404, 409, 413, 415, 429, 500. See errors for the shape and codes.

Publish the draft and turn the workflow on​

POST /v1/workflows/:id/publish

Checks the draft (one trigger, every step reachable, no loops, valid settings, existing segments, labels, people, teams, agents and workflows; a workflow can't start or remove from itself) and makes it the live version, with its re-entry setting and exit rules. Runs already going stay on the version they started on, and follow its rules. A draft with an Update CRM field step also needs integrations at full access. Test-mode API keys are refused.

Path parameters

NameTypeRequiredDescription
idstring (uuid)Yes

Headers

NameTypeRequiredDescription
x-workspace-idstringNoWorkspace for session (JWT) callers. API keys carry their own workspace.
idempotency-keystringNoMakes the write safe to retry for 24 hours. 1 to 255 printable characters. Keys starting with outreach:, workflow: or agent: are reserved for messages bubbl3 sends itself and are refused with 400. Up to 255 characters.

Response 200

FieldTypeDescription
active_contactsintegerContacts in the workflow now: with a running or waiting run, live tests included. Archiving stops them. From 0 to 9007199254740991.
can_deletebooleanWhether DELETE /v1/workflows/{id} would delete it now: a draft never published, with no runs (live tests included), that no other workflow starts or removes contacts from and no inbound source starts. Otherwise archive it.
created_atstring (date-time)ISO 8601 timestamp.
exit_rulesobject[]
exit_rules[].kindenumOne of: label_removed.
exit_rules[].label_idstring (uuid)
graphobject
graph.edgesobject[]Up to 400 items.
graph.edges[].fromstringUp to 64 characters.
graph.edges[].labelstringUp to 40 characters.
graph.edges[].tostringUp to 64 characters.
graph.nodesobject[]Up to 200 items.
graph.nodes[].configobjectDefault {}.
graph.nodes[].idstringUp to 64 characters.
graph.nodes[].positionobject
graph.nodes[].typeenumOne of: trigger, condition, wait, send, assign, label, webhook, ai, crm_update, conversion_event, wait_for_reply, start_workflow, remove_from_workflow.
has_draftboolean
idstring (uuid)
namestring
published_versioninteger or nullFrom 1 to 9007199254740991.
published_version_idstring (uuid) or null
reentryenumagain (default): a contact can go through again once their last run ends. once: a contact who has ever had a run of this workflow never starts another. One of: again, once.
runs_30dobject
runs_30d.completedintegerFrom 0 to 9007199254740991.
runs_30d.failedintegerFrom 0 to 9007199254740991.
runs_30d.totalintegerFrom 0 to 9007199254740991.
started_byobject[]Other live or paused workflows whose published version has a Start another workflow step that starts this one. Once it's archived, those steps are skipped.
started_by[].idstring (uuid)
started_by[].namestring
statusenumOne of: draft, live, paused, archived.
triggerenumOne of: message.received, contact.created, schedule, webhook, crm.lead_imported, crm.contact_updated, label.added, label.removed.
trigger_configobject
updated_atstring (date-time)ISO 8601 timestamp.
warningsobject[]Send steps a contact who hasn't replied yet wouldn't get. None for workflows an inbound message starts: that contact has replied.
warnings[].codestringThe governor code a first message with this text would fail with.
warnings[].messagestring
warnings[].node_idstring

Errors: 400, 401, 403, 404, 409, 413, 415, 429, 500. See errors for the shape and codes.

Cancel a live test run​

POST /v1/workflows/:id/runs/:runId/cancel

Ends a live test run that is still running or waiting, with test_cancelled. Only test runs: a real run is refused with 409, as is a test run that has already ended. Test-mode API keys are refused.

Path parameters

NameTypeRequiredDescription
idstring (uuid)Yes
runIdstring (uuid)Yes

Headers

NameTypeRequiredDescription
x-workspace-idstringNoWorkspace for session (JWT) callers. API keys carry their own workspace.
idempotency-keystringNoMakes the write safe to retry for 24 hours. 1 to 255 printable characters. Keys starting with outreach:, workflow: or agent: are reserved for messages bubbl3 sends itself and are refused with 400. Up to 255 characters.

Response 200

FieldTypeDescription
contact_idstring or nullContact id (ctc_...).
error_codestring or null
finished_atstring (date-time) or nullISO 8601 timestamp.
idstring (uuid)
resume_atstring (date-time) or nullISO 8601 timestamp.
started_atstring (date-time)ISO 8601 timestamp.
statusenumOne of: running, waiting, completed, failed, cancelled.
stepsobject[]
steps[].atstring (date-time)ISO 8601 timestamp.
steps[].detailobject
steps[].node_idstring
steps[].statusenumOne of: completed, skipped, failed, waiting, cancelled.
testbooleanA live test started from the editor. Test runs don't count in the workflow's run totals or its limits.
test_optionsobject or null
test_options.send_eventsbooleanFire event and Webhook steps send, marked test: true, to webhook URLs only. Off: they are skipped.
test_options.shorten_waitsbooleanEvery Wait and Wait for reply lasts 1 minute.
versionintegerFrom 1 to 9007199254740991.
workflow_idstring (uuid)
workflow_version_idstring (uuid)

Errors: 400, 401, 403, 404, 409, 413, 415, 429, 500. See errors for the shape and codes.

Simulate a workflow for one contact, sending nothing​

POST /v1/workflows/:id/simulate

Walks the workflow for an existing contact or a sample contact, with the rules a real run uses, and returns the steps it reaches: each message as it would read, each wait, each condition's answer and why, and what the other steps would do. Where a run would wait, the simulation stops and asks (pending): send the decisions so far in decisions to go on, Replied (with the reply's text) or No reply at a Wait for reply, Skip ahead at a Wait, or one of the workflow's exit rules at either. A flip decision tries a condition's other answer. Each call replays the decisions from the start, so the same body gives the same result. graph, trigger, trigger_config and exit_rules default to the draft, so the editor can simulate unsaved changes. Nothing is sent or saved: no runs, messages, labels, events or audit entries, and an Idempotency-Key is ignored, so no response is stored. A caller without access to conversations isn't told whether the contact has a conversation or when they last texted: the simulation then assumes a conversation and leaves out the recent-reply exemption from quiet hours. Test-mode API keys may call it.

Path parameters

NameTypeRequiredDescription
idstring (uuid)Yes

Headers

NameTypeRequiredDescription
x-workspace-idstringNoWorkspace for session (JWT) callers. API keys carry their own workspace.
idempotency-keystringNoMakes the write safe to retry for 24 hours. 1 to 255 printable characters. Keys starting with outreach:, workflow: or agent: are reserved for messages bubbl3 sends itself and are refused with 400. Up to 255 characters.

Request body

FieldTypeRequiredDescription
contact_idstringNoAn existing contact. Give this or sample_contact.
decisionsobject[]NoThe decisions so far, in order. Each call replays them from the start. Up to 200 items.
decisions[].kindenumYesOne of: skip_ahead.
exit_rulesobject[]NoDefaults to the draft's exit rules. Up to 20 items.
exit_rules[].kindenumYesOne of: label_removed.
exit_rules[].label_idstring (uuid)Yes
graphobjectNoThe graph to simulate, such as the editor's unsaved canvas. Defaults to the draft.
graph.edgesobject[]YesUp to 400 items.
graph.edges[].fromstringYesUp to 64 characters.
graph.edges[].labelstringNoUp to 40 characters.
graph.edges[].tostringYesUp to 64 characters.
graph.nodesobject[]YesUp to 200 items.
graph.nodes[].configobjectNoDefault {}.
graph.nodes[].idstringYesUp to 64 characters.
graph.nodes[].positionobjectNo
graph.nodes[].typeenumYesOne of: trigger, condition, wait, send, assign, label, webhook, ai, crm_update, conversion_event, wait_for_reply, start_workflow, remove_from_workflow.
sample_contactobjectNo
sample_contact.companystringNoUp to 200 characters.
sample_contact.fieldsobjectNoCustom fields.
sample_contact.first_namestringNoUp to 100 characters.
sample_contact.label_idsstring (uuid)[]NoLabels from the workspace's labels. Up to 50 items.
sample_contact.last_namestringNoUp to 100 characters.
sample_contact.opted_outbooleanNoSimulate a contact who has opted out. Default false.
sample_contact.timezonestringNoFor quiet hours. Defaults to the organisation's timezone. Up to 64 characters.
triggerenumNoDefaults to the draft's trigger. One of: message.received, contact.created, schedule, webhook, crm.lead_imported, crm.contact_updated, label.added, label.removed.
trigger_configobjectNoDefaults to the draft's trigger settings.
trigger_messagestringNoThe inbound message that starts the run, for the Inbound message trigger. Up to 2000 characters.

Response 200

FieldTypeDescription
contactobject
contact.label_idsstring (uuid)[]
contact.namestring
contact.opted_outboolean
endobject or null
end.codestring or null
end.messagestring
end.statusenumOne of: completed, cancelled, failed.
exit_rulesobject[]
exit_rules[].kindenumOne of: label_removed.
exit_rules[].label_idstring (uuid)
pendingobject or null
pending.choicesenum[]
pending.node_idstring
pending.untilstring (date-time)ISO 8601 timestamp.
stepsobject[]
steps[].atstring (date-time)ISO 8601 timestamp.
steps[].branchenum or nullOne of: yes, no, replied, no_reply.
steps[].codestring or null
steps[].decisions_beforeintegerFrom 0 to 9007199254740991.
steps[].detailstring or null
steps[].flippedboolean
steps[].messagestring or null
steps[].node_idstring
steps[].notesstring[]
steps[].statusenumOne of: completed, skipped, failed, waiting, cancelled.
steps[].titlestring
steps[].typeenumOne of: trigger, condition, wait, send, assign, label, webhook, ai, crm_update, conversion_event, wait_for_reply, start_workflow, remove_from_workflow.
warningsobject[]
warnings[].messagestring
warnings[].node_idstring or null

Errors: 400, 401, 403, 404, 409, 413, 415, 429, 500. See errors for the shape and codes.

Start a live test of the latest version for one test contact​

POST /v1/workflows/:id/test-runs

Runs the workflow's latest version, published or not, for real for one contact marked as a test contact. The draft must pass the publish checks first, and a draft with an Update CRM field or Fire event step also needs integrations at full access, as publishing does. Texts go through the normal send path: consent, opt-outs, quiet hours and line limits all apply. The run is marked test: it doesn't count in the workflow's run totals or toward its daily limit and re-entry setting, and a contact's test run and real run don't block each other. shorten_waits makes every Wait and Wait for reply last 1 minute. Fire event and Webhook steps are skipped unless send_events is on; then they go to webhook URLs only, marked test: true, never to a CRM or ad platform. Update CRM field and Start another workflow are always skipped, labels a test run changes start no other workflow, and the test contact's messages while the test runs start no real workflow and aren't logged to a CRM. A test run never sends workflow.run.completed. Unmarking the contact, or changing their phone, ends their open test runs. One open test run per contact and workflow: a second is refused with 409. A draft a test ran on is kept as it was; later edits go to a new draft version. Archived workflows and test-mode API keys are refused.

Path parameters

NameTypeRequiredDescription
idstring (uuid)Yes

Headers

NameTypeRequiredDescription
x-workspace-idstringNoWorkspace for session (JWT) callers. API keys carry their own workspace.
idempotency-keystringNoMakes the write safe to retry for 24 hours. 1 to 255 printable characters. Keys starting with outreach:, workflow: or agent: are reserved for messages bubbl3 sends itself and are refused with 400. Up to 255 characters.

Request body

FieldTypeRequiredDescription
contact_idstringYesA contact marked as a test contact.
send_eventsbooleanNoSend Fire event and Webhook steps, marked test: true, to webhook URLs only. CRM and ad-platform destinations never get test events. Default false.
shorten_waitsbooleanNoEvery Wait and Wait for reply lasts 1 minute. Default false.

Response 201

FieldTypeDescription
contact_idstring or nullContact id (ctc_...).
error_codestring or null
finished_atstring (date-time) or nullISO 8601 timestamp.
idstring (uuid)
resume_atstring (date-time) or nullISO 8601 timestamp.
started_atstring (date-time)ISO 8601 timestamp.
statusenumOne of: running, waiting, completed, failed, cancelled.
stepsobject[]
steps[].atstring (date-time)ISO 8601 timestamp.
steps[].detailobject
steps[].node_idstring
steps[].statusenumOne of: completed, skipped, failed, waiting, cancelled.
testbooleanA live test started from the editor. Test runs don't count in the workflow's run totals or its limits.
test_optionsobject or null
test_options.send_eventsbooleanFire event and Webhook steps send, marked test: true, to webhook URLs only. Off: they are skipped.
test_options.shorten_waitsbooleanEvery Wait and Wait for reply lasts 1 minute.
versionintegerFrom 1 to 9007199254740991.
workflow_idstring (uuid)
workflow_version_idstring (uuid)

Errors: 400, 401, 403, 404, 409, 413, 415, 429, 500. See errors for the shape and codes.

List a workflow's versions, newest first​

GET /v1/workflows/:id/versions

Path parameters

NameTypeRequiredDescription
idstring (uuid)Yes

Headers

NameTypeRequiredDescription
x-workspace-idstringNoWorkspace for session (JWT) callers. API keys carry their own workspace.

Response 200

FieldTypeDescription
dataobject[]
data[].created_atstring (date-time)ISO 8601 timestamp.
data[].graphobject
data[].graph.edgesobject[]Up to 400 items.
data[].graph.nodesobject[]Up to 200 items.
data[].idstring (uuid)
data[].published_atstring (date-time) or nullISO 8601 timestamp.
data[].versionintegerFrom 1 to 9007199254740991.
data[].workflow_idstring (uuid)

Errors: 400, 401, 403, 404, 409, 413, 415, 429, 500. See errors for the shape and codes.