Icon API

Search the catalogue and download SVGs from your own code. Bearer-token auth, JSON responses, credit-based pricing. Every account starts with a free sandbox tier of 500 credits a month.

Get a key →·Plans and credits →

Authentication

Send your key as a bearer token. Every endpoint requires it; there is no unauthenticated access and no public/anonymous tier.

curl https://freeicon.com/v1/me \
  -H "Authorization: Bearer fi_sk_your_key_here"

Server-side only. A key in browser JavaScript is readable by anyone who opens the network tab. The API sends no CORS headers, so browsers cannot call it cross-origin — that is deliberate, not an oversight. Keep keys in environment variables and call the API from your backend.

Keys are shown once when created and stored only as a hash, so a lost key cannot be recovered — only replaced. You can hold 2 at a time, which is what lets you rotate without downtime: create the new one, deploy it, then revoke the old one.

The key never goes in a query string. A credential in a URL ends up in access logs,Refererheaders and browser history, so ?api_key= is not accepted.

Credits

Every call spends credits from one of two buckets. Your monthly allowance refills on your plan’s billing date; top-up credits are bought once and never expire. Calls drain the monthly allowance first and fall back to top-ups, so purchased credits are never consumed while you still have monthly ones.

OperationCost
GET /v1/me0 — always free
GET /v1/icons/search1
GET /v1/icons/{id}1
GET /v1/icons/{id}/svg10

Monthly allowances: Free Sandbox 500, Basic 25,000, Pro 100,000, Lifetime 25,000 every month.

You are never charged for a failure. Validation errors, 401s, 404s, 403s and rate limits all cost 0 credits, a request is charged in full or not at all, and if a download fails on our side the credits are returned. An empty search result does cost 1 credit — the search ran.

SVG downloads need a paid plan — or any purchased top-up credits, which unlock them on the free tier too. Search and metadata work on every tier.

Rate limits

600 requests per minute per key, with a short-burst ceiling of 100 per 10 seconds. Every response carries X-RateLimit-Remaining, and a 429 carries Retry-After in seconds — pace on those headers rather than on a fixed sleep.

Credit balances come back on every charged response as X-Credits-Monthly-Remaining and X-Credits-Topup-Remaining, so you never need a second call to know where you stand.

Endpoints

GET/v1/mefree

Your plan, both credit balances, what your key is allowed to do, and the current cost table. Free, so it is safe to call when you are out of credits.

{
  "plan": "api_basic",
  "credits": {
    "monthlyRemaining": 24990,
    "monthlyAllowance": 25000,
    "topupRemaining": 0,
    "resetsAt": "2026-10-18T00:00:00.000Z"
  },
  "capabilities": { "search": true, "svg": true },
  "costs": { "me": 0, "search": 1, "metadata": 1, "svg": 10 },
  "rateLimit": { "perMinute": 600, "burst": 100, "burstWindowSeconds": 10 }
}
GET/v1/icons/search1 credit

q is required. Optional: limit (1–100, default 20), page (1–100), style (a style slug), and locale to match translated tags. Deeper pagination than page 100 is rejected rather than served slowly — narrow the query instead.

{
  "query": "home",
  "page": 1,
  "limit": 20,
  "total": 412,
  "icons": [
    {
      "id": "V1StGXR8Z5jdHi6B",
      "name": "Home",
      "slug": "home",
      "style": { "slug": "flat", "name": "Flat" },
      "svgUrl": "https://freeicon.com/v1/icons/V1StGXR8Z5jdHi6B/svg"
    }
  ]
}
GET/v1/icons/{id}1 credit

One icon, with its pack, style, tags and download count. An unknown id returns 404 and costs nothing.

{
  "id": "V1StGXR8Z5jdHi6B",
  "name": "Home",
  "slug": "home",
  "style": { "slug": "flat", "name": "Flat" },
  "svgUrl": "https://freeicon.com/v1/icons/V1StGXR8Z5jdHi6B/svg",
  "downloadCount": 1834,
  "pack": { "name": "Essential UI", "slug": "essential-ui" },
  "tags": ["home", "house", "building", "main"]
}
GET/v1/icons/{id}/svg10 credits

Returns the SVG itself as image/svg+xml, not JSON. GET only — a HEAD would spend 10 credits and return no body, so it is refused.

curl https://freeicon.com/v1/icons/V1StGXR8Z5jdHi6B/svg \
  -H "Authorization: Bearer $FREEICON_KEY" \
  -o home.svg

A download through the API counts exactly like a download from the website: the icon author is credited for it.

Errors

Every error has the same shape. Branch on code, which is stable; message is for humans and may be reworded.

{ "error": { "code": "insufficient_credits", "message": "You are out of credits..." } }
CodeHTTPMeaning
unauthorized401No bearer token, or a malformed one.
invalid_key401The key is unknown or has been revoked.
insufficient_credits402Both buckets are empty. Nothing was charged.
svg_not_available403SVG needs a paid plan or top-up credits.
rate_limited429Too fast. See Retry-After.
bad_request400A parameter is missing or out of range.
not_found404No such icon, or it is not published.
method_not_allowed405These endpoints are GET only.
server_error500Our fault. Any credits spent are returned.

Examples

JavaScript (Node)

const KEY = process.env.FREEICON_KEY;           // never hardcode it
const BASE = "https://freeicon.com/v1";

async function api(path) {
  const res = await fetch(BASE + path, {
    headers: { Authorization: `Bearer ${KEY}` },
  });
  if (res.status === 429) {
    // Pace on the header, not on a guess.
    const wait = Number(res.headers.get("Retry-After") ?? 1);
    await new Promise((r) => setTimeout(r, wait * 1000));
    return api(path);
  }
  if (!res.ok) {
    const { error } = await res.json();
    throw new Error(`${error.code}: ${error.message}`);
  }
  return res;
}

// 1. Find an icon
const { icons } = await (await api("/icons/search?q=home&limit=5")).json();

// 2. Download its SVG (10 credits)
const svg = await (await api(`/icons/${icons[0].id}/svg`)).text();
await require("node:fs/promises").writeFile("home.svg", svg);

// 3. Check what you have left — free
const me = await (await api("/me")).json();
console.log(me.credits);

Python

import os, time, requests

KEY = os.environ["FREEICON_KEY"]          # never hardcode it
BASE = "https://freeicon.com/v1"
HEADERS = {"Authorization": f"Bearer {KEY}"}

def api(path):
    res = requests.get(BASE + path, headers=HEADERS)
    if res.status_code == 429:
        time.sleep(int(res.headers.get("Retry-After", 1)))
        return api(path)
    if not res.ok:
        err = res.json()["error"]
        raise RuntimeError(f"{err['code']}: {err['message']}")
    return res

icons = api("/icons/search?q=home&limit=5").json()["icons"]

svg = api(f"/icons/{icons[0]['id']}/svg").text
open("home.svg", "w").write(svg)

print(api("/me").json()["credits"])

Fair use

The API is the supported way to get icons programmatically, and using it within your plan’s limits is exactly what it is for. Scraping the website instead, sharing or reselling a key, or working around credit and rate limits is not — see the License Agreement, which covers automated access explicitly. Keys and accounts that breach it can be suspended.

Icons themselves stay free for personal and commercial use with no attribution required, however you fetch them.