Workflows
Every call needs Authorization: Bearer <key>. 15 calls:
- List workflow runs, newest first:
GET /v1/workflow-runs - Get a workflow run and its steps:
GET /v1/workflow-runs/:id - List the workflow templates:
GET /v1/workflow-templates - Get a workflow template with its graph and setup checklist:
GET /v1/workflow-templates/:id - List workflows, most recently edited first:
GET /v1/workflows - Create a draft workflow:
POST /v1/workflows - Draft a workflow from a description:
POST /v1/workflows/draft-from-description - Get a workflow with its draft graph:
GET /v1/workflows/:id - Edit the draft, or pause, resume or archive a workflow:
PATCH /v1/workflows/:id - Delete a draft that was never published or run:
DELETE /v1/workflows/:id - Publish the draft and turn the workflow on:
POST /v1/workflows/:id/publish - Cancel a live test run:
POST /v1/workflows/:id/runs/:runId/cancel - Simulate a workflow for one contact, sending nothing:
POST /v1/workflows/:id/simulate - Start a live test of the latest version for one test contact:
POST /v1/workflows/:id/test-runs - List a workflow's versions, newest first:
GET /v1/workflows/:id/versions
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.completedevent. 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
| Name | Type | Required | Description |
|---|---|---|---|
limit | integer | No | From 1 to 100. Default 50. |
cursor | string | No | Opaque cursor from next_cursor. Up to 512 characters. |
workflow_id | string (uuid) | No | |
status | enum | No | One of: running, waiting, completed, failed, cancelled. |
contact_id | string | No | Contact id (ctc_...). |
test | enum | No | Real runs by default (false). true lists only live test runs, all both. One of: true, false, all. |
Headers
| Name | Type | Required | Description |
|---|---|---|---|
x-workspace-id | string | No | Workspace for session (JWT) callers. API keys carry their own workspace. |
Response 200
| Field | Type | Description |
|---|---|---|
data | object[] | |
data[].contact_id | string or null | Contact id (ctc_...). |
data[].error_code | string or null | |
data[].finished_at | string (date-time) or null | ISO 8601 timestamp. |
data[].id | string (uuid) | |
data[].resume_at | string (date-time) or null | ISO 8601 timestamp. |
data[].started_at | string (date-time) | ISO 8601 timestamp. |
data[].status | enum | One of: running, waiting, completed, failed, cancelled. |
data[].steps | object[] | |
data[].steps[].at | string (date-time) | ISO 8601 timestamp. |
data[].steps[].detail | object | |
data[].steps[].node_id | string | |
data[].steps[].status | enum | One of: completed, skipped, failed, waiting, cancelled. |
data[].test | boolean | A live test started from the editor. Test runs don't count in the workflow's run totals or its limits. |
data[].test_options | object or null | |
data[].test_options.send_events | boolean | Fire event and Webhook steps send, marked test: true, to webhook URLs only. Off: they are skipped. |
data[].test_options.shorten_waits | boolean | Every Wait and Wait for reply lasts 1 minute. |
data[].version | integer | From 1 to 9007199254740991. |
data[].workflow_id | string (uuid) | |
data[].workflow_version_id | string (uuid) | |
next_cursor | string or null | Pass 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
| Name | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | Yes |
Headers
| Name | Type | Required | Description |
|---|---|---|---|
x-workspace-id | string | No | Workspace for session (JWT) callers. API keys carry their own workspace. |
Response 200
| Field | Type | Description |
|---|---|---|
contact_id | string or null | Contact id (ctc_...). |
error_code | string or null | |
finished_at | string (date-time) or null | ISO 8601 timestamp. |
id | string (uuid) | |
resume_at | string (date-time) or null | ISO 8601 timestamp. |
started_at | string (date-time) | ISO 8601 timestamp. |
status | enum | One of: running, waiting, completed, failed, cancelled. |
steps | object[] | |
steps[].at | string (date-time) | ISO 8601 timestamp. |
steps[].detail | object | |
steps[].node_id | string | |
steps[].status | enum | One of: completed, skipped, failed, waiting, cancelled. |
test | boolean | A live test started from the editor. Test runs don't count in the workflow's run totals or its limits. |
test_options | object or null | |
test_options.send_events | boolean | Fire event and Webhook steps send, marked test: true, to webhook URLs only. Off: they are skipped. |
test_options.shorten_waits | boolean | Every Wait and Wait for reply lasts 1 minute. |
version | integer | From 1 to 9007199254740991. |
workflow_id | string (uuid) | |
workflow_version_id | string (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
| Name | Type | Required | Description |
|---|---|---|---|
x-workspace-id | string | No | Workspace for session (JWT) callers. API keys carry their own workspace. |
Response 200
| Field | Type | Description |
|---|---|---|
data | object[] | |
data[].category | enum | One of: new_leads, bookings, follow_up. |
data[].description | string | |
data[].id | enum | One of: speed_to_lead, abandoned_checkout, book_consultation, appointment_reminder, review_request, reengage_cold. |
data[].name | string | |
data[].preview | string[] | |
data[].preview_steps | object[] | |
data[].preview_steps[].kind | enum | One of: trigger, send, wait, wait_for_reply, branch, label, event, stop. |
data[].preview_steps[].text | string | |
data[].tip | string or null | |
data[].tip_short | string or null | |
data[].trigger | enum | One of: message.received, contact.created, schedule, webhook, crm.lead_imported, crm.contact_updated, label.added, label.removed. |
data[].use_case | string |
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
| Name | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Up to 64 characters. |
Headers
| Name | Type | Required | Description |
|---|---|---|---|
x-workspace-id | string | No | Workspace for session (JWT) callers. API keys carry their own workspace. |
Response 200
| Field | Type | Description |
|---|---|---|
category | enum | One of: new_leads, bookings, follow_up. |
checklist | object[] | |
checklist[].hint | string | |
checklist[].id | string | |
checklist[].placeholder | string or null | |
checklist[].required | boolean | |
checklist[].suggested_minutes | integer or null | From 1 to 9007199254740991. |
checklist[].targets | object[] | |
checklist[].targets[].field | string | |
checklist[].targets[].kind | enum | One of: step. |
checklist[].targets[].node_id | string | |
checklist[].task | enum | One of: fill_in, pick_label, pick_event, pick_segment, set_wait, check_words. |
checklist[].title | string | |
description | string | |
graph | object | |
graph.edges | object[] | Up to 400 items. |
graph.edges[].from | string | Up to 64 characters. |
graph.edges[].label | string | Up to 40 characters. |
graph.edges[].to | string | Up to 64 characters. |
graph.nodes | object[] | Up to 200 items. |
graph.nodes[].config | object | Default {}. |
graph.nodes[].id | string | Up to 64 characters. |
graph.nodes[].position | object | |
graph.nodes[].type | enum | One of: trigger, condition, wait, send, assign, label, webhook, ai, crm_update, conversion_event, wait_for_reply, start_workflow, remove_from_workflow. |
id | enum | One of: speed_to_lead, abandoned_checkout, book_consultation, appointment_reminder, review_request, reengage_cold. |
name | string | |
preview | string[] | |
preview_steps | object[] | |
preview_steps[].kind | enum | One of: trigger, send, wait, wait_for_reply, branch, label, event, stop. |
preview_steps[].text | string | |
settings | object | |
settings.exit_rules | object[] | |
settings.exit_rules[].kind | enum | One of: label_removed. |
settings.exit_rules[].label_id | string (uuid) | |
settings.reentry | enum | One of: again, once. |
tip | string or null | |
tip_short | string or null | |
trigger | enum | One of: message.received, contact.created, schedule, webhook, crm.lead_imported, crm.contact_updated, label.added, label.removed. |
trigger_config | object | |
use_case | string |
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
| Name | Type | Required | Description |
|---|---|---|---|
limit | integer | No | From 1 to 100. Default 50. |
cursor | string | No | Opaque cursor from next_cursor. Up to 512 characters. |
status | enum | No | One of: draft, live, paused, archived. |
exclude_archived | enum | No | true leaves archived workflows out. Without status or this, every status is listed. Not with status=archived. One of: true, false. |
Headers
| Name | Type | Required | Description |
|---|---|---|---|
x-workspace-id | string | No | Workspace for session (JWT) callers. API keys carry their own workspace. |
Response 200
| Field | Type | Description |
|---|---|---|
data | object[] | |
data[].active_contacts | integer | Contacts in the workflow now: with a running or waiting run, live tests included. Archiving stops them. From 0 to 9007199254740991. |
data[].can_delete | boolean | Whether 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_at | string (date-time) | ISO 8601 timestamp. |
data[].exit_rules | object[] | |
data[].exit_rules[].kind | enum | One of: label_removed. |
data[].exit_rules[].label_id | string (uuid) | |
data[].graph | object | |
data[].graph.edges | object[] | Up to 400 items. |
data[].graph.nodes | object[] | Up to 200 items. |
data[].has_draft | boolean | |
data[].id | string (uuid) | |
data[].name | string | |
data[].published_version | integer or null | From 1 to 9007199254740991. |
data[].published_version_id | string (uuid) or null | |
data[].reentry | enum | again (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_30d | object | |
data[].runs_30d.completed | integer | From 0 to 9007199254740991. |
data[].runs_30d.failed | integer | From 0 to 9007199254740991. |
data[].runs_30d.total | integer | From 0 to 9007199254740991. |
data[].started_by | object[] | 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[].id | string (uuid) | |
data[].started_by[].name | string | |
data[].status | enum | One of: draft, live, paused, archived. |
data[].trigger | enum | One of: message.received, contact.created, schedule, webhook, crm.lead_imported, crm.contact_updated, label.added, label.removed. |
data[].trigger_config | object | |
data[].updated_at | string (date-time) | ISO 8601 timestamp. |
data[].warnings | object[] | 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[].code | string | The governor code a first message with this text would fail with. |
data[].warnings[].message | string | |
data[].warnings[].node_id | string | |
next_cursor | string or null | Pass 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
| Name | Type | Required | Description |
|---|---|---|---|
x-workspace-id | string | No | Workspace for session (JWT) callers. API keys carry their own workspace. |
idempotency-key | string | No | Makes 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
| Field | Type | Required | Description |
|---|---|---|---|
exit_rules | object[] | No | Cancel 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[].kind | enum | Yes | One of: label_removed. |
exit_rules[].label_id | string (uuid) | Yes | |
graph | object | No | Default {"edges":[],"nodes":[]}. |
graph.edges | object[] | Yes | Up to 400 items. |
graph.edges[].from | string | Yes | Up to 64 characters. |
graph.edges[].label | string | No | Up to 40 characters. |
graph.edges[].to | string | Yes | Up to 64 characters. |
graph.nodes | object[] | Yes | Up to 200 items. |
graph.nodes[].config | object | No | Default {}. |
graph.nodes[].id | string | Yes | Up to 64 characters. |
graph.nodes[].position | object | No | |
graph.nodes[].type | enum | Yes | One of: trigger, condition, wait, send, assign, label, webhook, ai, crm_update, conversion_event, wait_for_reply, start_workflow, remove_from_workflow. |
name | string | Yes | Up to 120 characters. |
reentry | enum | No | again (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". |
trigger | enum | Yes | One of: message.received, contact.created, schedule, webhook, crm.lead_imported, crm.contact_updated, label.added, label.removed. |
trigger_config | object | No | Default {}. |
Response 201
| Field | Type | Description |
|---|---|---|
active_contacts | integer | Contacts in the workflow now: with a running or waiting run, live tests included. Archiving stops them. From 0 to 9007199254740991. |
can_delete | boolean | Whether 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_at | string (date-time) | ISO 8601 timestamp. |
exit_rules | object[] | |
exit_rules[].kind | enum | One of: label_removed. |
exit_rules[].label_id | string (uuid) | |
graph | object | |
graph.edges | object[] | Up to 400 items. |
graph.edges[].from | string | Up to 64 characters. |
graph.edges[].label | string | Up to 40 characters. |
graph.edges[].to | string | Up to 64 characters. |
graph.nodes | object[] | Up to 200 items. |
graph.nodes[].config | object | Default {}. |
graph.nodes[].id | string | Up to 64 characters. |
graph.nodes[].position | object | |
graph.nodes[].type | enum | One of: trigger, condition, wait, send, assign, label, webhook, ai, crm_update, conversion_event, wait_for_reply, start_workflow, remove_from_workflow. |
has_draft | boolean | |
id | string (uuid) | |
name | string | |
published_version | integer or null | From 1 to 9007199254740991. |
published_version_id | string (uuid) or null | |
reentry | enum | again (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_30d | object | |
runs_30d.completed | integer | From 0 to 9007199254740991. |
runs_30d.failed | integer | From 0 to 9007199254740991. |
runs_30d.total | integer | From 0 to 9007199254740991. |
started_by | object[] | 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[].id | string (uuid) | |
started_by[].name | string | |
status | enum | One of: draft, live, paused, archived. |
trigger | enum | One of: message.received, contact.created, schedule, webhook, crm.lead_imported, crm.contact_updated, label.added, label.removed. |
trigger_config | object | |
updated_at | string (date-time) | ISO 8601 timestamp. |
warnings | object[] | Send steps a contact who hasn't replied yet wouldn't get. None for workflows an inbound message starts: that contact has replied. |
warnings[].code | string | The governor code a first message with this text would fail with. |
warnings[].message | string | |
warnings[].node_id | string |
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
| Name | Type | Required | Description |
|---|---|---|---|
x-workspace-id | string | No | Workspace for session (JWT) callers. API keys carry their own workspace. |
idempotency-key | string | No | Makes 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
| Field | Type | Required | Description |
|---|---|---|---|
description | string | Yes | What the workflow should do, in plain words. Up to 1000 characters. |
Response 200
| Field | Type | Description |
|---|---|---|
graph | object | |
graph.edges | object[] | Up to 400 items. |
graph.edges[].from | string | Up to 64 characters. |
graph.edges[].label | string | Up to 40 characters. |
graph.edges[].to | string | Up to 64 characters. |
graph.nodes | object[] | Up to 200 items. |
graph.nodes[].config | object | Default {}. |
graph.nodes[].id | string | Up to 64 characters. |
graph.nodes[].position | object | |
graph.nodes[].type | enum | One of: trigger, condition, wait, send, assign, label, webhook, ai, crm_update, conversion_event, wait_for_reply, start_workflow, remove_from_workflow. |
name | string | |
notes | string[] | |
settings | object | |
settings.exit_rules | object[] | |
settings.exit_rules[].kind | enum | One of: label_removed. |
settings.exit_rules[].label_id | string (uuid) | |
settings.reentry | enum | One of: again, once. |
source | enum | One of: ai, template. |
template_id | enum | One of: speed_to_lead, abandoned_checkout, book_consultation, appointment_reminder, review_request, reengage_cold. |
trigger | enum | One of: message.received, contact.created, schedule, webhook, crm.lead_imported, crm.contact_updated, label.added, label.removed. |
trigger_config | object |
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
| Name | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | Yes |
Headers
| Name | Type | Required | Description |
|---|---|---|---|
x-workspace-id | string | No | Workspace for session (JWT) callers. API keys carry their own workspace. |
Response 200
| Field | Type | Description |
|---|---|---|
active_contacts | integer | Contacts in the workflow now: with a running or waiting run, live tests included. Archiving stops them. From 0 to 9007199254740991. |
can_delete | boolean | Whether 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_at | string (date-time) | ISO 8601 timestamp. |
exit_rules | object[] | |
exit_rules[].kind | enum | One of: label_removed. |
exit_rules[].label_id | string (uuid) | |
graph | object | |
graph.edges | object[] | Up to 400 items. |
graph.edges[].from | string | Up to 64 characters. |
graph.edges[].label | string | Up to 40 characters. |
graph.edges[].to | string | Up to 64 characters. |
graph.nodes | object[] | Up to 200 items. |
graph.nodes[].config | object | Default {}. |
graph.nodes[].id | string | Up to 64 characters. |
graph.nodes[].position | object | |
graph.nodes[].type | enum | One of: trigger, condition, wait, send, assign, label, webhook, ai, crm_update, conversion_event, wait_for_reply, start_workflow, remove_from_workflow. |
has_draft | boolean | |
id | string (uuid) | |
name | string | |
published_version | integer or null | From 1 to 9007199254740991. |
published_version_id | string (uuid) or null | |
reentry | enum | again (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_30d | object | |
runs_30d.completed | integer | From 0 to 9007199254740991. |
runs_30d.failed | integer | From 0 to 9007199254740991. |
runs_30d.total | integer | From 0 to 9007199254740991. |
started_by | object[] | 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[].id | string (uuid) | |
started_by[].name | string | |
status | enum | One of: draft, live, paused, archived. |
trigger | enum | One of: message.received, contact.created, schedule, webhook, crm.lead_imported, crm.contact_updated, label.added, label.removed. |
trigger_config | object | |
updated_at | string (date-time) | ISO 8601 timestamp. |
warnings | object[] | Send steps a contact who hasn't replied yet wouldn't get. None for workflows an inbound message starts: that contact has replied. |
warnings[].code | string | The governor code a first message with this text would fail with. |
warnings[].message | string | |
warnings[].node_id | string |
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
| Name | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | Yes |
Headers
| Name | Type | Required | Description |
|---|---|---|---|
x-workspace-id | string | No | Workspace for session (JWT) callers. API keys carry their own workspace. |
idempotency-key | string | No | Makes 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
| Field | Type | Required | Description |
|---|---|---|---|
exit_rules | object[] | No | Cancel 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[].kind | enum | Yes | One of: label_removed. |
exit_rules[].label_id | string (uuid) | Yes | |
graph | object | No | |
graph.edges | object[] | Yes | Up to 400 items. |
graph.edges[].from | string | Yes | Up to 64 characters. |
graph.edges[].label | string | No | Up to 40 characters. |
graph.edges[].to | string | Yes | Up to 64 characters. |
graph.nodes | object[] | Yes | Up to 200 items. |
graph.nodes[].config | object | No | Default {}. |
graph.nodes[].id | string | Yes | Up to 64 characters. |
graph.nodes[].position | object | No | |
graph.nodes[].type | enum | Yes | One of: trigger, condition, wait, send, assign, label, webhook, ai, crm_update, conversion_event, wait_for_reply, start_workflow, remove_from_workflow. |
name | string | No | Up to 120 characters. |
reentry | enum | No | again (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. |
status | enum | No | One of: paused, archived, live. |
trigger | enum | No | One of: message.received, contact.created, schedule, webhook, crm.lead_imported, crm.contact_updated, label.added, label.removed. |
trigger_config | object | No |
Response 200
| Field | Type | Description |
|---|---|---|
active_contacts | integer | Contacts in the workflow now: with a running or waiting run, live tests included. Archiving stops them. From 0 to 9007199254740991. |
can_delete | boolean | Whether 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_at | string (date-time) | ISO 8601 timestamp. |
exit_rules | object[] | |
exit_rules[].kind | enum | One of: label_removed. |
exit_rules[].label_id | string (uuid) | |
graph | object | |
graph.edges | object[] | Up to 400 items. |
graph.edges[].from | string | Up to 64 characters. |
graph.edges[].label | string | Up to 40 characters. |
graph.edges[].to | string | Up to 64 characters. |
graph.nodes | object[] | Up to 200 items. |
graph.nodes[].config | object | Default {}. |
graph.nodes[].id | string | Up to 64 characters. |
graph.nodes[].position | object | |
graph.nodes[].type | enum | One of: trigger, condition, wait, send, assign, label, webhook, ai, crm_update, conversion_event, wait_for_reply, start_workflow, remove_from_workflow. |
has_draft | boolean | |
id | string (uuid) | |
name | string | |
published_version | integer or null | From 1 to 9007199254740991. |
published_version_id | string (uuid) or null | |
reentry | enum | again (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_30d | object | |
runs_30d.completed | integer | From 0 to 9007199254740991. |
runs_30d.failed | integer | From 0 to 9007199254740991. |
runs_30d.total | integer | From 0 to 9007199254740991. |
started_by | object[] | 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[].id | string (uuid) | |
started_by[].name | string | |
status | enum | One of: draft, live, paused, archived. |
trigger | enum | One of: message.received, contact.created, schedule, webhook, crm.lead_imported, crm.contact_updated, label.added, label.removed. |
trigger_config | object | |
updated_at | string (date-time) | ISO 8601 timestamp. |
warnings | object[] | Send steps a contact who hasn't replied yet wouldn't get. None for workflows an inbound message starts: that contact has replied. |
warnings[].code | string | The governor code a first message with this text would fail with. |
warnings[].message | string | |
warnings[].node_id | string |
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
| Name | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | Yes |
Headers
| Name | Type | Required | Description |
|---|---|---|---|
x-workspace-id | string | No | Workspace for session (JWT) callers. API keys carry their own workspace. |
idempotency-key | string | No | Makes 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
| Name | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | Yes |
Headers
| Name | Type | Required | Description |
|---|---|---|---|
x-workspace-id | string | No | Workspace for session (JWT) callers. API keys carry their own workspace. |
idempotency-key | string | No | Makes 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
| Field | Type | Description |
|---|---|---|
active_contacts | integer | Contacts in the workflow now: with a running or waiting run, live tests included. Archiving stops them. From 0 to 9007199254740991. |
can_delete | boolean | Whether 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_at | string (date-time) | ISO 8601 timestamp. |
exit_rules | object[] | |
exit_rules[].kind | enum | One of: label_removed. |
exit_rules[].label_id | string (uuid) | |
graph | object | |
graph.edges | object[] | Up to 400 items. |
graph.edges[].from | string | Up to 64 characters. |
graph.edges[].label | string | Up to 40 characters. |
graph.edges[].to | string | Up to 64 characters. |
graph.nodes | object[] | Up to 200 items. |
graph.nodes[].config | object | Default {}. |
graph.nodes[].id | string | Up to 64 characters. |
graph.nodes[].position | object | |
graph.nodes[].type | enum | One of: trigger, condition, wait, send, assign, label, webhook, ai, crm_update, conversion_event, wait_for_reply, start_workflow, remove_from_workflow. |
has_draft | boolean | |
id | string (uuid) | |
name | string | |
published_version | integer or null | From 1 to 9007199254740991. |
published_version_id | string (uuid) or null | |
reentry | enum | again (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_30d | object | |
runs_30d.completed | integer | From 0 to 9007199254740991. |
runs_30d.failed | integer | From 0 to 9007199254740991. |
runs_30d.total | integer | From 0 to 9007199254740991. |
started_by | object[] | 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[].id | string (uuid) | |
started_by[].name | string | |
status | enum | One of: draft, live, paused, archived. |
trigger | enum | One of: message.received, contact.created, schedule, webhook, crm.lead_imported, crm.contact_updated, label.added, label.removed. |
trigger_config | object | |
updated_at | string (date-time) | ISO 8601 timestamp. |
warnings | object[] | Send steps a contact who hasn't replied yet wouldn't get. None for workflows an inbound message starts: that contact has replied. |
warnings[].code | string | The governor code a first message with this text would fail with. |
warnings[].message | string | |
warnings[].node_id | string |
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
| Name | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | Yes | |
runId | string (uuid) | Yes |
Headers
| Name | Type | Required | Description |
|---|---|---|---|
x-workspace-id | string | No | Workspace for session (JWT) callers. API keys carry their own workspace. |
idempotency-key | string | No | Makes 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
| Field | Type | Description |
|---|---|---|
contact_id | string or null | Contact id (ctc_...). |
error_code | string or null | |
finished_at | string (date-time) or null | ISO 8601 timestamp. |
id | string (uuid) | |
resume_at | string (date-time) or null | ISO 8601 timestamp. |
started_at | string (date-time) | ISO 8601 timestamp. |
status | enum | One of: running, waiting, completed, failed, cancelled. |
steps | object[] | |
steps[].at | string (date-time) | ISO 8601 timestamp. |
steps[].detail | object | |
steps[].node_id | string | |
steps[].status | enum | One of: completed, skipped, failed, waiting, cancelled. |
test | boolean | A live test started from the editor. Test runs don't count in the workflow's run totals or its limits. |
test_options | object or null | |
test_options.send_events | boolean | Fire event and Webhook steps send, marked test: true, to webhook URLs only. Off: they are skipped. |
test_options.shorten_waits | boolean | Every Wait and Wait for reply lasts 1 minute. |
version | integer | From 1 to 9007199254740991. |
workflow_id | string (uuid) | |
workflow_version_id | string (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
| Name | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | Yes |
Headers
| Name | Type | Required | Description |
|---|---|---|---|
x-workspace-id | string | No | Workspace for session (JWT) callers. API keys carry their own workspace. |
idempotency-key | string | No | Makes 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
| Field | Type | Required | Description |
|---|---|---|---|
contact_id | string | No | An existing contact. Give this or sample_contact. |
decisions | object[] | No | The decisions so far, in order. Each call replays them from the start. Up to 200 items. |
decisions[].kind | enum | Yes | One of: skip_ahead. |
exit_rules | object[] | No | Defaults to the draft's exit rules. Up to 20 items. |
exit_rules[].kind | enum | Yes | One of: label_removed. |
exit_rules[].label_id | string (uuid) | Yes | |
graph | object | No | The graph to simulate, such as the editor's unsaved canvas. Defaults to the draft. |
graph.edges | object[] | Yes | Up to 400 items. |
graph.edges[].from | string | Yes | Up to 64 characters. |
graph.edges[].label | string | No | Up to 40 characters. |
graph.edges[].to | string | Yes | Up to 64 characters. |
graph.nodes | object[] | Yes | Up to 200 items. |
graph.nodes[].config | object | No | Default {}. |
graph.nodes[].id | string | Yes | Up to 64 characters. |
graph.nodes[].position | object | No | |
graph.nodes[].type | enum | Yes | One of: trigger, condition, wait, send, assign, label, webhook, ai, crm_update, conversion_event, wait_for_reply, start_workflow, remove_from_workflow. |
sample_contact | object | No | |
sample_contact.company | string | No | Up to 200 characters. |
sample_contact.fields | object | No | Custom fields. |
sample_contact.first_name | string | No | Up to 100 characters. |
sample_contact.label_ids | string (uuid)[] | No | Labels from the workspace's labels. Up to 50 items. |
sample_contact.last_name | string | No | Up to 100 characters. |
sample_contact.opted_out | boolean | No | Simulate a contact who has opted out. Default false. |
sample_contact.timezone | string | No | For quiet hours. Defaults to the organisation's timezone. Up to 64 characters. |
trigger | enum | No | Defaults to the draft's trigger. One of: message.received, contact.created, schedule, webhook, crm.lead_imported, crm.contact_updated, label.added, label.removed. |
trigger_config | object | No | Defaults to the draft's trigger settings. |
trigger_message | string | No | The inbound message that starts the run, for the Inbound message trigger. Up to 2000 characters. |
Response 200
| Field | Type | Description |
|---|---|---|
contact | object | |
contact.label_ids | string (uuid)[] | |
contact.name | string | |
contact.opted_out | boolean | |
end | object or null | |
end.code | string or null | |
end.message | string | |
end.status | enum | One of: completed, cancelled, failed. |
exit_rules | object[] | |
exit_rules[].kind | enum | One of: label_removed. |
exit_rules[].label_id | string (uuid) | |
pending | object or null | |
pending.choices | enum[] | |
pending.node_id | string | |
pending.until | string (date-time) | ISO 8601 timestamp. |
steps | object[] | |
steps[].at | string (date-time) | ISO 8601 timestamp. |
steps[].branch | enum or null | One of: yes, no, replied, no_reply. |
steps[].code | string or null | |
steps[].decisions_before | integer | From 0 to 9007199254740991. |
steps[].detail | string or null | |
steps[].flipped | boolean | |
steps[].message | string or null | |
steps[].node_id | string | |
steps[].notes | string[] | |
steps[].status | enum | One of: completed, skipped, failed, waiting, cancelled. |
steps[].title | string | |
steps[].type | enum | One of: trigger, condition, wait, send, assign, label, webhook, ai, crm_update, conversion_event, wait_for_reply, start_workflow, remove_from_workflow. |
warnings | object[] | |
warnings[].message | string | |
warnings[].node_id | string 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
| Name | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | Yes |
Headers
| Name | Type | Required | Description |
|---|---|---|---|
x-workspace-id | string | No | Workspace for session (JWT) callers. API keys carry their own workspace. |
idempotency-key | string | No | Makes 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
| Field | Type | Required | Description |
|---|---|---|---|
contact_id | string | Yes | A contact marked as a test contact. |
send_events | boolean | No | Send Fire event and Webhook steps, marked test: true, to webhook URLs only. CRM and ad-platform destinations never get test events. Default false. |
shorten_waits | boolean | No | Every Wait and Wait for reply lasts 1 minute. Default false. |
Response 201
| Field | Type | Description |
|---|---|---|
contact_id | string or null | Contact id (ctc_...). |
error_code | string or null | |
finished_at | string (date-time) or null | ISO 8601 timestamp. |
id | string (uuid) | |
resume_at | string (date-time) or null | ISO 8601 timestamp. |
started_at | string (date-time) | ISO 8601 timestamp. |
status | enum | One of: running, waiting, completed, failed, cancelled. |
steps | object[] | |
steps[].at | string (date-time) | ISO 8601 timestamp. |
steps[].detail | object | |
steps[].node_id | string | |
steps[].status | enum | One of: completed, skipped, failed, waiting, cancelled. |
test | boolean | A live test started from the editor. Test runs don't count in the workflow's run totals or its limits. |
test_options | object or null | |
test_options.send_events | boolean | Fire event and Webhook steps send, marked test: true, to webhook URLs only. Off: they are skipped. |
test_options.shorten_waits | boolean | Every Wait and Wait for reply lasts 1 minute. |
version | integer | From 1 to 9007199254740991. |
workflow_id | string (uuid) | |
workflow_version_id | string (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
| Name | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | Yes |
Headers
| Name | Type | Required | Description |
|---|---|---|---|
x-workspace-id | string | No | Workspace for session (JWT) callers. API keys carry their own workspace. |
Response 200
| Field | Type | Description |
|---|---|---|
data | object[] | |
data[].created_at | string (date-time) | ISO 8601 timestamp. |
data[].graph | object | |
data[].graph.edges | object[] | Up to 400 items. |
data[].graph.nodes | object[] | Up to 200 items. |
data[].id | string (uuid) | |
data[].published_at | string (date-time) or null | ISO 8601 timestamp. |
data[].version | integer | From 1 to 9007199254740991. |
data[].workflow_id | string (uuid) |
Errors: 400, 401, 403, 404, 409, 413, 415, 429, 500. See errors for the shape and codes.