Sign in Open console

Documentation#

One endpoint, one line of HTML: give icons a domain, get its favicon back as a crisp PNG. This page covers usage, the fallback behavior, caching, rate limits, and how verified tenants get higher limits.

Getting started#

The fastest integration is an image tag:

<img src="https://icon0.eu/domain/example.eu" width="32" height="32" alt="">

That is the whole SDK. No account, no API key, no client library. The same URL works from CSS and from code:

.site-icon {
  background-image: url("https://icon0.eu/domain/example.eu");
  background-size: 16px 16px;
}
const res = await fetch("https://icon0.eu/domain/example.eu")
const blob = await res.blob() // image/png - CORS is open to every origin

Icon endpoints#

Endpoint What
GET /domain/{domain} the domain's favicon as a PNG (anonymous rate limits)
GET /t/{public_id}/domain/{domain} the same, on your tenant path (elevated rate limits once the referring domain is verified)

Both endpoints always answer with Content-Type: image/png and never with a broken image: if a site has no usable favicon, a deterministic colored letter tile is served instead (see Fallback behavior).

How icons are resolved#

For each domain we look for icons in this order and pick the source that renders best at 32 pixels:

  1. HTML <link rel="icon"> and <link rel="apple-touch-icon"> tags
  2. Web app manifest icons
  3. A probe of /favicon.ico

Supported source formats: ICO, PNG, GIF, JPEG, BMP, WebP, and SVG (rasterized server-side). The output is always a PNG.

Sizes. Add ?sz= for other sizes: any value from 16 to 256, default 32 (the square form ?sz=64x64 is tolerated). Requests of 80px and larger are rendered from a high-resolution master (up to 256px, sourced from apple-touch icons, web manifests, or SVG) when the site offers one; smaller requests use the site's small-optimized favicon art, which is usually pixel-hinted for exactly those sizes. 256 is the maximum quality supported.

<img src="https://icon0.eu/domain/github.com?sz=64" width="64" height="64">

Fallback behavior#

When no usable favicon exists, the response is still a valid PNG: a colored letter tile derived deterministically from the domain - the same domain always produces the same tile, so your UI stays stable. Fallback responses are marked with the header:

X-Icons-Fallback: 1

Check for it if you want to treat generated tiles differently from real favicons.

Caching#

Responses are built to be cached hard:

Cache-Control: public, max-age=86400, stale-while-revalidate=604800
ETag: "…"            (strong)
Access-Control-Allow-Origin: *

Browsers keep an icon for 24 hours and may serve it stale for up to 7 days while revalidating. Conditional requests with If-None-Match get a 304 Not Modified. In practice, most of your users' requests never reach our servers at all.

Rate limits#

Rate limits exist purely to protect the systems - they are not a business model, and the service is free at every tier.

Per minute Per day
Anonymous (per IP) 30 1,000
Verified tenant (whole tenant) 3,000 100,000
Verified tenant (per-IP slice) 300 10,000

Limiting is done with a salted in-memory hash of the caller's IP; the salt rotates daily at 00:00 UTC and nothing is persisted - see the Privacy policy. Requests over the limit answer 429 Too Many Requests.

Verified tenants#

Higher limits require no payment - only proof that you control the domain your pages load icons from:

  1. Sign in at icon0.eu/console - your organization becomes your tenant, and the console shows your icon base URL (https://icon0.eu/t/{public_id}/domain/).
  2. Add your domain in the console (or via the API below).
  3. Publish the TXT record: create a DNS TXT record at _icons-verify.<host> whose value is the verify_token the console shows.
  4. Verify: click Verify (or POST /api/v1/domains/{id}/verify). Fresh DNS changes can take a few minutes to propagate.

From then on, requests to your /t/{public_id}/domain/… base whose Referer or Origin matches a verified domain are served under the tenant limits.

<img src="https://icon0.eu/t/{public_id}/domain/example.eu" width="32" height="32" alt="">

Tenant API reference#

The management API lives under https://icon0.eu/api/v1 and authenticates with the OIDC access token the console itself uses (Authorization: Bearer …). All endpoints are JSON; errors use {"error":{"code":"…","message":"…"}}.

Endpoint What
GET /api/v1/config public: auth issuer/client, host, and the current limit values
GET /api/v1/me {tenant_id, public_id, plan, roles, icon_base}
GET /api/v1/domains list your domains, each with verify_token, txt_record, verified_at
POST /api/v1/domains add a domain - {"host":"app.example.eu"} (409 host_taken, 400 invalid/reserved)
POST /api/v1/domains/{id}/verify run the DNS TXT check; returns the domain plus txt_ok
DELETE /api/v1/domains/{id} remove the domain (204)

Privacy in one paragraph#

We do not log IP addresses, user agents, or referrers. The only per-visitor state anywhere in the system is the salted, in-memory rate-limit hash described above, and its salt rotates daily - even we cannot link yesterday's traffic to today's. Icons are fetched from the target sites and cached as public image data; nothing about the people loading them is stored. Everything runs on servers in the European Union. The full picture: Privacy policy and Compliance.

More#