> ## Documentation Index
> Fetch the complete documentation index at: https://velt.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Update Definition

Use this API to update an existing workflow definition. Atomically increments the version, snapshots the prior content, and rejects if the stored version mismatches `ifVersion`. In-flight executions keep running on the version they started with.

# Endpoint

`POST https://api.velt.dev/v2/workflow/definitions/update`

# Headers

<ParamField header="x-velt-api-key" type="string" required>
  Your API key.
</ParamField>

<ParamField header="x-velt-auth-token" type="string" required>
  Your [Auth Token](/docs/security/auth-tokens).
</ParamField>

# Body

#### Params

Accepts every field from [Create Definition](/docs/api-reference/rest-apis/v2/approval-engine/definitions/create-definition) plus:

<Warning>
  Update replaces the definition; it is not a partial patch. Send the complete `name`, `nodes`, and `edges` on every call. Any field you omit is not preserved from the stored definition.

  Read the current definition with [Get Definition](/docs/api-reference/rest-apis/v2/approval-engine/definitions/get-definition), change what you need, then send the whole object back with `ifVersion`. A read response is not accepted verbatim: strip the server-owned `version`, `createdAt`, `updatedAt`, `status`, and `compiled` fields first. Strip the explicit nulls too. Every optional field you never set comes back as `null`: `description`, `groups`, `triggers`, `tags`, `custom`, and `organizationId` / `documentId` inside `scope`. Update takes those fields as absent or as real values, never as `null`, so a leftover `null` or a leftover server-owned field fails the request with `INVALID_ARGUMENT`.
</Warning>

