Vai al contenuto

Convenzioni

Convenzioni

Le regole valide su ogni endpoint, enunciate una volta qui invece di essere ripetute venticinque volte.

Idempotenza

Gli endpoint che modificano qualcosa accettano un'intestazione Idempotency-Key. Ripetere una richiesta con la stessa chiave riproduce la risposta originale invece di ripetere l'effetto, perciò un nuovo tentativo dopo un timeout non può creare un secondo passaporto. Le chiavi appartengono a una sola organizzazione, così due clienti non collidono mai sullo stesso valore. Prova e produzione restano separate: una chiave già usata in un modo viene rifiutata nell'altro anziché riprodotta. Un'eccezione, che è una capacità e non un fatto: l'URL di caricamento firmato restituito da `POST /documents` viene riemesso alla ripetizione finché il file non è arrivato, e omesso una volta arrivato — riprodurre un URL scaduto non restituirebbe nulla di utilizzabile.

Una chiave resta in memoria per 30 giorni. Dopo viene dimenticata del tutto, e un nuovo tentativo che la porta viene eseguito di nuovo invece di essere riprodotto: un processo di riconciliazione che potrebbe partire più tardi dovrebbe quindi generare una chiave nuova.

Richiesta
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"}'

Impaginazione

Le raccolte si impaginano a cursore, mai a scostamento. Uno scostamento è sbagliato in presenza di scritture concorrenti: un record inserito fra due pagine sposta tutti quelli successivi, e un client salta o duplica righe senza accorgersene.

Rimandi next_cursor come cursor per ottenere la pagina successiva. Un cursore codifica i filtri con cui è stato emesso, perciò inviarlo con filtri diversi viene rifiutato invece di restituire una pagina che in silenzio significherebbe altro.

Risposta
{
  "object": "list",
  "data": [],
  "has_more": true,
  "next_cursor": "eyJjIjoiMjAyNi0wOC0wMVQxNDowMzoyMi41MDFaIn0"
}

Scritture condizionali

La lettura di un passaporto restituisce la sua versione, nel corpo della risposta e come ETag. Prenda la versione dal corpo della risposta invece di analizzare l'ETag: la nostra CDN può indebolire l'ETag quando comprime, quindi togliendo le virgolette può restare un prefisso W/. In un PATCH, rimandi quella versione in Expected-Version: la scrittura viene rifiutata se nel frattempo il passaporto è cambiato, ed è l'unico modo per rendere sicura una sequenza leggi-modifica-scrivi di fronte a un editor concorrente. Non usi If-Match. I due endpoint che lo leggono — aggiornare un passaporto e staccare un documento — rifiutano qualsiasi valore diverso dal carattere jolly *, perché la nostra CDN confronta l'intestazione con la risposta e ne riscrive il risultato, quindi la scrittura verrebbe applicata e le verrebbe comunque segnalata come un rifiuto. Altrove lo ignoriamo, ma la nostra CDN forse no: l'abbiamo vista sostituire un successo con un 412 dopo la nostra risposta, quindi la cosa più sicura è non inviare affatto l'intestazione. Un PATCH riuscito non restituisce alcun ETag; la nuova versione si trova nel corpo della risposta, nel campo version. Ripeta una scrittura rifiutata con una NUOVA Idempotency-Key: il rifiuto resta registrato sulla precedente e verrebbe riprodotto.

Richiesta
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}}'

Limiti di frequenza

Su ogni richiesta autenticata girano tre contatori insieme e decide il più stretto. I valori pubblicati sono punti di partenza deliberatamente prudenti, non soglie ottimizzate.

ContatoreRichiesteFinestra
key_org100060s
organization300060s
credential1000060s
destructive503600s

Ogni risposta autenticata conteggiata include le intestazioni RateLimit e RateLimit-Policy, che indicano il contatore più vicino al proprio limite. Un rifiuto aggiunge Retry-After, su ogni percorso. Attenda tale intervallo anziché ipotizzarlo.

