Skip to content
Dashboard

Workflows

A workflow is an automation: a trigger (a reservation is created, a task is completed, a schedule fires), optional conditions, and the actions to run. The SuiteOp app calls them agents. Over the API you describe a workflow in plain language and SuiteOp’s AI builds the steps, then you choose where it runs and publish it. This guide covers the nine workflow operations. Every field is listed on the operation’s page in the API Reference.

OperationRequestPermission
create_workflowPOST /workflowsmanage_workflows
modify_workflowPOST /workflows/{workflowId}/modifymanage_workflows
set_workflow_scopesPOST /workflows/{workflowId}/scopesmanage_workflows
publish_workflowPOST /workflows/{workflowId}/publishmanage_workflows
update_workflowPATCH /workflows/{workflowId}manage_workflows
list_workflow_outlinesGET /workflowsview_workflows
get_workflow_outlineGET /workflows/{workflowId}view_workflows
unpublish_workflowPOST /workflows/{workflowId}/unpublishmanage_workflows
delete_workflowDELETE /workflows/{workflowId}manage_workflows and delete_workflows

Paths are relative to your region’s base URL, for example https://api-us.suiteop.com/api/v1. See Authentication for permissions.

The API reads a workflow’s structure but not its content: no title, step names, message bodies or step settings come back on any operation. The one name it returns is the workflow’s folder. Keep the workflowId that create_workflow returns, because nothing else identifies a workflow you made. The API also can’t connect the integrations a workflow uses.

A workflow goes live in three steps. Skipping one leaves it unable to fire.

  1. Create. create_workflow saves the workflow as an inactive, unscoped draft.
  2. Scope. set_workflow_scopes chooses the properties, property groups or tags it runs on. A workflow with no scope never runs, and publishing one is refused.
  3. Publish. publish_workflow makes the draft live and switches the workflow on.

Later edits with modify_workflow land on a new draft. The live version keeps running unchanged until you publish again. update_workflow renames, pauses, resumes or refiles a workflow without touching its steps. unpublish_workflow takes the live version offline and keeps the steps as a draft. delete_workflow removes the workflow for good.

POST /workflows takes one field, description: the whole automation in plain language, up to 20,000 characters. The AI sees this text and nothing else, so name the trigger, the timing, the conditions and each action explicitly.

Terminal window
curl -X POST https://api-us.suiteop.com/api/v1/workflows \
-H "Authorization: Bearer sk_live_your_key_here" \
-H "Idempotency-Key: 9c4e2a1b-7d3f-4b8e-a5c6-0f1d2e3b4a57" \
-H "Content-Type: application/json" \
-d '{"description": "When a reservation is created, create a cleaning task in the Housekeeping department due on the check-out day at 11:00, and send a Slack message to #ops."}'
{
"data": {
"success": true,
"workflowId": "d8b3f1a2-6c4e-4f7a-9b2d-1e0c3a5f7b64",
"nodeCount": 3,
"edgeCount": 2
},
"meta": { "requestId": "…" }
}
  • missingPlugin is set when the workflow needs an integration your organization hasn’t connected. It is an integration ID such as slack, or unknown. Connect it in the app, then send the request again.

  • refusalCode and refusalMessage are set when the description can’t be built as asked. refusalMessage says what to change. Rewrite the description and resend.

  • When the AI declines to build the workflow, create_workflow returns one of these refusalCode values with a matching refusalMessage:

    refusalCodeMeaning
    validation.workflow.generate_email_sender_unavailableThe workflow sends email and the organization has no verified sender. Verify one in the app
    validation.workflow.generate_email_template_unavailableThe workflow sends email and the organization has no email template. Create one with create_email_template
    validation.workflow.generate_task_template_unavailableThe workflow creates a task and the organization has no task template. Create one first
    validation.workflow.generate_unsupportedAnything else, such as a schedule SuiteOp has no trigger for. Describe one starting event and the steps after it, using triggers and actions the workflow editor offers

    Branch on refusalCode, not on the message text. modify_workflow can still return success: false with neither field when the AI declines; rephrase the instruction and resend.

The AI can build a different graph from the same text on two calls, and a call can take up to about 50 seconds. Set your HTTP client’s timeout accordingly. The response doesn’t include the workflow’s title or its steps.

POST /workflows/{workflowId}/modify applies a plain-language instruction to an existing workflow. The response has the same fields as create, without workflowId.

