Sessions and messages
This content is not available in your language yet.
A session is one conversation between an end user and one agent. Your server starts it with the API key and receives a session token (avatarToken) that is valid for that session only. Sending a message returns true at once; the reply is generated asynchronously and stored as a message on the session, so a server-to-server integration polls the session’s messages (or subscribes to sessionEvents from a browser, see Events and Subscriptions).
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 |
|---|---|---|---|
sdkStart | mutation | API key | Start a session against an agent and receive its session token. |
sdkStartVoiceCall | mutation | API key | Start a live voice call with an agent and receive the LiveKit URL and join token for the caller. |
sdkSendMessage | mutation | Session token (avatarToken) | Send one user message into the session. Returns true at once; the reply follows asynchronously. |
sessionsByExternalCustomerId | query | API key | List the sessions started with one clientId, with their messages. The API-key way to poll for a reply. |
session | query | Dashboard JWT | Read one session and its full message history by id (dashboard credential). |
sdkEndSession | mutation | Session token (avatarToken) | End the session. The session token stops working for new messages. |
evaluation | query | Dashboard JWT | Read the scored evaluation of an ended session (score, recommendation, summary, strengths, weaknesses). |
refreshAvatarToken | mutation | No credential header | Exchange a session token that is about to expire (or expired in the last 10 minutes) for a fresh one. |
sdkStart
Section titled “sdkStart”Start a session against an agent and receive its session token.
- Kind: mutation
- Credential: API key:
X-Api-Key: sk_..., kept on your server only - Available in: sandbox and production
- Call it from your server only. The API key must never reach a browser or a mobile app.
clientIdis your own identifier for the end user or conversation. Give each conversation its own value (for example the ticket number) if you plan to poll withsessionsByExternalCustomerId.contextis an optional flat JSON object (at most 32 keys, scalar values, 8 KB) that every workflow turn can read assession.context. Never put secrets in it.- Starting a session can send the agent’s opening greeting, which is a metered model call.
- Limited to 30 calls per minute per client IP.
Request
mutation StartSession($input: SdkStartInput!) { sdkStart(input: $input) { sessionId avatarToken agentId graphqlEndpoint }}{ "input": { "agentId": 7, "clientId": "ticket-1001", "context": { "ticketId": "T-1001", "product": "laptop" } }}Input fields
SdkStartInput
| Field | Type | Required | Description |
|---|---|---|---|
agentId | Int | no | Id of the agent to talk to (an integer from the agents query); NotFound when it is not in the API key’s workspace. Omit to use the workspace’s oldest ACTIVE agent; when the workspace has no active agent at all, a basic agent is created from agentName, position, personaPrompt, voiceTier and useCase. |
agentName | String | no | Name for the agent created when the workspace has no active agent (at most 100 characters, default “AI Assistant”). Ignored whenever an existing agent is used. |
clientId | String | no | Your own identifier for the end user or conversation (at most 255 characters), stored on the session as clientExternalId so you can find it again with sessionsByExternalCustomerId. When omitted the server generates “sdk-” followed by a random UUID. |
context | JSON | no | Optional per-session data as a flat JSON object (pass it through variables, not an inline literal), for example {accountId, contactId, channelName, company} (channel itself is a reserved name and is refused). Same rules as turnContext: at most 32 keys matching [A-Za-z][A-Za-z0-9_]*, scalar values (strings up to 4096 characters), 8 KB in total, reserved runner names refused, never secrets. Stored on the session (readable through the session metadata the dashboard and API-key session queries return) and exposed to every workflow turn as ctx.session.context (for example {{session.context.company}}). |
personaPrompt | String | no | System prompt for the agent created when the workspace has no active agent (at most 2000 characters; a generic friendly-assistant prompt when omitted). Ignored whenever an existing agent is used: it never overrides an agent’s own persona. |
position | String | no | Role title for the agent created when the workspace has no active agent (at most 100 characters, default “Software Engineer”). Ignored whenever an existing agent is used. |
useCase | AgentUseCase | no | Use case for the agent created when the workspace has no active agent (default INTERVIEW). Ignored whenever an existing agent is used. |
voiceTier | VoiceTier | no | Voice tier for the agent created when the workspace has no active agent (default STANDARD). Ignored whenever an existing agent is used. |
Allowed values
AgentUseCase:CUSTOM,EDUCATION,INTERVIEW,ONBOARDING,SALES,SUPPORTVoiceTier:CHIRP3_HD,NEURAL2,STANDARD,STUDIO,WAVENET
Code samples
#!/usr/bin/env bash# sdkStart: Start a session against an agent and receive its session token.# Credential: API key (WETEL_API_KEY).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 "X-Api-Key: $WETEL_API_KEY" \ --data-binary @- <<'JSON'{ "query": "mutation StartSession($input:SdkStartInput!){sdkStart(input:$input){sessionId avatarToken agentId graphqlEndpoint}}", "variables": { "input": { "agentId": 7, "clientId": "ticket-1001", "context": { "ticketId": "T-1001", "product": "laptop" } } }}JSON// sdkStart: Start a session against an agent and receive its session token.// Credential: API key (WETEL_API_KEY). Node 18+ (built-in fetch).const API_URL = process.env.WETEL_API_URL ?? "https://api.wetel.dev/graphql";
const query = `mutation StartSession($input: SdkStartInput!) { sdkStart(input: $input) { sessionId avatarToken agentId graphqlEndpoint }}`;
const variables = { input: { agentId: 7, clientId: "ticket-1001", context: { ticketId: "T-1001", product: "laptop", }, },};
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", "X-Api-Key": process.env.WETEL_API_KEY ?? "", }, 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?.sdkStart, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// sdkStart: Start a session against an agent and receive its session token.// Credential: API key (WETEL_API_KEY). PHP 7.3+ with the curl extension.$apiUrl = getenv('WETEL_API_URL') ?: 'https://api.wetel.dev/graphql';
$query = <<<'GRAPHQL'mutation StartSession($input: SdkStartInput!) { sdkStart(input: $input) { sessionId avatarToken agentId graphqlEndpoint }}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "input": { "agentId": 7, "clientId": "ticket-1001", "context": { "ticketId": "T-1001", "product": "laptop" } }}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', 'X-Api-Key: ' . getenv('WETEL_API_KEY'), ], 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']['sdkStart'], JSON_PRETTY_PRINT), PHP_EOL;// sdkStart: Start a session against an agent and receive its session token.// Credential: API key (WETEL_API_KEY). .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 StartSession($input: SdkStartInput!) { sdkStart(input: $input) { sessionId avatarToken agentId graphqlEndpoint } } """;
var variables = JsonNode.Parse(""" { "input": { "agentId": 7, "clientId": "ticket-1001", "context": { "ticketId": "T-1001", "product": "laptop" } } } """);
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.Add("X-Api-Key", Environment.GetEnvironmentVariable("WETEL_API_KEY"));
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"]?["sdkStart"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# sdkStart: Start a session against an agent and receive its session token.# Credential: API key (WETEL_API_KEY). 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 StartSession($input: SdkStartInput!) { sdkStart(input: $input) { sessionId avatarToken agentId graphqlEndpoint }}"""
VARIABLES = { "input": { "agentId": 7, "clientId": "ticket-1001", "context": { "ticketId": "T-1001", "product": "laptop", }, },}
response = requests.post( API_URL, json={"query": QUERY, "variables": VARIABLES}, headers={ "x-huat-platform": "customer", "X-Api-Key": os.environ["WETEL_API_KEY"], }, 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"]["sdkStart"], indent=2))Responses
Success (HTTP 200)
{ "data": { "sdkStart": { "sessionId": 4211, "avatarToken": "example-avatar-token-not-real", "agentId": 7, "graphqlEndpoint": "https://api.wetel.dev/graphql" } }}UNAUTHENTICATED: Missing API key (HTTP 200)
{ "errors": [ { "message": "Missing X-Api-Key header", "locations": [ { "line": 2, "column": 3 } ], "path": [ "sdkStart" ], "extensions": { "code": "UNAUTHENTICATED", "originalError": { "message": "Missing X-Api-Key header", "error": "Unauthorized", "statusCode": 401 } } } ], "data": null}UNAUTHENTICATED: Unknown or revoked API key (HTTP 200)
{ "errors": [ { "message": "Invalid API key", "locations": [ { "line": 2, "column": 3 } ], "path": [ "sdkStart" ], "extensions": { "code": "UNAUTHENTICATED", "originalError": { "message": "Invalid API key", "error": "Unauthorized", "statusCode": 401 } } } ], "data": null}NOT_FOUND: Agent not found in your workspace (HTTP 200)
{ "errors": [ { "message": "Agent #7 not found", "locations": [ { "line": 2, "column": 3 } ], "path": [ "sdkStart" ], "extensions": { "code": "NOT_FOUND", "status": 404, "originalError": { "message": "Agent #7 not found", "error": "Not Found", "statusCode": 404 } } } ], "data": null}FORBIDDEN: Platform header missing (HTTP 200)
{ "errors": [ { "message": "Platform is not specified!", "locations": [ { "line": 2, "column": 3 } ], "path": [ "sdkStart" ], "extensions": { "code": "FORBIDDEN", "originalError": { "message": "Platform is not specified!", "error": "Forbidden", "statusCode": 403 } } } ], "data": null}BAD_USER_INPUT: Unknown field in the input (HTTP 400)
Sent with variables:
{ "input": { "agentId": 7, "customerId": "ticket-1001" }}{ "errors": [ { "message": "Variable \"$input\" got invalid value { agentId: 7, customerId: \"ticket-1001\" }; Field \"customerId\" is not defined by type \"SdkStartInput\".", "locations": [ { "line": 1, "column": 23 } ], "extensions": { "code": "BAD_USER_INPUT" } } ]}sdkStartVoiceCall
Section titled “sdkStartVoiceCall”Start a live voice call with an agent and receive the LiveKit URL and join token for the caller.
- Kind: mutation
- Credential: API key:
X-Api-Key: sk_..., kept on your server only - Available in: sandbox and production
- Staging only, on request. Answers FORBIDDEN where voice calls are not enabled; not enabled in production.
- Call it from your server only. The API key must never reach a browser or a mobile app.
- The join token is valid to join for about two minutes and carries a session credential that the caller’s browser can read. Treat it as belonging to one caller; never log or share it.
contextis an optional flat JSON object exposed to the workflow asctx.session.context, with the same rules assdkStart.- Limited to 60 calls per minute per client address and 30 call starts per minute per workspace. The over-limit error may carry no specific code.
- Capped at 1,000 call requests per API key per UTC day by default. Past it the call is refused with
extensions.status429 and the messageDaily voice call limit reached for this API key; try again tomorrow (UTC). A call nobody joins is closed asEXPIREDwithin about 15 minutes.
Request
mutation StartVoiceCall($input: SdkStartVoiceCallInput!) { sdkStartVoiceCall(input: $input) { sessionId agentId livekitUrl livekitToken roomName expiresAt }}{ "input": { "agentId": 7, "clientId": "call-0042" }}Input fields
SdkStartVoiceCallInput
| Field | Type | Required | Description |
|---|---|---|---|
agentId | Int! | yes | Id of the agent that answers the call (an integer from the agents query); NotFound when it is not in the API key’s workspace. The agent’s published workflow answers every turn, so it should be a voice-safe workflow: short spoken sentences, no lists or markdown. |
clientId | String | no | Your own identifier for the caller or the call, shown on the session (at most 255 characters). A fresh one is generated when omitted. |
context | JSON | no | Optional per-call data as a flat JSON object (pass it through variables, not an inline literal), exposed to the workflow as ctx.session.context. Same rules as the context of sdkStart. |
engine | VoiceEngine | no | Who answers the call (default WORKFLOW: the agent’s published workflow). LIVE uses a speech-to-speech model from the agent’s instructions only (no workflow, no tools, transcript recorded after the call) and is refused unless the deployment has it enabled. |
Allowed values
VoiceEngine:LIVE,WORKFLOW
Code samples
#!/usr/bin/env bash# sdkStartVoiceCall: Start a live voice call with an agent and receive the LiveKit URL and join token for the caller.# Credential: API key (WETEL_API_KEY).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 "X-Api-Key: $WETEL_API_KEY" \ --data-binary @- <<'JSON'{ "query": "mutation StartVoiceCall($input:SdkStartVoiceCallInput!){sdkStartVoiceCall(input:$input){sessionId agentId livekitUrl livekitToken roomName expiresAt}}", "variables": { "input": { "agentId": 7, "clientId": "call-0042" } }}JSON// sdkStartVoiceCall: Start a live voice call with an agent and receive the LiveKit URL and join token for the caller.// Credential: API key (WETEL_API_KEY). Node 18+ (built-in fetch).const API_URL = process.env.WETEL_API_URL ?? "https://api.wetel.dev/graphql";
const query = `mutation StartVoiceCall($input: SdkStartVoiceCallInput!) { sdkStartVoiceCall(input: $input) { sessionId agentId livekitUrl livekitToken roomName expiresAt }}`;
const variables = { input: { agentId: 7, clientId: "call-0042", },};
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", "X-Api-Key": process.env.WETEL_API_KEY ?? "", }, 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?.sdkStartVoiceCall, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// sdkStartVoiceCall: Start a live voice call with an agent and receive the LiveKit URL and join token for the caller.// Credential: API key (WETEL_API_KEY). PHP 7.3+ with the curl extension.$apiUrl = getenv('WETEL_API_URL') ?: 'https://api.wetel.dev/graphql';
$query = <<<'GRAPHQL'mutation StartVoiceCall($input: SdkStartVoiceCallInput!) { sdkStartVoiceCall(input: $input) { sessionId agentId livekitUrl livekitToken roomName expiresAt }}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "input": { "agentId": 7, "clientId": "call-0042" }}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', 'X-Api-Key: ' . getenv('WETEL_API_KEY'), ], 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']['sdkStartVoiceCall'], JSON_PRETTY_PRINT), PHP_EOL;// sdkStartVoiceCall: Start a live voice call with an agent and receive the LiveKit URL and join token for the caller.// Credential: API key (WETEL_API_KEY). .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 StartVoiceCall($input: SdkStartVoiceCallInput!) { sdkStartVoiceCall(input: $input) { sessionId agentId livekitUrl livekitToken roomName expiresAt } } """;
var variables = JsonNode.Parse(""" { "input": { "agentId": 7, "clientId": "call-0042" } } """);
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.Add("X-Api-Key", Environment.GetEnvironmentVariable("WETEL_API_KEY"));
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"]?["sdkStartVoiceCall"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# sdkStartVoiceCall: Start a live voice call with an agent and receive the LiveKit URL and join token for the caller.# Credential: API key (WETEL_API_KEY). 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 StartVoiceCall($input: SdkStartVoiceCallInput!) { sdkStartVoiceCall(input: $input) { sessionId agentId livekitUrl livekitToken roomName expiresAt }}"""
VARIABLES = { "input": { "agentId": 7, "clientId": "call-0042", },}
response = requests.post( API_URL, json={"query": QUERY, "variables": VARIABLES}, headers={ "x-huat-platform": "customer", "X-Api-Key": os.environ["WETEL_API_KEY"], }, 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"]["sdkStartVoiceCall"], indent=2))Responses
Success (HTTP 200)
{ "data": { "sdkStartVoiceCall": { "sessionId": 4212, "agentId": 7, "livekitUrl": "wss://voice.example.com", "livekitToken": "example-join-token-not-real", "roomName": "call-4212", "expiresAt": "2026-10-19T09:02:00.000Z" } }}UNAUTHENTICATED: Missing API key (HTTP 200)
{ "errors": [ { "message": "Missing X-Api-Key header", "locations": [ { "line": 2, "column": 3 } ], "path": [ "sdkStartVoiceCall" ], "extensions": { "code": "UNAUTHENTICATED", "originalError": { "message": "Missing X-Api-Key header", "error": "Unauthorized", "statusCode": 401 } } } ], "data": null}UNAUTHENTICATED: Unknown or revoked API key (HTTP 200)
{ "errors": [ { "message": "Invalid API key", "locations": [ { "line": 2, "column": 3 } ], "path": [ "sdkStartVoiceCall" ], "extensions": { "code": "UNAUTHENTICATED", "originalError": { "message": "Invalid API key", "error": "Unauthorized", "statusCode": 401 } } } ], "data": null}FORBIDDEN: Voice calls not enabled on this environment (HTTP 200)
{ "errors": [ { "message": "Voice calls are not enabled on this environment", "locations": [ { "line": 2, "column": 3 } ], "path": [ "sdkStartVoiceCall" ], "extensions": { "code": "FORBIDDEN", "originalError": { "message": "Voice calls are not enabled on this environment", "error": "Forbidden", "statusCode": 403 } } } ], "data": null}NOT_FOUND: Agent not found in your workspace (HTTP 200)
{ "errors": [ { "message": "Agent #7 not found", "locations": [ { "line": 2, "column": 3 } ], "path": [ "sdkStartVoiceCall" ], "extensions": { "code": "NOT_FOUND", "status": 404, "originalError": { "message": "Agent #7 not found", "error": "Not Found", "statusCode": 404 } } } ], "data": null}sdkSendMessage
Section titled “sdkSendMessage”Send one user message into the session. Returns true at once; the reply follows asynchronously.
- Kind: mutation
- Credential: Session token (avatarToken):
Authorization: Bearer <avatarToken>, returned bysdkStartfor one session - Available in: sandbox and production
trueis an acknowledgement, not the reply. Read the reply by polling the session’s messages or from thesessionEventssubscription.textholds up to 100,000 characters. It may be empty only whenattachmentshas at least one entry.turnContextis an optional flat JSON object for this turn only (workflows read it asturn.context).- The token must belong to the same session as
input.sessionId.
Request
mutation SendMessage($input: SdkSendMessageInput!) { sdkSendMessage(input: $input)}{ "input": { "sessionId": 4211, "text": "My laptop cannot connect to the office VPN since this morning." }}Input fields
SdkSendMessageInput
| Field | Type | Required | Description |
|---|---|---|---|
attachments | [MessageAttachmentInput!] | no | Optional files sent with this message (at most 10). Validated here (absolute http(s) URL, no embedded credentials, sanitised filename, MIME and kind inferred when omitted) but NOT fetched: a workflow node downloads them when it needs them. In workflow context they appear as ctx.attachments (list of {url, mime, filename, kind}), ctx.attachmentImageUrls (the IMAGE urls, usable as imageUrlsVar), and the first entry fills ctx.attachment, ctx.attachmentUrl, ctx.attachmentMime and ctx.attachmentFilename. When present they take precedence over any attachment marker written inside the text. text may be empty when at least one attachment is sent. |
clientTurnId | String | no | Optional opaque correlation token of your own (at most 255 characters). Not stored or interpreted; echoed back verbatim on every AiResponseEvent of this turn’s reply so you can match a reply to the call that caused it. |
sessionId | Int! | yes | Id of the session (sdkStart.sessionId). Must be the session the avatarToken was issued for, otherwise the call is refused. |
text | String! | yes | The user message, up to 100,000 characters. May be an empty string only when attachments has at least one entry. |
turnContext | JSON | no | Optional per-turn data as a flat JSON object (pass it through variables, not an inline literal). At most 32 keys matching [A-Za-z][A-Za-z0-9_]*, values string (up to 4096 characters), number, boolean or null, 8 KB in total; runner-reserved names such as session, channel or userMessage are refused. Never put secrets here. Appears in workflow context as ctx.turn.context (for example {{turn.context.orderId}}); a string trigger key also sets ctx.channel.trigger. Applies to this turn only. |
MessageAttachmentInput
| Field | Type | Required | Description |
|---|---|---|---|
filename | String | no | Original filename, at most 255 characters. Quotes, square brackets and control characters are removed. |
kind | AttachmentKind | no | Kind of file. When omitted it is inferred from the MIME type (image/*, audio/*, video/*, PDF and Office or text types are DOCUMENT, anything else OTHER). |
mime | String | no | MIME type, at most 100 characters (for example image/png). When omitted it is inferred from the filename, then from the URL path extension. |
url | String! | yes | Absolute http(s) URL of the file, at most 2048 characters, without user:password credentials. Treat a signed URL as a credential: it is redacted wherever messages are read back. |
Allowed values
AttachmentKind:AUDIO,DOCUMENT,IMAGE,OTHER,VIDEO
Code samples
#!/usr/bin/env bash# sdkSendMessage: Send one user message into the session. Returns true at once; the reply follows asynchronously.# Credential: Session token (avatarToken) (WETEL_AVATAR_TOKEN).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_AVATAR_TOKEN" \ --data-binary @- <<'JSON'{ "query": "mutation SendMessage($input:SdkSendMessageInput!){sdkSendMessage(input:$input)}", "variables": { "input": { "sessionId": 4211, "text": "My laptop cannot connect to the office VPN since this morning." } }}JSON// sdkSendMessage: Send one user message into the session. Returns true at once; the reply follows asynchronously.// Credential: Session token (avatarToken) (WETEL_AVATAR_TOKEN). Node 18+ (built-in fetch).const API_URL = process.env.WETEL_API_URL ?? "https://api.wetel.dev/graphql";
const query = `mutation SendMessage($input: SdkSendMessageInput!) { sdkSendMessage(input: $input)}`;
const variables = { input: { sessionId: 4211, text: "My laptop cannot connect to the office VPN since this morning.", },};
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_AVATAR_TOKEN ?? ""}`, }, 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?.sdkSendMessage, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// sdkSendMessage: Send one user message into the session. Returns true at once; the reply follows asynchronously.// Credential: Session token (avatarToken) (WETEL_AVATAR_TOKEN). PHP 7.3+ with the curl extension.$apiUrl = getenv('WETEL_API_URL') ?: 'https://api.wetel.dev/graphql';
$query = <<<'GRAPHQL'mutation SendMessage($input: SdkSendMessageInput!) { sdkSendMessage(input: $input)}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "input": { "sessionId": 4211, "text": "My laptop cannot connect to the office VPN since this morning." }}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_AVATAR_TOKEN'), ], 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']['sdkSendMessage'], JSON_PRETTY_PRINT), PHP_EOL;// sdkSendMessage: Send one user message into the session. Returns true at once; the reply follows asynchronously.// Credential: Session token (avatarToken) (WETEL_AVATAR_TOKEN). .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 SendMessage($input: SdkSendMessageInput!) { sdkSendMessage(input: $input) } """;
var variables = JsonNode.Parse(""" { "input": { "sessionId": 4211, "text": "My laptop cannot connect to the office VPN since this morning." } } """);
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_AVATAR_TOKEN"));
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"]?["sdkSendMessage"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# sdkSendMessage: Send one user message into the session. Returns true at once; the reply follows asynchronously.# Credential: Session token (avatarToken) (WETEL_AVATAR_TOKEN). 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 SendMessage($input: SdkSendMessageInput!) { sdkSendMessage(input: $input)}"""
VARIABLES = { "input": { "sessionId": 4211, "text": "My laptop cannot connect to the office VPN since this morning.", },}
response = requests.post( API_URL, json={"query": QUERY, "variables": VARIABLES}, headers={ "x-huat-platform": "customer", "Authorization": "Bearer " + os.environ["WETEL_AVATAR_TOKEN"], }, 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"]["sdkSendMessage"], indent=2))Responses
Success (acknowledgement only) (HTTP 200)
{ "data": { "sdkSendMessage": true }}FORBIDDEN: Token for a different session (HTTP 200)
{ "errors": [ { "message": "This avatar token is not valid for the requested session", "locations": [ { "line": 2, "column": 3 } ], "path": [ "sdkSendMessage" ], "extensions": { "code": "FORBIDDEN", "originalError": { "message": "This avatar token is not valid for the requested session", "error": "Forbidden", "statusCode": 403 } } } ], "data": null}UNAUTHENTICATED: Expired or invalid session token (HTTP 200)
{ "errors": [ { "message": "Invalid or expired avatar token", "locations": [ { "line": 2, "column": 3 } ], "path": [ "sdkSendMessage" ], "extensions": { "code": "UNAUTHENTICATED", "originalError": { "message": "Invalid or expired avatar token", "error": "Unauthorized", "statusCode": 401 } } } ], "data": null}BAD_REQUEST: Session already ended (HTTP 200)
{ "errors": [ { "message": "Session 4211 is not active (status: ended)", "locations": [ { "line": 2, "column": 3 } ], "path": [ "sdkSendMessage" ], "extensions": { "code": "BAD_REQUEST", "originalError": { "message": "Session 4211 is not active (status: ended)", "error": "Bad Request", "statusCode": 400 } } } ], "data": null}UNAUTHENTICATED: No Authorization header (HTTP 200)
{ "errors": [ { "message": "Missing or malformed Authorization header", "locations": [ { "line": 2, "column": 3 } ], "path": [ "sdkSendMessage" ], "extensions": { "code": "UNAUTHENTICATED", "originalError": { "message": "Missing or malformed Authorization header", "error": "Unauthorized", "statusCode": 401 } } } ], "data": null}sessionsByExternalCustomerId
Section titled “sessionsByExternalCustomerId”List the sessions started with one clientId, with their messages. The API-key way to poll for a reply.
- Kind: query
- Credential: API key:
X-Api-Key: sk_..., kept on your server only - Available in: sandbox and production
externalCustomerIdis theclientIdyou passed tosdkStart. Sessions come oldest first;firstdefaults to 20.- Messages come oldest first. A reply is a
MODELmessage that follows yourUSERmessage. Poll every one to two seconds with a timeout; a reply usually takes a few seconds and can take longer when the workflow calls tools. - Do not select every field of every message: the API refuses a query whose complexity is above 50 fields.
Request
query PollSession($externalCustomerId: String!, $first: Int) { sessionsByExternalCustomerId(externalCustomerId: $externalCustomerId, first: $first) { totalCount edges { node { id status messages { id role text turnIndex createdAt } } } }}{ "externalCustomerId": "ticket-1001", "first": 1}Allowed values
SessionStatus:ACTIVE,ENDED,EXPIRED,PENDINGMessageRole:MODEL,USER
Code samples
#!/usr/bin/env bash# sessionsByExternalCustomerId: List the sessions started with one clientId, with their messages. The API-key way to poll for a reply.# Credential: API key (WETEL_API_KEY).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 "X-Api-Key: $WETEL_API_KEY" \ --data-binary @- <<'JSON'{ "query": "query PollSession($externalCustomerId:String!$first:Int){sessionsByExternalCustomerId(externalCustomerId:$externalCustomerId first:$first){totalCount edges{node{id status messages{id role text turnIndex createdAt}}}}}", "variables": { "externalCustomerId": "ticket-1001", "first": 1 }}JSON// sessionsByExternalCustomerId: List the sessions started with one clientId, with their messages. The API-key way to poll for a reply.// Credential: API key (WETEL_API_KEY). Node 18+ (built-in fetch).const API_URL = process.env.WETEL_API_URL ?? "https://api.wetel.dev/graphql";
const query = `query PollSession($externalCustomerId: String!, $first: Int) { sessionsByExternalCustomerId(externalCustomerId: $externalCustomerId, first: $first) { totalCount edges { node { id status messages { id role text turnIndex createdAt } } } }}`;
const variables = { externalCustomerId: "ticket-1001", first: 1,};
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", "X-Api-Key": process.env.WETEL_API_KEY ?? "", }, 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?.sessionsByExternalCustomerId, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// sessionsByExternalCustomerId: List the sessions started with one clientId, with their messages. The API-key way to poll for a reply.// Credential: API key (WETEL_API_KEY). PHP 7.3+ with the curl extension.$apiUrl = getenv('WETEL_API_URL') ?: 'https://api.wetel.dev/graphql';
$query = <<<'GRAPHQL'query PollSession($externalCustomerId: String!, $first: Int) { sessionsByExternalCustomerId(externalCustomerId: $externalCustomerId, first: $first) { totalCount edges { node { id status messages { id role text turnIndex createdAt } } } }}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "externalCustomerId": "ticket-1001", "first": 1}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', 'X-Api-Key: ' . getenv('WETEL_API_KEY'), ], 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']['sessionsByExternalCustomerId'], JSON_PRETTY_PRINT), PHP_EOL;// sessionsByExternalCustomerId: List the sessions started with one clientId, with their messages. The API-key way to poll for a reply.// Credential: API key (WETEL_API_KEY). .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 PollSession($externalCustomerId: String!, $first: Int) { sessionsByExternalCustomerId(externalCustomerId: $externalCustomerId, first: $first) { totalCount edges { node { id status messages { id role text turnIndex createdAt } } } } } """;
var variables = JsonNode.Parse(""" { "externalCustomerId": "ticket-1001", "first": 1 } """);
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.Add("X-Api-Key", Environment.GetEnvironmentVariable("WETEL_API_KEY"));
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"]?["sessionsByExternalCustomerId"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# sessionsByExternalCustomerId: List the sessions started with one clientId, with their messages. The API-key way to poll for a reply.# Credential: API key (WETEL_API_KEY). 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 PollSession($externalCustomerId: String!, $first: Int) { sessionsByExternalCustomerId(externalCustomerId: $externalCustomerId, first: $first) { totalCount edges { node { id status messages { id role text turnIndex createdAt } } } }}"""
VARIABLES = { "externalCustomerId": "ticket-1001", "first": 1,}
response = requests.post( API_URL, json={"query": QUERY, "variables": VARIABLES}, headers={ "x-huat-platform": "customer", "X-Api-Key": os.environ["WETEL_API_KEY"], }, 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"]["sessionsByExternalCustomerId"], indent=2))Responses
Reply has arrived (HTTP 200)
{ "data": { "sessionsByExternalCustomerId": { "totalCount": 1, "edges": [ { "node": { "id": 4211, "status": "ACTIVE", "messages": [ { "id": 90311, "role": "USER", "text": "My laptop cannot connect to the office VPN since this morning.", "turnIndex": 0, "createdAt": "2026-10-12T03:15:02.114Z" }, { "id": 90312, "role": "MODEL", "text": "Sorry to hear that. Which operating system is the laptop running, and do you see an error message?", "turnIndex": 1, "createdAt": "2026-10-12T03:15:05.871Z" } ] } } ] } }}UNAUTHENTICATED: API key missing (HTTP 200)
{ "errors": [ { "message": "Missing X-Api-Key header", "locations": [ { "line": 2, "column": 3 } ], "path": [ "sessionsByExternalCustomerId" ], "extensions": { "code": "UNAUTHENTICATED", "originalError": { "message": "Missing X-Api-Key header", "error": "Unauthorized", "statusCode": 401 } } } ], "data": null}session
Section titled “session”Read one session and its full message history by id (dashboard credential).
- Kind: query
- Credential: Dashboard JWT:
Authorization: Bearer <jwt>, fromlogin(a person or a one-off script) - Available in: sandbox and production
- Takes a dashboard JWT. An API key or a session token is refused with
Unauthorized; with an API key, poll withsessionsByExternalCustomerIdinstead. - Returns
null(not an error) for an id that does not exist in your workspace.
Request
query ReadSession($id: ID!) { session(id: $id) { id status startedAt endedAt messages { id role text turnIndex } }}{ "id": "4211"}Allowed values
SessionStatus:ACTIVE,ENDED,EXPIRED,PENDINGMessageRole:MODEL,USER
Code samples
#!/usr/bin/env bash# session: Read one session and its full message history by id (dashboard credential).# 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 ReadSession($id:ID!){session(id:$id){id status startedAt endedAt messages{id role text turnIndex}}}", "variables": { "id": "4211" }}JSON// session: Read one session and its full message history by id (dashboard credential).// 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 ReadSession($id: ID!) { session(id: $id) { id status startedAt endedAt messages { id role text turnIndex } }}`;
const variables = { id: "4211",};
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?.session, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// session: Read one session and its full message history by id (dashboard credential).// 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 ReadSession($id: ID!) { session(id: $id) { id status startedAt endedAt messages { id role text turnIndex } }}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "id": "4211"}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']['session'], JSON_PRETTY_PRINT), PHP_EOL;// session: Read one session and its full message history by id (dashboard credential).// 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 ReadSession($id: ID!) { session(id: $id) { id status startedAt endedAt messages { id role text turnIndex } } } """;
var variables = JsonNode.Parse(""" { "id": "4211" } """);
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"]?["session"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# session: Read one session and its full message history by id (dashboard credential).# 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 ReadSession($id: ID!) { session(id: $id) { id status startedAt endedAt messages { id role text turnIndex } }}"""
VARIABLES = { "id": "4211",}
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"]["session"], indent=2))Responses
Success (HTTP 200)
{ "data": { "session": { "id": 4211, "status": "ENDED", "startedAt": "2026-10-12T03:15:01.502Z", "endedAt": "2026-10-12T03:18:40.020Z", "messages": [ { "id": 90311, "role": "USER", "text": "My laptop cannot connect to the office VPN since this morning.", "turnIndex": 0 }, { "id": 90312, "role": "MODEL", "text": "Sorry to hear that. Which operating system is the laptop running, and do you see an error message?", "turnIndex": 1 } ] } }}Not in your workspace (HTTP 200)
{ "data": { "session": null }}UNAUTHENTICATED: Called with an API key instead of a JWT (HTTP 200)
{ "errors": [ { "message": "Unauthorized", "locations": [ { "line": 2, "column": 3 } ], "path": [ "session" ], "extensions": { "code": "UNAUTHENTICATED", "originalError": { "message": "Unauthorized", "statusCode": 401 } } } ], "data": { "session": null }}sdkEndSession
Section titled “sdkEndSession”End the session. The session token stops working for new messages.
- Kind: mutation
- Credential: Session token (avatarToken):
Authorization: Bearer <avatarToken>, returned bysdkStartfor one session - Available in: sandbox and production
evaluationis generated asynchronously and only for conversations with at least two turns, so it is oftennullin this response.
Request
mutation EndSession($input: SdkEndSessionInput!) { sdkEndSession(input: $input) { sessionId durationSeconds evaluation { score summary } }}{ "input": { "sessionId": 4211 }}Input fields
SdkEndSessionInput
| Field | Type | Required | Description |
|---|---|---|---|
sessionId | Int! | yes | Id of the session to end (sdkStart.sessionId). Must be the session the avatarToken was issued for. |
Code samples
#!/usr/bin/env bash# sdkEndSession: End the session. The session token stops working for new messages.# Credential: Session token (avatarToken) (WETEL_AVATAR_TOKEN).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_AVATAR_TOKEN" \ --data-binary @- <<'JSON'{ "query": "mutation EndSession($input:SdkEndSessionInput!){sdkEndSession(input:$input){sessionId durationSeconds evaluation{score summary}}}", "variables": { "input": { "sessionId": 4211 } }}JSON// sdkEndSession: End the session. The session token stops working for new messages.// Credential: Session token (avatarToken) (WETEL_AVATAR_TOKEN). Node 18+ (built-in fetch).const API_URL = process.env.WETEL_API_URL ?? "https://api.wetel.dev/graphql";
const query = `mutation EndSession($input: SdkEndSessionInput!) { sdkEndSession(input: $input) { sessionId durationSeconds evaluation { score summary } }}`;
const variables = { input: { sessionId: 4211, },};
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_AVATAR_TOKEN ?? ""}`, }, 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?.sdkEndSession, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// sdkEndSession: End the session. The session token stops working for new messages.// Credential: Session token (avatarToken) (WETEL_AVATAR_TOKEN). PHP 7.3+ with the curl extension.$apiUrl = getenv('WETEL_API_URL') ?: 'https://api.wetel.dev/graphql';
$query = <<<'GRAPHQL'mutation EndSession($input: SdkEndSessionInput!) { sdkEndSession(input: $input) { sessionId durationSeconds evaluation { score summary } }}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "input": { "sessionId": 4211 }}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_AVATAR_TOKEN'), ], 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']['sdkEndSession'], JSON_PRETTY_PRINT), PHP_EOL;// sdkEndSession: End the session. The session token stops working for new messages.// Credential: Session token (avatarToken) (WETEL_AVATAR_TOKEN). .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 EndSession($input: SdkEndSessionInput!) { sdkEndSession(input: $input) { sessionId durationSeconds evaluation { score summary } } } """;
var variables = JsonNode.Parse(""" { "input": { "sessionId": 4211 } } """);
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_AVATAR_TOKEN"));
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"]?["sdkEndSession"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# sdkEndSession: End the session. The session token stops working for new messages.# Credential: Session token (avatarToken) (WETEL_AVATAR_TOKEN). 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 EndSession($input: SdkEndSessionInput!) { sdkEndSession(input: $input) { sessionId durationSeconds evaluation { score summary } }}"""
VARIABLES = { "input": { "sessionId": 4211, },}
response = requests.post( API_URL, json={"query": QUERY, "variables": VARIABLES}, headers={ "x-huat-platform": "customer", "Authorization": "Bearer " + os.environ["WETEL_AVATAR_TOKEN"], }, 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"]["sdkEndSession"], indent=2))Responses
Success (HTTP 200)
{ "data": { "sdkEndSession": { "sessionId": 4211, "durationSeconds": 219, "evaluation": null } }}FORBIDDEN: Token for a different session (HTTP 200)
{ "errors": [ { "message": "This avatar token is not valid for the requested session", "locations": [ { "line": 2, "column": 3 } ], "path": [ "sdkEndSession" ], "extensions": { "code": "FORBIDDEN", "originalError": { "message": "This avatar token is not valid for the requested session", "error": "Forbidden", "statusCode": 403 } } } ], "data": null}evaluation
Section titled “evaluation”Read the scored evaluation of an ended session (score, recommendation, summary, strengths, weaknesses).
- Kind: query
- Credential: Dashboard JWT:
Authorization: Bearer <jwt>, fromlogin(a person or a one-off script) - Available in: sandbox and production
- Returns
nulluntil the session has ended and the background evaluation has finished, and staysnullfor a session that was not evaluated (fewer than two turns, or evaluation not enabled for it). Poll with a back-off aftersdkEndSession. scoreis 0 to 100. For a support agentrecommendationis one ofresolved,escalateorfollow-up(n/awhen none fits); an agent with its own evaluation labels returns those.transcriptis also available on this type; it holds the whole conversation, so select it only if you need it.
Request
query SessionEvaluation($sessionId: Int!) { evaluation(sessionId: $sessionId) { id sessionId score recommendation summary strengths weaknesses generatedAt }}{ "sessionId": 4211}Code samples
#!/usr/bin/env bash# evaluation: Read the scored evaluation of an ended session (score, recommendation, summary, strengths, weaknesses).# 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 SessionEvaluation($sessionId:Int!){evaluation(sessionId:$sessionId){id sessionId score recommendation summary strengths weaknesses generatedAt}}", "variables": { "sessionId": 4211 }}JSON// evaluation: Read the scored evaluation of an ended session (score, recommendation, summary, strengths, weaknesses).// 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 SessionEvaluation($sessionId: Int!) { evaluation(sessionId: $sessionId) { id sessionId score recommendation summary strengths weaknesses generatedAt }}`;
const variables = { sessionId: 4211,};
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?.evaluation, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// evaluation: Read the scored evaluation of an ended session (score, recommendation, summary, strengths, weaknesses).// 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 SessionEvaluation($sessionId: Int!) { evaluation(sessionId: $sessionId) { id sessionId score recommendation summary strengths weaknesses generatedAt }}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "sessionId": 4211}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']['evaluation'], JSON_PRETTY_PRINT), PHP_EOL;// evaluation: Read the scored evaluation of an ended session (score, recommendation, summary, strengths, weaknesses).// 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 SessionEvaluation($sessionId: Int!) { evaluation(sessionId: $sessionId) { id sessionId score recommendation summary strengths weaknesses generatedAt } } """;
var variables = JsonNode.Parse(""" { "sessionId": 4211 } """);
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"]?["evaluation"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# evaluation: Read the scored evaluation of an ended session (score, recommendation, summary, strengths, weaknesses).# 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 SessionEvaluation($sessionId: Int!) { evaluation(sessionId: $sessionId) { id sessionId score recommendation summary strengths weaknesses generatedAt }}"""
VARIABLES = { "sessionId": 4211,}
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"]["evaluation"], indent=2))Responses
Success (HTTP 200)
{ "data": { "evaluation": { "id": 1532, "sessionId": 4211, "score": 82, "recommendation": "resolved", "summary": "The user could not connect to the VPN. The agent identified an expired client certificate and walked the user through renewing it; the user confirmed it works.", "strengths": [ "Asked for the operating system and error before suggesting a fix" ], "weaknesses": [ "Did not offer to open a ticket in case the fix failed" ], "generatedAt": "2026-10-12T03:18:52.114Z" } }}Not evaluated yet (or never) (HTTP 200)
{ "data": { "evaluation": null }}UNAUTHENTICATED: Called with an API key instead of a JWT (HTTP 200)
{ "errors": [ { "message": "Unauthorized", "locations": [ { "line": 2, "column": 3 } ], "path": [ "evaluation" ], "extensions": { "code": "UNAUTHENTICATED", "originalError": { "message": "Unauthorized", "statusCode": 401 } } } ], "data": { "evaluation": null }}refreshAvatarToken
Section titled “refreshAvatarToken”Exchange a session token that is about to expire (or expired in the last 10 minutes) for a fresh one.
- Kind: mutation
- Credential: No credential header: The credential travels inside the input (see the operation)
- Available in: sandbox and production
- A session token lives about one hour. The token goes in
input.avatarToken, not in an Authorization header, because an expired token must be accepted here. - The session must still be active. Limited to 10 calls per minute per client IP.
- An open
sessionEventsWebSocket keeps the old token; close it and reopen it with the new one.
Request
mutation RefreshToken($input: RefreshAvatarTokenInput!) { refreshAvatarToken(input: $input) { sessionId avatarToken expiresInMs }}{ "input": { "avatarToken": "example-avatar-token-not-real" }}Input fields
RefreshAvatarTokenInput
| Field | Type | Required | Description |
|---|---|---|---|
avatarToken | String! | yes | The current avatarToken, still valid or expired for at most the grace window (10 minutes by default). At most 4096 characters. |
Code samples
#!/usr/bin/env bash# refreshAvatarToken: Exchange a session token that is about to expire (or expired in the last 10 minutes) for a fresh one.# Credential: No credential header.API_URL="${WETEL_API_URL:-https://api.wetel.dev/graphql}"
curl -sS "$API_URL" \ -H "Content-Type: application/json" \ -H "x-huat-platform: customer" \ --data-binary @- <<'JSON'{ "query": "mutation RefreshToken($input:RefreshAvatarTokenInput!){refreshAvatarToken(input:$input){sessionId avatarToken expiresInMs}}", "variables": { "input": { "avatarToken": "example-avatar-token-not-real" } }}JSON// refreshAvatarToken: Exchange a session token that is about to expire (or expired in the last 10 minutes) for a fresh one.// Credential: No credential header. Node 18+ (built-in fetch).const API_URL = process.env.WETEL_API_URL ?? "https://api.wetel.dev/graphql";
const query = `mutation RefreshToken($input: RefreshAvatarTokenInput!) { refreshAvatarToken(input: $input) { sessionId avatarToken expiresInMs }}`;
const variables = { input: { avatarToken: "example-avatar-token-not-real", },};
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", }, 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?.refreshAvatarToken, null, 2));}
main().catch(err => { console.error(err); process.exit(1);});
export {};<?php// refreshAvatarToken: Exchange a session token that is about to expire (or expired in the last 10 minutes) for a fresh one.// Credential: No credential header. PHP 7.3+ with the curl extension.$apiUrl = getenv('WETEL_API_URL') ?: 'https://api.wetel.dev/graphql';
$query = <<<'GRAPHQL'mutation RefreshToken($input: RefreshAvatarTokenInput!) { refreshAvatarToken(input: $input) { sessionId avatarToken expiresInMs }}GRAPHQL;
$variables = json_decode(<<<'JSON'{ "input": { "avatarToken": "example-avatar-token-not-real" }}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', ], 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']['refreshAvatarToken'], JSON_PRETTY_PRINT), PHP_EOL;// refreshAvatarToken: Exchange a session token that is about to expire (or expired in the last 10 minutes) for a fresh one.// Credential: No credential header. .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 RefreshToken($input: RefreshAvatarTokenInput!) { refreshAvatarToken(input: $input) { sessionId avatarToken expiresInMs } } """;
var variables = JsonNode.Parse(""" { "input": { "avatarToken": "example-avatar-token-not-real" } } """);
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");
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"]?["refreshAvatarToken"]?.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));return 0;# refreshAvatarToken: Exchange a session token that is about to expire (or expired in the last 10 minutes) for a fresh one.# Credential: No credential header. 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 RefreshToken($input: RefreshAvatarTokenInput!) { refreshAvatarToken(input: $input) { sessionId avatarToken expiresInMs }}"""
VARIABLES = { "input": { "avatarToken": "example-avatar-token-not-real", },}
response = requests.post( API_URL, json={"query": QUERY, "variables": VARIABLES}, headers={ "x-huat-platform": "customer", }, 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"]["refreshAvatarToken"], indent=2))Responses
Success (HTTP 200)
{ "data": { "refreshAvatarToken": { "sessionId": 4211, "avatarToken": "example-refreshed-token-not-real", "expiresInMs": 3600000 } }}UNAUTHENTICATED: Expired beyond the grace window, or invalid (HTTP 200)
{ "errors": [ { "message": "Invalid or expired avatar token", "locations": [ { "line": 2, "column": 3 } ], "path": [ "refreshAvatarToken" ], "extensions": { "code": "UNAUTHENTICATED", "originalError": { "message": "Invalid or expired avatar token", "error": "Unauthorized", "statusCode": 401 } } } ], "data": null}BAD_REQUEST: Session already ended (HTTP 200)
{ "errors": [ { "message": "Session 4211 is not active (status: ended)", "locations": [ { "line": 2, "column": 3 } ], "path": [ "refreshAvatarToken" ], "extensions": { "code": "BAD_REQUEST", "originalError": { "message": "Session 4211 is not active (status: ended)", "error": "Bad Request", "statusCode": 400 } } } ], "data": null}