Una richiesta che arriva senza una chiave utilizzabile viene misurata da un contatore a sé, molto più stretto: 60 richieste al minuto per indirizzo client. Non si applica mai a una chiave valida.

Dimensione della richiesta

Un corpo che supera il tetto del proprio endpoint viene rifiutato con payload_too_large prima che qualcosa venga letto verso il database. Il tetto è controllato due volte: prima sul Content-Length dichiarato, che non costa nulla, poi sui byte che arrivano davvero, così anche una richiesta che non dichiara alcuna lunghezza resta limitata.

EndpointTetto
Ogni endpoint che scrive un singolo record256 KiB (262144 bytes)
Importazione massiva e lotti di unità4 MiB (4194304 bytes)

Il tetto dei lotti nasce dal lotto più grande che questa API accetta, non da una cifra tonda: 500 righe di importazione con ogni campo di una categoria compilato, oppure 1000 unità serializzate. Nessuno dei due entra nel tetto standard, ed è esattamente il motivo per cui quei due endpoint ne hanno uno proprio.

Il corpo deve arrivare come application/json; è accettato anche application/merge-patch+json. Qualsiasi altro tipo viene rifiutato con unsupported_media_type invece di fallire dentro il parser: il caso da conoscere è curl -d, che invia application/x-www-form-urlencoded se non gli si dice altro. Un corpo inviato senza alcun Content-Type viene letto come JSON.

Metodi HTTP

Un endpoint risponde solo ai metodi che documenta. Qualunque altro metodo viene rifiutato con method_not_allowed e un'intestazione Allow che nomina quelli che funzionano, così un client che sbaglia verbo impara quello giusto dal rifiuto stesso. HEAD funziona ovunque funzioni GET, e OPTIONS funziona ovunque.

Risposta
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"] }
  }
}

Operazioni distruttive

Ritirare un passaporto e spostarne uno nel cestino tolgono entrambi dal mercato un record pubblicato, perciò attingono a un tetto separato oltre al normale limite di frequenza. Un ciclo di richieste economiche e ben formate non deve poter spegnere un intero catalogo.

Allegare un documento a un campo

Alcuni campi di tipo url accettano un file caricato al posto di un collegamento: per esempio un rapporto di prova o una dichiarazione di conformità. Carichi prima il file, poi faccia puntare il campo al documento scrivendone l’identificativo nella chiave associata a quel campo, dentro `data`. Il campo può restare vuoto: un campo che accetta documenti risulta compilato non appena vi è allegato un documento, quindi un passaporto con l’url vuota resta validabile e pubblicabile.

Non deve mai comporre quella chiave a mano. Un campo che accetta documenti porta due attributi nello schema della categoria: `document_types`, che elenca i tipi ammessi, e `document_id_key`, che indica la chiave esatta da scrivere. Legga la chiave dallo schema invece di affidarsi alla regola di denominazione: è l’unico modo supportato. La chiave associata è una proprietà del campo e non un campo a sé, perciò non compare mai nell’elenco `fields` dello schema.

Una regola che lo schema non esplicita: l’`access_tier` del documento deve coincidere con quello del campo. Un documento pubblico non può soddisfare un campo di livello «authority»; l’associazione viene rifiutata con `access_tier_mismatch`, perché un documento con livello diverso da quello del campo non verrebbe mostrato dove il campo è mostrato. Crei il documento con l’`access_tier` indicato dal descrittore del campo.

Richiesta
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"}}'

Svuoti la chiave associata per staccare il documento; il file resta nella sua libreria finché non lo elimina. Se elimina un documento a cui un campo punta ancora, la risposta indica che cosa è stato staccato, e tale segnalazione raggiunge soltanto il chiamante che ha effettuato la richiesta.