Terminal window
curl -X POST "https://api-us.suiteop.com/api/v1/workflows/d8b3f1a2-6c4e-4f7a-9b2d-1e0c3a5f7b64/modify" \
-H "Authorization: Bearer sk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"instruction": "Create the cleaning task and send the Slack message to #ops, and also email the guest the Welcome template two days before check-in."}'
  • State the end state, not a delta. The AI rewrites the complete graph, so steps you don’t mention can change. get_workflow_outline shows the step types but not their wording or settings, so describe the whole workflow you want.
  • Edits land on the draft. A published workflow keeps running its previous version until you call publish_workflow again.
  • Hidden values stay put. Values SuiteOp masks, such as HTTP headers, query parameters, request bodies and URLs, can’t be changed this way. When the instruction tried to change one, the edit is saved without that change and hiddenValuesNotChanged lists each one by nodeId and field (url, body, header, query or parameter). cleared: true means the field was saved without its value and must be re-entered in the app.
  • A deleted workflow, or one in another organization, returns 404 not_found_error. If someone saves the workflow while the AI is working, the call returns 409 conflict_error; send it again.

POST /workflows/{workflowId}/scopes sets where the workflow runs, one scopeType at a time: property, property_group or tag.

Terminal window
curl -X POST "https://api-us.suiteop.com/api/v1/workflows/d8b3f1a2-6c4e-4f7a-9b2d-1e0c3a5f7b64/scopes" \
-H "Authorization: Bearer sk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"scopeType": "property_group", "entityIds": ["2e6d0f5b-8a1c-4d3e-9b7f-6c0a2e4d8f13"]}'
{ "data": { "success": true }, "meta": { "requestId": "…" } }
  • It replaces one type. entityIds is the complete list for that scopeType: anything you leave out is removed, and [] clears it. The other two types are untouched.
  • IDs must match scopeType and be yours. IDs from another organization return 400 validation_error with details.key set to validation.scope_ids_not_in_org.
  • A live workflow can’t lose its last scope. Clearing it returns 400 validation_error. Pause the workflow with update_workflow first.
  • The response doesn’t echo the scopes. These are workflow scopes, separate from the entity scopes of portal content.

POST /workflows/{workflowId}/publish takes an empty body. It makes the current draft the live version and switches the workflow on.

Terminal window
curl -X POST "https://api-us.suiteop.com/api/v1/workflows/d8b3f1a2-6c4e-4f7a-9b2d-1e0c3a5f7b64/publish" \
-H "Authorization: Bearer sk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{}'
{
"data": {
"success": true,
"publishedVersionId": "71c0e9d4-3a2b-4c5f-8e6d-9b1a0f2c3d48",
"activeVersionId": "71c0e9d4-3a2b-4c5f-8e6d-9b1a0f2c3d48",
"isActive": true
},
"meta": { "requestId": "…" }
}

Publishing checks the draft first. It returns 400 validation_error when the workflow has no scope (details.key is validation.workflow.publish_unscoped), no trigger, steps missing required settings, unresolved {{…}} references, or a trigger that is no longer offered. Each refusal’s message says what to change. It returns 404 when there is no draft to publish, and 409 conflict_error when the workflow changed during the call. Read the draft again with get_workflow_outline, then retry.

PATCH /workflows/{workflowId} changes a workflow’s settings, never its steps. Send only the fields that change.

Terminal window
curl -X PATCH "https://api-us.suiteop.com/api/v1/workflows/d8b3f1a2-6c4e-4f7a-9b2d-1e0c3a5f7b64" \
-H "Authorization: Bearer sk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"isActive": false}'
  • isActive: false pauses the workflow, true resumes it. Switching on a workflow with no scope returns 400 validation_error. To put new steps live, use publish_workflow.
  • title: a new name, 1 to 200 characters.
  • folderId: moves the workflow to a folder in your organization, or null takes it out of its folder. Folders are created in the app.

The response is { "success": true }.

GET /workflows lists the workflows you can reach, drafts included, 20 per page by default (maximum 100). It is paginated and filters on isActive and trigger, a trigger category such as reservation, task, device, payment or schedule.

Terminal window
curl "https://api-us.suiteop.com/api/v1/workflows?isActive=true&trigger=reservation" \
-H "Authorization: Bearer sk_live_your_key_here"
{
"data": [
{
"id": "d8b3f1a2-6c4e-4f7a-9b2d-1e0c3a5f7b64",
"isActive": true,
"isPublished": true,
"hasUnpublishedChanges": false,
"triggers": ["Reservation created"],
"folder": "Turnovers",
"updatedAt": "2026-10-08T15:22:40.000Z"
}
],
"meta": {
"requestId": "…",
"pagination": { "total": 1, "limit": 20, "offset": 0 }
}
}

