Approvals
An approval node pauses a workflow run until a person approves or rejects a request (for example an urgent escalation). These operations are the inbox behind it. They take a dashboard JWT of a workspace **owner**; a member is refused. A decision is asynchronous: the response is the decided request, and the paused run continues shortly after on the approved or rejected edge.
Every operation is a POST of { "query", "variables" } to https://api.wetel.dev/graphql (sandbox) with the x-huat-platform: customer header and the credential shown. New here? Start with the Quickstart and the Authentication cheat sheet. Errors are listed in Errors; Postman and OpenAPI files are on Downloads.
| Operation | Kind | Credential | What it does |
|---|---|---|---|
approvals | query | Dashboard JWT (owner) | List approval requests, newest first. Without a filter it lists only PENDING requests. |
approval | query | Dashboard JWT (owner) | Read one approval request by id. |
decideApproval | mutation | Dashboard JWT (owner) | Approve or reject a pending request. The paused run resumes asynchronously. |
cancelApproval | mutation | Dashboard JWT (owner) | Withdraw a pending request. The paused run does not resume. |
approvals
Section titled “approvals”List approval requests, newest first. Without a filter it lists only PENDING requests.
- Kind: query
- Credential: Dashboard JWT:
Authorization: Bearer <jwt>, fromlogin(a person or a one-off script). Workspace owner only - Available in: sandbox and production
- Pass
statusfor one state orstatusesfor several in one list (for example every decided request). Passing both, or an emptystatuseslist, is aBAD_REQUEST. - Forward pagination:
first(default 20, at most 100) andafter(a previous page’spageInfo.endCursor, which walks to older rows). OptionalworkflowIdandagentIdnarrow the list. - A request past
expiresAtthat has not been swept yet still readsPENDINGbut can no longer be decided. payloadis the thing being decided and may contain customer data.
Request
query Inbox($statuses: [ApprovalStatus!], $first: Int) { approvals(statuses: $statuses, first: $first) { edges { node { id status title summary payload editableFields expiresAt workflowRunId } } pageInfo { hasNextPage endCursor } }}{ "statuses": [ "PENDING" ], "first": 20}Allowed values
ApprovalStatus:APPROVED,CANCELLED,CHANGES_REQUESTED,EXPIRED,PENDING,REJECTED
Code samples
#!/usr/bin/env bash# approvals: List approval requests, newest first. Without a filter it lists only PENDING requests.# Credential: Dashboard JWT (WETEL_JWT).API_URL="${WETEL_API_URL:-https://api.wetel.dev/graphql}"
curl -sS "$API_URL" \ -H "Content-Type: application/json" \ -H "x-huat-platform: customer" \ -H "Authorization: Bearer $WETEL_JWT" \ --data-binary @- <<'JSON'{ "query": "query Inbox($statuses:[ApprovalStatus!]$first:Int){approvals(statuses:$statuses first:$first){edges{node{id status title summary payload editableFields expiresAt workflowRunId}}pageInfo{hasNextPage endCursor}}}", "variables": { "statuses": [ "PENDING" ], "first": 20 }}JSON// approvals: List approval requests, newest first. Without a filter it lists only PENDING requests.// Credential: Dashboard JWT (WETEL_JWT). Node 18+ (built-in fetch).const API_URL = process.env.WETEL_API_URL ?? "https://api.wetel.dev/graphql";
const query = `query Inbox($statuses: [ApprovalStatus!], $first: Int) { approvals(statuses: $statuses, first: $first) { edges { node { id status title summary payload editableFields expiresAt workflowRunId } } pageInfo { hasNextPage endCursor } }}`;
const variables = { statuses: ["PENDING"], first: 20,};
type GraphQLResponse = { data?: Record<string, unknown>; errors?: { message: string; extensions?: { code?: string } }[];};
async function main(): Promise<void> { const res = await fetch(API_URL, { method: "POST", headers: { "Content-Type": "application/json", "x-huat-platform": "customer", Authorization: `Bearer ${process.env.WETEL_JWT ?? ""}`, }, body: JSON.stringify({ query, variables }), }); const body = (await res.json()) as GraphQLResponse; if (body.errors?.length) { const error = body.errors[0]; throw new Error(`${error.extensions?.code}: ${error.message}`); } console.log(JSON.stringify(body.data?.approvals, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// approvals: List approval requests, newest first. Without a filter it lists only PENDING requests.// Credential: Dashboard JWT (WETEL_JWT). PHP 7.3+ with the curl extension.$apiUrl = getenv('WETEL_API_URL') ?: 'https://api.wetel.dev/graphql';
$query = <<<'GRAPHQL'query Inbox($statuses: [ApprovalStatus!], $first: Int) { approvals(statuses: $statuses, first: $first) { edges { node { id status title summary payload editableFields expiresAt workflowRunId } } pageInfo { hasNextPage endCursor } }}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "statuses": [ "PENDING" ], "first": 20}JSON);
$ch = curl_init($apiUrl);curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'x-huat-platform: customer', 'Authorization: Bearer ' . getenv('WETEL_JWT'), ], CURLOPT_POSTFIELDS => json_encode(['query' => $query, 'variables' => $variables]),]);$body = json_decode(curl_exec($ch), true);curl_close($ch);
if (!empty($body['errors'])) { $error = $body['errors'][0]; fwrite(STDERR, ($error['extensions']['code'] ?? 'ERROR') . ': ' . $error['message'] . PHP_EOL); exit(1);}echo json_encode($body['data']['approvals'], JSON_PRETTY_PRINT), PHP_EOL;// approvals: List approval requests, newest first. Without a filter it lists only PENDING requests.// Credential: Dashboard JWT (WETEL_JWT). .NET 8: put this in Program.cs of a console app and run "dotnet run".using System.Net.Http.Headers;using System.Net.Http.Json;using System.Text.Json;using System.Text.Json.Nodes;
var apiUrl = Environment.GetEnvironmentVariable("WETEL_API_URL") ?? "https://api.wetel.dev/graphql";
const string query = """ query Inbox($statuses: [ApprovalStatus!], $first: Int) { approvals(statuses: $statuses, first: $first) { edges { node { id status title summary payload editableFields expiresAt workflowRunId } } pageInfo { hasNextPage endCursor } } } """;
var variables = JsonNode.Parse(""" { "statuses": [ "PENDING" ], "first": 20 } """);
using var http = new HttpClient();using var request = new HttpRequestMessage(HttpMethod.Post, apiUrl){ Content = JsonContent.Create(new { query, variables }),};request.Headers.Add("x-huat-platform", "customer");request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("WETEL_JWT"));
using var response = await http.SendAsync(request);var body = JsonNode.Parse(await response.Content.ReadAsStringAsync())!;if (body["errors"] is JsonArray errors && errors.Count > 0){ Console.Error.WriteLine($"{errors[0]?["extensions"]?["code"]}: {errors[0]?["message"]}"); return 1;}Console.WriteLine(body["data"]?["approvals"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# approvals: List approval requests, newest first. Without a filter it lists only PENDING requests.# Credential: Dashboard JWT (WETEL_JWT). Python 3.8+ with "requests".import jsonimport os
import requests
API_URL = os.environ.get("WETEL_API_URL", "https://api.wetel.dev/graphql")
QUERY = """query Inbox($statuses: [ApprovalStatus!], $first: Int) { approvals(statuses: $statuses, first: $first) { edges { node { id status title summary payload editableFields expiresAt workflowRunId } } pageInfo { hasNextPage endCursor } }}"""
VARIABLES = { "statuses": [ "PENDING", ], "first": 20,}
response = requests.post( API_URL, json={"query": QUERY, "variables": VARIABLES}, headers={ "x-huat-platform": "customer", "Authorization": "Bearer " + os.environ["WETEL_JWT"], }, timeout=30,)body = response.json()if body.get("errors"): error = body["errors"][0] raise SystemExit(f"{error.get('extensions', {}).get('code')}: {error['message']}")print(json.dumps(body["data"]["approvals"], indent=2))Responses
Success (HTTP 200)
{ "data": { "approvals": { "edges": [ { "node": { "id": 118, "status": "PENDING", "title": "Emergency escalation for ticket T-1001", "summary": "Severity high, many people affected. Page the on-call lead?", "payload": { "ticketId": "T-1001", "severity": "high", "queue": "emergency" }, "editableFields": [ "queue" ], "expiresAt": "2026-10-12T05:15:00.000Z", "workflowRunId": 5530 } } ], "pageInfo": { "hasNextPage": false, "endCursor": "MTE4" } } }}BAD_REQUEST: Both status and statuses passed (HTTP 200)
{ "errors": [ { "message": "Pass either `status` or `statuses`, not both", "locations": [ { "line": 2, "column": 3 } ], "path": [ "approvals" ], "extensions": { "code": "BAD_REQUEST", "originalError": { "message": "Pass either `status` or `statuses`, not both", "error": "Bad Request", "statusCode": 400 } } } ], "data": null}FORBIDDEN: Caller is a member, not the owner (HTTP 200)
{ "errors": [ { "message": "Only the owner of this workspace can do this", "locations": [ { "line": 2, "column": 3 } ], "path": [ "approvals" ], "extensions": { "code": "FORBIDDEN", "originalError": { "message": "Only the owner of this workspace can do this", "error": "Forbidden", "statusCode": 403 } } } ], "data": null}BAD_USER_INPUT: Unknown status value (HTTP 400)
Sent with variables:
{ "statuses": [ "OPEN" ]}{ "errors": [ { "message": "Variable \"$statuses\" got invalid value \"OPEN\" at \"statuses[0]\"; Value \"OPEN\" does not exist in \"ApprovalStatus\" enum.", "locations": [ { "line": 1, "column": 13 } ], "extensions": { "code": "BAD_USER_INPUT" } } ]}approval
Section titled “approval”Read one approval request by id.
- Kind: query
- Credential: Dashboard JWT:
Authorization: Bearer <jwt>, fromlogin(a person or a one-off script). Workspace owner only - Available in: sandbox and production
Request
query OneApproval($id: ID!) { approval(id: $id) { id status title payload editableFields decidedAt decisionComment }}{ "id": "118"}Allowed values
ApprovalStatus:APPROVED,CANCELLED,CHANGES_REQUESTED,EXPIRED,PENDING,REJECTED
Code samples
#!/usr/bin/env bash# approval: Read one approval request by id.# Credential: Dashboard JWT (WETEL_JWT).API_URL="${WETEL_API_URL:-https://api.wetel.dev/graphql}"
curl -sS "$API_URL" \ -H "Content-Type: application/json" \ -H "x-huat-platform: customer" \ -H "Authorization: Bearer $WETEL_JWT" \ --data-binary @- <<'JSON'{ "query": "query OneApproval($id:ID!){approval(id:$id){id status title payload editableFields decidedAt decisionComment}}", "variables": { "id": "118" }}JSON// approval: Read one approval request by id.// Credential: Dashboard JWT (WETEL_JWT). Node 18+ (built-in fetch).const API_URL = process.env.WETEL_API_URL ?? "https://api.wetel.dev/graphql";
const query = `query OneApproval($id: ID!) { approval(id: $id) { id status title payload editableFields decidedAt decisionComment }}`;
const variables = { id: "118",};
type GraphQLResponse = { data?: Record<string, unknown>; errors?: { message: string; extensions?: { code?: string } }[];};
async function main(): Promise<void> { const res = await fetch(API_URL, { method: "POST", headers: { "Content-Type": "application/json", "x-huat-platform": "customer", Authorization: `Bearer ${process.env.WETEL_JWT ?? ""}`, }, body: JSON.stringify({ query, variables }), }); const body = (await res.json()) as GraphQLResponse; if (body.errors?.length) { const error = body.errors[0]; throw new Error(`${error.extensions?.code}: ${error.message}`); } console.log(JSON.stringify(body.data?.approval, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// approval: Read one approval request by id.// Credential: Dashboard JWT (WETEL_JWT). PHP 7.3+ with the curl extension.$apiUrl = getenv('WETEL_API_URL') ?: 'https://api.wetel.dev/graphql';
$query = <<<'GRAPHQL'query OneApproval($id: ID!) { approval(id: $id) { id status title payload editableFields decidedAt decisionComment }}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "id": "118"}JSON);
$ch = curl_init($apiUrl);curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'x-huat-platform: customer', 'Authorization: Bearer ' . getenv('WETEL_JWT'), ], CURLOPT_POSTFIELDS => json_encode(['query' => $query, 'variables' => $variables]),]);$body = json_decode(curl_exec($ch), true);curl_close($ch);
if (!empty($body['errors'])) { $error = $body['errors'][0]; fwrite(STDERR, ($error['extensions']['code'] ?? 'ERROR') . ': ' . $error['message'] . PHP_EOL); exit(1);}echo json_encode($body['data']['approval'], JSON_PRETTY_PRINT), PHP_EOL;// approval: Read one approval request by id.// Credential: Dashboard JWT (WETEL_JWT). .NET 8: put this in Program.cs of a console app and run "dotnet run".using System.Net.Http.Headers;using System.Net.Http.Json;using System.Text.Json;using System.Text.Json.Nodes;
var apiUrl = Environment.GetEnvironmentVariable("WETEL_API_URL") ?? "https://api.wetel.dev/graphql";
const string query = """ query OneApproval($id: ID!) { approval(id: $id) { id status title payload editableFields decidedAt decisionComment } } """;
var variables = JsonNode.Parse(""" { "id": "118" } """);
using var http = new HttpClient();using var request = new HttpRequestMessage(HttpMethod.Post, apiUrl){ Content = JsonContent.Create(new { query, variables }),};request.Headers.Add("x-huat-platform", "customer");request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("WETEL_JWT"));
using var response = await http.SendAsync(request);var body = JsonNode.Parse(await response.Content.ReadAsStringAsync())!;if (body["errors"] is JsonArray errors && errors.Count > 0){ Console.Error.WriteLine($"{errors[0]?["extensions"]?["code"]}: {errors[0]?["message"]}"); return 1;}Console.WriteLine(body["data"]?["approval"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# approval: Read one approval request by id.# Credential: Dashboard JWT (WETEL_JWT). Python 3.8+ with "requests".import jsonimport os
import requests
API_URL = os.environ.get("WETEL_API_URL", "https://api.wetel.dev/graphql")
QUERY = """query OneApproval($id: ID!) { approval(id: $id) { id status title payload editableFields decidedAt decisionComment }}"""
VARIABLES = { "id": "118",}
response = requests.post( API_URL, json={"query": QUERY, "variables": VARIABLES}, headers={ "x-huat-platform": "customer", "Authorization": "Bearer " + os.environ["WETEL_JWT"], }, timeout=30,)body = response.json()if body.get("errors"): error = body["errors"][0] raise SystemExit(f"{error.get('extensions', {}).get('code')}: {error['message']}")print(json.dumps(body["data"]["approval"], indent=2))Responses
Success (HTTP 200)
{ "data": { "approval": { "id": 118, "status": "APPROVED", "title": "Emergency escalation for ticket T-1001", "payload": { "ticketId": "T-1001", "severity": "high", "queue": "emergency" }, "editableFields": [ "queue" ], "decidedAt": "2026-10-12T03:21:44.000Z", "decisionComment": "Confirmed with the on-call lead." } }}NOT_FOUND: Not in your workspace (HTTP 200)
{ "errors": [ { "message": "Approval #118 not found", "locations": [ { "line": 2, "column": 3 } ], "path": [ "approval" ], "extensions": { "code": "NOT_FOUND", "status": 404, "originalError": { "message": "Approval #118 not found", "error": "Not Found", "statusCode": 404 } } } ], "data": null}decideApproval
Section titled “decideApproval”Approve or reject a pending request. The paused run resumes asynchronously.
- Kind: mutation
- Credential: Dashboard JWT:
Authorization: Bearer <jwt>, fromlogin(a person or a one-off script). Workspace owner only - Available in: sandbox and production
commentis optional (at most 1,000 characters); the workflow reads it asapproval.comment.editedPayloadis honoured onAPPROVEonly. Its keys must all be in the request’seditableFields. Send it through variables, never as an inline literal.- Only one of several simultaneous decisions wins; the others receive
CONFLICT. Limited to 30 decisions per minute per client IP by default.
Request
mutation Decide($id: ID!, $decision: ApprovalDecisionChoice!, $comment: String, $editedPayload: JSON) { decideApproval(id: $id, decision: $decision, comment: $comment, editedPayload: $editedPayload) { id status decidedAt editedPayload }}{ "id": "118", "decision": "APPROVE", "comment": "Confirmed with the on-call lead.", "editedPayload": { "queue": "emergency-weekend" }}Allowed values
ApprovalDecisionChoice:APPROVE,REJECT,REQUEST_CHANGESApprovalStatus:APPROVED,CANCELLED,CHANGES_REQUESTED,EXPIRED,PENDING,REJECTED
Code samples
#!/usr/bin/env bash# decideApproval: Approve or reject a pending request. The paused run resumes asynchronously.# Credential: Dashboard JWT (WETEL_JWT).API_URL="${WETEL_API_URL:-https://api.wetel.dev/graphql}"
curl -sS "$API_URL" \ -H "Content-Type: application/json" \ -H "x-huat-platform: customer" \ -H "Authorization: Bearer $WETEL_JWT" \ --data-binary @- <<'JSON'{ "query": "mutation Decide($id:ID!$decision:ApprovalDecisionChoice!$comment:String$editedPayload:JSON){decideApproval(id:$id decision:$decision comment:$comment editedPayload:$editedPayload){id status decidedAt editedPayload}}", "variables": { "id": "118", "decision": "APPROVE", "comment": "Confirmed with the on-call lead.", "editedPayload": { "queue": "emergency-weekend" } }}JSON// decideApproval: Approve or reject a pending request. The paused run resumes asynchronously.// Credential: Dashboard JWT (WETEL_JWT). Node 18+ (built-in fetch).const API_URL = process.env.WETEL_API_URL ?? "https://api.wetel.dev/graphql";
const query = `mutation Decide($id: ID!, $decision: ApprovalDecisionChoice!, $comment: String, $editedPayload: JSON) { decideApproval(id: $id, decision: $decision, comment: $comment, editedPayload: $editedPayload) { id status decidedAt editedPayload }}`;
const variables = { id: "118", decision: "APPROVE", comment: "Confirmed with the on-call lead.", editedPayload: { queue: "emergency-weekend", },};
type GraphQLResponse = { data?: Record<string, unknown>; errors?: { message: string; extensions?: { code?: string } }[];};
async function main(): Promise<void> { const res = await fetch(API_URL, { method: "POST", headers: { "Content-Type": "application/json", "x-huat-platform": "customer", Authorization: `Bearer ${process.env.WETEL_JWT ?? ""}`, }, body: JSON.stringify({ query, variables }), }); const body = (await res.json()) as GraphQLResponse; if (body.errors?.length) { const error = body.errors[0]; throw new Error(`${error.extensions?.code}: ${error.message}`); } console.log(JSON.stringify(body.data?.decideApproval, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// decideApproval: Approve or reject a pending request. The paused run resumes asynchronously.// Credential: Dashboard JWT (WETEL_JWT). PHP 7.3+ with the curl extension.$apiUrl = getenv('WETEL_API_URL') ?: 'https://api.wetel.dev/graphql';
$query = <<<'GRAPHQL'mutation Decide($id: ID!, $decision: ApprovalDecisionChoice!, $comment: String, $editedPayload: JSON) { decideApproval(id: $id, decision: $decision, comment: $comment, editedPayload: $editedPayload) { id status decidedAt editedPayload }}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "id": "118", "decision": "APPROVE", "comment": "Confirmed with the on-call lead.", "editedPayload": { "queue": "emergency-weekend" }}JSON);
$ch = curl_init($apiUrl);curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'x-huat-platform: customer', 'Authorization: Bearer ' . getenv('WETEL_JWT'), ], CURLOPT_POSTFIELDS => json_encode(['query' => $query, 'variables' => $variables]),]);$body = json_decode(curl_exec($ch), true);curl_close($ch);
if (!empty($body['errors'])) { $error = $body['errors'][0]; fwrite(STDERR, ($error['extensions']['code'] ?? 'ERROR') . ': ' . $error['message'] . PHP_EOL); exit(1);}echo json_encode($body['data']['decideApproval'], JSON_PRETTY_PRINT), PHP_EOL;// decideApproval: Approve or reject a pending request. The paused run resumes asynchronously.// Credential: Dashboard JWT (WETEL_JWT). .NET 8: put this in Program.cs of a console app and run "dotnet run".using System.Net.Http.Headers;using System.Net.Http.Json;using System.Text.Json;using System.Text.Json.Nodes;
var apiUrl = Environment.GetEnvironmentVariable("WETEL_API_URL") ?? "https://api.wetel.dev/graphql";
const string query = """ mutation Decide($id: ID!, $decision: ApprovalDecisionChoice!, $comment: String, $editedPayload: JSON) { decideApproval(id: $id, decision: $decision, comment: $comment, editedPayload: $editedPayload) { id status decidedAt editedPayload } } """;
var variables = JsonNode.Parse(""" { "id": "118", "decision": "APPROVE", "comment": "Confirmed with the on-call lead.", "editedPayload": { "queue": "emergency-weekend" } } """);
using var http = new HttpClient();using var request = new HttpRequestMessage(HttpMethod.Post, apiUrl){ Content = JsonContent.Create(new { query, variables }),};request.Headers.Add("x-huat-platform", "customer");request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("WETEL_JWT"));
using var response = await http.SendAsync(request);var body = JsonNode.Parse(await response.Content.ReadAsStringAsync())!;if (body["errors"] is JsonArray errors && errors.Count > 0){ Console.Error.WriteLine($"{errors[0]?["extensions"]?["code"]}: {errors[0]?["message"]}"); return 1;}Console.WriteLine(body["data"]?["decideApproval"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# decideApproval: Approve or reject a pending request. The paused run resumes asynchronously.# Credential: Dashboard JWT (WETEL_JWT). Python 3.8+ with "requests".import jsonimport os
import requests
API_URL = os.environ.get("WETEL_API_URL", "https://api.wetel.dev/graphql")
QUERY = """mutation Decide($id: ID!, $decision: ApprovalDecisionChoice!, $comment: String, $editedPayload: JSON) { decideApproval(id: $id, decision: $decision, comment: $comment, editedPayload: $editedPayload) { id status decidedAt editedPayload }}"""
VARIABLES = { "id": "118", "decision": "APPROVE", "comment": "Confirmed with the on-call lead.", "editedPayload": { "queue": "emergency-weekend", },}
response = requests.post( API_URL, json={"query": QUERY, "variables": VARIABLES}, headers={ "x-huat-platform": "customer", "Authorization": "Bearer " + os.environ["WETEL_JWT"], }, timeout=30,)body = response.json()if body.get("errors"): error = body["errors"][0] raise SystemExit(f"{error.get('extensions', {}).get('code')}: {error['message']}")print(json.dumps(body["data"]["decideApproval"], indent=2))Responses
Success (the run continues shortly after) (HTTP 200)
{ "data": { "decideApproval": { "id": 118, "status": "APPROVED", "decidedAt": "2026-10-12T03:21:44.000Z", "editedPayload": { "queue": "emergency-weekend" } } }}CONFLICT: Already decided (HTTP 200)
{ "errors": [ { "message": "Approval #118 was already approved by user #3 at 2026-10-12T03:21:44.000Z", "locations": [ { "line": 2, "column": 3 } ], "path": [ "decideApproval" ], "extensions": { "code": "CONFLICT", "status": 409, "originalError": { "message": "Approval #118 was already approved by user #3 at 2026-10-12T03:21:44.000Z", "error": "Conflict", "statusCode": 409 } } } ], "data": null}CONFLICT: Expired (HTTP 200)
{ "errors": [ { "message": "Approval #118 expired at 2026-10-12T05:15:00.000Z and can no longer be decided", "locations": [ { "line": 2, "column": 3 } ], "path": [ "decideApproval" ], "extensions": { "code": "CONFLICT", "status": 409, "originalError": { "message": "Approval #118 expired at 2026-10-12T05:15:00.000Z and can no longer be decided", "error": "Conflict", "statusCode": 409 } } } ], "data": null}BAD_REQUEST: Edited a key that is not editable (HTTP 200)
{ "errors": [ { "message": "Approval #118 does not allow editing: severity", "locations": [ { "line": 2, "column": 3 } ], "path": [ "decideApproval" ], "extensions": { "code": "BAD_REQUEST", "originalError": { "message": "Approval #118 does not allow editing: severity", "error": "Bad Request", "statusCode": 400 } } } ], "data": null}BAD_USER_INPUT: Decision value is not APPROVE or REJECT (HTTP 400)
Sent with variables:
{ "id": "118", "decision": "APPROVED"}{ "errors": [ { "message": "Variable \"$decision\" got invalid value \"APPROVED\"; Value \"APPROVED\" does not exist in \"ApprovalDecisionChoice\" enum. Did you mean the enum value \"APPROVE\"?", "locations": [ { "line": 1, "column": 27 } ], "extensions": { "code": "BAD_USER_INPUT" } } ]}cancelApproval
Section titled “cancelApproval”Withdraw a pending request. The paused run does not resume.
- Kind: mutation
- Credential: Dashboard JWT:
Authorization: Bearer <jwt>, fromlogin(a person or a one-off script). Workspace owner only - Available in: sandbox and production
reasonis optional (at most 1,000 characters).
Request
mutation Cancel($id: ID!, $reason: String) { cancelApproval(id: $id, reason: $reason) { id status }}{ "id": "118", "reason": "Raised by mistake during testing."}Allowed values
ApprovalStatus:APPROVED,CANCELLED,CHANGES_REQUESTED,EXPIRED,PENDING,REJECTED
Code samples
#!/usr/bin/env bash# cancelApproval: Withdraw a pending request. The paused run does not resume.# Credential: Dashboard JWT (WETEL_JWT).API_URL="${WETEL_API_URL:-https://api.wetel.dev/graphql}"
curl -sS "$API_URL" \ -H "Content-Type: application/json" \ -H "x-huat-platform: customer" \ -H "Authorization: Bearer $WETEL_JWT" \ --data-binary @- <<'JSON'{ "query": "mutation Cancel($id:ID!$reason:String){cancelApproval(id:$id reason:$reason){id status}}", "variables": { "id": "118", "reason": "Raised by mistake during testing." }}JSON// cancelApproval: Withdraw a pending request. The paused run does not resume.// Credential: Dashboard JWT (WETEL_JWT). Node 18+ (built-in fetch).const API_URL = process.env.WETEL_API_URL ?? "https://api.wetel.dev/graphql";
const query = `mutation Cancel($id: ID!, $reason: String) { cancelApproval(id: $id, reason: $reason) { id status }}`;
const variables = { id: "118", reason: "Raised by mistake during testing.",};
type GraphQLResponse = { data?: Record<string, unknown>; errors?: { message: string; extensions?: { code?: string } }[];};
async function main(): Promise<void> { const res = await fetch(API_URL, { method: "POST", headers: { "Content-Type": "application/json", "x-huat-platform": "customer", Authorization: `Bearer ${process.env.WETEL_JWT ?? ""}`, }, body: JSON.stringify({ query, variables }), }); const body = (await res.json()) as GraphQLResponse; if (body.errors?.length) { const error = body.errors[0]; throw new Error(`${error.extensions?.code}: ${error.message}`); } console.log(JSON.stringify(body.data?.cancelApproval, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// cancelApproval: Withdraw a pending request. The paused run does not resume.// Credential: Dashboard JWT (WETEL_JWT). PHP 7.3+ with the curl extension.$apiUrl = getenv('WETEL_API_URL') ?: 'https://api.wetel.dev/graphql';
$query = <<<'GRAPHQL'mutation Cancel($id: ID!, $reason: String) { cancelApproval(id: $id, reason: $reason) { id status }}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "id": "118", "reason": "Raised by mistake during testing."}JSON);
$ch = curl_init($apiUrl);curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'x-huat-platform: customer', 'Authorization: Bearer ' . getenv('WETEL_JWT'), ], CURLOPT_POSTFIELDS => json_encode(['query' => $query, 'variables' => $variables]),]);$body = json_decode(curl_exec($ch), true);curl_close($ch);
if (!empty($body['errors'])) { $error = $body['errors'][0]; fwrite(STDERR, ($error['extensions']['code'] ?? 'ERROR') . ': ' . $error['message'] . PHP_EOL); exit(1);}echo json_encode($body['data']['cancelApproval'], JSON_PRETTY_PRINT), PHP_EOL;// cancelApproval: Withdraw a pending request. The paused run does not resume.// Credential: Dashboard JWT (WETEL_JWT). .NET 8: put this in Program.cs of a console app and run "dotnet run".using System.Net.Http.Headers;using System.Net.Http.Json;using System.Text.Json;using System.Text.Json.Nodes;
var apiUrl = Environment.GetEnvironmentVariable("WETEL_API_URL") ?? "https://api.wetel.dev/graphql";
const string query = """ mutation Cancel($id: ID!, $reason: String) { cancelApproval(id: $id, reason: $reason) { id status } } """;
var variables = JsonNode.Parse(""" { "id": "118", "reason": "Raised by mistake during testing." } """);
using var http = new HttpClient();using var request = new HttpRequestMessage(HttpMethod.Post, apiUrl){ Content = JsonContent.Create(new { query, variables }),};request.Headers.Add("x-huat-platform", "customer");request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("WETEL_JWT"));
using var response = await http.SendAsync(request);var body = JsonNode.Parse(await response.Content.ReadAsStringAsync())!;if (body["errors"] is JsonArray errors && errors.Count > 0){ Console.Error.WriteLine($"{errors[0]?["extensions"]?["code"]}: {errors[0]?["message"]}"); return 1;}Console.WriteLine(body["data"]?["cancelApproval"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# cancelApproval: Withdraw a pending request. The paused run does not resume.# Credential: Dashboard JWT (WETEL_JWT). Python 3.8+ with "requests".import jsonimport os
import requests
API_URL = os.environ.get("WETEL_API_URL", "https://api.wetel.dev/graphql")
QUERY = """mutation Cancel($id: ID!, $reason: String) { cancelApproval(id: $id, reason: $reason) { id status }}"""
VARIABLES = { "id": "118", "reason": "Raised by mistake during testing.",}
response = requests.post( API_URL, json={"query": QUERY, "variables": VARIABLES}, headers={ "x-huat-platform": "customer", "Authorization": "Bearer " + os.environ["WETEL_JWT"], }, timeout=30,)body = response.json()if body.get("errors"): error = body["errors"][0] raise SystemExit(f"{error.get('extensions', {}).get('code')}: {error['message']}")print(json.dumps(body["data"]["cancelApproval"], indent=2))Responses
Success (HTTP 200)
{ "data": { "cancelApproval": { "id": 118, "status": "CANCELLED" } }}CONFLICT: Already decided, expired or cancelled (HTTP 200)
{ "errors": [ { "message": "Approval #118 is already approved and cannot be cancelled", "locations": [ { "line": 2, "column": 3 } ], "path": [ "cancelApproval" ], "extensions": { "code": "CONFLICT", "status": 409, "originalError": { "message": "Approval #118 is already approved and cannot be cancelled", "error": "Conflict", "statusCode": 409 } } } ], "data": null}