API gratuite de centrage des cartes : PSA et photos

Centering Lab est une API REST gratuite de centrage des cartes, sans clé API. Sur https://centeringlab.com/api/v1, elle renvoie mesures photographiques, ratios et plafonds PSA, BGS, CGC ou SGC fondés sur le centrage seul. Elle ne prédit pas la note finale et n’authentifie pas les cartes. Appelle-la depuis ton serveur. OpenAPI.

Démarrage rapide

Exécute curl dans ton terminal. Les deux axes du recto sont fournis, mais le verso manque : incomplete vaut true. PSA 10 est un plafond de centrage, pas une prédiction de note finale.

POST /api/v1/grade

Requête

curl --fail-with-body 'https://centeringlab.com/api/v1/grade' \
  -H 'Content-Type: application/json' \
  --data '{"front_lr":0.55,"front_tb":"50/50","house":"PSA"}'

Réponse HTTP 200

Extrait d’une vraie réponse HTTP locale ; des champs et bandes supplémentaires sont omis. Les clés JSON et messages du serveur restent en anglais.

{
  "front": {
    "ratios": {
      "lr": 55,
      "tb": 50
    }
  },
  "combined": {
    "PSA": {
      "maximum_grade": 10,
      "source_status": "official"
    }
  },
  "normalizations": [
    {
      "field": "front_lr",
      "input": 0.55,
      "ratio": "55/45",
      "note": "0.55 read as 55/45"
    }
  ],
  "incomplete": true,
  "missing_faces": [
    "back"
  ],
  "disclaimer": "This is a ceiling from centering alone, not a prediction of the final grade."
}

Endpoints et champs

Ratios connus

POST /grade exige au moins un champ parmi front_lr, front_tb, back_lr, back_tb. Les valeurs sont normalisées au côté majoritaire. Pourcentages (55), fractions (0.55) et paires de somme 100 (55/45) ou 1 (0.55/0.45) sont admis. Séparateurs : /, -, –, —, :, x ou espaces ; % facultatif. Le scalaire 1 est ambigu et refusé. L/R ou left/right uniquement pour *_lr ; T/B ou top/bottom pour *_tb.

house filtre PSA, BGS, CGC ou SGC. normalizations signale les conversions de fractions. missing_axes, missing_faces et incomplete signalent les données manquantes pouvant abaisser le plafond. combined combine les faces ; limiting_face et limiting_axis indiquent la contrainte. Chaque plafond inclut sources et statut.

Champs du contrat (noms JSON exacts)

