Aller au contenu

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.

Requête
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.

Réponse
{
  "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é.

Requête
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.

CompteurRequêtesFenêtre
key_org100060s
organization300060s
credential1000060s
destructive503600s

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.

Une requête qui arrive sans clé exploitable est mesurée par un compteur distinct, bien plus étroit : 60 requêtes par minute et par adresse cliente. Il ne s'applique jamais à une clé valide.

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 terminaisonPlafond
Tout point de terminaison qui écrit un seul enregistrement256 KiB (262144 bytes)
Import en masse et lots d'unités4 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.

Réponse
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.

Requête
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. L'essai de référence de durée de vie en cycles porte `required_when` pour `battery_category` avec `ev` et `lmt` : le règlement sur les batteries inclut les données du passeport « dans la mesure applicable à la catégorie ou la sous-catégorie de batterie concernée », et les orientations de la Commission sur le passeport de batterie associent chaque point de données aux catégories auxquelles il s'applique. Une catégorie peut aussi réclamer un champ par une règle qui lit plusieurs champs à la fois : `required_when` ne les décrit pas, et c'est le point de terminaison de validation qui les résout.

Lisez les deux ensemble et traitez `required` comme la réponse la plus large : un client qui ignore `required_when` 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.

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.
  • 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.

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.