JSON API reference
Version 1 of the JSON API lives at https://heymadefonts.com/api/v1. It needs no key and sets no cookies. Responses allow any origin (Access-Control-Allow-Origin: *).
Response envelope
Every successful response wraps its payload in data and adds meta:
{
"data": { … },
"meta": { "requestId": "k3x9…", "version": "v1" }
}
Errors use one shape, with a stable machine-readable code, a message written for people, and whether retrying the same request may succeed:
{
"error": {
"code": "MIGRATE_NOTHING_FOUND",
"message": "No font stylesheet URL was found. …",
"retryable": false
}
}
Validation errors add details, an object of field names and messages. Every response carries an X-Request-Id header; quote it when you report a problem.
Status codes
| Status | Meaning |
|---|---|
| 200 | Success. |
| 201, 202 | A scan was created or accepted for processing. |
| 302 | Redirect to a file on the font CDN (downloads). |
| 400 | The request is malformed or a required field is missing (VALIDATION_FAILED, MIGRATE_INPUT_REQUIRED). |
| 404 | No published font or scan with that identifier (NOT_FOUND). |
| 413 | Input too large (MIGRATE_INPUT_TOO_LARGE). |
| 422 | The request is well-formed but cannot be processed (MIGRATE_NOTHING_FOUND). |
| 429 | Rate limit reached (RATE_LIMITED, retryable: true). Wait and retry. |
| 500, 503 | Our fault (INTERNAL_ERROR, UNAVAILABLE, CATALOG_UNAVAILABLE). Retry later. |
Fonts
GET /api/v1/fonts
Published families, paginated. Pagination details are in meta.pagination.
{
"id": 42,
"slug": "lato",
"family": "Lato",
"category": "sans-serif",
"designer": "Łukasz Dziedzic",
"license": {
"type": "OFL",
"spdx": "OFL-1.1",
"url": "https://heymadefonts.com/fonts/lato/license",
"attribution": "Copyright (c) 2010-2014 by tyPoland Lukasz Dziedzic …"
},
"styles": [
{ "weightMin": 400, "weightMax": 400, "italic": false, "variableAxes": {}, "subsets": ["latin", "latin-ext", "…"] }
],
"isVariable": false,
"supportsScandinavian": true,
"cssUrl": "https://fonts.heymadefonts.com/css2?family=Lato"
}
GET /api/v1/fonts/{slug}
One family with the same fields as a list item. Unknown or unpublished slugs return 404.
GET /api/v1/fonts/{slug}/download
Redirects (302) to an immutable ZIP on the font CDN containing the font files, the licence text and a README with attribution. The website’s download link, https://heymadefonts.com/fonts/lato/download, does the same.
GET /api/v1/catalog/version
The live catalogue version and when it was published. Use it to decide when to refresh a cached copy of the font list.
Migration
POST /api/v1/migrate
Converts pasted font-loading code (for example a fonts.googleapis.com link) into snippets for the closest HeyMadeFonts families. The text is parsed only: nothing in it is fetched, executed or stored. Send JSON or a form field named input, up to 20 KB and 20 URLs.
curl -s https://heymadefonts.com/api/v1/migrate \
-H 'Content-Type: application/json' \
-d '{"input": "<link href=\"https://fonts.googleapis.com/css2?family=Lato:wght@400;700&display=swap\" rel=\"stylesheet\">"}'
The data object contains:
statussupported(everything found),partialorunsupported(nothing usable).summary- Counts:
references,families,supported,partial,unsupported. result- Combined
url,css2Query,preconnect,linkandimportsnippets covering every supported style, ornull. references[]- One entry per URL found:
source,kind(link,import,wordpress,url),line,api,status,issues[], a drop-inreplacementin the same syntax, andfamilies[]withname,catalogName,slug,requested,status,reason,missing[](style and reason),fontUrlanddownloadUrl. notes[]- Things the snippet does not fix by itself, such as preconnect hints to the old font host.
Errors: MIGRATE_INPUT_REQUIRED (400), MIGRATE_INPUT_TOO_LARGE (413, over 20 KB or more than 20 URLs), MIGRATE_NOTHING_FOUND (422), CATALOG_UNAVAILABLE (503).
Website scans
The scanner opens one public page and reports the font requests it observes. Ownership of a scan is proven with the X-Scan-Owner-Token header returned when the scan is created; there are no accounts or cookies.
POST /api/v1/scans- Create a scan for a public
https://orhttp://URL. Subject to per-client, per-domain and global rate limits. GET /api/v1/scans/{id}- Status and, when finished, the findings with evidence.
POST /api/v1/scans/{id}/share- Turn a time-limited public share link on or off (owner token required).
GET /api/v1/scans/{id}/report.pdf- The report as a PDF, with the same access rules as the report page.
See how the scanner works for what a scan can and cannot show.
HeyMadeIt integration
GET /api/v1/integrations/heymadeit/fonts
A feed of published families for HeyMadeIt, our sister design and print product, including each family’s licence and its print-embedding review status. It is public and read-only; GET /api/v1/integrations/heymadeit/fonts/{slug} returns one family.
Rate limits
Read endpoints allow 240 requests per minute per client, migration 30 per minute. Clients are identified by a daily-rotating hash of the IP address used only for rate limiting. Exceeding a limit returns 429 with retryable: true.