Rate Limiting
This page lists every rate limit currently enforced by the Wetel API, sourced directly from the API’s throttling configuration (last reconciled against the deployed code on 2026-09-17). Anything not listed here has no enforced limit today — see No limit today below before relying on that in production.
How limits are keyed
Section titled “How limits are keyed”Every GraphQL rate limit below is keyed by the calling client’s IP address — not by API key, tenant, or user. Two consequences worth designing for:
- If your backend proxies many end users through one server, all of them share that server’s bucket. Apply your own per-user fairness limiter in front if that matters to you.
- Conversely, one abusive caller cannot exhaust a tenant’s budget for everyone else — which is
the reason
sdkStartis limited this way rather than per tenant.
The window is fixed per operation (mostly 60 seconds). Exceeding a limit returns a GraphQL error
with HTTP 429 semantics; back off and retry after the window.
All throttled GraphQL operations require the standard x-huat-platform: customer header
alongside your normal authentication — see Authentication.
Sessions
Section titled “Sessions”| Operation | Limit | Window |
|---|---|---|
sdkStart | 30 requests | per 60 seconds, per client IP |
refreshAvatarToken | 10 requests | per 60 seconds, per client IP |
sdkStart: added 2026-09-08. Every sdkStart creates a session and fires a metered
opening-greeting LLM call, so an unthrottled endpoint would let anyone holding a leaked API key
inflate a tenant’s bill. Thirty per minute is far above any real embed’s start rate; if you
legitimately need more (e.g. a load test), run it from more than one source IP.
refreshAvatarToken: added 2026-09-14. This is the one operation that deliberately accepts an
already-expired credential (within its grace window — see
refreshAvatarToken), so the rate limit is the
only thing capping brute-force attempts against the token signature. One session legitimately needs
about one refresh per hour, so ten per minute leaves very large headroom. Keyed per client IP rather
than per tenant on purpose: a tenant-keyed bucket would let one abusive caller lock that tenant’s
real embeds out of refreshing at all.
Knowledge base / embeddings
Section titled “Knowledge base / embeddings”| Operation | Limit | Window |
|---|---|---|
embed (text embedding query) | 10 requests | per 60 seconds, per client IP |
requestKnowledgeDocumentUpload (step 1 of document upload) | 10 requests | per 60 seconds, per client IP |
ingestDocumentFromUrl | 10 requests | per 60 seconds, per client IP |
embed also caps texts at 100 entries per call. The embeddings limit is documented in context
at Embeddings Integration.
Example request against a throttled operation:
curl -X POST https://api.wetel.dev/graphql \ -H "Content-Type: application/json" \ -H "X-Api-Key: <key>" \ -H "x-huat-platform: customer" \ -d '{"query":"query { embed(texts: [\"hello\"]) { vectors } }"}'Custom actions
Section titled “Custom actions”| Operation | Limit | Window |
|---|---|---|
createCustomAction | 10 requests | per 60 seconds, per client IP |
updateCustomAction | 10 requests | per 60 seconds, per client IP |
deleteCustomAction | 10 requests | per 60 seconds, per client IP |
Workflow generation
Section titled “Workflow generation”| Operation | Limit | Window |
|---|---|---|
aiGenerateWorkflow (LLM-assisted workflow graph generation) | 5 requests | per 60 seconds, per client IP |
generateWorkflowDiff (LLM-assisted incremental edit) | 20 requests | per 60 seconds, per client IP |
aiGenerateWorkflow is tighter than the others because each call triggers a full-graph LLM
generation, not a simple CRUD write. generateWorkflowDiff is looser because it’s designed for an
interactive co-pilot loop.
Workflow tasks
Section titled “Workflow tasks”Added 2026-09-21.
| Operation | Limit | Window |
|---|---|---|
runWorkflowTask | 60 requests | per 60 seconds, per client IP |
runWorkflowTask can fan out into LLM calls and
outbound partner requests, so an unthrottled endpoint would let anyone holding a leaked API key
inflate a tenant’s bill (and hammer a partner’s API) — the same reasoning as sdkStart. The ceiling
is deliberately looser than sdkStart’s 30/min because a machine orchestrator legitimately bursts (a
queue drain, a retry sweep) where a human-driven embed does not. Keyed per client IP, like everything
else here: a tenant-keyed bucket would let one abusive caller lock a tenant’s real orchestrator out
of its own endpoint.
LLM API (API-key tier)
Section titled “LLM API (API-key tier)”| Operation | Limit | Window |
|---|---|---|
generateText | 10 requests | per 60 seconds, per client IP |
startTextGeneration | 10 requests | per 60 seconds, per client IP |
See LLM API for the request shapes (including the 4,000-character
prompt cap).
Dashboard-only operations
Section titled “Dashboard-only operations”| Operation | Limit | Window |
|---|---|---|
testLlmProvider | 10 requests | per 60 seconds, per client IP |
transcribeSpeech | 10 requests | per 60 seconds, per client IP |
Authentication
Section titled “Authentication”Added 2026-09-08 (see the changelog).
| Operation | Limit | Window |
|---|---|---|
login | 10 requests | per 60 seconds, per client IP |
loginWithWebbyxOne | 10 requests | per 60 seconds, per client IP |
register | 5 requests | per 60 seconds, per client IP |
isRegisterable | 5 requests | per 60 seconds, per client IP |
refreshToken | 20 requests | per 60 seconds, per client IP |
generateOTP | 3 requests | per 5 minutes, per client IP |
validateSecuredToken | 5 requests | per 60 seconds, per client IP |
forgotPasswordWithOTP | 5 requests | per 15 minutes, per client IP |
resetUserPasswordWithUrl | 5 requests | per 15 minutes, per client IP |
Avatar service (TTS / STT / avatar sessions)
Section titled “Avatar service (TTS / STT / avatar sessions)”The avatar service’s REST endpoints (POST /tts, POST /tts/google, POST /stt,
POST /avatar-session) carry their own throttles, separate from the GraphQL API above. Exact
limits and request/response shapes are documented at
Avatar Service REST Reference — not repeated here to
avoid the two pages drifting out of sync.
Added 2026-09-17: each of those four routes is now governed by two independent limits that
must both pass — a per-client-IP ceiling (unchanged, same numbers as before) and a new,
higher per-tenant ceiling shared across every caller authenticated with that tenant’s avatar
tokens. An unauthenticated or invalid-token request is governed by the IP limit alone; the tenant
limit only ever applies once a valid avatarToken resolves a tenant. See the REST reference page
for the exact numbers per route.
Structural limits (not rate limits)
Section titled “Structural limits (not rate limits)”These apply to every request regardless of rate:
- Query depth ≤ 7 and query complexity ≤ 50 — see Request limits.
sendMessage/sdkSendMessagetext≤ 10,000 characters — rejected, not truncated.ingestDocumentinlinetext≤ 500,000 characters; uploaded files ≤ 20 MB, PDF, DOCX, Markdown, CSV, or XLSX (Markdown/CSV/XLSX support added 2026-09-17 — see Knowledge Base (RAG): File upload).
No limit today
Section titled “No limit today”The following core session operations have no enforced rate limit as of this writing:
sdkSendMessagesdkEndSessionsendMessage,startSession,endSession
Any other GraphQL operation not listed in the tables above is also currently unthrottled.
This is a deliberate current state, not a guarantee. Treat anything not explicitly listed on this
page as subject to change without notice, and design your integration (including any
headless / channel-connector integration) with your own
backoff and retry handling regardless of whether a limit is currently enforced server-side —
especially for any integration that fans a single external event (e.g. a busy group chat) into
many sdkSendMessage calls in a short window.