Every authorization failure carries a semanticDocumentation Index
Fetch the complete documentation index at: https://docs.mascot.bot/llms.txt
Use this file to discover all available pages before exploring further.
code and an actionable
message. The SDK surfaces them as typed errors so your app can show the
right next step (“re-subscribe”, “update card”, “use a dev key”) instead of a
flat 401.
The taxonomy
Five classes, mapped to failure domains:| Class | Domain |
|---|---|
LipsyncError | Base. Also used directly for client-side codes (e.g. bad_timeline). |
LicenseError | Authorization at init. |
RefusedError | Hard refusal (init or refresh) — not retryable. |
NetworkError | Transport failure reaching the edge service. |
EngineError | Inference/runtime failure. |
Authorization codes
The edge worker verifies the key and maps the rejection to one envelope.| HTTP | code | Fires when | Recommended UI |
|---|---|---|---|
| 401 | missing_bearer | No Authorization header. | Fix the integration (key not sent). |
| 401 | invalid_api_key | Key not recognized — typo’d or never existed. | ”Check the key at app.mascot.bot/api-keys.” |
| 401 | key_expired | Dev keys auto-expire 30 days after creation. | ”Mint a fresh key.” |
| 402 | key_disabled | Key disabled — canonical canceled/revoked path. | ”Re-subscribe, or mint a new key.” |
| 402 | subscription_past_due_expired | Payment failed and the grace period ran out. | ”Update your card.” |
| 402 | subscription_canceled | Subscription canceled (identity preserved). | ”Re-subscribe.” |
| 403 | wrong_key_scope | Key used on a surface it is not authorized for. | ”Issue the matching key type.” |
| 403 | prod_key_on_localhost | Production key from localhost. | ”Use a development key locally.” |
| 403 | dev_key_on_public_domain | Development key from a public origin. | ”Use a production publishable key.” |
| 403 | origin_not_allowed | Production key + origin not on the allow-list. | ”Add the origin at app.mascot.bot/security.” |
| 403 | origin_allowlist_empty | Production key with no origins configured. | ”Configure the allow-list.” |
| 403 | missing_origin | Production key with no Origin header. | Fix the request. |
| 429 | rate_limited | Key verification throttled. Transient. | ”Try again in a few seconds.” |
| 404 | session_expired | The referenced session is gone (refresh only). Hard refusal. | ”Reload the page to start a fresh session.” |
session_expired realistically happens when a tab is backgrounded long enough
that throttled refresh ticks let the session lapse. It is never recoverable by
retry — only a fresh init (a reload) recovers, so prompt the user immediately.
Client-side codes (no network)
SomeLipsyncErrors are thrown entirely client-side and still carry
.code:
| Class | code | Fires when | Treat as |
|---|---|---|---|
LipsyncError | bad_timeline | parseTimeline(input) rejected a persisted/untrusted timeline — version mismatch, non-positive frameMs, non-monotonic cue offsets, missing leading t: 0, out-of-range viseme id, or wrong field types. | ”Regenerate via client.processAudio()” — not a license/network condition. |
Wire format
Every error response is JSON withContent-Type: application/json; the HTTP
status is the envelope status:
.code / .message. When a body
is not JSON (5xx, network blip) the SDK synthesizes a status-derived envelope,
so .code is always present.
Next
Licensing & keys
Why these refusals happen.
API conventions
Why the taxonomy is shaped this way.
Troubleshooting
Fixing them in practice.