input: front_lr, front_tb, back_lr, back_tb, house
output: front, front.ratios, front.ratios.lr, front.ratios.tb, front.missing_axes, front.ceilings, front.ceilings.PSA, front.ceilings.PSA.maximum_grade, front.ceilings.PSA.label, front.ceilings.PSA.rank, front.ceilings.PSA.limiting_face, front.ceilings.PSA.limiting_axis, front.ceilings.PSA.distance_to_next, front.ceilings.PSA.next_grade, front.ceilings.PSA.next_label, front.ceilings.PSA.borderline, front.ceilings.PSA.unconstrained, front.ceilings.PSA.source_status, front.ceilings.PSA.standard_status, front.ceilings.PSA.source, front.ceilings.PSA.sources, front.ceilings.PSA.verified, front.ceilings.PSA.verification, front.ceilings.BGS, front.ceilings.BGS.maximum_grade, front.ceilings.BGS.label, front.ceilings.BGS.rank, front.ceilings.BGS.limiting_face, front.ceilings.BGS.limiting_axis, front.ceilings.BGS.distance_to_next, front.ceilings.BGS.next_grade, front.ceilings.BGS.next_label, front.ceilings.BGS.borderline, front.ceilings.BGS.unconstrained, front.ceilings.BGS.source_status, front.ceilings.BGS.standard_status, front.ceilings.BGS.source, front.ceilings.BGS.sources, front.ceilings.BGS.verified, front.ceilings.BGS.verification, front.ceilings.CGC, front.ceilings.CGC.maximum_grade, front.ceilings.CGC.label, front.ceilings.CGC.rank, front.ceilings.CGC.limiting_face, front.ceilings.CGC.limiting_axis, front.ceilings.CGC.distance_to_next, front.ceilings.CGC.next_grade, front.ceilings.CGC.next_label, front.ceilings.CGC.borderline, front.ceilings.CGC.unconstrained, front.ceilings.CGC.source_status, front.ceilings.CGC.standard_status, front.ceilings.CGC.source, front.ceilings.CGC.sources, front.ceilings.CGC.verified, front.ceilings.CGC.verification, front.ceilings.SGC, front.ceilings.SGC.maximum_grade, front.ceilings.SGC.label, front.ceilings.SGC.rank, front.ceilings.SGC.limiting_face, front.ceilings.SGC.limiting_axis, front.ceilings.SGC.distance_to_next, front.ceilings.SGC.next_grade, front.ceilings.SGC.next_label, front.ceilings.SGC.borderline, front.ceilings.SGC.unconstrained, front.ceilings.SGC.source_status, front.ceilings.SGC.standard_status, front.ceilings.SGC.source, front.ceilings.SGC.sources, front.ceilings.SGC.verified, front.ceilings.SGC.verification, back, back.ratios, back.ratios.lr, back.ratios.tb, back.missing_axes, back.ceilings, back.ceilings.PSA, back.ceilings.PSA.maximum_grade, back.ceilings.PSA.label, back.ceilings.PSA.rank, back.ceilings.PSA.limiting_face, back.ceilings.PSA.limiting_axis, back.ceilings.PSA.distance_to_next, back.ceilings.PSA.next_grade, back.ceilings.PSA.next_label, back.ceilings.PSA.borderline, back.ceilings.PSA.unconstrained, back.ceilings.PSA.source_status, back.ceilings.PSA.standard_status, back.ceilings.PSA.source, back.ceilings.PSA.sources, back.ceilings.PSA.verified, back.ceilings.PSA.verification, back.ceilings.BGS, back.ceilings.BGS.maximum_grade, back.ceilings.BGS.label, back.ceilings.BGS.rank, back.ceilings.BGS.limiting_face, back.ceilings.BGS.limiting_axis, back.ceilings.BGS.distance_to_next, back.ceilings.BGS.next_grade, back.ceilings.BGS.next_label, back.ceilings.BGS.borderline, back.ceilings.BGS.unconstrained, back.ceilings.BGS.source_status, back.ceilings.BGS.standard_status, back.ceilings.BGS.source, back.ceilings.BGS.sources, back.ceilings.BGS.verified, back.ceilings.BGS.verification, back.ceilings.CGC, back.ceilings.CGC.maximum_grade, back.ceilings.CGC.label, back.ceilings.CGC.rank, back.ceilings.CGC.limiting_face, back.ceilings.CGC.limiting_axis, back.ceilings.CGC.distance_to_next, back.ceilings.CGC.next_grade, back.ceilings.CGC.next_label, back.ceilings.CGC.borderline, back.ceilings.CGC.unconstrained, back.ceilings.CGC.source_status, back.ceilings.CGC.standard_status, back.ceilings.CGC.source, back.ceilings.CGC.sources, back.ceilings.CGC.verified, back.ceilings.CGC.verification, back.ceilings.SGC, back.ceilings.SGC.maximum_grade, back.ceilings.SGC.label, back.ceilings.SGC.rank, back.ceilings.SGC.limiting_face, back.ceilings.SGC.limiting_axis, back.ceilings.SGC.distance_to_next, back.ceilings.SGC.next_grade, back.ceilings.SGC.next_label, back.ceilings.SGC.borderline, back.ceilings.SGC.unconstrained, back.ceilings.SGC.source_status, back.ceilings.SGC.standard_status, back.ceilings.SGC.source, back.ceilings.SGC.sources, back.ceilings.SGC.verified, back.ceilings.SGC.verification, combined, combined.PSA, combined.PSA.maximum_grade, combined.PSA.label, combined.PSA.rank, combined.PSA.limiting_face, combined.PSA.limiting_axis, combined.PSA.distance_to_next, combined.PSA.next_grade, combined.PSA.next_label, combined.PSA.borderline, combined.PSA.unconstrained, combined.PSA.source_status, combined.PSA.standard_status, combined.PSA.source, combined.PSA.sources, combined.PSA.verified, combined.PSA.verification, combined.BGS, combined.BGS.maximum_grade, combined.BGS.label, combined.BGS.rank, combined.BGS.limiting_face, combined.BGS.limiting_axis, combined.BGS.distance_to_next, combined.BGS.next_grade, combined.BGS.next_label, combined.BGS.borderline, combined.BGS.unconstrained, combined.BGS.source_status, combined.BGS.standard_status, combined.BGS.source, combined.BGS.sources, combined.BGS.verified, combined.BGS.verification, combined.CGC, combined.CGC.maximum_grade, combined.CGC.label, combined.CGC.rank, combined.CGC.limiting_face, combined.CGC.limiting_axis, combined.CGC.distance_to_next, combined.CGC.next_grade, combined.CGC.next_label, combined.CGC.borderline, combined.CGC.unconstrained, combined.CGC.source_status, combined.CGC.standard_status, combined.CGC.source, combined.CGC.sources, combined.CGC.verified, combined.CGC.verification, combined.SGC, combined.SGC.maximum_grade, combined.SGC.label, combined.SGC.rank, combined.SGC.limiting_face, combined.SGC.limiting_axis, combined.SGC.distance_to_next, combined.SGC.next_grade, combined.SGC.next_label, combined.SGC.borderline, combined.SGC.unconstrained, combined.SGC.source_status, combined.SGC.standard_status, combined.SGC.source, combined.SGC.sources, combined.SGC.verified, combined.SGC.verification, normalizations, normalizations[].field, normalizations[].input, normalizations[].ratio, normalizations[].note, incomplete, missing_faces, summary, disclaimer

