Ressources
L'API
Lancer des analyses depuis vos outils, d'une seule page au site entier
Sommaire
Démarrer
L'API répond sous https://api.getgrammage.com/v1, en JSON, et son contrat complet se lit dans openapi.json, qu'on peut ouvrir dans n'importe quel outil compatible OpenAPI ou donner à un générateur de client. Elle analyse une seule page comme le site entier, mais jamais le code source, qui ne se lit que dans votre CI.
Une mesure prend souvent plus longtemps qu'une requête HTTP ne peut attendre, alors tout ce qui mesure est asynchrone. La demande répond tout de suite en 202 avec l'adresse à suivre, qu'on relit ensuite ou qu'on écoute en direct jusqu'au verdict.
L'authentification
Le test gratuit, les attestations et le badge sont publics, sans compte ni clé. Les routes de l'attestation et des résultats demandent votre licence, envoyée dans l'en-tête Authorization, et une licence ne relit jamais que ses propres résultats.
curl https://api.getgrammage.com/v1/results/<id> \
-H "Authorization: Bearer $GRAMMAGE_LICENSE"Les routes
| Route | Accès | Rôle |
|---|---|---|
POST /v1/tests | public | met une page en file pour le test gratuit |
GET /v1/tests/:id | public | l'état du test, puis son rapport complet |
GET /v1/tests/:id/events | public | le même état en direct, en Server-Sent Events |
GET /v1/tests/:id/brief | public | le brief pour un agent de code, ou l'aide RGESN, en Markdown |
POST /v1/tests/:id/cancel | public | annule le test, qui reste compté dans les limites du jour |
POST /v1/certifications | licence avec badge | fait mesurer et vérifier un domaine par Solyzon, sans CI |
POST /v1/results | licence | enregistre un rapport signé par votre CI, ce que fait grammage certify |
GET /v1/results/:id | la licence qui l'a envoyé | le résultat, sa vérification et son rapport détaillé |
GET /v1/results/:id/events | la licence qui l'a envoyé | l'avancement de la vérification en direct |
GET /v1/attestations/:host | public | l'attestation en cours du site, ou d'une page avec ?path= |
GET /v1/attestations/:host/documents | public | les documents vérifiés et leur empreinte SHA-256 |
GET /v1/badge.js | public | le module du badge |
GET /v1/openapi.json | public | le contrat OpenAPI de l'API |
GET /v1/version | public | la version de l'API et du moteur, et celle du format du rapport |
Le test gratuit
N'importe qui peut faire mesurer une page, sans licence ni compte, trois fois par jour et par domaine. La page est mesurée en un passage, sur mobile et en catégorie auto par défaut, avec le rapport complet, ses correctifs et le gain de chacun, mais aucune attestation n'est signée, puisque c'est un test.
curl -s https://api.getgrammage.com/v1/tests \
-H 'content-type: application/json' \
-d '{"url": "https://exemple.fr/", "device": "mobile", "category": "auto"}'const response = await fetch('https://api.getgrammage.com/v1/tests', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ url: 'https://exemple.fr/' }),
});
const { id, url, status, queuePosition } = await response.json();import requests
response = requests.post('https://api.getgrammage.com/v1/tests', json={'url': 'https://exemple.fr/'})
test = response.json()
print(test['id'], test['status'], test['queuePosition'])La réponse arrive en 202 avec l'identifiant du test et sa place dans la file. Si la même page a déjà été mesurée il y a moins de quatre heures, l'API rend ce résultat en 200, sans compter dans vos limites.
{
"id": "6f1c0e3a-8b1d-4c52-9a7e-2d4b0f3c9e11",
"url": "https://api.getgrammage.com/v1/tests/6f1c0e3a-8b1d-4c52-9a7e-2d4b0f3c9e11",
"status": "pending",
"queuePosition": 2
}Suivre une mesure
On suit une mesure de deux façons, en écoutant son flux d'événements, qui envoie l'état à chaque changement puis se ferme au verdict, ou en relisant GET /v1/tests/:id jusqu'à ce que status quitte pending. Le flux évite de relire pour rien, et c'est donc lui qu'on préfère.
const events = new EventSource('https://api.getgrammage.com/v1/tests/' + id + '/events');
events.onmessage = (event) => {
const { status, queuePosition, progress } = JSON.parse(event.data);
if (status !== 'pending') {
events.close();
}
};let test;
do {
await new Promise((resolve) => setTimeout(resolve, 5000));
test = await (await fetch('https://api.getgrammage.com/v1/tests/' + id)).json();
} while (test.status === 'pending');
const score = test.report?.pages[0]?.score.score;{
"status": "pending",
"queuePosition": null,
"progress": {
"step": "measure",
"page": 1,
"pages": 1,
"run": 1,
"runs": 1,
"url": "https://exemple.fr/",
"updatedAt": "2026-09-30T09:30:12.000Z"
},
"detail": null
}step vaut discovery pendant la découverte d'un site, measure pendant la mesure d'une page et report une fois les pages mesurées, et progress reste nul tant que la mesure attend son tour, queuePosition disant alors sa place. Le dernier état vaut passed, avec le rapport complet dans report, ou failed et error, avec un bloc failure qui dit pourquoi, ou encore cancelled quand le test a été annulé.
Un test qui ne sert plus s'annule avec POST /v1/tests/:id/cancel. Il sort alors de la file, ou son résultat est ignoré si la mesure avait déjà commencé, mais il reste quand même compté dans les limites du jour, pour qu'on ne puisse pas les contourner en annulant. Annuler deux fois répond pareil, et un test déjà terminé répond 409.
Le brief et l'aide RGESN
Une fois la page mesurée, la même mesure donne le brief de correction pour un agent de code et l'aide à la déclaration d'écoconception, tous deux en Markdown.
curl -s 'https://api.getgrammage.com/v1/tests/<id>/brief?format=agent' > grammage-agent.md
curl -s 'https://api.getgrammage.com/v1/tests/<id>/brief?format=rgesn' > grammage-rgesn.mdLes résultats attestés
Un résultat naît de deux façons, d'un rapport signé que votre CI envoie par POST /v1/results, ce que fait pour vous grammage certify, ou d'une vérification à la demande par POST /v1/certifications. Dans les deux cas, la réponse donne son identifiant, et GET /v1/results/:id rend ensuite son état, sa place dans la file, l'avancement de la mesure de Solyzon sous verification.progress et, une fois vérifié, son rapport détaillé.
Son flux d'événements demande la licence, et comme EventSource ne sait pas poser d'en-tête Authorization, on le lit par fetch et son corps en flux, ou on le relaie depuis son propre serveur. Une CI qui renvoie le même rapport avec le même en-tête Idempotency-Key retrouve d'ailleurs son résultat sans en créer un second.
curl -N https://api.getgrammage.com/v1/results/<id>/events \
-H "Authorization: Bearer $GRAMMAGE_LICENSE"const response = await fetch('https://api.getgrammage.com/v1/results/' + id + '/events', {
headers: { authorization: 'Bearer ' + license },
});
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
for (let chunk = await reader.read(); !chunk.done; chunk = await reader.read()) {
for (const line of chunk.value.split('\n')) {
if (line.startsWith('data: ')) {
console.log(JSON.parse(line.slice(6)));
}
}
}Les attestations
L'attestation en cours d'un domaine est publique, c'est elle que lit le badge. Elle arrive signée, et la clé publique qui la vérifie est resultats-2026-09. La route des documents donne les trois documents vérifiés, le rapport en HTML et en PDF et l'attestation en PDF, avec leur empreinte SHA-256, les mêmes que publie la page de vérification.
curl -s https://api.getgrammage.com/v1/attestations/exemple.fr
curl -s 'https://api.getgrammage.com/v1/attestations/exemple.fr?path=/tarifs'
curl -s https://api.getgrammage.com/v1/attestations/exemple.fr/documentsUn badge posé sur www. retrouve l'attestation mesurée sans, et l'inverse. Ces réponses restent une minute en cache chez Cloudflare et cinq minutes dans le navigateur, donc un nouveau résultat se voit au plus six minutes après son enregistrement.
Lire le rapport
Le rapport est le même partout, que la mesure vienne de l'API, de votre CI ou d'un poste. Sa forme ne perd jamais un champ tant que schemaVersion vaut 1, et un changement de ce genre ferait passer le schéma à 2.
| Champ | Ce qu'il dit |
|---|---|
pages[].score.score | la note sur 100 de la page |
pages[].score.context | la catégorie retenue, inferred quand Grammage l'a déduite |
pages[].co2.swd4 | le carbone par vue, selon le modèle SWD v4 |
pages[].stack | le framework reconnu, qui choisit les correctifs propres |
pages[].advice | les correctifs, rangés par poids puis par gain, avec fix, stackFix et gain |
pages[].valid | false quand la page n'a pas pu être mesurée, avec sa raison |
schemaVersion | la forme du rapport, qui ne perd aucun champ tant qu'elle vaut 1 |
tool.version, protocol, score.method | les trois versions, sans lesquelles deux scores ne se comparent pas |
Les erreurs
Les erreurs arrivent au format application/problem+json de la RFC 9457, avec un titre et un detail en français qu'on peut afficher tel quel à vos utilisateurs.
{
"type": "about:blank",
"title": "Trop de demandes",
"status": 429,
"detail": "au plus 3 tests gratuits par jour et par domaine"
}| Code | Ce qu'il veut dire |
|---|---|
200 | la réponse, ou pour un test, la même page déjà mesurée il y a moins de 4 heures |
202 | la demande est en file, son adresse est dans url et dans l'en-tête Location |
400 | la requête est illisible, detail dit quel champ corriger |
401 | la licence est absente ou invalide |
403 | la licence n'est plus active, ou ne couvre pas ce domaine ou cette fonction |
404 | l'identifiant ou le domaine est inconnu |
422 | le rapport vise une adresse qui n'est pas celle du domaine attesté |
429 | une limite est atteinte, Retry-After dit quand réessayer |
503 | la file est pleine, réessayez après Retry-After |
Quand une mesure échoue
Une mesure qui échoue ne rend pas une erreur HTTP, puisque la demande, elle, a bien été traitée. Elle passe en failed ou en error avec un bloc failure, dont le code dit ce qui s'est passé et retryable s'il vaut la peine de proposer un nouvel essai. Les erreurs réseau passagères sont déjà réessayées une fois de notre côté avant d'arriver là.
{
"code": "bot-challenge",
"message": "le site bloque les navigateurs automatisés par Cloudflare, il faut autoriser l’agent Grammage",
"retryable": false,
"vendor": "Cloudflare",
"httpStatus": 403
}| Code | Ce qui s'est passé | Nouvel essai |
|---|---|---|
dns | le nom de domaine ne répond pas | oui |
unreachable | le site refuse ou coupe la connexion | oui |
timeout | le site met trop de temps à répondre | oui |
tls | le certificat HTTPS est invalide ou expiré | non |
http-status | la page répond 4xx ou 5xx, httpStatus le précise | sur 5xx et 429 |
bot-challenge | un service anti-robots bloque la mesure, vendor le nomme | non |
not-html | l'adresse ne renvoie pas une page HTML | non |
unresponsive | la page ne répond plus pendant la mesure | oui |
private-address | l'adresse mène à un réseau privé | non |
measurement-error | la mesure n'a pas abouti de notre côté | oui |
Les limites
Les limites protègent les machines qui mesurent, et chacune répond 429 avec un en-tête Retry-After, sans jamais rien facturer.
| Quoi | Limite |
|---|---|
| Test gratuit | 3 par jour et par domaine, 10 par jour et par adresse, 1 page |
| Même page déjà testée | le résultat de moins de 4 heures est rendu, sans compter dans les limites |
| Flux d'événements | 30 minutes au plus, 5 ouverts à la fois par adresse |
| Vérification de CI | 10 par jour et par domaine, 60 par heure et 300 par jour par licence |
| Vérification à la demande | 1 par semaine et par domaine, dans le quota mensuel de la licence |
| Pages d'une vérification | 100 au plus |
| Lectures publiques | une limite par minute et par adresse, au-delà 429 |
Ce que l'API garde, et combien de temps
Chaque nuit, une purge applique ces durées, et le dernier résultat de chaque site ou page échappe toujours aux deux premières. Les appels publics, dont ceux du badge, ne gardent aucune adresse IP et ne se comptent que par minute, par route et par statut.
| Donnée | Durée | Ce qui est effacé |
|---|---|---|
| Test gratuit | 30 jours | le résultat, sa mesure et son rapport sont effacés |
| Rapport complet | 13 mois | le rapport est effacé, le résultat reste |
| Résultat et attestation | 6 ans | les deux sont effacés |
| Adresse IP d'un appel sous licence | 12 mois | l'adresse est effacée |
| Journal des requêtes | 12 mois | les lignes sont effacées |
Voyez ce que votre site pèse vraiment
Grammage réunit dans un seul outil l'audit dans un vrai navigateur, l'analyse du code dans la CI/CD et un résultat signé que chacun peut vérifier.
Essai gratuit, sans engagement.
Essayez gratuitement