Generate Files: Voice and Video
This recipe builds a single workflow that produces four artifacts for one customer briefing: a markdown summary document, a spoken voice-over, a short jingle and a narrated video script. It then shows how your integration reads each artifact and presents or downloads it.
Before you start
Section titled “Before you start”Platform switches
Section titled “Platform switches”Every artifact type used here is behind a platform switch that is off by default. Your integration cannot use these nodes until Wetel has switched them on for your environment.
| Switch | Controls | Who turns it on |
|---|---|---|
| Artifact generation | markdown, html_page, video_script | Wetel staff, per environment |
| Image generation | image (not used in this recipe) | Wetel staff, per environment |
| Audio generation | audio_speech, audio_music | Wetel staff, per environment |
| Video narration | narrate: true on video_script | Wetel staff, per environment |
While a switch is off, the node fails immediately with the code DISABLED and no model call is made. Do not ship a customer-facing flow until you have confirmed with Wetel that every switch you need is on.
Per-workspace daily limits
Section titled “Per-workspace daily limits”Audio artifacts have per-workspace daily limits that apply regardless of how many workflows or sessions produce them:
| Artifact type | Default daily limit | Max file size |
|---|---|---|
audio_speech | 10 files per workspace per day | 20 MB |
audio_music | 5 files per workspace per day | 20 MB |
When narrate is true on a video_script, each scene counts as one speech file against the daily speech limit. A scene whose speech file cannot be produced (for example because the limit was reached mid-video) simply has no voice; the video still succeeds.
All artifact types also count toward the workspace’s overall daily artifact limit and monthly spend cap. See the Generate Artifact Node reference for details.
The workflow graph
Section titled “The workflow graph”The workflow has five nodes in sequence: a set_variable node to prepare the input, then four generate_artifact nodes. Each artifact node writes an artifactId to a different output variable. A final llm node composes the reply to the user.
Every generate_artifact node has a failure edge so the conversation keeps going even when one artifact cannot be produced.
flowchart TD start([start]) --> prep["prepare_inputs (set_variable)"] prep --> doc["gen_document (markdown)"] doc -- success --> voice["gen_voiceover (audio_speech)"] doc -- failure --> docfail["handle_doc_failure (llm)"] --> voice voice -- success --> jingle["gen_jingle (audio_music)"] voice -- failure --> voicefail["handle_voice_failure (llm)"] --> jingle jingle -- success --> video["gen_video (video_script, narrate)"] jingle -- failure --> musicfail["handle_music_failure (llm)"] --> video video -- success --> reply["compose_reply (llm)"] video -- failure --> videofail["handle_video_failure (llm)"] --> reply reply --> done([end])See Workflow Overview for the node/edge envelope and Fail Gracefully for the failure-routing pattern.
Node configurations
Section titled “Node configurations”1. Prepare inputs
Section titled “1. Prepare inputs”{ "id": "prepare_inputs", "type": "set_variable", "config": { "assignments": [ { "variable": "customerName", "expression": "channel.raw.customerName || 'Valued Customer'" }, { "variable": "briefing", "expression": "lastUserMessage" } ] }}2. Generate the document (markdown)
Section titled “2. Generate the document (markdown)”{ "id": "gen_document", "type": "generate_artifact", "config": { "artifactType": "markdown", "titleTemplate": "Briefing for {{customerName}}", "promptTemplate": "Write a one-page executive summary of this briefing for the customer. Briefing: {{briefing}}", "outputVar": "docResult", "timeoutMs": 90000 }}On success, docResult contains { "artifactId": 501 } (the actual id will differ).
3. Generate the voice-over (audio_speech)
Section titled “3. Generate the voice-over (audio_speech)”{ "id": "gen_voiceover", "type": "generate_artifact", "config": { "artifactType": "audio_speech", "titleTemplate": "Voice-over for {{customerName}}", "promptTemplate": "Read this briefing summary aloud in a clear, professional tone: {{briefing}}", "voice": "Aoede", "outputVar": "voiceResult", "timeoutMs": 120000 }}The voice field accepts one of Kore, Charon, Fenrir, Aoede or Puck. If omitted it defaults to Kore. The resulting artifact is a WAV file.
4. Generate the jingle (audio_music)
Section titled “4. Generate the jingle (audio_music)”{ "id": "gen_jingle", "type": "generate_artifact", "config": { "artifactType": "audio_music", "titleTemplate": "Jingle for {{customerName}}", "promptTemplate": "Compose a short, upbeat corporate jingle, about 15 seconds, suitable for an introduction to a briefing video.", "outputVar": "jingleResult", "timeoutMs": 120000 }}The resulting artifact is an MP3 file.
5. Generate the narrated video script (video_script)
Section titled “5. Generate the narrated video script (video_script)”{ "id": "gen_video", "type": "generate_artifact", "config": { "artifactType": "video_script", "titleTemplate": "Video briefing for {{customerName}}", "promptTemplate": "Write a 45-second narrated explainer video about this briefing. Open with a hook, present 2-3 key points on cards, and close with a call to action. Briefing: {{briefing}}", "narrate": true, "outputVar": "videoResult", "timeoutMs": 180000 }}What you get. A video_script artifact is a small JSON manifest of 1 to 8 scenes (hook, cards, table, flow and a closing call to action), each with a spoken narration. It is not a server-rendered MP4 file. The workspace app plays it in the Documents window, and the “Download video” button records an MP4 (or WebM) in the viewer’s own browser in real time. The spoken voice is included only when narrate is true and the video narration switch is on; each scene’s speech file is a separate speech artifact that counts against the daily speech limit. Scenes carry no URLs or markup.
If your integration needs to present the video outside the workspace app, you will need to either play the JSON manifest in your own player or have the end user record the MP4 from the browser.
Routing failures
Section titled “Routing failures”Each generate_artifact node should have a failure edge. On the failure path, use a lightweight llm node or set_variable node to set a message telling the user that one part could not be produced.
The failure handler has access to _lastNodeErrorCode and _lastNodeErrorPublic. Show only _lastNodeErrorPublic to the end user; _lastNodeError can contain host names and provider details.
You can branch on the code with a switch node. The codes the generate_artifact node can produce are:
| Code | Meaning | Typical handling |
|---|---|---|
DISABLED | The platform switch is off. | Tell the user the feature is not available. Do not retry. |
QUOTA_EXCEEDED | Daily artifact limit or concurrency limit reached. | Tell the user to try again tomorrow. Do not retry. |
SPEND_CAP | Monthly spend cap reached. | Tell the user the workspace limit has been reached. |
INVALID_INPUT | Bad type, empty/oversized/credential-shaped prompt. | A workflow authoring bug. Fix the template. |
BLOCKED | The model declined the prompt, or a video script was invalid. | Tell the user the content could not be generated. |
TOO_LARGE | The produced file exceeds the size limit. | Simplify the prompt. |
PROVIDER_UNAVAILABLE | No provider configured or provider failed. | Retry may help. Consider a retry policy on an upstream node; see Build a Reliable Workflow. |
INTERNAL | Storage failure. | Retry may help. |
Example failure-handler node:
{ "id": "handle_voice_failure", "type": "set_variable", "config": { "assignments": [ { "variable": "voiceResult", "expression": "{ \"artifactId\": null }" }, { "variable": "voiceError", "expression": "_lastNodeErrorPublic" } ] }}See Fail Gracefully for the full pattern.
Reading and downloading the artifacts
Section titled “Reading and downloading the artifacts”After the workflow finishes, each output variable holds { "artifactId": <number> } (or null if that step failed). Your integration reads the artifact and gets a download link through the Artifacts API.
Check the artifact status
Section titled “Check the artifact status”query GetArtifact($id: Int!) { artifact(id: $id) { id kind status title mimeType sizeBytes errorCode }}{ "id": 501 }{ "data": { "artifact": { "id": 501, "kind": "markdown", "status": "READY", "title": "Briefing for Acme Corp", "mimeType": "text/markdown", "sizeBytes": 3210, "errorCode": null } }}An artifact may briefly be PENDING while it is being stored. Poll until status is READY or FAILED. If FAILED, errorCode explains why.
Get a download link
Section titled “Get a download link”mutation GetDownloadUrl($id: Int!) { artifactDownloadUrl(id: $id) { artifactId url expiresAt }}{ "id": 501 }The returned url is short-lived: it stops working at expiresAt, so ask for a fresh one each time instead of storing it. For audio artifacts (WAV or MP3), your client can play the URL directly. For the document, render or display the markdown in your UI.
List all artifacts for a session
Section titled “List all artifacts for a session”If your integration surfaces all files a conversation produced, use the artifacts query:
query SessionArtifacts($sessionId: Int!) { artifacts(sessionId: $sessionId, first: 20) { edges { node { id kind status title mimeType sizeBytes } } pageInfo { hasNextPage endCursor } }}Authentication
Section titled “Authentication”An end user in your chat UI can use the avatarToken from sdkStart to read and download artifacts from their own session. A dashboard JWT can access any artifact in the workspace. An API key is not accepted for artifact operations. See the Artifacts API for details.
Playing or downloading each type
Section titled “Playing or downloading each type”| Artifact kind | File format | How to present |
|---|---|---|
markdown | Text (Markdown) | Render in your UI or offer as a .md download. |
audio_speech | WAV | Play with an <audio> element or offer as a download. |
audio_music | MP3 | Play with an <audio> element or offer as a download. |
video_script | JSON manifest | The workspace app’s Documents window plays it and offers “Download video,” which records an MP4 or WebM in the browser. If you are building your own player, you will need to interpret the scene manifest. This is not an MP4 served from the platform. |
Testing in mock mode
Section titled “Testing in mock mode”A draft test (testDraft: true on sendMessage) runs in mock mode by default. Each generate_artifact node checks its config and templates exactly as in a live run, then produces { "artifactId": null, "mocked": true } without calling any model, storing any file or consuming any quota. This lets you verify the entire graph, including failure edges, without cost.
See Workflow Testing for mock mode details.
Complete node list
Section titled “Complete node list”For reference, here is the full set of nodes in this recipe’s workflow:
[ { "id": "prepare_inputs", "type": "set_variable", "config": { "assignments": [ { "variable": "customerName", "expression": "channel.raw.customerName || 'Valued Customer'" }, { "variable": "briefing", "expression": "lastUserMessage" } ] } }, { "id": "gen_document", "type": "generate_artifact", "config": { "artifactType": "markdown", "titleTemplate": "Briefing for {{customerName}}", "promptTemplate": "Write a one-page executive summary of this briefing for the customer. Briefing: {{briefing}}", "outputVar": "docResult", "timeoutMs": 90000 } }, { "id": "gen_voiceover", "type": "generate_artifact", "config": { "artifactType": "audio_speech", "titleTemplate": "Voice-over for {{customerName}}", "promptTemplate": "Read this briefing summary aloud in a clear, professional tone: {{briefing}}", "voice": "Aoede", "outputVar": "voiceResult", "timeoutMs": 120000 } }, { "id": "gen_jingle", "type": "generate_artifact", "config": { "artifactType": "audio_music", "titleTemplate": "Jingle for {{customerName}}", "promptTemplate": "Compose a short, upbeat corporate jingle, about 15 seconds, suitable for an introduction to a briefing video.", "outputVar": "jingleResult", "timeoutMs": 120000 } }, { "id": "gen_video", "type": "generate_artifact", "config": { "artifactType": "video_script", "titleTemplate": "Video briefing for {{customerName}}", "promptTemplate": "Write a 45-second narrated explainer video about this briefing. Open with a hook, present 2-3 key points on cards, and close with a call to action. Briefing: {{briefing}}", "narrate": true, "outputVar": "videoResult", "timeoutMs": 180000 } }, { "id": "handle_doc_failure", "type": "set_variable", "config": { "assignments": [ { "variable": "docResult", "expression": "{ \"artifactId\": null }" }, { "variable": "docError", "expression": "_lastNodeErrorPublic" } ] } }, { "id": "handle_voice_failure", "type": "set_variable", "config": { "assignments": [ { "variable": "voiceResult", "expression": "{ \"artifactId\": null }" }, { "variable": "voiceError", "expression": "_lastNodeErrorPublic" } ] } }, { "id": "handle_music_failure", "type": "set_variable", "config": { "assignments": [ { "variable": "jingleResult", "expression": "{ \"artifactId\": null }" }, { "variable": "musicError", "expression": "_lastNodeErrorPublic" } ] } }, { "id": "handle_video_failure", "type": "set_variable", "config": { "assignments": [ { "variable": "videoResult", "expression": "{ \"artifactId\": null }" }, { "variable": "videoError", "expression": "_lastNodeErrorPublic" } ] } }, { "id": "compose_reply", "type": "llm", "config": { "promptTemplate": "Tell the customer their briefing materials are ready. Mention which items were produced and which could not be generated. Document: {{docResult.artifactId}}, Voice-over: {{voiceResult.artifactId}}, Jingle: {{jingleResult.artifactId}}, Video: {{videoResult.artifactId}}." } }]The edges between nodes (including success and failure edges) follow the graph described above. See Workflow Overview for the edge format.
Next steps
Section titled “Next steps”- Generate Artifact Node — full config reference and failure codes.
- Artifacts API — queries and mutations for reading, listing and downloading artifacts.
- Fail Gracefully — the general pattern for failure routing.
- Build a Reliable Workflow — retry policies, mock-mode testing and publish/rollback.
- Workflow Testing — mock mode and draft tests.