Put a support or sales chat on any web page by dropping in one custom element. The visitor gets an AI text agent that can answer, collect a form, and hand off to a human — all over a single QUIC connection (with a WSS fallback), and all driven by the same visual flow builder your voice IVR uses. You build the conversation once as a flow of steps; it runs the same whether the caller reached you by phone or by the chat bubble. The chat modality is widget-first. You embed a web component, configure it with attributes, and everything else — routing, AI vs. human, forms, queueing — is authored in the flow builder and served by the engine. For custom clients you can speak the envelope protocol directly over raw MoQT; there is no separate polyglot SDK for chat today (see Honest limits).

When to use it

Support / sales chat

A chat bubble on your marketing site or app that greets visitors, answers with an AI text agent, and escalates to a human when needed.

One flow, phone + chat

Reuse the announcements, forms, and skill queues you already built for voice. The step engine is shared; chat is just a text-mode surface over it.

Lead capture

Present a form mid-conversation and block until the visitor returns a validated result — structured intake without leaving the chat.

Concurrent human agents

One human agent handles several conversations at once from the console, with skill-based routing and re-offer if they miss one.

Files & reactions

Visitors and agents attach files (images, docs) and react to messages with emoji or quote-reply. The AI text agent sees uploaded files as context.

Embed the widget

The web component tag is your brand name, lowercased, plus -chat — so the element you place on the page is <telequick-chat>.
1

Load the widget script

Add the loader <script type="module"> from your workspace’s widget URL to the page <head>. It registers the custom element and defines <telequick-chat>.
2

Place the element

Drop <telequick-chat org="…" flow="…"> anywhere in the body. The widget renders a launcher bubble and, on open, a chat panel.
3

It connects on its own

On open, the widget mints a namespace-scoped token and opens a MoQT session to the relay — QUIC first, WSS fallback — then subscribes to its conversation’s server track. No connection code on your side.
org
string
required
Your workspace / organization id. Scopes the conversation namespace and the minted token.
flow
string
required
The published flow (step-engine graph) that drives the conversation — announcements, forms, and the queue-to-skill step. Authored in the flow builder, the same one voice uses.
theme
string
default:"auto"
light, dark, or auto. Cosmetic; does not affect routing.
The widget subscribes to exactly one track (server) and publishes to one (client). Everything the visitor sees — AI replies, human replies, system announcements, forms — arrives on that single server subscription, so the UI never has to reconcile multiple streams. See Tracks.

The three tracks

Each conversation is addressed as chat/<org>/<conv> and carries three MoQT tracks. The split keeps the widget’s job trivial (publish one, subscribe one) while letting a human agent join without the widget knowing or caring.
TrackDirectionWho publishesWho subscribes
clientvisitor → enginethe widgetthe engine
serverengine → visitorthe enginethe widget (only this one)
agenthuman agent → enginethe agent consolethe engine
A human agent publishes on agent; the engine relays those envelopes onto server. That is why the widget only ever needs one subscription — AI-generated and human-generated replies both surface on server, indistinguishable to the transport layer and tagged by role in the envelope.

The envelope protocol

Every MoQT object on a chat track is one JSON envelope. This is the wire contract a custom client speaks if you build your own UI over raw MoQT instead of the widget.
{
  "v": 1,
  "seq": 42,
  "ts": 1720800000000,
  "kind": "message",
  "role": "user",
  "data": { "text": "Hi, I need help with billing" }
}
v
number
required
Envelope version. Currently 1.
seq
number
required
Monotonic per-track sequence number, for ordering and gap detection.
ts
number
required
Millisecond epoch timestamp set by the sender.
kind
string
required
One of message · typing · form · form_result · reaction · event.
role
string
required
Who authored it — e.g. user, agent (AI or human), system.
data
object
required
Kind-specific payload — message text, a form schema, a submitted result, or an event body.
kindMeaningTypical data
messageA chat message (optionally with attachments / a quoted reply){ mid, text, attachments?, reply_to? }
typingTyping indicator{ active }
formEngine presents a form and awaits a resulta field schema
form_resultVisitor’s submitted, to-be-validated answersthe field values
reactionAdd or remove an emoji reaction on a message{ op, emoji, target_mid }
eventLifecycle / control signale.g. queued, assigned, closed, file uploaded

Message ids, attachments, and replies