Champs du contrat (noms JSON exacts)

input: maximum_grade, label, rank, limiting_face, limiting_axis, distance_to_next, next_grade, next_label, borderline, unconstrained, source_status, standard_status, source, sources, verified, verification
output: maximum_grade, label, rank, limiting_face, limiting_axis, distance_to_next, next_grade, next_label, borderline, unconstrained, source_status, standard_status, source, sources, verified, verification

Mesure photographique

POST /measure exige face : front ou back. card_size vaut par défaut standard (63×88 mm) ; small correspond à 59×86 mm. Une seule source : image_url HTTPS publique sur le port 443 ou image_base64 brut sans préfixe data-URL. Adresses privées, identifiants et réponses non-image sont refusés, redirections comprises. REST accepte aussi un élément openaiFileIdRefs fourni par le runtime GPT Actions ; utilise URL ou base64 pour les intégrations ordinaires. Envoie un raster à une seule image, de préférence JPG, PNG ou WebP.

HTTP 200 peut contenir status ok, needs-review ou failed. Seul ok expose ceilings. Vérifie code, confidence, warnings, lr et tb. uncertainty_pp et sigma_pp sont en points de pourcentage ; stations_major_percent contient les échantillons. En cas d’incertitude, reprends une photo ou contrôle adjust_url. L’exemple envoie délibérément une petite image unie : NO_CARD est un véritable échec, pas une mesure réussie.

Champs du contrat (noms JSON exacts)

input: face, card_size, image_url, image_base64, openaiFileIdRefs, openaiFileIdRefs[].name, openaiFileIdRefs[].id, openaiFileIdRefs[].mime_type, openaiFileIdRefs[].download_link
output: status, code, face, card_size, lr, lr.ratio, lr.major_percent, lr.minor_percent, lr.uncertainty_pp, lr.sigma_pp, lr.worst_station_px, lr.stations_major_percent, tb, tb.ratio, tb.major_percent, tb.minor_percent, tb.uncertainty_pp, tb.sigma_pp, tb.worst_station_px, tb.stations_major_percent, confidence, warnings, ceilings, ceilings.PSA, ceilings.PSA.maximum_grade, ceilings.PSA.label, ceilings.PSA.rank, ceilings.PSA.limiting_face, ceilings.PSA.limiting_axis, ceilings.PSA.distance_to_next, ceilings.PSA.next_grade, ceilings.PSA.next_label, ceilings.PSA.borderline, ceilings.PSA.unconstrained, ceilings.PSA.source_status, ceilings.PSA.standard_status, ceilings.PSA.source, ceilings.PSA.sources, ceilings.PSA.verified, ceilings.PSA.verification, ceilings.BGS, ceilings.BGS.maximum_grade, ceilings.BGS.label, ceilings.BGS.rank, ceilings.BGS.limiting_face, ceilings.BGS.limiting_axis, ceilings.BGS.distance_to_next, ceilings.BGS.next_grade, ceilings.BGS.next_label, ceilings.BGS.borderline, ceilings.BGS.unconstrained, ceilings.BGS.source_status, ceilings.BGS.standard_status, ceilings.BGS.source, ceilings.BGS.sources, ceilings.BGS.verified, ceilings.BGS.verification, ceilings.CGC, ceilings.CGC.maximum_grade, ceilings.CGC.label, ceilings.CGC.rank, ceilings.CGC.limiting_face, ceilings.CGC.limiting_axis, ceilings.CGC.distance_to_next, ceilings.CGC.next_grade, ceilings.CGC.next_label, ceilings.CGC.borderline, ceilings.CGC.unconstrained, ceilings.CGC.source_status, ceilings.CGC.standard_status, ceilings.CGC.source, ceilings.CGC.sources, ceilings.CGC.verified, ceilings.CGC.verification, ceilings.SGC, ceilings.SGC.maximum_grade, ceilings.SGC.label, ceilings.SGC.rank, ceilings.SGC.limiting_face, ceilings.SGC.limiting_axis, ceilings.SGC.distance_to_next, ceilings.SGC.next_grade, ceilings.SGC.next_label, ceilings.SGC.borderline, ceilings.SGC.unconstrained, ceilings.SGC.source_status, ceilings.SGC.standard_status, ceilings.SGC.source, ceilings.SGC.sources, ceilings.SGC.verified, ceilings.SGC.verification, adjust_url, advice, elapsed_ms, image_bytes, summary, disclaimer

