Workflows API
Every Workflows action (10) and data model (3): endpoints, scopes, input, preconditions, events and examples.
Automations that react to what happens, or run on a schedule, and do the work for you.
Module code workflows · 10 actions · 3 data models · 10 events. A key or token never does more here than the person behind it can; row-level security applies exactly as in the app. Scopes: workflows (everything), workflows:read (read only) or workflows:<capability>.
Resources
| Resource | Table | List and read | POST | PATCH | DELETE |
|---|---|---|---|---|---|
canvas_run_notes | canvas_run_note | /api/v1/canvas_run_notes | |||
canvas_triggers | canvas_trigger | /api/v1/canvas_triggers | |||
canvas_waits | canvas_wait | /api/v1/canvas_waits |
Lists take limit (up to 200), cursor, order (e.g. -created_at), fields and q; every other query parameter is a column filter: ?status=paid, ?total[gte]=100, ?id[in]=a,b, ?x=null.
Actions
| Action | Needs | What it does |
|---|---|---|
workflows.cancel_wait | workflows:write | Cancel a waiting workflow run |
workflows.decide_wait | workflows:write | Approve or reject a waiting workflow |
workflows.draft | workflows:manage | Draft a workflow |
workflows.install_template | workflows:manage | Start a workflow from a template |
workflows.note_undo | workflows:write | Record an undo of a workflow run |
workflows.park_canvas | workflows:write | Park a workflow run at a wait |
workflows.record_step_key | workflows:write | Remember a step’s idempotency key |
workflows.report_run_failed | workflows:read | Report a failed workflow run |
workflows.subscribe_canvas | workflows:manage | Start a workflow on a module event |
workflows.unsubscribe_canvas | workflows:manage | Stop a workflow listening to events |
workflows.cancel_wait
Cancel a waiting workflow run — Stops a canvas execution that is waiting; the blocks that already ran stay done.
Needs workflows:write · removes or cannot be undone · asks a person to confirm when Era runs it
https://app.getera.io/api/v1/canvas_waits/{id}/actions/cancel_wait- Canonical address:
POST /api/v1/actions/workflows.cancel_wait
Input
| Field | Type | Required | Notes |
|---|---|---|---|
waitId | uuid | yes | Filled from the path {id} · Waiting run · id of canvas_wait |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The waiting run must exist in this workspace. |
already_finished | The run must still be waiting. |
Events it publishes: workflows.wait_cancelled
What it writes: canvas_wait (update) — Marks the wait for cancelling
curl -X POST https://app.getera.io/api/v1/canvas_waits/CANVAS_WAIT_ID/actions/cancel_wait \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{}'{
"ok": true,
"result": "…",
"events": [
"workflows.wait_cancelled"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.canvasWaits.cancelWait("CANVAS_WAIT_ID")era canvas_waits cancel_wait CANVAS_WAIT_ID{
"name": "era__workflows__cancel_wait",
"arguments": {
"waitId": "CANVAS_WAIT_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold workflows:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, already_finished · 429 slow down
workflows.decide_wait
Approve or reject a waiting workflow — Answers an Era approval. Completing its task on the Tasks screen does the same.
Needs workflows:write · changes data
https://app.getera.io/api/v1/canvas_waits/{id}/actions/decide_wait- Canonical address:
POST /api/v1/actions/workflows.decide_wait
Input
| Field | Type | Required | Notes |
|---|---|---|---|
waitId | uuid | yes | Filled from the path {id} · Waiting run · id of canvas_wait |
approve | boolean | yes | Approve |
note | string | Note · max 500 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The waiting run must exist in this workspace. |
not_waiting_for_approval | The run must be waiting at an approval. |
Events it publishes: workflows.wait_decided
What it writes: canvas_wait (update) — Records the decision
curl -X POST https://app.getera.io/api/v1/canvas_waits/CANVAS_WAIT_ID/actions/decide_wait \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"approve":true}'{
"ok": true,
"result": "…",
"events": [
"workflows.wait_decided"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.canvasWaits.decideWait("CANVAS_WAIT_ID", {
approve: true
})era canvas_waits decide_wait CANVAS_WAIT_ID \
--approve true{
"name": "era__workflows__decide_wait",
"arguments": {
"waitId": "CANVAS_WAIT_ID",
"approve": true
}
}Errors: 401 no or expired credential · 403 the credential does not hold workflows:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, not_waiting_for_approval · 429 slow down
workflows.draft
Draft a workflow — Checks a whole workflow — trigger, conditions and steps — and draws it as a canvas for a person to review and deploy.
Needs workflows:manage · changes data
https://app.getera.io/api/v1/actions/workflows.draftInput
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | yes | Name · max 160 |
triggerEvent | string | When this happens · A module event, like crm.lead_won — or crm.* for every event a module publishes. · max 120 | |
triggerKind | string (enum) | Trigger · Allowed values: event, schedule, webhook, app_event, manual | |
triggerConfig | object / array | Trigger settings · Schedule: { cron, timezone, overlap }. Webhook: { webhookId }. Any: { cancelOn: { event, match } }. | |
description | string | Description · max 2000 | |
conditions | object / array | Only when | |
steps | object / array | Steps · A list of { action, input, options }. { "$event": "payload.id" } reads off the trigger; { "$step": "0", "path": "id" } off an earlier step. Flow steps: flow.branch, flow.filter, flow.delay, flow.wait_until, flow.approval, flow.connector, flow.loop, flow.parallel, flow.era ({ instruction } — one Era turn on the person’s AI subscription; "every day at 9, do X" = a schedule trigger + one flow.era step). |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
empty_name | A workflow needs a name. |
unknown_event | The trigger must be an event some module declares. |
bad_trigger | A webhook trigger names its endpoint; an app-event trigger is an apps.* event. |
bad_schedule | A schedule is a five-field cron in a real time zone. |
bad_condition | A condition needs a path and a known comparison. |
bad_step | Every step must be an object naming an action, with a well-formed flow input. |
unknown_action | A step may only call an action this platform registers, or a flow step. |
Events it publishes: workflows.drafted
curl -X POST https://app.getera.io/api/v1/actions/workflows.draft \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"name":"Name"}'{
"ok": true,
"result": "…",
"events": [
"workflows.drafted"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("workflows.draft", {
name: "Name"
})era workflows draft \
--name "Name"{
"name": "era__workflows__draft",
"arguments": {
"name": "Name"
}
}Errors: 401 no or expired credential · 403 the credential does not hold workflows:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 empty_name, unknown_event, bad_trigger, bad_schedule, bad_condition, bad_step, unknown_action · 429 slow down
workflows.install_template
Start a workflow from a template — Fills a template in for you and draws it as a canvas, ready to review and deploy.
Needs workflows:manage · changes data
https://app.getera.io/api/v1/actions/workflows.install_templateInput
| Field | Type | Required | Notes |
|---|---|---|---|
code | string | yes | Template · max 80 |
params | object / array | Answers |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
unknown_template | The template must exist. |
template_needs_module | Every module the template uses must be switched on. |
Events it publishes: workflows.template_used
curl -X POST https://app.getera.io/api/v1/actions/workflows.install_template \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"code":"Template"}'{
"ok": true,
"result": "…",
"events": [
"workflows.template_used"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("workflows.install_template", {
code: "Template"
})era workflows install_template \
--code "Template"{
"name": "era__workflows__install_template",
"arguments": {
"code": "Template"
}
}Errors: 401 no or expired credential · 403 the credential does not hold workflows:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 unknown_template, template_needs_module · 429 slow down
workflows.note_undo
Record an undo of a workflow run — Records which Era actions of a run were undone through their declared inverse, and which refused.
Needs workflows:write · changes data
https://app.getera.io/api/v1/canvas_run_notes/actions/note_undo- Canonical address:
POST /api/v1/actions/workflows.note_undo
Input
| Field | Type | Required | Notes |
|---|---|---|---|
canvasId | string | yes | Canvas workflow · max 128 |
executionId | string | yes | Canvas execution · max 128 |
status | string (enum) | yes | Outcome · Allowed values: done, partial |
steps | object / array | yes | Steps |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
bad_canvas | The canvas id must be a workflow id. |
already_undone | A run is undone once. |
Events it publishes: workflows.run_undone
What it writes: canvas_run_note (upsert) — Records the undo
curl -X POST https://app.getera.io/api/v1/canvas_run_notes/actions/note_undo \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"canvasId":"Canvas workflow","executionId":"Canvas execution","status":"done","steps":{}}'{
"ok": true,
"result": "…",
"events": [
"workflows.run_undone"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.canvasRunNotes.noteUndo({
canvasId: "Canvas workflow",
executionId: "Canvas execution",
status: "done",
steps: {}
})era canvas_run_notes note_undo --data '{"canvasId":"Canvas workflow","executionId":"Canvas execution","status":"done","steps":{}}'{
"name": "era__workflows__note_undo",
"arguments": {
"canvasId": "Canvas workflow",
"executionId": "Canvas execution",
"status": "done",
"steps": {}
}
}Errors: 401 no or expired credential · 403 the credential does not hold workflows:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 bad_canvas, already_undone · 429 slow down
workflows.park_canvas
Park a workflow run at a wait — Machine-only: the Era wait / approval block parks the canvas execution until its time, its answer or its expiry.
Needs workflows:write · changes data
403 machine_only).https://app.getera.io/api/v1/canvas_waits/actions/park_canvas- Canonical address:
POST /api/v1/actions/workflows.park_canvas
Input
| Field | Type | Required | Notes |
|---|---|---|---|
canvasId | string | yes | Canvas workflow · max 128 |
executionId | string | yes | Canvas execution · max 128 |
contextId | string | yes | Pause point · max 300 |
kind | string (enum) | yes | Waits for · Allowed values: wait, approval |
until | date-time | Until | |
taskId | uuid | Approval task · id of ai_task | |
blockName | string | Block · max 200 | |
event | object / array | Trigger event |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
bad_canvas | The canvas id must be a workflow id. |
bad_wait | A wait ends at a readable moment within 30 days. |
Events it publishes: workflows.canvas_parked
What it writes: canvas_wait (upsert) — Records the parked execution
Errors: 401 no or expired credential · 403 the credential does not hold workflows:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 bad_canvas, bad_wait · 429 slow down
workflows.record_step_key
Remember a step’s idempotency key — Machine-only: an Era block with an idempotency key succeeded, so it does not run again for that key.
Needs workflows:write · changes data
403 machine_only).https://app.getera.io/api/v1/actions/workflows.record_step_keyInput
| Field | Type | Required | Notes |
|---|---|---|---|
canvasId | string | yes | Canvas workflow · max 128 |
blockId | string | yes | Block · max 128 |
key | string | yes | Key · max 500 |
executionId | string | Canvas execution · max 128 | |
result | object / array | Result |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
bad_canvas | A step key names its canvas, its block and the key. |
already_done | The block has not already succeeded for this key. |
Events it publishes: workflows.step_key_recorded
What it writes: canvas_step_key (insert) — Remembers the key
Errors: 401 no or expired credential · 403 the credential does not hold workflows:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 bad_canvas, already_done · 429 slow down
workflows.report_run_failed
Report a failed workflow run — Machine-only: turns a failed canvas execution into the workflows.run_failed event for its owner, once.
Needs workflows:read · changes data
403 machine_only).https://app.getera.io/api/v1/canvas_run_notes/actions/report_run_failed- Canonical address:
POST /api/v1/actions/workflows.report_run_failed
Input
| Field | Type | Required | Notes |
|---|---|---|---|
canvasId | string | yes | Canvas workflow · max 128 |
executionId | string | yes | Canvas execution · max 128 |
name | string | Workflow · max 300 | |
error | string | Error · max 2000 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
bad_canvas | The canvas id must be a workflow id. |
already_reported | A failure is reported once. |
Events it publishes: workflows.run_failed
What it writes: canvas_run_note (upsert) — Marks the failure reported
Errors: 401 no or expired credential · 403 the credential does not hold workflows:read · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 bad_canvas, already_reported · 429 slow down
workflows.subscribe_canvas
Start a workflow on a module event — The canvas starts by itself when the event happens — called when a canvas with an Era event trigger is deployed.
Needs workflows:manage · changes data
https://app.getera.io/api/v1/canvas_triggers/actions/subscribe_canvas- Canonical address:
POST /api/v1/actions/workflows.subscribe_canvas
Input
| Field | Type | Required | Notes |
|---|---|---|---|
canvasId | string | yes | Canvas workflow · max 128 |
eventPattern | string | yes | When this happens · A module event, like crm.lead_won — or crm.* for every event a module publishes. · max 120 |
enabled | boolean | Listening | |
cancelOn | object / array | Stop a waiting run when · { event, match } — e.g. { "event": "inbox.assigned", "match": "payload.id" }. |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
bad_canvas | The canvas id must be a workflow id. |
unknown_event | Something in the platform must publish the event. |
bad_trigger | A "stop when" names an event and the path it matches. |
Events it publishes: workflows.canvas_subscribed
What it writes: canvas_trigger (upsert) — Creates or updates the canvas subscription
curl -X POST https://app.getera.io/api/v1/canvas_triggers/actions/subscribe_canvas \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"canvasId":"Canvas workflow","eventPattern":"When this happens"}'{
"ok": true,
"result": "…",
"events": [
"workflows.canvas_subscribed"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.canvasTriggers.subscribeCanvas({
canvasId: "Canvas workflow",
eventPattern: "When this happens"
})era canvas_triggers subscribe_canvas \
--canvas-id "Canvas workflow" \
--event-pattern "When this happens"{
"name": "era__workflows__subscribe_canvas",
"arguments": {
"canvasId": "Canvas workflow",
"eventPattern": "When this happens"
}
}Errors: 401 no or expired credential · 403 the credential does not hold workflows:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 bad_canvas, unknown_event, bad_trigger · 429 slow down
workflows.unsubscribe_canvas
Stop a workflow listening to events — Removes the canvas subscription; the canvas no longer starts on module events.
Needs workflows:manage · removes or cannot be undone
https://app.getera.io/api/v1/canvas_triggers/actions/unsubscribe_canvas- Canonical address:
POST /api/v1/actions/workflows.unsubscribe_canvas
Input
| Field | Type | Required | Notes |
|---|---|---|---|
canvasId | string | yes | Canvas workflow · max 128 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_subscribed | The canvas must be subscribed. |
Events it publishes: workflows.canvas_unsubscribed
What it writes: canvas_trigger (delete) — Removes the subscription
curl -X POST https://app.getera.io/api/v1/canvas_triggers/actions/unsubscribe_canvas \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"canvasId":"Canvas workflow"}'{
"ok": true,
"result": "…",
"events": [
"workflows.canvas_unsubscribed"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.canvasTriggers.unsubscribeCanvas({
canvasId: "Canvas workflow"
})era canvas_triggers unsubscribe_canvas \
--canvas-id "Canvas workflow"{
"name": "era__workflows__unsubscribe_canvas",
"arguments": {
"canvasId": "Canvas workflow"
}
}Errors: 401 no or expired credential · 403 the credential does not hold workflows:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_subscribed · 429 slow down
Data models
CanvasRunNote
Table canvas_run_note · Resource canvas_run_notes
GET /api/v1/canvas_run_notesGET /api/v1/canvas_run_notes/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
canvas_id | string · text | |||
execution_id | string · text | |||
failure_reported_at | string (date-time) · timestamptz | yes | ||
undo_status | string · text | yes | ||
undo | object · jsonb | yes | ||
created_at | string (date-time) · timestamptz | |||
updated_at | string (date-time) · timestamptz |
Primary key: id
Checks:
CHECK (((undo_status IS NULL) OR (undo_status = ANY (ARRAY['done'::text, 'partial'::text]))))
CanvasTrigger
Table canvas_trigger · Resource canvas_triggers
GET /api/v1/canvas_triggersGET /api/v1/canvas_triggers/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
canvas_id | string · text | |||
event_pattern | string · text | |||
acts_as_user_id | string (uuid) · uuid | app_user | ||
is_enabled | boolean · bool | |||
last_run_at | string (date-time) · timestamptz | yes | ||
run_count | integer · int4 | |||
failure_count | integer · int4 | |||
last_error | string · text | yes | ||
created_at | string (date-time) · timestamptz | |||
updated_at | string (date-time) · timestamptz | |||
cancel_on | object · jsonb | yes |
Primary key: id
Checks:
CHECK ((canvas_id ~ '^[A-Za-z0-9_-]{1,128}$'::text))CHECK (((event_pattern = '*'::text) OR (event_pattern ~ '^[a-z][a-z0-9_]*\.([a-z][a-z0-9_]*\|\*)$'::text)))
CanvasWait
Table canvas_wait · Resource canvas_waits
GET /api/v1/canvas_waitsGET /api/v1/canvas_waits/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
canvas_id | string · text | |||
execution_id | string · text | |||
context_id | string · text | |||
kind | string · text | wait, approval | ||
status | string · text | waiting, resuming, resumed, rejected, expired, cancelled, failed | ||
wake_at | string (date-time) · timestamptz | yes | ||
task_id | string (uuid) · uuid | yes | ||
decision | string · text | yes | ||
acts_as_user_id | string (uuid) · uuid | app_user | ||
block_name | string · text | yes | ||
cancel_key | string · text | yes | ||
error | string · text | yes | ||
created_at | string (date-time) · timestamptz | |||
resolved_at | string (date-time) · timestamptz | yes |
Primary key: id
Checks:
CHECK ((canvas_id ~ '^[A-Za-z0-9_-]{1,128}$'::text))CHECK (((decision IS NULL) OR (decision = ANY (ARRAY['approved'::text, 'rejected'::text]))))
Events
Workflows and webhooks react to these events.
| Event | What happens | Payload |
|---|---|---|
workflows.canvas_parked | A workflow run is waiting at a wait or an approval. | id, canvasId, executionId, kind, wakeAt |
workflows.canvas_subscribed | A canvas workflow started listening to a module event. | id, canvasId, eventPattern |
workflows.canvas_unsubscribed | A canvas workflow stopped listening to module events. | id, canvasId |
workflows.drafted | A workflow was drafted and drawn as a canvas. | name, trigger, steps |
workflows.run_failed | A workflow run failed. For its owner. | id, canvasId, runId, name, error, ownerUserId, recipientUserId |
workflows.run_undone | What a workflow run did was undone. | id, canvasId, runId, status |
workflows.step_key_recorded | A block with an idempotency key succeeded. | id, canvasId, blockId |
workflows.template_used | A workflow was started from a template. | code, name |
workflows.wait_cancelled | A person cancelled a waiting workflow run. | id, canvasId, executionId |
workflows.wait_decided | A person approved or rejected a waiting workflow run. | id, canvasId, executionId, decision |