Image optimization service for web games. Upload originals at full quality; the service precomputes lighter variants (AVIF and WebP, three size tiers), serves them as static files cacheable forever, and groups them into zip bundles downloadable in a single request. A JavaScript SDK picks the right variant for the player's device and network.
Base URL: https://games-backstages.com — Web interface: / (account required) — This page: /docs
[a-z0-9][a-z0-9-]{1,39}). Owned by an account.bonus/bomb ([A-Za-z0-9][A-Za-z0-9_-]*, segments separated by /). Replacing the image keeps the reference.bonus). No segment: group _root. Group _all contains the whole project.| tier | scale | max side | quality AVIF / WebP |
|---|---|---|---|
high | 100 % | 4096 px | 60 / 85 |
mid | 50 % | 2048 px | 55 / 80 |
low | 35 % | 1024 px | 45 / 70 |
CORS open (Access-Control-Allow-Origin: *): usable directly as WebGL textures.
| Route | Response |
|---|---|
GET /p/{project}/manifest.json | Project index (see below). Cache-Control: no-cache. |
GET /p/{project}/{ref}.{hash}.{tier}.{format} | One variant (format = avif | webp). Immutable, cached 1 year. |
GET /p/{project}/_bundles/{group}.{hash}.{tier}.{format}.zip | All images of a group in one zip (uncompressed, entries {ref}.{format}). Immutable. |
GET /i/{project}/{ref}?tier={tier}&format={avif|webp|auto} | 302 to the variant. Defaults: mid, auto (AVIF if the Accept header allows it). Stable URL for an <img>. |
GET /sdk/backstages.js | SDK (ES module), types: /sdk/backstages.d.ts. |
{
"project": "shardball",
"updatedAt": 1790409039586,
"base": "/p/shardball/",
"pattern": "{name}.{hash}.{tier}.{format}",
"bundlePattern": "_bundles/{group}.{hash}.{tier}.{format}.zip",
"tiers": { "high": { "scale": 1, "maxSide": 4096, "webp": 85, "avif": 60 }, "mid": { … }, "low": { … } },
"formats": ["avif", "webp"],
"images": {
"bonus/bomb": {
"hash": "250ce049cf", "w": 1254, "h": 1254, "bytes": 1172804,
"variants": { "high": { "w": 1254, "h": 1254, "avif": 39170, "webp": 73536 }, "mid": { … }, "low": { … } }
}
},
"bundles": {
"bonus": { "hash": "dd73691661", "images": ["bonus/bomb", "…"], "bytes": { "mid": { "avif": 87042, "webp": 163084 }, … } }
}
}
Variant URL: base + pattern; bundle URL: base + bundlePattern. bytes = size in bytes; w/h in pixels.
Each project has an API key, visible and regenerable in the web interface (project page). Header:
Authorization: Bearer {key}. Regenerating the key revokes the old one immediately.
| Route | Effect | Response |
|---|---|---|
GET /api/projects/{project} | Reads the manifest. | 200 manifest |
GET /api/projects/{project}/images/{ref} | Downloads the original file (private). | 200 image |
PUT /api/projects/{project}/images/{ref}body = raw image file | Creates or replaces the image, computes all variants and bundles (a few seconds). PNG, JPEG, WebP, AVIF, TIFF, GIF; 50 MB max. | 201 {changed: true, image}200 {changed: false, image} if identical |
POST /api/projects/{project}/images/{ref}?… | Edits the original in one pass, then re-processes once. Applied in this order:
1. op= comma-separated, in order: trim (crop to visible pixels, else to the uniform border),
center (center the subject in its transparent frame, same size), rotr/rotl (90° right/left),
flop/flip (horizontal/vertical mirror);
2. angle: free rotation, −45 to 45° (clockwise if positive), bilinear. fit=crop (default): cropped to the largest rectangle with the image's aspect ratio (no empty corners); fit=adapt: the whole image stays visible (frame enlarged, transparent corners, then tightened to visible pixels);
3. crop=left,top,right,bottom: manual crop, margins removed as fractions (0–1) of the image at this stage;
4. scale: downscale factor, 0.01–1, aspect ratio kept (area averaging, alpha-weighted);
5. color: saturation, contrast, brightness (multipliers 0–3, 1 = unchanged), hue (−180 to 180°).
Example: ?op=trim,rotr&angle=-7.5&scale=0.5&hue=30. The math is public in /pixels.js. | 200 {changed, image} |
DELETE /api/projects/{project}/images/{ref} | Deletes the image. | 204 |
DELETE /api/projects/{project}/folders/{folder} | Deletes every image in the folder. | 200 {deleted: n} |
image = {name, hash, ext, w, h, bytes, variants, updatedAt}.
curl -X PUT -H "Authorization: Bearer $KEY" --data-binary @bomb.png \ https://games-backstages.com/api/projects/my-game/images/bonus/bomb
JSON body {"error": "message"}. 400 invalid name or parameter · 401 missing or invalid key ·
404 unknown image, folder or project · 413 file too large · 415 unsupported format.
import { createAssets } from 'https://games-backstages.com/sdk/backstages.js';
const assets = createAssets({ base: 'https://games-backstages.com', project: 'my-game' });
await assets.ready; // reads the manifest
await assets.loadGroup('bonus'); // one zip for the whole folder; also loadAll(), loadImage(ref)
const bmp = assets.get('bonus/bomb'); // ImageBitmap (e.g. PIXI.Texture.from(bmp))
const url = assets.url('bonus/bomb'); // URL of the chosen variant
Options: tier (auto | low | mid | high), format (auto | avif | webp), decode (default createImageBitmap).
With auto, the tier depends on the screen (size × pixel density), device memory and data-saver mode,
then drops one step if the measured throughput is low; the format is AVIF if the browser can decode it.
No public sign-up: an administrator creates accounts. An account owns its projects and creates or deletes them
from the web interface. Session routes (/api/login, /api/projects…) serve the interface;
for automation, use a project's API key.