Custom actions
This content is not available in your language yet.
A custom action is a named, reusable REST call to your own system, for example “create a ticket” in your ticketing backend. A workflow action node calls it with arguments rendered from the conversation. Every operation here takes a dashboard JWT. A stored bearerToken is kept as a secret and never returned; it is sent as Authorization: Bearer <token>, or under the header you name in credentialSecretHeaderName for APIs that use a static secret header.
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 |
|---|---|---|---|
createCustomAction | mutation | Dashboard JWT | Register a REST call for workflow action nodes. |
customActions | query | Dashboard JWT | List the workspace’s custom actions. |
testCustomAction | mutation | Dashboard JWT | Make one real call of a custom action and classify the answer, before a conversation depends on it. |
deleteCustomAction | mutation | Dashboard JWT | Delete a custom action. Action nodes that use it fail when they run. |
createCustomAction
Section titled “createCustomAction”Register a REST call for workflow action nodes.
- Kind: mutation
- Credential: Dashboard JWT:
Authorization: Bearer <jwt>, fromlogin(a person or a one-off script) - Available in: sandbox and production
methodis one ofGET,POST,PUT,PATCH,DELETE(defaultPOST).argsSchemadocuments the arguments the call expects; it is not enforced at run time.- Limited to 10 calls per minute per client IP.
Request
mutation CreateAction($input: CreateCustomActionInput!) { createCustomAction(input: $input) { id name method url credentialSecretHeaderName }}{ "input": { "name": "create_ticket", "description": "Create a ticket in the ticketing backend and return its number.", "url": "https://tickets.example.com/api/tickets", "method": "POST", "bearerToken": "example-backend-token-not-real", "argsSchema": { "type": "object", "properties": { "subject": { "type": "string" }, "level": { "type": "string", "enum": [ "L1", "L2", "L3" ] } }, "required": [ "subject", "level" ] } }}Input fields
CreateCustomActionInput
| Field | Type | Required | Description |
|---|---|---|---|
argsSchema | JSON | no | Optional JSON Schema (an object, send it through variables) documenting the arguments; not enforced on calls. |
bearerToken | String | no | Optional static secret, sent as Authorization: Bearer (or under credentialSecretHeaderName). Stored as a secret and never returned. Omit for an endpoint that needs no credential. |
credentialSecretHeaderName | String | no | Optional header name to send the stored bearerToken under, for third-party APIs that authenticate with a static non-Bearer secret header (e.g. ‘X-Client-Secret’, ‘X-Api-Key’). Omit for the default ‘Authorization: Bearer <token>’ behavior. When set, the raw secret value is sent under this header with no ‘Bearer ’ prefix, and no Authorization header is sent. Only accepted when bearerToken is supplied in the same call. |
credentialSource | String | no | Optional SESSION_ value that resolves the credential live, per call, from the end user signed in to the conversation through single sign-on, for the products that support it; an invalid value is refused with the list of allowed values. Leave unset for bearerToken or Lark app credentials. |
creditsPerCall | Int | no | The credits charged for one successful call of this action (an integer, 0 or more). Omit to use the platform default (2 credits). |
description | String | no | Optional free-text description. |
extraHeaders | JSON | no | Static, non-secret headers sent on every call: a JSON object of header name to string value (send it through variables). Never a credential; it is returned as is. |
larkAppId | String | no | App id of your own Lark (or Feishu) app; give it together with larkAppSecret instead of bearerToken. The action then authenticates with that app’s tenant access token, fetched by the server. |
larkAppSecret | String | no | App secret of your own Lark (or Feishu) app, given with larkAppId. Stored as a secret and never returned. |
larkRegion | String | no | Which platform the Lark app lives on: FEISHU or LARK_SUITE (default LARK_SUITE). |
method | String | no | GET, POST, PUT, PATCH or DELETE (default POST). |
name | String! | yes | Display name (1 to 100 characters). |
url | String! | yes | URL to call. Private and internal addresses are refused, and redirects are not followed. |
Code samples
#!/usr/bin/env bash# createCustomAction: Register a REST call for workflow action nodes.# 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 CreateAction($input:CreateCustomActionInput!){createCustomAction(input:$input){id name method url credentialSecretHeaderName}}", "variables": { "input": { "name": "create_ticket", "description": "Create a ticket in the ticketing backend and return its number.", "url": "https://tickets.example.com/api/tickets", "method": "POST", "bearerToken": "example-backend-token-not-real", "argsSchema": { "type": "object", "properties": { "subject": { "type": "string" }, "level": { "type": "string", "enum": [ "L1", "L2", "L3" ] } }, "required": [ "subject", "level" ] } } }}JSON// createCustomAction: Register a REST call for workflow action nodes.// 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 CreateAction($input: CreateCustomActionInput!) { createCustomAction(input: $input) { id name method url credentialSecretHeaderName }}`;
const variables = { input: { name: "create_ticket", description: "Create a ticket in the ticketing backend and return its number.", url: "https://tickets.example.com/api/tickets", method: "POST", bearerToken: "example-backend-token-not-real", argsSchema: { type: "object", properties: { subject: { type: "string", }, level: { type: "string", enum: ["L1", "L2", "L3"], }, }, required: ["subject", "level"], }, },};
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?.createCustomAction, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// createCustomAction: Register a REST call for workflow action nodes.// 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 CreateAction($input: CreateCustomActionInput!) { createCustomAction(input: $input) { id name method url credentialSecretHeaderName }}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "input": { "name": "create_ticket", "description": "Create a ticket in the ticketing backend and return its number.", "url": "https://tickets.example.com/api/tickets", "method": "POST", "bearerToken": "example-backend-token-not-real", "argsSchema": { "type": "object", "properties": { "subject": { "type": "string" }, "level": { "type": "string", "enum": [ "L1", "L2", "L3" ] } }, "required": [ "subject", "level" ] } }}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']['createCustomAction'], JSON_PRETTY_PRINT), PHP_EOL;// createCustomAction: Register a REST call for workflow action nodes.// 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 CreateAction($input: CreateCustomActionInput!) { createCustomAction(input: $input) { id name method url credentialSecretHeaderName } } """;
var variables = JsonNode.Parse(""" { "input": { "name": "create_ticket", "description": "Create a ticket in the ticketing backend and return its number.", "url": "https://tickets.example.com/api/tickets", "method": "POST", "bearerToken": "example-backend-token-not-real", "argsSchema": { "type": "object", "properties": { "subject": { "type": "string" }, "level": { "type": "string", "enum": [ "L1", "L2", "L3" ] } }, "required": [ "subject", "level" ] } } } """);
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"]?["createCustomAction"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# createCustomAction: Register a REST call for workflow action nodes.# 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 CreateAction($input: CreateCustomActionInput!) { createCustomAction(input: $input) { id name method url credentialSecretHeaderName }}"""
VARIABLES = { "input": { "name": "create_ticket", "description": "Create a ticket in the ticketing backend and return its number.", "url": "https://tickets.example.com/api/tickets", "method": "POST", "bearerToken": "example-backend-token-not-real", "argsSchema": { "type": "object", "properties": { "subject": { "type": "string", }, "level": { "type": "string", "enum": [ "L1", "L2", "L3", ], }, }, "required": [ "subject", "level", ], }, },}
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"]["createCustomAction"], indent=2))Responses
Success (HTTP 200)
{ "data": { "createCustomAction": { "id": 9, "name": "create_ticket", "method": "POST", "url": "https://tickets.example.com/api/tickets", "credentialSecretHeaderName": null } }}BAD_REQUEST: Header name without a secret (HTTP 200)
{ "errors": [ { "message": "credentialSecretHeaderName requires bearerToken in the same call — a header name with no secret to send under it has no effect.", "locations": [ { "line": 2, "column": 3 } ], "path": [ "createCustomAction" ], "extensions": { "code": "BAD_REQUEST", "originalError": { "message": "credentialSecretHeaderName requires bearerToken in the same call — a header name with no secret to send under it has no effect.", "error": "Bad Request", "statusCode": 400 } } } ], "data": null}BAD_REQUEST: Unsupported method (HTTP 200)
{ "errors": [ { "message": "method must be one of the following values: GET, POST, PUT, PATCH, DELETE", "locations": [ { "line": 2, "column": 3 } ], "path": [ "createCustomAction" ], "extensions": { "code": "BAD_REQUEST", "originalError": { "message": "method must be one of the following values: GET, POST, PUT, PATCH, DELETE", "error": "Bad Request", "statusCode": 400 } } } ], "data": null}customActions
Section titled “customActions”List the workspace’s custom actions.
- Kind: query
- Credential: Dashboard JWT:
Authorization: Bearer <jwt>, fromlogin(a person or a one-off script) - Available in: sandbox and production
Request
query ListActions { customActions { id name method url description creditsPerCall }}{}Code samples
#!/usr/bin/env bash# customActions: List the workspace's custom actions.# 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 ListActions{customActions{id name method url description creditsPerCall}}", "variables": {}}JSON// customActions: List the workspace's custom actions.// 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 ListActions { customActions { id name method url description creditsPerCall }}`;
const variables = {};
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?.customActions, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// customActions: List the workspace's custom actions.// 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 ListActions { customActions { id name method url description creditsPerCall }}GRAPHQL;
$variables = json_decode(<<<'JSON'{}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']['customActions'], JSON_PRETTY_PRINT), PHP_EOL;// customActions: List the workspace's custom actions.// 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 ListActions { customActions { id name method url description creditsPerCall } } """;
var variables = JsonNode.Parse(""" {} """);
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"]?["customActions"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# customActions: List the workspace's custom actions.# 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 ListActions { customActions { id name method url description creditsPerCall }}"""
VARIABLES = {}
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"]["customActions"], indent=2))Responses
Success (HTTP 200)
{ "data": { "customActions": [ { "id": 9, "name": "create_ticket", "method": "POST", "url": "https://tickets.example.com/api/tickets", "description": "Create a ticket in the ticketing backend and return its number.", "creditsPerCall": null } ] }}UNAUTHENTICATED: Called with an API key instead of a JWT (HTTP 200)
{ "errors": [ { "message": "Unauthorized", "locations": [ { "line": 2, "column": 3 } ], "path": [ "customActions" ], "extensions": { "code": "UNAUTHENTICATED", "originalError": { "message": "Unauthorized", "statusCode": 401 } } } ], "data": null}testCustomAction
Section titled “testCustomAction”Make one real call of a custom action and classify the answer, before a conversation depends on it.
- Kind: mutation
- Credential: Dashboard JWT:
Authorization: Bearer <jwt>, fromlogin(a person or a one-off script) - Available in: sandbox and production
- The call really happens, through the same path a workflow uses. An action whose method is not
GETneedsconfirmWrite: trueand may change data on the target. sampleArgs(a flat JSON object) is sent as the body, or as the query string forGETandDELETE, exactly as written: template syntax such as{{x}}is refused.outcometells you what to fix:CREDENTIAL_REJECTED(401/403: the stored token or its header is wrong),REQUEST_REJECTED(other 4xx),SERVER_ERROR(5xx),TIMEOUT,UNREACHABLE,BLOCKED(a private or reserved address),WRITE_NOT_CONFIRMED. The response snippet is masked and cut to 500 characters.- A successful test is billed like the same call in a workflow (
creditsBilled); a failed or refused test is free. Limited to 10 calls per minute per client IP.
Request
mutation TestAction($input: TestCustomActionInput!) { testCustomAction(input: $input) { outcome httpStatus message responseSnippet durationMs creditsBilled }}{ "input": { "id": 9, "confirmWrite": true, "sampleArgs": { "subject": "Test ticket from the developer portal", "level": "L1" } }}Input fields
TestCustomActionInput
| Field | Type | Required | Description |
|---|---|---|---|
confirmWrite | Boolean | no | Required to test any action whose method is not GET, because the call really happens and may change data on the target. Default false. |
id | Int! | yes | The custom action to test. |
sampleArgs | JSON | no | A flat JSON object sent as the request body (or the query string for GET/DELETE), exactly as a workflow node would send its rendered arguments. Values are sent verbatim: template syntax such as {{x}} is rejected. Omit to send no arguments. |
Allowed values
CustomActionTestOutcome:BLOCKED,CREDENTIAL_REJECTED,CREDENTIAL_UNAVAILABLE,NOT_TESTABLE,OK,REQUEST_REJECTED,SERVER_ERROR,TIMEOUT,UNKNOWN_ERROR,UNREACHABLE,WRITE_NOT_CONFIRMED
Code samples
#!/usr/bin/env bash# testCustomAction: Make one real call of a custom action and classify the answer, before a conversation depends on it.# 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 TestAction($input:TestCustomActionInput!){testCustomAction(input:$input){outcome httpStatus message responseSnippet durationMs creditsBilled}}", "variables": { "input": { "id": 9, "confirmWrite": true, "sampleArgs": { "subject": "Test ticket from the developer portal", "level": "L1" } } }}JSON// testCustomAction: Make one real call of a custom action and classify the answer, before a conversation depends on it.// 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 TestAction($input: TestCustomActionInput!) { testCustomAction(input: $input) { outcome httpStatus message responseSnippet durationMs creditsBilled }}`;
const variables = { input: { id: 9, confirmWrite: true, sampleArgs: { subject: "Test ticket from the developer portal", level: "L1", }, },};
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?.testCustomAction, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// testCustomAction: Make one real call of a custom action and classify the answer, before a conversation depends on it.// 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 TestAction($input: TestCustomActionInput!) { testCustomAction(input: $input) { outcome httpStatus message responseSnippet durationMs creditsBilled }}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "input": { "id": 9, "confirmWrite": true, "sampleArgs": { "subject": "Test ticket from the developer portal", "level": "L1" } }}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']['testCustomAction'], JSON_PRETTY_PRINT), PHP_EOL;// testCustomAction: Make one real call of a custom action and classify the answer, before a conversation depends on it.// 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 TestAction($input: TestCustomActionInput!) { testCustomAction(input: $input) { outcome httpStatus message responseSnippet durationMs creditsBilled } } """;
var variables = JsonNode.Parse(""" { "input": { "id": 9, "confirmWrite": true, "sampleArgs": { "subject": "Test ticket from the developer portal", "level": "L1" } } } """);
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"]?["testCustomAction"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# testCustomAction: Make one real call of a custom action and classify the answer, before a conversation depends on it.# 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 TestAction($input: TestCustomActionInput!) { testCustomAction(input: $input) { outcome httpStatus message responseSnippet durationMs creditsBilled }}"""
VARIABLES = { "input": { "id": 9, "confirmWrite": True, "sampleArgs": { "subject": "Test ticket from the developer portal", "level": "L1", }, },}
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"]["testCustomAction"], indent=2))Responses
OK (HTTP 200)
{ "data": { "testCustomAction": { "outcome": "OK", "httpStatus": 200, "message": "The target answered successfully.", "responseSnippet": "{\"ticketNumber\":\"T-1003\"}", "durationMs": 284, "creditsBilled": 2 } }}Write not confirmed (a classified outcome, not an error) (HTTP 200)
{ "data": { "testCustomAction": { "outcome": "WRITE_NOT_CONFIRMED", "httpStatus": null, "message": "This action uses POST, so the test call would really be made and may change data on the target. Send confirmWrite: true to run it.", "responseSnippet": null, "durationMs": 0, "creditsBilled": 0 } }}BAD_REQUEST: Template syntax in sampleArgs (HTTP 200)
{ "errors": [ { "message": "sampleArgs are sent verbatim and may not contain template syntax ({{ }}).", "locations": [ { "line": 2, "column": 3 } ], "path": [ "testCustomAction" ], "extensions": { "code": "BAD_REQUEST", "originalError": { "message": "sampleArgs are sent verbatim and may not contain template syntax ({{ }}).", "error": "Bad Request", "statusCode": 400 } } } ], "data": null}NOT_FOUND: Not in your workspace (HTTP 200)
{ "errors": [ { "message": "Custom action #9 not found", "locations": [ { "line": 2, "column": 3 } ], "path": [ "testCustomAction" ], "extensions": { "code": "NOT_FOUND", "status": 404, "originalError": { "message": "Custom action #9 not found", "error": "Not Found", "statusCode": 404 } } } ], "data": null}deleteCustomAction
Section titled “deleteCustomAction”Delete a custom action. Action nodes that use it fail when they run.
- Kind: mutation
- Credential: Dashboard JWT:
Authorization: Bearer <jwt>, fromlogin(a person or a one-off script) - Available in: sandbox and production
- Limited to 10 calls per minute per client IP.
Request
mutation DeleteAction($id: Int!) { deleteCustomAction(id: $id)}{ "id": 9}Code samples
#!/usr/bin/env bash# deleteCustomAction: Delete a custom action. Action nodes that use it fail when they run.# 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 DeleteAction($id:Int!){deleteCustomAction(id:$id)}", "variables": { "id": 9 }}JSON// deleteCustomAction: Delete a custom action. Action nodes that use it fail when they run.// 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 DeleteAction($id: Int!) { deleteCustomAction(id: $id)}`;
const variables = { id: 9,};
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?.deleteCustomAction, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// deleteCustomAction: Delete a custom action. Action nodes that use it fail when they run.// 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 DeleteAction($id: Int!) { deleteCustomAction(id: $id)}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "id": 9}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']['deleteCustomAction'], JSON_PRETTY_PRINT), PHP_EOL;// deleteCustomAction: Delete a custom action. Action nodes that use it fail when they run.// 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 DeleteAction($id: Int!) { deleteCustomAction(id: $id) } """;
var variables = JsonNode.Parse(""" { "id": 9 } """);
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"]?["deleteCustomAction"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# deleteCustomAction: Delete a custom action. Action nodes that use it fail when they run.# 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 DeleteAction($id: Int!) { deleteCustomAction(id: $id)}"""
VARIABLES = { "id": 9,}
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"]["deleteCustomAction"], indent=2))Responses
Success (HTTP 200)
{ "data": { "deleteCustomAction": true }}NOT_FOUND: Not in your workspace (HTTP 200)
{ "errors": [ { "message": "Custom action #9 not found", "locations": [ { "line": 2, "column": 3 } ], "path": [ "deleteCustomAction" ], "extensions": { "code": "NOT_FOUND", "status": 404, "originalError": { "message": "Custom action #9 not found", "error": "Not Found", "statusCode": 404 } } } ], "data": null}