Integrate the SDK
Errors
When something fails in a session — while it starts, while it runs, or when a request you made is rejected — it reaches your code as one typed error. You catch it, explain it to the user in your own words, and branch on it; every code it can carry is listed below.
The error object
Every error is one object:
Tip
error.code, never on the wording of any message, which may change.Two messages, for two readers
Every error carries both, and they are not interchangeable. userMessage is written for the person using your app and arrives already translated into the session's language, so you can put it on screen as it is. message is English and written for you: it names options, settings and values, and belongs in a log, not in your UI.
// Show one, log the other.
showBanner(error.userMessage); // translated, for the person
log.error(error.code, error.message); // English, for you
// Reporting a problem? Quote the id of the answer it came from.
log.error("reference", session.getState().requestId);Set the language with uiLanguage, on createClient or on a single session. Leave it unset and Speechineer picks the one the browser asked for.
The id to quote
state.requestId is the id of the most recent answer. It belongs to the call rather than to any one error, which is why it sits on the state and not on the error. The same id follows that request through every part of Speechineer that handled it — so quoting it in a bug report is the difference between us guessing and us looking.
Categories and classes
Every error also belongs to a category, and each category has its own class. Catch the class to handle a whole family at once — SpeechineerQuotaError covers every way an allowance can run out — or read error.code when you need the specific cause:
| Category | Class | When |
|---|---|---|
| client | SpeechineerClientError | Raised by the SDK itself — nothing was sent. Fix locally; most are worth a retry button. |
| auth | SpeechineerAuthError | A credential travelled and was refused. Fix the token or key configuration; do not retry unchanged. |
| quota | SpeechineerQuotaError | An allowance is spent. Surface it to the user or your operations; retrying will not help until limits change. error.meta carries the numbers — see below. |
| access | SpeechineerAccessError | The credentials are valid but this workspace, organization or account may not do it — nothing on the page side changes it. |
| not_found | SpeechineerNotFoundError | Something the request names does not exist. Check the key, version and language — retrying unchanged never helps. |
| conflict | SpeechineerConflictError | The request is well formed but disagrees with how the thing it names is already set up. |
| validation | SpeechineerValidationError | Speechineer understood the request and refused its content. One entry per offending field: read them all, and each entry's meta.field names the field it is about. |
| method | SpeechineerMethodError | The address is right and the request is not. Nothing was read and nothing changed — these four say the request could not be accepted as it was sent, before anything looked at what it asked for. |
| not_acceptable | SpeechineerNotAcceptableError | Your `Accept` header rules out every format available for this answer. |
| too_large | SpeechineerTooLargeError | The content is fine; there is too much of it. Nothing was stored. |
| unsupported_media | SpeechineerUnsupportedMediaError | The body may be perfectly well-formed — it is the content type that is not supported here. |
| rate_limit | SpeechineerRateLimitError | Nothing is spent — you are going faster than allowed. Retrying later is the fix. |
| runtime | SpeechineerRuntimeError | The session was live and could not continue. It is over — start a new one. |
| service | SpeechineerServiceError | Ours, not yours. Retrying later is reasonable; tell us the request id if it persists. |
| upstream | SpeechineerUpstreamError | Something Speechineer relies on replied with something unusable. Retrying later is reasonable. |
| unavailable | SpeechineerUnavailableError | Nothing answered at all. Retrying later is reasonable. |
| timeout | SpeechineerTimeoutError | The work may or may not have happened. Retry only what is safe to repeat. |
The numbers on a quota error
A quota error is the one category that carries figures, on error.meta. The keys are the same whether the allowance ran out on the request that started the session or while it was running, so one piece of code renders both:
| Key | What it holds |
|---|---|
| limit | The ceiling that bound, in units. |
| used | How much of it was already consumed. |
| remaining | What was left — 0 whenever the error fired. |
| layer | Which allowance bound, as `<scope>:<period>` — e.g. `account:month`. This is what tells a spent account apart from a spent workspace. |
Listen for errors
However a session fails, you never have to hunt for the error: the same object is delivered to three places, each suited to a different job — a callback for reacting the moment something fails, the session state for rendering, and the rejected promise for the code that asked. Use whichever fits the code you are writing.
- The
onErrorcallback — called the moment it happens. Log the error or send it to analytics from there:tsconst voice = useSpeechToForm({ form: intakeForm, onError: (error) => showBanner(error.code), });tsreadonly voice = injectSpeechToForm({ form: intakeForm, onError: (error) => this.showBanner(error.code), });tsconst session = speechineer.speechToForm({ form: intakeForm, onError: (error) => showBanner(error.code), }); - The session state — the failure sits in
errors(the first entry is the one to show) and stays until the nextstart(). Render your message from there —describeis your own mapping from a code to your wording:tsx{voice.errors.length > 0 && <p role="alert">{voice.errors[0].userMessage}</p>}html@if (voice.errors()[0]; as error) { <p role="alert">{{ error.userMessage }}</p> }tssession.subscribe((state) => { const cause = state.errors[0]; errorEl.hidden = !cause; errorEl.textContent = cause ? cause.userMessage : ""; }); - The rejected promise —
start()andend()reject with the same error, so anawaitwhere you call them catches it directly:tsxconst onSpeak = async () => { try { await voice.start(); } catch (error) { showBanner(error.code); } };tsasync speak() { try { await this.voice.start(); } catch (error) { this.showBanner(error.code); } }tsspeak.onclick = async () => { try { await session.start(); } catch (error) { showBanner(error.code); } };
Error codes
One section per category, so a section is exactly what one catch gives you: the class in the heading is what instanceof matches, and the codes under it are every way that class can arrive. The codes are frozen — they will not change under you.
In the browser
SpeechineerClientError · client — Raised by the SDK itself — nothing was sent. Fix locally; most are worth a retry button.
| Code | Meaning | Possible fix |
|---|---|---|
| MICROPHONE_DENIED | Microphone access was blocked, or the page is not on a secure origin (HTTPS or localhost). Worth asking again. | Serve over HTTPS or localhost and surface the browser's permission prompt again — offer a retry button. |
| MICROPHONE_UNAVAILABLE | No usable microphone was found. | Ask the user to plug in or enable a microphone, then retry; check the browser's device permissions. |
| AUDIO_UNSUPPORTED | This browser cannot record audio in the required format. | Ask the user to switch to a current browser; no configuration on your side changes this. |
| NETWORK | The request never got an answer — connectivity, or the browser blocked the request (check its console). | If the browser console shows a blocked cross-origin request, your origin was refused — check baseUrl for typos and tell us the origin you serve from. Otherwise check connectivity and retry. |
| NO_SESSION | An action needed a running session and there was none. You should not see this in normal use: the actions that need a session open one themselves, and the ones that do not are quiet when there is nothing to act on. It is the safety net, not a calling-order rule. | Nothing in how you call it — you do not have to call start() first. If you do see it, send us state.requestId. |
| NO_CLIENT | No client is provided above this component — the provider / provide call is missing. | Wrap the component in the provider (React), call the provide function at bootstrap (Angular), or pass the client explicitly. |
| NO_AUTH | The SDK had no credentials to send — nothing reached Speechineer. Distinct from AUTH_REQUIRED, which means a credential was sent and refused. | Pass the API key to createClient — or, in Signed mode, make sure your token endpoint returns a token. |
| NO_ACCOUNT | An account is required alongside an API key, and none was given. | Pass account to createClient, or to the session when it differs per user. |
| UNKNOWN | Something failed in a way the SDK could not name — the last resort, not a category. | Read error.cause for what actually threw, and tell us state.requestId if one is set. |
Authentication
SpeechineerAuthError · auth — A credential travelled and was refused. Fix the token or key configuration; do not retry unchanged.
| Code | Meaning | Possible fix |
|---|---|---|
| AUTH_REQUIRED | A credential was presented and Speechineer refused it — missing, malformed or expired. | Check the token has not expired and is minted for the right workspace; re-mint and retry once. |
| API_KEY_INVALID | The API key in the token is unknown or inactive. | Check the key against API keys in your workspace; create a new one if it was deactivated or archived. |
| AUTH_SIGNATURE_REQUIRED | The workspace or key requires a PS256-signed token; alg=none was presented. | This page needs a Signed key: mint PS256 tokens in your backend with a signing key, or use an Unsigned key only where the workspace allows it. |
| AUTH_MODE_MISMATCH | The token's alg does not match the API key's auth mode (Signed keys accept only PS256; Unsigned keys only alg=none). | Check which client the page resolves and which mode its key carries — one app can hold both (see Authentication). |
| AUTH_ENVELOPE_INVALID | The token is malformed, or its claims are missing or wrong. | Compare the claims your backend signs with the reference set (api_key, account_key, aud, iat, exp, jti) and the alg / kid header. |
| AUTH_ENVELOPE_REPLAYED | This token was already used (replayed `jti`). | Mint a fresh token per session start with a unique jti; never cache a token across sessions. |
| AUTH_ENVELOPE_LIFETIME_EXCEEDED | The token is valid for longer than the maximum allowed lifetime. | Shorten exp − iat in your backend; a few minutes is plenty, the SDK asks for a new token every start. |
| ACCOUNT_REQUIRED | The token does not name an account. | Add the account claim to the token (Signed) or pass account.key to createClient (Unsigned). |
| SIGNING_KEY_NOT_FOUND | The token's key id matches no active signing key in the workspace. | Put the signing key's id in the token's kid header, and check the key is still active in Signing keys. |
| NOT_AUTHENTICATED | Nothing usable was presented at all — no credential reached Speechineer, where one was required. | Check that the API key or token is actually being sent; this is what an empty credential looks like from our side. |
| SECRET_KEY_REQUIRED | This call needs the secret half of the key, and only the key id was sent. | Send the full key, not just its id — the id alone identifies a key, it does not authenticate it. |
| SECRET_KEY_INVALID | The secret sent alongside the key id does not match that key. | Re-copy the key from your workspace; a key is shown once, so a truncated paste looks exactly like this. |
Quotas and billing
SpeechineerQuotaError · quota — An allowance is spent. Surface it to the user or your operations; retrying will not help until limits change. error.meta carries the numbers — see below.
| Code | Meaning | Possible fix |
|---|---|---|
| API_KEY_QUOTA_EXCEEDED | The API key's units limit is reached. | Raise or remove the key's units limit in its settings, or wait for the period to roll over. |
| WORKSPACE_QUOTA_EXCEEDED | The workspace units limit is reached. | Raise the workspace limit in its settings, or wait for the period to roll over. |
| ACCOUNT_QUOTA_EXCEEDED | The account's units limit is reached. | Raise the account's limit from your backend or in Accounts; tell the user their allowance is used up. |
| FORM_QUOTA_EXCEEDED | The form's units limit is reached. | Raise the form's units limit in its settings, or wait for the period to roll over. |
| PLAN_ALLOWANCE_EXHAUSTED | Included units are used up and no prepaid units remain. | Buy prepaid units or wait for the next cycle; the dashboard shows what remains. |
| OVERSPEND_LIMIT_REACHED | The configured overspend cap is reached. | Raise the overspend cap in Billing, or wait for the next cycle. |
Not allowed
SpeechineerAccessError · access — The credentials are valid but this workspace, organization or account may not do it — nothing on the page side changes it.
| Code | Meaning | Possible fix |
|---|---|---|
| WORKSPACE_INACTIVE | The workspace is deactivated or archived. | Reactivate the workspace in its settings; an archived workspace cannot be brought back — create a new one. |
| CLIENT_INACTIVE | The owning organization is deactivated. | Contact your organization's owner; nothing on the page side fixes this. |
| ACCOUNT_INACTIVE | The end user's account is deactivated or archived. | Reactivate the account in Accounts, or point the session at a different one — an archived account is final. |
| FEATURE_NOT_AVAILABLE | The plan does not include the capability this session asked for. | Check what your plan covers in Billing; ask us to enable the capability if you need it. |
| PERMISSION_DENIED | The caller is known and the thing is closed to them — the general refusal, where no more specific one fits. | Check which workspace the key belongs to and what it is allowed to reach; retrying unchanged never helps. |
| INTERNAL_IDENTITY_PROTECTED | The account named is one Speechineer provisions and manages, and it cannot be used from an integration. | Pass one of your own accounts instead — the reserved one exists so your usage and ours never share a row. |
Named but not there
SpeechineerNotFoundError · not_found — Something the request names does not exist. Check the key, version and language — retrying unchanged never helps.
| Code | Meaning | Possible fix |
|---|---|---|
| FORM_NOT_FOUND | No form with this key in the workspace. | Compare form.key with the key shown on the form's page; a key belongs to one workspace only. |
| FORM_VERSION_NOT_PUBLISHED | The version your code names exists but is a draft. | Publish it from the form's header (Publish this version), or point version at the published one. |
| NO_PUBLISHED_VERSION_FOR_LANGUAGE | No published version exists for the requested language. | Add the language to the form and publish, or request a language the published version supports. |
| WORKFLOW_NOT_FOUND | No session answers that id — it was never created, or it has ended and been swept. This is the one the SDK watches for: it starts a fresh session rather than reporting a failure. | Nothing, normally — the SDK resumes by starting a new session. If you hold session ids yourself, stop reusing that one. |
| WORKFLOW_KIND_NOT_FOUND | No workflow of that kind exists. | Check the workflow name against the ones your plan enables; leave it unset to get the default. |
| ENDPOINT_NOT_FOUND | No endpoint answers that path — the URL is wrong, as opposed to the thing it names being missing. | Check the method and the path against the reference, and check baseUrl for a stray path segment. |
| NOT_FOUND | Something the request names does not exist, where no more specific code fits. | Check every key the call passes against what your workspace holds. |
| WORKFLOW_FEATURE_UNKNOWN | The workflow does not offer the capability the session asked for. | Check which capability you start against what the workflow supports. |
| ADAPTER_CONFIG_NOT_FOUND | A model this session pins by name is not registered for your workspace. | Check the model keys you pass; leave them unset to use the ones the form already names. |
Conflicts with what exists
SpeechineerConflictError · conflict — The request is well formed but disagrees with how the thing it names is already set up.
| Code | Meaning | Possible fix |
|---|---|---|
| FORM_KEY_PORTAL_OWNED | This form is configured in Speechineer — use source: "workspace" for it. | Name the form with source: "workspace" instead of passing an inline definition under that key. |
| COMPONENT_IN_USE | The thing you tried to remove is still referenced by something live. | Detach or archive what still points at it, then retry. |
| ACTION_PRECONDITION_UNMET | An action was called before the thing it needs had happened — asking for an extraction before any text arrived, for instance. | Wait for the state the action depends on (watch the session state) and call it then; the same call succeeds once the precondition holds. |
The request was not acceptable
SpeechineerValidationError · validation — Speechineer understood the request and refused its content. One entry per offending field: read them all, and each entry's meta.field names the field it is about.
| Code | Meaning | Possible fix |
|---|---|---|
| VALIDATION_ERROR | The request was refused and no single field is named — the summary that accompanies the field-level entries below. | Read every entry of errors, not just the first; each names its own field in meta.field. |
| PARSE_ERROR | The request body could not be parsed as the content type it declared — malformed JSON, most often. | Check that the body is valid JSON and that Content-Type matches what you sent. |
| FORM_FIELD_INVALID | A field in an inline form definition is not valid for its type. | Check the field's type against what it carries — a slider needs a range, a select needs options. |
| CONFIG_INVALID | This workflow's configuration is incomplete or invalid. | Check the model keys and prompts the session pins; an inline form must supply everything the models it selects require. |
| WORKFLOW_STANDALONE_MODE_UNSUPPORTED | This workflow has no standalone mode. | Use a workspace form for this workflow; inline definitions are not supported here. |
| ADAPTER_CONFIG_NOT_ALLOWED | A model was pinned for a capability this workflow does not have. | Pin models only for the capabilities the workflow actually runs. |
| ADAPTER_CONFIG_NOT_ENTITLED | Your plan does not include the model this session pins. | Choose a model your plan covers, or ask us to enable it. |
| FORM_VERSION_LANGUAGE_UNAVAILABLE | The published version does not carry the language the session asked for. | Add the language to the form and publish it, or ask for one the version already has. |
| FEATURE_CONFIG_INVALID | The capability's configuration is not valid for this form. | Read every entry of errors — each names the setting and what was wrong with it. |
| FEATURE_FIELD_CONFIG_REQUIRED | A field is configured in code but the call did not supply its configuration. | Pass fieldConfigs for every field the form leaves open for code. |
| FEATURE_SETTING_NOT_ALLOWED | A setting the call passes may not be set from code for this form. | Drop the setting — the form's configuration owns it. |
| FEATURE_SETTING_INVALID | A setting the call passes is outside the values it accepts. | Read the entry's meta for the accepted range, and pass a value inside it. |
| FIELD_REQUIRED | A field the request must carry was absent or empty. | Pass the field named in meta.field. |
| FIELD_TYPE_INVALID | A value is of the wrong kind — a string where a number belongs, and so on. | Send the field named in meta.field as the type it declares. |
| FIELD_TOO_LARGE | A number is above its maximum. | Keep the value inside the bounds in the entry's meta. |
| FIELD_TOO_SMALL | A number is below its minimum. | Keep the value inside the bounds in the entry's meta. |
| FIELD_TOO_LONG | More characters than the field holds. | Shorten the value to the length in the entry's meta. |
| FIELD_TOO_SHORT | Fewer characters than the field requires. | Lengthen the value to the minimum in the entry's meta. |
| FIELD_PATTERN_INVALID | A value does not match the shape the field requires. | Match the pattern in the entry's meta — it is the one the field was configured with. |
| FIELD_ENUM_INVALID | A value is not one of the accepted ones. | Send one of the values in the entry's meta; a select accepts only what it offers. |
The wrong way to ask
SpeechineerMethodError · method — The address is right and the request is not. Nothing was read and nothing changed — these four say the request could not be accepted as it was sent, before anything looked at what it asked for.
| Code | Meaning | Possible fix |
|---|---|---|
| METHOD_NOT_ALLOWED | That path exists, but it does not accept this HTTP method. | Use the method the endpoint documents — a create is a POST, a read a GET. |
Nothing we can send back
SpeechineerNotAcceptableError · not_acceptable — Your `Accept` header rules out every format available for this answer.
| Code | Meaning | Possible fix |
|---|---|---|
| NOT_ACCEPTABLE | No representation of the answer matches the `Accept` header that was sent. | Accept `application/json`, or drop the header and take the default. |
Too big to read
SpeechineerTooLargeError · too_large — The content is fine; there is too much of it. Nothing was stored.
| Code | Meaning | Possible fix |
|---|---|---|
| REQUEST_TOO_LARGE | The request body is larger than this endpoint will read. | Send less in one request — split a long list, or upload a smaller file. |
A type we do not read
SpeechineerUnsupportedMediaError · unsupported_media — The body may be perfectly well-formed — it is the content type that is not supported here.
| Code | Meaning | Possible fix |
|---|---|---|
| UNSUPPORTED_MEDIA_TYPE | The body's content type is not one this endpoint reads. | Send the content type the endpoint documents, and set `Content-Type` to match the body. |
Too much at once
SpeechineerRateLimitError · rate_limit — Nothing is spent — you are going faster than allowed. Retrying later is the fix.
| Code | Meaning | Possible fix |
|---|---|---|
| THROTTLED | Too many requests in the window this endpoint counts. Nothing was spent and nothing changed. | Slow the request rate down and retry; the answer is safe to repeat. |
| CONCURRENCY_LIMIT_REACHED | Too many workflows are running concurrently. | End sessions you no longer need, queue starts on your side, or ask for a higher concurrency limit. |
A running session stopped
SpeechineerRuntimeError · runtime — The session was live and could not continue. It is over — start a new one.
| Code | Meaning | Possible fix |
|---|---|---|
| TRANSCRIPTION_FAILED | Transcription stopped and could not be restarted. | Start a new session. If it recurs on one form, try its prompts in the Engineer tab. |
| EXTRACTION_FAILED | Field extraction stopped and could not be restarted. | Start a new session. If it recurs on one form, try its prompts in the Engineer tab. |
| AUDIO_CHUNK_INVALID | An audio chunk arrived in a shape transcription cannot accept, so the recording stopped. | Start a new session. If you capture audio yourself rather than through the SDK, send the format the session answer asks for. |
Speechineer could not answer
SpeechineerServiceError · service — Ours, not yours. Retrying later is reasonable; tell us the request id if it persists.
| Code | Meaning | Possible fix |
|---|---|---|
| INTERNAL_ERROR | Something failed on our side that we did not anticipate. | Retry once; if it persists, send us state.requestId. |
| LIFECYCLE_INVARIANT_BROKEN | Something on our side was asked to do what its own state forbids. Nothing in the request causes it. | Start a new session; if it recurs, send us state.requestId — this one is always worth reporting. |
| REQUEST_FAILED | Speechineer answered, but not in a shape that carried a code — something between you and us answered on its behalf (a proxy, a gateway). The status and the raw body are on error.meta. | Read error.meta.status and error.meta.body. A 502 or 503 here is usually network infrastructure, not your request. |
A dependency answered badly
SpeechineerUpstreamError · upstream — Something Speechineer relies on replied with something unusable. Retrying later is reasonable.
| Code | Meaning | Possible fix |
|---|---|---|
| UPSTREAM_INVALID | A service Speechineer depends on returned an answer it could not use. | Retry; if it persists, send us state.requestId. |
A dependency could not be reached
SpeechineerUnavailableError · unavailable — Nothing answered at all. Retrying later is reasonable.
| Code | Meaning | Possible fix |
|---|---|---|
| SERVICE_UNAVAILABLE | A service Speechineer depends on could not be reached. | Retry with a short backoff; if it persists, send us state.requestId. |
A dependency took too long
SpeechineerTimeoutError · timeout — The work may or may not have happened. Retry only what is safe to repeat.
| Code | Meaning | Possible fix |
|---|---|---|
| ACTION_TIMEOUT | An action did not complete within the time allowed for it. | Retry the action; if it times out repeatedly, send us state.requestId. |