Avatar API — v1 — stable
Avatars & identicons
Pass any string — a user id, an email hash, a commit sha — and get a stable, unique mark back. Nothing is stored; the same seed always renders the same image.
curl "https://avatar.waldrand.dev/ada.svg?size=256&style=ridge"
Seeds & determinism
The seed is everything between the leading slash and the file extension. It is hashed with SHA-256, and the digest is the only source of randomness a style may draw on — so the mark for a seed is fixed, on every machine, in every process, for as long as the style stands.
There is no database and no account. Two callers who send the same seed get the same bytes without ever having met.
- Any UTF-8 string up to 256 characters. Percent-encode anything a URL would otherwise read as structure —
/,?,#. - Seeds are case-sensitive:
Adaandadaare two different people. - Dots are fine. Only a trailing
.svg,.pngor.webpis read as a format, soada.lovelace.pngis the seedada.lovelace. - The style is part of the hash, so
?style=gridis a different drawing, not the same one recoloured.
Hash the identifier before sending it if it is personal. An email address in a URL is an email address in every log and cache between you and here; its SHA-256 renders just as well.
Formats
The extension picks the encoder. SVG is the default and the cheapest — it is a few hundred bytes of geometry that scales to any size, which is why size does nothing for it.
| Path | Content-Type | Notes |
|---|---|---|
/{seed}.svg | image/svg+xml | The default. Resolution-independent, around 400 bytes. |
/{seed}.png | image/png | Rasterised at size. Transparent where bg=none. |
/{seed}.webp | image/webp | Same pixels, roughly a third of the bytes. |
Styles
Four, and no plans for a fifth. Each one is a different way of turning the same digest into a shape; the accent appears exactly once in every mark, which is what keeps a wall of them looking like one set.
The house mark, redrawn per seed: two ridgelines and the threshold. The default.
The classic identicon — a 5×5 field mirrored down the middle.
One or two letters off the seed on a tinted tile. The only readable style.
Concentric arcs, turned and broken. Holds together at 16px, where grid turns to static.
GET /{seed}.svg
Returns image/svg+xml. The markup carries a <title> and an aria-label holding the seed, so a screen reader announces something better than “image”. There is no script and no external reference in it.
# a 64px mark for a user id, rounded to a circle curl "https://avatar.waldrand.dev/u_18f2c9.svg?radius=50" # drop it straight into HTML — no build step, no proxy <img src="https://avatar.waldrand.dev/u_18f2c9.svg?radius=50" width="64" height="64" alt="">
GET /{seed}.png
Returns image/png, rasterised at size. Use it where SVG is not welcome — email, OpenGraph cards, anything that ends up in a native image view. Swap the extension for .webp to send a third of the bytes.
# 512px, transparent ground, saved to disk curl "https://avatar.waldrand.dev/ada.png?size=512&bg=none" -o ada.png # same seed, same pixels, smaller file curl "https://avatar.waldrand.dev/ada.webp?size=512" -o ada.webp
Parameters
| Name | Type | Default | Notes |
|---|---|---|---|
| seed | string | required | In the path. Any UTF-8 string up to 256 characters. |
| size | 16–1024 | 256 | Pixel edge length. Ignored for SVG. |
| style | enum | ridge | ridge · grid · initials · rings |
| radius | 0–50 | 0 | Corner rounding in percent. 50 = circle. |
| bg | hex | auto | Background fill, or none for transparent. |
Rate limits
Rate limit — per IP
60req / min
The bucket holds four minutes’ worth, so a page of 200 avatars loads in one go; it refills at one a second. Only fresh renders count: a 304 or an avatar already in the server’s cache is free, and browser-cached images never reach us at all.
Response headers
- X-RateLimit-Limit:
- 60
- X-RateLimit-Remaining:
- 237
- X-RateLimit-Reset:
- 1738 (epoch s)
- Retry-After:
- on 429 only
Every rendered avatar comes back with a strong ETag and Cache-Control: public, max-age=2592000, immutable. A conditional request that matches is answered with a 304 and costs you nothing to render.
Errors
The happy path is an image; everything else is JSON, because a caller holding an error would rather parse it than read it.
| Status | Code | When |
|---|---|---|
| 400 | bad_seed | Empty, over 256 characters, or not valid percent-encoding. |
| 400 | bad_param | A parameter is out of range or not a known value. The body names which. |
| 400 | unknown_format | An extension other than .svg, .png or .webp. |
| 405 | method_not_allowed | Anything but GET, HEAD or OPTIONS. |
| 429 | rate_limited | Over the ceiling. Retry-After says how long. |
{
"error": {
"status": 400,
"code": "bad_param",
"param": "size",
"message": "size must be between 16 and 1024, got 4096."
},
"docs": "https://avatar.waldrand.dev/"
}