A message envelope’s data carries a few more fields beyond text:
data.mid
string
An author-owned message id, unique within the conversation. The engine stamps and relays it. Reactions and quoted replies both address a message by its mid.
data.attachments
array
Zero or more attachment metas — { id, name, size, mime, url } — for files sent with this message. An attachment-only message may have empty text. See File uploads.
data.reply_to
object
A quoted reply: { mid, preview } pointing at the message being replied to.

File uploads

Visitors and agents can attach images and documents to a message. The file bytes travel out-of-band — not through MoQT. You upload the bytes over HTTPS, get back an attachment meta, then publish a normal message envelope that references it.
1

POST the bytes

Send the raw file bytes to the control-plane API with the session token. The server enforces an allowed set of MIME types and a max size, and rejects anything outside them.
POST https://engine.telequick.dev/v1/chat/upload
Authorization: Bearer <session-token>
x-cc-filename: quarterly-report.pdf
Content-Type: application/pdf

<raw file bytes>
2

Read the attachment meta

The response is the attachment meta — store the whole object; you attach it to the message verbatim.
{ "id": "att_9f2…", "name": "quarterly-report.pdf", "size": 184320,
  "mime": "application/pdf", "url": "…" }
3

Publish a message that references it

Publish a normal message envelope with data.attachments set to an array of metas. The text may be empty for an attachment-only message.
{ "v": 1, "seq": 51, "ts": 1720800001000, "kind": "message", "role": "user",
  "data": { "mid": "m_abc", "text": "",
    "attachments": [ { "id": "att_9f2…", "name": "quarterly-report.pdf",
      "size": 184320, "mime": "application/pdf", "url": "…" } ] } }
Files download from the engine’s download URL at GET /v1/chat/attachment/<conv>/<id>/<name>, served from object storage.
On upload the engine emits a chat.file.uploaded event { role, count } and the transcript records "[file: <names>]", so an AI text agent has context about the file even though the bytes never crossed the AI’s text channel.

Reactions

Emoji reactions and quoted replies let either side annotate a specific message — addressed by its mid. A reaction is its own envelope kind:
{ "v": 1, "seq": 60, "ts": 1720800002000, "kind": "reaction", "role": "agent",
  "data": { "op": "add", "emoji": "👍", "target_mid": "m_abc" } }
data.op
string
required
add or remove.
data.emoji
string
required
The emoji being applied.
data.target_mid
string
required
The mid of the message being reacted to.
The engine relays the reaction to the other side and emits a chat.reaction event { role }.
On the widget these work with no code. The built-in UX includes a paperclip attach button, drag-and-drop, a multi-file picker (with accept filtering), an emoji picker, a hover quick-reaction strip, and reaction pills that show counts. The envelope and upload details above are for custom clients and for understanding the wire — see Widget & API and the Cookbook for the how.

Same flow engine as voice IVR

Every conversation runs through the same visual flow builder and step engine as voice — you are not learning a second product. The steps you already know from IVR apply directly to text:
  • Announcements — send a scripted message (a greeting, business hours, a policy notice).
  • Waits + queue position — hold the visitor and surface their position while they wait for an agent, delivered as event envelopes.
  • Present a form — emit a form and block on a validated form_result before continuing (lead capture, structured intake).
  • Queue to a skill — route the conversation to a skill queue, exactly like an ACD queue on the voice side.
A queue-to-skill step resolves to one of two handlers:
The conversation is handed to a text conversation DAG — the same agent runtime as voice, running in text mode instead of speech. It answers, can call tools, and can itself decide to escalate by queueing to a human skill.
Because AI and human handlers both publish through the engine onto server, a mid-conversation AI→human handoff is invisible to the widget: the visitor keeps the same panel and history, and the next envelope simply carries role: "agent" from a person instead of the model.

Honest limits

There is no polyglot SDK for chat today. The supported surfaces are the embeddable web widget and the agent-side console. Custom clients are possible by speaking the envelope protocol over raw MoQT, but that path is JS/widget-first and unversioned beyond the v field above — treat it as advanced usage, not a stable client library.

Widget & API

Embed the widget, configure its attributes, and speak the envelope protocol from a custom client.

Cookbook

Task snippets: embed on a page, present a form, route to a skill, hand off to a human.

Recipes

End-to-end: a support widget with AI-to-human handoff and a lead-capture form.

Agent runtime

The same runtime the AI text agent uses — here in text mode.

Human handoff

How escalation to a human works across modalities.

Skill queues (ACD)

The skill-based routing model the queue-to-skill step shares with voice.