跳转到内容

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.

OperationKindCredentialWhat it does
approvalsqueryDashboard JWT (owner)List approval requests, newest first. Without a filter it lists only PENDING requests.
approvalqueryDashboard JWT (owner)Read one approval request by id.
decideApprovalmutationDashboard JWT (owner)Approve or reject a pending request. The paused run resumes asynchronously.
cancelApprovalmutationDashboard JWT (owner)Withdraw a pending request. The paused run does not resume.

List approval requests, newest first. Without a filter it lists only PENDING requests.

  • Kind: query
  • Credential: Dashboard JWT: Authorization: Bearer <jwt>, from login (a person or a one-off script). Workspace owner only
  • Available in: sandbox and production
  • Pass status for one state or statuses for several in one list (for example every decided request). Passing both, or an empty statuses list, is a BAD_REQUEST.
  • Forward pagination: first (default 20, at most 100) and after (a previous page’s pageInfo.endCursor, which walks to older rows). Optional workflowId and agentId narrow the list.
  • A request past expiresAt that has not been swept yet still reads PENDING but can no longer be decided.
  • payload is 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
}
}
}
variables
{
"statuses": [
"PENDING"
],
"first": 20
}

Allowed values

  • ApprovalStatus: APPROVED, CANCELLED, CHANGES_REQUESTED, EXPIRED, PENDING, REJECTED

Code samples

approvals.sh
#!/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

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"
}
}
]
}

Read one approval request by id.

  • Kind: query
  • Credential: Dashboard JWT: Authorization: Bearer <jwt>, from login (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
}
}
variables
{
"id": "118"
}

Allowed values

  • ApprovalStatus: APPROVED, CANCELLED, CHANGES_REQUESTED, EXPIRED, PENDING, REJECTED

Code samples

approval.sh
#!/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

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
}

Approve or reject a pending request. The paused run resumes asynchronously.

  • Kind: mutation
  • Credential: Dashboard JWT: Authorization: Bearer <jwt>, from login (a person or a one-off script). Workspace owner only
  • Available in: sandbox and production
  • comment is optional (at most 1,000 characters); the workflow reads it as approval.comment.
  • editedPayload is honoured on APPROVE only. Its keys must all be in the request’s editableFields. 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
}
}
variables
{
"id": "118",
"decision": "APPROVE",
"comment": "Confirmed with the on-call lead.",
"editedPayload": {
"queue": "emergency-weekend"
}
}

Allowed values

  • ApprovalDecisionChoice: APPROVE, REJECT, REQUEST_CHANGES
  • ApprovalStatus: APPROVED, CANCELLED, CHANGES_REQUESTED, EXPIRED, PENDING, REJECTED

Code samples

decideApproval.sh
#!/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

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"
}
}
]
}

Withdraw a pending request. The paused run does not resume.

  • Kind: mutation
  • Credential: Dashboard JWT: Authorization: Bearer <jwt>, from login (a person or a one-off script). Workspace owner only
  • Available in: sandbox and production
  • reason is optional (at most 1,000 characters).

Request

mutation Cancel($id: ID!, $reason: String) {
cancelApproval(id: $id, reason: $reason) {
id
status
}
}
variables
{
"id": "118",
"reason": "Raised by mistake during testing."
}

Allowed values

  • ApprovalStatus: APPROVED, CANCELLED, CHANGES_REQUESTED, EXPIRED, PENDING, REJECTED

Code samples

cancelApproval.sh
#!/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

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
}