Widget error codes, startAgent contract change

Widget failures now carry a real code, and the visitor sees the real reason

Every widget failure used to reach you as errorCode: "unknown" and show the visitor the same generic “Something went wrong starting the call.” Both are fixed: the error now carries the code for the failure that actually happened, and the visitor sees copy written for that failure — a blocked microphone now reads “Microphone access was blocked. Allow it in your browser settings to talk.”

The error object carries two separate codes, and the difference matters:

PropertyWhat it is
codeThe widget’s own code — mic_denied, insecure_context, session_rejected, connection_lost, and the rest. Branch on this.
serverCodeThe platform error envelope’s code, present only when the failure came from an API response — e.g. spend_budget_exceeded, spend_cap_exceeded.

A refused session mint is code: "session_rejected" whatever the reason, with the specific reason on serverCode. Spend-limit codes are not widget codes: branching on code === "spend_budget_exceeded" never matches. That split is deliberate — a new platform error code must not become a new widget code every integrator has to keep up with.

The full code table is now published in the embed guide.

The cause was two bundles each inlining their own WidgetError constructor, so instanceof was false across the bundle boundary and the whole per-code message lookup was dead.

startAgent connect failures now delivered on the promise rejection

A call to startAgent() that fails before the session is established now delivers the failure via the promise rejection only. Previously several of these went to onError and threw, reporting one visitor-visible failure twice and inflating widget.error on the class most likely to be actioned.

This is a programmatic startAgent contract change. If you wired only onError for connect failures, you now need a catch on the await:

1import { startAgent } from "https://cdn.speechify.ai/widget/agents.mjs";
2
3try {
4 await startAgent({ agentId: "agent_01HS..." });
5} catch (err) {
6 // err.code is the widget code: mic_denied, insecure_context,
7 // session_rejected, timeout. err.serverCode carries the platform
8 // code when the session mint was refused.
9}

The <speechify-agent> element is unaffected — it still raises its error event for both categories. Embedders using the element can stop reading here.