API keys
This content is not available in your language yet.
An API key is the long-lived server credential for runtime calls such as sdkStart. Create one named key per consumer (an environment, an application) so you can revoke one without breaking the others. Managing keys takes a dashboard JWT. Keys belong to one environment: a sandbox key does not work in production.
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 |
|---|---|---|---|
createNamedApiKey | mutation | Dashboard JWT | Create a named API key. The raw key is returned once and cannot be read again. |
namedApiKeys | query | Dashboard JWT | List the workspace’s named API keys (metadata only, never the key itself). |
revokeNamedApiKey | mutation | Dashboard JWT | Revoke one named API key. Immediate and permanent; other keys are not affected. |
createNamedApiKey
Section titled “createNamedApiKey”Create a named API key. The raw key is returned once and cannot be read again.
- Kind: mutation
- Credential: Dashboard JWT:
Authorization: Bearer <jwt>, fromlogin(a person or a one-off script) - Available in: sandbox and production
- Store
keyimmediately in your server’s secret store. Only a SHA-256 hash is kept on the platform side. - Keep
id; it is whatrevokeNamedApiKeytakes. scopesis optional and adds rights beyond runtime access. The only accepted value today istemplates:hire, and only a workspace owner may grant a scope. Omit it for a plain runtime key.
Request
mutation CreateKey($name: String!, $scopes: [String!]) { createNamedApiKey(name: $name, scopes: $scopes) { id name key warning }}{ "name": "helpdesk-sandbox"}Code samples
#!/usr/bin/env bash# createNamedApiKey: Create a named API key. The raw key is returned once and cannot be read again.# 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 CreateKey($name:String!$scopes:[String!]){createNamedApiKey(name:$name scopes:$scopes){id name key warning}}", "variables": { "name": "helpdesk-sandbox" }}JSON// createNamedApiKey: Create a named API key. The raw key is returned once and cannot be read again.// 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 CreateKey($name: String!, $scopes: [String!]) { createNamedApiKey(name: $name, scopes: $scopes) { id name key warning }}`;
const variables = { name: "helpdesk-sandbox",};
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?.createNamedApiKey, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// createNamedApiKey: Create a named API key. The raw key is returned once and cannot be read again.// 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 CreateKey($name: String!, $scopes: [String!]) { createNamedApiKey(name: $name, scopes: $scopes) { id name key warning }}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "name": "helpdesk-sandbox"}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']['createNamedApiKey'], JSON_PRETTY_PRINT), PHP_EOL;// createNamedApiKey: Create a named API key. The raw key is returned once and cannot be read again.// 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 CreateKey($name: String!, $scopes: [String!]) { createNamedApiKey(name: $name, scopes: $scopes) { id name key warning } } """;
var variables = JsonNode.Parse(""" { "name": "helpdesk-sandbox" } """);
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"]?["createNamedApiKey"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# createNamedApiKey: Create a named API key. The raw key is returned once and cannot be read again.# 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 CreateKey($name: String!, $scopes: [String!]) { createNamedApiKey(name: $name, scopes: $scopes) { id name key warning }}"""
VARIABLES = { "name": "helpdesk-sandbox",}
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"]["createNamedApiKey"], indent=2))Responses
Success (HTTP 200)
{ "data": { "createNamedApiKey": { "id": 31, "name": "helpdesk-sandbox", "key": "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "warning": "Save this key immediately. It will not be shown again. Revoking it does not affect any other named key or the legacy tenant API key." } }}BAD_REQUEST: Unknown scope (sent with scopes [“admin”]) (HTTP 200)
{ "errors": [ { "message": "Unknown API key scope(s): admin. Allowed: templates:hire.", "locations": [ { "line": 2, "column": 3 } ], "path": [ "createNamedApiKey" ], "extensions": { "code": "BAD_REQUEST", "originalError": { "message": "Unknown API key scope(s): admin. Allowed: templates:hire.", "error": "Bad Request", "statusCode": 400 } } } ], "data": null}FORBIDDEN: A member (not the owner) asked for a scope (HTTP 200)
{ "errors": [ { "message": "Only the owner of this workspace can do this", "locations": [ { "line": 2, "column": 3 } ], "path": [ "createNamedApiKey" ], "extensions": { "code": "FORBIDDEN", "originalError": { "message": "Only the owner of this workspace can do this", "error": "Forbidden", "statusCode": 403 } } } ], "data": null}UNAUTHENTICATED: Called with an API key instead of a JWT (HTTP 200)
{ "errors": [ { "message": "Unauthorized", "locations": [ { "line": 2, "column": 3 } ], "path": [ "createNamedApiKey" ], "extensions": { "code": "UNAUTHENTICATED", "originalError": { "message": "Unauthorized", "statusCode": 401 } } } ], "data": null}namedApiKeys
Section titled “namedApiKeys”List the workspace’s named API keys (metadata only, never the key itself).
- Kind: query
- Credential: Dashboard JWT:
Authorization: Bearer <jwt>, fromlogin(a person or a one-off script) - Available in: sandbox and production
- Revoked and expired keys are included; filter on
revokedAtandexpiresAtfor an “active only” view.
Request
query ListKeys { namedApiKeys { id name scopes createdAt revokedAt expiresAt }}{}Code samples
#!/usr/bin/env bash# namedApiKeys: List the workspace's named API keys (metadata only, never the key itself).# 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 ListKeys{namedApiKeys{id name scopes createdAt revokedAt expiresAt}}", "variables": {}}JSON// namedApiKeys: List the workspace's named API keys (metadata only, never the key itself).// 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 ListKeys { namedApiKeys { id name scopes createdAt revokedAt expiresAt }}`;
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?.namedApiKeys, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// namedApiKeys: List the workspace's named API keys (metadata only, never the key itself).// 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 ListKeys { namedApiKeys { id name scopes createdAt revokedAt expiresAt }}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']['namedApiKeys'], JSON_PRETTY_PRINT), PHP_EOL;// namedApiKeys: List the workspace's named API keys (metadata only, never the key itself).// 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 ListKeys { namedApiKeys { id name scopes createdAt revokedAt expiresAt } } """;
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"]?["namedApiKeys"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# namedApiKeys: List the workspace's named API keys (metadata only, never the key itself).# 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 ListKeys { namedApiKeys { id name scopes createdAt revokedAt expiresAt }}"""
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"]["namedApiKeys"], indent=2))Responses
Success (HTTP 200)
{ "data": { "namedApiKeys": [ { "id": 31, "name": "helpdesk-sandbox", "scopes": null, "createdAt": "2026-10-12T02:40:11.000Z", "revokedAt": null, "expiresAt": null }, { "id": 24, "name": "old-test-key", "scopes": null, "createdAt": "2026-09-02T08:12:45.000Z", "revokedAt": "2026-09-30T10:01:02.000Z", "expiresAt": null } ] }}UNAUTHENTICATED: Missing credential (HTTP 200)
{ "errors": [ { "message": "Unauthorized", "locations": [ { "line": 2, "column": 3 } ], "path": [ "namedApiKeys" ], "extensions": { "code": "UNAUTHENTICATED", "originalError": { "message": "Unauthorized", "statusCode": 401 } } } ], "data": null}revokeNamedApiKey
Section titled “revokeNamedApiKey”Revoke one named API key. Immediate and permanent; other keys are not affected.
- Kind: mutation
- Credential: Dashboard JWT:
Authorization: Bearer <jwt>, fromlogin(a person or a one-off script) - Available in: sandbox and production
- There is no un-revoke. Create a new key instead.
Request
mutation RevokeKey($id: Int!) { revokeNamedApiKey(id: $id)}{ "id": 31}Code samples
#!/usr/bin/env bash# revokeNamedApiKey: Revoke one named API key. Immediate and permanent; other keys are not affected.# 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 RevokeKey($id:Int!){revokeNamedApiKey(id:$id)}", "variables": { "id": 31 }}JSON// revokeNamedApiKey: Revoke one named API key. Immediate and permanent; other keys are not affected.// 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 RevokeKey($id: Int!) { revokeNamedApiKey(id: $id)}`;
const variables = { id: 31,};
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?.revokeNamedApiKey, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// revokeNamedApiKey: Revoke one named API key. Immediate and permanent; other keys are not affected.// 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 RevokeKey($id: Int!) { revokeNamedApiKey(id: $id)}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "id": 31}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']['revokeNamedApiKey'], JSON_PRETTY_PRINT), PHP_EOL;// revokeNamedApiKey: Revoke one named API key. Immediate and permanent; other keys are not affected.// 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 RevokeKey($id: Int!) { revokeNamedApiKey(id: $id) } """;
var variables = JsonNode.Parse(""" { "id": 31 } """);
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"]?["revokeNamedApiKey"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# revokeNamedApiKey: Revoke one named API key. Immediate and permanent; other keys are not affected.# 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 RevokeKey($id: Int!) { revokeNamedApiKey(id: $id)}"""
VARIABLES = { "id": 31,}
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"]["revokeNamedApiKey"], indent=2))Responses
Success (HTTP 200)
{ "data": { "revokeNamedApiKey": true }}NOT_FOUND: Not a key of your workspace (HTTP 200)
{ "errors": [ { "message": "Named API key #31 not found", "locations": [ { "line": 2, "column": 3 } ], "path": [ "revokeNamedApiKey" ], "extensions": { "code": "NOT_FOUND", "status": 404, "originalError": { "message": "Named API key #31 not found", "error": "Not Found", "statusCode": 404 } } } ], "data": null}BAD_USER_INPUT: Id sent as a string (HTTP 400)
Sent with variables:
{ "id": "thirty-one"}{ "errors": [ { "message": "Variable \"$id\" got invalid value \"thirty-one\"; Int cannot represent non-integer value: \"thirty-one\"", "locations": [ { "line": 1, "column": 20 } ], "extensions": { "code": "BAD_USER_INPUT" } } ]}