Conventions
Conventions
Les règles valables sur tous les points de terminaison, énoncées une fois ici plutôt que répétées vingt-cinq fois.
Idempotence
Les points de terminaison qui modifient quelque chose acceptent un en-tête Idempotency-Key. Répéter une requête avec la même clé rejoue la réponse d'origine au lieu de reproduire l'effet : une reprise après expiration ne peut donc pas créer un second passeport. Les clés appartiennent à une organisation, si bien que deux clients n'entrent jamais en collision sur la même valeur. Le test et le direct restent séparés : une clé déjà utilisée dans un mode est refusée dans l'autre plutôt que rejouée. Une exception, qui relève d'une capacité et non d'un fait : l'URL de téléversement signée renvoyée par `POST /documents` est réémise lors d'une reprise tant que le fichier n'est pas arrivé, et omise une fois qu'il l'est — rejouer une URL expirée ne restituerait rien d'utilisable.
Une clé est mémorisée pendant 30 jours. Passé ce délai elle est entièrement oubliée, et une reprise qui la porte s'exécute de nouveau au lieu d'être rejouée — un travail de rapprochement susceptible de s'exécuter plus tard doit donc créer une clé neuve.
curl "https://passportcraft.com/api/v1/organizations/{organization}/passports" \
-X POST \
-H "Authorization: Bearer $PASSPORTCRAFT_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"category":"textile"}'Pagination
Les collections se paginent par curseur, jamais par décalage. Un décalage est faux dès qu'il y a des écritures concurrentes : un enregistrement inséré entre deux pages décale tous les suivants, et un client saute ou duplique des lignes sans s'en apercevoir.
Renvoyez next_cursor comme cursor pour obtenir la page suivante. Un curseur encode les filtres sous lesquels il a été émis ; l'envoyer avec d'autres filtres est refusé plutôt que de retourner une page qui signifierait discrètement autre chose.
{
"object": "list",
"data": [],
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0wOC0wMVQxNDowMzoyMi41MDFaIn0"
}Écritures conditionnelles
La lecture d'un passeport renvoie sa version, dans le corps de la réponse et dans un ETag. Prenez la version dans le corps de la réponse plutôt que d'analyser l'ETag : notre CDN peut affaiblir l'ETag lorsqu'il compresse, si bien qu'en retirant les guillemets un préfixe W/ peut subsister. Sur un PATCH, renvoyez cette version dans Expected-Version : l'écriture est refusée si le passeport a changé entre-temps, ce qui est le seul moyen de sécuriser un lire-modifier-écrire face à un éditeur concurrent. N'utilisez pas If-Match. Les deux points de terminaison qui le lisent — mettre à jour un passeport et détacher un document — refusent toute valeur autre que le caractère générique *, car notre CDN compare l'en-tête à la réponse et en réécrit le résultat : l'écriture serait appliquée tout en vous étant signalée comme un refus. Ailleurs nous l'ignorons, mais ce n'est peut-être pas le cas de notre CDN : nous l'avons vu remplacer un succès par un 412 après notre réponse. Le plus sûr est donc de ne pas envoyer l'en-tête du tout. Un PATCH réussi ne renvoie pas d'ETag ; la nouvelle version se trouve dans le corps de la réponse, dans le champ version. Réessayez une écriture refusée avec une NOUVELLE Idempotency-Key : le refus est enregistré sous l'ancienne et serait rejoué.
curl "https://passportcraft.com/api/v1/organizations/{organization}/passports/{id}" \
-X PATCH \
-H "Authorization: Bearer $PASSPORTCRAFT_API_KEY" \
-H "Expected-Version: 4" \
-H "Content-Type: application/json" \
-d '{"data":{"recycled_content_percentage":38}}'Limites de débit
Trois compteurs tournent en parallèle sur chaque requête authentifiée et le plus étroit tranche. Les valeurs publiées sont des points de départ délibérément prudents, non des plafonds optimisés.
| Compteur | Requêtes | Fenêtre |
|---|---|---|
| key_org | 1000 | 60s |
| organization | 3000 | 60s |
| credential | 10000 | 60s |
| destructive | 50 | 3600s |
Chaque réponse authentifiée comptabilisée comporte les en-têtes RateLimit et RateLimit-Policy, qui nomment le compteur le plus proche de sa limite. Un refus ajoute Retry-After, sur tous les chemins. Attendez ce délai plutôt que de deviner.
Taille des requêtes
Un corps de requête au-delà du plafond de son point de terminaison est refusé avec payload_too_large avant que quoi que ce soit ne soit lu vers la base. Le plafond est vérifié deux fois : d'abord sur le Content-Length déclaré, ce qui ne coûte rien, puis sur les octets réellement reçus — une requête qui ne déclare aucune longueur est donc bornée elle aussi.
| Points de terminaison | Plafond |
|---|---|
| Tout point de terminaison qui écrit un seul enregistrement | 256 KiB (262144 bytes) |
| Import en masse et lots d'unités | 4 MiB (4194304 bytes) |
Le plafond des lots est calé sur le plus gros lot que cette API accepte, et non sur un chiffre rond : 500 lignes d'import dont chaque champ d'une catégorie est renseigné, ou 1000 unités sérialisées. Ni l'un ni l'autre ne tient dans le plafond standard, et c'est précisément pourquoi ces deux points de terminaison en ont un à eux.
Un corps doit arriver en application/json ; application/merge-patch+json est également accepté. Tout autre type est refusé avec unsupported_media_type plutôt que d'échouer dans l'analyseur — le cas à connaître est curl -d, qui envoie application/x-www-form-urlencoded sauf indication contraire. Un corps envoyé sans aucun Content-Type est lu comme du JSON.
Méthodes HTTP
Un point de terminaison ne répond qu'aux méthodes qu'il documente. Toute autre méthode est refusée avec method_not_allowed et un en-tête Allow qui nomme celles qui fonctionnent : un client qui se trompe de verbe apprend le bon dans le refus lui-même. HEAD fonctionne partout où GET fonctionne, et OPTIONS fonctionne partout.
HTTP/1.1 405 Method Not Allowed
Allow: GET, HEAD, PATCH, OPTIONS
Content-Type: application/json
{
"error": {
"code": "method_not_allowed",
"documentation_url": "https://passportcraft.com/docs/api/errors#method_not_allowed",
"request_id": "req_9Fv2KpQ0aXbT4Lmn",
"details": { "method": "DELETE", "allowed": ["GET", "HEAD", "PATCH", "OPTIONS"] }
}
}Opérations destructrices
Dépublier un passeport et en mettre un à la corbeille retirent tous deux du marché un enregistrement publié : ils tirent donc sur un plafond distinct, en plus de la limite de débit ordinaire. Une boucle de requêtes bien formées et peu coûteuses ne doit pas pouvoir éteindre un catalogue entier.
Joindre un document à un champ
Certains champs de type url acceptent un fichier téléversé à la place d'un lien — un rapport d'essai ou une déclaration de conformité, par exemple. Téléversez d'abord le fichier, puis faites pointer le champ vers lui en écrivant l'identifiant du document dans la clé associée à ce champ, dans `data`. Le champ lui-même peut rester vide : un champ acceptant les documents est considéré comme renseigné dès qu'un document y est joint, si bien qu'un passeport dont l'url est vide reste validable et publiable.
Vous n'avez jamais à composer cette clé vous-même. Un champ qui accepte les documents porte deux attributs dans le schéma de catégorie : `document_types`, qui énumère les types admis, et `document_id_key`, qui nomme la clé exacte à écrire. Lisez la clé dans le schéma plutôt que de vous fier à la règle de nommage : c'est la seule méthode prise en charge. La clé associée est une propriété du champ et non un champ à part entière ; elle n'apparaît donc jamais dans la liste `fields` du schéma.
Une règle que le schéma ne détaille pas : l'`access_tier` du document doit correspondre à celui du champ. Un document public ne peut pas satisfaire un champ de niveau « authority » ; la liaison est refusée avec `access_tier_mismatch`, car un document dont le niveau diffère de celui du champ ne serait pas affiché là où le champ l'est. Créez le document avec l'`access_tier` indiqué par le descripteur du champ.
curl "https://passportcraft.com/api/v1/organizations/{organization}/passports/{id}" \
-X PATCH \
-H "Authorization: Bearer $PASSPORTCRAFT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"data":{"conformity_declaration_url__document_id":"doc_123"}}'Videz la clé associée pour détacher le document ; le fichier reste dans votre bibliothèque jusqu'à ce que vous le supprimiez. La suppression d'un document vers lequel un champ pointe encore indique ce qui a été détaché, et cette information ne parvient qu'à l'appelant qui a émis la requête.
L'`upload_state` d'un document vaut `ready`, `pending` ou `unknown`. `unknown` signifie que nous n'avons pas pu établir si les octets sont dans le stockage : soit le stockage n'a pas répondu, soit la ligne se situait au-delà des 25 documents sans octets qu'une requête vérifie. Considérez-le comme indéterminé, non comme absent : à la publication, `unknown` donne `storage_unavailable` plutôt qu'un fichier déclaré manquant, et une lecture ultérieure tranche généralement.
Champs qui ne s'appliquent qu'à certains produits
L'indicateur `required` d'un champ signifie que le contrôle de publication peut exiger ce champ. Lorsque cette exigence dépend du produit lui-même, le champ porte aussi `required_when`, qui nomme un autre champ et les valeurs qui déclenchent l'exigence. Le seuil de capacité d'épuisement d'une batterie porte `required_when` pour `battery_category` avec `ev`, car le règlement sur les batteries ne le demande que pour les batteries de véhicules électriques. Une catégorie peut aussi exiger un champ par une règle qui lit plusieurs champs à la fois. `required_when` ne décrit pas ces règles, et le point de terminaison de validation les résout.
Un champ peut aussi porter `required_unless`, une liste de réponses qui désactivent l'exigence. Chaque entrée nomme un champ et les valeurs de `one_of`. Une entrée avec `only_when` ne s'applique que si cet autre champ contient l'une de ses valeurs. Une réponse vide ne désactive jamais une exigence. Par exemple, l'essai de référence de durée de vie en cycles d'une batterie n'est pas exigé lorsque `battery_designed_use` vaut `backup_only` et que `battery_category` vaut `industrial_stationary` ou `industrial_other`.
Lisez `required` avec `required_when` et `required_unless`, et traitez `required` comme la réponse la plus large : un client qui ignore les deux autres clés demande tout au plus davantage que nécessaire. Il ne considère jamais un passeport comme prêt pour se voir refuser la publication. Pour un passeport précis, la réponse exacte vient du point de terminaison de validation, qui dispose de ce passeport.
Champs obligatoires à partir d’une date
Un champ doté de `required_from` devient obligatoire pour la publication à cette date, selon l’heure de Bruxelles ; avant cette date, il est facultatif. Nous publions cette date dès que possible, normalement au moins 60 jours à l’avance ; lorsqu’un règlement fixe ou modifie une date avec un préavis plus court, le champ suit la date prévue par le règlement. Lisez `required` avec `required_when` et `required_from` ; le point de terminaison de validation donne la réponse pour un passeport précis. Une règle qui lit plusieurs champs peut aussi entrer en vigueur à une date. Avant cette date, le point de terminaison de validation la signale comme un avertissement, et son message indique la date.
Relevés de batterie
Chaque relevé indique une valeur, sa source et la date à laquelle elle a été mesurée ou déclarée. Pour corriger une valeur, enregistrez un autre relevé. La date d’observation la plus récente prévaut ; à date égale, la dernière saisie prévaut.
per_battery: override
Une batterie utilise la valeur de son modèle tant qu’elle n’a pas son propre relevé pour ce champ. Une valeur de modèle n’a pas de date de mesure.
Valeurs initiales pour les batteries individuelles (facultatif)
model_health_declaration- Tant que la déclaration s’applique, chaque valeur d’état de santé provient du modèle jusqu’à ce que vous enregistriez cette valeur pour la batterie.
- Les valeurs manquantes du modèle restent vides. Avec cette déclaration, l’état de l’énergie certifiée commence à 100 %.
- L’enregistrement d’une valeur remplace la déclaration du modèle uniquement pour ce champ.
- Le premier statut enregistré maintient la déclaration. Si vous enregistrez à nouveau un statut, la déclaration cesse de fournir des valeurs d’état de santé pour cette batterie. Cela s’applique même si vous choisissez le même statut. Un statut saisi par erreur ne compte pas si vous le corrigez dans la fenêtre de confirmation pour rendre à la batterie le statut qu’elle avait avant l’erreur. Une correction vers un autre statut revient à enregistrer ce statut à la place du statut saisi par erreur.
- Les valeurs déjà enregistrées pour cette batterie sont conservées.
Les valeurs obligatoires manquantes d’une batterie figurent dans missing_fields. Elles n’empêchent ni la création de la batterie ni la publication de son modèle.
Dans l’application, un éditeur peut corriger un statut saisi par erreur. La saisie erronée et la correction restent toutes deux dans l’historique.
Identifiants de requête
Chaque réponse authentifiée comporte un en-tête Request-Id, en cas de succès comme d'échec. Indiquez-le dans une demande d'assistance et l'appel exact pourra être retrouvé. Les deux points de terminaison de catégories répondent aux requêtes réussies sans clé depuis un cache partagé ; ces réponses ne comportent pas de Request-Id — un refus en comporte toujours un, sur tous les chemins.
Le flux d'événements conserve 30 jours d'historique. Une intégration qui interroge moins souvent manquera des changements et devrait plutôt se rapprocher de la liste des passeports.