POST /api/v1/measure

Requête

curl --fail-with-body 'https://centeringlab.com/api/v1/measure' \
  -H 'Content-Type: application/json' \
  --data '{"face":"front","card_size":"standard","image_base64":"iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAACXBIWXMAAAPoAAAD6AG1e1JrAAAAD0lEQVQImWNQwAEYhpYEAGFPGAHjXOFDAAAAAElFTkSuQmCC"}'

Réponse HTTP 200

Extrait d’une vraie réponse HTTP locale ; des champs et bandes supplémentaires sont omis. Les clés JSON et messages du serveur restent en anglais.

{
  "status": "failed",
  "code": "NO_CARD",
  "face": "front",
  "card_size": "standard",
  "lr": null,
  "tb": null,
  "confidence": null,
  "warnings": [],
  "ceilings": null,
  "adjust_url": "https://centeringlab.com/"
}

Enregistre ta photo comme card.jpg. Cette réponse utilise la photo de démonstration du site : l’incertitude entraîne needs-review, donc ceilings vaut null. Ta photo donnera ses propres mesures.

POST /api/v1/measure

Requête

python3 - <<'PY' > measure.json
import base64, json
from pathlib import Path
print(json.dumps({
    'face': 'front', 'card_size': 'standard',
    'image_base64': base64.b64encode(Path('card.jpg').read_bytes()).decode('ascii'),
}))
PY
curl --fail-with-body 'https://centeringlab.com/api/v1/measure' \
  -H 'Content-Type: application/json' \
  --data-binary @measure.json

Réponse HTTP 200

Extrait d’une vraie réponse HTTP locale ; des champs et bandes supplémentaires sont omis. Les clés JSON et messages du serveur restent en anglais.

{
  "status": "needs-review",
  "code": "HIGH_UNCERTAINTY",
  "face": "front",
  "card_size": "standard",
  "lr": {
    "ratio": "56/44",
    "major_percent": 55.54242922739353,
    "minor_percent": 44.45757077260647,
    "uncertainty_pp": 5.1,
    "sigma_pp": 2.3393336872317274,
    "worst_station_px": 350.163778228702,
    "stations_major_percent": [
      55.54242922739353,
      54.38358480163707,
      53.25627521942576
    ]
  },
  "tb": {
    "ratio": "52/48",
    "major_percent": 52.059885599520676,
    "minor_percent": 47.940114400479324,
    "uncertainty_pp": 6.4,
    "sigma_pp": 2.9286347555409855,
    "worst_station_px": 250.03684789689908,
    "stations_major_percent": [
      52.059885599520676,
      51.766308753028554,
      51.468031630625774
    ]
  },
  "confidence": 0.8603421926529179,
  "warnings": [
    "GET_CLOSER"
  ],
  "ceilings": null,
  "adjust_url": "https://centeringlab.com/"
}

