API
REST API for fetching pixabot images, animations, and metadata
All endpoints are CORS-enabled (Access-Control-Allow-Origin: *) and return cache-friendly headers (1-day fresh + 7-day stale-while-revalidate) for deterministic URLs. You can embed pixabots directly from any site — no proxy, no key, no auth.
GET /api/pixabot/{id}
Returns a pixabot image or metadata.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
id | path | required | 4-char base36 pixabot ID (e.g. 2156, f76a) |
size | query | 128 | Image size in pixels, any integer from 32 to 1920. Nearest-neighbor scaled from the 32×32 source. |
format | query | — | Set to json for metadata, svg for vector output |
animated | query | — | Set to true for an animated GIF. Any size 32–1920, same as PNG. |
webp | query | — | With animated=true, return lossless animated WebP instead of GIF. Smaller files, preserves alpha. |
speed | query | 1 | Animation speed multiplier (0.25–4). Only works with animated=true. |
hue | query | — | Hue rotation in degrees (0–359). Shifts all colors while preserving relative palette. |
saturate | query | — | Saturation multiplier (0 = greyscale, 1 = unchanged, up to 4 = very punchy). |
bg | query | — | Background color as #rrggbb hex. Flattens transparency onto a solid color. |
v | query | — | Cache-bust key. Pass an integer matching ANIM_VERSION from @pixabots/core; value is ignored by the renderer, so any change forces a new CDN cache entry. See Animation versioning. |
PNG (default)
curl https://pixabots.com/api/pixabot/2156 -o pixabot.png
curl https://pixabots.com/api/pixabot/2156?size=480 -o pixabot-480.png
curl https://pixabots.com/api/pixabot/2156?size=1920 -o pixabot-huge.pngSizes
Any integer from 32 to 1920 works. Multiples of 32 produce the cleanest pixel scaling (every source pixel becomes an integer block); non-multiples still render via nearest-neighbor. Common choices:
| Size | Use case |
|---|---|
32 | Native source, crispest for small avatars |
64, 96 | Favicons, dense lists |
128, 192, 240 | Default avatars, chat UIs |
256, 384, 480 | Profile cards, hero avatars |
512, 768, 960 | High-DPI, retina displays |
1920 | Posters, social banners |
Animated GIF
Add ?animated=true to get the idle bounce animation. Works at any size from 32 to 1920, same as the PNG endpoint:
curl https://pixabots.com/api/pixabot/2156?animated=true -o bounce.gif
curl https://pixabots.com/api/pixabot/2156?animated=true&size=480 -o bounce-480.gif
curl https://pixabots.com/api/pixabot/2156?animated=true&size=1920 -o bounce-huge.gifThe animation is 8 frames at 72ms (~14fps). Tune speed with ?speed=0.25 (slow-mo) through ?speed=4 (fast).
Palette (hue + saturation)
Shift the whole character's colors without drawing new sprites. Works with PNG, GIF, and WebP outputs (SVG is unaffected — use CSS there).
curl "https://pixabots.com/api/pixabot/2156?size=240&hue=120" -o green.png
curl "https://pixabots.com/api/pixabot/2156?size=240&saturate=0" -o grey.png
curl "https://pixabots.com/api/pixabot/2156?size=240&hue=200&saturate=1.5" -o cold.pnghue rotates hue in degrees (0–359). saturate is a multiplier: 0 greyscale, 1 unchanged, 4 max.
Background (bg)
Default output has a transparent background. Add ?bg=%23rrggbb to flatten it onto a solid color — handy for GIFs (which don't support true alpha) and for baking a final exported avatar.
curl "https://pixabots.com/api/pixabot/2156?size=240&bg=%23fde68a" -o sunny.png
curl "https://pixabots.com/api/pixabot/2156?animated=true&bg=%23000000" -o on-black.gifWorks with PNG, GIF, WebP, and SVG outputs. Pairs with hue/saturate.
SVG (vector)
Add ?format=svg for a vector output — one <rect> per opaque pixel, scales crisply to any size without interpolation.
curl "https://pixabots.com/api/pixabot/2156?format=svg&size=480" -o pixabot.svgGood for print, large displays, or anywhere you want infinite zoom without stepping through the raster size enum.
Animated WebP
Smaller files than GIF, with alpha preserved. Add ?webp=true together with animated=true:
curl "https://pixabots.com/api/pixabot/2156?animated=true&webp=true&size=480" -o bounce.webpJSON metadata
curl https://pixabots.com/api/pixabot/2156?format=json{
"id": "2156",
"combo": { "eyes": 2, "heads": 1, "body": 5, "top": 6 },
"parts": {
"eyes": "glasses",
"heads": "blob",
"body": "wings",
"top": "mohawk"
},
"png": "https://pixabots.com/api/pixabot/2156?size=128",
"gif": "https://pixabots.com/api/pixabot/2156?size=128&animated=true"
}Errors
curl https://pixabots.com/api/pixabot/zzzz
# → {"error":"Invalid pixabot ID"}GET /api/pixabot/{id}/frames
Returns the animation timeline for a pixabot as JSON. Hand it to a <canvas> / CSS sprite-sheet renderer to animate client-side — skips the animated-GIF rate limit, stays immutably cached, and lets you scrub, pause, or speed-up without re-hitting the API.
curl https://pixabots.com/api/pixabot/2156/frames{
"id": "2156",
"combo": { "eyes": 2, "heads": 1, "body": 5, "top": 6 },
"parts": { "eyes": "glasses", "heads": "blob", "body": "wings", "top": "mohawk" },
"animVersion": 1,
"frameMs": 72,
"loopLength": 16,
"layerOrder": ["top", "body", "heads", "eyes"],
"nativeSize": 32,
"frames": [
{
"tick": 0,
"offsets": { "top": 0, "heads": 0, "eyes": 0, "body": 0 },
"spriteIndex": { "top": 0, "heads": 0, "eyes": 0, "body": 0 }
},
...
],
"sprites": {
"top": { "url": "https://pixabots.com/parts/top/mohawk.png", "frames": 1, "kind": "static" },
"body": { "url": "https://pixabots.com/parts/body/wings.png", "frames": 1, "kind": "static" },
"heads": { "url": "https://pixabots.com/parts/heads/blob.png", "frames": 1, "kind": "static" },
"eyes": { "url": "https://pixabots.com/parts/eyes/glasses.png", "frames": 2, "kind": "blink" }
}
}To render one frame at tick t:
- For each layer in
layerOrder, drawsprites[layer].urlinto a 32×32 canvas, cropping horizontally to the32 × spriteIndex[layer]column. - Offset each layer vertically by
frames[t].offsets[layer]native pixels. - Advance
teveryframeMsms, wrapping atloopLength.
animVersion matches ANIM_VERSION from @pixabots/core. Cache-bust your client when it changes.
GET /api/pixabot/batch
Get metadata (id, combo, parts, png, gif URLs) for many pixabots in one JSON response.
| Parameter | Type | Default | Description |
|---|---|---|---|
ids | query | — | Comma-separated list of up to 100 IDs. Mutually exclusive with count. |
count | query | — | Return N random pixabots (1–100). Mutually exclusive with ids. |
size | query | 128 | Size used in the returned png / gif URLs. |
curl "https://pixabots.com/api/pixabot/batch?ids=2156,f76a,0000"
curl "https://pixabots.com/api/pixabot/batch?count=20&size=240"{
"count": 3,
"pixabots": [
{
"id": "2156",
"combo": { "eyes": 2, "heads": 1, "body": 5, "top": 6 },
"parts": { "eyes": "glasses", "heads": "blob", "body": "wings", "top": "mohawk" },
"png": "https://pixabots.com/api/pixabot/2156?size=128",
"gif": "https://pixabots.com/api/pixabot/2156?size=128&animated=true"
},
...
]
}With ids, responses are cached immutably (same input → same output). With count, Cache-Control: no-store so each call returns a fresh batch.
GET /api/pixabot/random
Returns a random pixabot. Redirects (302) to the deterministic URL so the image gets cached. The redirect itself is sent with Cache-Control: no-store so each call returns a different bot.
| Parameter | Type | Default | Description |
|---|---|---|---|
size | query | 128 | Forwarded to the redirect target |
animated | query | — | Set to true for an animated GIF. Forwarded to the redirect target. |
speed | query | 1 | Forwarded to the redirect target. Animation speed multiplier (0.25–4). |
format | query | — | Set to json to get the random ID directly (no redirect) |
curl -L https://pixabots.com/api/pixabot/random -o random.png
curl -L "https://pixabots.com/api/pixabot/random?animated=true&size=480" -o random.gif
curl https://pixabots.com/api/pixabot/random?format=jsonEmbedding
Drop a pixabot anywhere — any site, any framework. CORS is wide open and images cache on the CDN for a day (with 7 more days of stale-while-revalidate) so repeat loads stay instant.
HTML
<img
src="https://pixabots.com/api/pixabot/2156?size=240"
alt="pixabot 2156"
width="240"
height="240"
style="image-rendering: pixelated"
/>React / Next.js
<img
src={`https://pixabots.com/api/pixabot/${id}?size=240&animated=true`}
alt={`pixabot ${id}`}
width={240}
height={240}
style={{ imageRendering: "pixelated" }}
/>Markdown
CSS background
.avatar {
background-image: url("https://pixabots.com/api/pixabot/2156?size=240");
image-rendering: pixelated;
}Fetch from JS
const res = await fetch("https://pixabots.com/api/pixabot/2156?format=json");
const { png, gif, parts } = await res.json();iframe widget
For contexts that don't allow raw <img> from cross-origin (or if you want a self-contained frame that respects reduced motion and a background param), use the embed widget:
<iframe
src="https://pixabots.com/embed/2156?size=240&bg=0b0f14"
width="240"
height="240"
style="border:0; background:transparent"
title="pixabot 2156"
loading="lazy"
></iframe>Query params: size (integer 32–1920, default 240), animated (false to disable, default true), bg (hex without #).
Always use image-rendering: pixelated in CSS so browsers don't antialias the pixel art.
Caching
Deterministic endpoints return Cache-Control: public, max-age=86400, stale-while-revalidate=604800. Same ID + size + flags always produces the same image for a given animation version, so the CDN keeps it fresh for a day and can serve stale for another 7 days while re-fetching in the background. That gives sprite / animation changes a natural rollout window (≤ 1 day for fresh traffic, ≤ 8 days for the long tail). The /random endpoint never caches (302 redirect, target cached normally).
Animation versioning
Animated output (?animated=true) is deterministic and CDN-cached for 1 day + 7 days of stale-while-revalidate. If we change the animation — new frame timing, new blink schedule, new bounce curve — fresh traffic picks up the new render within a day, and the long-tail (CDN SWR + consumer HTTP cache) can linger up to 8 days. Use ?v=N when you need instant propagation.
To opt into the latest render, add ?v=N to the URL. N is the ANIM_VERSION integer exported by @pixabots/core:
import { ANIM_VERSION } from "@pixabots/core";
// ANIM_VERSION === 1curl "https://pixabots.com/api/pixabot/2156?animated=true&v=1" -o bounce.gif@pixabots/react automatically appends the current v to every animated <Pixabot />, so upgrading the npm package is enough to pick up new animations. Static <img src> and <iframe> embeds need the param appended manually after we bump ANIM_VERSION.
The server ignores the value — any change to v just creates a fresh CDN cache entry.
Maintainer runbook (when the animation changes):
- Bump
ANIM_VERSIONinpackages/core/src/animation.ts. - Bump
@pixabots/coreand@pixabots/reactminor versions, publish. - (Optional) Purge the Vercel CDN so pinned raw-URL consumers also get the new render.
CORS
Every response includes:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, OPTIONS
Access-Control-Allow-Headers: Content-TypeWorks from any origin, including file:// for local prototyping.
Rate limiting
Expensive endpoints (animated GIF/WebP + OG image rendering) have a best-effort per-IP rate limit:
| Path | Limit |
|---|---|
/api/pixabot/{id}?animated=true | 30 requests / minute |
/api/og | 20 requests / minute |
Static PNG (/api/pixabot/{id}), SVG (?format=svg), JSON metadata, and /batch are not rate-limited — those are cheap and immutably cached at the CDN, so repeat hits never reach the origin.
On exceed, the API returns 429 Too Many Requests with:
Retry-After: 42
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1713478200The limit is per-IP per-lambda-instance, in-memory. It's not a hard guarantee — cold starts reset the counter and multiple Vercel instances have independent counters. Think of it as a guardrail, not a firewall.
OpenAPI Spec
Full spec at /openapi.json.