Platform rendering tutorial

Generate CS2 Skin Images from Inspect Links

Build a Node.js script that turns one CS2 inspect link into front and back PNG files.

Last updated 2026-09-04 · Reviewed by OpenSkin · 35 minutes · Intermediate

What you will build

Create a Node.js script that reads the item details from one inspect link and saves matching front and back PNG files. In OpenSkin, a scene is a saved set of camera and background settings.

Front and back PNG files for the same inspected CS2 item
Front and back PNG files generated from the same inspected item.

Before you start

  • Node.js 20 or newer
  • A Platform API key with inspect:read, catalog:read, render:create, and render:read
  • An inspect link copied directly from a current CS2 listing
  • Front and back scene IDs saved in Scene Builder

Project files

exact-item-media/
  render-item.mjs
  output/
    front.png
    back.png

Build it

  1. Save front and back settings

    In Scene Builder, save one set of camera and background settings for the front and another for the back. Then copy both scene IDs.

  2. Read the inspect link once

    The script reads the item details from one inspect link and reuses them for both images.

  3. Create the two images

    The script requests one front image and one back image. The API returns an image request ID for each; the script checks those IDs until the PNG files are ready.

  4. Wait for completion

    The script checks each job until it succeeds. It stops on failed, canceled, or timeout states.

  5. Save both PNG files

    Completed download links expire, so the script immediately saves output/front.png and output/back.png.

  6. Run the script

    Set the variables and run the exact command below from the project directory.

render-item.mjs

Complete inspect-to-image workflow

import {createHash} from 'node:crypto'
import {mkdir, writeFile} from 'node:fs/promises'
const API = process.env.OPENSKIN_BASE_URL ?? 'https://api-v2.openskin.dev'
const key = process.env.OPENSKIN_API_KEY
const [inspectLink, frontSceneId, backSceneId] = process.argv.slice(2)
if (!key || !inspectLink || !frontSceneId || !backSceneId) throw new Error('Set OPENSKIN_API_KEY and pass inspect link, front scene ID, and back scene ID.')

async function request(path, options = {}) {
  const response = await fetch(API + path, {...options, headers:{'X-API-Key':key, 'Content-Type':'application/json', ...(options.headers ?? {})}})
  const body = await response.json()
  if (!response.ok) throw new Error(`${response.status}: ${body.error?.message ?? 'Request failed'}`)
  return body
}

const resolved = await request('/v2/inspect/resolve-item', {method:'POST', body:JSON.stringify({link:inspectLink})})
const item = resolved.data?.render?.request
if (resolved.data?.render?.status !== 'available' || !item) throw new Error('The inspect link did not resolve to a renderable item.')

async function createRender(sceneId, view) {
  const output = {width:1280,height:960,format:'png',background:'transparent'}
  const requestKey = createHash('sha256').update(JSON.stringify({inspectLink,sceneId,view,output})).digest('hex').slice(0,24)
  const created = await request('/v2/renders', {method:'POST', headers:{'Idempotency-Key':`tutorial-${requestKey}`}, body:JSON.stringify({item, scene_id:sceneId, output})})
  return created.job.id
}
async function waitForRender(id) {
  for (let attempt = 0; attempt < 60; attempt += 1) {
    const job = await request(`/v2/renders/${id}`, {headers:{'Content-Type':'application/json'}})
    if (job.data.state === 'succeeded') return job.data.result
    if (['failed','canceled'].includes(job.data.state)) throw new Error(`Render ${id} ended in ${job.data.state}`)
    await new Promise(resolve => setTimeout(resolve, 2000))
  }
  throw new Error(`Render ${id} timed out`)
}

await mkdir('output', {recursive:true})
for (const [view, sceneId] of [['front',frontSceneId],['back',backSceneId]]) {
  const result = await waitForRender(await createRender(sceneId, view))
  const image = await fetch(result.url)
  if (!image.ok) throw new Error(`Download failed (${image.status})`)
  await writeFile(`output/${view}.png`, Buffer.from(await image.arrayBuffer()))
  console.log(`Saved output/${view}.png (1280 x 960)`)
}

Run it

export OPENSKIN_API_KEY='replace-with-your-key'
export INSPECT_LINK='replace-with-a-current-inspect-link'
export FRONT_SCENE_ID='replace-with-front-scene-id'
export BACK_SCENE_ID='replace-with-back-scene-id'
node render-item.mjs "$INSPECT_LINK" "$FRONT_SCENE_ID" "$BACK_SCENE_ID"

Expected output

Saved output/front.png (1280 x 960)
Saved output/back.png (1280 x 960)

How it works

  • Both images use the item details returned by one inspect-link request, so their wear, pattern, stickers, charm, StatTrak, and name tag match.
  • The two saved scenes control the camera angle and lighting.
  • The API download link is temporary. The two files saved in output are the copies you keep.

Verify the result

Confirm that output/front.png and output/back.png both exist and open correctly at 1280 × 960. Both files must use the item details read from the single inspect link.

Troubleshooting

What you seeLikely causeWhat to do
The inspect link does not resolveThe link is old, expired, or missing item data.Copy a current inspect link. Do not guess the missing wear or pattern.
409 idempotency conflictThe unique request key was reused with different item, scene, or image settings.Use a new request key when the item, scene, or image settings change.
The image download returns 403The temporary download link expired before the script saved it.Run the image job again and save the result as soon as it completes.