Normes et sources

GET /standards accepte house et face facultatifs. official, interpreted et approximate décrivent les preuves, sans garantir la précision. bands[].status concerne chaque bande ; source, sources, verified et verification donnent la provenance. TAG est exclu. SGC ne publie pas de tableau de centrage du verso : les bandes sont vides et le verso seul ne détermine pas de plafond SGC.

Champs du contrat (noms JSON exacts)

input: house, face
output: standards, standards[].house, standards[].face, standards[].bands, standards[].bands[].value, standards[].bands[].label, standards[].bands[].rank, standards[].bands[].loose, standards[].bands[].tight, standards[].bands[].status, standards[].source, standards[].sources, standards[].status, standards[].verified, standards[].verification, standards[].note, rounding_rule, tag_note, summary, disclaimer

GET /api/v1/standards?house=PSA&face=front

Requête

curl --fail-with-body 'https://centeringlab.com/api/v1/standards?house=PSA&face=front'

Réponse HTTP 200

Extrait d’une vraie réponse HTTP locale ; des champs et bandes supplémentaires sont omis. Les clés JSON et messages du serveur restent en anglais.

{
  "standards": [
    {
      "house": "PSA",
      "face": "front",
      "bands": [
        {
          "value": 10,
          "label": "GEM MT",
          "rank": 100,
          "loose": 55,
          "status": "official"
        }
      ],
      "status": "official",
      "source": "https://www.psacard.com/gradingstandards",
      "note": "Published percentage thresholds. Only the listed centering bands are used; no missing grades are invented."
    }
  ],
  "tag_note": "TAG is excluded because its centering assessment is proprietary and is not represented by these public percentage bands."
}

État et schéma

GET /health renvoie état, nom, version et disclaimer. GET /openapi.json renvoie OpenAPI 3.1.0. Le schéma de mesure vise GPT Actions (URL ou référence de fichier runtime) ; REST accepte aussi le base64 selon les champs ci-dessus.

GET /api/v1/health

Requête

curl --fail-with-body 'https://centeringlab.com/api/v1/health'

Réponse HTTP 200

Extrait d’une vraie réponse HTTP locale ; des champs et bandes supplémentaires sont omis. Les clés JSON et messages du serveur restent en anglais.

{
  "status": "ok",
  "name": "centering-lab",
  "version": "1.1.2",
  "disclaimer": "This is a ceiling from centering alone, not a prediction of the final grade."
}

GET /api/v1/openapi.json

Requête

curl --fail-with-body 'https://centeringlab.com/api/v1/openapi.json'

Réponse HTTP 200

Extrait d’une vraie réponse HTTP locale ; des champs et bandes supplémentaires sont omis. Les clés JSON et messages du serveur restent en anglais.

{
  "openapi": "3.1.0",
  "info": {
    "title": "Centering Lab API",
    "version": "1.1.2"
  },
  "servers": [
    {
      "url": "https://centeringlab.com"
    }
  ]
}

JavaScript et Python

Utilise fetch de Node côté serveur. Les exemples envoient la même requête initiale. Python utilise sa bibliothèque standard. Vérifie les erreurs HTTP et, pour une mesure, status même avec HTTP 200.

JavaScript · Node

const response = await fetch('https://centeringlab.com/api/v1/grade', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({"front_lr":0.55,"front_tb":"50/50","house":"PSA"}),
});
const result = await response.json();
if (!response.ok) throw new Error(result.code + ': ' + result.message);
console.log(result);

Python · urllib

import json
from urllib.request import Request, urlopen
from urllib.error import HTTPError

request = Request(
    'https://centeringlab.com/api/v1/grade',
    data=json.dumps({"front_lr":0.55,"front_tb":"50/50","house":"PSA"}).encode('utf-8'),
    headers={'Content-Type': 'application/json'},
    method='POST',
)
try:
    with urlopen(request, timeout=20) as response:
        print(json.load(response))
except HTTPError as error:
    print(error.code, json.load(error))
    raise

Erreurs et nouvelles tentatives

Les erreurs HTTP contiennent status, code, message et disclaimer. Le tableau concerne REST, sans erreurs de protocole MCP ni statistiques privées. UNSUPPORTED_ENCODING vaut 415 pour du JSON compressé, 400 pour une réponse image compressée. Les codes du détecteur arrivent avec HTTP 200.