L'`upload_state` di un documento è `ready`, `pending` oppure `unknown`. `unknown` significa che non siamo riusciti a stabilire se i byte siano nell'archivio: o l'archivio non ha risposto, oppure la riga era oltre i 25 documenti privi di byte che una richiesta verifica. Lo consideri indeterminato, non assente: in pubblicazione `unknown` produce `storage_unavailable` invece di dichiarare mancante un file, e una lettura successiva di norma lo risolve.

Campi che si applicano solo ad alcuni prodotti

L'indicatore `required` di un campo significa che il controllo di pubblicazione può richiederlo. Quando tale richiesta dipende dal prodotto stesso, il campo porta anche `required_when`, che indica un altro campo e i valori che attivano la richiesta. La prova di riferimento per la vita di ciclo porta `required_when` per `battery_category` con `ev` e `lmt`: il regolamento sulle batterie include i dati del passaporto «nella misura applicabile alla categoria o alla sottocategoria di batterie in questione», e gli orientamenti della Commissione sul passaporto della batteria associano ciascun punto di dati alle categorie a cui si applica. Una categoria può anche richiedere di nuovo un campo tramite una regola che legge più campi insieme: `required_when` non le descrive, ed è l'endpoint di convalida a risolverle.

Legga i due insieme e consideri `required` come la risposta più ampia: un client che ignora `required_when` al massimo chiede più del necessario. Non considera mai pronto un passaporto per poi vedersi rifiutare la pubblicazione. Per un passaporto specifico la risposta esatta viene dall'endpoint di convalida, che ha quel passaporto davanti.

Campi obbligatori a partire da una data

Un campo con `required_from` diventa obbligatorio per la pubblicazione a partire da quella data, secondo l’ora di Bruxelles; prima è facoltativo. Pubblichiamo tale data il prima possibile, normalmente con almeno 60 giorni di anticipo; quando un regolamento stabilisce o modifica una data con un preavviso più breve, il campo segue la data prevista dal regolamento. Legga `required` insieme a `required_when` e `required_from`; l’endpoint di convalida fornisce la risposta per un passaporto specifico.

Letture della batteria

Ogni lettura registra un valore, la sua fonte e la data in cui è stato misurato o dichiarato. Per correggere un valore, registri un’altra lettura. Prevale la data di osservazione più recente; a parità di data, prevale la registrazione successiva.

per_battery: override

Una batteria usa il valore del proprio modello finché non ha una lettura propria per quel campo. Il valore del modello non ha una data di misurazione.

Valori iniziali per le singole batterie (facoltativo)

model_health_declaration
  • Finché si applica la dichiarazione, ciascun valore dello stato di salute proviene dal modello, fino a quando non registra quel valore per la batteria.
  • I valori mancanti del modello restano vuoti. Con questa dichiarazione, lo stato dell’energia certificata parte dal 100%.
  • La registrazione di un valore sostituisce la dichiarazione del modello solo per quel campo.
  • Il primo stato salvato mantiene la dichiarazione. Se salva nuovamente uno stato, la dichiarazione smette di fornire valori dello stato di salute per quella batteria. Questo vale anche se sceglie lo stesso stato.
  • I valori già registrati per quella batteria vengono conservati.

I valori obbligatori mancanti della batteria sono riportati in missing_fields. Non impediscono di creare una batteria o pubblicare il suo modello.

Identificativi di richiesta

Ogni risposta autenticata include un'intestazione Request-Id, sia in caso di successo sia di errore. La citi in una richiesta di assistenza e la chiamata esatta potrà essere individuata. I due endpoint delle categorie rispondono alle richieste riuscite senza chiave da una cache condivisa, quindi tali risposte non includono un Request-Id; un rifiuto lo include sempre, su ogni percorso.

Il flusso degli eventi conserva 30 giorni di storico. Un'integrazione che interroga più di rado perderà dei cambiamenti e dovrebbe piuttosto riconciliarsi con l'elenco dei passaporti.