API
Read-only public API: fetch the fallback CSS and calibrated metrics of every font in the database, as JSON or CSS.
Overview
The API exposes, read-only, every font in the database with its calibrated metrics and the matching fallback CSS. It is meant to be called directly from a third-party site, a build script or a CMS plugin.
- Base URL:
https://nmfs.agencewebperformance.fr/api/v1 - No authentication, no key, no declared quota. Responses are cached upstream (see Caching), which absorbs repeated calls.
- Meant for projects in development, not for production. Fetch the CSS at build time or server-side, and ship it in your own stylesheet. Never reference an API URL from a live site: this is not a CDN, and nothing guarantees its availability or latency for your visitors.
- No CORS. A visitor's browser on a third-party site cannot read these responses. The intended uses — build script, server-side CMS plugin,
curl— do not need it. - Only published, public fonts are exposed. Private fonts and pending submissions return a 404 indistinguishable from a non-existent id — their existence cannot be inferred.
- No internal data leaks: no file path, no e-mail address, and never the font file itself.
Endpoints
| Method and path | Returns | Type |
|---|---|---|
GET /api/v1/fonts | Paginated list of fonts | application/json |
GET /api/v1/fonts/{id} | Metadata, metrics and CSS of one font | application/json |
GET /api/v1/fonts/{id}/css | The CSS alone, to copy into your project | text/plain |
Only GET and HEAD are accepted. Another method on a known path returns 405; an unknown path under /api returns 404, always as JSON.
Listing fonts
GET https://nmfs.agencewebperformance.fr/api/v1/fonts?stack=serif&per_page=25
| Parameter | Values | Effect |
|---|---|---|
q | free text | Keeps fonts whose name contains this text. |
stack | sans or serif | Keeps only that fallback stack. Any other value is ignored. |
slug | exact slug, e.g. poppins | Exact match. Since a slug is not guaranteed unique, the response is still a list (0 to n items) and is not paginated. |
page | integer ≥ 1 | Requested page. Default 1. |
per_page | 1 to 100 | Page size. Default 50, capped at 100. |
Fonts are sorted by name, case-insensitively. Each item carries links to its JSON record, its CSS and its page in the tool:
{
"data": [
{
"id": 1,
"name": "Poppins",
"slug": "poppins",
"stack": "sans",
"updated_at": "2026-05-12T15:33:02+00:00",
"links": {
"self": "https://nmfs.agencewebperformance.fr/api/v1/fonts/1",
"css": "https://nmfs.agencewebperformance.fr/api/v1/fonts/1/css",
"html": "https://nmfs.agencewebperformance.fr/font/1"
}
}
],
"meta": { "page": 1, "per_page": 50, "total": 81, "total_pages": 2 }
}
A single font
GET https://nmfs.agencewebperformance.fr/api/v1/fonts/1
The record gathers everything needed to integrate the font without going through the tool. Metrics are given per target — the system font each fallback actually aims at — rather than by slot number:
{
"id": 1,
"name": "Poppins",
"slug": "poppins",
"stack": "sans",
"contributor": "Eroan",
"created_at": "2026-04-30T09:17:41+00:00",
"updated_at": "2026-05-12T15:33:02+00:00",
"fallbacks": {
"android": {
"font": "Roboto",
"size_adjust": 112.54,
"ascent_override": 97.24,
"descent_override": 35.78,
"line_gap_override": null
},
"desktop": {
"font": "Arial",
"size_adjust": 113,
"ascent_override": 96,
"descent_override": 34.2,
"line_gap_override": null
}
},
"font_family": "'Poppins', 'Poppins-fallback', sans-serif",
"css": "/* Android fallback (Roboto) — declared FIRST */\n@font-face { … }\n\nbody { font-family: … }",
"links": { "self": "…", "css": "…", "html": "…" }
}
| Field | Content |
|---|---|
stack | sans (Arial + Roboto) or serif (Times New Roman + Noto Serif). |
fallbacks.desktop | Fallback for Windows, macOS and iOS. It is the face declared last in the CSS, hence the one that wins when both system fonts are present. |
fallbacks.android | Fallback for Android, where the desktop font does not exist. Declared first. |
*.size_adjust | Percentage, always present (100 = unchanged). |
*.ascent_override, descent_override, line_gap_override | Percentage, or null when the descriptor is normal and is not emitted in the CSS. |
font_family | The font-family stack alone, to apply on the selector of your choice. |
css | The complete integration example, identical to the font's page: the two @font-face rules (one fallback family, two faces) and the body declaration. |
contributor | Display name of the last contributor, or null. |
created_at, updated_at | ISO 8601, with the server's UTC offset. |
CSS only
GET https://nmfs.agencewebperformance.fr/api/v1/fonts/1/css
Returns the @font-face rules preceded by a provenance header, so they can be fetched without parsing JSON — typically from a build script:
curl -s https://nmfs.agencewebperformance.fr/api/v1/fonts/1/css >> src/styles/fonts.css
This endpoint is not a stylesheet, and cannot be used as one. It is served as text/plain with X-Content-Type-Options: nosniff: no modern browser will apply it through <link rel="stylesheet">. Better still, when the browser itself announces a style destination (Sec-Fetch-Dest: style), the request is refused with a 403 and an explicit message. The API exists to feed your projects, not to have your visitors depend on it on every page load.
The CSS is identical to the one shown on the font's page: the two @font-face rules, then a body { font-family: … } declaration. The latter is an integration example — adapt the selector to your project (html, .site, a component…); the value alone is also provided in the font_family field of the JSON record:
body { font-family: 'Poppins', 'Poppins-fallback', sans-serif; }
The CSS reproduces the font's stack exactly as calibrated in the tool. If you reorganise these rules, keep the order: the desktop face must stay declared last.
Caching and conditional requests
Every response carries Cache-Control: public, max-age=3600 and an ETag that changes as soon as the record is saved again. Any intermediate cache or HTTP client honouring these headers can therefore keep them for an hour without hitting us.
To revalidate without re-downloading, send back the ETag you received: the answer is a body-less 304 Not Modified if nothing changed.
GET /api/v1/fonts/1/css If-None-Match: "c8cf8315612e57dc9057159925c729d7" HTTP/1.1 304 Not Modified
The API sets no cookie — that is what makes shared caching possible.
Errors
Errors are always JSON, including on the CSS endpoint:
HTTP/1.1 404 Not Found
{ "error": "not_found" }
| Status | Case |
|---|---|
404 | Unknown id, private font, submission awaiting review, or unknown path. The first three cases are deliberately indistinguishable. |
403 | Attempt to load the CSS endpoint as a stylesheet (Sec-Fetch-Dest: style) — { "error": "stylesheet_hotlink_forbidden" }. |
405 | Method other than GET / HEAD on a known path. |
500 | Internal error — { "error": "internal_error" }. Details are never exposed. |
An invalid filter parameter is not an error: it is ignored (stack) or clamped into range (per_page).
Stability
The /v1 prefix is a commitment: the fields documented here will be neither renamed nor removed in this version. Fields may be added — write your clients so they ignore what they don't know. A breaking change would ship as /v2, leaving /v1 in service.
Numeric ids are stable over time. Slugs are derived from the font's name and may change if it is renamed; prefer the id to reference a font durably.