HTTP API
Hosted at https://api.navii.dev. No auth, public CORS, fully cacheable. Plain text errors, image responses for everything else.
Compatibility promise
Every endpoint, query param, response format, and response header documented as of v0.24.x stays available on the hosted API. Existing URLs such as https://api.navii.dev/avatar/alice keep working unauthenticated.
Future Pro features are additive: they use Authorization: Bearer <polar_license_key> only when you request a Pro-only capability. Get a license at https://navii.dev/pro. The free API remains anonymous, and hosted free-tier rate limits will not tighten beyond the currently published limits.
Why so strict? Avatar responses use cache-control: public, max-age=31536000, immutable. Once a URL is in customer HTML, browsers and CDNs may hold it for a year. Navii treats those URLs as a public contract.
GET /avatar/:seed[.svg|.png]
Returns a deterministic mascot avatar for the given seed. Same seed → same avatar, byte-for-byte. Append .png to the seed to receive a rasterized PNG instead of SVG.
Path
| Param | Type | Description |
|---|---|---|
:seed | string | Any unique identifier. Use a stable user id, UUID, or email. Avoid display names — see the seed rule. |
Query
| Param | Type | Default | Description |
|---|---|---|---|
size | int | 96 | Output size in px. Clamped to 16–1024. |
palette | enum | seeded | Force a color family. See palette catalog. |
background | enum | seeded | none · solid · ring. |
tileBg | color | none | Opaque circular fill behind avatar. Any CSS color (URL-encoded, e.g. %23ffffff) or auto to use the palette accent. |
mood | enum | seeded | neutral · happy · serious · sleepy · wink. Overrides seed-derived eyes + mouth with a curated pair. Same seed + mood = byte-identical render. |
packs | csv | none | Comma-separated pack ids — e.g. accra-gallery, command-center. Premium themed bodies, palettes, accessories, and outfits. Unknown ids are silently skipped. Order doesn't affect cached output. |
style | enum | seeded | masc · femme · neutral. Style-hint bias on seeded picks. Only meaningful alongside packs; harmless otherwise. |
title | string | none | Accessible label. Adds role="img" + aria-label to the SVG root. |
animated | 0 / 1 | 0 | Idle motion (float, blink, antenna sway, spark pulse, twinkle). SVG only — ignored for PNG. Honors prefers-reduced-motion. |
Examples
https://api.navii.dev/avatar/alice
https://api.navii.dev/avatar/alice?palette=violet&animated=1
https://api.navii.dev/avatar/alice?tileBg=%23ffffff
https://api.navii.dev/avatar/alice?mood=happy
https://api.navii.dev/avatar/alice?packs=accra-gallery
https://api.navii.dev/avatar/alice?packs=command-center&style=neutral
https://api.navii.dev/avatar/alice.png?size=512&tileBg=auto
GET /random[.png]
Returns a fresh avatar inline — same URL, different avatar every refresh. Internally picks a new UUID seed per request and renders directly. No redirect. Point an <img src="/random"> at it and every page refresh swaps the avatar.
Query
All /avatar/:seed params apply (size, palette, background, tileBg, mood, packs, style, title, animated) — same semantics, same clamps, same enums.
Response headers
x-navii-seed— the seed that was chosen. Read it from afetch()response if you want to persist the avatar (e.g. save to user profile so it's stable on next visit).cache-control: no-store— never cached by the browser or CDN. Refresh = new avatar.access-control-allow-origin: *+access-control-expose-headers: x-navii-seed— embed anywhere; cross-origin JS can read the seed header.
Examples
https://api.navii.dev/random
https://api.navii.dev/random?palette=mint&size=128
https://api.navii.dev/random.png?size=256
Persisting the chosen seed — useful for onboarding flows where the user gets an avatar without picking one, then keeps it forever:
const res = await fetch('https://api.navii.dev/random');
const seed = res.headers.get('x-navii-seed');
const svg = await res.text();
await db.users.update(user.id, { naviiSeed: seed });
GET /group
Renders multiple seeded avatars as a single horizontally-stacked SVG with optional overlap and a +N counter tile for overflow.
Query
| Param | Type | Default | Description |
|---|---|---|---|
seeds | csv | — | Comma-separated seeds (up to 50). Required. |
size | int | 64 | Per-tile size in px. Clamped to 16–256. |
overlap | float | 0.3 | Fraction each tile overlaps the previous. 0 = no overlap, 0.7 = heavy stack. |
max | int | all | Max tiles to render. Extra seeds collapse into a +N tile. |
ring | color | #ffffff | Border color around each tile. |
tileBg | color | #ffffff | Opaque fill behind each avatar (prevents overlap show-through). |
animated | 0 / 1 | 0 | Per-avatar animation. |
SDK-only: counterFill and counterInk (the +N tile's colors) are settable via GroupOptions in @usenavii/core but not yet wired to query params.
Utility endpoints
| Path | Description |
|---|---|
GET / | Landing page with live playground. |
GET /api | Service metadata as JSON. Returns { "name": "navii", "version": "...", "endpoints": {...} }. |
GET /healthz | Liveness probe. Returns { "ok": true, "pngCacheSize": N }. |
GET /gallery | HTML grid of N seeded avatars (visual debug). |
GET /favicon.svg | Brand favicon. SVG. |
GET /apple-touch-icon.png | 180×180 dark-tile icon for iOS home-screen. |
GET /og.png | 1200×630 Open Graph image. No params. |
GET /robots.txt, /sitemap.xml | SEO essentials. |
Response headers
All image responses set:
cache-control: public, max-age=31536000, immutable— safe to cache forever (seed + params fully determine bytes).access-control-allow-origin: *— embed anywhere, no preflight forGET.content-type: image/svg+xml; charset=utf-8for SVG,image/pngfor PNG,application/jsonfor/api+/healthz.
Rate-limited routes additionally emit:
x-ratelimit-limit— max requests in the current window.x-ratelimit-remaining— remaining requests.x-ratelimit-reset— Unix epoch seconds when the window resets.retry-after— only on429responses.
The avatar route emits a warning when the seed shape suggests PII:
x-navii-warning: plaintext-email-seed; hash with seedFromEmail()— set when the seed matches an email pattern. Avatar still renders; hash the email client-side withseedFromEmail()to drop the warning.
Errors
Plain-text bodies. Status codes:
| Status | Meaning | Body |
|---|---|---|
| 400 | Bad request | seed required · seeds required (comma-separated) |
| 429 | Rate limited | Rate limit exceeded |
| 501 | Not implemented | PNG rasterization unavailable: ... — server missing @resvg/resvg-js. SVG endpoint still works. |
Rate limits
/avatar/* is limited to 600 req/min/IP. Other routes are unlimited. Exceeded → HTTP 429 + Retry-After.
Full table, cache rationale, and self-hosting tuning live on the dedicated Rate limits page.
Seeds and URL encoding
Anything you can put in a URL path can be a seed. Seeds with @, ., or other URL-special chars work — just URL-encode them on the client (most browsers do this automatically inside <img src>).
raw: alice@example.com
encoded: alice%40example.com
The server decodes back to the raw seed before hashing, so both URLs produce the same SVG. Empty seeds → 400.
Versioning and stability
The deterministic contract is scoped to a single release of the engine. A given seed + a given engine version → byte-identical SVG, forever. We won't silently change that.
What we promise to keep stable in patch + minor releases:
- Existing seeds' part selections don't shift when new variants are added (new parts append to the PRNG stream, never insert).
- Endpoint URLs, query params, response formats, and response headers stay backwards-compatible; Pro-only features are additive and gated only when requested.
- SVG markup may change in tiny non-visible ways (formatting, attribute order) — treat as text-content stable, not byte-stable across upgrades.
What can change in a major release:
- Cast rebases (existing seeds get new combinations). Documented in the changelog.
- Default option values.
If you need absolute byte-stability across engine upgrades, mirror the SVG bytes locally — see the caching recipe.
Authentication
None for the documented free API. CORS is wide open (access-control-allow-origin: *) — embed from anywhere, no token, no signup. Future Pro-only capabilities may accept Authorization: Bearer <polar_license_key>, but that auth requirement applies only to the Pro-only capability being requested. Get a license at https://navii.dev/pro. If you need to lock down a self-hosted deployment, put it behind your own auth layer (Cloudflare Access, BasicAuth via Caddy, etc.).