Resolve Step
curl --request POST \
--url https://api.velt.dev/v2/workflow/steps/resolve \
--header 'Content-Type: application/json' \
--header 'x-velt-api-key: <x-velt-api-key>' \
--header 'x-velt-auth-token: <x-velt-auth-token>' \
--data '
{
"data": {
"executionId": "<string>",
"stepId": "<string>",
"action": "<string>",
"actorId": "<string>",
"output": {},
"reason": "<string>"
}
}
'import requests
url = "https://api.velt.dev/v2/workflow/steps/resolve"
payload = { "data": {
"executionId": "<string>",
"stepId": "<string>",
"action": "<string>",
"actorId": "<string>",
"output": {},
"reason": "<string>"
} }
headers = {
"x-velt-api-key": "<x-velt-api-key>",
"x-velt-auth-token": "<x-velt-auth-token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'x-velt-api-key': '<x-velt-api-key>',
'x-velt-auth-token': '<x-velt-auth-token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
data: {
executionId: '<string>',
stepId: '<string>',
action: '<string>',
actorId: '<string>',
output: {},
reason: '<string>'
}
})
};
fetch('https://api.velt.dev/v2/workflow/steps/resolve', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.velt.dev/v2/workflow/steps/resolve",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'data' => [
'executionId' => '<string>',
'stepId' => '<string>',
'action' => '<string>',
'actorId' => '<string>',
'output' => [
],
'reason' => '<string>'
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-velt-api-key: <x-velt-api-key>",
"x-velt-auth-token: <x-velt-auth-token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.velt.dev/v2/workflow/steps/resolve"
payload := strings.NewReader("{\n \"data\": {\n \"executionId\": \"<string>\",\n \"stepId\": \"<string>\",\n \"action\": \"<string>\",\n \"actorId\": \"<string>\",\n \"output\": {},\n \"reason\": \"<string>\"\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-velt-api-key", "<x-velt-api-key>")
req.Header.Add("x-velt-auth-token", "<x-velt-auth-token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.velt.dev/v2/workflow/steps/resolve")
.header("x-velt-api-key", "<x-velt-api-key>")
.header("x-velt-auth-token", "<x-velt-auth-token>")
.header("Content-Type", "application/json")
.body("{\n \"data\": {\n \"executionId\": \"<string>\",\n \"stepId\": \"<string>\",\n \"action\": \"<string>\",\n \"actorId\": \"<string>\",\n \"output\": {},\n \"reason\": \"<string>\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.velt.dev/v2/workflow/steps/resolve")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-velt-api-key"] = '<x-velt-api-key>'
request["x-velt-auth-token"] = '<x-velt-auth-token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"data\": {\n \"executionId\": \"<string>\",\n \"stepId\": \"<string>\",\n \"action\": \"<string>\",\n \"actorId\": \"<string>\",\n \"output\": {},\n \"reason\": \"<string>\"\n }\n}"
response = http.request(request)
puts response.read_body{
"result": {
"resolved": true,
"executionId": "exec_1777374504255_xzy43k9q",
"stepId": "step_agent-draft_..._lwofay__to__human-brand",
"action": "force-complete"
}
}
Steps
Resolve Step
POST
/
v2
/
workflow
/
steps
/
resolve
Resolve Step
curl --request POST \
--url https://api.velt.dev/v2/workflow/steps/resolve \
--header 'Content-Type: application/json' \
--header 'x-velt-api-key: <x-velt-api-key>' \
--header 'x-velt-auth-token: <x-velt-auth-token>' \
--data '
{
"data": {
"executionId": "<string>",
"stepId": "<string>",
"action": "<string>",
"actorId": "<string>",
"output": {},
"reason": "<string>"
}
}
'import requests
url = "https://api.velt.dev/v2/workflow/steps/resolve"
payload = { "data": {
"executionId": "<string>",
"stepId": "<string>",
"action": "<string>",
"actorId": "<string>",
"output": {},
"reason": "<string>"
} }
headers = {
"x-velt-api-key": "<x-velt-api-key>",
"x-velt-auth-token": "<x-velt-auth-token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'x-velt-api-key': '<x-velt-api-key>',
'x-velt-auth-token': '<x-velt-auth-token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
data: {
executionId: '<string>',
stepId: '<string>',
action: '<string>',
actorId: '<string>',
output: {},
reason: '<string>'
}
})
};
fetch('https://api.velt.dev/v2/workflow/steps/resolve', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.velt.dev/v2/workflow/steps/resolve",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'data' => [
'executionId' => '<string>',
'stepId' => '<string>',
'action' => '<string>',
'actorId' => '<string>',
'output' => [
],
'reason' => '<string>'
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-velt-api-key: <x-velt-api-key>",
"x-velt-auth-token: <x-velt-auth-token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.velt.dev/v2/workflow/steps/resolve"
payload := strings.NewReader("{\n \"data\": {\n \"executionId\": \"<string>\",\n \"stepId\": \"<string>\",\n \"action\": \"<string>\",\n \"actorId\": \"<string>\",\n \"output\": {},\n \"reason\": \"<string>\"\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-velt-api-key", "<x-velt-api-key>")
req.Header.Add("x-velt-auth-token", "<x-velt-auth-token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.velt.dev/v2/workflow/steps/resolve")
.header("x-velt-api-key", "<x-velt-api-key>")
.header("x-velt-auth-token", "<x-velt-auth-token>")
.header("Content-Type", "application/json")
.body("{\n \"data\": {\n \"executionId\": \"<string>\",\n \"stepId\": \"<string>\",\n \"action\": \"<string>\",\n \"actorId\": \"<string>\",\n \"output\": {},\n \"reason\": \"<string>\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.velt.dev/v2/workflow/steps/resolve")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-velt-api-key"] = '<x-velt-api-key>'
request["x-velt-auth-token"] = '<x-velt-auth-token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"data\": {\n \"executionId\": \"<string>\",\n \"stepId\": \"<string>\",\n \"action\": \"<string>\",\n \"actorId\": \"<string>\",\n \"output\": {},\n \"reason\": \"<string>\"\n }\n}"
response = http.request(request)
puts response.read_body{
"result": {
"resolved": true,
"executionId": "exec_1777374504255_xzy43k9q",
"stepId": "step_agent-draft_..._lwofay__to__human-brand",
"action": "force-complete"
}
}
Use this API to resolve a step that’s currently
Errors:
running or waiting. The endpoint supports two families of actions:
- Admin-grade overrides (
force-approve,force-reject,force-complete,force-fail) — break a stuck step out ofwaitingor terminate a running step with an admin-supplied output. Audit log records the actor as an operator override. - Reviewer-scoped decisions (
reviewer-approve,reviewer-reject) — record a reviewer’s approve/reject decision through the API instead of through the comment-reply flow. Auth-gated to userIds in the step’s declared reviewer list.
step.overridden audit event with { actorId, action, reason, decision }.
Endpoint
POST https://api.velt.dev/v2/workflow/steps/resolve
Headers
string
required
Your API key.
string
required
Your Auth Token.
Body
Params
object
required
Show properties
Show properties
string
required
The execution containing the step.
string
required
The step to resolve.
string
required
One of
force-approve / force-reject / force-complete / force-fail / reviewer-approve / reviewer-reject. See the action matrix below.string
required
1–256 chars. For
force-* actions, the operator’s identifier. For reviewer-* actions, must equal a userId declared in the step’s reviewer list — otherwise the request is rejected with PERMISSION_DENIED.object
Optional. Admin-supplied output for
force-complete / force-fail. Ignored for approve/reject actions — decision, approved, and approvalReply are computed authoritatively by the engine.string
≤ 2000 chars. Recorded on the audit event.
Action matrix
| Action | Auth gate | Allowed step states | Resulting step status | Notes |
|---|---|---|---|---|
force-approve | auth token | waiting (human only) | completed | Synthesizes output.decision = 'approve'. Downstream edges that gate on decision == 'approve' fire. |
force-reject | auth token | waiting (human only) | completed | Synthesizes output.decision = 'reject'. Downstream edges that gate on decision == 'reject' fire. |
force-complete | auth token | running or waiting (any) | completed | Caller-supplied output (or {}) is written through. Downstream edges fire as if the step completed normally. |
force-fail | auth token | running or waiting (any) | failed | Caller-supplied output is recorded. Edges that gate on the failed terminal status fire (e.g. escalation routing). |
reviewer-approve | auth token + actorId ∈ step.reviewerIds | waiting (human only) | completed | Mechanically identical to force-approve but distinguished in the audit log so “reviewer acted” ≠ “operator overrode”. |
reviewer-reject | auth token + actorId ∈ step.reviewerIds | waiting (human only) | completed | Mechanically identical to force-reject. Same audit distinction. |
Authority-of-record. For all approve/reject actions (admin and reviewer), the engine computes
decision, approved, and approvalReply itself. Caller-supplied output keys with the same names cannot override them. Other keys in output pass through. This prevents a reviewer from sending action: 'reviewer-reject' with output: { decision: 'approve' } and routing edges as an approval while the audit log records a rejection.reviewer-reject and force-reject do not populate output.rejectedBy / output.rejectorMandatory. Those fields are only set on the natural Record Reviewer Decision aggregator path. If you drive loop regions via reviewer-reject, the default loop predicate (decision == 'reject' && rejectorMandatory == true) won’t fire — supply an explicit onIterationReject.when predicate that filters on decision alone.Example Requests
Reviewer approves through the API
{
"data": {
"executionId": "exec_1777374504255_xzy43k9q",
"stepId": "step_agent-draft_..._lwofay__to__human-legal",
"action": "reviewer-approve",
"actorId": "u_legal_01",
"reason": "looks good"
}
}
Admin force-completes a stuck blocking-agent step
{
"data": {
"executionId": "exec_1777374504255_xzy43k9q",
"stepId": "step_blocking-agent_...",
"action": "force-complete",
"actorId": "ops_jane",
"output": { "summary": "Manually closed; agent hung past deadline" },
"reason": "Agent stuck on retry; closing to unblock execution"
}
}
Admin force-fails a step to trigger an escalation edge
{
"data": {
"executionId": "exec_1777374504255_xzy43k9q",
"stepId": "step_agent-draft_..._lwofay__to__human-brand",
"action": "force-fail",
"actorId": "ops_jane",
"reason": "Reviewer on PTO indefinitely; routing to escalation edge"
}
}
Response
Success Response
{
"result": {
"resolved": true,
"executionId": "exec_1777374504255_xzy43k9q",
"stepId": "step_agent-draft_..._lwofay__to__human-brand",
"action": "force-complete"
}
}
Failure Response
{
"error": {
"message": "ERROR_MESSAGE",
"status": "FAILED_PRECONDITION"
}
}
| Code | Cause |
|---|---|
INVALID_ARGUMENT | Schema validation (missing action, missing actorId, etc.). |
PERMISSION_DENIED | reviewer-approve / reviewer-reject with actorId not in the step’s declared reviewer list. |
FAILED_PRECONDITION | Step is already terminal, step is not in an allowed state for the action (e.g. force-approve on a non-human step), or the underlying transition CAS rejected the change. |
NOT_FOUND | executionId or stepId does not exist. |
Workspace-admin RBAC is post-GA. Today the
force-* actions gate on the standard auth token only. Until role-based access lands, restrict who can call this endpoint inside your own application.{
"result": {
"resolved": true,
"executionId": "exec_1777374504255_xzy43k9q",
"stepId": "step_agent-draft_..._lwofay__to__human-brand",
"action": "force-complete"
}
}
Was this page helpful?
⌘I

