Returning.AIDocs
v1

API reference / Workflow Builder

.md

Validate a stored workflow

Runs the same static check that deploy runs, without changing anything: each step's options against its type's schema, every reference resolving to an earlier step or the trigger, router branch shape and operators, retries that repeat side effects, credentials an import left empty, user-field keys, and leaderboard ownership.

Last updated 28 Oct 2026API v1

Method
POST
Path
https://api.returning.ai/v1/workflow-builder/data-workflows/{workflowId}/validate
Permission
workflowBuilder
Retries
Safe to retry: it never writes and never executes a step.

Authentication#

Header
Authorization: Bearer <API_KEY>
Permission
workflowBuilder

No If-Match is needed because the call writes nothing: it always validates the stored workflow, and the body is ignored.

FailureMeaningRecovery
403 API_KEY_PERMISSION_DENIEDThe key has no workflowBuilder permissionGrant the permission.
404 WORKFLOW_NOT_FOUNDUnknown id, or another community's workflowUse an id from this community.

Behaviour#

It is advisory. Every finding comes back in warnings, errors is always empty, and valid is always true: it does not tell you whether anything was found, so read warnings. No finding here refuses a deploy: deploy runs the same check, returns the same list as warnings, and proceeds either way. Deploy refuses only on its two tenant checks, a router branch that lists a step from another workflow (400 NODE_NOT_IN_WORKFLOW) and a leaderboard from another community (400 LEADERBOARD_NOT_IN_COMMUNITY); validate reports both as warnings. Read the report, decide, and fix what matters.

Nothing is executed and nothing is written, so a key that only inspects workflows can check one before anyone deploys it.

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>

Content-Type#stringREQUIRED
Request body format.

Ruleapplication/json

Watch for

  • A report with warnings is deployable, and valid stays true either way: check whether warnings is empty, not valid. The platform accepts every one of these and then behaves as described.
  • MISSING_SECRET appears only for credentials that were blanked by an import. A step whose authentication is deliberately empty is not reported. Because deploy does not refuse on it, an imported workflow can go live with a credential still missing: fill every path in secretsToFill first.
  • Validation checks run order, not creation order: a reference may only read the trigger or a step that has already run on the same path. A step after a router cannot read a step inside one branch without a warning.
  • A router branch that uses OR or WHERE is stored without complaint, deploys, and then evaluates as always true. Build conditions with AND groups and treat ROUTER_OPERATOR_UNSAFE as an editing task, not a blocker.
  • Validation is not a test. It does not resolve member data, call an outside system, or prove that a leaderboard has members.
  • validate writes nothing, so it is the cheapest way to check a workflow before you deploy or hand it over. It reaches outside the workflow only for two scoped reads: leaderboard ids and the community's user-field catalog.
More examples and details

A clean workflow:

JSON
{
  "data": {
    "valid": true,
    "errors": [],
    "warnings": [],
    "revision": "162af81f5de4ff2b557a4087de9a9a73a0ed4e621f737787e35a93614cb50d1c"
  }
}

A workflow whose router uses an OR condition group, plus a reference to a branch step that may not run. Both are warnings, valid is still true, and the workflow can be deployed exactly as it is:

JSON
{
  "data": {
    "valid": true,
    "errors": [],
    "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."
      }
    ],
    "revision": "68f78253046393414dec404e5442c140bb5db0393a7d1c389ff57bba71585501"
  }
}
curl --request POST \
  --url 'https://api.returning.ai/v1/workflow-builder/data-workflows/<workflowId>/validate' \
  --header 'Authorization: Bearer <API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '{}'

Response#

HTTP 200 whenever the workflow was read and checked, whatever the report says: a workflow with warnings is a successful validation with valid: true, an empty errors and a non-empty warnings. There is no state change, so the readback is the report itself plus revision, which is stable until the next authored change.

Warning codes a report can carry. None of them stops a deploy. deploy returns the same list in its own warnings, so a workflow can go live with every code below present:

The router and reference rows come from the same rules the run path uses, including its operator tables. That is what makes the report worth reading even though it refuses nothing: it names exactly what a run will ignore or misread.

data#object
Advisory static check result. Every finding is a warning, valid is always true (read warnings to see what was found), and no finding refuses a deploy.

Eg{ ... }

valid#boolean
Always true: validation is advisory. Whether anything was found is in warnings. Nothing here blocks a deploy.

Egtrue

errors#object[]
Always empty: the validator reports every finding in warnings.

Eg[]

nodeId#string
Step the problem belongs to, when it belongs to one.

Eg"65f1a2b3c4d5e6f708192a3c"

path#string
Field path, for example options.branches[0].conditions[0].operator.

Eg"options.url"

code#string
Stable machine code for the problem.

Eg"NODE_OPTIONS_INVALID"

message#string
Human explanation.

Eg"URL is required"

warnings#object[]
Every finding, advisory: a schema mismatch, a reference that may not resolve, a router shape, a retry that repeats side effects, an empty imported credential. Deploy proceeds and reports the same list.

Eg[]

nodeId#string
Step the warning belongs to.

Eg"65f1a2b3c4d5e6f708192a3d"

path#string
Field path.

Eg"options.body"

code#string
Stable machine code for the warning.

Eg"REFERENCE_MAY_BE_UNSET"

message#string
Human explanation.

Eg"This reads a branch step that may not run."

revision#string
Revision the report was computed from.

Eg"162af81f5de4ff2b557a4087de9a9a73a0ed4e621f737787e35a93614cb50d1c"

{
  "data": {
    "valid": true,
    "errors": [],
    "warnings": [],
    "revision": "162af81f5de4ff2b557a4087de9a9a73a0ed4e621f737787e35a93614cb50d1c"
  }
}

Errors#

Fix the request06

400VALIDATION_FAILEDFix the request
The id is not 24 hexadecimal characters, or the body is not a JSON object. Send {} and a valid id.
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.
413VALIDATION_FAILEDFix the request
The request body is too large to read. Nothing was written. Send a smaller body; a large import is better split into a create plus step writes.
415UNSUPPORTED_MEDIA_TYPEFix the request
The body was not declared application/json. Send the header with {}.

Fix the data01

404WORKFLOW_NOT_FOUNDFix the data
Unknown id, or another community's workflow. Use an id from this community.

Retry with backoff02

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. Retry with bounded backoff.

Retries

Safe to retry: it never writes and never executes a step. The report can differ between calls only because the workflow changed, so compare revision between reports rather than assuming they describe the same state.

{
  "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#

Test one stepPOST/v1/workflow-builder/data-workflows/{workflowId}/nodes/{nodeId}/test