跳转到内容

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.

Every GraphQL rate limit below is keyed by the calling client’s IP address — not by API key, tenant, or user, with one exception: generateText added a second, independent per-tenant ceiling alongside its per-IP limit on 2026-09-24 — see that section below. Two consequences worth designing for on every other operation:

  • 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 sdkStart is limited this way rather than per tenant.

“Client IP” is the address your request reaches Wetel from, read at the edge from Cloudflare’s CF-Connecting-IP; a request that does not come through Cloudflare cannot set it. Before 2026-10-06 the platform counted the Cloudflare edge address instead, so callers who happened to share an edge could also share a count. Each real address is now counted on its own, which is what this page has always described. If your servers or your users sit behind one NAT, they share one bucket.

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.

Since 2026-10-29 on staging (production rollout pending), that error carries extensions.code TOO_MANY_REQUESTS and extensions.retryAfterSeconds, with a message like Too many requests. Try again in about 2 minutes.; extensions.status stays 429 and the HTTP status of the response is unchanged. Before, the code was INTERNAL_SERVER_ERROR. See Error handling.

All throttled GraphQL operations require the standard x-huat-platform: customer header alongside your normal authentication — see Authentication.

OperationLimitWindow
sdkStart30 requestsper 60 seconds, per client IP
refreshAvatarToken10 requestsper 60 seconds, per client IP
speechToken30 requestsper 60 seconds, per client IP
agentActivitySummary30 requestsper 60 seconds, per client IP
OperationLimitWindow
agentTemplates30 requestsper 60 seconds, per client IP
hireAgentTemplate5 requestsper 60 seconds, per client IP
provideAgentTemplateSlotCredential10 requestsper 60 seconds, per client IP
bindAgentTemplateSlot20 requestsper 60 seconds, per client IP

Hiring also has its own ceilings per workspace: 10 hired roles at once and 5 hires per 24 hours.

The partner operations (an API key with the templates:hire scope) have their own limits, also per client IP per 60 seconds: partnerHireAgentTemplate, partnerAgentTemplates, partnerAgentTemplateInstall and partnerAgentTemplateInstalls 60 requests, partnerBindAgentTemplateSlot 20 requests. Their per-key ceilings are 200 roles hired at once and 50 hires per 24 hours, unless Wetel set other limits for the key. Live on staging and in production since 2026-10-09.

The numbers above are the defaults. sdkStart and refreshAvatarToken are deployment settings (SDK_START_LIMIT_PER_MIN, AVATAR_TOKEN_REFRESH_LIMIT_PER_MIN), so an environment tuned for a higher-volume partner can differ from this page; the limit that applies to you is the one in the error you get back. Separately, every new session also asks the avatar service for its token, which is limited to 60 per minute per source IP for the whole backend, so the effective session-start ceiling is the lower of the two.

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.

agentActivitySummary: added 2026-10-03. It is the most expensive read in the schema (grouped scans over up to 92 days of runs), so a dashboard should fetch it on load and on focus rather than polling. The limit is per client IP, so a team behind one office network shares it.

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.

OperationLimitWindow
embed (text embedding query)10 requestsper 60 seconds, per client IP
requestKnowledgeDocumentUpload (step 1 of document upload)10 requestsper 60 seconds, per client IP
confirmKnowledgeDocumentUpload (step 2 of document upload)10 requestsper 60 seconds, per client IP
ingestDocumentFromUrl10 requestsper 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:

Terminal window
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 } }"}'
OperationLimitWindow
createCustomAction10 requestsper 60 seconds, per client IP
updateCustomAction10 requestsper 60 seconds, per client IP
deleteCustomAction10 requestsper 60 seconds, per client IP
testCustomAction10 requestsper 60 seconds, per client IP
OperationLimitWindow
aiGenerateWorkflow (LLM-assisted workflow graph generation)5 requestsper 60 seconds, per client IP
generateWorkflowDiff (LLM-assisted incremental edit)20 requestsper 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.

Added 2026-09-21.

OperationLimitWindow
runWorkflowTask60 requestsper 60 seconds, per client IP
workflowTaskOutcome60 requestsper 60 seconds, per client IP (the same bucket as runWorkflowTask)

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.

Per-workspace cap on tasks in flight (in production since 2026-10-29). Separately from the per-IP limit, one workspace can have a limited number of headless tasks (calls to runWorkflowTask and scheduled fires) waiting or running at once: 10 by default. A call over that cap is refused with HTTP 429 and recorded with the rejection code TENANT_BUSY; retry when an earlier task finishes. Chat turns do not count towards it. The count is best effort: two simultaneous calls can both get in by one. Over GraphQL, read the message rather than extensions.code, which is INTERNAL_SERVER_ERROR for a 429.

OperationLimitWindow
generateText30 requestsper 60 seconds, per client IP
generateText60 requestsper 60 seconds, per tenant
startTextGeneration10 requestsper 60 seconds, per client IP

Updated 2026-09-24: generateText’s per-IP floor was raised from 10/min to 30/min, and a second, independent per-tenant ceiling of 60/min was added — both must pass. This is the one operation on this whole page keyed partly by tenant rather than client IP alone, so a partner backend proxying many end users through one server IP isn’t bottlenecked by the IP tier the way every other operation here is by design (see How limits are keyed above). The tenant is resolved from whichever credential authenticated the call — a dashboard JWT or an X-Api-Key header, both now accepted (see LLM API: Auth). startTextGeneration is unchanged — still 10/min per client IP only, X-Api-Key only.

See LLM API for the request shapes (including the 4,000-character prompt cap).

OperationLimitWindow
testLlmProvider10 requestsper 60 seconds, per client IP
llmModelCatalog60 requestsper 60 seconds, per client IP
transcribeSpeech10 requestsper 60 seconds, per client IP

Added 2026-09-08 (see the changelog).

OperationLimitWindow
login10 requestsper 60 seconds, per client IP
loginWithWebbyxOne10 requestsper 60 seconds, per client IP
register5 requestsper 60 seconds, per client IP
isRegisterable5 requestsper 60 seconds, per client IP
refreshToken20 requestsper 60 seconds, per client IP
generateOTP3 requestsper 5 minutes, per client IP
validateSecuredToken5 requestsper 60 seconds, per client IP
forgotPasswordWithOTP5 requestsper 15 minutes, per client IP
resetUserPasswordWithUrl5 requestsper 15 minutes, per client IP
requestEmailVerification3 requestsper 15 minutes, per user (staging; production rollout pending)
confirmEmailVerification5 requestsper 15 minutes, per user (staging; production rollout pending)

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.

Changed 2026-10-05: the per-address ceilings on the speech routes are 120 a minute for speech-to-text and 300 for text-to-speech (they were 10 and 30, and were in effect shared by every caller). The per-tenant ceilings (200 and 600) are unchanged and normally bind first.

These apply to every request regardless of rate:

  • Query depth ≤ 7 and query complexity ≤ 50 — see Request limits.
  • sendMessage / sdkSendMessage text ≤ 100,000 characters — rejected, not truncated.
  • Request body ≤ 10 MB on the GraphQL API (6 MB on the avatar service’s REST routes). An oversized request is refused with a 413 that carries no CORS headers, so a browser reports it as a CORS error rather than a size error: check the size first when a large request fails that way.
  • ingestDocument inline text ≤ 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).

The following core session operations have no enforced rate limit as of this writing:

  • sdkSendMessage
  • sdkEndSession
  • sendMessage, 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.