Render API
GET /api/still returns one
image/webp of one CS2 item, drawn by the same renderer as the live
embed. Use the URL as the src of an
<img> or as an Open Graph image.
The first request for a picture waits while it renders. Every request after it is served from the cache; see Caching and the placeholder.
Request
Name the item with its Steam inspect link, URL-encoded, and say how to draw it:
GET https://inspekt.gg/api/still?inspect=steam%3A%2F%2Frungame%2F730%2F76561202255233023%2F%2Bcsgo_econ_action_preview%20001807209A0228043004388F85D7F30340950512E67D72&view=front&bg=rarity&w=640&h=400&key=cs2pk_your_key_here
That link is an AK-47 | Redline at float 0.24 and pattern 661. view=front turns it
square-on, bg=rarity puts it on its Classified plate, and w and
h make the picture 640 × 400.
The link sets the item: the finish, by its definition index and paint index, and its float,
pattern, stickers, charm, StatTrak count and name tag. The link's rarity and quality fields are
ignored, and bg=rarity takes the tier from the catalog. Only view,
bg, light, w, h, wait and
key are read beside the link. A link works when it carries the item's data as hex: one copied for an item
inside CS2, or a "gen" link like this one, including one you build yourself. A link from a Steam
Community Market listing or a Steam inventory page ends in
S…A…D… or M…A…D… and is
refused with a 400.
Catalog ids
To name an item without a link, send its catalog id and the item parameters instead.
id=ak47_cu_ak47_cobra_gen&wear=0.24&seed=661 is the same Redline and returns
the same cached file.
Every catalog id is a row of /assets/skins.json,
one row per finish the service renders. A row carries the id, the
name as Steam shows it (AK-47 | Redline), the itemIndex
and paintKitIndex an inspect link carries as its definition index and paint index
(7 and 282), and the float range the finish is made in,
wearRemapMin to wearRemapMax. The file is about 0.8 MB and has no CORS
headers, so fetch it from your server or a build step. An id is the row's
weapon without its weapon_ prefix, then its kit, then
_gen: weapon_ak47 and cu_ak47_cobra make
ak47_cu_ak47_cobra_gen. The studio shows the same id as
s= in its address bar while the item is open.
Response
The response is the render. The first request for a picture waits while it renders, and every request after it gets the same file from the cache:
HTTP/1.1 200 OK
content-type: image/webp
cache-control: private, max-age=86400
x-cs2-counted: 1
x-request-id: b545991495f2
With wait=0, the first request gets a placeholder at once instead:
HTTP/1.1 200 OK
content-type: image/webp
cache-control: no-store
x-cs2-notes: this is a placeholder at the default wear and pattern, with no stickers, charm, StatTrak counter or name tag: retry for the exact render
x-cs2-counted: 1
x-request-id: 1820c7979e64
When we are busy, a server may get 202 Accepted at once, uncounted, with
Retry-After in seconds and x-cs2-queued, how many pictures are drawn
before it. Ask again after the Retry-After and keep only a 200. An
<img> gets the placeholder instead.
HTTP/1.1 202 Accepted
content-type: application/json; charset=utf-8
cache-control: no-store
retry-after: 3
x-cs2-queued: 12
x-cs2-counted: 0
x-request-id: 3f0a9c21d7b4
{"queued":true,"ahead":12,"retryAfter":3}
x-cs2-notes lists, separated by semicolons, what the picture shows differently
from the request, such as a placeholder or a wear outside the finish's range.
x-cs2-counted is 1 when the request counted toward the plan's render
quota and 0 when it did not; see Quota. Every
response carries an x-request-id. Quote it when you contact us about a request.
Parameters
| Param | Values | Default |
|---|---|---|
| inspect | A Steam inspect link, URL-encoded | required, unless id is sent, or charm for a
standalone charm, or sticker for a
standalone sticker |
| id | A catalog id, /^[a-z0-9][a-z0-9_.-]*$/i; see
Catalog ids |
required, unless inspect is sent, or charm for a
standalone charm, or sticker for a
standalone sticker |
| wear | 0 to 1. A value outside the finish's own range is
drawn at the nearest end of it and named in x-cs2-notes |
the finish's default |
| seed | Whole number, 0 to 1000 |
0, the pattern in Valve's inventory picture |
| st | Up to 5 stickers; see Stickers | none |
| sticker | Sent with no id, one sticker by itself, by its Steam
definition index; see Standalone stickers |
none |
| charm | One charm and where it hangs; see Charms. Sent
with no id, the charm by itself; see
Standalone charms |
none |
| stattrak | Whole number, 0 to 999999. Sending it makes
the item StatTrak, so stattrak=0 is a StatTrak item at zero kills |
a plain item |
| name | Name tag text, up to 20 characters, without control characters | none |
| view | hero, front, back or
top; see Views | hero |
| bg | A background name; see Backgrounds | steel |
| light | icon or inspect; see
Lighting | set by view |
| w, h | Whole numbers, 160 to 1600, or up to
2560 with a key on a plan; see HQ stills |
1200 × 630 |
| wait | 1 waits for the render, up to 95 seconds. 0
answers at once, with a placeholder when the picture is not cached yet; see
Caching and the placeholder |
1 |
| key | Your publishable key, /^[a-z0-9_-]{4,64}$/i. A server can
send its secret key in an x-cs2-key header instead; a publishable key there
needs an Origin naming a registered origin. See
Keys and origins |
required |
A value outside these bounds is refused with a 400 that names the parameter and
its bounds; see Errors.
Stickers
st is up to five stickers separated by commas, each
slot:stickerId:wear, optionally followed by :offsetX:offsetY:rotation.
Leave a placement field empty to keep the slot's default: 3:1044:0.12:::90 only
rotates the sticker in slot 3.
A slot is one of the sticker positions the weapon's model numbers, and an offset moves the
sticker from that position's default. Since CS2's free placement, a slot can appear more than
once and runs to 7. An M4A4 | Howl carrying a sticker in slot 0, one in slot 3 and three in slot 6,
with a Sticker Slab (see Sticker Slab):
GET https://inspekt.gg/api/still?id=m4a1_cu_m4a1_howling_gen&wear=0.170842&seed=655&st=0:103:0.00,3:7916:0.98:-0.3514:0.08:151.5,6:5952:0.00:0.2242:-0.0444:6,6:7916:0.98:0.0027:0.0179:130.5,6:7916:0.98:-0.0266:0.075:171&charm=37:15.9565:0.4259:14.7431~103&view=front&w=640&h=400&key=cs2pk_your_key_here
| Field | Bounds | Default |
|---|---|---|
| slot | 0 to 7, as often as the item has it | required |
| stickerId | Steam definition index, 1 to 999999 |
required |
| wear | 0 to 1, up to 4 decimals | required |
| offsetX, offsetY | -1 to 1, up to 4 decimals |
0, the slot's own position |
| rotation | -360 to 360 degrees, up to 4 decimals |
0 |
- A sticker's centre has to stay inside the area CS2's sticker editor allows on that weapon.
An offset that moves it outside is refused with a
400that names the slot and the nearest offset inside; see Errors. - A sticker is drawn at its slot's size. A seventh field (a scale) is ignored. An item from an inspect link keeps the scale its link records.
- A sticker sent to a slot without a surface, such as slot 3 of the M249, is left off the
picture, and one sent to a slot the model does not number, such as slot 6 on an AK-47, is
drawn at one it does.
x-cs2-notesnames both. - Overlapping stickers are drawn in slot order, and stickers sharing a slot in the order
stlists them, the later one on top. - A value out of bounds, or an
stthat fails to parse, refuses the whole request.
Standalone stickers
Send sticker with no id and no inspect, and the picture is the
sticker by itself, flat and square-on, as its inventory picture shows it. The value is the
sticker's Steam definition index, the same stickerId that st takes.
Paper, holo, foil, gold, glitter and lenticular stickers are each drawn in their own finish.
Space Wolves on a transparent background, 512 × 384:
GET https://inspekt.gg/api/still?sticker=9362&bg=transparent&w=512&h=384&key=cs2pk_your_key_here
sticker=9362It is a still of its own, cached and counted as any still. bg, w,
h, wait and key work as they do for a gun. A parameter
that describes a gun (wear, seed, st,
stattrak, name, charm, a view other than
hero, light=inspect or bg=rarity) is refused with a
400 that names it, and so is a number with no sticker behind it.
Charms
charm hangs one charm on the gun: any of the 143 charms in CS2, from any keychain
spot the game offers on that gun, at any pattern seed. The value is the charm's Steam definition
index, optionally followed by :offsetX:offsetY:offsetZ, a point on the gun that
picks the spot, then by @seed, and then, for a Sticker Slab, by
~stickerId, the sticker sealed in it; see Sticker Slab. On the M4A1-S | Fade, charm=60
hangs Lil' Chirp from the default spot, charm=60:-10.4688:0.345:2.4583 moves it to
the stock end, and charm=60@75000 hangs it from the default spot at seed 75000,
teal where seed 0 is pink. The overview shows four charms beside
Valve's pictures of them. Sent with no id, charm draws the charm by
itself; see Standalone charms.
| Field | Bounds | Default |
|---|---|---|
| defIndex | Steam definition index, 1 to 999999; see
Finding a charm |
required |
| offsetX, offsetY, offsetZ | -64 to 64, up to 4
decimals, in Source units (inches) in the gun model's own space; see
Spots |
the gun's default spot |
| seed | Whole number, 0 to 100000: the charm's
Charm Template in CS2; see Seeds |
0 |
| stickerId | Steam definition index, 1 to 999999: the
sticker sealed in a Sticker Slab; see Sticker Slab |
none |
Examples
Each of these draws a 640 × 400 picture from the front. Glitter Bomb on the Glock-18 | Neo-Noir, from the default spot:
GET https://inspekt.gg/api/still?id=glock_cu_glock_noir_gen&wear=0.03&seed=100&charm=73&view=front&w=640&h=400&key=cs2pk_your_key_here
Lil' Chirp on the Desert Eagle | Printstream at seed 75000:
GET https://inspekt.gg/api/still?id=deagle_cu_deag_printstream_gen&wear=0.03&seed=100&charm=60@75000&view=front&w=640&h=400&key=cs2pk_your_key_here
Glitter Bomb at the stock end of the AK-47 | Redline, at seed 40000:
GET https://inspekt.gg/api/still?id=ak47_cu_ak47_cobra_gen&wear=0.24&seed=661&charm=73:-9.874:0.718:-1.735@40000&view=front&w=640&h=400&key=cs2pk_your_key_here
8 Ball IGL on the same AK-47, sent a point 35 inches past the muzzle, which hangs it from the spot nearest that point, at the muzzle end:
GET https://inspekt.gg/api/still?id=ak47_cu_ak47_cobra_gen&wear=0.24&seed=661&charm=78:60:0:0&view=front&w=640&h=400&key=cs2pk_your_key_here
Finding a charm
Each charm is a row of
/assets/charms/index.json:
{"id":"kc_missinglink_lilhatchling","name":"Lil' Chirp","defIndex":60,"icon":"econ/charms/kc_missinglink_lilhatchling.webp","group":"Missing Link Community Charm Collection"}
defIndex is the number charm takes, and name is the
charm's name as Steam shows it. group is its collection or tournament.
icon is Valve's inventory picture of the charm, a 256 × 192 webp at that path
under /assets/, so Lil' Chirp's is
/assets/econ/charms/kc_missinglink_lilhatchling.webp.
The file has no CORS headers, so fetch it from your server or a build step.
An inspect link names its charm by the same number, the keychain's sticker_id, and
carries the charm's spot, seed and sealed sticker as well, so a request with
inspect draws the charm where its owner hung it. Every gun takes a charm. A charm
sent with a knife or gloves is left off the picture.
Sticker Slab
The Sticker Slab, defIndex 37, holds one sticker, named by its Steam definition
index after a ~: charm=37~103 hangs a Slab with sticker 103 sealed in
it from the default spot, and charm=37:15.9565:0.4259:14.7431@512~103 from another
spot at seed 512. The sticker is drawn in the window below the Slab's label, where the game's own
model places it. charm=37 is the Slab with no sticker, as its inventory picture shows
it.
- Only the Slab holds a sticker. A sticker sent with any other charm is refused with a
400; the still the embed draws leaves it out and names it inx-cs2-notes. - A sticker we hold no art for is left out of the picture and named in
x-cs2-notes. - The sealed sticker is part of the key a still is cached under, so
charm=37andcharm=37~103are two stills.
Spots
A gun's keychain spots are the ones the game lets a player drag a charm to. A
charm without offsets hangs from the default spot, where the game first hangs one.
The three offsets are a point in the gun model's own space, in Source units (inches): X runs
from the stock toward the muzzle, Y across the gun and Z up. They are the numbers an inspect link carries
as its keychain's offset_x, offset_y and offset_z. The
charm hangs from the spot nearest the point you send, so any point inside the bounds lands on
the gun.
- To pick a spot by eye, open the gun in the studio, add the charm, press
Move and click a dot. The address bar's
charm=then holds that spot's point, and a share link saves it too. - Send all three offsets or none.
charm=60:0:0:0is the default spot, the same request ascharm=60.
Seeds
The seed is the charm's Charm Template in CS2, a whole number from 0 to 100000, and the
pattern an inspect link carries for its keychain. It changes a charm the way the
game's own charm materials do, on most charms by shifting the hue. A charm sent without a seed
is drawn at seed 0, so charm=60, charm=60@0 and
charm=60:0:0:0 are one still. Offsets are cached as sent, so send each spot's point
the same way each time.
Errors
A charm outside these rules refuses the whole request with a 400
whose message names the field and its bounds. The checks read the value's form, so a number
with no charm behind it, such as charm=34, passes them on a gun: take the
defIndex from the index. The embed drops a
charm it cannot read and names it in x-cs2-dropped.
Standalone charms
Send charm with no id and no inspect, and the picture is the
charm by itself, as its inventory picture shows it, hanging at rest from its ring. The value is
defIndex[@seed][~stickerId], the same definition index, seed and sealed sticker a
charm takes on a gun.
Lil' Chirp at seed 75000:
GET https://inspekt.gg/api/still?charm=60@75000&w=640&h=400&key=cs2pk_your_key_here
charm=60@75000It is a still of its own, cached and counted as any still, and takes bg,
w, h, wait and key as a gun does. The
parameters that describe a gun, and offsets, which name a spot on one, are refused with a
400, as for a standalone sticker, and so is a
number with no charm behind it.
Views
| Value | What you get |
|---|---|
hero | The default. The item as its Steam inventory icon shows it. A gun is shown side-on at its icon's angle, and a knife lies on the diagonal with the tip to the upper left. |
front | Square-on to the sticker side through a 10° lens, muzzle left. The shot for a listing row. |
back | The other flank, muzzle right. |
top | Straight down, muzzle left, lined up with
front. |
Each view fits the whole item in the frame, so match w and h to the
item's shape, for example a wide frame for a rifle.
Backgrounds
| Value | What you get |
|---|---|
steel | The default. A cool wash at the top falling to near-black. |
black | #000000. |
white | #ffffff, for a listing row on a light
page. |
charcoal | #141619, the catalog cards' colour. A
black finish keeps its silhouette on it. |
ember | Orange rising off the bottom edge into black corners. |
violet | A violet halo behind the item. |
rarity | The plate of the item's own rarity tier, looked up for
you. Knives and gloves get Covert, and an item whose rarity we do not recognise gets
rarity-stock. |
rarity-stock … rarity-contraband | One plate per
CS2 rarity tier, the tier's colour rising off the bottom edge out of black:
rarity-stock, rarity-consumer, rarity-industrial,
rarity-milspec, rarity-restricted, rarity-classified,
rarity-covert, rarity-contraband. |
transparent | Only the item. The webp keeps its alpha channel, for compositing over your own background. Stills only. |
whiteandblackare exact in every corner, so awhitestill sits on a white page without a visible edge. The gradient backgrounds pass through lossy webp compression and can be a level off, so to match a rarity plate to your page, draw it in CSS as below.bg=rarityand the plate it resolves to are the same request and the same cached file.- To ask for a background outside this list, email hello@inspekt.gg.
Rarity plates in CSS
A gradient plate baked into a webp shows banding. On a page that loads a stylesheet, request
bg=transparent and draw the plate behind the item in CSS. plateCss()
builds it from the colours, stops and shape of the render's own plates:
var PLATE_COLOR = {
stock: '#9aa0a6', consumer: '#b0c3d9', industrial: '#5e98d9', milspec: '#4b69ff',
restricted: '#8847ff', classified: '#d32ce6', covert: '#eb4b4b', contraband: '#e4ae39',
};
// [position along the ramp, share of the tier colour left there]
var PLATE_STOPS = [[0, 0.8], [0.2, 0.4], [0.42, 0.124], [0.68, 0.036], [1, 0]];
// An ellipse 95% of the frame wide and 95% tall, centred 15% below its bottom edge.
function plateCss(tier) {
var hex = PLATE_COLOR[tier];
if (!hex) return null;
var n = parseInt(hex.slice(1), 16);
var rgb = [(n >> 16) & 255, (n >> 8) & 255, n & 255];
var stops = PLATE_STOPS.map(function (s) {
var c = rgb.map(function (v) {
return ('0' + Math.round(v * s[1]).toString(16)).slice(-2);
}).join('');
return '#' + c + ' ' + Number((s[0] * 100).toFixed(4)) + '%';
}).join(', ');
return 'radial-gradient(95% 95% at 50% 115%, ' + stops + ')';
}
Pass it the tier bg=rarity would resolve to, and set the result as the
background of the element around the <img>. For the Redline,
plateCss('classified') gives:
<div style="background: radial-gradient(95% 95% at 50% 115%, #a923b8 0%, #54125c 20%, #1a051d 42%, #080208 68%, #000000 100%)"><img src="https://inspekt.gg/api/still?id=ak47_cu_ak47_cobra_gen&wear=0.24&seed=661&view=front&bg=transparent&w=640&h=400&key=cs2pk_your_key_here" width="640" height="400" referrerpolicy="strict-origin" alt="AK-47 | Redline, Field-Tested"></div>
Keep bg=rarity for an Open Graph image, and anywhere else a single file is fetched
without a stylesheet.
Lighting
| Value | What you get |
|---|---|
icon | The clean studio look of the inventory. The default for
hero. |
inspect | The look of the in-game inspect screen. Every view is lit from
the viewer's side, and foil and gold stickers reflect it as they do in the game. The
default for front, back and top. |
- Send
light=iconto draw afront,backortopview under the icon light. - Any other value is refused with a
400: "light must be one of icon, inspect". - An
iconstill and aninspectstill of the same item are two files, each cached and counted as a still. - A standalone charm takes
icononly.
Keys and origins
A key is required. A request without one is refused with 401 and
key_required, from another site's page and from a server alike. Image links shared
from inspekt.gg carry a sig parameter in place of a key. They serve link previews
and open in a browser tab, and inside another site's page they are refused like any request
without a key.
A request with a key is checked against the key's registered origins, using the request's
Origin header or, when it has none, the origin in its Referer. An
<img> sends only the Referer, and a browser puts the page's origin
in it by default. A registered origin is served. Any other is refused with 403 and
origin_not_allowed.
An origin is a scheme, a host and an optional port, with no path. http origins
register as well as https ones, so a page in development can use a key:
register http://localhost:3000 or http://127.0.0.1:8461 as you would
https://example.com. Each one counts toward the plan's
origins per key.
A keyed request missing both headers is refused with 403 and
origin_required. Put referrerpolicy="strict-origin" on the
<img>, and on an <iframe> you build yourself, as the snippets
on this site do, so the browser sends the origin even on a page whose own
Referrer-Policy withholds the referrer. CS2Embed.mount() sets it on the
frames it builds. From your own server, send the key with an Origin header naming one of the
key's registered origins.
Secret keys and tokens
The publishable key is enough when your own pages make every request. Two optional credentials, both managed in the dashboard, cover calls from your server and short-lived keys for your pages.
| Credential | Where it goes | What checks it |
|---|---|---|
cs2pk_… | Your pages, in the page source. | The origin
list, and for the embed the browser's own
frame-ancestors. |
cs2sk_… | Your server, and your server alone. | The credential itself. It carries no origin list, so keep it out of anything a browser downloads. |
cs2tk_… | Your pages, in place of the publishable key. Minted by your server and valid for up to an hour. | The same origin list the publishable key is checked against. |
The secret key
Create one under Secret key in the dashboard. It is shown once, and we keep only a hash of it. Creating a new one retires the old one and every token it minted. Revoking it does the same and also turns off Require tokens.
The secret key works without an Origin or Referer header, so a backend
can call with it, and it is exempt from the
daily limit per visitor. It counts toward the
same monthly quota and requests-per-second allowance as the publishable key, and appears in the
same usage records.
Minting a token
A token is a publishable key that expires. Mint one with the secret key and send it in place of
key=. It works on /embed, /api/still and
/api/inventory-lookup, checked against the same registered origins.
POST https://inspekt.gg/api/token
x-cs2-key: cs2sk_your_secret_key_here
content-type: application/json
{"ttl": 3600}
200 OK
{"ok": true, "token": "cs2tk_…", "expiresIn": 3600, "expiresAt": "2026-09-22T19:04:11.000Z"}
ttl is in seconds, from 1 to 3600, and defaults to 3600. A larger value mints a
3600-second token, and 0 or less a 1-second one. Only the secret key can mint a token; a publishable key or a token answers 403 and
secret_key_required. Minting is rate limited per account and per address, so mint
one token per server process and reuse it until it nears expiry.
// One token per process, reused until it is nearly out of time. Minting one per
// request would spend a round trip on every page view and meet the rate limit.
let token = null;
let expiresAt = 0;
async function inspektToken() {
if (token && Date.now() < expiresAt - 60_000) return token;
const res = await fetch('https://inspekt.gg/api/token', {
method: 'POST',
headers: {
'x-cs2-key': process.env.INSPEKT_SECRET_KEY, // server-side only
'content-type': 'application/json',
},
body: JSON.stringify({ ttl: 3600 }),
});
if (!res.ok) throw new Error(`inspekt token: ${res.status} ${await res.text()}`);
const body = await res.json();
token = body.token;
expiresAt = Date.parse(body.expiresAt);
return token;
}
// In the page handler, the token goes where the publishable key used to:
app.get('/listing/:id', async (req, res) => {
const key = await inspektToken();
const item = await lookup(req.params.id);
res.send(renderPage({
still: `https://inspekt.gg/api/still?inspect=${encodeURIComponent(item.inspectLink)}`
+ `&view=front&w=640&h=400&key=${key}`,
}));
});
The 60-second margin leaves the browser time to use a token before it expires. A request with an
expired token answers 401 and token_expired. A token whose signature
fails, such as one minted by a secret key you have since rotated, answers 401 and
token_invalid. In both cases, mint another.
Require tokens
Require tokens is a dashboard setting, off by default. While it is on, your
publishable key answers 403 and token_required on all three surfaces,
and tokens and the secret key keep working. A token copied from your page source then works for
an hour at most, and only from a registered origin.
Turn it on in this order. Create the secret key, send tokens from your pages, check that your traffic is served, and turn the setting on last. Turning it on while your pages still send the publishable key refuses your own visitors.
Caching and the placeholder
A still is cached under its item and picture parameters (id, wear,
seed, st, charm, stattrak,
name, view, bg, light, w,
h) and the renderer's version. The same values return the same file, whichever form named the item, and
view=hero is the same request as leaving view off.
- Hit. The webp, with
cache-control: private, max-age=86400: the viewer's browser keeps it for 24 hours, and a CDN or proxy between us and the viewer does not keep it. When the renderer changes, the URL returns the new picture. - Miss. The response waits for the render, up to 95 seconds, and returns it. When we are busy it may answer at once instead; see The render queue.
- Miss with
wait=0. The placeholder at once, and the render is queued. The same URL returns the render once it is drawn.
A placeholder is a webp of your w × h, so an
<img> keeps its shape when the render replaces it. It shows the item at its
default wear and pattern, without stickers, charm, StatTrak counter or name tag, or a plain frame
of that size when we are busy. It answers cache-control: no-store, and
x-cs2-notes names it a placeholder. A request for an image gets one, uncounted, in
place of a render that failed or ran out of time, so the page shows the item and its next load
asks for the render again.
During an outage
While our rendering is down, every miss gets the placeholder at once, whatever
wait says and whoever asks, an <img> or a server. It is a
200 with cache-control: no-store, x-cs2-notes naming it a
placeholder and x-cs2-counted: 0, and it is free. Show it, and ask again later: the
request that gets the still is the one counted.
On a page
- Leave
waitoff. The first visitor to ask for a picture waits for its render and sees the exact item. - Pre-warm, so that no visitor waits. Request each still once from your server
before a page shows it, for example when the listing is created, and discard the body. Send the
key as
key=or in anx-cs2-keyheader, with anOriginheader naming one of the key's registered origins; see Keys and origins. The cache is shared across keys, so the<img>with the same parameters gets the file the pre-warm rendered. The pre-warm counts as one render. - Send
wait=0when the page must paint at once and you cannot pre-warm, such as a grid of items nobody has asked for yet. The first visitor sees placeholders, and the next load gets the renders.
You may store an image the API returns and show your copy for up to 24 hours from when we sent it. After that, request it again: Terms 3.5.
The render queue
New pictures are drawn in turn, and requests for the same picture share one render. When we
are busy, higher plans are served first, and a request whose picture is far off is answered at
once, uncounted, as Response shows. If we cannot take more, a server
gets 503 with code: "queue_full" and a Retry-After. A
wait that runs out is a 504. Ask again after the Retry-After.
Errors
An error body is JSON, {"error":"…","requestId":"…"}. An access refusal
and a full render queue add a code, and a 404 adds the defindex and
paintindex the link decoded to. An access refusal opened in a browser tab is shown as
a page instead.
An access refusal to a request for an image is a webp of the request's w ×
h with the status and the code drawn on it, so the page shows a card
where the image would be. A request is for an image when its Sec-Fetch-Dest header
is image, or, without that header, when its Accept header ranks an
image type above text/html, application/json and */*.
A request for an image gets the placeholder in place of a 502, a
504 or a queue_full; see
Caching and the placeholder.
| Status | Request | Error message |
|---|---|---|
| 400 | wear=1.5, or any value outside the bounds above |
"wear must be 0..1": the parameter and its bounds |
| 400 | id=ak47_redline, an id the catalog lacks |
"no such skin: ak47_redline" |
| 400 | id=ak47_cu_ak47_cobra_gen&st=0:1044:0.1:0.5, a sticker offset past
the weapon's sticker area |
"st slot 0 offset puts the sticker's centre at 0.6540,-0.4380, outside the area CS2 lets a sticker go on this weapon; the nearest offset inside is 0.3238,0.0308" |
| 400 | an inspect link ending in S…A…D… or
M…A…D…, or a value that fails to decode |
"inspect link: this is a market/inventory inspect link (S…A…D…): …" |
| 400 | an inspect link to an item without a finish, such as a vanilla knife |
"inspect link names no paint kit" |
| 404 | an inspect link to a finish the catalog lacks |
"item 7/1234 is not in the catalog" |
| 503 | a miss we cannot take right now; see The render queue | "this caller has too many stills queued, …" or similar, with
code: "queue_full" and Retry-After in seconds |
| 502 | a miss whose render fails | "still render failed: …" |
| 504 | a miss whose wait runs out, after up to 95 seconds | "still render timed out. Retry, it may be cached by then", with a
Retry-After in seconds when its render is still queued |
| 503 | a placeholder for an item with no picture to draw it from, such as the Budapest 2025 Highlight charm | "still not rendered yet. Retry shortly", with Retry-After: 10 |
An access refusal answers 401, 402, 403 or
429, and its code names the cause. key=x gets a
401 with "That is not a valid key." and key_unknown. A
429 carries Retry-After. See Error codes.
Quota
A request counts once toward the plan's monthly render quota when it gets a cached still, the
render it waited for, or, with wait=0, a placeholder whose render it queued or
joined. Every other placeholder and every error is uncounted. A token changes the URL every
hour, so a still fetched with one counts once per viewer, per picture, per 24 hours.
X-CS2-Counted says 1 or 0 on every response.
Past the quota, /api/still answers 429 with
code: "quota_exceeded" until the billing period renews; see
Limits & SLA.
HQ stills
A request with a key (a publishable key, a secret key or a token) on a plan or a free trial
may ask for w and h up to 2560. A request without a key
stops at 1600. Every answer to a key that may ask for more says how much in
x-cs2-still-max.
A still within 1600 × 1600 counts one render. A larger one counts one render per
1600 × 1600 of pixels, rounded up, cached or not, and says how many in
x-cs2-units: 2560 × 1440 counts 2, and 2560 × 2560 counts 3. A still
that counts more renders than the key has left is refused with 429 and
quota_exceeded, and the message says how many are left. A key may ask for 30
stills larger than 1600 px a minute; past that it gets 429 and
rate_limited with a Retry-After, and its smaller stills are served as
before.