hashavatar.app is the public HTTP API and demo website for deterministic,
procedural avatars generated by the hashavatar
Rust crate.
The service is designed for stable public avatar URLs: bounded request inputs, namespace-aware identities, cache-friendly responses, optional object-storage links, conservative browser security headers, and release gates for dependency, audit, SBOM, reproducibility, smoke, and GitHub CodeQL default setup checks.
The current service version is 1.0.0.
Implemented now:
- Landing page and browser demo at
/. - Health endpoint at
/healthz. - Query API at
/v1/avatar. - Path API at
/avatar/{kind}/{identity}/{format}. - Optional signed object-storage link endpoint at
/v1/avatar/link. - OpenAPI metadata at
/docs/openapi.json. - Deterministic output for stable CDN-backed avatar URLs.
- Namespace-aware tenant and style-version parameters.
- SHA-512 identity hashing.
- WebP avatar responses.
- Avatar families from
hashavatar 1.0.0:cat,dog,robot,fox,alien,monster,ghost,slime,bird,wizard,skull,paws,planet,rocket,mushroom,cactus,frog,panda,cupcake,pizza,icecream,octopus,knight,bear,penguin,dragon,ninja,astronaut,diamond,coffee-cup, andshield. - Background modes:
themed,white,black,dark,light,transparent,polka-dot,striped,checkerboard,grid,sunrise,ocean, andstarry. - Bounded in-memory rate limiter storage.
- Trusted-proxy validation before forwarded IP headers are honored.
- Generic internal error responses with detailed server-side tracing.
- Security headers on all responses.
- Wolfi-based container runtime.
- Fluxheim deployment example.
- Local gates for formatting, clippy, tests, security invariants, dependency policy, RustSec advisories, local smoke testing, SBOM generation, and reproducible release builds.
Intentionally external:
- TLS termination, WAF rules, global rate limits, and bot controls should live at the reverse proxy, CDN, or infrastructure layer.
- Long-term object storage is optional and configured through S3-compatible environment variables.
- The reusable renderer lives in the separate
hashavatarcrate.
| Area | Status |
|---|---|
| Service license | EUPL-1.2 |
| Renderer crate | hashavatar 1.0.0 |
| MSRV | Rust 1.95.0 |
| Runtime container | Wolfi |
| HTTP framework | axum |
| Object storage | Optional S3-compatible backend |
| Rate limiter | Bounded LRU map |
| Forwarded IP policy | Trusted proxies only |
| Internal errors | Detailed logs, generic client body |
| Security headers | CSP, permissions policy, referrer policy, nosniff, frame denial, CORP, COOP, HSTS |
| Release evidence | fmt, metadata, docs, clippy, tests, deny, audit, smoke, SBOM, reproducibility |
| Code scanning | GitHub CodeQL default setup |
Security-control details live in docs/SECURITY_CONTROLS.md. Rendering provenance lives in PROVENANCE.md. URL stability expectations live in VERSIONING.md.
GET /v1/avatar?id=cat@hashavatar.app&algorithm=sha512&kind=cat&background=themed&accessory=none&color=default&expression=default&shape=square&format=webp&size=256
Important query parameters:
| Parameter | Default | Notes |
|---|---|---|
id |
cat@hashavatar.app |
Public identity input for deterministic rendering. |
tenant |
public |
Namespace tenant for isolation. |
style_version |
v2 |
Namespace style rollout version. |
algorithm |
sha512 |
Identity hash algorithm. Only sha512 is supported. |
kind |
cat |
Avatar family. |
background |
themed |
Background mode: themed, white, black, dark, light, transparent, polka-dot, striped, checkerboard, grid, sunrise, ocean, or starry. |
accessory |
none |
Optional style layer: none, glasses, hat, headphones, crown, bowtie, eyepatch, scarf, halo, or horns. |
color |
default |
Accent color: default, neon-mint, pastel-pink, crimson, gold, or deep-sea-blue. |
expression |
default |
Facial expression: default, happy, grumpy, surprised, sleepy, winking, cool, or crying. |
shape |
square |
Avatar crop shape: square, circle, squircle, hexagon, or octagon. |
format |
webp |
Output format. Only webp is supported for avatar responses. |
size |
256 |
Square image size in pixels. |
persist |
false |
Store through configured S3-compatible backend when enabled; uses the stricter storage rate limit. |
GET /avatar/cat/cat@hashavatar.app/webp
GET /avatar/fox/fox@hashavatar.app/webp
Path requests use the default tenant, style version, themed background, default
style layers, and 256 pixel size.
GET /v1/avatar/link?id=robot@hashavatar.app&kind=robot&background=white&accessory=glasses&color=gold&expression=happy&shape=circle&format=webp&size=256
This endpoint requires object storage configuration. It renders and stores the avatar when needed, then returns object metadata, a signed URL, and a hashed cache key. Standard avatar responses do not expose signed-link metadata in response headers.
| Limit | Value |
|---|---|
| Minimum avatar size | 64 pixels |
| Maximum avatar size | 1024 pixels |
| Maximum service identity input | 512 bytes |
| Maximum namespace tenant | 64 ASCII path-safe bytes |
| Maximum namespace style version | 64 ASCII path-safe bytes |
| Maximum renderer identity input | 1024 bytes |
| Rate-limit buckets | 65,536 |
| Avatar render timeout | 3s |
| Storage operation timeout | 5s |
The service accepts email-shaped identifiers for compatibility, but stable internal ids or one-way hashes are preferred when you want less personal data in URL logs. It validates its own public size, identity, and namespace ranges before calling the renderer. Namespace components may contain only ASCII letters, digits, hyphens, and underscores so they are safe to use in object-storage keys.
Accessory and expression layers apply to character-style avatar families.
Object-style families such as planet, rocket, paws, mushroom,
cactus, cupcake, pizza, icecream, diamond, coffee-cup, and
shield are normalized to accessory=none and expression=default.
Avatar responses are deterministic for the tuple:
tenant + style_version + sha512 + id + kind + background + accessory + color + expression + shape + webp + size
This makes aggressive edge caching appropriate. Avatar responses include:
Cache-Control: public, max-age=86400, s-maxage=31536000, immutableCDN-Cache-Control: public, max-age=31536000, immutableCloudflare-CDN-Cache-Control: public, max-age=31536000, immutableETag
The recommended production strategy is:
- use a stable internal user id or one-way hash as
id - use
tenantfor product or environment isolation - use
style_versionfor visual rollouts - change
style_versionintentionally when cached visuals should change
Requires Rust 1.95.0 or newer.
cargo runDefault bind:
127.0.0.1:8080
Use a different port:
PORT=3011 PUBLIC_WEBSITE_HOST=127.0.0.1 cargo runContainer and deployment examples set PUBLIC_WEBSITE_HOST=0.0.0.0
explicitly so the service is reachable through the configured reverse proxy.
Smoke test a local server:
scripts/smoke_local.shGeneral:
PORTPUBLIC_WEBSITE_HOSTHASHAVATAR_TRUSTED_PROXIES
Object storage:
HASHAVATAR_S3_BUCKETHASHAVATAR_S3_REGIONHASHAVATAR_S3_ENDPOINTHASHAVATAR_S3_PATH_STYLEHASHAVATAR_S3_PREFIXHASHAVATAR_S3_PRESIGN_TTL_SECONDS
HASHAVATAR_TRUSTED_PROXIES accepts a comma or whitespace separated list of IP
addresses and CIDR ranges. Forwarded client IP headers are ignored unless the
direct peer address matches this allowlist.
Run the fast local gate:
scripts/checks.shRun the runtime smoke test:
scripts/smoke_local.shRun the fuller release gate:
scripts/stable_release_gate.sh checkThe repository includes:
- release metadata validation
- Markdown link checks
- security invariant checks
- clippy with warnings denied
- unit tests for rate limiting, trusted proxy handling, renderer validation, and internal error disclosure behavior
- local HTTP smoke tests for health, WebP rendering, security headers, unsupported algorithm/format rejection, and invalid namespace rejection
- local Podman smoke tests for the Wolfi image when
HASHAVATAR_API_GATE_PODMAN=1is set cargo denydependency and license policy- RustSec advisory scanning
- SPDX and CycloneDX SBOM generation
- reproducible release build checks
- GitHub CodeQL default setup
For self-hosting with Podman and Fluxheim, see:
The compose example builds this service with the Wolfi runtime image and places Fluxheim in front of it as the public TLS reverse proxy.
Release tags publish a Wolfi runtime image to GitHub Container Registry:
ghcr.io/valkyoth/hashavatar-api:<version>
ghcr.io/valkyoth/hashavatar-api:<version>-wolfi
Manual workflow runs from the default branch also publish dev, dev-wolfi,
latest, and latest-wolfi tags.
Licensed under the European Union Public Licence v. 1.2 (LICENSE, LICENSE-EUPL-1.2.txt).