Error codes
Two families of error, from two different layers, reported differently depending on which surface you're calling. Knowing which one you're looking at is most of the debugging.
Access codes (below) come from the key/origin/ quota gate every public surface shares. They mean "you're not allowed to make this request right now" and apply identically to the embed and both JSON APIs.
Fallback codes are specific to the embed and mean something different: access was granted, the frame loaded, and then the specific item or your browser couldn't be rendered (a bad inspect link, no WebGPU, and so on).
Access codes
| Code | HTTP status | Meaning |
|---|---|---|
| key_required | 401 | No key was given, and the request isn't recognisably first-party
(same-origin/no-Origin) traffic. Add key=<your key>. |
| key_unknown | 401 | The key doesn't match any registered key: wrong shape, a typo, or a key that was never issued. |
| key_disabled | 403 | The key is real but has been disabled. Get in touch if that's unexpected. |
| origin_not_allowed | 403 | The requesting page's origin isn't in this key's registered origins. Add it in the dashboard. |
| quota_exceeded | 429 | Over the plan's monthly limit for this kind of request. Carries a
Retry-After header. See Limits & SLA for exactly
what happens next on your plan. |
How each surface reports an access refusal
| Surface | How it's reported |
|---|---|
/api/still, /api/inventory-lookup |
JSON: { error, code } (plus limit/used for
quota_exceeded), at the status above. |
/embed |
Both of the following, at once. See below. |
The embed can't return JSON. It's a page, not an API call. When
/embed is refused, it does both of the following:
- Serves an HTML page in place of the viewer, with a short plain-language sentence and the error code, so it never shows as a blank or black box on your site. This card allows framing from anywhere on purpose, so it's visible wherever the misconfiguration actually is.
- Fires a postMessage
errorevent:{ channel: "cs2embed", type: "error", code, message }, addressed to theoriginyou set (or*with none). Your code can catch that signal instead of relying on someone to notice the card. See postMessage API.
Handle whichever one fits how you monitor things: watch for the event if you already have error reporting on your page, or read the card if you're eyeballing it during integration.
The visitor's browser enforces this too, independent of anything on our side: a key's registered
origins go directly into a Content-Security-Policy: frame-ancestors header on
/embed. An origin_not_allowed refusal on a stolen key holds even if our
server-side check were ever wrong, because the browser refuses to paint the frame on an unregistered
page regardless.
Fallback codes (embed only)
These happen after access was granted. The frame loaded successfully, but the specific
item or the visitor's browser couldn't be shown. The frame renders a small 2D card in place
of the 3D view, and fires a postMessage fallback event: { channel: "cs2embed",
type: "fallback", code, message, id }.
| Code | Card text | Cause |
|---|---|---|
| no-webgpu | "The live 3D view needs WebGPU. Open this in Chrome or Edge." | The visitor's browser can't run the renderer. |
| no-item | "No item was specified." | Neither inspect nor s was given. |
| bad-inspect | "That inspect link could not be read." | The inspect value isn't a link this service can decode. |
| not-in-catalog | "This finish is not renderable yet." | The inspect link decoded to a real item/paint pair that isn't in the catalog yet. |
| unknown-id | "That item is not in the catalog." | The s catalog id doesn't match anything. |
| index-unavailable | "The viewer is temporarily unavailable." | The catalog index itself failed to load. |
| boot-failed | "The 3D renderer could not start on this device." | WebGPU was available but the renderer failed to initialize. |
| load-failed | "This item could not be loaded." | The item's assets failed to load after the renderer started. |
These are not access problems, and registering an origin or a valid key won't fix them: they're
about the specific item or the visitor's device. bad-inspect and unknown-id
are the two worth validating up front if you generate embed URLs from your own data.