Error codes
Check the code field first. It puts the failure in one of two
families, and each family has its own fix.
- Access codes mean the key, origin or quota check refused the request. The embed and both JSON APIs share them. Fix the key, the origin or the plan.
- Fallback codes come from the embed alone. Access was granted and the frame loaded, and then the item could not be rendered in the visitor's browser. A new key or origin will not fix one.
Request IDs
Every response carries an x-request-id header, and every error repeats it in the
body as requestId: a 12-character hex string, unique to that request.
HTTP/1.1 401 Unauthorized
x-request-id: 4f9c1ad3e0b7
{"error":"That is not a valid key.","code":"key_unknown","requestId":"4f9c1ad3e0b7"}
Log it, and include it when you contact us, especially about a 5xx. With the id, we can find the exact request in our logs.
Access codes
| Code | HTTP status | Meaning |
|---|---|---|
| key_required | 401 | The request has no key. Add key=<your key>, or send your secret key
from a backend in an x-cs2-key header. A publishable key sent in that header
with no Origin is refused with origin_required. |
| key_unknown | 401 | The key is unrecognized: wrong format, a typo, or a key that does not exist. |
| key_disabled | 403 | The key has been disabled. If that is unexpected, email hello@inspekt.gg. |
| tenant_suspended | 403 | The account is suspended, and every request on it is refused. Email hello@inspekt.gg. |
| plan_required | 402 | The account has no plan: it is new, its free trial has ended, or its subscription has ended. Every request is refused until the account starts a free trial or subscribes in the dashboard. See Terms 5.7 and 5.8. |
| not_in_plan | 402 | The plan leaves this request out, for example a Lens key on the Inventory API. Move to Hobby or Pro in the dashboard. |
| origin_not_allowed | 403 | The requesting page's origin is not registered for this key. Add it in the dashboard. Keys and origins explains how the origin is read. |
| origin_required | 403 | The request carried a key without an Origin or Referer header.
Put referrerpolicy="strict-origin" on the <img>, and on the
<iframe> if you build it yourself (CS2Embed.mount() sets it),
so the browser sends the origin even when the page's
Referrer-Policy withholds the referrer. From a server, send an
Origin header naming a registered origin. See
Keys and origins. |
| token_required | 403 | The account has Require tokens on, and the request carried the publishable key. Send a token minted by your server with the secret key. See Require tokens. |
| token_expired | 401 | The token has expired. Mint another with your secret key. The Node example replaces its token a minute before it expires. |
| token_invalid | 401 | The token is one we did not issue, or the secret key that minted it has been rotated or revoked, or the account's publishable key has been rotated since. Mint another. |
| secret_key_required | 403 | POST /api/token was called with a publishable key or a token. Only the
account's secret key can mint a token. See
Minting a token. |
| quota_exceeded | 429 | The plan's monthly quota for this kind of request is spent. Requests of that kind are
refused until the quota resets at the end of the billing period, the time given in the
Retry-After header and the error message. Other kinds keep working, and
moving up a plan takes effect on the next request. See
Limits & SLA. |
| ip_daily_limit | 429 | One visitor address has reached the account's daily limit for this kind of request on
this key. Retry-After points at the next 00:00 UTC. Other visitors are
served as usual. Set the limit, or turn it off, in the dashboard;
see Daily limit per visitor. |
| rate_limited | 429 | Requests arrived faster than the plan's requests-per-second allowance, which all three
surfaces share. Retry after the number of seconds in Retry-After. See
Requests per second. |
A refused request does not count toward any quota.
Refusals by surface
| Surface | Reported as |
|---|---|
/api/still, /api/inventory-lookup |
JSON: { error, code, requestId } (plus limit/used
for quota_exceeded and ip_daily_limit), at the status above. On /api/still, a request
for an image, such as an <img>, gets a webp card of the requested
w × h with the status and the code drawn on it, at the same
status. See Render API. |
/embed |
A refusal card and a postMessage error event. See below. |
The embed is a page, so it cannot return JSON. A refused
/embed does both of these at once:
- Draws a refusal card in place of the viewer, with one sentence and the error code. The card can be framed on any site, so it appears wherever the misconfiguration is.
- Fires a postMessage
errorevent:{ channel: "cs2embed", type: "error", code, message }, addressed to theoriginyou set, or*with none. See postMessage API.
Use the card while you integrate, and log the event in production.
The visitor's browser also enforces the origin list. A served /embed sends the
key's registered origins in a Content-Security-Policy: frame-ancestors header, so
the browser draws the viewer only when every page around it is on that list. The refusal card
goes out with frame-ancestors *, so it shows on whichever site used the key.
Fallback codes (embed only)
These occur after access is granted, when the item cannot be shown in the visitor's browser.
The frame keeps the still it has already painted and puts the reason in a note over it. A frame
with no still shows a 2D card in place of the 3D view. Either way, it 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 has no WebGPU support. |
| no-item | "No item was specified." | Both inspect and s are missing. |
| bad-inspect | "That inspect link could not be read." | The inspect value cannot be decoded, for example a Steam Market or
inventory link ending in M…A…D… or
S…A…D…. |
| not-in-catalog | "This finish is not renderable yet." | The inspect link names a real item and finish that the catalog does not have yet. |
| unknown-id | "That item is not in the catalog." | The s catalog id matches no item. |
| index-unavailable | "The viewer is temporarily unavailable." | The catalog index failed to load. |
| boot-failed | "The 3D renderer could not start on this device." | WebGPU was available, and the renderer failed to initialize. |
| load-failed | "This item could not be loaded." | The item's assets failed to load after the renderer started. |
If you build embed URLs from your own data, validate the inspect links and catalog ids first.
Invalid ones produce bad-inspect and unknown-id, the two fallback codes
an integration produces most often.