Returning.AIDocs
v1

API reference / Workflow Builder

.md

Deploy a workflow

Takes a workflow live.

Last updated 28 Oct 2026API v1

Method
POST
Path
https://api.returning.ai/v1/workflow-builder/data-workflows/{workflowId}/deploy
Permission
workflowBuilder
Retries
Deploying an already deployed workflow is idempotent in effect: the second call stores the same status and rebuilds the same step order.

Authentication#

Header
Authorization: Bearer <API_KEY>
Permission
workflowBuilder

Authorization: Bearer <API_KEY> with workflowBuilder, on a workflow of the key's community. If-Match is optional: send the revision you read and a stale value is refused with 409 VERSION_CONFLICT, omit it and the deploy applies as the last write. Deploying a workflow that is already deployed is allowed, and needs no extra header.

There is no separate deploy permission: the one workflowBuilder permission covers building, testing, deploying and reading. If you want a workflow to be reviewable before it runs, that has to be a policy on your side, or a copy of the workflow deployed while the original stays undeployed.

FailureMeaningRecovery
404 WORKFLOW_NOT_FOUNDUnknown id, or another community's workflowUse an id from this community.
400 NODE_NOT_IN_WORKFLOWA router branch lists a step that is not part of this workflowFix the branch lists; this is a tenant check and one of the two things that can refuse a deploy.
400 LEADERBOARD_NOT_IN_COMMUNITYA step names a leaderboard this community does not haveUse a leaderboard of the key's community; the other refusal.

Behaviour#

Deploy stores the deployed state, rebuilds the step order from the stored steps, and arms the trigger.

Validation is advisory: deploy runs the same static check as POST /{workflowId}/validate, returns its findings as warnings in the response, and never refuses because of them. A workflow with a router it cannot evaluate, a dangling reference, an empty imported credential or no trigger at all still deploys; it may then fail or do nothing at run time, which is exactly what the warnings describe.

After a deploy, the trigger starts runs: a webhook accepts its calls, an ingest accepts batches, a schedule runs on its cadence.

Request#

Path parameters#

workflowId#stringREQUIRED
Workflow identifier: 24 hexadecimal characters, as returned in _id.

Rule^[0-9a-fA-F]{24}$

Eg65f1a2b3c4d5e6f708192a3b

Headers#

Authorization#stringREQUIRED
Community API key: Bearer <API_KEY>.

EgBearer <API_KEY>

If-Match#stringOPTIONAL
Optional. Send the revision the change is based on, bare or quoted, to avoid overwriting a concurrent change: a stale value is refused with 409 VERSION_CONFLICT. Omit it for last-write-wins.

Eg162af81f5de4ff2b557a4087de9a9a73a0ed4e621f737787e35a93614cb50d1c

Watch for

  • Deploy does not refuse a workflow with no trigger. It deploys, status reads deployed, and nothing runs until PUT /{workflowId}/trigger sets one; the response's warnings carry TRIGGER_REQUIRED.
  • Deploy rebuilds the workflow's step order from the stored steps: main line by order, then each router's branch members. The order a client last sent is not trusted.
  • A deployed workflow accepts every edit, including a delete, and the change applies to the next run immediately. Copy, edit the copy, dry-run it, then deploy the copy if you want a reviewable change.
  • No validation finding refuses a deploy: a router that uses OR or WHERE condition groups, a template whose step id is not in the workflow, retryOnFailure on a router that pays, an empty imported credential, POST_LOGIC_REQUIRED, NODE_LISTED_TWICE or MISSING_SECRET all arrive as warnings and the workflow goes live. The warnings are the whole safety net, so read them.
  • Because the validator reuses the run path's own reference and operator rules, a warning names exactly what a run will mishandle: a condition the platform evaluates as always true, a reference that resolves to nothing, a retry that repeats a payout.
  • A Code step deploys like any other step. Its source runs in the platform sandbox on every run, so dry-run it first if you have not run that source here before.
  • Deploying does not send a test event and does not prove the trigger is reachable from outside. Fire it for real.
  • A webhook key cannot be changed by a deploy or by a PUT that sends a new value. Getting a different key means replacing the trigger's webhook type, which is a trigger change, not a deploy.
More examples and details

A workflow with a trigger and no warnings:

BASH
curl --request POST \
  --url "https://api.returning.ai/v1/workflow-builder/data-workflows/65f1a2b3c4d5e6f708192a3b/deploy" \
  --header "Authorization: Bearer <API_KEY>" \
  --header "If-Match: 162af81f5de4ff2b557a4087de9a9a73a0ed4e621f737787e35a93614cb50d1c"
