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

Codes are stable — branch on 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.

ts
// 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:

CategoryClassWhen
clientSpeechineerClientErrorRaised by the SDK itself — nothing was sent. Fix locally; most are worth a retry button.
authSpeechineerAuthErrorA credential travelled and was refused. Fix the token or key configuration; do not retry unchanged.
quotaSpeechineerQuotaErrorAn 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.
accessSpeechineerAccessErrorThe credentials are valid but this workspace, organization or account may not do it — nothing on the page side changes it.
not_foundSpeechineerNotFoundErrorSomething the request names does not exist. Check the key, version and language — retrying unchanged never helps.
conflictSpeechineerConflictErrorThe request is well formed but disagrees with how the thing it names is already set up.
validationSpeechineerValidationErrorSpeechineer 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.
methodSpeechineerMethodErrorThe 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_acceptableSpeechineerNotAcceptableErrorYour `Accept` header rules out every format available for this answer.
too_largeSpeechineerTooLargeErrorThe content is fine; there is too much of it. Nothing was stored.
unsupported_mediaSpeechineerUnsupportedMediaErrorThe body may be perfectly well-formed — it is the content type that is not supported here.
rate_limitSpeechineerRateLimitErrorNothing is spent — you are going faster than allowed. Retrying later is the fix.
runtimeSpeechineerRuntimeErrorThe session was live and could not continue. It is over — start a new one.
serviceSpeechineerServiceErrorOurs, not yours. Retrying later is reasonable; tell us the request id if it persists.
upstreamSpeechineerUpstreamErrorSomething Speechineer relies on replied with something unusable. Retrying later is reasonable.
unavailableSpeechineerUnavailableErrorNothing answered at all. Retrying later is reasonable.
timeoutSpeechineerTimeoutErrorThe 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:

KeyWhat it holds
limitThe ceiling that bound, in units.
usedHow much of it was already consumed.
remainingWhat was left — 0 whenever the error fired.
layerWhich 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 onError callback — called the moment it happens. Log the error or send it to analytics from there:
    ts
    const voice = useSpeechToForm({
      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 next start(). Render your message from there — describe is your own mapping from a code to your wording:
    tsx
    {voice.errors.length > 0 && <p role="alert">{voice.errors[0].userMessage}</p>}
  • The rejected promise — start() and end() reject with the same error, so an await where you call them catches it directly:
    tsx
    const onSpeak = async () => {
      try {
        await voice.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.

CodeMeaningPossible fix
MICROPHONE_DENIEDMicrophone 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_UNAVAILABLENo usable microphone was found.Ask the user to plug in or enable a microphone, then retry; check the browser's device permissions.
AUDIO_UNSUPPORTEDThis browser cannot record audio in the required format.Ask the user to switch to a current browser; no configuration on your side changes this.
NETWORKThe 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_SESSIONAn 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_CLIENTNo 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_AUTHThe 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_ACCOUNTAn account is required alongside an API key, and none was given.Pass account to createClient, or to the session when it differs per user.
UNKNOWNSomething 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.

CodeMeaningPossible fix
AUTH_REQUIREDA 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_INVALIDThe 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_REQUIREDThe 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_MISMATCHThe 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_INVALIDThe 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_REPLAYEDThis 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_EXCEEDEDThe 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_REQUIREDThe token does not name an account.Add the account claim to the token (Signed) or pass account.key to createClient (Unsigned).
SIGNING_KEY_NOT_FOUNDThe 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_AUTHENTICATEDNothing 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_REQUIREDThis 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_INVALIDThe 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.

CodeMeaningPossible fix
API_KEY_QUOTA_EXCEEDEDThe 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_EXCEEDEDThe workspace units limit is reached.Raise the workspace limit in its settings, or wait for the period to roll over.
ACCOUNT_QUOTA_EXCEEDEDThe 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_EXCEEDEDThe 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_EXHAUSTEDIncluded 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_REACHEDThe 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.

CodeMeaningPossible fix
WORKSPACE_INACTIVEThe workspace is deactivated or archived.Reactivate the workspace in its settings; an archived workspace cannot be brought back — create a new one.
CLIENT_INACTIVEThe owning organization is deactivated.Contact your organization's owner; nothing on the page side fixes this.
ACCOUNT_INACTIVEThe 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_AVAILABLEThe 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_DENIEDThe 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_PROTECTEDThe 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.

CodeMeaningPossible fix
FORM_NOT_FOUNDNo 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_PUBLISHEDThe 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_LANGUAGENo 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_FOUNDNo 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_FOUNDNo workflow of that kind exists.Check the workflow name against the ones your plan enables; leave it unset to get the default.
ENDPOINT_NOT_FOUNDNo 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_FOUNDSomething 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_UNKNOWNThe workflow does not offer the capability the session asked for.Check which capability you start against what the workflow supports.
ADAPTER_CONFIG_NOT_FOUNDA 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.

CodeMeaningPossible fix
FORM_KEY_PORTAL_OWNEDThis 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_USEThe thing you tried to remove is still referenced by something live.Detach or archive what still points at it, then retry.
ACTION_PRECONDITION_UNMETAn 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.

CodeMeaningPossible fix
VALIDATION_ERRORThe 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_ERRORThe 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_INVALIDA 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_INVALIDThis 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_UNSUPPORTEDThis workflow has no standalone mode.Use a workspace form for this workflow; inline definitions are not supported here.
ADAPTER_CONFIG_NOT_ALLOWEDA model was pinned for a capability this workflow does not have.Pin models only for the capabilities the workflow actually runs.
ADAPTER_CONFIG_NOT_ENTITLEDYour plan does not include the model this session pins.Choose a model your plan covers, or ask us to enable it.
FORM_VERSION_LANGUAGE_UNAVAILABLEThe 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_INVALIDThe 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_REQUIREDA 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_ALLOWEDA setting the call passes may not be set from code for this form.Drop the setting — the form's configuration owns it.
FEATURE_SETTING_INVALIDA 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_REQUIREDA field the request must carry was absent or empty.Pass the field named in meta.field.
FIELD_TYPE_INVALIDA 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_LARGEA number is above its maximum.Keep the value inside the bounds in the entry's meta.
FIELD_TOO_SMALLA number is below its minimum.Keep the value inside the bounds in the entry's meta.
FIELD_TOO_LONGMore characters than the field holds.Shorten the value to the length in the entry's meta.
FIELD_TOO_SHORTFewer characters than the field requires.Lengthen the value to the minimum in the entry's meta.
FIELD_PATTERN_INVALIDA 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_INVALIDA 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.

CodeMeaningPossible fix
METHOD_NOT_ALLOWEDThat 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.

CodeMeaningPossible fix
NOT_ACCEPTABLENo 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.

CodeMeaningPossible fix
REQUEST_TOO_LARGEThe 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.

CodeMeaningPossible fix
UNSUPPORTED_MEDIA_TYPEThe 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.

CodeMeaningPossible fix
THROTTLEDToo 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_REACHEDToo 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.

CodeMeaningPossible fix
TRANSCRIPTION_FAILEDTranscription stopped and could not be restarted.Start a new session. If it recurs on one form, try its prompts in the Engineer tab.
EXTRACTION_FAILEDField 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_INVALIDAn 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.

CodeMeaningPossible fix
INTERNAL_ERRORSomething failed on our side that we did not anticipate.Retry once; if it persists, send us state.requestId.
LIFECYCLE_INVARIANT_BROKENSomething 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_FAILEDSpeechineer 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.

CodeMeaningPossible fix
UPSTREAM_INVALIDA 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.

CodeMeaningPossible fix
SERVICE_UNAVAILABLEA 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.

CodeMeaningPossible fix
ACTION_TIMEOUTAn action did not complete within the time allowed for it.Retry the action; if it times out repeatedly, send us state.requestId.