CodeHTTPSignificationQue faire
BODY_TOO_LARGE413Taille dépasséeRéduis les octets ou pixels ; le base64 compte dans le corps JSON.
EMPTY_IMAGE400Image non décodableEnvoie une seule image raster prise en charge, de préférence JPG, PNG ou WebP ; retire le préfixe data-URL.
IMAGE_DNS_FAILED400URL d’image refusée ou inaccessibleUtilise une image HTTPS publique sur le port 443 sans identifiants ; vérifie DNS, redirections et réponse.
IMAGE_DOWNLOAD_FAILED400URL d’image refusée ou inaccessibleUtilise une image HTTPS publique sur le port 443 sans identifiants ; vérifie DNS, redirections et réponse.
IMAGE_DOWNLOAD_TIMEOUT504Délai dépasséUtilise une image plus petite ou un hébergeur plus rapide ; sinon utilise le site.
IMAGE_REDIRECT_LIMIT400URL d’image refusée ou inaccessibleUtilise une image HTTPS publique sur le port 443 sans identifiants ; vérifie DNS, redirections et réponse.
IMAGE_TOO_LARGE413Taille dépasséeRéduis les octets ou pixels ; le base64 compte dans le corps JSON.
IMAGE_TOO_MANY_PIXELS413Taille dépasséeRéduis les octets ou pixels ; le base64 compte dans le corps JSON.
INTERNAL_ERROR500Erreur serveur ou workerRéessaie après un délai, réduis la photo ou utilise le site.
INVALID_ARGUMENTS400Requête ou ratio invalideCorrige les champs ; envoie du JSON valide non compressé.
INVALID_BASE64400Image non décodableEnvoie une seule image raster prise en charge, de préférence JPG, PNG ou WebP ; retire le préfixe data-URL.
INVALID_BODY400Requête ou ratio invalideCorrige les champs ; envoie du JSON valide non compressé.
INVALID_HOST403Hôte ou origine refuséAppelle centeringlab.com depuis ton serveur, pas depuis le navigateur d’un autre site.
INVALID_HTTP400Requête ou ratio invalideCorrige les champs ; envoie du JSON valide non compressé.
INVALID_IMAGE400Image non décodableEnvoie une seule image raster prise en charge, de préférence JPG, PNG ou WebP ; retire le préfixe data-URL.
INVALID_IMAGE_URL400URL d’image refusée ou inaccessibleUtilise une image HTTPS publique sur le port 443 sans identifiants ; vérifie DNS, redirections et réponse.
INVALID_INPUT400Requête ou ratio invalideCorrige les champs ; envoie du JSON valide non compressé.
INVALID_JSON400Requête ou ratio invalideCorrige les champs ; envoie du JSON valide non compressé.
INVALID_ORIGIN403Hôte ou origine refuséAppelle centeringlab.com depuis ton serveur, pas depuis le navigateur d’un autre site.
INVALID_RATIO400Requête ou ratio invalideCorrige les champs ; envoie du JSON valide non compressé.
JSON_REQUIRED415Requête ou ratio invalideCorrige les champs ; envoie du JSON valide non compressé.
MEASURE_QUEUE_FULL503Service saturéRespecte Retry-After et espace les nouvelles tentatives.
MEASURE_TIMEOUT504Délai dépasséUtilise une image plus petite ou un hébergeur plus rapide ; sinon utilise le site.
MEASURE_WORKER_FAILED500Erreur serveur ou workerRéessaie après un délai, réduis la photo ou utilise le site.
METHOD_NOT_ALLOWED405Route ou méthode indisponibleUtilise les endpoints et méthodes documentés.
NOT_AN_IMAGE400URL d’image refusée ou inaccessibleUtilise une image HTTPS publique sur le port 443 sans identifiants ; vérifie DNS, redirections et réponse.
NOT_FOUND404Route ou méthode indisponibleUtilise les endpoints et méthodes documentés.
RATE_LIMIT_CAPACITY503Service saturéRespecte Retry-After et espace les nouvelles tentatives.
RATE_LIMIT_DAY429Quota atteintRespecte Retry-After en secondes ; le quota quotidien repart à minuit UTC.
RATE_LIMIT_MINUTE429Quota atteintRespecte Retry-After en secondes ; le quota quotidien repart à minuit UTC.
REQUEST_CANCELLED499Requête annuléeRéessaie seulement si le client en a encore besoin.
REQUEST_CAPACITY503Service saturéRespecte Retry-After et espace les nouvelles tentatives.
UNSAFE_IMAGE_URL400URL d’image refusée ou inaccessibleUtilise une image HTTPS publique sur le port 443 sans identifiants ; vérifie DNS, redirections et réponse.
UNSUPPORTED_ENCODING400 / 415Requête ou ratio invalideCorrige les champs ; envoie du JSON valide non compressé.
UNSUPPORTED_IMAGE400Image non décodableEnvoie une seule image raster prise en charge, de préférence JPG, PNG ou WebP ; retire le préfixe data-URL.

