LLM API
This content is not available in your language yet.
Wetel already sits in front of multiple LLM providers (Bedrock Mantle, WebbyxAI/Claude Haiku, Gemini) for its own agent/workflow traffic. This page documents the two operations that let you call an LLM directly, as a simple middleman — you send a prompt, Wetel routes it to a provider and bills the call, you get a completion back. No session, no agent, no workflow required, and nothing to set up on your own account with AWS, Google, or any other model vendor.
This is deliberately the simplest possible version of “bring your model calls to Wetel” — a flat, metered pass-through billed the same way any other generation already is on your account. It is not a prepaid-balance or model-marketplace product (no wallet, no per-model markup, no arbitrary third-party model selection) — this page covers what’s actually built today.
Auth: X-Api-Key, not a JWT
Section titled “Auth: X-Api-Key, not a JWT”Every operation on this page uses the same tenant-scoped API key as embed — generate one via Tenant: API keys. Set it as an X-Api-Key header on HTTP requests, and in connectionParams (not a header) for the WebSocket subscription — see Streaming below.
Every request must also include the x-huat-platform: customer header, same as every other operation in this API.
generateText
Section titled “generateText”Single-response — sends prompt to a chosen provider chain and returns the full completion. Real cost is incurred and metered the same as any other generation call on your account.
generateText(input: GenerateTextInput!): GenerateTextResultDto!GenerateTextInput fields
| Field | Type | Required | Notes |
|---|---|---|---|
prompt | String! | yes | Max 4000 characters. |
provider | LlmProviderType | no | BEDROCK_MANTLE | WEBBYXAI_HAIKU | GEMINI_DIRECT. Omit to use the app-wide default. |
modelId | String | no | Overrides which specific model the selected provider chain calls. Ignored by WEBBYXAI_HAIKU (single fixed model) and by the app-wide default. |
images | [String!] | no | Full data: URLs. Only consumed by BEDROCK_MANTLE with a vision-capable modelId — every other combination ignores this silently. Max 1 image. |
GenerateTextResultDto fields
| Field | Type | Notes |
|---|---|---|
text | String! | The completion. |
providerId | String! | Which provider actually served this call — can differ from input.provider on a silent fallback. |
modelId | String! | Which model actually served the call. |
costUsd | Float! | Real, incurred cost for this call. |
Request
query GenerateText($input: GenerateTextInput!) { generateText(input: $input) { text providerId modelId costUsd }}{ "input": { "prompt": "Write a haiku about databases." } }POST /graphqlContent-Type: application/jsonX-Api-Key: <your-api-key>x-huat-platform: customerResponse
{ "data": { "generateText": { "text": "Rows in silence wait\nA query breaks the stillness\nJoins reveal the truth", "providerId": "bedrock-mantle", "modelId": "zai.glm-5", "costUsd": 0.00012 } }}Streaming: startTextGeneration + textGenerationEvents
Section titled “Streaming: startTextGeneration + textGenerationEvents”For a streamed reply, start generation with a mutation, then subscribe to its events.
startTextGeneration(input: GenerateTextInput!): StartTextGenerationResultDto!input is the same GenerateTextInput shape as generateText above. Returns immediately:
{ "data": { "startTextGeneration": { "requestId": "b3f1..." } } }Then subscribe:
subscription TextGenerationEvents($requestId: String!) { textGenerationEvents(requestId: $requestId) { requestId text done providerId modelId error }}TextGenerationEventDto fields
| Field | Type | Notes |
|---|---|---|
requestId | String! | Echoes the id from startTextGeneration. |
text | String! | This chunk’s incremental text — append it to what you already have. Empty on the terminal chunk unless error is set. |
done | Boolean! | true on the final event — no further events arrive after this one. |
providerId | String | Only populated on the terminal (done: true) event, same convention as generateText. |
modelId | String | Only populated on the terminal event. |
error | String | Set (with done: true) if generation failed — the provider’s own error message, safe to show directly. |
WebSocket auth for this subscription specifically
Section titled “WebSocket auth for this subscription specifically”Unlike every other subscription in this API (which authenticate via a dashboard JWT), textGenerationEvents authenticates via the same API key as the mutations above — pass it as x-api-key in connectionParams when you open the graphql-ws connection (not as an HTTP header — WebSocket connections don’t carry HTTP headers the same way):
import { createClient } from "graphql-ws";
const client = createClient({ url: "wss://api.wetel.dev/graphql", // See "Troubleshooting: connect before you start generation" below for // why lazy: false matters here. lazy: false, connectionParams: { "x-api-key": "<your-api-key>", "x-huat-platform": "customer", },});A requestId you didn’t start yourself (or that already expired — see below) is rejected with a Forbidden error at subscribe time, not silently ignored.
requestId lifetime
Section titled “requestId lifetime”A requestId is valid to subscribe against for 10 minutes after startTextGeneration returns it — after that it’s treated as never having existed. In practice, subscribe immediately after starting generation; there’s no legitimate reason to hold onto a requestId for later.
Monthly spend cap
Section titled “Monthly spend cap”Every tenant has a monthly spend ceiling on this API, based on plan tier. Both generateText and startTextGeneration check it up front — if you’re already at or over the cap, the call fails immediately with a BadRequestException (before any provider is called, so it never incurs cost) and a message naming your limit and current spend:
This tenant has reached its monthly LLM spend limit of $5.00 (spent $5.12 so far this month). Upgrade your plan or wait until next month.This is a hard stop, not a soft warning — plan ahead if you’re running a burst of calls near the end of a billing month. The cap resets at the start of each calendar month.
Troubleshooting
Section titled “Troubleshooting”Chunks never arrive — connect (and subscribe) before calling startTextGeneration
Section titled “Chunks never arrive — connect (and subscribe) before calling startTextGeneration”Events are delivered over plain Redis pub/sub, not a queue — there is no replay buffer. If generation finishes before your subscription is actually listening, those chunks are gone forever; your subscription just sits there until it times out on your end, with no error from the server (the requestId is still valid, ownership still checks out, there’s simply nothing left to deliver).
For a short prompt against a fast model, generation can complete in well under a second — faster than a fresh WebSocket handshake, in practice. Get the connection established first, then start generation:
const client = createClient({ url: "wss://api.wetel.dev/graphql", lazy: false, // see below connectionParams: { "x-api-key": "<your-api-key>", "x-huat-platform": "customer", },});
// Wait for the socket to actually be open before doing anything else.await new Promise(resolve => { client.on("connected", resolve);});
// NOW start generation and subscribe — not before.const { requestId } = await startTextGeneration(prompt);client.subscribe( { query: TEXT_GENERATION_EVENTS, variables: { requestId } }, sink);graphql-ws’s client defaults to a lazy connection — by design, it doesn’t open the socket at all until the first subscribe() call, so a naive “wait for connected, then do stuff” pattern deadlocks with the default options (connected never fires because nothing has triggered a connection yet). Pass lazy: false at client creation so the socket opens immediately and connected actually fires — this is why the code sample above sets it explicitly.
See also
Section titled “See also”- Knowledge Base & Embeddings:
embed— the other API-key-tier utility operation, for building your own vector index - Tenant: API keys — generating the
X-Api-Keythis page’s operations use - Events & Subscriptions — general
graphql-wsconnection setup (written for the dashboard-JWT case; this page’s WebSocket auth note above is the one thing that differs fortextGenerationEvents) - API Reference: Overview