JSON
{
  "data": {
    "_id": "65f1a2b3c4d5e6f708192a3b",
    "communityId": "65f1a2b3c4d5e6f708192a50",
    "name": "VIP bonus",
    "status": "deployed",
    "outgoingWebhookUrl": null,
    "createdAt": "2026-10-24T09:15:00.000Z",
    "updatedAt": "2026-10-24T13:02:41.000Z",
    "trigger": { "_id": "65f1a2b3c4d5e6f708192a41", "name": "Trade bonus trigger", "type": "event-ingest", "status": "idle", "options": { "batchIdPath": "metadata.batch_id", "recordIdPaths": ["trade_id"] } },
    "nodes": [],
    "revision": "68f78253046393414dec404e5442c140bb5db0393a7d1c389ff57bba71585501",
    "redactedPaths": [],
    "warnings": []
  }
}

A deploy that carried warnings. The workflow is live; the warnings name what a run may mishandle:

JSON
{
  "data": {
    "_id": "65f1a2b3c4d5e6f708192a3b",
    "communityId": "65f1a2b3c4d5e6f708192a50",
    "name": "VIP bonus",
    "status": "deployed",
    "outgoingWebhookUrl": null,
    "createdAt": "2026-10-24T09:15:00.000Z",
    "updatedAt": "2026-10-24T13:02:41.000Z",
    "trigger": { "_id": "65f1a2b3c4d5e6f708192a41", "name": "Trades", "type": "event-ingest", "status": "idle", "options": { "batchIdPath": "metadata.batch_id", "recordIdPaths": ["trade_id"] } },
    "nodes": [],
    "revision": "68f78253046393414dec404e5442c140bb5db0393a7d1c389ff57bba71585501",
    "redactedPaths": [],
    "warnings": [
      { "nodeId": "65f1a2b3c4d5e6f708192a3e", "path": "options.branches[0].conditions[0].operator", "code": "ROUTER_OPERATOR_UNSAFE", "message": "The runtime treats OR and WHERE condition groups as always true." },
      { "nodeId": "65f1a2b3c4d5e6f708192a3d", "path": "options.fields[0].value", "code": "REFERENCE_MAY_BE_UNSET", "message": "This reads a branch step that may not run; the value can be unset." }
    ]
  }
}
curl --request POST \
  --url 'https://api.returning.ai/v1/workflow-builder/data-workflows/<workflowId>/deploy' \
  --header 'Authorization: Bearer <API_KEY>'

Response#

HTTP 200 with the workflow, status: "deployed" and the new revision in the body and the ETag header. The trigger reads idle, which is the armed state. The body also carries warnings, the same advisory findings POST /{workflowId}/validate returns, so an agent that skipped validate still sees them. Read them, because nothing else will stop the workflow from running in a shape the platform warns about.

Read back with GET /{workflowId}: status must be deployed, and the trigger must be the type you expect. Then prove the workflow runs: fire the trigger with a synthetic event and read the run with GET /{workflowId}/runs. A deploy alone proves configuration, not execution.

data#object
A workflow after a deploy, with the validator's advisory findings.

Eg{ ... }

_id#string
Workflow identifier.

Eg"65f1a2b3c4d5e6f708192a3b"

communityId#string
Community the workflow belongs to.

Eg"65f1a2b3c4d5e6f708192a50"

name#string
Workflow name.

Eg"VIP bonus"

status#string
draft, deployed or undeployed.

Eg"deployed"

outgoingWebhookUrl#string
Failure webhook URL, when one is set.

RuleNullable

Egnull

createdAt#string
Creation time.

Eg"2026-10-24T09:15:00.000Z"

updatedAt#string
Last authored change.

Eg"2026-10-24T09:20:11.000Z"

trigger#object
The trigger, or null when the workflow has none.

RuleNullable

Eg{ ... }

_id#string
Trigger identifier. Use it as the source of a reference.

Eg"65f1a2b3c4d5e6f708192a41"

name#string
Display name.

Eg"Trade bonus trigger"

type#string
Trigger type.

Eg"webhook"

status#string
Trigger lifecycle state, for example idle.

Eg"idle"

options#object
Trigger options. A webhook key is shown only in the response that created it.

RuleNullable

Eg{ ... }

nodes#object[]
Main-line steps in canvas order, routers carrying their branches.

Eg[ ... ]

_id#string
Step identifier.

Eg"65f1a2b3c4d5e6f708192a3c"

name#string
Step name.

Eg"Read member XP"

type#string
Step type.

Eg"platform-action"

order#integer
Position on the main line, from 1.

Eg1

continueOnFailure#boolean
Whether a failure stops the run.

