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 un ETag portant sa version. Renvoyez-le en If-Match sur un PATCH et l'écriture est refusée si le passeport a changé entre-temps : c'est le seul moyen de sécuriser un lire-modifier-écrire face à un éditeur concurrent.

Requête
curl "https://passportcraft.com/api/v1/organizations/{organization}/passports/{id}" \
  -X PATCH \
  -H "Authorization: Bearer $PASSPORTCRAFT_API_KEY" \
  -H "If-Match: \"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 comptabilisée porte des en-têtes RateLimit et RateLimit-Policy nommant le compteur le plus proche de sa limite. Un refus ajoute Retry-After : 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.

Identifiants de requête

Chaque réponse porte un en-tête Request-Id, en cas de succès comme en cas d'échec. Citez-le dans une demande d'assistance et l'appel exact pourra être retrouvé.

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.

Conventions — API PassportCraft | PassportCraft