Changelog
此内容尚不支持你的语言。
Notable API-facing changes, newest first. This tracks what integrators need to know — not every internal change, just anything that adds a capability, fixes a real bug you might have hit, or changes behavior you may be relying on.
2026-11-06
Section titled “2026-11-06”-
Added (on staging; code ships with the next production release; pilot, off by default): two owner-only reads for the outbound self-test.
outboundAvailabilitysays whether a “Call my phone” self-test could start now (availableplus a plainreason, masked phone, allowance) andoutboundCall(id)returns the state of one call (status, duration, end reason, masked last four digits and country, never the number). Workspace owner only; both are reads and never place a call. Not in the production schema yet, and outbound calling stays off. See Reading a call. -
Added (in production since 2026-11-05): the
knowledge_searchworkflow node looks up passages of one knowledge base and writes them to a variable as[{ chunkId, documentId, score, text }], with no model call. A lookup error fails the node and can be routed on afailureedge; no match is an empty list.NodeTypegainsKNOWLEDGE_SEARCH, so a client that lists node types must accept it. See Knowledge Search Node. -
Added (in production since 2026-11-05): request changes on approvals.
decideApprovalacceptsdecision: REQUEST_CHANGES(workspace owner only,commentrequired: it is the feedback). The request ends as the new statusCHANGES_REQUESTEDand the run resumes on an optionalchanges_requestededge of theapprovalnode, normally back to the node that drafted the content, with the feedback as{{approval.comment}}. The revised draft opens a new request.ApprovalDtogainsrevision: Int!(how many times it was sent back,0for a first draft); at most 3 revisions, after which onlyAPPROVEorREJECTis accepted.REQUEST_CHANGESis refused (BAD_REQUEST) without a comment, on an approval step with nochanges_requestededge, and after 3 revisions. Existing workflows are unchanged: the edge is optional. A query that selectsrevision, or a filter that sendsCHANGES_REQUESTED, fails on an API that has not released this yet. See Approval Node and Human approval: a walkthrough.
2026-11-05
Section titled “2026-11-05”-
Added (in production since 2026-11-05): a
sub_agenttarget whose workflow pauses at anapprovalnode is no longer reported as a failure. Its entry inresultVarhas the newstatus: "awaiting_approval"plusapprovalId(the open request in the owner’s inbox) and, when the target had already spoken,text. The change is additive: a graph that checksstatus == "ok"is unaffected, and arouterorswitchcan add anawaiting_approvalbranch. The owner’s decision resumes the target’s own run, not the master’s. Publishing such a workflow throughpublishWorkflowis no longer refused (see the next entry). A client that models the status as a closedok | timeout | errorset must accept the new value. See A target that waits for an owner approval. -
Changed (in production since 2026-11-05):
publishWorkflowno longer refuses a workflow that contains anapprovalnode when another workflow’ssub_agentnode targets the agent that runs it. The refusal text (“run as a sub-agent by workflow N … an approval cannot run inside a sub-agent dispatch”) is gone; a graph that publishes now needs no change on your side. The workspace app screens for the delegating pattern are still on the staging-backed app. See A target that waits for an owner approval. -
Site: a new product tour walks through a Sales-first AI Office in a demo workspace, with a status on every step. No API change.
-
Added (in production since 2026-11-05): text helpers in
condition,switchandset_variableexpressions, so a graph can check text without anllmnode. Existing expressions are unchanged. See Condition Node. -
Added (code in production since 2026-11-05, switched off until enabled for a workspace; pilot):
startOutboundCall(input: StartOutboundCallInput!): StartOutboundCallResult!makes an AI employee phone the workspace owner’s own number. Workspace owner only, self-test only (selfTest: true; the destination must be the owner’s profile phone, prefixes+1and+60by default), off unless the platform and the workspace switches are on (FORBIDDENotherwise), hard maximum length 15 to 300 seconds (default 120). It returnsqueuedat once; the destination number is stored only as a keyed hash plus its last four digits and country. Call states aredialing,ringing,answered,voicemail,no_answer,busy,declined,failedandcompleted; completion with duration and end reason is recorded when an answered call ends, and voicemail is detected and hung up. There is no public query for a call’s later states yet. See Telephony API reference. -
Added (code in production since 2026-11-05, switched off until enabled for a workspace):
sdkStartVoiceCalltakes an optionalengine:WORKFLOW(default) orLIVE, a speech-to-speech model that answers from the agent’s instructions only (no workflow, no tools).LIVEis refused unless the deployment has it enabled. The voice worker stores a live call’s transcript with the newsdkRecordVoiceTranscript(called with the call’s own token, once per call, not from a browser). See Telephony API reference. -
Added (code in production since 2026-11-05, switched off until enabled for a workspace):
sendOwnerDigestNow(workspace owner only) builds the weekly ops digest now (conversations, messages, workflow runs by outcome, approvals, files created, AI cost and the most active AI employees over seven days; counts and AI employee names only) and sends it as an in-app notice to the workspace owners. Limited to 3 calls an hour;FORBIDDENwhile the feature is switched off on the server.
2026-11-03
Section titled “2026-11-03”- Now on in production: the
generate_artifactnode can produce documents and pages, spoken voice-over files (audio_speech), jingles (audio_music) and narrated video scripts (video_scriptwithnarrate: true) on the production API. Each workspace has daily limits for speech and music files (the platform defaults apply). Image generation (image) stays off in production for now. See Generate files, voice and video. - Now on in production: workspace Office presence and room chat, and per-run cost attribution (internal usage records). These affect the workspace app, not the integration API.
2026-11-01
Section titled “2026-11-01”- Added (on staging; switched off in production):
generate_artifactacceptsnarrate: trueforartifactType: "video_script": one spoken voice file per scene, saved with the video, counted against the workspace’s daily speech limit; off unless the platform’s video narration switch is on. A video script’s default length is now about 45 seconds unless the brief names one. The workspace app can play a video script, and “Download video” records an MP4 or WebM in the browser. See Generate Artifact Node. - Added (on staging once released; switched off in production):
generate_artifactacceptsartifactType: "video_script": the model writes a short narrated explainer as 1 to 8 scenes, the platform validates it (length limits, no URLs or markup, last scene a call to action) and stores only a valid manifest as an artifact of kindvideo_script; an invalid answer fails the node withBLOCKED. The workspace app plays it in the Documents window; it is not an MP4. See Generate Artifact Node. - Added (on staging; in the production schema, switched off): the
generate_artifactnode can now produce audio.artifactTypeacceptsaudio_speech(the prompt is read aloud and stored as a WAV file; optionalvoice:Kore,Charon,Fenrir,AoedeorPuck) andaudio_music(a short piece composed from the prompt, stored as an MP3 file). Audio has its own platform switch, off by default, a per-workspace daily limit (10 speech and 5 music files by default) and a 20 MB cap per file. The artifact kinds areaudio_speechandaudio_music. See Generate Artifact Node. Do not build a customer-facing flow on it until Wetel has told you it is on for your environment.
2026-10-31
Section titled “2026-10-31”- Added (in production since 2026-10-30):
updateKnowledgeBase(input: { id, name, description }): rename a knowledge base or change its description without touching its documents. See Knowledge and embeddings. - Added (in production since 2026-10-30):
workspaceModelsandownerChannelConnectors(workspace owner only, read-only): the models the workspace may use and its default, and the workspace’s channel connectors with their linked AI employee, status and last activity (never credentials or message text). See Tenant and Channels. - Added (in production since 2026-10-30; attribution off by default):
tenantCostByAgent(workspace owner only): what each AI employee cost in a window. Model calls are now recorded with the AI employee, session and run they belong to, but attribution is a platform setting that is off until Wetel switches it on: while it is offagentsis empty andattributionEnabledisfalse, and calls with no agent are reported as unattributed (isFloor). Credits are approximate. See Tenant. - Added (in production since 2026-10-30): test chats.
startSessiontakesisTest, andSessionDtoandSessionActivitySummaryDtoreturnisTest. A test chat is hidden fromsessions,sessionActivitySummaries,sessionCountand activity counts and is never evaluated; a workspace owner can list them withincludeTests: true. Their cost still counts. See Sessions. - Added (in production since 2026-10-30):
withdrawRelease(releaseId): the author (or the owner) takes back aPENDINGrelease so the draft can be edited and submitted again. See Tenant. - Added (in production since 2026-10-29): release audiences.
setReleaseAudience(owner only) limits an approved release toSELECTEDcolleagues or a privateTEST_GROUP;releaseVisibilitysays who may use released resources;ResourceReleaseDtogainsaudienceEnforcedandaudienceUserIds. Audiences are enforced only while a workspace has member-built drafts on (the default is off). While it is on, only the owner can create an API key. Turning member-built drafts on now releases to everyone every existing resource that has no release and was not made by a member. This replaces the earlier note that releasing did not change who can use a resource. See Release audience. - Added (in schema; off by default, do not rely on it): the
generate_artifactworkflow node, which asks a model to write a markdown document or an HTML page, or to draw an image, and stores it as an artifact. It is behind platform switches that are off, so it fails withDISABLEDuntil Wetel enables them.ArtifactDtogainsagentId,generator,hasThumbnail,isTestandvisibility(reserved, not enforced);artifactstakesincludeTests(owner only);artifactDownloadUrltakesthumbnail; the artifacterrorCodecan now beBLOCKED. See Generate Artifact Node and Artifacts. - Changed (in production since 2026-10-31): a typed dashboard chat or a test chat with an agent that has no workflow now gets the agent’s persona without the spoken-style voice rules. Sessions from
startAvatarDemoSession,sdkStart, channels and voice calls are unchanged. See Sessions. - Reminder: a request body sent with a
Content-Encodingother thanidentityis answered with HTTP415(UNSUPPORTED_CONTENT_ENCODING) since 2026-10-23. Send it uncompressed; see Troubleshooting.
2026-10-29
Section titled “2026-10-29”- Added (available on staging; production rollout pending): email verification.
requestEmailVerification(signed in; at most 3 calls per 15 minutes per user) emails a 6-digit code to your own address and always answerstrue;confirmEmailVerification(code: String!)(at most 5 calls per 15 minutes per user) marks the address verified.User.emailVerified(Boolean) is visible on the current user. Enforcement is a platform setting that is off by default: when on, an account whose email is not verified is refused withFORBIDDEN(HTTP 403) andextensions.codeEMAIL_NOT_VERIFIED(“Verify your email address first”) onpublishWorkflow,rollbackWorkflow,createNamedApiKey,generateApiKey,inviteTenantUser,resendTenantInvite,hireAgentTemplate,bindAgentTemplateSlotandprovideAgentTemplateSlotCredential. Accounts created or linked through WebbyX One sign-in and accounts created by accepting a workspace invite count as verified. See Email verification. - Changed (available on staging; production rollout pending): a throttled GraphQL call now returns
extensions.codeTOO_MANY_REQUESTS(it wasINTERNAL_SERVER_ERRORwith the messageThrottlerException), a message like “Too many requests. Try again in about 2 minutes.” andextensions.retryAfterSeconds.extensions.statusstays429and the HTTP status of the response is unchanged. See Error handling and Rate limiting. - Now in production: since 2026-10-29 (backend release of that night): the owner
createTextArtifact, the member-built drafts and releases for knowledge bases, custom actions, agents and workflows (off per workspace until an owner enables member-built drafts), the per-workspace cap on headless tasks in flight (TENANT_BUSY), the owner email on a workflow failure and thePLAINTEXT_SECRET_IN_CONFIGdiagnostic. The owner audit view (tenantAuditLogs,requestAuditLogExport,auditLogExport) is not in production yet.
2026-10-28
Section titled “2026-10-28”- Docs: new Channel capabilities page: what each channel supports today (text, groups, voice, files, approvals, hand-off, limits), checked against the code. The Channels page now lists the
APIchannel type, says that only PDF and image files are accepted from Telegram, and shows the current attachment URL form (a signed gateway link; the Telegram bot token never appears in it). - Added (in production since 2026-10-29): member-built drafts and owner releases for knowledge bases and custom actions:
setMemberBuilderForTenant,Tenant.memberBuilder,submitRelease,myReleases,releasesByState,decideRelease,revokeRelease, andcreatedByMe/releaseStateonknowledgeBasesandcustomActions. Off by default. A member’s draft custom action carries no credentials; publishing a workflow is refused while it uses an unreleased knowledge base or custom action. See Tenant. Off by default per workspace: nothing changes until an owner turns on member-built drafts (Tenant.memberBuilder,falseuntil then). - Added (in production since 2026-10-29): releases now also cover agents and workflows:
submitReleaseacceptsresourceTypeAGENTandWORKFLOW, andagentsandworkflowsreturncreatedByMeandreleaseState. While member-built drafts are on, an agent a member creates startsINACTIVEand a member cannot set itsworkflowIdor external sync fields (BAD_REQUEST); a member can publish a workflow only once it is released, and publishing is also refused while a sub-agent node points at an unreleased agent. A resource a member may not see answersNOT_FOUND(an agent or workflow reads asnull). See Tenant. The release primitive stays off for a workspace until its owner enables member-built drafts. - Added (available on staging; production rollout pending):
tenantAuditLogs,requestAuditLogExportandauditLogExport(workspace owner only): the audit trail of the whole workspace and a CSV export (at most 100,000 rows, download link valid for 15 minutes). Field names only, never values; reads, chat sessions and messages and workflow runs are not audited. See Tenant. - Added (in production since 2026-10-29):
createTextArtifact(workspace owner only): store an HTML page or a Markdown document as an artifact (kindhtml_pageormarkdown, at most 262144 bytes). The file is stored as plain text and must be shown through a sanitised, sandboxed preview. There is no delete mutation yet. See Artifacts. - Added (in production since 2026-10-29): a per-workspace cap on headless tasks in flight (default 10).
runWorkflowTaskand scheduled fires over the cap are refused with HTTP429and recorded asTENANT_BUSY. See Rate limiting andrunWorkflowTask. - Added (in production since 2026-10-29): with
settings.notifyOnFailureset toOWNERS, each active owner with an address also gets an email, not only the in-app notice. It holds the workflow name, run id and time, never error text. See Workflows. - Added (in production since 2026-10-29): workflow diagnostic
PLAINTEXT_SECRET_IN_CONFIG(warning, never blocks save or publish) for a likely literal credential in a webhook, tool or action node’s configuration; the message never repeats the value. See Workflow diagnostics.
2026-10-27
Section titled “2026-10-27”-
Added (in production since 2026-10-27):
exportGuestSubjectanderaseGuestSubject(workspace owner only) for data-subject requests about one guest: export their data as JSON, or erase it with a requireddryRunflag. See Tenant. -
Changed (in production since 2026-10-27): in the Group Brain API, a follow-up from the person the agent just answered is no longer answered by rule: it goes to the judge as a follow-up candidate, so off-topic messages get
SILENT. Inmention_onlymode the follow-up window no longer applies. Thefollow_upreason code remains. If your client depends on the agent answering every follow-up, tell us. -
Added (in production since 2026-10-27):
publishSentenceson thellmnode: send each finished sentence to the client while the model is still writing (partialAI_RESPONSEevents withisFinal: false, then an empty final event). Off by default and per node; refused withoutputSchemaand withstreamOutput: false. -
Docs: the Group Brain API now explains the
follow_uprule and thecontinuityMswindow, and the mention-only recipe has a section on running more than one agent in the same group. No API change. -
Docs: new Telephony primer: the phone network, SIP and RTP, why phone audio is 8 kHz, the listen-think-speak loop and a glossary, with how it maps to Wetel Telephony today. No API change.
-
Docs: new telephony page How a call flows: sequence and component diagrams for a call to a demo phone number and for a Wetel-run outbound test, what each part does, what you can try today and what is not available. No API change; telephony is still a test-environment capability on request.
-
Added:
signInmutation — authenticates with email/phone + password and supports two-factor authentication in a single flow. Returns aSignInResultwith statusSIGNED_IN,TWO_FACTOR_REQUIRED, orTWO_FACTOR_ENROLMENT_REQUIRED. Two-factor is completed withcompleteSignInWithTwoFactor(submit authenticator code) and, for first-time set-up,generateTwoFactorForSignIn(get the QR code). See Signing in with two-factor support. -
Changed:
loginnow refuses accounts that have two-factor authentication (or whose workspace requires it) withFORBIDDENand codeTWO_FACTOR_REQUIREDorTWO_FACTOR_ENROLMENT_REQUIRED. Integrations with such accounts must migrate tosignIn+completeSignInWithTwoFactor. -
Added:
signOutEverywhere— signs the current user out of every session on every device; all existing tokens are refused from the next request.revokeUserSessions(owner only) — does the same for a specific user of your workspace. See Session revocation. -
Added: Workspace invites —
inviteTenantUser,acceptTenantInvite,resendTenantInvite,revokeTenantInvite,tenantInvites. Owners invite by email; the invitee receives a one-time code (never returned by the API) valid for 7 days. See Workspace invites. -
Added:
setRequireTwoFactorForTenant(owner only) — enforce two-factor authentication for every password sign-in to the workspace.resetTenantUserTwoFactor(owner only) — turn off two-factor for a user who lost their authenticator (also revokes their sessions). See Workspace security. -
Added:
workspaceSecurityEvents(owner only) — paginated security event log for your workspace (sign-ins, failures, locks, password events, owner actions, invite events, two-factor policy changes).mySignInHistory— the caller’s own account security history. See Workspace security. -
In production since 2026-10-27: structured output (
outputSchema) and per-nodetimeoutMson I/O nodes. See Structured output and Workflows API reference.
2026-10-24
Section titled “2026-10-24”- Now in production: the 2026-10-22 and 2026-10-23 changes below that were marked “available on staging” are live in production since 2026-10-24: the per-node
retrypolicy (withattempts/retryOutcomeon the trace and theRETRY_NOT_IDEMPOTENTdiagnostic), mock mode for draft tests and regression replays (draftSideEffects,draftMocks,draftMockLlm,mockResponse,runRegressionScenario’ssideEffectsand its mock-mode default,WorkflowRunDto.mocked), the empty end-of-turn event (turnComplete: truewith emptytext) and the Group Brain API, which stays shadow-only: it needs a named API key with thegroups:decidescope, and every decision is answeredSILENTuntil Wetel staff switch the profile live. A production draft test now runs in mock mode by default; passdraftSideEffects: LIVE(workspace owner only) to run every node for real. Telephony (sdkStartVoiceCall) is unchanged: staging only, on request, not in production.
2026-10-23
Section titled “2026-10-23”- Added (available on staging; in production since 2026-10-27): structured output for the
llmnode.outputSchema(a JSON Schema subset: root object, at most 3 levels of objects, at most 20 properties per object, at most 4 KB,additionalProperties: false, every property inrequired, optional values as nullable[type, "null"],enum/pattern/length and range limits;format,anyOf,$ref, nullable enums and backtracking-prone patterns are refused withBAD_USER_INPUT) makes the node store the parsed, validated object inoutputVar;flattenalso writes each top-level property to its own variable;structuredMode(autoorprompt) chooses how the model is asked. An invalid reply gets one repair call, billed as its own model call (none after a reply cut off at the token limit); a reply still invalid fails the node, which follows itsfailureedge with_lastNodeErrorCodeINVALID_DATA. Cannot be combined withstreamOutput: true,extractFirstInteger,matchAnyOforextractAlphanumericId. Trace rows gainstructuredMode,repairedandstructuredRaw; new info diagnosticLLM_STRUCTURED_WITHOUT_FAILURE_EDGE, andROUTER_DOTTED_VARIABLE_OF_TEXTno longer fires for a structured node. A valid reply has the right shape, not necessarily the right values: check them in a later node or an approval step. Nodes withoutoutputSchemaare unchanged. - Docs (correction): the telephony overview now has one “what works today, what does not, what is next” table, and the telephony roadmap, controls and compliance and roadmap pages match it. No API change. Corrections: asking for a person gets a spoken callback offer, not a live transfer; live calls are tested in English only; ordinary phone calls (including Malaysian +60 numbers, inbound or outbound) and joining Meet, Zoom or Teams meetings are not available; the overview’s “no outbound sales calls, and it will not” answer now states the actual policy: no cold outbound sales calls, while callbacks the person asked for and follow-ups with people who agreed to be called are planned at specification stage, with no date. New FAQ answers cover +60 numbers, meetings and languages.
- Docs: new cookbook recipe Build a reliable workflow, no API change: a lookup that retries with an idempotent policy, reads its result with
set_variableand branches with aswitch; a create call whose retry policy leaves outTIMEOUT(and theRETRY_NOT_IDEMPOTENTwarning if you add it);failureResponseandnotifyOnFailureas the safety net; one mock-mode test turn per branch withdraftMocks; and publish with a note, thenrollbackWorkflow. The retry and mock-mode parts are available on production from 2026-10-24. - Added (available on staging; in production since 2026-10-24): a per-node
retrypolicy ontool,actionandwebhooknodes:maxAttempts(1 to 4, required),backoff,delayMs,maxDelayMs,jitter,on(any ofTIMEOUT,UNREACHABLE,RATE_LIMITED,UPSTREAM_ERROR; defaultUNREACHABLEandRATE_LIMITED),idempotentanddeadlineMs. A request that may already have reached the service is resent only when it is idempotent. Withoutretry, a node keeps its current behaviour. The trace row of a node that retried showsattemptsandretryOutcome, and a new WARNING diagnostic,RETRY_NOT_IDEMPOTENT, flags a policy whoseTIMEOUT/UPSTREAM_ERRORclasses can never be retried. - Added (available on staging; in production since 2026-10-24; shadow-only): the Group Brain API for platforms that run their own group chats:
groupDecide(should the assistant speak, stay silent or wait for this batch of messages),groupReportOutcome,groupSetStateandgroupDecisions. It needs a named API key with thegroups:decidescope and a channel connector of typeAPI. Every decision is recorded and answeredSILENTuntil Wetel staff switch the profile live. - Changed (live, breaking for a few clients): security hardening, in production since 2026-10-23. Compressed request bodies are refused: a request to the Wetel APIs (GraphQL, the REST endpoints, the speech and avatar service, and the endpoints that receive webhooks) that carries a
Content-Encodingheader other thanidentityis answered with HTTP415; the application’s own refusal body is{"statusCode":415,"code":"UNSUPPORTED_CONTENT_ENCODING",...}, and the public edge may answer a plain415first. Send the body uncompressed (noContent-Encoding). Almost every client already does: browsers,fetch, axios and the usual GraphQL clients do not compress a request body unless you ask them to. Response compression is not affected;Accept-Encodingstill works as before. See Send plain request bodies. - Changed (live): outbound responses must not be Brotli-compressed. When Wetel calls a URL you configured, it now declines Brotli and refuses a response that arrives Brotli-encoded:
webhookandactionnode calls and file fetches ask forAccept-Encoding: identityand refuse any response that arrives with aContent-Encoding; MCP connector calls,externalSyncUrldeliveries and URL ingestion ask forgzip, deflateand refuse a Brotli response. A target that compresses regardless fails that call with a short error naming the encoding. Turn off response compression, or at least Brotli, for the endpoints Wetel calls. See Responses Wetel cannot read.
2026-10-22
Section titled “2026-10-22”- Docs: three new cookbook recipes for features that are live in production, with no API change: Answer when mentioned (an agent in your platform’s group chats that answers only when mentioned or replied to, built on
sdkStart/sdkSendMessagewith one session per group), Fail gracefully (failure edges, aswitchon_lastNodeErrorCode, and_lastNodeErrorPublicas the only error text users see) and Delegate to a specialist (sub_agentwithinputsandoutputs). The partner template page is now Provision agents for your customers with a template hire, same URL and anchors, with a one-agent-per-customer walkthrough, a test checklist and its limits. - Docs (correction): the telephony overview, controls and compliance and roadmap pages listed per-key daily voice call caps as planned or staging only. The cap is in the production code as well (1,000 call requests per API key per UTC day by default, set per environment; past it
sdkStartVoiceCallanswersINTERNAL_SERVER_ERRORwithextensions.status429), so the “still staging only” note in the entry below was wrong about the cap. Voice calls themselves remain staging only, on request, so in practice the cap applies only there. - Added (available on staging; in production since 2026-10-24): mock mode for draft tests and regression replays. A draft test (
sendMessagewithtestDraft: true) now runs withdraftSideEffects: MOCKby default:tool,action,webhookandpdf_exportnodes build their request exactly as live and then return a mock result instead of calling out (no outbound call, no credentials, no file written, no metering), asub_agentnode’s children run in mock mode too, and LLM nodes still call the model unless the test passesdraftMockLlm: trueor mocks them.draftMocksscripts a result ({ "output": ... }) or a failure ({ "fail": "..." }, which follows the node’sfailureedge) per node, and the new node config fieldmockResponse(up to 16 KB, mockable node types only) sets a node’s default mock. At most 20 real model calls per test turn.draftSideEffects: LIVEruns every node for real and is limited to the workspace owner.runRegressionScenarioalso defaults to mock mode, with mocks taken from the scenario’s recorded run, and takessideEffects: LIVE(owner only); each result recordssideEffectMode. A mocked run hasWorkflowRunDto.mocked: trueand each mocked trace row"mocked": true. New info diagnostic:MOCK_RESPONSE_SET. Until 2026-10-24, in production a draft test and a replay still ran every node for real. See Testing a Workflow Safely. - Changed (available on staging; in production since 2026-10-24): a workflow run that completes without a reply marked
turnComplete: truenow ends the turn with one emptyAiResponseEvent(textempty,turnComplete: true), the same event an approval pause sends. Skip events with empty text when rendering; nothing is stored. See A turn can end with an empty event. - Now in production: the 2026-10-21 changes below that were marked “available on staging” (expressions that cannot assign,
_lastNodeErrorCode/_lastNodeErrorPublic,OUTBOUND_URL_REFUSEDandBAD_GATEWAY,normalize: "numbers"and the speech-to-text billing change) are live in production since 2026-10-21. The voice call daily cap is still staging only. - Changed (live, breaking):
changePasswordrequirescurrentPasswordinPasswordInput(since 2026-10-21), so a session token alone can no longer change a password, and it returnstrueon success (it returnedfalsebefore). A wrong current password or an unchanged one isBAD_REQUEST; at most 5 calls per 15 minutes per user. SendcurrentPasswordfrom your change-password form; accounts created through single sign-on set a password withgenerateOTPandforgotPasswordWithOTPinstead.
2026-10-21
Section titled “2026-10-21”- Changed (available on staging; in production since 2026-10-21): workflow expressions can no longer assign. The
=operator (x = 5) and function definitions (f(a) = a + 1) are refused incondition,switchandset_variableexpressions, and aconditionis evaluated on a copy of the run’s variables, so an expression can never change them.==,!=,<=,>=and built-in functions are unaffected (map(sqrt, items)still works; an inline callback such asmap(f(x) = x * 2, items)does not). Aswitchorset_variableexpression with=is refused at save withBAD_USER_INPUT; aconditionthat uses one fails the run at that node. - Added (available on staging; in production since 2026-10-21): when a
failureedge handles a node failure, the run context now also holds_lastNodeErrorCode(TIMEOUT,UNREACHABLE,RATE_LIMITED,REJECTED,UPSTREAM_ERROR,INVALID_DATAorUNKNOWN) and_lastNodeErrorPublic, a fixed neutral sentence for that code, next to the raw_lastNodeError. Branch on the code, and show end users only_lastNodeErrorPublic:_lastNodeErrorcan name hosts, ids and the other service’s own message. Additive; existing graphs are unchanged. - Changed (available on staging; in production since 2026-10-21): a refused outbound URL (
serverUrloncreateMcpConnector/updateMcpConnector,urloncreateCustomAction/updateCustomAction, an agent’sexternalSyncUrl, awebhooknode’surlonupdateWorkflow) now answersBAD_REQUESTinstead ofINTERNAL_SERVER_ERROR, withextensions.originalError.codeOUTBOUND_URL_REFUSEDandextensions.originalError.reason(INVALID_URL,UNRESOLVABLE_HOSTorPRIVATE_ADDRESS); the message is unchanged.listMcpToolsanswersBAD_GATEWAY(extensions.status502) when your MCP server cannot be reached or does not answer properly. See Refused URLs and an unreachable MCP server and Errors. - Added (available on staging; in production since 2026-10-21):
POST /stt,/stt/googleand/stt/geminitake an optionalnormalize: "numbers". English, Malay and Mandarin number words in the transcript become digits (empat lapan dua satubecomes4821,lima puluh ringgitbecomesRM50), and the response addsrawTranscriptwith the engine’s original text. Off by default; no model call; billing unchanged. - Changed (available on staging; in production since 2026-10-21): speech-to-text billing. A transcript is billed once even when the Cloud engine re-checked the clip (
attempts: 2), a transcript from the Gemini engine is billed at the Gemini model’s token rate instead of per 15-second block, and the dashboard playground’stranscribeSpeechis now metered and counts toward the monthly spend cap. See Speech engines. - Changed (staging, on request; not in production):
sdkStartVoiceCallhas a per-API-key daily cap, 1,000 call requests per UTC day by default; past it the call is refused withextensions.status429 and the messageDaily voice call limit reached for this API key; try again tomorrow (UTC). A call nobody joins is now closed asEXPIREDabout 10 minutes after the request (within about 15 minutes). - Docs (correction): Pricing said speech usage does not count toward the monthly spend cap. Speech through the speech service already counts, at its underlying cost; tool calls do not.
2026-10-19
Section titled “2026-10-19”- Docs: Why Wetel chose LiveKit, a sourced comparison of the platforms we looked at for real-time voice agents (no API change). Vendor facts were checked on 2026-10-19 and are linked; we did not benchmark any competitor.
- Added (staging, on request; not in production): Telephony, live voice calls between a caller and a Wetel agent over LiveKit (WebRTC). The new mutation
sdkStartVoiceCall(input: SdkStartVoiceCallInput!)takes the same named API key assdkStart(server side only, withx-huat-platform: customer) and anagentId, an optionalclientIdand an optional flatcontext. It starts a session assdkStartdoes and returnssessionId,agentId,livekitUrl,livekitToken,roomNameandexpiresAt; the token joins one fresh room and is accepted for about two minutes. The agent speaks first (saying it is an AI), can be interrupted, and answers with the agent’s published workflow. Errors:UNAUTHENTICATED(Missing X-Api-Key header,Invalid API key),FORBIDDEN(Voice calls are not enabled on this environment),NOT_FOUNDfor an unknown agent; rate limits of 60 a minute per client address and 30 call starts a minute per workspace (the over-limit error may carry no specific code). Read the limits: a Wetel person runs the voice worker for arranged test sessions (no always-on hosted worker yet), measured reply time is about 3 to 5 s against a target of about 1.5 s, the join token’s contents are readable by the caller’s browser, there are no call records yet, and SIP, WhatsApp calling and phone numbers are not available. Pricing is not set; usage is metered with the session. See Telephony, the quickstart, building a voice-safe workflow, pilot testing and the roadmap.
2026-10-17
Section titled “2026-10-17”- Changed (live): when a run pauses at an
approvalnode,sessionEventssubscribers (SDK and guest chat clients) now receive an emptyAiResponseEventwithturnComplete: truebefore the decision, so a client that waits forturnCompleteis no longer held until a manager decides. Nothing is stored (no message, not replayed on reconnect); skip events with empty text. The outcome reply after the decision is delivered as before. - Changed (live): the
pdf_exportnode also accepts sections JSON that anllmnode wrapped in a markdown code fence or surrounded with prose, or that has raw line breaks inside its strings. The first complete value that parses and looks like sections is used, with the same rules and limits. - Added (live): the
pdf_exportworkflow node. It turns a list of sections held in a run variable (an array,{ sections }, or the same as JSON text from anllmnode) into a PDF in one of three fixed layouts (brochure,itinerary,slides), stores it as an artifact and writes{ artifactId }to itsoutputVar. No model call, no credits. Limits are strict, never truncating (brochure and itinerary: 40 sections, 4,000-character bodies, 30 items; slides: 30 sections, 1,200-character bodies, 8 items; 60,000 characters and 60 pages per document). A server renders one document at a time, so an export started while another renders fails at once: add afailureedge, which this node now supports (failure routing and theFAILURE_WITHOUT_SUCCESS/SUCCESS_WITHOUT_FAILUREdiagnostics include it). A render or storage failure marks the artifactFAILEDwith a fixed code (RENDER_FAILED,TIMEOUT,TOO_LARGE,UPLOAD_FAILED,INTERNAL). Dashboard draft test runs create real artifacts, with kindpdf_document_draft. - Added (live): the Artifacts API for generated files.
artifact(id)andartifacts(sessionId, kind, first, after)(newest first,firstdefault 20, at most 100) read statusPENDING/READY/FAILED, title, size anderrorCode; the mutationartifactDownloadUrl(id)mints a signed link valid for 5 minutes (CONFLICTuntil the artifact isREADY,NOT_FOUNDoutside your scope, 60 calls per minute per IP). Each takes a dashboard JWT (any artifact of the workspace) or theavatarTokenfromsdkStart(only that session’s artifacts). Treat the link as a credential: never store, log or share it. - Added (live):
settings.notifyOnFailureonupdateWorkflow, live in production since 2026-10-16.OWNERSgives every active workspace owner an in-app notice (read withgetNotifications) when a run of that workflow fails, at most one per workflow per 60 minutes, the next one saying how many failures were held back;NONE(the default, also for a hired template) turns it off. Never for draft tests, regression replays, interrupted or superseded turns, or a failure afailureedge handled. The notice names the workflow and the failing node type and links to the run (/workflows/<id>/runs/<runId>in the dashboard); it never contains the error text or anything from the conversation. - Docs: Workspace preview walkthrough (preview, no API change). See Workspace Preview.
2026-10-16
Section titled “2026-10-16”- Added (live): a workflow version history API, all with a dashboard JWT.
workflowVersions(workflowId, first, after)lists every publish, rollback or template hire that changed the published graph, newest first (Relay paging,firstdefault 20, at most 100);workflowVersion(workflowId, versionNumber)returns one version’s full graph;workflowVersionDiff(workflowId, fromVersion, toVersion?)names the node and edge ids and node keys that changed (never values), and withtoVersionomitted compares against the draft, which is exactly what the next publish would change.rollbackWorkflow(workflowId, versionNumber, note?)publishes an old graph again as a new version (source: ROLLBACK) and leaves the draft untouched; it is re-checked against today’s rules and refused with the same errors aspublishWorkflowwhen it no longer passes, and withBAD_USER_INPUTwhen that graph is already the one live.restoreWorkflowVersionToDraft(workflowId, versionNumber)copies a version into the draft (replacing unpublished edits) without publishing.publishWorkflow(id, note)takes an optional note (at most 500 characters) stored on the version. The newest 100 versions are kept, plus the current one and any a waiting conversation uses. The reference now also says correctly thatWorkflowDto.versiongoes up on every save (updateWorkflowandpublishWorkflow) and starts at 1; it is not a version number. - Added (live):
WorkflowDto.currentVersionId(the version new turns run) andWorkflowRunDto.workflowVersionId(the version a run executed;nullfor draft tests and older runs). Paused conversations already resume on the version they paused on in production (see the 2026-10-14 entry and Publishing while a conversation waits); a rollback behaves like a publish there. - Added (live): workflow settings with a custom failure reply.
updateWorkflow(input: { settings: { failureResponse } })sets plain text (1 to 500 characters after trimming) sent instead of the standard “Sorry, I ran into a problem” reply when a run of that workflow fails, including draft tests, resumed conversations and headless tasks (it shows inworkflowTaskOutcome.replies). It is refused withBAD_USER_INPUTwhen it contains{{ }}, HTML tags, control characters or credential-shaped text;nullclears it. It applies as soon as it is saved, is not part of any version, and is not sent for a failure handled by afailureedge or for an interrupted or superseded turn. Read it back withWorkflowDto.settings(nevernull). - Added (live):
SessionDto.startedByUserId, the dashboard user who started a session withstartSession;nullfor API-key (sdkStart),avatarToken, channel, sub-agent and replay starts and for older sessions, and alwaysnullonsessionsByExternalCustomerId.sessionActivitySummariestakes an opt-inmine: Booleanthat keeps only your own sessions (totalCountincluded); a caller with no user behind the token gets an empty page. Withoutminenothing changes. - Docs: 11 more operations in the developer portal catalogue, each with a curated example, example responses, its credential, the usual errors and samples in five languages (no API change, all live in production):
evaluationon Sessions and messages;workflowRunandpendingAwaitReplieson Workflows;knowledgeDocument,resyncKnowledgeDocumentandretrieveChunkson Knowledge bases; and two new pages, Scheduled workflows (createScheduledWorkflow,scheduledWorkflows) and Workspace and usage (myTenant,apiUsageSummary,customerUsageSummary). The Postman collection, OpenAPI file and interactive reference include them.
2026-10-15
Section titled “2026-10-15”- Changed ((live)): failure routing with
success/failureedge labels now works onwebhook,llm,sub_agentandset_variablenodes, not onlytoolandaction. Opt-in by label: a node with neither label behaves exactly as before. Only an error raised by that node itself takes thefailureedge (the trace row isFAILED_HANDLED, the run completes and the message is in_lastNodeError); a routing error, the run budget, the visit limit or an interrupt never does. Afailureedge without asuccessedge is refused at publish. On anllmnode a refused model (not in your workspace’s or API key’s allowed list, or not in the catalog) or a refused image now takes thefailurebranch when there is one, and the run completes. Asub_agentstill reports a failed or timed-out target in itsresultVar; itsfailureedge is for configuration errors only. On arouter,successandfailurestay ordinary route values. - Changed ((live)):
workflowDiagnosticshas a new INFO code,FAILURE_EDGE_ON_UNROUTABLE_NODE, for asuccessorfailurelabel on aresponse,end_session,conditionorswitchnode, where the label has no failure meaning.FAILURE_WITHOUT_SUCCESSandSUCCESS_WITHOUT_FAILUREnow cover all six node types that route failures. - Changed ((live)):
sub_agentinputs and outputs (listed under 2026-10-14) go to production with this release: namedinputsrendered from the master’s context and set as variables in the sub-agent, namedoutputsreturned asvalues(withmissingValuesfor any that were absent or too large), reserved and underscore-prefixed names refused at save, and a bad input failing only its own target. The page now also notes that a duplicated output name and an input template that does not parse are refused at save.
2026-10-14
Section titled “2026-10-14”- Docs: the Sub-Agent Node page now warns that the conversation passed to a sub-agent includes the master’s own earlier replies (which a model can echo) and that
[[...]]marker lines are not stripped from the text it sees. Verified in a live two-turn run of the ticket-specialist example on staging. No behaviour change. - Added ((live)): a
sub_agenttarget can takeinputs(named values, rendered from the master’s context, set as variables in the sub-agent before it starts) andoutputs(names of sub-agent variables returned asvalueson that target’s result entry, withmissingValuesfor any that were absent). Both are optional, so existing workflows are unchanged. A non-LLM node in a sub-agent no longer needs an extractionllmnode to use a value from the master. - Docs: the Sub-Agent Node page now compares sub-agents with router branches in one workflow (which one keeps
channel.raw, costs less, supports multi-turn, and gives per-specialist cost lines), and shows how to give a sub-agent the conversation, since a sub-agent starts with empty history. No behaviour change. - Docs: the Sub-Agent Node page now answers whether a sub-agent can run actions (yes), where an action should live depending on where its inputs come from, and what differs inside a sub-agent (no
channel, noawait_reply, no retry, timeout and depth limits). No behaviour change. - Docs: the Sub-Agent Node page now explains which nodes can use the text a sub-agent receives (
llm) and which need a variable (condition,switch,router, andaction/tool/webhookarguments), and shows how to avoid an extraction call by keeping those nodes in the master. No behaviour change. - Docs: the Read Channel Facts Without an LLM Call recipe now says up front which workflows have
channel.raw(the one a message starts, not a sub-agent) and thatset_variableis optional: readchannel.rawdirectly, and addset_variableonly for shorter names, a number or default, or a node that needs a flat variable name. - Docs: new cookbook recipe, Read Channel Facts Without an LLM Call: replace extraction
llmnodes with oneset_variablenode that readschannel.raw(bracket syntax for dotted keys, a guard for a missing marker, andexcludeHistory/matchAnyOf/modelOverridefor the nodes that still need a model). - Changed ((live)): workflow runs record the published version they executed, and a paused conversation (
await_replyor approval) resumes on the version it paused on, not on a graph published later. See Publishing while a conversation waits. - Fixed ((live)):
sdkEndSessionno longer fails when the evaluation model’s recommendation is longer than 50 characters; it returnsn/afor the recommendation instead. - Docs (correction): the Sub-Agent Node page no longer suggests
set_variablefor pulling a value out of the sub-agent’s opening message.set_variablecomputes from existing variables and cannot split text; use the LLM node reading{{userMessage}}directly, or an extraction LLM node when a non-LLM node needs a variable. - Docs: the Sub-Agent Node page now states that
channel(includingchannel.raw) andattachmentare not available inside a sub-agent, and how to pass those values as text. No behaviour change. - Docs: the Sub-Agent Node page now explains how to use a master variable inside the sub-agent’s LLM node: put it in
contextTemplate, then read{{userMessage}}in the sub-agent. No behaviour change. The Channels page also now shows how to read a dottedchannel.rawkey such assupport.name({{channel.raw.[support.name]}}). - Docs: a read-only Docs MCP Server for coding assistants that speak the Model Context Protocol. Seven tools answer from the developer portal’s generated files, offline: the catalogued operations with every input field, type and allowed value, the published samples in curl, TypeScript, PHP, C# and Python, a keyword search of the docs, the workflow node reference, and the JSON to Flow visualizer’s workflow checks with a code and a fix per finding. It never calls the API and never takes a credential. Not published to npm yet: run it from a checkout of the docs site repository. No API change.
- Docs: a new Interactive API Reference shows the 41 operations of the Developer Portal catalogue on one page, rendered from the OpenAPI file: each operation listed as
/graphql#<operationName>, its credential, variables with types and allowed values, example responses and request samples in curl, JavaScript, PHP, C# and Python. The Try it console is not switched on yet. No API change. - Changed ((live)): a workflow run’s trace now has one row per dispatch. When a node ran but routing then failed (a condition with no edge for its result, a switch with no matching edge, a router with no match and no default, an approval outcome with no edge, a labelled node with no
successedge), that node’s row isFAILEDand carries a newfailurePhase(routing, orpause_persistwhen saving anawait_replypause failed) instead of aCOMPLETEDrow followed by aFAILEDrow. The error text, the run status andNodeFailedEventare unchanged. Runs stored before the change keep the two rows. - Fixed:
createAgentnow checksllmModelOverrideandllmProviderOverrideagainst your workspace’s allowed models, asupdateAgentalready did. Before, an agent could be created on a blocked model and was only refused at its next update. Workspaces with no model restriction see no change. - Changed:
sessionsByExternalCustomerIdreturns at most 100 sessions per page (a largerfirstis clamped to 100, a value below 1 to 1). Page withhasNextPage/endCursor. Before,first: 0could reporthasNextPage: truewith no cursor, and a negative value failed with a server error. - Fixed: for agents with
useCase: CUSTOM, the evaluation returned bysdkEndSessionnow uses the agent’s ownevaluationPromptTemplateandevaluationRecommendationLabels. Before, it used the generic good/fair/poor prompt, while the background evaluation afterendSessionalready used yours. Built-in use cases are unchanged. - Docs:
sdkEndSessionevaluates before it returns (the evaluation is in the response); the troubleshooting and reference pages now say so.
2026-10-12
Section titled “2026-10-12”- Docs: the Developer Portal reference and the downloadable OpenAPI file now describe every field, input, argument and allowed value of the 41 catalogued operations (for example which
sdkStartfields only apply when the workspace has no active agent). The same text appears in the GraphQL explorer on the sandbox. No API behaviour changed. - Docs (live): a new Developer Portal for developers who usually work with REST: a Hello ticket quickstart (start a session, send a message, poll for the reply with
sessionsByExternalCustomerId, end the session) in curl, TypeScript, PHP, C# and Python; an Authentication cheat sheet with the exact message of every credential error; an Errors page with everyextensions.code; and an operation reference for sessions, API keys and approvals with allowed values and example responses. A Postman collection, a Postman environment and an OpenAPI 3.1 file cover the same operations. Everything is generated from one set of curated examples that is validated against the API schema. - Docs (live): the examples now use the sandbox base URL,
https://api.wetel.dev/graphql, stated once on Getting Started; your production base URL is issued when you go live. The speech service address is always theavatarServiceUrlreturned by the API. Core API Flow also had two errors fixed:sdkEndSession’sevaluationselectsscoreandsummary(there is noratingorfeedbackfield), and the session token lives about one hour, not 24. Thex-huat-platformheader is checked right after the credential, not before it, so a request missing both reportsUNAUTHENTICATEDfirst. No API change. - Docs (live): the Developer Portal operation reference now also covers agents, workflows (including
runWorkflowTask,workflowTaskOutcome,workflowDiagnosticsandnodeConfigSchemas), knowledge bases (with the three-step file upload), MCP connectors and custom actions, 41 operations in all, each in five languages and in the Postman and OpenAPI files. A short Route a ticket recipe lists the calls that turn the ticket triage cookbook into a running agent. - Docs: new end-to-end cookbook recipe, Triage a Support Ticket End to End: one complete graph (15 nodes, 19 edges) that collects a ticket with
await_reply, classifies it with anllmnode (matchAnyOf,streamOutput: false), normalises the values withset_variable(includingcoerce: "number"), branches with aswitch, asks a workspace owner to approve urgent escalations with anapprovalnode, replies and ends the session. It shows the run trace (assigned,matchedCase), a draft test withdraftApprovalOutcome, and what eachworkflowDiagnosticscode would mean for that graph. The graph passes the platform’s save and publish checks and can be pasted into the JSON to Flow visualizer or the editor’s Import dialog.
2026-10-11
Section titled “2026-10-11”- Added (live in production since 2026-10-13): a workflow run trace id.
WorkflowRunDto.traceId(a UUID,nullfor older runs) is shared by every run of one logical execution: a new id per ordinary turn, the paused run’s id when a run continues anawait_replyorapprovalpause, and the parent’s id for asub_agentchild run. The same value is sent as thex-wetel-trace-idrequest header on the run’s outboundwebhook,actionand MCPtoolcalls, so a request in your logs can be matched to the run. A header of the same name that you configure yourself is kept. Additive: existing queries, graphs and callers need no change, and a server that ignores unknown headers is unaffected. It is never shown to end users. - Added (live in production since 2026-10-11):
approvalstakes an optionalstatuses: [ApprovalStatus!]argument that lists requests in any of several states in one newest-first list, for example every decided request withstatuses: [APPROVED, REJECTED, EXPIRED, CANCELLED]. Paging (first,after) is unchanged. Sending bothstatusandstatuses, or an emptystatuseslist, answersBAD_REQUEST; duplicates are ignored. With neither argument the inbox still lists onlyPENDINGrequests, so existing callers need no change. - Added (live in production since 2026-10-11): a
switchworkflow node: 1 to 10 cases, each alabeland anexpressionin the condition node’s expression language, tested in order. The first true case routes along the edge labelled with its label; when none is true the run follows the edge labelleddefault. Publishing requires exactly one edge per case label and exactly onedefaultedge, and refuses any other edge label (exceptloop_exhausted) and reserved case labels. A case that names a variable the run does not hold counts as false, like a condition. The trace row recordsmatchedCase,matchedCaseIndexandswitchUnresolved.nodeConfigSchemasreturns 13 entries. See Switch, router or condition? for which branching node to use. - Changed (live in production since 2026-10-11):
workflowDiagnosticsno longer reportsLLM_EXTRACTION_REGEX_HELPER. It was an INFO note that said “no action needed” whenever anllmnode usedmatchAnyOf,extractFirstIntegerorextractAlphanumericIdcorrectly.LLM_HELPER_IGNORED_WHEN_STREAMING, the real warning for those helpers, is unchanged. If your tooling switches on codes, treat an unseen code as a generic finding, as before. - Changed (live in production since 2026-10-11): the JSON to Flow visualizer now draws every node type, including
await_reply,approvalandset_variable, with a type chip, a colour-coded border, a one-line summary per node, labelled and coloured edges, and a dotteddefaultedge for a router’sdefaultTarget. Its Findings panel runs the same checks asworkflowDiagnostics. The workflow editor in the Wetel app shows the same look, aset_variablepalette entry and form, and a Checks strip with the draft’s findings. - Added (live in production since 2026-10-11): a
set_variableworkflow node that writes context variables without calling a language model. Each of its 1 to 20 assignments is anexpression(the condition node’s expression language), atemplate(Handlebars text) or aliteral, with optionalcoerce(number,boolean,string,json) and, for expressions, adefaultused when a referenced variable is not set (without one, that case fails the run, unlike a condition).coerce: "number"stores a real number, so a later condition compares numbers rather than text. It replaces anllmnode used only to copy or convert a value. The run trace row lists the variables it wrote (assigned).
2026-10-10
Section titled “2026-10-10”- Added (live in production since 2026-10-11):
workflowDiagnostics(id, published), a read-only query that lists non-blocking findings about a workflow graph: a router with no fallback, a router edge that is never followed, a dotted router variable that reads into LLM text, a condition missing a branch, an extraction option a streamingllmnode ignores, two parallel paths meeting at one node with no join, unreachable nodes and more. Each finding has a stablecode, aWARNING/INFOseverity and a message naming the node and what to do. It checks the draft by default (published: truechecks the published snapshot) and never blocks saving or publishing. See Workflow Diagnostics and the API reference. - Changed (behaviour, live in production since 2026-10-10): a
routerwhose value matches no outgoing edge label and that has nodefaultTarget(and no tripped circuit breaker) now fails the run at that node: aFAILEDtrace row whoseerrornames the variable, the value and the available labels, aNodeFailedEvent, and the standard failure reply. Before, the run ended with no reply and no error. A router with adefaultTargetbehaves as before. Set adefaultTargeton every router. - Added (live in production since 2026-10-10): a
router’svariablecan be a dotted path into an object in the run context, for exampleanalysis.severitywhenanalysisholds an object. If the first part of the path holds plain text (anllmnode stores its answer as text, and the platform does not parse it), the router cannot read inside it: the node’s trace row now carries arouterNoteexplaining that, so write the value to its own variable with a separate step instead.
2026-10-09
Section titled “2026-10-09”- Added (live in production since 2026-10-09):
partnerAgentTemplatesreturns each slot’sslotKey, so you know which key to pass inslotBindingsorpartnerBindAgentTemplateSlotbefore hiring. The workspace gallery still returnsnullfor it. See Hiring role templates with an API key. - Added (live in production since 2026-10-09): partners can hire role templates with an API key.
createNamedApiKeytakes optionalscopes(templates:hire, owner only); with that scope a key can callpartnerAgentTemplates,partnerHireAgentTemplate(synchronous, idempotent, 200 active roles and 50 hires per day per key by default),partnerAgentTemplateInstall(s)andpartnerBindAgentTemplateSlot, and sees only the installs it made itself.slotBindingson the hire binds shared custom actions, MCP connectors and knowledge bases instead of creating new ones. Templates have an audience (gallery, partner or both) that Wetel sets. See Hiring role templates with an API key.
2026-10-08
Section titled “2026-10-08”- Docs: the AI Coding Assistant Skills file now describes Wetel as an enterprise AI workforce platform and adds the rules the live docs depend on (number gates with
> 0,streamOutput: falsefor extraction, approval as a step you add, draft tests and secrets in templates, what an API key can do) plus guidance for the assistant itself: never print keys, ask before publishing, and do not claim capabilities the docs list as not built. If you copied the older skill, replace it. - Docs (correction): the skill file listed 11 workflow node types without
approvaland said an API key only starts sessions; it now matches Authentication. The Workbench page now says the platform header is required. Troubleshooting said a secret in a rendered request shows “unredacted”; recognisable secret shapes are masked on a best-effort basis, but a raw secret in a template can still appear. The Agents page lists all fivevoiceTiervalues. - Docs (correction): an API key cannot create or change agents or workflows (those need a dashboard login); Multi-Tenancy said a key grants full access to agents and workflows, and the Authentication credentials table said a key only starts sessions. Both now describe the runtime operations a key can run (starting sessions, workflow tasks, model and embedding calls). The
generateApiKeycurl example sent an API key as the bearer token; the mutation needs a login access token. The Admin API panel guide said usage is billed through Wetel; Wetel reports usage and does not bill it yet. - Docs (correction): the order-lookup
extract_order_idstep in Worked examples setextractFirstIntegerwithoutstreamOutput: false. Anllmnode streams by default and a streaming node ignores the extraction options, so the order number was never extracted and theorderId > 0check could not pass. The example now setsstreamOutput: false. If you copied it, add that line to your extraction step. - Docs (correction): the order-lookup example in Worked examples gated on
orderId != '0'. Since 2026-09-11extractFirstIntegerstores the number0when nothing is found, and a condition does not coerce types, so that gate let a “no order number” message through to the lookup. It now readsorderId > 0. If you copied the old expression, change it. The order-support recipe no longer calls the extracted value a string. - Docs (correction): Pricing said the monthly spend cap covers only the direct language-model API. It is also checked before each chat turn, workflow task (scheduled runs included) and playground call. It is a check made before work starts, so work already under way can carry spend past it, and speech and tool-call usage does not count toward it. The wording “exact” for speech and tool-call credits now reads “calculated from fixed per-unit rates”, because credits are a usage figure and not an invoice.
- Docs (correction): the Pricing, Multi-Tenancy and Core Concepts pages said or implied that a dedicated database per workspace is available. It is designed but not currently offered; workspaces share the platform’s database and every request is scoped to the signed-in workspace. The Voice Messages page listed the old four-language fallback set; the default is English and Malay (changed 2026-09-29). The Roadmap no longer lists hiring a role yourself as future work, since owners can already do it (2026-10-04).
- Added (live in production since 2026-10-09):
runWorkflowTaskaccepts an optionalattachmentslist (up to 10 files, the same shape as onsdkSendMessage) next to the singleattachmentUrlfields; sending both is a400. Changed (stricter): the singleattachmentUrl/attachmentMime/attachmentFilenamefields now go through the same validation asattachments:attachmentMimemust be a validtype/subtype(it is lowercased; a malformed one answers400 attachmentMime is not a valid MIME type), the URL must not carryuser:password@, and the filename loses quotes, brackets and control characters. Regression scenarios now capture and replay structured attachments andturnContextas structured input (no marker is written into the text). - Added (live in production since 2026-10-09):
sessionEvents(sessionId, sinceMessageId). A reply published while your websocket was closed is no longer lost: pass the id of the last model message you processed (0on the first subscribe) and the server first replays the stored replies you missed (newest 20, oldest first), then continues live, delivering each reply once.AiResponseEventgainsmessageId. Omit the argument for the old live-only behaviour. See Reconnecting without losing a reply. - Added (live in production since 2026-10-09): messages now store their structured attachments. The message
attachmentsfield returns each attachment with its URL’s query string and credentials removed (?[redacted]), andimageMarkerHistoryon anllmnode reads structuredIMAGEattachments from earlier user messages as well as[image:]markers. Changed: in a run’s context (workflowRuns),attachments[].url,attachmentUrlandattachmentImageUrlsare now returned with the query string removed, so a signed link can no longer be copied back out of the run view; the stored value is unchanged and nodes still fetch the full URL. See Sending files and context. - Added (live in production since 2026-10-09): structured attachments and context on the SDK.
sdkSendMessageandsendMessagetake optionalattachments(up to 10 files:url,mime,filename,kind) andturnContext, andsdkStarttakes an optionalcontext. Workflows see them asattachments,attachmentImageUrls(usable directly as anllmnode’simageUrlsVar),turn.contextandsession.context. Empty message text is allowed when an attachment is present. Text markers keep working and are overridden by structured attachments.attachments,attachmentImageUrlsandturnare now reserved workflow context names. See Sending files and context. - Added:
WorkflowRun.continuedByRunId: on a run that paused at anawait_replyorapprovalnode, the id of the run that continued it (the inverse ofresumedFromRunId). It isnullwhile the run is genuinely still waiting, so a dashboard can tell a waiting run from one that was already continued; the paused run’s status staysAWAITING_INPUT. Returned byworkflowRunandworkflowRuns. - Changed, live in production: the Gemini speech model behind
POST /stt(andPOST /stt/gemini) is nowgemini-3.5-flashinstead of the dedicatedgemini-3.5-transcribe, which Google caps at 100 requests a day per project. No request or response change. Transcript wording can differ slightly for the same audio. In a 48-hour test on staging, 99.6% of 1,382 clips succeeded, 1 fell back to Cloud Speech-to-Text, none came back empty, the median call took 1.65 s and the 95th percentile 3.2 s.
2026-10-07
Section titled “2026-10-07”- Changed: a “not found” error now answers
extensions.code = NOT_FOUNDand a conflict answersCONFLICT(both used to arrive asINTERNAL_SERVER_ERROR, with the real HTTP status only inextensions.status, which is still there). The message is unchanged.BAD_REQUEST,UNAUTHENTICATEDandFORBIDDENare unchanged, and rate limits (429) and server errors keepINTERNAL_SERVER_ERROR. If your client treatsINTERNAL_SERVER_ERRORas retryable, a missing record or a conflict is no longer retried, which is what you want. See Error handling. - Added: the
approvalworkflow node and the approvals API: a workflow can pause until a workspace owner approves or rejects a step, then continue down theapproved,rejectedorexpirededge. New operationsapprovals,approval,decideApproval,cancelApprovaland the subscriptionsapprovalOpened/approvalDecided(owner-only), plusdraftApprovalOutcomeonsendMessagefor draft tests. Email notification is not available yet. Step by step: Human approval: a walkthrough. - Changed:
sdkSendMessage’s andsendMessage’stextlimit raised from 10,000 to 100,000 characters (still rejected, not truncated, withtext must be shorter than or equal to 100000 characters). The model sees only as much history as fits its token budget, so very long messages leave less room for earlier turns. If your own code also caps the text you pass along (for example a prompt-building step with its own limit), raise that too. - Changed: input-validation errors now say what is wrong. The response is still HTTP 200 with
extensions.code = BAD_REQUESTandextensions.originalError.messageas the array of constraint messages, but the top-levelerrors[0].messageis those messages joined with;(for examplename should not be empty) instead ofBad Request Exception, on the main API and the portal API. Template keys must be 3 to 64 characters of lowercase letters, digits and dashes, starting and ending with a letter or digit (pattern^[a-z0-9][a-z0-9-]{1,62}[a-z0-9]$, for examplepartner-image-reader); a dot is refused with a message naming the pattern. See Troubleshooting. - Added:
createNamedApiKeyalso returns the key’sidandname(the key itself is still shown once); pass theidtorevokeNamedApiKeyto revoke it. See Authentication. - Changed: workflow node
typevalues must be lowercase (start,llm,response, …); an uppercase one is refused at save and publish withBAD_USER_INPUT(node n1: type "START" must be lowercase: "start"), and publishing a graph with no start node lists the node types it saw. Previously an uppercase type was only caught at execution. - Fixed: setting
llmModelOverrideon an agent withoutllmProviderOverridenow runs the agent on the provider that owns that model (a Gemini model name with the default provider used to fail every image node).createAgentandupdateAgentrefuse a model no enabled provider serves, and a provider that contradicts the model, withBAD_REQUESTand a message that lists the allowed choices. See Agents. - Docs: the LLM node page now says
matchAnyOfreplaces the output with the matched label, so it is only for single-label classification nodes, and how to handle JSON wrapped in a markdown code fence. The photo-reading recipe notes that the SDK credential cannot readworkflowRunsor the serving model.
2026-10-06
Section titled “2026-10-06”-
Changed: invalid
llmimage options are now refused withBAD_USER_INPUT(wasINTERNAL_SERVER_ERROR) when you callupdateWorkfloworpublishWorkflow, with a message naming the node id, the field and the allowed range.imageMarkerHistorymust be an integer from 1 to 40,imageUrlsVara non-empty string,imageUrlsFailPolicyskiporfail, andimageVarsan array of non-empty strings. See LLM node. -
Changed: signed URLs in
[image:<url>]markers are still stored exactly as you send them (the node needs them to fetch the image), but when the message text is read back through the API (session messages,workflowRunscontext, the portal) the marker is shown as[image:https://host/path?[redacted]]: scheme, host and path are kept, userinfo, query and fragment are removed. Text outside a marker is not rewritten, and your own conversation-sync integration still receives the original text. -
Added: Recipe: Read Many Photos in One Node, a live-verified step-by-step walkthrough of the
llmnode URL image options: requests and responses, theimageFetchtrace, limits, a migration note and a troubleshooting table. -
Fixed (docs):
voiceTieris optional oncreateAgent(it defaults toSTANDARD), not required. A missingx-huat-platformheader on a real operation answers HTTP 200 withextensions.code = FORBIDDEN, not an HTTP 403. The docs now say which credential each management call takes (a dashboard JWT forcreateAgent,publishWorkflowandworkflowRuns; the API key forsdkStartandrunWorkflowTask), thatrunWorkflowTaskvariablesmust be flat scalars (send animageUrlsVarlist as a JSON string), and that an out-of-rangeimageMarkerHistorywas then reported asINTERNAL_SERVER_ERROR(nowBAD_USER_INPUT, see above). -
Changed: the URL image options (
imageUrlsVar,imageMarkerHistory,imageUrlsFailPolicy), server-side image shrinking and the 40-image default are now live on production as well as staging. After tuning, the shared fetch deadline for the whole fetch-and-shrink phase is 90 seconds (was 30) and each server shrinks three images at a time (was two). Measured with 40 large phone photos (about 253 MB of originals, 12.6 MB after shrinking): alone, 40 of 40 attached in about 54 s; 2 overlapping runs attached 40 and 40 in about 113 s each; 4 overlapping runs attached 38 to 40 in 109 to 129 s each. With the old 30 s and two at a time, 2 overlapping runs attached only 23 and 25 of 40. See LLM node. -
Changed:
llmnode images fetched by URL are now shrunk for you instead of skipped when large. Originals up to 20 MB are fetched, auto-oriented and shrunk to a long edge of about 1568 px (typically 200 to 600 KB each; an image already within the limits is passed through untouched), soimageUrlsVarandimageMarkerHistorynow take up to 40 images per node by default (imageMarkerHistory1 to 40).imageVarskeeps its cap of 20. Originals over 20 MB are skipped (too-large) and so are images over 36 megapixels (too-many-pixels). After shrinking, the 5 MB per image (about 3.9 MB on the Claude route) and 18 MB per call limits apply, or 24 MB when the node pins a Gemini model. The fetch deadline is now 90 seconds (see the first entry of this day), and a run with 40 large photos can take about a minute or more, so keepimageMarkerHistoryas small as the question needs. The earlier advice to resize images before sending them is no longer required. -
Fixed: per-IP rate limits now count each caller’s own address. The platform used to see a Cloudflare edge address for every request, so callers that happened to share an edge could share a count, and a limit such as the 5-per-minute sign-in limit was not really per caller. Limits are unchanged; only whose requests are counted together changed. Callers behind one office network or one server still share a bucket. Per-tenant ceilings are unaffected.
-
Added: an
llmnode can fetch images by URL itself.imageUrlsVarnames a context variable holding one or more image URLs, andimageMarkerHistoryattaches the images named by the last N[image:<url>]markers in the user’s messages. Until now each image needed its own fetch step (actionwithfetchAsBase64Var) feedingimageVars; the URL options replace that, and the image bytes are never stored in the workflow context or run record. Onlyhttps://ands3://(granted buckets) URLs, no redirects, no private addresses; each image must be a real JPEG/PNG/GIF/WEBP of at most 5 MB (about 3.9 MB on Claude models through the default provider), a 90-second deadline for the fetch (30 seconds when this was first released), and 18 MB per node in total. Update, same day: large images are now shrunk instead of skipped, and the limit is 40 images (see the entry above). An image that cannot be loaded is skipped and the model is told ([note: 1 of 3 images could not be loaded]);imageUrlsFailPolicy: "fail"fails the node instead. The node trace records counts, sizes, hosts and reasons, never the URLs. ExistingimageVarsgraphs are unchanged.
2026-10-05
Section titled “2026-10-05”- Fixed: the per-address rate limits on the speech routes (
/stt,/stt/google,/stt/gemini,/tts,/tts/google,/tts/gemini) counted every caller on the platform together, because the service saw only its reverse proxy’s address. One integration’s burst of voice notes could return429to unrelated customers. The service now uses the caller’s address, and the defaults are 120 requests a minute for speech-to-text and 300 for text-to-speech per address (were 10 and 30); the per-tenant ceilings (200 and 600) are unchanged and are the ones to plan around. See Avatar Service REST API: rate limiting. - Added:
speechTokenreturns a credential for speech-to-text and text-to-speech (/stt,/tts) without starting a session. Before this, the only way to get a token for those routes wassdkStart, which creates a metered session and is limited to 30 starts a minute per client IP. Works with a dashboard JWT or anX-Api-Key. The token is tenant-scoped and rejected by every session operation. See the Speech-to-Text and Text-to-Speech Integration Guide. - Fixed: the
avatarTokenquery has been failing since tokens were bound to sessions (2026-09-08). It now returns the same speech token asspeechTokenand is deprecated in favour of it. - Fixed:
POST /stt/googlewith aLINEAR16WAV file that was not 16 kHz (Gemini text-to-speech output is 24 kHz, most phones record 44.1 or 48 kHz) returned500. The sample rate is now read from the WAV header, as it already was forOGG_OPUS. For a WAV the header wins over an explicitsampleRateHertz; for raw PCM the explicit value is still used. - Changed: a request the speech provider rejects as invalid (audio that does not match its declared encoding or sample rate, input too long) now returns
400instead of500on every/sttroute, so you can tell your request from a provider outage. Do not retry a400unchanged. - Changed: an
llmnode’simageVarsnow accepts up to 20 images per call, up from 4. Each image is still under about 5 MB and all images on one call under 18 MB combined. Entries beyond 20 are dropped, not rejected. A workflow that listed more than 4imageVarsused to have the extras ignored and now sends them, so it uses more of the shared model quota.
2026-10-04
Section titled “2026-10-04”- Added: a Wetel administrator can now restrict a single API key to a list of models, on top of the tenant-wide list. A key can only narrow what the tenant allows; sessions started with the key keep the restriction for every later turn, workflow LLM node and sub-agent. Set on request; documented 2026-10-06.
- Added: workspace owners can hire a role themselves.
agentTemplateslists the roles Wetel has made available,hireAgentTemplatehires one into your workspace (owner only, PRO plan for now, 10 roles at once and 5 hires per day), andprovideAgentTemplateSlotCredentialfinishes a waiting setup step by giving its credential. Credentials are write-only. Nothing appears in the gallery until Wetel lists a role, so this changes nothing for existing integrations. - Added: every user now has a
tenantRole(OWNERorMEMBER) inside their workspace, readable on the user object. It is set from WebbyX One’s company ownership at sign-in, or toOWNERfor the creator of a workspace, and is what the owner-only operations above check.
2026-10-03
Section titled “2026-10-03”- Added:
agentActivitySummary, a per-AI-employee outcome roll-up for supervision dashboards: runs by status, recovered failures, average evaluation score, open questions waiting on a person, schedule health and aneedsAttentionflag, for the whole tenant or one agent over a preset or custom window (up to 92 days). JWT only, 30 requests per minute per client IP.creditsUsedandscheduleFiresarenullfor now. - Fixed: the opening greeting, the direct (non-workflow) streamed reply and the evaluation run when a session is ended through
sdkEndSessionwere never counted in your usage totals or against the monthly spend cap. They are now, soapiUsageSummaryand the cap can read slightly higher than before for tenants using those paths. - Fixed:
timeoutMsonaction,webhook,toolandsub_agentnodes (and the media fetch) now has a floor as well as a ceiling.0, a negative number or a non-number used to disable the timeout on some nodes; it now means “unset” and the default applies, and a value below1000is raised to1000.toolnodes now also enforce the 30-second ceiling their docs always stated. - Docs fix:
expiresInandrefreshExpiresInon the login responses are absolute Unix epoch seconds, not durations. The example responses showed duration-like values; they now show epochs. - Changed: a knowledge-base document that would exceed your plan’s chunk limit is now rejected before it is embedded, so a rejected upload no longer consumes embedding usage.
confirmKnowledgeDocumentUploadis now rate limited to 10 requests per minute per client IP, the same asrequestKnowledgeDocumentUpload. - Changed:
isRegisterableno longer checks national identity numbers (it could confirm that a given identity number was registered, to anyone). TheidNoargument is deprecated and a request that sends it is now rejected; the response is{ "email": boolean }. Nothing that sends onlyemailis affected. - Changed: when a tenant is suspended, new work is now refused on every path, not only for dashboard sign-ins.
sdkStart,sdkSendMessage,sendMessage/startSession, the LLM API (generateText,startTextGeneration),runWorkflowTaskand scheduled workflow fires all return403(Tenant #N is suspended.), and scheduled workflows resume on their own once the suspension is lifted. Ending a session is still allowed so in-flight conversations can close. - Added: agents have a
timezone(IANA, defaultAsia/Kuala_Lumpur), and workflow templates have{{today}}and{{now}}helpers that resolve in it, daylight saving included. See Today’s date and the current time.agentTimezoneis now a reserved workflow variable name. - Added:
workflowTaskOutcome(sessionId)reads back the result of arunWorkflowTaskcall with the same API key: the newest run’s status, the runs, a masked error for a failed run, and the agent’s replies. Until now the only way to read a task’s outcome needed a dashboard login. It never returns the run context or node trace, and only sessions a task was accepted for are readable. - Changed: a session that has had no activity for 30 days is now moved from
ACTIVEtoEXPIRED(previously abandoned sessions stayedACTIVEforever, which inflated active-session counts and usage figures). No evaluation is run and no event is sent.runWorkflowTask,sdkStartand the channel gateways already start a fresh session when noACTIVEone matches, so a conversation that returns later is unaffected; a staleavatarTokenfor an expired session gets the existing “session is not active” error. - Added:
testCustomActionmakes one real call of a custom action through the same path a workflow uses and classifies the result (CREDENTIAL_REJECTEDfor a 401/403,REQUEST_REJECTED,SERVER_ERROR,TIMEOUT, …), so a wrong URL or credential is found before a live conversation hits it. A non-GETaction needsconfirmWrite. A successful test is billed like the same call in a workflow; a failed or refused test is free. - Changed:
agentConfigOptions.recommendedTtsVoicesnow lists 24 voices (was 6): en-US across all five tiers (Standard, WaveNet, Neural2, Chirp3 HD, Studio), en-GB, ms-MY and Mandarin (cmn-CN), each name checked against Google’s own voice list. Each voice has a newvoiceTierfield, the value to send as the agent’svoiceTier, so you can offer only the voices that fit the chosen tier. Existing queries are unaffected. - Fixed: a workflow node can no longer use
session,attachment,attachmentUrl,attachmentMime,attachmentFilename,channelorclientTurnIdas itsoutputVar,saveAsor similar variable name; saving such a workflow now fails with a “reserved workflow context key” error. A WEBHOOK node’s optionaloutputVaris checked too. Rename the variable if you hit it. - Added, live in production: agents that Wetel sets up for you from a role template now come with a setup checklist.
agentTemplateInstallshows what is left to finish, andbindAgentTemplateSlotfinishes a slot with a resource you created yourself (a custom action with your own token, or your own MCP connector). When the last required slot is bound the agent turns on. Your credentials are never handed to Wetel. - Behaviour change for those agents only: an agent that is still being set up refuses
sdkStartand channel connections with aForbiddenerror that says so. Agents you created yourself are unaffected.
2026-10-02
Section titled “2026-10-02”- Added, live in production: an administrator can now restrict a tenant to a list of LLM models (for example Claude Haiku only). The
llmModelCatalogquery returns only the allowed models,myTenant { allowedLlmModels }shows the list (nullmeans no restriction, the default), and a blocked model is refused with a clear error ongenerateText, the playground andllmnodes, and when saving an agent or publishing a workflow. Nothing changes for any tenant until a list is set. See Restricting which models a tenant can use. - Fixed, live in production: requests with a body over about 1 MB (for example an LLM request carrying a large base64 image) were rejected with HTTP 413 by the gateway, which browsers showed as a CORS error. The limit is now 10 MB. See the LLM API request size note.
- Docs (new page): Choosing Languages and Voices explains primary vs alternative speech-to-text languages, the three-alternative limit, the speech tiers, and how a voice name relates to language and tier. It links to Google’s own language and voice lists as the source of truth. Also corrected the
/sttpage, which said Mandarin and Cantonese “will be removed” from the default fallback set; they have not been in it since 2026-10-01. - Changed, live in production:
POST /sttnow transcribes with Gemini by default and falls back to Cloud Speech-to-Text on any error. Expectconfidence: 0(“not reported”) and yourlanguageCodeechoed back when Gemini answers.POST /stt/googlestill gives the previous behaviour exactly. - Added, live in production: the
WEBBYXAI_HAIKUprovider can serve Claude Sonnet 4.6 and Claude Opus 4.6 (with-low,-medium,-highand-maxeffort variants) throughmodelOverride,llmModelOverrideandgenerateText. Same per-token prices as Anthropic’s list (Sonnet $3 / $15, Opus $5 / $25 per million tokens in / out). They share the same model quota as Haiku, so use them sparingly.
2026-10-01
Section titled “2026-10-01”- Added, live in production since 2026-10-01: the default model provider (Claude Haiku) and the other Claude models it serves now read images. An
llmnode’simageVarsworks on a Haiku agent with no provider change, up to 100 images per call (each under 5 MB as base64). The per-node limit stays 4 by default and the platform operator can raise it to 20, with 18 MB combined. Gemini is still available per node throughmodelOverrideand answers automatically if an image is refused. On a quick digit-reading check Haiku scored 9 of 10 and Gemini 10 of 10. - Added, live in production since 2026-10-01:
POST /tts/gemini, an optional Gemini text-to-speech route for voice notes. WAV output, no word timings (not for lip-sync), about 3 to 4 seconds per reply./ttsand/tts/googleare unchanged. - Behaviour change, live in production since 2026-10-01:
end_session’srunEvaluationis now honoured, and leaving it out means no evaluation. It was always documented as an opt-in but the platform ignored it, so everyend_sessionevaluated and a node that saidrunEvaluation: falsestill did. Bothfalseand an omitted field are live. If your workflow omits the field and you read its evaluations, add"runEvaluation": true. Sessions ended throughsdkEndSessionstill return their evaluation. Chat flows that end a session after each exchange save up to two model calls per session. - Added, live in production since 2026-10-01:
POST /stt/geminiandPOST /stt/googlelet you pick the speech-to-text engine per call./stt/geminiuses a Gemini speech model: faster in our checks, capitalised and punctuated text, no language hint needed.confidenceis0(“not reported”) and the responselanguageCodeechoes your request. PlainPOST /sttis unchanged and keeps using Cloud Speech-to-Text unless the operator changes the default. - Added, live in production since 2026-10-01: an
llmnode’smodelOverridecan now name a model another provider serves, and that one node runs on that provider. Use it to give a single image node a Gemini model inside an agent that otherwise runs on Haiku. Before, naming another provider’s model failed the node. A model no provider serves still fails it.
2026-09-30
Section titled “2026-09-30”- Added, live in production since 2026-10-01:
runWorkflowTaskand scheduled workflow fires now leave an invocation record (agent, source, yourexternalConversationId, the names of the variables sent, accepted or rejected with a fixed reason code, and a link to the resulting run) so support can trace what an integration fired. Variable values, attachment details and error text are never stored. No change to the mutation’s arguments or result, and nothing for you to do. If you put something sensitive inexternalConversationId, stop: it is now stored as sent. - Added, live in production since 2026-10-01:
myAuditLogs, the signed-in user’s own recorded actions in their workspace, newest first, with cursor paging and optionalauditableTypeandactionfilters. Only field names are recorded, never values, and you only see your own actions. Recording now covers agents, API keys, workflows (including publish), custom actions, MCP connectors, channel connectors, scheduled workflows, knowledge bases and workspace settings; before, only agent updates were recorded. Additive: existing queries are unaffected. - Behaviour change, live in production since 2026-10-01:
agentAuditLogsno longer returns values for credential-like agent fields.externalSyncUrl,externalSyncSecretRef,externalSyncOwnerId,externalSyncSchemaConfigandavatarBackendConfigcome back as{ from: null, to: null }, including in older entries, and are no longer stored. Prompts and other ordinary settings still show their old and new values. The history also now includes the agent’s creation and deletion, which carry no field values. - Added, live in production since 2026-10-01:
apiUsageSummarybreakdownrows gaincreditsUsed: Int!, the credits each(model, operation)item consumed in the selected month. It uses the same rule as the item’s bucket and the rows in a bucket sum exactly to that bucket’screditsUsed(LLM credits are approximate; tool calls, speech and avatar are real charges).costUsdis unchanged and still returned. Additive, so existing queries keep working; selectcreditsUsedonly once your target environment has this release, since an unknown field fails the whole query. - Added, live in production since 2026-10-01:
sessionActivitySummariescan now be sorted on the server. New optional argumentssortBy(CREATED_AT,STARTED_AT,ENDED_AT,STATUS,CLIENT_EXTERNAL_ID,AGENT_NAME,EVALUATION_SCORE) andsortDirection(ASC,DESC). Rows with no value for the sort field come last in both directions, and pagination cursors are tied to the sort they were issued under. Without the new arguments nothing changes: newest first, and existing cursors keep working. Last-message time and latest-run status can’t be sorted yet. - Behaviour change + added, live in production since 2026-10-01:
apiUsageSummarybreakdownis now scoped to one calendar month. It used to count every call since the tenant was created, even though the dashboard labelled it “this month”. It also no longer truncates model names that contain a colon. New optional argumentyearMonth: String(YYYY-MM, past or current month) scopes every figure in the response to that month, so you can look at previous months. New fields:yearMonthon the summary andcostUsdon each breakdown row. Existing field names are unchanged. If you were readingbreakdownas lifetime totals, sum it across months yourself. - Live in production since 2026-10-01: Per-agent speech-to-text languages.
Agent,createAgentandupdateAgentgainsttLanguageCode(the STT primary;null= usettsLang) andsttAlternativeLanguageCodes(null= platform default English + Malay,[]= primary only, otherwise at most 3 entries, no duplicates, not containing the primary, BCP-47 shaped). SendingnullonupdateAgentresets a field to its default. Telegram voice notes now honour both fields. To bring Mandarin detection back for an agent, setsttAlternativeLanguageCodes: ["cmn-Hans-CN"]. - Live in production since 2026-10-01:
sdkStartandstartAvatarDemoSessionreturn two new fields,sttLanguageCode: String!(already resolved: agent setting, elsettsLang, elseen-US) andsttAlternativeLanguageCodes: [String!]. Both are additive.<vai-avatar>gains matchingstt-langandstt-alternative-langsattributes. - Behaviour change, live in production since 2026-10-01:
<vai-avatar>used to always senden-USas the STT primary. Whenstt-langis not set it now followstts-lang. Pages that set neither attribute are unaffected. Setstt-langexplicitly if your agent speaks one language but callers speak another.
2026-09-29
Section titled “2026-09-29”- Live in production since 2026-10-01:
POST /sttnow carries a fallback re-check guard: when a fallback language wins the recognition but produces an empty transcript or mixed-script garbage (a small Han fragment plus English text), short clips (under ~50 seconds) are automatically re-checked once in the primary language only. If the primary-only result is at least as good, it replaces the fallback’s result. The response now includesattempts: 1orattempts: 2so you can meter this — each recognition call is billed per 15-second audio block. Language reporting is now standardised: unrequested Chinese winners are reported in BCP-47 casing (cmn-Hans-CN,yue-Hant-HK) instead of all-lowercase; and requested Chinese languages are reported the way you spelled them in the request (sozh-CNcomes back aszh-CN, not normalized tocmn-Hans-CNor anything else). Known limits: (1) Cantonese on a Mandarin primary counts as the primary and is never retried. (2) Japanese fallbacks are re-checked only for empty transcripts, not mixed-script. (3) Genuine code-switching (Mandarin with English phrases woven in) can trigger a re-check but the original is kept unless the primary-only result is at least as confident.alternativeLanguageCodesis now protected: any Chinese variant (zh,zh-CN,zh-TW,zh-HK,cmn-*,yue-*) in the list is guarded by the re-check, even if your primary is not Chinese. - Already live: The default fallback language set for
POST /sttchanged from[en-US, cmn-Hans-CN, ms-MY, yue-Hant-HK]to[en-US, ms-MY]only (shipping 2026-09-29, now live in production and staging). Mandarin and Cantonese were removed because they trigger false-positive language misclassifications on accented English clips — even fluent English like"Hi testing one two three"the provider sometimes tags ascmn-Hans-CNoryue-Hant-HK. The language-tag correction (fall back to non-Han when the winning language’s script doesn’t match its transcript) still exists, but removing them from the defaults prevents the mislabel entirely. If your integration needs Chinese detection, pass it explicitly:"alternativeLanguageCodes": ["cmn-Hans-CN"]or["yue-Hant-HK"](or both, using up to 3 slots). The same changes apply to the LLM Playground’stranscribeSpeechquery. - Already live, undocumented:
POST /stt’sOGG_OPUSencoding now auto-detects sample rate from the file header itself — thesampleRateHertzfield is now genuinely optional for OGG/Opus files. Different source apps (Telegram, WhatsApp, etc.) record at different real rates, and guessing one value could never be right for every source. If you were passingsampleRateHertzexplicitly as a workaround for empty-transcript results on some sources, you can now leave it unset and let the provider read the real rate from the file.
2026-09-28
Section titled “2026-09-28”- Added: Lark joins OmniChat/Nortia/aisCRM as a certified-operation product —
certifiedOperations(product: "LARK"). Unlike the other three (Wetel’s own shared house accounts), Lark is a per-tenant app integration — bring your own Lark app (larkAppId/larkAppSecret, already self-serve viacreateCustomAction, see Using Lark) and certified Lark operations work against it directly, no WebbyX One sign-in step needed. Shipped same-day as 14 operations, not just one: Messaging (Send Message — execution-verified live against Lark Suite’s international API; Reply to Message, Get Message), Chats (Create Group Chat, Get Chat Info, Add Chat Member), Contacts (Get User Info, Batch Get User by Email/Mobile), Calendar (List/Create Events), Approval (Create/Get Instance), and Bitable (List/Create Records — Bitable’s own request/response envelope is fixed and certifiable even though a tenant’s table columns aren’t). Everything past Send Message is schema-verified against Lark’s official public API reference rather than execution-verified, since a live write there (a real group chat, calendar event, approval instance, or Bitable row) has no clean rollback — check an operation’s ownverificationNote. - Changed (every session, read this if you send messages quickly): A new message now supersedes a turn that is still in progress on the same session. Before, the two ran at the same time, and the earlier one’s reply could arrive last, answering a question the user had moved past. Now the earlier turn stops at its next node boundary (or next streamed sentence, for a direct non-workflow agent). Its reply is neither published nor saved to the transcript, and only the newer message is answered. No
InterruptedEventis sent for a supersede, and the superseded workflow run is recorded asINTERRUPTED. Three things to check on your side. (1) If you correlate replies byclientTurnId, a superseded message never gets one, so stop waiting for it once a later reply arrives. (2) A turn that has already sent its first reply (an early acknowledgement from aresponsenode) is never superseded and runs to completion alongside the newer message. A node already running, such as anactioncall, always finishes too. (3) The residual to design around: a workflow that performs a side effect before saying anything can have that step skipped if the user double-texts first. Put aresponseacknowledgement before any action that must not be skipped. ExplicitinterruptSessionbarge-ins now also drop a reply still being generated. Resumedawait_replyturns,runWorkflowTaskand scheduled runs are unaffected. See When a user sends a second message before the first reply. - Added: Two new
voiceTiervalues —CHIRP3_HDandSTUDIO.CHIRP3_HDis Google’s current-generation voice, positioned for conversational use, at a modest cost increase overNEURAL2.STUDIOis available but not recommended for a live voice session — Google tunes it for long-form narration, not dialogue, and it costs substantially more. Set either directly onAgentDto/UpsertAvatarConfigInput, same as any other tier. - Fixed:
POST /sttcould return the wronglanguageCodeon an otherwise-correct transcript — a fully English transcript (e.g."Hi testing one two three") sometimes came back taggedcmn-Hans-CNoryue-Hant-HK. The tag is now corrected when the winning language’s script doesn’t match its own transcript (a Han-script language with zero Han characters in the text it produced). This does not change the transcript itself, and does not affect a transcript that’s genuinely mixed-script. Separately, if you’re seeing garbled (not just mistagged) transcripts: send your best actual guess as the primarylanguageCode, not a placeholder — real testing found the primary carries much more weight in recognition than the fallback list does, and narrowing the fallback list does not help. - Added:
POST /sttnow falls back across languages instead of recognising in exactly one. Every call is sent with English (en-US), Mandarin (cmn-Hans-CN), Malay (ms-MY) and Cantonese (yue-Hant-HK) as fallbacks, minus your primarylanguageCodeand capped at 3. The response’slanguageCodeis now the language actually detected, where it used to echo the request. Before, a Mandarin or Malay voice note sent withen-USdidn’t error; it returned a short garbage transcript. On our synthetic test clips, Mandarin and Malay went from a garbage transcript to correct on every run, and Cantonese went from a garbage transcript to correct on about half of runs, with the rest coming back empty. A new optionalalternativeLanguageCodesfield overrides the list (max 3, since the speech provider silently ignores the whole list past that, so a 4th is rejected with a400). Send[]to turn fallback off. Two things to handle: a transcript recognised through a fallback language can come back with no confidence, reported asconfidence: 0, so apply any low-confidence cutoff only whenconfidence > 0. And a clip that switches language mid-sentence is still transcribed in one dominant language. Hokkien and Hakka are not supported. Real accented voice notes haven’t been measured yet, only synthetic voices, so check against your own traffic. The LLM Playground’stranscribeSpeechalso returns the detected language now.
2026-09-27
Section titled “2026-09-27”- Added / Deprecated:
sessionActivitySummaries— a Relay-paginated, newest-first session list withagentId/status/searchfilters andtotalCount, built for the new Supervision console.sessionsis now@deprecatedin favor of it — it still works, but returns every session the tenant has ever had with no pagination, which is a real problem for a tenant with a long history. Migrate any dashboard-style session list tosessionActivitySummaries. - Fixed: An
llmnode’smodelOverridenow takes effect. Until now the field was saved but never read, so everyllmnode silently ran on the agent’s or tenant’s model regardless of what it was set to. The model a node runs on now resolves nodemodelOverride→ agentllmModelOverride→ tenantllmModelOverride→ the provider’s default, most specific first. Two things to know before relying on it. (1) It picks a model, never a provider: the provider still comes from the agent/tenantllmProviderOverride, so a node can’t switch a non-vision provider into a vision one by itself. This corrects the 2026-09-17imageVarsentry below, which said settingmodelOverridewas enough. (2) The override is checked against the resolved provider’s model catalog (the same list the dashboard’s model picker shows). A model id that provider doesn’t serve now fails the run with an error naming the problem, where it would previously have been answered quietly by a different model. If you setmodelOverrideon nodes earlier, check those ids are valid for your provider before your next run.
2026-09-26
Section titled “2026-09-26”- Added: Scheduled Workflows: run an agent’s published workflow on a cron schedule, with no inbound message. Seven new dashboard-JWT operations:
createScheduledWorkflow,updateScheduledWorkflow,pauseScheduledWorkflow,resumeScheduledWorkflow,deleteScheduledWorkflow,scheduledWorkflow,scheduledWorkflows. Each fire is a headless run with the same starting context asrunWorkflowTask. Cron is strict 5-field only, so seconds-level and@daily-style aliases are rejected at save time, and the cron is evaluated in an IANA timezone you choose (defaultUTC). Three cost controls apply to every schedule: a per-plan limit on the number of schedules (paused and auto-disabled ones still count), the monthly LLM spend cap checked before every fire, and an automatic stop after N consecutive failed fires (maxConsecutiveFailures, default 3). Known limitation: an auto-disabled schedule sends no notification yet, no email and no webhook. PollscheduledWorkflowsforstatus: AUTO_DISABLEDif a schedule matters to you. - Fixed:
POST /sttno longer drops the rest of a voice message after a pause. For audio under ~50 seconds, which covers most real voice notes, the endpoint kept only the first transcript segment the speech provider returned. The provider typically starts a new segment at a pause, so everything after the first pause was silently lost, with a normal200and a normal-looking confidence. All segments are now joined into onetranscript, andconfidenceis averaged across them, matching how longer audio was already handled. This affected every/sttcaller, including the Telegram channel connector and the<vai-avatar>voice widget. If you were working around “the agent only heard the first half of my message,” you can drop the workaround. See Sending Voice Messages to an Agent. - Added: Six vision-capable models now available via
BEDROCK_MANTLE(in addition to the existingGEMINI_DIRECToption). SetllmProviderOverride: BEDROCK_MANTLEandllmModelOverrideto one of:mistral.mistral-large-3-675b-instruct,nvidia.nemotron-nano-12b-v2,mistral.magistral-small-2509,mistral.ministral-3-14b-instruct,google.gemma-3-27b-it, ormoonshotai.kimi-k2.5. See LLM API: Vision-capable models for accuracy, latency, and implementation notes. Caveat: all testing to date has been on clean, synthetic images (high-contrast printed text); real-world accuracy on photographed documents may vary. Validate against your own image sources before committing to a model.
2026-09-25
Section titled “2026-09-25”- Docs (capability was already live, just undocumented):
updateChannelConnectorSettings— toggles voice replies for a Telegram channel connector, either mirroring the input modality (voice in → voice out, text in → text out) or forcing every reply to be voice. This mutation and theChannelConnectorDto.voiceReplyEnabled/alwaysVoiceReplyfields shipped without a docs page; added now. - Fixed (same day, second pass):
POST /stt’sOGG_OPUShandling now auto-detects the real sample rate from the audio file itself, instead of assuming one fixed value. Voice messages from different source apps can be recorded at genuinely different real sample rates (confirmed: Telegram’s own voice notes are 48kHz) — assuming a single value, even a corrected one, could never be right for every source. If you were previously working around empty-transcript results for some voice-note sources by explicitly passingsampleRateHertz, you can now leave it unset — see Sending Voice Messages to an Agent. - Docs (clarification, same day follow-up): the
/sttfix below is confirmed working up to ~5 minutes of audio. A much longer clip (10+ minutes) could still hit an upstream limit and fail with an explicit500— not silently. If your integration handles very long recordings (e.g. no hard cap on user-recorded voice messages), see the updated constraints on Sending Voice Messages to an Agent and reach out if you need support for longer audio. - Fixed:
POST /sttno longer fails on voice messages longer than ~60 seconds. Previously this endpoint used synchronous transcription exclusively, which silently rejected anything over a hard ~60-second ceiling with no clean way to work around it (the underlying speech provider’s own limit for that call shape). It now automatically routes longer audio through an asynchronous long-running path instead — same request/response shape, no chunking, just proportionally longer latency for longer audio. See Sending Voice Messages to an Agent for the updated constraints. - Docs fix:
generateText’simagesfield was documented as “only consumed byBEDROCK_MANTLE” — this was stale.GEMINI_DIRECTalso has native inline-image support and always has. No API behavior changed; only the docs were wrong.
2026-09-24
Section titled “2026-09-24”- Breaking:
generateTextis now aMutation, not aQuery. Migration: changequery { generateText(...) }tomutation { generateText(...) }in your client — the field name, arguments, and response shape are all unchanged, only the operation keyword. This is architecturally correct for a billable action (it now matchesstartTextGeneration’s existing shape), but it is a real breaking change for any client still selecting it as a query. No known caller has integrated againstgenerateTextyet (it shipped 2026-08-27, ~4 weeks before this change); if you have, this needs a one-line fix on your side. - Changed:
generateTextnow accepts either a dashboard JWT or anX-Api-Keyheader (wasX-Api-Keyonly) — a dashboard-authenticated caller can now use it directly, alongside the existing API-key partner path.startTextGenerationandtextGenerationEventsare unaffected — stillX-Api-Keyonly. - Added:
GenerateTextResultDtogained a new field,creditsUsedApprox(Int!) — an approximate credit-equivalent ofcostUsd, for previewing what a call cost under the credits pricing model. A blended estimate, not an exact per-model breakdown. - Added: A second, independent rate-limit ceiling now applies to
generateText— 60 requests/minute per tenant, on top of its existing per-client-IP limit (raised from 10/min to 30/min in the same change). Both must pass. This is the one operation on the whole Rate Limiting 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. See Rate Limiting: LLM API.
2026-09-23
Section titled “2026-09-23”- Docs (clarification):
updateWorkflow— spelled out explicitly thatnodes/edgesare a full replacement at every level, not a field-level merge: omitting a node drops it, and omitting a field (e.g.position) on a node you DO re-send drops just that field. Found live building a partner integration — a re-send that only touchedconfigon some nodes silently wipedpositionfrom all of them.
2026-09-22
Section titled “2026-09-22”- Added: The
webhooknode can now embed a fetched file as base64 into its ownbodyTemplate, via a newfetchAsBase64field — up to 4 entries, each{ urlTemplate, bodyField }, fetching either anhttp(s)://URL or ans3://bucket/keyURI and injecting the base64 string at a dot path in the rendered JSON body. Until now this capability only existed on theactionnode (fetchAsBase64Var) — awebhooknode calling a partner endpoint with an embedded file had no way to do so without a separate registered Custom Action. The two fields aren’t identical:webhook’s never touchesctx(it’s injected straight into the request body), whileaction’s writes the base64 string to a context variable. Also new:actionnode’s existingfetchAsBase64Varnow accepts ans3://bucket/keyURI too, not just HTTP(S) — useful when the file already lives in your own S3 bucket rather than being reachable over the public internet. Ans3://fetch uses the platform’s own AWS credentials and is fail-closed: your tenant’s bucket must be explicitly granted first (the platform’s own bucket is always denied, regardless of any grant). Both fetch paths share the same 10 MB cap. See Workflows Overview: fetching files for the full behavior. - Added (behavior change worth reading if your graph has a back-edge): A workflow node can now run more than once in a single turn, via a new
maxVisitsfield on the node’s ownconfig— available on every node type, defaulting to1, capped at100, validated on draft save and publish. Until now a run visited each node at most once: a back-edge was accepted at publish time and then silently dropped at runtime, with no error, no log line and no trace entry, and the run still reportedCOMPLETED. If you drew a retry loop and wondered why it never retried, that is why. Set"maxVisits": 3on the node being retried and the back-edge genuinely re-runs it. Exhaustion is no longer silent, and there are three outcomes: (1) the node has an outgoing edge labeled'loop_exhausted'— a new reserved label, valid on any node type — and the run follows it, which is how you give up gracefully instead of dying; (2) the node declaredmaxVisitsand has no escape edge, so the runFAILEDs with a named error rather than quietly stopping; (3) the node never declaredmaxVisits, in which case that branch stops and the rest of the graph carries on exactly as before. All three now write anodeTraceentry withstatus: "VISIT_LIMIT_REACHED",visitLimitandvisitCount, where previously there was no record of any kind. Nothing changes for a graph that doesn’t opt in — an unsetmaxVisitsreproduces the old behavior precisely, so an accidental back-edge someone drew months ago does not start failing runs today. Three things to check on your side:maxVisitshas to be raised on every node inside the cycle, not just the one doing the work — a node left at the default halfway round the loop silently stops that branch before your escape edge is ever reached;nodeTracemay now hold more than one entry for the samenodeId, so anything keyed on “one entry per node” should key on array position instead; and every visit spends one of the run’s 250 node executions, so a loop meeting that ceiling reportsBUDGET_EXCEEDED, notVISIT_LIMIT_REACHED. The dashboard’s workflow builder has no input formaxVisitsyet — set it in thenodesJSON you pass toupdateWorkflow(nodeConfigSchemaalready returns it for every type if you form-generate your own editor). See Loops and re-entry for a worked bounded-retry graph, and Loop re-entry andVISIT_LIMIT_REACHEDfor the API shapes. - Added: A new workflow node type,
await_reply— ask the user a question, end the turn, and continue the same graph from that node’s successor when the next message arrives, with the workflow’s variables intact. Until now every inbound message restarted the graph atstart, so “where were we?” had to be re-derived each turn (usually by paying an LLM node to read the history back and guess). Routing is by edge label:answered(required on everyawait_replynode) andunanswered(required, and only reachable, whenmatchModeis'options'); both rules are checked at save/publish time, not mid-conversation.free_textmode takes any reply as the answer;optionsmode matches it against a list built from a context array and fixed at the moment the question is asked — by slot number, exact id, normalised label, then unique whole-token containment, every tier requiring exactly one winner, so an ambiguous reply cleanly takesunansweredinstead of guessing. New surface, all additive:pendingAwaitReplies(sessionId: Int)(reference) lists what is paused and until when;WorkflowRunStatus.AWAITING_INPUTis the paused run’s status (notCOMPLETED— exclude it from any “non-completed run = failure” alerting you have); andWorkflowRunDto.resumedFromRunIdlinks the continuation run back to the paused one. Three things to know before adopting it: (1) the question is published withturnComplete: true, because the turn genuinely is over until a human replies — a client using a quiet-period/silence timer instead ofturnCompleteto decide “the agent is done” should switch before pointing it at a graph that uses this node; (2) a wait expires after 24 hours by default (configurable per node, hard ceiling 7 days) and is then simply dropped — it is never auto-resumed downunanswered, so build your own timeout handling if your flow needs a faster guarantee; (3) the dashboard’s workflow builder has no editor UI for this node yet — author it viaupdateWorkflow’snodesJSON (nodeConfigSchema(type: AWAIT_REPLY)already returns a real JSON Schema if you form-generate your own editor).
2026-09-21
Section titled “2026-09-21”- Added (limits you should know about): A workflow run now executes under an explicit budget, and a graph is bounded when it is saved. Runtime: 250 node executions and 10 minutes of wall-clock time per run — exceeding either stops the run, persists it as
FAILED, and writes one extranodeTraceentry withstatus: "BUDGET_EXCEEDED"plusbudgetLimit("maxNodeExecutions"or"deadline"),budgetNodeExecutionsandbudgetElapsedMs, attributed to the node the engine refused to dispatch (that node never ran). Save time: 200 nodes / 400 edges max, rejected byupdateWorkflow(drafts included) andpublishWorkflowwithWorkflow has N nodes, exceeding the maximum of 200. Both ceilings are far above real usage — the largest graph on the platform is 73 nodes / 76 edges and the longest run ever recorded took 79 seconds — so this should be invisible unless something has genuinely run away. Branch onentry.status === "BUDGET_EXCEEDED"rather than parsing the error text; the threebudget*fields appear on that entry only, so existing trace parsing is unaffected. Note the budget is checked between node dispatches, never mid-node, so a single hanging call is still bounded by that node’s owntimeoutMs, not by this. See Workflow Overview: Graph size and run limits and Workflows API: Run limits andBUDGET_EXCEEDED. - Added:
runWorkflowTask— a credentialed, headless way to run an agent’s published workflow as a task, with no chat message, no conversational session to drive and no reply to collect. Auth isX-Api-Keyonly (no JWT), and the tenant is resolved from the key itself, so the input has no tenant field. Facts travel as a flatvariablesmap plus optionalattachmentUrl/attachmentFilename/attachmentMime, seeded into exactly the same run context a chat attachment produces — so one graph can serve both a conversational caller and a task caller. The caller cannot name aworkflowId: the agent owns which graph runs, as it does for a chat turn. Intended for an orchestrator’s webhook step, a channel gateway, your own backend, or a cURL. It is asynchronous — a successful return means the run was accepted and enqueued, never that it succeeded; read the real outcome fromworkflowRuns(sessionId) { nodeTrace context }(note that query is dashboard-JWT only, so an API-key-only integration should have the workflow report its own result outward via a terminalwebhooknode). Rate-limited at 60/min per client IP — see Rate Limiting: Workflow tasks. - Changed: A
conditionnode whose expression references a variable the current run’s context does not hold now evaluatesfalseand takes thefalseedge. It previously threw, which failed the entire run and produced a generic “Sorry, I ran into a problem” reply for what is usually just a turn that didn’t carry that fact. The unresolved names are recorded on the run’s context as_conditionUnresolvedVariables, so the reason is visible fromworkflowRuns(sessionId) { context }. Deliberately narrow: only an unresolved-variable reference maps tofalse— a syntax or type error still throws. If you were relying on a missing variable hard-failing a run, add an explicit gate instead. Practical upshot:someId > 0is now a complete guard covering absent,0,"0",""and prose in one expression — use it (neversomeId != 0, which does not coerce) in front of any node that writes outward.
2026-09-17
Section titled “2026-09-17”- Added: A second, per-tenant rate limit now layers on top of the existing per-client-IP limit on every avatar-service route —
POST /tts,POST /tts/google,POST /stt, and the newly-documentedPOST /avatar-session(see below). Both limits must pass — a caller can’t exceed either one. Per-tenant ceilings are set to 20x the existing per-IP figure on each route (600/min for TTS routes, 200/min for STT and avatar-session). An unauthenticated/invalid-token request is governed by the per-IP limit alone; the tenant limit only applies once a validavatarTokenresolves a tenant. See Rate Limiting: Avatar service and the REST reference. - Docs (previously undocumented):
POST /avatar-session— negotiates aHOSTED_API(photorealistic video avatar) rendering session. This endpoint already existed but had never been written up on this site. - Docs (correction): Avatar Service REST API:
POST /tts/googlesaid its rate limit was “shared with/tts.” It isn’t —/ttsand/tts/googleeach have their own independent counter (@nestjs/throttlerkeys per route handler); they just happen to use the same numeric limit. Corrected on that page. - Added: Knowledge base document upload (
requestKnowledgeDocumentUpload) now accepts Markdown (text/markdown), CSV (text/csv), and XLSX (application/vnd.openxmlformats-officedocument.spreadsheetml.sheet), alongside the existing PDF and DOCX — same 20MB cap, same three-step upload flow. Markdown is passed through as-is (no stripping); CSV and XLSX are re-serialized row-by-row asHeader: value | Header: value | ...lines (XLSX flattens every worksheet, each demarcated by a=== Sheet: <name> ===line) rather than dumped as flat comma-joined text, specifically so a chunk boundary landing mid-file doesn’t strand a value with no way to tell which column it came from. See Knowledge Base (RAG): File upload. - Added: New
actionnode config field,extractTextVar— an alternative output mode forfetchAsBase64Var’s fetch: instead of base64-encoding the fetched bytes intoctx[outputVar], it extracts plain text from them (through the same extractor registry the knowledge-base upload pipeline above uses — PDF, DOCX, Markdown, CSV, or XLSX) and writes the result intoctx[extractTextVar]instead, withctx[outputVar]left unpopulated. Capped atextractTextMaxChars(default 50,000 characters, hard ceiling 200,000);ctx["${extractTextVar}Truncated"]is always written as a boolean so your graph can branch on a partial extraction. RequiresfetchAsBase64Varto also be set. For mid-conversation document reading (a partner API hands back a file URL you want anllmnode to read), as distinct from Knowledge Base ingestion, which is for content you want retrievable across future sessions. - Added: New
llmnode config field,imageVars— attach up to 4 images (named context variables holding a data URL or bare base64 string, ~5MB cap each) to a model call, alongsidepromptTemplate’s text. Read this before using it: the platform’s default LLM provider does not support image input, and animageVars-bearing turn on an incapable provider fails the entire workflow run, not just the node —llmnodes have nosuccess/failureedge routing to fall back on the waytool/actionnodes do. SetmodelOverride, or an agent-level provider override, to a vision-capable provider before using this field. - Docs:
<vai-avatar>: Installation now documents a real gotcha for any integration that manages the SDK’s<script>tag itself (an SPA re-mounting the embed on route change, or a custom loader): removing and re-injectingvai-avatar.jsin the same page session throwsSyntaxError: Identifier '...' has already been declared, because its top-levelconstdeclarations persist in the browser’s global scope even after the<script>element is removed from the DOM. Fixed in Wetel’s own dashboard; if you wrote your own embed-loading logic, keep the script tag in the DOM permanently and only add/remove the<vai-avatar>element itself. - Docs (new page): Image Understanding for an Agent ties together the marker/
fetchAsBase64Var/imageVarspieces documented individually above into one end-to-end walkthrough — how an attached image reaches a workflow, the graph shape that gets it into a model call, and the vision-capable-provider requirement (llmProviderOverride) it depends on. Includes a concrete right-vs-wrong example of the attachment marker’s leading-block-only parsing rule (a marker followed by text on the same line silently fails to parse — this bit a real integration the same day this shipped).
2026-09-15
Section titled “2026-09-15”- Added:
POST /sttnow acceptsOGG_OPUSas anaudioEncodingvalue, alongside the existingMP3/LINEAR16/WEBM_OPUS. This matters becauseOGG_OPUSis the native format WhatsApp and Telegram voice notes are already recorded in — if your integration forwards a voice note to/stt, you no longer need to transcode it first. Defaults to a 16kHz sample rate whensampleRateHertzis omitted (matching WhatsApp’s native rate), distinct fromWEBM_OPUS’s 48kHz default. New walkthrough: Sending Voice Messages to an Agent — a copy-paste guide for transcribing a voice note with/sttand feeding it into an agent viasdkSendMessage, using theavatarTokenfromsdkStart(nothing restricts that token to the<vai-avatar>widget — it works the same way called directly from your own backend). - Docs (correction): Avatar Service REST API:
POST /sttoverstated the request size limit as “~5MB of raw audio (~7,000,000 base64 characters)”. The real, current limit is ~4MB of raw audio (~5,600,000 base64 characters) — corrected on that page.
2026-09-14
Section titled “2026-09-14”- Added:
refreshAvatarToken— renew a session’savatarTokenwithout starting a new session. AnavatarTokenlives about an hour, and until now there was no way to renew one: a conversation that outlived its token simply started failing withInvalid or expired avatar token, with no recovery path short of a freshsdkStart(and a fresh session). This matters for any long-running session — an embed left open on a tab, a channel-bridged chat a user returns to later, a headless integration holding one session for hours. Pass the current token asinput.avatarToken(not as anAuthorization: Bearerheader — this is the oneavatarTokenoperation that has to accept an already-expired token, which the normal header guard rejects outright); you get back a fresh full-TTL token, the samesessionId, andexpiresInMsso you can schedule the next refresh off the server’s TTL instead of hardcoding an hour. You don’t need to refresh pre-emptively: a token that expired within the last 10 minutes is still accepted (AVATAR_TOKEN_REFRESH_GRACE_MS, configurable per deployment), so the reactive “call → get the auth error → refresh → retry” pattern works. Only the expiry bound is relaxed — the signature is verified in full, the session must still beACTIVE, and the replacement carries the same session, tenant and avatar config, so it never widens a token’s authority. Rate-limited to 10/min per client IP (see Rate Limiting). The one thing to get right: a refreshed token cannot be swapped into a livesessionEventsWebSocket —graphql-wsreadsconnectionParamsonly once, at connection open — so close and reopen the subscription with the new token. That failure is silent, not loud: HTTP mutations resume on the new token while an un-reconnected socket holds an expired one and quietly stops delivering events.sdkSendMessage/sdkEndSessionneed no special handling; each sends its own header and picks the new token up on the next call. Full walkthrough in Authentication → Refreshing the session token. - Docs (correction): Authentication said an
avatarTokenwas “short-lived (about 24 hours)”. It has been about an hour (AVATAR_TOKEN_TTL_MS, 3,600,000 ms, on every deployed environment). If you sized a session-keepalive or refresh strategy off the 24-hour figure, it was wrong by a factor of 24 — this is exactly the gaprefreshAvatarTokenabove now closes.
2026-09-11
Section titled “2026-09-11”- Added:
credentialSecretHeaderNameoncreateCustomAction/updateCustomAction. A Custom Action’s storedbearerTokenused to only ever render asAuthorization: Bearer <token>— this lets you send it under any static header name instead (e.g.X-Api-Key,X-Client-Secret), with noBearerprefix and noAuthorizationheader sent, for third-party APIs that authenticate that way. Only applies to the plain self-servicebearerTokencredential; rejected on create unlessbearerTokenis supplied in the same call, and rejected on update if the action has no stored secret or uses a Wetel-provisioned/session-identity credential source. See Action Node: Credential header name for how it interacts withextraHeaders. - Changed:
extractFirstIntegernow stores a real number inoutputVar, not a digit string — it used to be a string. Motivation: aconditionnode’s expression is evaluated with no type coercion, so a"0"string sentinel compared withmyVar != 0evaluatedtrue(a string is never equal to a number), silently routing the “no match” case down the found branch. A live run hit exactly this and fired two outbound writes with an id of0before the partner API rejected them. If your workflow gates on this sentinel withmyVar != "0"or{{#if myVar}}, update it to a plainmyVar != 0/myVar > 0— that’s now what the value actually is. Rendering in a template is unaffected:{{myVar}}and"{{myVar}}"still render the same way they did before. - Changed: the
findIdhelper’sidFieldargument can now be a dotted path (e.g."current_application.id") to reach a nested field. A missing or non-object intermediate segment resolves to the same0“no match” sentinel rather than throwing, so a partner-side shape change degrades onto the existing not-found branch instead of failing the run. A numeric-string id (some partner APIs, including Nortia, return ids as"21"rather than21) is now coerced to a real number too —findIdalways returns a number or0. Motivation: a nestedcurrent_application.idlookup that a flat-field-onlyfindIdcouldn’t reach at all.
2026-09-10
Section titled “2026-09-10”- Changed: the
findIdHandlebars helper now matches in three tiers — exact, then normalised (case, whitespace, punctuation, diacritics, trailing plurals), then unique whole-token containment — and returns0whenever a tier is ambiguous instead of picking the first hit. Motivation: a live run where anllmnode wroteSolutions Architectfor a posting titledSolution Architect; the exact-only lookup rendered an empty id and the partner API rejected the write with a 422 after the agent had already told the candidate it was filing their application. Behaviour change to know: duplicate values in the array now return0at every tier (previously first-wins). Details and the “route the0case before any write” rule in Workflow Best Practices. - Added: Channel attachment/marker parsing moved server-side. Every workflow run’s context now carries
attachment({ url, mime, filename }ornull),channel({ trigger, nortiaEnabled, nortiaCompanyId, raw }), and flatattachmentUrl/attachmentMime/attachmentFilenamemirrors (empty string when absent) — seeded from the leading block of[[...]]marker lines a channel prepends to an incoming message (the Telegram gateway’s[[WETEL_ATTACHMENT url=... mime=... filename=...]], and OmniChat’s one-per-line[[trigger:...]]/[[nortia:enabled|company|resumeUrl|resumeName=...]]). If your graph used to run its ownllmnode to pullurl/mimeback out of the marker text, you can delete it — use the flat mirrors directly in aconditionexpression (they’re safe to compare even with no attachment present) and asfetchAsBase64Var’s source (it takes a bare variable name, not a dot path). Markers are not stripped fromuserMessage, so a graph that already extracts them itself keeps working unchanged. See Channels: attachment markers and the updated file-intake cookbook. If you’re bridging your own channel (not a packaged Channel Connector) intosdkSendMessage, neutralize[[in any end-user-typed text before prepending your own marker — otherwise a user-typed line can be parsed as if the platform itself sent it. - Resolved (follow-up to 2026-09-09’s open question below): Nortia’s REST Integration API (
List Job Postings (REST),Submit Resume (REST)) now authenticates with a static, service-owned secret header — Nortia issued Wetel one, and the old bearer-token path these two operations previously relied on has been removed on Nortia’s side entirely. Both operations are confirmed working end-to-end against the real API. Behavior change if you had a Custom Action pointed at either operation withcredentialSource: "SESSION_NORTIA": that combination is no longer offered — a workspace-wide service secret carries no signed-in end user’s identity, so a per-user credential source no longer makes sense for these two operations specifically. UsecredentialSource: "NORTIA"(Wetel-provisioned shared credential) instead;SESSION_NORTIAis unaffected on every Nortia GraphQL operation, where it continues to carry the real signed-in user’s own identity as before. - Docs (correction): Rate Limiting was stale — it still listed
sdkStartas unthrottled (it has been 30/min since 2026-09-08) and omittedgenerateWorkflowDiff(20/min),ingestDocumentFromUrl,generateText/startTextGeneration, and the auth-mutation limits added 2026-09-08. The page now lists every enforced limit, and states explicitly that all GraphQL limits are keyed by client IP, not by API key or tenant — two other pages that said “per key”/“per tenant” were corrected to match. - Docs (previously undocumented):
nodeConfigSchema/nodeConfigSchemas— shipped 2026-08-31, callable with a JWT or an API key, returns a real JSON Schema per workflow node type so an OEM builder can form-generate its property panels from the live schema instead of a hand-copied snapshot. Build Your Own Admin Panel now points at it, and its per-node config table was corrected (llmshould exposepromptTemplate, notsystemPrompt, as the main field;actionis a Custom Action + certified-operation picker, not a raw URL form;end_sessionhas no final-message field). - Docs:
sub_agentnow documents two requirements previously only discoverable at runtime — every target agent must have a published workflow (otherwise that target returnsstatus: 'error'), and the dispatch depth cap is exactly 5. - Docs: Workflow Best Practices documents the third Handlebars helper,
joinList, and states plainly that there is noeq/comparison helper — equality branching belongs in arouter/conditionnode.
2026-09-09
Section titled “2026-09-09”- Fixed (read this if a workflow using a Nortia certified operation is reading
current_status,reply, ordept_head_namefrom a response): Nortia’s live API changed a handful of response field names since these operations were first certified — 10 of Wetel’s certified Nortia operations broke as a result (they would have failed outright at runtime). All 10 are fixed as of today:Application.current_statusis nowstatus(affects Update Application Status, Move Application Stage, Record Not Hired, Record Manager Decision, Record Interview Outcome, Propose Expert Interview, and Assign Candidate to Job);AgentAnswer.reply(Ask Matchie) is nowanswer;Department.dept_head_name(Update Department) is now a relation,dept_head { id name }, rather than a plain string field; andJobPosting.interview_strategy(Get Job Posting) was removed with no confirmed successor yet, so that field is temporarily dropped from the certified selection rather than guessed at. Every affected operation’scertifiedOperationIdis unchanged — the fix was applied to the operation’s own definition, so no workflow needs re-wiring. If your workflow templates read one of the old field names from a Nortia action’s output, update the template to the new name. - Added: 4 new Nortia certified operations — Get Candidate (
candidate(id), read), Get Interview Info (interview(token), read — requires the interview invitation token, not a general “my interviews” list), AI Usage (aiUsage, read), and Update Job Posting (updateJobPosting, write — takes a full replacement payload, not a sparse patch). Catalog total is now 176 (Nortia 79 — 77 GraphQL + 2 REST, OmniChat 59, aisCRM 38). - Documented (no behavior change yet — open question): Nortia’s REST Integration API documentation now describes a different authentication scheme (a static, service-owned secret header) than the bearer-token flow Wetel’s REST certified operations (
List Job Postings (REST),Submit Resume (REST)) currently use. Whether the existing bearer-token flow still works has not yet been confirmed either way — nothing has changed on Wetel’s side pending that confirmation. If this turns out to be a breaking change, it will get its own callout here.
2026-09-08
Section titled “2026-09-08”- Fixed (security — read this if any ACTION/WEBHOOK/TOOL/MCP target or
externalSyncUrlever redirects, or its hostname doesn’t resolve): Outbound calls made on your behalf —actionnodes,webhooknodes,tool/MCP connector calls,ingestDocumentFromUrl, andexternalSyncUrl— no longer follow HTTP redirects blindly. A redirect target is now either rejected outright (action/webhook, which setmaxRedirects: 0, since no legitimate target needs to redirect) or re-validated against the same private-address check before being followed (thefetch-based paths: knowledge-document ingestion, MCP connector calls,externalSyncUrl). Separately, a target hostname that doesn’t resolve at all is now rejected, both at save time (updateAgent, workflow publish) and at call time — previously an unresolvable hostname was treated as allowed rather than blocked. If youraction/webhookURL legitimately 301/302/307/308s (e.g. anhttp://→https://upgrade, a trailing-slash normalization), point the node directly at the final URL — see Troubleshooting for the exact symptom. If a URL’s hostname doesn’t resolve from Wetel’s own servers, it can no longer be saved at all (e.g. an internal-DNS-only name, or a partner endpoint mid-provisioning) — the error names the hostname so it’s diagnosable. - Changed: On a Custom Action whose
credentialSourceis a Wetel-provisioned, tenant-wide shared credential ("OMNICHAT"/"NORTIA"/"AISCRM"/"LARK_TENANT"),urlandmethodare now immutable viaupdateCustomAction— a change to either is rejected with an error telling you to create a new action instead. This closes a gap where such an action could be repointed at an arbitrary endpoint while still sending Wetel’s own shared partner credential on every call. Session-identity credential sources (SESSION_OMNICHAT/SESSION_AISCRM/SESSION_NORTIA— your own end user’s identity, not a Wetel-shared one) are unaffected; you can still retarget those the same as before. - Changed (no client change needed for any existing integration): The
avatarTokenreturned bysdkStartis now bound to the specific session it was issued for.sdkSendMessage,sdkEndSession,sdkVerifyWebbyxOneIdentity,requestStudyKitExport,interruptSession, and thesessionEventssubscription now reject any call whosesessionIddoesn’t match thesessionIdthe presentedavatarTokenwas actually minted for, withForbidden. Every integration that already uses the{sessionId, avatarToken}pair together exactly as returned from a singlesdkStartcall — which is every known caller in this repo’s own reference tools and SDKs — is unaffected. Tokens minted shortly before this change keep authenticating during a short rollout window via a legacy compatibility path; that path is temporary and will be removed in a follow-up. - Added: Rate limits now apply to
login,register,refreshToken,loginWithWebbyxOne,isRegisterable,generateOTP,validateSecuredToken,forgotPasswordWithOTP,resetUserPasswordWithUrl, andsdkStart. A caller hammering one of these repeatedly now gets a rate-limit error instead of an unlimited retry budget — keyed per source IP, so a shared IP in front of many end users (a corporate NAT, a proxy) shares one budget across all of them. - Breaking:
generateOTPno longer accepts the optional top-levelaudienceargument — it has been removed from the schema entirely. If your integration ever passedaudiencealongsideinput(even the default"customer"value), drop it; the call now only takesinput. No known caller passed a non-default value; if yours did and it appeared to work, that was actually a gap — a non-"customer"value let an unauthenticated caller mint an OTP for a contact that had never registered. - Changed: OTP codes generated by
generateOTPare now purpose-bound — a code minted for oneTokenPurpose(e.g.SIGN_UP) can no longer be spent against a different flow expecting a different purpose (e.g.forgotPasswordWithOTP’sRESET_PASSWORD). Each code also now locks out after 5 wrong attempts, returning the same generic error used for an unknown or expired code — so a caller can’t distinguish “wrong code,” “locked out,” and “no such code” from the response alone. If your integration reuses one OTP across more than one purpose, or retries a wrong code more than a few times, both now fail; request a fresh OTP for the specific purpose you need. - Added:
responsenodestructuredContentTemplateand the correspondingMessageDto.structuredContentfield. Aresponsenode can now attach an optional structured payload (card|table|list) alongside its plain-text reply. Rendering fails safe — a bad template never fails the node,structuredContentis justnulland the text reply still sends. Dashboard-only consumer today; no interactive/button variant yet.
2026-09-07
Section titled “2026-09-07”- Docs: New recipe, Channel Agent with File Intake and a Partner Write-Back — a generalized, replicable build guide for a headless channel-connected agent that collects information over a conversation, accepts a file, and writes the result to a partner API via a certified operation. Generalized from a real, Kai-verified-end-to-end Telegram + Nortia HR screening build (role selection, a live PDF résumé submission, and a real candidate record created on Nortia). Includes a copy-pasteable workflow graph and a gotchas checklist covering the reliability patterns this build actually hit.
- Documented (not a behavior change): Channels now documents the
[[WETEL_ATTACHMENT url="..." mime="..." filename="..."]]marker a channel connector prepends to an incoming message’s text when the user sends a file — this convention already existed (shipped alongside Channel Connectors’ Telegram document/photo intake) but was never written up publicly until now. Also newly documented: Telegram’s/startsession reset — ending the mapped session and replying with the agent’s ownopeningGreetingon/start, giving an end user a self-serve way out of a long or stuck conversation with no operator intervention needed. Both behaviors are already live; this closes a real documentation gap, not a new capability. - Docs: Workflow Best Practices now documents the
findIdHandlebars helper in full (signature, worked example, the specific “an LLM confuses a code/ISBN’s own digits for the real id” failure it exists to avoid). Action Node and Tool Node already linked here for this, but the section itself didn’t exist yet — those links previously landed on a page with no matching content.
2026-09-06
Section titled “2026-09-06”- Added:
runRegressionScenarioandworkflowRegressionResults— Phase B of workflow regression scenarios. A saved scenario (captureRegressionScenario, shipped 2026-09-01) can now actually be replayed against your workflow’s current draft, and gets a real pass/fail verdict. Pass/fail is structural only — the same node-visitation path, router/condition branch, tool calls, and terminal node as the baseline — never the literal wording of an LLM/voice response, since that’s non-deterministic by design.runRegressionScenarioreturns immediately (before the replay finishes, since it drives a real LLM turn); pollworkflowRegressionScenarios/workflowRegressionResultsa few seconds later for the outcome.
2026-09-05
Section titled “2026-09-05”- Added:
agentAuditLogs— this agent’s change history (who changed what, when). v1 coversupdateAgentonly — a field set to the value it already had is not recorded, and there’s no tenant-wide admin log across every agent yet, just this per-agent history. - Added:
setPrimaryBillingAccount— sets which system bills your tenant’s usage:WEBBYX_ONE(WebbyX One’s own credit ledger) orWETEL(Wetel’s own billing; re-enables the tenant’s normal per-plan monthly LLM spend cap). Explicit and user-set — never inferred from which login method a given session happened to use. SelectingWEBBYX_ONErequires the account already have a linked WebbyX One identity vialinkIdentity— throws otherwise.myTenantnow exposes the current value asprimaryBillingAccount.
2026-09-04
Section titled “2026-09-04”- Added: the certified operation catalog is no longer exclusively GraphQL — 2 new Nortia entries,
List Job Postings (REST)andSubmit Resume (REST), run against a separate plain REST API (Nortia’s separate “Integration API” host, distinct from its GraphQL admin API). NewoperationTypevalues"rest_get"/"rest_post"alongside the existing"query"/"mutation". For a REST certified operation,argsTemplateis the full request body/params directly (no{query, variables}wrapper) — see Action Node: Certified operations. Both new entries are schema-verified only — read directly from Nortia’s own live, published OpenAPI spec — not yet live-execution-verified; see the operations’ ownverificationNote. Catalog total is now 172 (Nortia 75, OmniChat 59, aisCRM 38). - Added: a new
actionnode config field,fetchAsBase64Var— an alternative to the normalcustomActionId-driven call. Set it to a context variable holding a URL, and the node fetches that URL’s bytes server-side, base64-encodes them, and writes the result toctx[outputVar]. Built specifically to feed a certified operation’sfile_base64-shaped request field (e.g. the newSubmit Resume (REST)above) without needing a real GraphQL-multipart file upload, which no executor in this platform supports today.customActionIdbecomes optional onactionnodes whenfetchAsBase64Varis set — every other field on the node is ignored in this mode. Same SSRF protection as every otheractionnode call; sends noAuthorizationheader, so only point it at a URL meant to be fetched without auth.
2026-09-03
Section titled “2026-09-03”- Added: Request limits — every GraphQL query/mutation now enforces a max query depth of 7 and max query complexity of 50, rejected as a pre-execution validation error. Applies in all environments, doesn’t affect introspection. The schema’s own real nesting is shallow (one Relay connection, no deep object graphs), so this shouldn’t affect any normal integration query — it’s a worst-case resource-cost bound, not a new constraint on legitimate usage.
- Fixed: on a
tool/action/webhooknode’s HTTP failure,nodeTrace’serrorfield now includes the partner endpoint’s own response body — its status code and JSONmessage/error/errorsfield (or up to ~500 chars of raw text for a non-JSON body) — instead of only axios’s generic"Request failed with status code 400". Previously the actual reason a partner rejected a request (a validation message, a missing-field error) was invisible from Wetel’s side entirely; this was the exact blind spot behind an unresolved aisCRM write-400 in our own integration testing. Bounded and redacted the same way asrenderedRequest. Not retroactive. - Changed:
sdkSendMessage’s andsendMessage’stextfield limit raised to 10,000 characters (previously 4,000 forsdkSendMessage, 5,000 forsendMessage— the two had drifted with no principled reason for the difference; both are aligned now). No other behavior changes — the field was already stored in an unbounded Postgres column, this is purely the request-validation ceiling. If your integration constructs a large per-turn payload (e.g. embedding conversation history or system instructions directly intotext), consider whether Wetel’s own AgentpersonaPromptand native multi-turn history would let you send a shorter message instead — cheaper on LLM input tokens per turn, and won’t hit this ceiling again as conversations grow. - Expanded: the certified operation catalog’s Nortia coverage grew from 64 to 73 operations, adding 9 hiring-pipeline write mutations: application status/stage updates, application notes, not-hired/manager-decision/interview-outcome recording, expert-interview proposal, candidate assignment, and candidate contact-detail updates. All
SESSION_NORTIA-compatible. Unlike most of the catalog, these 9 are schema-verified only, not live-execution-verified — Nortia has no disposable candidate/application fixture to safely round-trip a write against, so exercising them for real would mutate an actual hiring record. Check an operation’sverificationNotebefore relying on one for anything beyond a controlled test. Catalog total is now 170 (Nortia 73, OmniChat 59, aisCRM 38). - Fixed:
createCustomAction/updateCustomActionnow accept aninput.credentialSourcefield, restricted to"SESSION_OMNICHAT"/"SESSION_AISCRM". Previously these two values were never actually reachable through either mutation — despite Credential sources already describingSESSION_OMNICHAT/SESSION_AISCRMas “any tenant, self-serve,” there was no way to set a new custom action onto that flow without direct database/seeder access, so the practical self-serve path for calling OmniChat/aisCRM described on this page and on the certified operation catalog page didn’t fully work end to end until now. Mutually exclusive withbearerToken/larkAppId+larkAppSecret; switching an existing action onto aSESSION_*source clears any stale stored credential.OMNICHAT/NORTIA/AISCRM(tenant-wide shared account) andLARK_TENANTremain not directly settable here — see the credential sources table for why. - Added:
createCustomAction/updateCustomAction’sinput.credentialSourcenow also accepts"SESSION_NORTIA", alongside the existing"SESSION_OMNICHAT"/"SESSION_AISCRM". Nortia now has the same self-serve, session-identity-driven credential flow as OmniChat and aisCRM — sign in via WebbyX One, link your Nortia identity (sdkVerifyWebbyxOneIdentity’sproductTickets), then reference the action’sid. No Wetel-provisioned shared account needed. See Credential sources and the certified operation catalog for the full picture.
2026-09-02
Section titled “2026-09-02”- Added:
renderedRequest(see below) now runs through a best-effort secret-redaction pass — JWTs,Bearertokens, vendor key prefixes (sk-,pk-, Slack, AWS), and long values under sensitive-sounding key names are redacted before the trace is stored. Defense-in-depth, not a guarantee — see Observability & Debugging Your Agent for what it does and doesn’t catch. Never hardcode a secret intoargsTemplate/bodyTemplate— use a Custom Action’s credential-source mechanism instead. - Added:
workflowRuns/workflowRun’snodeTraceentries now includerenderedRequestfortool,action, andwebhooknodes — the actual interpolated request body/args sent to the partner endpoint, captured on both success and failure. Previously only the response (outputValue/error) was visible in the trace, making it hard to tell whether a bad result came from a malformedargsTemplate/bodyTemplateon Wetel’s side or an issue on the partner’s own endpoint. Usually an object, but comes through as a raw string if the template failed to render to valid JSON; absent on trace data captured before this shipped. See Observability & Debugging Your Agent. - Added: Login with WebbyX One — three new mutations let a person log into the Wetel dashboard using their WebbyX One (IAM One) account:
loginWithWebbyxOne(ticket)(public, logs in or silently creates a Wetel account + auto-provisions/joins a tenant keyed to the WebbyX One company), andlinkIdentity/unlinkIdentity(JWT-guarded, add/remove a WebbyX One link from an already-logged-in account’s settings). Purely additive — never replaces or disables password login for any account; if a WebbyX One identity’s email matches an existing Wetel account, it auto-links to that account and its existing tenant always wins over a company-based one. This is a distinct mechanism fromsdkVerifyWebbyxOneIdentity(session-scoped end-user identity linking for calling certified operations) — see the note on both pages if you’re unsure which one you need.
2026-09-01
Section titled “2026-09-01”- Added: Opt-in success/failure edges for
toolandactionnodes — draw one outgoing edge labeledsuccessand one labeledfailure(same mechanism condition nodes already use fortrue/false) and the runner routes tofailureon any thrown error instead of failing the whole run, writing the error message to a_lastNodeErrorcontext variable. Fully additive and backward compatible — a node with neither label keeps today’s exact behavior (every edge fires unconditionally, an unhandled error still fails the run). Afailureedge requires a matchingsuccessedge; publishing without one is rejected. Available in the Workflow Editor UI via a card-toolbar toggle that swaps the single generic handle for the labeled pair. - Added (Phase A — capture and list only; replay shipped 2026-09-06, see above): Workflow regression scenarios —
captureRegressionScenariofreezes an already-completedWorkflowRunas a named baseline (reconstructed input script,nodeTracesnapshot, and derived structural assertions), andworkflowRegressionScenarioslists them per workflow. This exists to catch a real, documented failure mode — anintent/classification LLM node silently misrouting after a model change, with no error anywhere.
2026-08-30
Section titled “2026-08-30”- Expanded: the certified operation catalog (launched 2026-08-29, below, with 3 starter operations) grew to 161 certified operations — Nortia (64), OmniChat (59), aisCRM (38) — covering near-complete read and write access to each partner’s real API, not just a handful of example queries. New coverage includes full CRUD on OmniChat contacts/conversations/tasks/teams/opportunities plus automation/macro/SLA/canned-response management; Nortia candidate and job-posting lifecycle operations, talent pools, company settings, and usage/billing reads; and aisCRM companies/contacts/deals (including pipeline and win/loss moves), tasks, notes, and pipeline configuration. Every entry was verified live against each partner’s real, current API before being added — check
certifiedOperations(product: ...)for the authoritative, current list. - Fixed: the certified operation catalog (added 2026-08-29, below) returned an empty list / a 404 on
certifiedOperation(id)against both staging and production for a short window after launch — the table existed but hadn’t been seeded yet. Fixed same day;certifiedOperations/certifiedOperationare confirmed live and returning real data on bothaws-stagingandaws-productionnow. If you hit this earlier, it’s resolved — try again with the id your owncertifiedOperationsquery returns (ids are per-environment; don’t hardcode one from a doc example).
2026-08-29
Section titled “2026-08-29”-
Added: the certified operation catalog — for OmniChat, Nortia, and aisCRM
actionnodes, pick a Wetel-verified GraphQL query or mutation fromcertifiedOperations/certifiedOperationinstead of hand-authoring the request body yourself. SetActionNodeConfig.certifiedOperationIdandargsTemplatebecomes that operation’s GraphQL variables. Also available from the Workflow Editor UI as a second dropdown on theactionnode’s config panel, once the chosen Custom Action’s credential source has a matching certified catalog. Fully additive — existing hand-authoredargsTemplatebodies keep working unchanged. See Custom Actions API: Credential sources for which credential sources are self-serve today (SESSION_OMNICHAT/SESSION_AISCRMvia WebbyX One sign-in) versus Wetel-provisioned only (OMNICHAT/NORTIA/AISCRM). -
Added: a new
sub_agentworkflow node type — dispatches to one or more other agents concurrently, each running as its own independent session (own turn history, own cost tracking), and aggregates whatever comes back intoctx[resultVar]. This is the building block for multi-specialist orchestration: a dispatcher agent classifying a message and handing off to a billing specialist and a technical specialist at the same time, then combining both replies. One target failing or timing out never blocks or fails the others — see Sub-Agent Node for the full config reference, including the important gotcha thatresultVaris always an array, even for a single target, so{{resultVar}}on its own renders[object Object]— index a specific entry ({{resultVar.[0].text}}) instead.
2026-08-28
Section titled “2026-08-28”- Docs fix (read this if any
llmworkflow node sets bothsystemPromptandpromptTemplate): LLM Node’ssystemPromptfield description was wrong, and this page’s own worked example was broken as a result — copying it produced a node that silently never receives itspromptTemplatedata. WhensystemPromptis set, the renderedpromptTemplateis used only for token-cost accounting and never reaches the model in any form, not “used as well” as the old description said. The model still answers — with zero real context — which for a data-lookup node means a confident, fabricated-sounding reply with no error anywhere. Fix: leavesystemPromptunset in almost every case — put your instructions and any{{ctxVar}}data injection together inpromptTemplatealone. The page’s worked example is corrected to match.
2026-08-27
Section titled “2026-08-27”- Added:
createCustomActionnow supports Using Lark — send messages, create/query Base records, route approvals, and more, using your OWN Lark app (not a shared credential). SupplylarkAppId/larkAppSecret/larkRegioninstead ofbearerToken(mutually exclusive with it); Wetel exchanges them for a Larktenant_access_tokenand keeps it refreshed automatically. No new node type — this is aCustom Action/ACTIONnode integration like any other. - Added: New LLM API page —
generateText(single-response) andstartTextGeneration+textGenerationEvents(streaming) let you call an LLM directly through Wetel with no session, agent, or workflow, and no need to set up your own model provider credentials. API-key auth, same tier asembed.textGenerationEventsis the first subscription in this API to authenticate viaX-Api-KeyinconnectionParamsinstead of a dashboard JWT — see the page’s own WebSocket auth note if you’re building a streaming client. - Added: Both LLM API operations now enforce a per-tenant monthly spend cap by plan tier. A call made once you’re at or over the cap fails immediately with a
BadRequestExceptionnaming your limit and current spend — before any provider is called, so it never incurs cost. - Docs: LLM API gained a Troubleshooting section after live-testing the streaming path end to end — Redis pub/sub delivers
textGenerationEventswith no replay buffer, so a client that subscribes even slightly after generation finishes silently receives nothing (no error either side). Documents the fix (connect, wait for the socket to actually open, then start generation) and thegraphql-wsgotcha behind it: the client’s defaultlazy: truemeansconnectednever fires until something callssubscribe()first, which deadlocks a naive “wait for connected” pattern — passlazy: falseinstead. - Fixed (behavior change — read this if you edit a published workflow via the API): Editing an already-published workflow via
updateWorkflowno longer takes effect on a live session automatically. Previously,updateWorkflowwrote directly onto the same graph a live session executed, so an edit to an already-published workflow went live the instant the mutation returned — with no republish step, contrary to what this page’s own “two-call create pattern” section already documented for a first-time publish. That was an unintended gap, not documented behavior. NowupdateWorkflowalways writes to a separate draft; a live session only ever runs whatever was captured by the most recentpublishWorkflowcall. If your integration edits an already-published workflow and expects the change live without a follow-uppublishWorkflowcall, add that call now — see Workflows API: the two-call create pattern for the corrected, now-accurate-in-all-cases description. Theworkflowquery’s ownnodes/edgesfields were also clarified to state plainly that they reflect the draft, not necessarily what’s live. - Added:
sendMessageaccepts a newtestDraft: Booleaninput field — whentrue, that turn runs the agent’s workflow’s live draft instead of its published snapshot, no publish required. For testing in-progress edits from a dedicated test session only; never set this on a real user’s turn. - Added: Five new knowledge-base operations —
deleteKnowledgeDocument(soft-deletes a single document and its chunks, unlikedeleteKnowledgeBase, and releases the tenant’s quota reservation),resyncKnowledgeDocument(retries a FAILED file’s parse, or re-chunks/re-embeds a READY document’s stored text in place),ingestDocumentFromUrl(ingests a single web page — no link-following, SSRF-protected),knowledgeDocument(fetches one document including its fullrawText, kept separate from theknowledgeDocumentslist query so listing a KB never pays for fetching every document’s text), andupdateKnowledgeDocumentText(overwrites a document’s text and re-indexes it in place — the “edit” counterpart to resync). All fully additive; no existing operation’s behavior changed. - Added: New query
apiUsageSummary— this month’s LLM spend, the tenant’s monthly spend cap, and a per-model/per-operation call-count breakdown. Backs the dashboard’s new Developers page. Tenant-wide only; today’s schema can’t break usage down by individual named API key.
2026-08-26
Section titled “2026-08-26”- Added:
sdkVerifyWebbyxOneIdentitynow accepts an optionalproductTicketsarray and returns a newlinkedProductsresult field — a session’s signed-in WebbyX One identity can now be linked to sibling WebbyX-group products (aisCRM, OmniChat today) as itself, instead of a workflow always authenticating against those products as a single shared, admin-configured service account. Fully additive: omitproductTicketsand nothing changes. Requires minting an extra ticket per product client-side, in the same sign-in submit as the main ticket — see the mutation’s docs for why this can’t be done later or server-side. - Added: New query
webbyxOneProductClientIds— returns the public WebbyX OneX-Client-Idfor every sibling product a signed-in identity can link via theproductTicketsfield above. Lets a sign-in widget discover which extra/sso/logincalls to make without hardcoding a product list. Accepts either a dashboard JWT or the avatarToken fromsdkStart.
2026-08-25
Section titled “2026-08-25”- Added: Custom Actions now support
extraHeadersoncreateCustomAction/updateCustomAction, returned onCustomActionDto— a plain{ [key: string]: string }map of additional static headers merged onto every call made by anACTIONworkflow node, alongside (and never overriding) the existingAuthorizationheader. Unblocks target APIs that require more than one static header, such as a partner requiring bothAuthorizationand a separatex-organization-ididentifier header. Not a secret-storage mechanism — it’s stored and returned as plain JSON, visible to anyone who can querycustomActionsfor your tenant; genuinely secret values still belong inbearerToken, the one credential a custom action supports. See Custom Actions API: Extra static headers and Action Node.
2026-08-24
Section titled “2026-08-24”- Docs: Workflow Best Practices now documents the
_{outputVar}Structured/_{outputVar}StructuredJsoncontext keys atoolnode writes alongside its plain-textoutputVar— populated from the MCP protocol’sstructuredContentfield, or auto-parsed as a fallback if your server returns JSON-shaped text instead. Also documents two supportedresponsenode design choices for consuming it: extracting just a human-readable field for a plain-text client, or hand-authoring a structured JSON envelope (withjsonString) for your own frontend to parse client-side. Neither behavior is new — this closes a real documentation gap found while helping a partner debug a raw-JSON-leaking reply, which turned out to be a template-wiring choice, not a bug in either the platform or their integration.
2026-08-23
Section titled “2026-08-23”- Fixed:
sdkVerifyWebbyxOneIdentity’s docs incorrectly described the SSO login step as a redirect to a WebbyX-One-hosted login page, and stated the ticket TTL as ~60 seconds. Neither is correct: WebbyX One has no hosted login page for this flow — your own frontend hosts the email/password form and callsPOST /sso/logindirectly with those credentials — and the real, confirmed ticket TTL is 5 minutes. Docs corrected to match the real, live-tested contract; the mutation’s own behavior was never affected, only its description. - Added:
sdkVerifyWebbyxOneIdentity— exchanges a WebbyX One (IAM One) SSO ticket for the caller’s verified identity and stores it on the Session, so a workflow’srouter/conditionnodes can branch on{{session.webbyxOneIdentity.companyIds}}and similar fields. No<vai-avatar>embeddable SDK component ships a “Sign in with WebbyX One” affordance yet — Wetel’s own dashboard (Agent Testing screen) has a reference implementation of this exact flow if you want to see it working end to end before building your own.
2026-08-21
Section titled “2026-08-21”- Changed: Bedrock Mantle is now the platform default LLM provider (previously opt-in only via
llmProviderOverride: BEDROCK_MANTLE, as noted below). If you never setllmProviderOverrideon your agents or tenant, conversation turns are now served by Bedrock Mantle by default rather than the prior default provider — this can change latency, reply phrasing, and cost characteristics even without any change on your end. Every provider in the fallback chain (Bedrock Mantle → WebbyxAI/Claude Haiku → Gemini) still activates automatically on failure, so this is not a behavior change for reliability, only for which provider normally serves a healthy request. If you need the prior default back for your account, setllmProviderOverrideexplicitly — see Agents: LLM provider override. - Added:
llmworkflow nodes now supportmatchAnyOf— set it to the full list of valid labels on a classification node feeding arouter/condition, and the executor will extract whichever candidate label appears earliest in the model’s raw response instead of trusting the model to emit only that word. Fixes a real reliability gap: some models (particularly smaller/faster ones) occasionally ignore a “respond with ONLY one word” instruction and answer the user’s actual question instead, which previously caused a silent misroute to the router’sdefaultTargetwith no error anywhere. See LLM Node:matchAnyOf. - Fixed:
deactivateChannelConnectornow releases the connector’s claim on its platform identifier (bot id, phone number, etc.) instead of holding it indefinitely. Previously, once any connector claimed an identifier, no connector — including a reactivated version of the same one — could ever claim it again without direct database intervention, since deactivating only paused the connector without freeing the identifier. If you deactivate a connector and later want to reactivate it, callupdateChannelConnectorCredentialsagain first — a bareactivateChannelConnectoron a connector with noexternalIdentifiernow fails clearly instead of assuming the old registration still holds.
2026-08-20
Section titled “2026-08-20”- Added:
sendMessage/sdkSendMessagenow accept an optionalclientTurnIdstring, echoed back verbatim on theAiResponseEventproduced for that turn. Useful for matching a specific reply to the message that produced it, especially if your integration sends more than one message before waiting for a response — previously there was no way to correlate a reply to a specific turn beyond assuming strict send/receive ordering. Fully optional and backward compatible: omit it and nothing changes. See Sessions:sdkSendMessageand Events & Subscriptions. - Added (pilot): Agents and tenants can now override which language model provider answers conversation turns, via
llmProviderOverride(BEDROCK_MANTLE,WEBBYXAI_HAIKU, orGEMINI_DIRECT) and, forBEDROCK_MANTLEspecifically,llmModelOverrideto pick a specific model from AWS Bedrock’s open-weight catalog. Currently in limited pilot availability — contact your Wetel representative to enable this for your account. Every agent is unaffected unless both fields are explicitly set; any misconfigured or unavailable override falls back to the platform default automatically. See Agents: LLM provider override.
2026-08-19
Section titled “2026-08-19”- Added: Channel Connectors — a packaged, zero-code way to connect an Agent directly to Telegram. Five new operations:
createChannelConnector,updateChannelConnectorCredentials,activateChannelConnector,deactivateChannelConnector, andlistChannelConnectors. Activation runs a live health check against the platform and automatically registers the webhook for you — no manual setup step.WhatsApp,Slack, andLarkare validChannelTypevalues but not yet implemented; every operation against one currently returns a clear error rather than a silent no-op. See Channels and Headless Agents: Telegram is now a packaged, zero-code connector. - Fixed (docs correction + real behavior):
registernow always assigns a new tenant to a freshly registered user automatically — this page previously (and correctly, at the time) documented that a fresh signup starts with no tenant (myTenantreturningnull). If your integration was creating or assigning a tenant itself immediately after callingregister, checkmyTenantfirst — one already exists by the timeregisterreturns, and creating a second is unnecessary.loginalso self-heals this for any pre-existing account that was created before this change and never got a tenant assigned.
2026-08-17
Section titled “2026-08-17”- Added:
ExportRecordDto.statusnow has aFALLBACK_READYvalue, alongside the existingPENDING/RENDERING/READY/FAILED. When arequestStudyKitExportrender fails (including timing out) and a fallback document is configured server-side, the export settles intoFALLBACK_READYinstead ofFAILED—downloadUrlis populated with a fresh presigned URL to a fixed substitute document (7-day expiry, vs. the normalREADYpath’s 24h), anderrorMessageis also populated so you can tell this happened. TreatFALLBACK_READYas a terminal, download-available state alongsideREADYwhen pollingexportRecord. See Evaluation & Export: TheExportRecordDtotype. - Added:
llmworkflow nodes now supportexcludeHistory— settrueon a narrow “extract one value from the current message” node to exclude it from receiving the session’sconversationHistoryin its underlying model call. Fixes a real reliability gap: once a session has a few tool-backed turns behind it, structured JSONresponseenvelopes accumulate in that history, and a narrow extraction node with no legitimate use for that context can start echoing JSON-shaped output back instead of the bare value its own prompt asked for. See LLM Node:excludeHistory. - Added:
llmworkflow nodes withknowledgeBaseIdset now supportallowGeneralKnowledgeFallback— softens the default retrieval-augmentation directive (“answer using ONLY this reference material”) to “use it if relevant, otherwise answer from general knowledge and say so,” for nodes whose own prompt already has an explicit fallback instruction. Without this, a node like a study-kit generator would get overridden into refusing to answer whenever retrieval returned even a loosely-related chunk. See LLM Node:allowGeneralKnowledgeFallback. - Added: a new
jsonStringHandlebars helper, usable inside any text-field template (aresponsenode’smessageTemplate, anllmnode’s prompt, etc.) to safely embed a variable inside a hand-authored JSON string literal. Closes a gap in the existing text-field/JSON-field escaping split for workflows that render a JSON envelope as their outgoing message text. See Best Practices: building a hand-authored JSON envelope inside a text field. - Fixed: an agent with both
openingGreetingset and aworkflowIdattached could produce a double greeting on session start — the static greeting racing against the workflow’s own first response.openingGreetingis now always skipped for any agent with a workflow attached (it was already effectively unreliable in this configuration; this makes the behavior deterministic instead of a race). If your agent needs a specific first-turn behavior and has a workflow attached, build it into the workflow graph itself. See Agents:openingGreeting.
2026-08-13
Section titled “2026-08-13”- Corrected (docs): Events & Subscriptions had the
sessionEventssubscription’s$sessionIdvariable typed asInt!in its own example — wrong, it’sID!. Every other session-related field in the API (SdkSendMessageInput.sessionId, etc.) genuinely isInt!, which makesInt!the natural but incorrect guess here; a variable declared with the wrong named type fails GraphQL schema validation and the subscription never opens, with no reply ever arriving. Two real integrations hit this before it was caught. No API change — the schema was alwaysID!; only the docs’ own example was wrong. Added an explicit callout so this doesn’t recur. - Added (docs): Avatar rendering options now explains that
HOSTED_APIis a rendering tier with more than one possible underlying provider (a higher-fidelity photorealistic option and a lower-latency/lower-cost lightweight option), selected server-side per agent — your integration code doesn’t need to know or branch on which one is active. - Added (docs): Headless Agents now includes a full worked example of bridging a messaging channel (Telegram, as the concrete case) to a headless Wetel session — long-polling instead of a webhook (no public URL needed), session-per-conversation mapping, and the subscribe-before-send pattern required since
sdkSendMessageonly returns an ack.
2026-08-12
Section titled “2026-08-12”- Clarified (docs): Backend-Proxied Relay Architecture now makes explicit that adopting this pattern does not, by itself, persist any messages to your database — it only gives you the place to add that write. A real integration built this pattern’s transport (subscription + SSE relay) correctly but never added the actual persistence, then assumed it was “basically already happening” since every message visibly passed through their backend. No API changes — this is a docs clarification to prevent the same assumption elsewhere. See the new callout under “What this pattern does not give you for free.”
- Added (docs): Workflows Overview now includes a comparison to general-purpose state-machine libraries (XState and statecharts in general) — what Wetel’s workflow graph gives you for free (built-in LLM/tool/webhook node types, hosted cross-turn persistence) versus what it intentionally doesn’t attempt (hierarchical/parallel states, offline simulation tooling, portability outside the platform). No API changes — this is purely a docs addition for anyone evaluating the platform against familiar state-machine tooling.
- Added (docs): Backend-Proxied Relay Architecture — a documented alternative to the default browser-direct integration. Your backend holds the
sessionEventssubscription itself and relays replies to your frontend over a channel you control, so the browser never talks to Wetel directly. No API changes — this documents an architecture choice available today using existing mutations, for integrations that want backend-native message persistence, moderation before delivery, or centralized reconnection handling.
2026-08-11
Section titled “2026-08-11”- Added: Agents can now optionally use a hosted photorealistic avatar backend via the
avatarBackendconfiguration field, as an alternative to the default client-side 3D avatar. SetavatarBackend: HOSTED_APIwhen creating or updating an agent to enable this option. Currently in limited pilot availability — contact your Wetel representative to enable for your account. See Agents: avatarBackend and Avatar Rendering Options for details. - Added: Barge-in/interrupt now works on the pilot
HOSTED_APIavatar backend, not just the default 3D avatar — previously only<vai-avatar>’s TalkingHead-based (CLIENT_3D) path supported stopping the avatar mid-reply. Also added two new<vai-avatar>methods:handleBargeIn()for triggering an interrupt directly (e.g. an explicit “stop” button, independent of voice detection) andsendText(text)for sending a typed message through the same pipeline as a spoken one. See Avatar:handleBargeIn()and Avatar:sendText(). - Fixed: Interrupting an agent mid-reply could permanently break turn-based routing for the rest of that session — every subsequent message received the same generic fallback reply regardless of content, with no error surfaced anywhere. This affected any agent whose workflow uses a
ROUTERnode keyed on turn number. No API surface changed; if you build workflows with turn-count-based routing and useinterruptSession, this is now safe.
2026-08-10
Section titled “2026-08-10”- Added:
AiResponseEventnow has aturnCompletefield, alongside the existingisFinal. If your agent is workflow-driven and can speak multiple lines in one turn (e.g. a workflow with tworesponsenodes in sequence), you previously had no way to distinguish “this line is done” from “the whole turn is done” — every line arrived withisFinal: trueon its own. WatchturnCompleteinstead ofisFinalif you need to know when the agent has genuinely finished replying. See Events & Subscriptions for the full explanation.isFinalis unchanged — this is additive, not a breaking change. - Added:
sessionsByExternalCustomerId(externalCustomerId, first, after)— a new API-key-tier query for re-pulling all Sessions matching your ownclientExternalId/clientId, Relay-paginated. Useful as a backup/re-sync path if you lose local state and never persisted session ids yourself. See Sessions: API-key-tier customer lookup queries. - Added:
customerUsageSummary(externalCustomerId)— a new API-key-tier query returning rolled-uptokensIn/tokensOut/costUsdacross every Session for one customer. Same page linked above. - Added:
MessageDtonow hastokensIn,tokensOut, andcostUsdfields, populated per message (0 forUSERmessages and for workflowresponsenodes with no LLM call behind them). These are character-based estimates, not exact billing figures. - Added: Named API keys (
createNamedApiKey(name)/revokeNamedApiKey(id)) — create multiple independently-revocable keys per tenant instead of sharing onegenerateApiKeykey across every integration. Revoking one no longer affects any other. Fully additive — your existinggenerateApiKeykey keeps working unchanged if you don’t use this. See Authentication: Named API keys.
2026-08-09
Section titled “2026-08-09”- Fixed:
interruptSessionnow accepts either a dashboard JWT or the SDK avatarToken fromsdkStart— previously it only accepted a JWT, which meant the<vai-avatar>embed SDK’s own barge-in call could never authenticate. If you’ve built directly againstinterruptSessionusing the avatarToken and seen anUnauthorizederror, this is now fixed. - Fixed: the
sessionEventssubscription’s$sessionIdvariable type isID!, notInt!— if you hand-wrote this subscription against an older code sample, update the variable type or the subscription will fail to establish with no visible error (the triggering mutation still returns success, so this looked exactly like “no reply ever arrives”). - Fixed: publishing a workflow with two nodes sharing the same node ID, or with a node that has no path from START, is now rejected with a clear error at publish time — previously the duplicate silently became dead, unexecuted data, and the orphan just sat in the graph doing nothing. A workflow with an intentional loop-back (e.g. a router retrying a step) is unaffected — cycles are a supported pattern, not something this check rejects.
- Fixed: a workflow
conditionnode with no matchingtrue/falseoutgoing edge now fails the run with a clear error, instead of silently stopping with no signal at all. - Fixed:
publishWorkflownow validates every node’s configuration before publishing (previously only saving a draft did this) — a workflow can no longer go live with invalid node config. - Added:
webhooknodes now support a configurabletimeoutMs, matchingtool/actionnodes. - Improved: MCP connector failures are now classified (DNS resolution failed, connection refused, connection timed out, TLS handshake failed, authentication failed) instead of a single generic, undifferentiated error.
- Fixed: sending a message to an ended session now returns a clear error instead of a generic server error.
- Fixed: an internal inconsistency in how session-related mutations typed their ID argument has been standardized.
- Fixed: a workflow node’s
outputVarcan no longer silently collide with a reserved internal name — this is now rejected at save time with a clear error, instead of corrupting the running session silently.
Earlier
Section titled “Earlier”Embeddings API, external conversation sync, and AI-generated workflow drafts all shipped in the weeks prior to this changelog’s start — see the relevant guide pages for current behavior. A full historical changelog before this date isn’t available yet.