api

POST /api/audits

Mettez en file un audit SEO complet avec Core Web Vitals. Renvoie audit_id + status: queued en 1-2s. Compte contre le plan, respecte le cooldown par domaine.

Ce que fait cet endpoint

POST /api/audits met en file un audit SEO et lisibilité IA complet et renvoie immédiatement l’enveloppe. C’est la contrepartie authentifiée et complète de POST /api/public/audit.

  • Met en file de manière asynchrone et répond en 1-2 secondes avec status: "queued" et un vrai audit_id.
  • Tire Core Web Vitals (LCP, CLS, INP) depuis Google PageSpeed Insights dans le cadre du run.
  • Compte contre le quota du plan (Free 10/mois, Starter 50/mois, Pro illimité).
  • Respecte les cooldowns par domaine : 24h entre audits de la même URL exacte, plus un court cooldown global entre deux audits quelconques.
  • Renvoie la même forme que GET /api/audits/:id. Les findings sont vides jusqu’à ce que l’audit passe à completed.

Pourquoi c’est important

POST /api/audits est le bon endpoint dès qu’il faut un audit persistant à récupérer plus tard, un PDF, des données de trafic organique ou Core Web Vitals. Les audits anonymes couvrent environ 90 vérifications mais sautent PSI ; cet endpoint couvre tout.

Workflows concrets :

  • Un bot audit-sur-PR appelle POST /api/audits quand un preview-deploy part, capture l’audit_id, poll GET /api/audits/:id toutes les 5 secondes jusqu’à 60 secondes et poste un commentaire delta vs le dernier audit de la même URL.
  • Un cron hebdomadaire itère sur les landings les plus visitées du compte, met en file un nouvel audit pour chacune et envoie un digest depuis GET /api/audits.

Comment l’utiliser

L’endpoint est asynchrone par conception. Traitez le 201 Created immédiat comme un accusé de réception et poll GET /api/audits/:id avec l’audit_id renvoyé toutes les quelques secondes. Le temps bout-en-bout typique est de 10 à 30 secondes ; jusqu’à 90 secondes pour les cibles lentes.

Requête

POST /api/audits HTTP/1.1
Host: app.metricspot.com
Authorization: Bearer ms_live_xxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json

{ "url": "https://example.com" }

Le champ url est requis, doit être absolu (https://...), parsable et de 2000 caractères maximum.

curl

curl -X POST https://app.metricspot.com/api/audits \
  -H "authorization: Bearer ms_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "content-type: application/json" \
  -d '{"url": "https://example.com"}'

Node (mise en file + polling)

const headers = {
  authorization: "Bearer ms_live_xxxxxxxxxxxxxxxxxxxxxxxx",
  "content-type": "application/json",
};

const queueRes = await fetch("https://app.metricspot.com/api/audits", {
  method: "POST",
  headers,
  body: JSON.stringify({ url: "https://example.com" }),
});
const { audit } = await queueRes.json();
const id = audit.id;

while (true) {
  await new Promise((r) => setTimeout(r, 3000));
  const r = await fetch(`https://app.metricspot.com/api/audits/${id}`, { headers });
  const data = await r.json();
  if (["completed", "failed"].includes(data.audit.status)) {
    console.log(data.audit.status, data.audit.score);
    break;
  }
}

Python httpx

import httpx, time

HEADERS = {"authorization": "Bearer ms_live_xxxxxxxxxxxxxxxxxxxxxxxx"}

r = httpx.post(
    "https://app.metricspot.com/api/audits",
    headers=HEADERS,
    json={"url": "https://example.com"},
    timeout=30.0,
)
audit_id = r.json()["audit"]["id"]

for _ in range(30):
    time.sleep(3)
    r = httpx.get(f"https://app.metricspot.com/api/audits/{audit_id}", headers=HEADERS, timeout=30.0)
    audit = r.json()["audit"]
    if audit["status"] in ("completed", "failed"):
        print(audit["status"], audit["score"])
        break

Réponse

201 Created :

{
  "audit": {
    "id": 12345,
    "domain": "example.com",
    "url": "https://example.com",
    "status": "queued",
    "score": null,
    "created_at": "2026-05-14T10:18:04.000Z"
  }
}

Champs de la réponse de mise en file :

  • id : id entier d’audit, utilisé dans chaque appel suivant (GET /api/audits/:id, PDF, Google).
  • domain : hostname extrait de l’URL.
  • url : écho de la requête.
  • status : toujours "queued" en réponse initiale. Passe à "running" puis "completed" ou "failed".
  • score : null jusqu’à la fin.
  • created_at : horodatage ISO 8601.

Une fois status === "completed", GET /api/audits/:id renvoie l’enveloppe complète avec score, module_scores et le tableau findings (voir api-get-audit).

Enveloppe d’erreur

{ "error": "Quota exceeded", "message": "Upgrade to Starter for 50 audits per month." }

Erreurs courantes

CodeQuandAction
UNAUTHORIZED (401)Bearer manquant ou invalideCréez une clé sur app.metricspot.com/settings/api-keys
QUOTA_EXCEEDED (402)Quota mensuel épuiséUpgrade sur app.metricspot.com/billing
INVALID_URL (400)URL non parsable ou > 2000 caractèresPassez une URL absolue https://
RATE_LIMITED (429)Cooldown par domaine ou globalAttendez la fenêtre indiquée avant de retenter la même URL
UPSTREAM_FAILED (5xx)Aléa PSI ou crawler en amontRéessayez une fois avec backoff

Questions fréquentes

Combien de temps avant que l’audit soit completed ?

10 à 30 secondes sur les sites typiques. Cibles riches en JS ou pages avec beaucoup de structured data peuvent prendre 90 secondes. Poll GET /api/audits/:id toutes les 3-5 secondes : l’audit est complet quand status bascule sur completed.

Un audit échoué consomme-t-il du quota ?

Un audit failed ne consomme pas de quota. Le compteur ne décrémente que lorsque le run réussit et que les findings sont stockés. Les échecs de rate-limit PSI sont retentés en interne avant remontée.

Pourquoi un cooldown par domaine ?

La même URL auditée deux fois en cinq minutes renvoie les mêmes findings : ça brûle le quota PSI et brouille les dashboards de trafic. Le cooldown de 24h par URL est appliqué côté serveur. Le message d’erreur inclut l’horodatage retry_at exact pour que les clients puissent reprogrammer.

Puis-je mettre en file plusieurs audits en parallèle ?

Oui, jusqu’au cooldown global (quelques secondes entre deux audits quelconques par compte). Pour des lots de 50+ URL, espacez-les côté client ou passez à Pro, qui lève à la fois le quota mensuel et resserre le throttle global.

Sources

Dernière mise à jour 2026-05-14