Convenciones
Convenciones
Las reglas que rigen en todos los endpoints, enunciadas una vez aquí en lugar de repetirse veinticinco veces.
Idempotencia
Los endpoints que modifican algo aceptan una cabecera Idempotency-Key. Repetir una solicitud con la misma clave reproduce la respuesta original en lugar de repetir el efecto, de modo que un reintento tras un tiempo de espera agotado no puede crear un segundo pasaporte. Las claves pertenecen a una organización, así que dos clientes nunca chocan con el mismo valor. La prueba y la producción están separadas: una clave ya usada en un modo se rechaza en el otro en lugar de reproducirse. Una excepción, y es una capacidad más que un hecho: la URL de subida firmada que devuelve `POST /documents` se vuelve a emitir en la repetición mientras el archivo siga pendiente, y se omite una vez que ha llegado; una URL caducada sería la repetición fiel de algo inservible.
Una clave se recuerda durante 30 días. Pasado ese plazo se olvida por completo, y un reintento que la lleve vuelve a ejecutarse en lugar de reproducirse, así que un proceso de conciliación que pueda ejecutarse más tarde debería generar una clave nueva.
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"}'Paginación
Las colecciones se paginan por cursor, nunca por desplazamiento. Un desplazamiento es incorrecto con escrituras concurrentes: un registro insertado entre dos páginas desplaza a todos los siguientes, y un cliente se salta o duplica filas sin enterarse.
Devuelva next_cursor como cursor para obtener la página siguiente. Un cursor codifica los filtros con los que se emitió, así que enviarlo con otros filtros se rechaza en vez de responder con una página que en silencio significaría otra cosa.
{
"object": "list",
"data": [],
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0wOC0wMVQxNDowMzoyMi41MDFaIn0"
}Escrituras condicionales
Leer un pasaporte devuelve un ETag con su versión. Envíelo de vuelta como If-Match en un PATCH y la escritura se rechazará si el pasaporte cambió entretanto: es la única forma de hacer segura una secuencia de leer, modificar y escribir frente a un editor concurrente.
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}}'Límites de tasa
En cada solicitud autenticada funcionan tres contadores a la vez y decide el más estrecho. Las cifras publicadas son puntos de partida deliberadamente prudentes, no techos ajustados.
| Contador | Solicitudes | Ventana |
|---|---|---|
| key_org | 1000 | 60s |
| organization | 3000 | 60s |
| credential | 10000 | 60s |
| destructive | 50 | 3600s |
Cada respuesta contabilizada lleva cabeceras RateLimit y RateLimit-Policy que nombran el contador que más se acercó a su límite. Un rechazo añade Retry-After: espere ese tiempo en lugar de adivinarlo.
Una solicitud que llega sin una clave utilizable se mide con un contador aparte, mucho más estrecho: 60 solicitudes por minuto y dirección de cliente. Nunca se aplica a una clave válida.
Tamaño de la solicitud
Un cuerpo que supera el tope de su endpoint se rechaza con payload_too_large antes de leer nada hacia la base de datos. El tope se comprueba dos veces: primero contra el Content-Length declarado, que no cuesta nada, y después contra los bytes que llegan de verdad, de modo que una solicitud que no declara longitud queda acotada igual.
| Endpoints | Tope |
|---|---|
| Todo endpoint que escribe un único registro | 256 KiB (262144 bytes) |
| Importación masiva y lotes de unidades | 4 MiB (4194304 bytes) |
El tope de los lotes sale del lote más grande que acepta esta API, no de una cifra redonda: 500 filas de importación con todos los campos de una categoría rellenos, o 1000 unidades serializadas. Ninguno cabe en el tope estándar, y por eso esos dos endpoints tienen uno propio.
El cuerpo debe llegar como application/json; application/merge-patch+json también se acepta. Cualquier otro tipo se rechaza con unsupported_media_type en vez de fallar dentro del analizador; el caso que conviene conocer es curl -d, que envía application/x-www-form-urlencoded salvo que se indique otra cosa. Un cuerpo enviado sin ningún Content-Type se lee como JSON.
Métodos HTTP
Un endpoint responde solo a los métodos que documenta. Cualquier otro se rechaza con method_not_allowed y una cabecera Allow que nombra los que sí funcionan, así que un cliente que se equivoca de verbo aprende el correcto en el propio rechazo. HEAD funciona allí donde funciona GET, y OPTIONS funciona en todas partes.
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"] }
}
}Operaciones destructivas
Despublicar un pasaporte y enviarlo a la papelera retiran del mercado un registro publicado, así que ambas consumen un tope aparte, además del límite de tasa ordinario. Un bucle de solicitudes baratas y bien formadas no debe poder apagar un catálogo entero.
Adjuntar un documento a un campo
Algunos campos de tipo url admiten un archivo subido en lugar de un enlace: un informe de ensayo o una declaración de conformidad, por ejemplo. Suba primero el archivo y después apunte el campo hacia él escribiendo el identificador del documento en la clave asociada de ese campo, dentro de `data`. El campo puede quedar vacío: un campo que admite documentos se considera cumplimentado en cuanto se le adjunta uno, de modo que un pasaporte con la url vacía sigue pudiendo validarse y publicarse.
Nunca tiene que construir esa clave a mano. Un campo que admite documentos lleva dos atributos en el esquema de la categoría: `document_types`, que enumera los tipos aceptados, y `document_id_key`, que indica la clave exacta que debe escribirse. Lea la clave del esquema en lugar de dar por supuesta la regla de nomenclatura: es la única vía admitida. La clave asociada es una propiedad del campo, no un campo en sí, por lo que nunca aparece en la lista `fields` del esquema.
Una regla que el esquema no detalla: el `access_tier` del documento debe coincidir con el del campo. Un documento público no puede satisfacer un campo de nivel «authority»; la vinculación se rechaza con `access_tier_mismatch`, porque un documento con un nivel distinto al del campo no se mostraría allí donde el campo sí se muestra. Cree el documento con el `access_tier` que indique el descriptor del campo.
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"}}'Vacíe la clave asociada para desvincular el documento; el archivo permanece en su biblioteca hasta que lo elimine. Al eliminar un documento al que todavía apunta un campo, la respuesta indica qué se desvinculó, y ese aviso solo llega a quien realizó la petición.
El `upload_state` de un documento es `ready`, `pending` o `unknown`. `unknown` significa que no hemos podido establecer si los bytes están en el almacenamiento: o bien el almacenamiento no respondió, o bien la fila quedó más allá de los 25 documentos sin bytes que comprueba cada petición. Considérelo indeterminado, no ausente: al publicar, `unknown` produce `storage_unavailable` en lugar de afirmar que falta un archivo, y una lectura posterior suele resolverlo.
Identificadores de solicitud
Cada respuesta lleva una cabecera Request-Id, tanto en caso de éxito como de fallo. Cítela en una consulta de soporte y podrá localizarse la llamada exacta.
El flujo de eventos conserva 30 días de historial. Una integración que consulte con menos frecuencia perderá cambios y debería conciliar contra la lista de pasaportes.