Pixabots

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

ParameterTypeDefaultDescription
idpathrequired4-char base36 pixabot ID (e.g. 2156, f76a)
sizequery128Image size in pixels, any integer from 32 to 1920. Nearest-neighbor scaled from the 32×32 source.
formatquerySet to json for metadata, svg for vector output
animatedquerySet to true for an animated GIF. Any size 32–1920, same as PNG.
webpqueryWith animated=true, return lossless animated WebP instead of GIF. Smaller files, preserves alpha.
speedquery1Animation speed multiplier (0.254). Only works with animated=true.
huequeryHue rotation in degrees (0–359). Shifts all colors while preserving relative palette.
saturatequerySaturation multiplier (0 = greyscale, 1 = unchanged, up to 4 = very punchy).
bgqueryBackground color as #rrggbb hex. Flattens transparency onto a solid color.
vqueryCache-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.png

Sizes

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:

SizeUse case
32Native source, crispest for small avatars
64, 96Favicons, dense lists
128, 192, 240Default avatars, chat UIs
256, 384, 480Profile cards, hero avatars
512, 768, 960High-DPI, retina displays
1920Posters, 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.gif
Animated pixabot

The 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.png

hue 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.gif

Works 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.svg

Good 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.webp

JSON 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:

  1. For each layer in layerOrder, draw sprites[layer].url into a 32×32 canvas, cropping horizontally to the 32 × spriteIndex[layer] column.
  2. Offset each layer vertically by frames[t].offsets[layer] native pixels.
  3. Advance t every frameMs ms, wrapping at loopLength.

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.

ParameterTypeDefaultDescription
idsqueryComma-separated list of up to 100 IDs. Mutually exclusive with count.
countqueryReturn N random pixabots (1–100). Mutually exclusive with ids.
sizequery128Size 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.

ParameterTypeDefaultDescription
sizequery128Forwarded to the redirect target
animatedquerySet to true for an animated GIF. Forwarded to the redirect target.
speedquery1Forwarded to the redirect target. Animation speed multiplier (0.254).
formatquerySet 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=json

Embedding

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

![pixabot](https://pixabots.com/api/pixabot/2156?size=240)

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 === 1
curl "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):

  1. Bump ANIM_VERSION in packages/core/src/animation.ts.
  2. Bump @pixabots/core and @pixabots/react minor versions, publish.
  3. (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-Type

Works 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:

PathLimit
/api/pixabot/{id}?animated=true30 requests / minute
/api/og20 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: 1713478200

The 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.