Skip to content

AI Coding Assistant Skills

This is a self-contained “skill” file for AI coding assistants that support the emerging SKILL.md convention (Claude Code being the primary one today). Drop it into your assistant’s skills folder and it’ll have working knowledge of Wetel’s API shape, auth model, and common gotchas — without you having to paste this whole site into every conversation.

No install step, no npm package, no SDK required — this is a plain markdown file.

For exact field names, enum values, code samples and workflow checks on demand, add the read-only Docs MCP Server alongside this skill.

Save the content below as wetel-integration/SKILL.md in your skills directory:

  • Claude Code, personal (all projects): ~/.claude/skills/wetel-integration/SKILL.md
  • Claude Code, this project only: .claude/skills/wetel-integration/SKILL.md at your repo root

Then just ask your assistant to build something against Wetel — it’ll pick up the skill automatically when the request matches.

---
name: wetel-integration
description: Use when building an integration with Wetel, an enterprise AI workforce platform (AI employees for defined roles) — creating/configuring agents, writing workflow graphs, calling the GraphQL API, or debugging a Wetel integration. Covers auth, the core session flow, the workflow node model, safety rules and common mistakes.
---
# Building on Wetel
Wetel is an enterprise AI workforce platform: a business deploys AI employees for defined roles that follow its procedures, work inside its own systems, and stay supervised by people. An AI employee is an **Agent** (a persona) driven by a **Workflow** (a graph of nodes/edges). Integrators talk to it over one GraphQL endpoint: `https://api.wetel.dev/graphql`.
Full reference: https://wetel.dev/docs/ — treat this skill as a fast-start summary, not the complete spec. Check the live docs for anything not covered here, especially exact field names before writing real code (the API reference there is generated from the real schema, not hand-maintained prose).
## Every single request needs this header
```
x-huat-platform: customer
```
Missing it produces an error that looks unrelated to auth ("Platform is not specified!"). This is the single most common first-integration mistake — check it first if anything fails mysteriously.
## Auth — three credential types, don't mix them up
| Credential | Get it via | Unlocks |
| ----------------------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
| JWT | `login` mutation | Full dashboard/builder access (agents, workflows, admin) |
| API key | `generateApiKey` (needs a JWT first) | `sdkStart` and the other runtime operations in [Using the API key](/docs/auth/#using-the-api-key); not agent or workflow editing |
| Session token (`avatarToken`) | Returned by `sdkStart` | `sdkSendMessage`, `sdkEndSession`, the events subscription |
There is no API-key path into agent/workflow CRUD — that always needs a JWT. Runtime integrators almost always want the API-key → session-token flow, not JWT.
## The core runtime flow
```graphql
mutation {
sdkStart(input: { agentId: 1, clientId: "user-123" }) {
sessionId
avatarToken
}
}
# → then, using avatarToken as the Bearer token:
mutation {
sdkSendMessage(input: { sessionId: 1, text: "Hello" })
}
```
`sdkSendMessage` returns immediately — it's an ACK, not the reply. The actual reply streams asynchronously over a GraphQL subscription (`sessionEvents(sessionId)`), watching for `AiResponseEvent`. This trips up almost every first integration — don't poll or assume a synchronous reply.
**Subscription gotcha:** `sessionEvents` returns a union type. You MUST explicitly select every member type with `... on TypeName { fields }` — Apollo silently drops any type you don't select, with zero error. If events seem to vanish, check your selection set first.
## Workflows — the node/edge model
A workflow is `{ nodes: [...], edges: [...] }`. Node types (lowercase): `start`, `llm`, `condition`, `router`, `response`, `webhook`, `tool`, `action`, `await_reply`, `approval`, `sub_agent`, `end_session`.
Two gotchas that will burn you immediately:
1. **`createWorkflow` returns an EMPTY graph** — no `nodes`/`edges` input field exists on it. Add the real graph via a second `updateWorkflow(id, { nodes, edges })` call.
2. **A workflow does nothing until `publishWorkflow(id)` is called explicitly** — even with a perfectly valid graph and the agent's `workflowId` correctly set, an unpublished workflow silently produces a generic failure reply for every message. If a workflow "does nothing, no error," check `isPublished` first.
`nodes`/`edges` are `GraphQLJSON` scalars — always send them via a GraphQL `variables` block, never inline in the query string (inline literal JSON syntax doesn't parse).
**`condition` nodes use `expr-eval`, NOT JavaScript** — `==`/`!=` not `===`/`!==`, `and`/`or`/`not` not `&&`/`||`/`!`, bare flat variable names only (no `context.x` property access). Outgoing edges must be labeled exactly `'true'`/`'false'` (lowercase strings) or that branch never fires.
## Rules the live docs depend on (follow these when you generate or edit a workflow)
- **Number gates:** a value from `extractFirstInteger` is a **number** (`0` when nothing was found). Gate with `orderId > 0`, never `!= '0'` (a condition does not coerce types). Expressions only read variables: `=` assignment and function definitions are refused in `condition`, `switch` and `set_variable`. Extraction and classification options (`extractFirstInteger`, `extractAlphanumericId`) only work with `"streamOutput": false` on the `llm` node, because a streaming node ignores them.
- **Success and failure paths:** labelled `success`/`failure` edges exist on `tool`, `action`, `webhook`, `llm`, `sub_agent`, `set_variable` and `pdf_export` steps; after a handled failure, show users `{{_lastNodeErrorPublic}}` and branch on `_lastNodeErrorCode`, never put the raw `_lastNodeError` in a reply; only an error from that step itself takes `failure`, a `failure` edge needs a `success` edge, and a `sub_agent` reports failed targets in its `resultVar` instead. `await_reply` has no expired edge: when a wait runs out, the next message starts a fresh run.
- **Approval is a step you add, not a default.** An `approval` node needs all three labelled edges (`approved`, `rejected`, `expired`); only workspace owners decide; notification is in-app only (no email). Nothing pauses for a person unless the graph contains that step.
- **Draft tests:** `testDraft: true` runs the current draft instead of the published graph. Since 2026-10-24 (production and staging) a draft test runs in mock mode by default: `tool`, `action`, `webhook` and `pdf_export` steps return mock results instead of calling out, LLM steps still call the model, and only the workspace owner can ask for `draftSideEffects: LIVE`. See [Testing a Workflow Safely](/docs/workflows/testing/).
- **Reserved names:** a node's `outputVar`/`saveAs` cannot be `session`, `attachment`, `attachmentUrl`, `attachmentMime`, `attachmentFilename`, `channel` or `clientTurnId`.
- **No secrets in templates.** Rendered requests appear in run records; masking of recognisable secrets is best-effort. Use a custom action's credential setting, never a raw key in `argsTemplate`/`bodyTemplate`.
- **What an API key can do:** `sdkStart`, `runWorkflowTask`/`workflowTaskOutcome`, `embed`, `generateText`/`startTextGeneration` and the other runtime operations in [Using the API key](/docs/auth/#using-the-api-key). It cannot create or edit agents or workflows.
- **Limits:** each plan has a monthly spend ceiling and there are per-address rate limits (a backend serving many customers shares one count). See [Pricing](/docs/pricing/) and [Rate limiting](/docs/reference/rate-limiting/).
- **Do not invent fields.** Read the live API reference and the node configuration schema before writing a node config; a GraphQL input with a field the schema does not define is rejected.
## How you (the assistant) should behave
- Never print, log or commit an API key, token or secret. Keys live in server-side environment variables, not in prompts, client code or the repository.
- Ask the user before publishing a workflow, generating or revoking a key, or changing production data. Prefer a test workspace.
- Do not tell the user a capability exists if the docs say it is not built: phone calls, approval on by default, a dedicated-database option, security certifications, approval emails, memory across conversations, event-based triggers. Check the [Roadmap](/docs/roadmap/) and the [Changelog](/docs/changelog/) for the current state.
- After you generate a workflow: save it with `updateWorkflow`, publish it, test it as a draft, then read the run record (`workflowRuns(sessionId)`) before telling the user it works.
## Common mistakes (check these before deep-diving into a bug)
- Missing `x-huat-platform: customer` header (see above).
- Treating `sdkSendMessage`'s return as the AI's reply — it's not, subscribe instead.
- Forgetting union-type selections on the events subscription.
- Building a workflow via `createWorkflow` and expecting it to accept the graph directly — it doesn't, use `updateWorkflow`.
- Forgetting to `publishWorkflow` after editing an existing one.
- Writing `condition` node expressions as real JavaScript.
- Putting an API key in client-side/browser code — proxy it through your own backend instead.
- Gating on `orderId != '0'` or omitting `"streamOutput": false` on an extraction step.
- Assuming an approval pause exists by default, or that an API key can edit agents or workflows.
## Where to look next
- Full GraphQL field reference (every query/mutation, real request/response examples): https://wetel.dev/docs/api-reference/overview/
- Workflow node-by-node reference + worked full-graph examples: https://wetel.dev/docs/workflows/overview/ and https://wetel.dev/docs/workflows/examples/
- Troubleshooting / debugging a failed workflow run: https://wetel.dev/docs/troubleshooting/

This skill is a hand-maintained summary — if Wetel’s API changes in a way that would make something above wrong, the live docs are always the source of truth. Check the Changelog if something here seems out of date.