{"id":49979018,"url":"https://github.com/valkyoth/hashavatar-api","last_synced_at":"2026-05-30T00:01:21.513Z","repository":{"id":356720381,"uuid":"1233693614","full_name":"valkyoth/hashavatar-api","owner":"valkyoth","description":"Public avatar API and demo site for deterministic procedural avatars, designed for aggressive CDN caching.","archived":false,"fork":false,"pushed_at":"2026-05-18T12:35:41.000Z","size":378,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-18T14:26:38.159Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://hashavatar.app","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"eupl-1.2","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/valkyoth.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["eldryoth"],"thanks_dev":"u/gh/eldryoth"}},"created_at":"2026-05-09T08:37:23.000Z","updated_at":"2026-05-18T12:30:26.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/valkyoth/hashavatar-api","commit_stats":null,"previous_names":["valkyoth/hashavatar-api"],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/valkyoth/hashavatar-api","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/valkyoth%2Fhashavatar-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/valkyoth%2Fhashavatar-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/valkyoth%2Fhashavatar-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/valkyoth%2Fhashavatar-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/valkyoth","download_url":"https://codeload.github.com/valkyoth/hashavatar-api/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/valkyoth%2Fhashavatar-api/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33675019,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-29T02:00:06.066Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-18T14:10:29.099Z","updated_at":"2026-05-30T00:01:21.499Z","avatar_url":"https://github.com/valkyoth.png","language":"Rust","funding_links":["https://github.com/sponsors/eldryoth","https://thanks.dev/u/gh/eldryoth"],"categories":[],"sub_categories":[],"readme":"# hashavatar.app\n\n`hashavatar.app` is the public HTTP API and demo website for deterministic,\nprocedural avatars generated by the [`hashavatar`](https://crates.io/crates/hashavatar)\nRust crate.\n\nThe service is designed for stable public avatar URLs: bounded request inputs,\nnamespace-aware identities, cache-friendly responses, optional object-storage\nlinks, conservative browser security headers, and release gates for dependency,\naudit, SBOM, reproducibility, smoke, and GitHub CodeQL default setup checks.\n\n## Current Status\n\nThe current service version is `1.0.1`.\n\nImplemented now:\n\n- Landing page and browser demo at `/`.\n- Health endpoint at `/healthz`.\n- Query API at `/v1/avatar`.\n- Path API at `/avatar/{kind}/{identity}/{format}`.\n- Optional signed object-storage link endpoint at `/v1/avatar/link`.\n- OpenAPI metadata at `/docs/openapi.json`.\n- Deterministic output for stable CDN-backed avatar URLs.\n- Namespace-aware tenant and style-version parameters.\n- SHA-512 identity hashing.\n- WebP avatar responses.\n- Avatar families from `hashavatar 1.0.1`: `cat`, `dog`, `robot`, `fox`,\n  `alien`, `monster`, `ghost`, `slime`, `bird`, `wizard`, `skull`, `paws`,\n  `planet`, `rocket`, `mushroom`, `cactus`, `frog`, `panda`, `cupcake`,\n  `pizza`, `icecream`, `octopus`, `knight`, `bear`, `penguin`, `dragon`,\n  `ninja`, `astronaut`, `diamond`, `coffee-cup`, and `shield`.\n- Background modes: `themed`, `white`, `black`, `dark`, `light`,\n  `transparent`, `polka-dot`, `striped`, `checkerboard`, `grid`, `sunrise`,\n  `ocean`, and `starry`.\n- Bounded in-memory rate limiter storage.\n- Trusted-proxy validation before forwarded IP headers are honored.\n- Generic internal error responses with detailed server-side tracing.\n- Security headers on all responses.\n- Wolfi-based container runtime.\n- Fluxheim deployment example.\n- Local gates for formatting, clippy, tests, security invariants, dependency\n  policy, RustSec advisories, local smoke testing, SBOM generation, and\n  reproducible release builds.\n\nIntentionally external:\n\n- TLS termination, WAF rules, global rate limits, and bot controls should live\n  at the reverse proxy, CDN, or infrastructure layer.\n- Long-term object storage is optional and configured through S3-compatible\n  environment variables.\n- The reusable renderer lives in the separate\n  [`hashavatar`](https://github.com/valkyoth/hashavatar) crate.\n\n## Trust Dashboard\n\n| Area | Status |\n| --- | --- |\n| Service license | `EUPL-1.2` |\n| Renderer crate | `hashavatar 1.0.1` |\n| MSRV | Rust `1.96.0` |\n| Runtime container | Wolfi |\n| HTTP framework | `axum` |\n| Object storage | Optional S3-compatible backend |\n| Rate limiter | Bounded LRU map |\n| Forwarded IP policy | Trusted proxies only |\n| Internal errors | Detailed logs, generic client body |\n| Security headers | CSP, permissions policy, referrer policy, `nosniff`, frame denial, CORP, COOP, HSTS |\n| Release evidence | fmt, metadata, docs, clippy, tests, deny, audit, smoke, SBOM, reproducibility |\n| Code scanning | GitHub CodeQL default setup |\n\nSecurity-control details live in\n[docs/SECURITY_CONTROLS.md](docs/SECURITY_CONTROLS.md). Rendering provenance\nlives in [PROVENANCE.md](PROVENANCE.md). URL stability expectations live in\n[VERSIONING.md](VERSIONING.md).\n\n## Public API\n\n### Query Avatar\n\n```text\nGET /v1/avatar?id=cat@hashavatar.app\u0026algorithm=sha512\u0026kind=cat\u0026background=themed\u0026accessory=none\u0026color=default\u0026expression=default\u0026shape=square\u0026format=webp\u0026size=256\n```\n\nImportant query parameters:\n\n| Parameter | Default | Notes |\n| --- | --- | --- |\n| `id` | `cat@hashavatar.app` | Public identity input for deterministic rendering. |\n| `tenant` | `public` | Namespace tenant for isolation. |\n| `style_version` | `v2` | Namespace style rollout version. |\n| `algorithm` | `sha512` | Identity hash algorithm. Only `sha512` is supported. |\n| `kind` | `cat` | Avatar family. |\n| `background` | `themed` | Background mode: `themed`, `white`, `black`, `dark`, `light`, `transparent`, `polka-dot`, `striped`, `checkerboard`, `grid`, `sunrise`, `ocean`, or `starry`. |\n| `accessory` | `none` | Optional style layer: `none`, `glasses`, `hat`, `headphones`, `crown`, `bowtie`, `eyepatch`, `scarf`, `halo`, or `horns`. |\n| `color` | `default` | Accent color: `default`, `neon-mint`, `pastel-pink`, `crimson`, `gold`, or `deep-sea-blue`. |\n| `expression` | `default` | Facial expression: `default`, `happy`, `grumpy`, `surprised`, `sleepy`, `winking`, `cool`, or `crying`. |\n| `shape` | `square` | Avatar crop shape: `square`, `circle`, `squircle`, `hexagon`, or `octagon`. |\n| `format` | `webp` | Output format. Only `webp` is supported for avatar responses. |\n| `size` | `256` | Square image size in pixels. |\n| `persist` | `false` | Store through configured S3-compatible backend when enabled; uses the stricter storage rate limit. |\n\n### Path Avatar\n\n```text\nGET /avatar/cat/cat@hashavatar.app/webp\nGET /avatar/fox/fox@hashavatar.app/webp\n```\n\nPath requests use the default tenant, style version, themed background, default\nstyle layers, and `256` pixel size.\n\n### Signed Storage Link\n\n```text\nGET /v1/avatar/link?id=robot@hashavatar.app\u0026kind=robot\u0026background=white\u0026accessory=glasses\u0026color=gold\u0026expression=happy\u0026shape=circle\u0026format=webp\u0026size=256\n```\n\nThis endpoint requires object storage configuration. It renders and stores the\navatar when needed, then returns object metadata, a signed URL, and a hashed\ncache key. Standard avatar responses do not expose signed-link metadata in\nresponse headers.\n\n## Limits\n\n| Limit | Value |\n| --- | --- |\n| Minimum avatar size | `64` pixels |\n| Maximum avatar size | `1024` pixels |\n| Maximum service identity input | `512` bytes |\n| Maximum namespace tenant | `64` ASCII path-safe bytes |\n| Maximum namespace style version | `64` ASCII path-safe bytes |\n| Maximum renderer identity input | `1024` bytes |\n| Rate-limit buckets | `65,536` |\n| Avatar render timeout | `3s` |\n| Storage operation timeout | `5s` |\n\nThe service accepts email-shaped identifiers for compatibility, but stable\ninternal ids or one-way hashes are preferred when you want less personal data\nin URL logs. It validates its own public size, identity, and namespace ranges\nbefore calling the renderer. Namespace components may contain only ASCII\nletters, digits, hyphens, and underscores so they are safe to use in\nobject-storage keys.\n\nAccessory and expression layers apply to character-style avatar families.\nObject-style families such as `planet`, `rocket`, `paws`, `mushroom`,\n`cactus`, `cupcake`, `pizza`, `icecream`, `diamond`, `coffee-cup`, and\n`shield` are normalized to `accessory=none` and `expression=default`.\n\n## Determinism And Caching\n\nAvatar responses are deterministic for the tuple:\n\n```text\ntenant + style_version + sha512 + id + kind + background + accessory + color + expression + shape + webp + size\n```\n\nThis makes aggressive edge caching appropriate. Avatar responses include:\n\n- `Cache-Control: public, max-age=86400, s-maxage=31536000, immutable`\n- `CDN-Cache-Control: public, max-age=31536000, immutable`\n- `Cloudflare-CDN-Cache-Control: public, max-age=31536000, immutable`\n- `ETag`\n\nThe recommended production strategy is:\n\n- use a stable internal user id or one-way hash as `id`\n- use `tenant` for product or environment isolation\n- use `style_version` for visual rollouts\n- change `style_version` intentionally when cached visuals should change\n\n## Running Locally\n\nRequires Rust `1.96.0` or newer.\n\n```bash\ncargo run\n```\n\nDefault bind:\n\n```text\n127.0.0.1:8080\n```\n\nUse a different port:\n\n```bash\nPORT=3011 PUBLIC_WEBSITE_HOST=127.0.0.1 cargo run\n```\n\nContainer and deployment examples set `PUBLIC_WEBSITE_HOST=0.0.0.0`\nexplicitly so the service is reachable through the configured reverse proxy.\n\nSmoke test a local server:\n\n```bash\nscripts/smoke_local.sh\n```\n\n## Configuration\n\nGeneral:\n\n- `PORT`\n- `PUBLIC_WEBSITE_HOST`\n- `HASHAVATAR_TRUSTED_PROXIES`\n\nObject storage:\n\n- `HASHAVATAR_S3_BUCKET`\n- `HASHAVATAR_S3_REGION`\n- `HASHAVATAR_S3_ENDPOINT`\n- `HASHAVATAR_S3_PATH_STYLE`\n- `HASHAVATAR_S3_PREFIX`\n- `HASHAVATAR_S3_PRESIGN_TTL_SECONDS` clamped to `60..=604800`\n\n`HASHAVATAR_TRUSTED_PROXIES` accepts a comma or whitespace separated list of IP\naddresses and CIDR ranges. Forwarded client IP headers are ignored unless the\ndirect peer address matches this allowlist. Keep this allowlist limited to\nproxies that overwrite or correctly append client IP headers; rate-limit state\nis intentionally sized for the real client IP space of the deployment.\n\nDo not proxy `/metrics` from a public listener. The endpoint is intended for\nlocal scraping only; a same-host reverse proxy can make the loopback peer check\npass if it forwards the route.\n\n## Testing And Release Evidence\n\nRun the fast local gate:\n\n```bash\nscripts/checks.sh\n```\n\nRun the runtime smoke test:\n\n```bash\nscripts/smoke_local.sh\n```\n\nRun the fuller release gate:\n\n```bash\nscripts/stable_release_gate.sh check\n```\n\nThe repository includes:\n\n- release metadata validation\n- Markdown link checks\n- security invariant checks\n- clippy with warnings denied\n- unit tests for rate limiting, trusted proxy handling, renderer validation,\n  and internal error disclosure behavior\n- local HTTP smoke tests for health, WebP rendering, security headers,\n  unsupported algorithm/format rejection, and invalid namespace rejection\n- local Podman smoke tests for the Wolfi image when\n  `HASHAVATAR_API_GATE_PODMAN=1` is set\n- `cargo deny` dependency and license policy\n- RustSec advisory scanning\n- SPDX and CycloneDX SBOM generation\n- reproducible release build checks\n- GitHub CodeQL default setup\n\n## Deployment\n\nFor self-hosting with Podman and Fluxheim, see:\n\n- [DEPLOYMENT-HETZNER.md](DEPLOYMENT-HETZNER.md)\n- [deploy/README.md](deploy/README.md)\n- [deploy/podman-compose.yml](deploy/podman-compose.yml)\n- [deploy/fluxheim.toml](deploy/fluxheim.toml)\n\nThe compose example builds this service with the Wolfi runtime image and places\nFluxheim in front of it as the public TLS reverse proxy.\n\nRelease tags publish a Wolfi runtime image to GitHub Container Registry:\n\n```text\nghcr.io/valkyoth/hashavatar-api:\u003cversion\u003e\nghcr.io/valkyoth/hashavatar-api:\u003cversion\u003e-wolfi\n```\n\nManual workflow runs from the default branch also publish `dev`, `dev-wolfi`,\n`latest`, and `latest-wolfi` tags.\n\n## Related Projects\n\n- [`hashavatar`](https://crates.io/crates/hashavatar)\n- [`hashavatar` docs](https://docs.rs/hashavatar/latest/hashavatar/)\n- [`hashavatar` source](https://github.com/valkyoth/hashavatar)\n\n## License\n\nLicensed under the European Union Public Licence v. 1.2\n([LICENSE](LICENSE), [LICENSE-EUPL-1.2.txt](LICENSE-EUPL-1.2.txt)).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvalkyoth%2Fhashavatar-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvalkyoth%2Fhashavatar-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvalkyoth%2Fhashavatar-api/lists"}