Codes du détecteur : HIGH_UNCERTAINTY, NEEDS_REVIEW, LOW_CONFIDENCE, NO_MEASUREMENT, NO_CARD, CARD_TOO_SMALL, CARD_CUT_OFF, NO_PRINTED_BORDER. failed et needs-review ne donnent aucun plafond. Reprends la carte entière de dessus, sans pochette, sur une surface sombre plane, ou utilise l’éditeur manuel du site.

Limites d’utilisation

Quotas par IP, partagés dans les groupes correspondants. Grade et standards partagent un compteur à la minute ; health et OpenAPI un autre. Minutes fixes et journées UTC ; pas de quota quotidien pour les appels simples/protocole. Le base64 augmente les octets et compte dans le JSON : la limite image complète peut ne pas tenir.

LimiteValeur actuelle
POST /measure · IP20 par minute; 200 par jour UTC
POST /grade + GET /standards · IP120 par minute; Pas de quota quotidien dans le limiteur
GET /health + GET /openapi.json · IP120 par minute; Pas de quota quotidien dans le limiteur
Corps JSON12 MiB (12582912 bytes)
Octets de l’image décodée15 MiB (15728640 bytes)
Pixels décodés (toutes les images)40000000
Mesure, file d’attente comprise15 s
Téléchargement de l’image10 s
Mesures actives2
Mesures en attente8
Requêtes HTTP en cours16

429 et 503 de capacité incluent Retry-After en secondes. File de mesure pleine et REQUEST_CAPACITY : 5 secondes ; capacité du limiteur : 60. Espace les nouvelles tentatives. Capacité partagée, sans réservation par client. Aucun SLA de disponibilité ou de délai promis.

Navigateur, CORS et confidentialité

Un autre site reçoit 403 INVALID_ORIGIN depuis son navigateur : utilise ton backend. Aucune clé nécessaire ; en ajouter une n’ouvre pas CORS. Les images API atteignent le serveur, sont traitées uniquement en mémoire et ne sont pas enregistrées sur disque. Le site traite les photos sur l’appareil. Aucun journal par requête : seulement des comptes agrégés par endpoint/tool et code de résultat. Confidentialité et conditions.

MCP et ressources

https://centeringlab.com/mcp propose aux clients AI les trois mêmes outils et le même moteur. REST offre des appels HTTP directs. Consulte AI et MCP, la méthode de mesure et à propos.

Questions sur l’API

L’API de centrage des cartes est-elle gratuite pour les développeurs ?

Oui. Tous les endpoints sont gratuits, sans compte ni formule payante. Les limites s’appliquent ; aucune authentification ni prédiction de note finale.

Les intégrations REST nécessitent-elles une clé API ?

Non. Envoie des requêtes valides depuis ton serveur ; la restriction d’origine du navigateur reste active.

Quels quotas s’appliquent à cette API de centrage ?

Mesure : 20 par minute et 200 par jour UTC par IP. Grade et standards partagent 120 par minute par IP. Respecte Retry-After et les limites de taille et capacité.

L’API REST conserve-t-elle les photographies envoyées ?

Non. Elles atteignent le serveur mais sont traitées seulement en mémoire. Aucun journal par requête, seulement des comptes agrégés et codes. Le site traite les photos sur l’appareil.

Un autre site peut-il appeler directement depuis son navigateur ?

Non. Il reçoit 403 INVALID_ORIGIN. Appelle depuis ton backend, sans clé API.

Quelle différence entre cette API REST et MCP ?

REST utilise des endpoints HTTP ; MCP offre trois outils aux clients AI. Même moteur et mêmes normes. API/MCP traitent les images sur le serveur, le site localement.