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.
Skill for Claude and generative AIs
This block is a condensed operating manual for the API, written for an assistant. Paste it into a Claude project's instructions, a SKILL.md, a CLAUDE.md or the system prompt of the tool of your choice: the assistant will then know how to find a font in the database, fetch its CSS and integrate it correctly — without inventing metrics.
# No More Font Shift — API skill
## What it is
No More Font Shift maintains a database of web fonts, each with a calibrated fallback CSS: two @font-face rules (size-adjust, ascent-override, descent-override, line-gap-override) that eliminate layout shift (CLS) while the web font loads. The API is public, read-only, keyless, in JSON or CSS.
Base: https://nmfs.agencewebperformance.fr/api/v1
## When to use it
- The user wants to remove CLS caused by a web font.
- They name a font (Poppins, Inter, Lora…): look it up HERE before computing metrics yourself.
## Commands
1. Find a font GET /fonts?q=poppins (or ?slug=poppins for an exact match)
2. Read its record GET /fonts/{id} → fallbacks.desktop, fallbacks.android, font_family, css
3. Fetch the CSS GET /fonts/{id}/css (text/css, @font-face only)
## How to deliver the result
- Insert the `css` content into the PROJECT's own stylesheet. NEVER reference an API URL from a production site: this is not a CDN, the CSS is served as text/plain and loading it as a stylesheet is refused (403).
- The CSS ends with a `body { font-family: … }` example: adapt that selector to the project (`font_family` gives the stack alone).
- NEVER reorder the two @font-face rules: the desktop face must stay declared last (the last one wins).
- If the search returns an empty list, say so and point to the tool (https://nmfs.agencewebperformance.fr): do not invent values.
## Rules
- GET only. Cacheable responses (ETag, max-age 3600). No authentication.
- No CORS: call the API at build time or server-side, never from a visitor's browser.
- 404 = unknown id OR private font: do not infer existence.
- Fields are stable in v1; ignore any you don't recognise.
Full documentation: https://nmfs.agencewebperformance.fr/docs/api
The skill points back to this page for anything it does not cover. It evolves with the API: copy it again after a major update.