<ParamField body="data" type="object" required>
  <Expandable title="properties">
    <ParamField body="definitionId" type="string" required>
      The definition to update.
    </ParamField>

    <ParamField body="ifVersion" type="integer" required>
      Optimistic-locking version. Must equal the stored definition's current `version`. If someone else updated the definition since you read it, the request is rejected with `FAILED_PRECONDITION` and a message like `Version conflict: expected 4, current 5`, so a concurrent edit is never silently overwritten.
    </ParamField>

    <ParamField body="name" type="string" required>
      1–200 chars.
    </ParamField>

    <ParamField body="description" type="string">
      0–2000 chars.
    </ParamField>

    <ParamField body="scope" type="object">
      Defaults to `{ level: "apiKey" }`. Options:

      * `{ level: "apiKey" }`: workspace-wide.
      * `{ level: "organization", organizationId: "<id>" }`: bound to one organization. `organizationId` is required (returns `INVALID_ARGUMENT` if omitted).
      * `{ level: "document", organizationId: "<id>", documentId: "<id>" }`: bound to one document under an organization. Both fields required.

      Update replaces `scope` like every other field. Omit it and `level` resets to `apiKey`, silently demoting an organization-scoped or document-scoped definition to workspace scope. Resend the `scope` you read from [Get Definition](/docs/api-reference/rest-apis/v2/approval-engine/definitions/get-definition) on every update.
    </ParamField>

    <ParamField body="nodes" type="array" required>
      1–100 nodes of type `agent`, `human`, `notification`, or `webhook`. Each node accepts an optional `slaMs` (up to 7 days) and optional cosmetic `name` (1–200 chars) and `description` (≤ 2000 chars) labels. See [Node configuration](/docs/ai/approval-engine/customize-behavior#node-configuration) and the [Create Definition](/docs/api-reference/rest-apis/v2/approval-engine/definitions/create-definition) type table.
    </ParamField>

    <ParamField body="edges" type="array" required>
      0–500 edges, unified `EdgeSchema`: `{ from, to, on?, when?, loop? }`. `from` / `to` are `EdgeEndpoint`s (bare node-id string, `{ kind: "node", nodeId }`, or `{ kind: "group", groupId }`); `on` ∈ `approve` / `reject` / `always` (default) / `exhausted` / `custom`; `when` is valid only on `on:"custom"`; `loop { maxIterations: 1–20 }` only on an `on:"reject"` back-edge. A `human` node must have an outgoing `on:"reject"` edge. See [the edge model](/docs/ai/approval-engine/customize-behavior#edge-model) for full semantics and the [Create Definition](/docs/api-reference/rest-apis/v2/approval-engine/definitions/create-definition) field table.
    </ParamField>

    <ParamField body="groups" type="array">
      0–100 parallel-group definitions. A `waitAll` or `cancelOnQuorum` group can be an edge source (`from: { kind: "group", groupId }`); group-to-node fan-out (`to: { kind: "group", groupId }`) is accepted for all policies. A forward `on:"reject"` from a `joinOnQuorum` / `cancelOnQuorum` group is rejected as a dead edge. See [parallel groups and quorum policies](/docs/ai/approval-engine/customize-behavior#parallel-groups-and-quorum-policies).
    </ParamField>

    <ParamField body="triggers" type="array">
      0–50 trigger declarations: `{ triggerId, eventName?, filters?, inboundWebhook?, schedule?, appTrigger? }`. A trigger starts executions for you, with no dispatch call. The three mechanisms are mutually exclusive on a single entry. See the [Create Definition](/docs/api-reference/rest-apis/v2/approval-engine/definitions/create-definition) field table and [Triggers](/docs/ai/approval-engine/customize-behavior#triggers).

      Updating `triggers` replaces the stored array. Removing a `schedule` entry, or setting its `enabled` to `false`, stops that schedule from firing.
    </ParamField>

    <ParamField body="webhookConfig" type="object">
      Default webhook receiver for every execution of this definition: `{ url, secret, eventTypes? }`. See [Webhook delivery](/docs/ai/approval-engine/customize-behavior#webhook-delivery).
    </ParamField>

    <ParamField body="tags" type="string[]">
      0–20 tags, each ≤ 64 chars.
    </ParamField>

    <ParamField body="custom" type="object">
      Free-form metadata.
    </ParamField>

    <ParamField body="organizationId" type="string">
      Required when scoped to an organization or document.
    </ParamField>

    <ParamField body="documentId" type="string">
      Required when scoped to a document.
    </ParamField>
  </Expandable>
</ParamField>

<Note>
  Every successful update increments `version` and snapshots the prior content. In-flight executions are immune to updates: they keep running on the pinned `definitionVersion` from their dispatch.

  Snapshots are kept for audit and recovery only. There is no endpoint to read an old version and no rollback, so keep your own copy of anything you may want to restore. See [Versioning](/docs/ai/approval-engine/patterns#versioning-and-what-it-does-not-do).
</Note>

## **Example Requests**

#### Update a definition

Only `name` changes here, so the request resends the unchanged `nodes` and `edges` verbatim.

```JSON theme={null}
{
  "data": {
    "definitionId": "marketing-copy-approval",
    "ifVersion": 1,
    "name": "Marketing copy approval (Q2 revision)",
    "scope": { "level": "apiKey" },
    "nodes": [
      { "nodeId": "agent-draft",   "type": "agent", "config": { "agentId": "copy-agent-v1",    "urlPath": "documentUrl" } },
      { "nodeId": "human-review",  "type": "human", "config": { "reviewers": [{ "userId": "u_reviewer_01", "mandatory": true }] } },
      { "nodeId": "agent-publish", "type": "agent", "config": { "agentId": "publish-agent-v1", "urlPath": "documentUrl" } },
      { "nodeId": "agent-rework",  "type": "agent", "config": { "agentId": "rework-agent-v1",  "urlPath": "documentUrl" } }
    ],
    "edges": [
      { "from": "agent-draft", "to": "human-review" },
      { "from": "human-review", "to": "agent-publish", "on": "approve" },
      { "from": "human-review", "to": "agent-rework", "on": "reject" }
    ]
  }
}
```

# Response

#### Success Response

```JSON theme={null}
{
  "result": {
    "definitionId": "marketing-copy-approval",
    "name": "Marketing copy approval (Q2 revision)",
    "description": null,
    "version": 2,
    "scope": { "level": "apiKey", "organizationId": null, "documentId": null },
    "nodes": [
      { "nodeId": "agent-draft", "type": "agent", "config": { "agentId": "copy-agent-v1", "urlPath": "documentUrl" } },
      { "nodeId": "human-review", "type": "human", "config": { "reviewers": [{ "userId": "u_reviewer_01", "mandatory": true }] } },
      { "nodeId": "agent-publish", "type": "agent", "config": { "agentId": "publish-agent-v1", "urlPath": "documentUrl" } },
      { "nodeId": "agent-rework", "type": "agent", "config": { "agentId": "rework-agent-v1", "urlPath": "documentUrl" } }
    ],
    "edges": [
      { "from": "agent-draft", "to": "human-review" },
      { "from": "human-review", "to": "agent-publish", "on": "approve" },
      { "from": "human-review", "to": "agent-rework", "on": "reject" }
    ],
    "groups": null,
    "compiled": {
      "forwardEdges": [
        { "from": "agent-draft", "to": "human-review", "role": "always", "when": null },
        { "from": "human-review", "to": "agent-publish", "role": "approve", "when": { "op": "eq", "args": [{ "var": "output.decision" }, "approve"] } },
        { "from": "human-review", "to": "agent-rework", "role": "reject", "when": { "op": "eq", "args": [{ "var": "output.decision" }, "reject"] } }
      ],
      "loops": []
    },
    "triggers": null,
    "tags": null,
    "custom": null,
    "createdAt": 1731432000000,
    "updatedAt": 1731518400000,
    "status": "active"
  }
}
```

#### Failure Response

```JSON theme={null}
{
  "error": {
    "message": "ERROR_MESSAGE",
    "status": "FAILED_PRECONDITION"
  }
}
```

**Errors:** `NOT_FOUND` (definition does not exist) / `FAILED_PRECONDITION` (`ifVersion` mismatch) / `INVALID_ARGUMENT` (schema, `compileGraph`, or linter failure).

<ResponseExample>
  ```js theme={null}
  {
    "result": {
      "definitionId": "marketing-copy-approval",
      "name": "Marketing copy approval (Q2 revision)",
      "description": null,
      "version": 2,
      "scope": { "level": "apiKey", "organizationId": null, "documentId": null },
      "nodes": [
        { "nodeId": "agent-draft", "type": "agent", "config": { "agentId": "copy-agent-v1", "urlPath": "documentUrl" } },
        { "nodeId": "human-review", "type": "human", "config": { "reviewers": [{ "userId": "u_reviewer_01", "mandatory": true }] } },
        { "nodeId": "agent-publish", "type": "agent", "config": { "agentId": "publish-agent-v1", "urlPath": "documentUrl" } },
        { "nodeId": "agent-rework", "type": "agent", "config": { "agentId": "rework-agent-v1", "urlPath": "documentUrl" } }
      ],
      "edges": [
        { "from": "agent-draft", "to": "human-review" },
        { "from": "human-review", "to": "agent-publish", "on": "approve" },
        { "from": "human-review", "to": "agent-rework", "on": "reject" }
      ],
      "groups": null,
      "compiled": {
        "forwardEdges": [
          { "from": "agent-draft", "to": "human-review", "role": "always", "when": null },
          { "from": "human-review", "to": "agent-publish", "role": "approve", "when": { "op": "eq", "args": [{ "var": "output.decision" }, "approve"] } },
          { "from": "human-review", "to": "agent-rework", "role": "reject", "when": { "op": "eq", "args": [{ "var": "output.decision" }, "reject"] } }
        ],
        "loops": []
      },
      "triggers": null,
      "tags": null,
      "custom": null,
      "createdAt": 1731432000000,
      "updatedAt": 1731518400000,
      "status": "active"
    }
  }
  ```
</ResponseExample>