Egfalse

retryOnFailure#boolean
Whether the step is retried on failure.

Egfalse

options#object
Stored step options, with every credential replaced by ["__REDACTED__"].

RuleNullable

Eg{ ... }

status#string
Last run state of the step, when one exists.

Eg"success"

nestedNodes#object[]
Present on a router: each branch carries its own workflowNodeIds and nestedNodes.
revision#string
64 hexadecimal characters. Send it back as If-Match; it also arrives as the ETag header.

Eg"68f78253046393414dec404e5442c140bb5db0393a7d1c389ff57bba71585501"

redactedPaths#string[]
Every location where a credential was replaced by ["__REDACTED__"].

Eg[]

warnings#object[]
The same advisory findings POST /{workflowId}/validate returns, in the same shape.

Eg[]

{
  "data": {
    "_id": "65f1a2b3c4d5e6f708192a3b",
    "communityId": "65f1a2b3c4d5e6f708192a50",
    "name": "VIP bonus",
    "status": "deployed",
    "outgoingWebhookUrl": null,
    "createdAt": "2026-10-24T09:15:00.000Z",
    "updatedAt": "2026-10-24T09:20:11.000Z",
    "trigger": {
      "_id": "65f1a2b3c4d5e6f708192a41",
      "name": "Trade bonus trigger",
      "type": "webhook",
      "status": "idle",
      "options": {
        "tokenExchangeEnabled": false,
        "apiKey": {
          "enabled": false
        },
        "aesKey": {
          "enabled": false
        }
      }
    },
    "nodes": [
      {
        "_id": "65f1a2b3c4d5e6f708192a3c",
        "name": "Read member XP",
        "type": "platform-action",
        "order": 1,
        "continueOnFailure": false,
        "retryOnFailure": false,
        "options": {
          "action": "get-user-fields",
          "selectUser": {
            "type": "email",
            "value": ["{{65f1a2b3c4d5e6f708192a41.email}}"]
          },
          "fields": [
            {
              "label": "Total XP",
              "field": "total_xp"
            }
          ]
        }
      }
    ],
    "revision": "68f78253046393414dec404e5442c140bb5db0393a7d1c389ff57bba71585501",
    "redactedPaths": [],
    "warnings": []
  }
}

Errors#

Fix the request06

400VALIDATION_FAILEDFix the request
The id is malformed. Use the id exactly as returned.
400NODE_NOT_IN_WORKFLOWFix the request
A router branch list names a step that is not in this workflow. Fix the branch lists.
400LEADERBOARD_NOT_IN_COMMUNITYFix the request
A step names a leaderboard this community does not have. Use one of this community's leaderboards.
401AUTHENTICATION_REQUIREDFix the request
No key, or an unknown key. Send a valid key.
401API_KEY_REQUIREDFix the request
A valid admin session was used, because this API accepts community API keys only. Use a community API key.
403API_KEY_PERMISSION_DENIEDFix the request
No workflowBuilder. Grant the permission.

Fix the data03

404WORKFLOW_NOT_FOUNDFix the data
Unknown id, or another community's workflow. Use an id from this community.
409VERSION_CONFLICTFix the data
You sent an If-Match that is not the current revision. meta.currentRevision carries the live value. Re-read and deploy again.
409WORKFLOW_LOCKEDFix the data
Another write to this workflow held the lock for too long, and this write was not applied. Re-read and retry.

Retry with backoff03

429RATE_LIMITEDRetry with backoff
The community's API rate limit was reached. Wait for the window to reset, then retry.
500INTERNAL_ERRORRetry with backoff
Unexpected failure. Re-read the status before retrying.
503LOCK_UNAVAILABLERetry with backoff
The write lock store did not answer, so nothing was written. Retry shortly.

Retries

Deploying an already deployed workflow is idempotent in effect: the second call stores the same status and rebuilds the same step order. It returns a new revision only if something changed. A retry after a timeout is safe in the sense that it cannot create a second workflow. Check the status first: if the workflow reads deployed, the first call landed.

{
  "meta": {
    "status": "error",
    "statusCode": 400,
    "code": "VALIDATION_FAILED",
    "errors": [
      {
        "path": "params.workflowId",
        "message": "Id must be a 24 character hex string"
      }
    ],
    "requestId": "3f4d0e8b-7a6c-4f1e-9b2d-5a7c8e1f0a3b"
  },
  "message": "API request rejected error.",
  "detail": "The request body is not valid.",
  "solution": "Read error detail and try again."
}

Next step#

Undeploy a workflowPOST/v1/workflow-builder/data-workflows/{workflowId}/undeploy