Rows carry no title. They are sorted by title, so the order is stable but you can’t read the sort key. Match a workflow by the id you stored, or by its triggers. folder is present only when the workflow is in a folder.

GET /workflows/{workflowId} returns one workflow’s outline: its steps in run order, how they connect, where it applies, and what would block publishing.

{
"data": {
"id": "d8b3f1a2-6c4e-4f7a-9b2d-1e0c3a5f7b64",
"isActive": true,
"isPublished": true,
"hasUnpublishedChanges": true,
"triggerCategory": "reservation",
"steps": [
{ "id": "trigger-1", "kind": "trigger", "label": "Reservation created" },
{
"id": "action-1",
"kind": "action",
"label": "Send a Slack message",
"action": "send_a_slack_message"
}
],
"connections": [{ "from": "trigger-1", "to": "action-1" }],
"appliesTo": { "propertyGroupIds": ["2e6d0f5b-8a1c-4d3e-9b7f-6c0a2e4d8f13"] },
"stepCount": 2,
"issues": [],
"note": "This agent has unpublished draft changes — the live version is still the last published one. Publishing is what makes edits take effect."
},
"meta": { "requestId": "…" }
}
  • It reads the draft. When hasUnpublishedChanges is true, the steps are the draft’s, not the version that is running. A workflow that has never been published also reports hasUnpublishedChanges: true.
  • label is SuiteOp’s name for the step type, never the name someone gave the step. A step that runs a custom plugin action reports action: "custom".
  • unreachable: true marks a step no trigger leads to. These come last, and are usually a mistake in the graph.
  • appliesTo lists propertyIds, propertyGroupIds and tagIds. An unscoped workflow reports unscoped: true and a note instead, and never runs.
  • issues lists what publish_workflow would refuse, by stepId, code and status (new since the live version, or pre_existing). New issues come first. Use modify_workflow to fix them.
  • At most 80 steps and 40 issues are returned. stepsOmitted and issuesOmitted count the rest. stepCount is always the full count.

POST /workflows/{workflowId}/unpublish takes an empty body. It takes the live version offline and switches the workflow off, so it stops firing. The steps stay saved as a draft, and publish_workflow puts the workflow back live. This is the reversible way to stop a workflow.

{
"data": { "success": true, "activeVersionId": null, "isActive": false },
"meta": { "requestId": "…" }
}

update_workflow with isActive: false is different: it switches the workflow off but keeps it published, so isActive: true resumes the same live version.

DELETE /workflows/{workflowId} deletes the workflow. It stops firing and leaves list_workflow_outlines, and no operation restores it. It needs delete_workflows as well as manage_workflows, so a key created without the delete permission can’t call it. The response is { "success": true }. Deleting an already-deleted workflow returns 404 not_found_error.

If the workflow is deleted or moved to another organization while an unpublish runs, the call returns 409 conflict_error and changes nothing. Unpublish doesn’t check for other changes: if someone publishes while it runs, both calls succeed and whichever finishes last decides whether the workflow ends up live, so check it with get_workflow_outline afterwards. Two deletes racing each other both succeed; a delete returns 409 only if the workflow is removed or moved to another organization while the call runs.

  • Unknown fields are rejected. Every workflow operation rejects fields it doesn’t define with 400 validation_error. There is no propertyIds, isActive or publish on create or modify.
  • AI rate limit. create_workflow and modify_workflow count toward the per-organization budget for AI-backed operations, shared by every key in the organization (see Rate limits). An AI outage returns 502 or 503; retry later.
  • Idempotency. Send an Idempotency-Key on the POST operations so a retried create doesn’t make a second workflow (see Idempotency). PATCH ignores the header. An AI call can run close to a minute, so wait for the first request to finish before retrying with the same key, or the retry returns 409.
  • Property-limited callers. An OAuth token carries its member’s property access. For such a caller, set_workflow_scopes replaces only the scopes the member can reach and keeps the rest, so a 200 doesn’t mean the workflow runs exactly where you asked. An ID outside the member’s access returns 404. The list shows such a caller the workflows on its properties plus unscoped ones, the outline’s propertyIds leave out properties outside the grant, and a workflow outside its reach returns 404 on every operation.
  • Match on keys, not text. Workflow refusals carry a sentence in message and a stable key such as validation.workflow.publish_unscoped in details.key (see Errors). Match on details.key and error.code, never on the English wording.
  • No webhooks. Creating, publishing or running a workflow doesn’t send a webhook event.