English version

API de retrait laboratoire KebecPhoto v1

Guide d'intégration machine à machine pour Photolab Yves Thomas — version 1.1 — juillet 2026

1. Introduction

L'API de retrait permet à votre système de récupérer les lots d'impression de KebecPhoto par HTTPS — le ou les manifestes plus chaque fichier image prêt à imprimer — au lieu d'attendre notre dépôt SFTP dans votre dossier /incoming. Le manifeste servi par cette API est le XML SoumissionLabo détaillé (voir la §7). Il diffère du CSV Timestone simplifié que nous déposons sur SFTP : les fichiers image et leurs noms sont identiques octet pour octet sur les deux canaux, mais pas le manifeste — le SFTP expédie le CSV Timestone, cette API expédie le XML SoumissionLabo.

Pendant la période de transition, les deux canaux fonctionnent en parallèle : nous continuons de déposer sur votre SFTP exactement comme aujourd'hui, et les mêmes lots sont simultanément disponibles via cette API. Une fois votre intégration validée en production, nous cesserons (d'un commun accord) le dépôt SFTP; l'API deviendra alors le seul canal de livraison. La bascule est instantanément réversible de notre côté.

Images identiques octet pour octet. Les fichiers image servis par cette API sont exactement les octets que le dépôt SFTP expédie — mêmes recadrages, mêmes noms de fichiers. Le manifeste, lui, diffère selon le canal : le SFTP expédie le CSV Timestone simplifié, cette API expédie le XML SoumissionLabo détaillé — mêmes commandes, enveloppe différente. Chaque fichier (et manifeste) est accompagné de sa taille et de son empreinte SHA-256 pour vérification.

Ce que l'API vous offre :

2. Démarrage rapide

# 1. Lister les lots en attente (les plus anciens d'abord)
curl -H "Authorization: Bearer $CLE" \
  "$BASE/batches?status=UPLOADED"

# 2. Obtenir l'inventaire des fichiers d'un lot
curl -H "Authorization: Bearer $CLE" \
  "$BASE/batches/{batchId}"

# 3. Télécharger le ou les manifestes
curl -OJ -H "Authorization: Bearer $CLE" \
  "$BASE/batches/{batchId}/manifests/{name}"

# 4. Télécharger chaque fichier (max 4 en parallèle)
curl -OJ -H "Authorization: Bearer $CLE" \
  "$BASE/batches/{batchId}/files/{filename}"

# 5. Confirmer la réception (idempotent)
curl -X POST -H "Authorization: Bearer $CLE" \
  "$BASE/batches/{batchId}/confirm"

3. Environnements et URL de base

EnvironnementURL de basePréfixe de clé API
Staging (tests d'intégration)https://staging.kebecphoto.com/api/lab/v1kplab_stg_…
Productionhttps://app.kebecphoto.com/api/lab/v1kplab_prod_…

Les clés sont liées à l'environnement : une clé staging est cryptographiquement inerte contre la production et vice-versa (le serveur rejette une étiquette d'environnement non concordante avant toute consultation). Vous recevrez d'abord une clé staging; nous passerons à la production une fois votre flux de bout en bout validé contre des lots de test staging.

4. Authentification

Chaque requête porte votre clé API dans l'en-tête Authorization :

Authorization: Bearer kplab_prod_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Échecs d'authentification

Tout échec d'authentification — en-tête absent, clé mal formée, clé inconnue, clé révoquée, clé expirée — retourne la même réponse uniforme (volontairement : aucune information ne fuit sur l'état de la clé) :

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer
Content-Type: application/json

{"error":"unauthorized"}
Verrouillage progressif. Des échecs répétés avec une clé bien formée (mauvaise, révoquée, expirée ou du mauvais environnement) depuis une même IP — plus de 5 par minute — déclenchent un délai d'attente progressif (1 min → 5 min → 15 min → 60 min) pendant lequel les requêtes reçoivent un 429 avec en-tête Retry-After. (Les requêtes sans clé syntaxiquement valide reçoivent toujours un simple 401.) Un client correctement configuré ne rencontre jamais ce verrouillage; si cela vous arrive, cessez les tentatives, vérifiez la clé et respectez Retry-After.

5. Conventions

6. Référence des points d'accès

6.1 Lister les lots

GET/batches

Retourne les lots visibles pour votre laboratoire, les plus anciens d'abord par date de création (createdAt croissant, départage déterministe par id), avec pagination par curseur. Notez que c'est l'ordre de création, pas l'ordre de disponibilité — un lot d'école différé qui devient disponible ce soir peut apparaître avant des lots créés après lui. Seuls les lots livrables sont listés — vous ne verrez jamais de lots internes, échoués ou remplacés.

ParamètreValeursDéfautDescription
statusUPLOADED · CONFIRMED · allUPLOADEDUPLOADED = disponibles, pas encore confirmés par vous. CONFIRMED = déjà accusés réception. Toute autre valeur retombe sur le défaut.
sinceISO 8601—Seulement les lots dont les fichiers sont devenus disponibles à partir de cet instant. C'est la date de disponibilité, pas la date de création : un lot d'école créé il y a trois jours mais téléversé ce soir APPARAÎTRA. Une valeur non interprétable est ignorée.
cursorid du lot—Reprendre après ce lot — n'envoyez jamais autre chose que le nextCursor de la réponse immédiatement précédente. Une valeur mal formée est ignorée (vous obtenez la première page); un id inconnu donne une page vide (hasMore: false) — repartez sans curseur au prochain sondage.
limit1–10020Taille de page (les valeurs hors plage sont ramenées dans la plage).

Réponse 200 :

{
  "batches": [
    {
      "id": "cmkabc1230000xyz456def789",
      "status": "UPLOADED",
      "createdAt":   "2026-06-10T14:00:00.000Z",
      "submittedAt": "2026-06-10T14:05:12.000Z",
      "confirmedAt": null,
      "orderCount": 3,
      "itemCount": 12,
      "isConsolidated": false,
      "trigger": "DATED_DEADLINE",
      "filesAvailable": true
    }
  ],
  "nextCursor": null,
  "hasMore": false
}
ChampTypeSignification
idchaîneIdentifiant du lot — utilisez-le dans tous les autres points d'accès.
statuschaîneUPLOADED ou CONFIRMED (voir le cycle de vie).
createdAthorodatageCréation du lot de notre côté (peut précéder la disponibilité de plusieurs jours pour la consolidation des livraisons à l'école).
submittedAthorodatage · nullMoment où le lot a fini son téléversement / est devenu livrable.
confirmedAthorodatage · nullMoment où VOUS avez confirmé la réception (null d'ici là).
orderCount / itemCountnombreCommandes et lignes d'impression dans le lot.
isConsolidatedbooléenInformatif : vrai pour un lot produit par la consolidation nocturne ou déclenché manuellement pour traitement immédiat (croisez avec trigger : NIGHTLY_RUN vs MANUAL). Vous pouvez l'ignorer.
triggerchaîne · nullCause de la soumission : DATED_DEADLINE, NIGHTLY_RUN, SAME_DAY_HOME, MANUAL, ou null (héritage).
filesAvailablebooléenfalse = rien n'a jamais été publié pour téléchargement API (lot antérieur à l'API) — sautez-le. Note : dans la LISTE, ce champ ne reflète pas la purge de rétention; la purge est rapportée par le DÉTAIL (purged: true, dont le filesAvailable passe alors à false).
nextCursor / hasMorechaîne · null / booléenPagination : repassez nextCursor comme cursor tant que hasMore est vrai.

6.2 Détail d'un lot (inventaire des fichiers)

GET/batches/{batchId}

Tous les champs de la liste ci-dessus, plus l'inventaire complet : manifestes et fichiers image, chacun avec taille et SHA-256.

{
  "id": "cmkabc1230000xyz456def789",
  "status": "UPLOADED",
  "…": "…(tous les champs de la liste)…",
  "filesAvailable": true,
  "purged": false,
  "manifests": [
    {
      "name": "BATCH-2026-06-10-cmkabc1230000xyz456def789.xml",
      "groupKey": "batch",
      "sizeBytes": 8421,
      "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
    }
  ],
  "files": [
    {
      "filename": "KP-20260610-A1B2C3_001_YT-4X6-MAT.jpg",
      "groupKey": "batch",
      "orderRef": "KP-20260610-A1B2C3",
      "quantity": 5,
      "lineType": "STANDARD",
      "side": null,
      "sizeBytes": 2048312,
      "sha256": "ab12cd…"
    },
    {
      "filename": "KP-20260610-A1B2C3_007_YT-CARTE_CARD.jpg",
      "side": null, "…": "…"
    },
    {
      "filename": "KP-20260610-A1B2C3_012_YT-CARTE_FRONT.jpg",
      "side": "FRONT", "…": "…"
    },
    {
      "filename": "KP-20260610-A1B2C3_012_YT-CARTE_BACK.jpg",
      "side": "BACK", "…": "…"
    }
  ]
}
ChampTypeSignification
manifests[].namechaîneNom du manifeste — passez-le tel quel au point d'accès de téléchargement de manifeste. Un par groupe de livraison (voir groupKey).
files[].filenamechaîneNom de fichier exact — identique au dépôt SFTP et à l'élément <FichierImage> / <FichierImageVerso> du manifeste. Passez-le tel quel au point d'accès de téléchargement de fichier.
groupKeychaîneGroupe de livraison, reflétant les dossiers SFTP : batch = commandes groupées au studio (votre dossier batch/); ORDER-… = une expédition directe par groupe (votre dossier orders/).
orderRefchaîne · nullNuméro de commande lisible auquel appartient le fichier (aussi intégré au nom de fichier).
quantitynombre · nullQuantité d'impression pour la ligne (un fichier peut s'imprimer N fois — élément <Quantite> du manifeste).
lineTypechaîne · nullSTANDARD (impression simple / carte sportive) ou SHEET (feuille imposée pré-rendue — imprimer telle quelle).
sidechaîne · nullFaces de cartes sportives seulement : FRONT / BACK pour la paire recto/verso héritée (une carte recto seul porte FRONT). Null pour le fichier fusionné _CARD (le suffixe du nom de fichier l'identifie) et pour tout le reste.
sizeBytes / sha256nombre / chaîneTaille exacte en octets + SHA-256 (hex) du fichier — vérifiez après téléchargement.
purgedbooléenVrai lorsque les fichiers ont dépassé la rétention et ont été retirés (voir la rétention) — les métadonnées d'inventaire restent, les téléchargements répondent 404.

Code de statut particulier — 410 Gone : si un lot que vous aviez vu a été remplacé par un lot consolidé avant le retrait, son détail retourne :

{"error":"batch_superseded","supersededByBatchId":"cmk…"}
410 signifie NE PAS IMPRIMER ce lot. Jetez tout ce que vous auriez pu récupérer pour lui et traitez le lot remplaçant (supersededByBatchId, chaîne ou null) — il apparaît dans votre liste normale. Si supersededByBatchId est null, jetez simplement le lot et fiez-vous à votre sondage status=UPLOADED habituel. Si des téléchargements de fichiers se mettent à répondre 404 en plein retrait, relisez le détail du lot — un 410 à cet endroit correspond à ce cas.

6.3 Télécharger un manifeste

GET/batches/{batchId}/manifests/{name}

Transmet le manifeste — le XML SoumissionLabo détaillé (voir la §7 pour le schéma). UTF-8, ouvrant sur une déclaration <?xml version="1.0" encoding="UTF-8"?>. C'est le format de manifeste de cette API; le dépôt SFTP, lui, transporte le CSV Timestone simplifié.

En-têtes de réponse en cas de succès (200) :

Content-Type: application/xml
Content-Length: 8421
Content-Disposition: attachment; filename="BATCH-2026-06-10-….xml"
X-Checksum-SHA256: 9f86d081…
Cache-Control: private, no-store

404 {"error":"not_found"} pour un nom inconnu, un lot purgé, ou un lot qui n'est pas (ou plus) livrable.

6.4 Télécharger un fichier

GET/batches/{batchId}/files/{filename}

Transmet un JPEG prêt à imprimer, identique octet pour octet au dépôt SFTP. Mêmes en-têtes que ci-dessus avec Content-Type: image/jpeg.

6.5 Confirmer la réception

POST/batches/{batchId}/confirm

À appeler quand TOUS les fichiers du lot sont téléchargés et vérifiés. Le lot passe à CONFIRMED (et sort de votre liste status=UPLOADED par défaut). Corps de requête vide.

Réponse 200 :

{
  "id": "cmkabc1230000xyz456def789",
  "status": "CONFIRMED",
  "confirmedAt": "2026-06-10T15:42:08.000Z",
  "alreadyConfirmed": false
}

Rapport de vérification optionnel (depuis le 2026-06-12). Un client qui vérifie ses téléchargements (comme l'application de bureau KebecPhoto Labo) peut joindre un corps JSON (≤ 1 Ko) :

{
  "agentVersion": "1.0.0",
  "mode": "auto",
  "imagesVerified": 135,
  "manifestsVerified": 2,
  "bytesVerified": 287345234,
  "sha256MismatchCount": 0,
  "durationMs": 184233
}
La confirmation compte : c'est notre signal d'accusé de réception (il alimente nos tableaux de bord et, à terme, la décision de bascule SFTP), et elle démarre l'horloge de rétention après laquelle les fichiers sont purgés. Le serveur ne suit pas vos téléchargements — confirmer est purement votre attestation. Confirmez seulement quand vous détenez (et avez vérifié) réellement tout.

7. Référence des données

Grammaire des noms de fichiers

{refCommande}_{seq}_{codeLab}[_{FRONT|BACK|CARD}].jpg
 KP-20260610-A1B2C3 _ 001 _ YT-4X6-MAT          .jpg   → impression standard
 KP-20260610-A1B2C3 _ 007 _ YT-CARTE _ CARD     .jpg   → carte recto/verso FUSIONNÉE (défaut)
 KP-20260610-A1B2C3 _ 012 _ YT-CARTE _ FRONT    .jpg   → carte, recto (paire héritée / recto seul)
 KP-20260610-A1B2C3 _ 012 _ YT-CARTE _ BACK     .jpg   → carte, verso (paire héritée)

Cartes sportives (recto/verso)

Défaut — fichier fusionné _CARD : une carte recto/verso dépose désormais UN seul fichier, le recto et le verso côte à côte sur une même image de largeur double (p. ex. une carte 2.5×3.5 po → un fichier de classe 5×3.5 po). La densité (ppp) inscrite dans le JPEG est la vérité physique du format — le fichier EST la feuille finale, pré-rendue aux dimensions d'impression : imprimer tel quel, sans recadrage ni redimensionnement, puis couper au trait central. Dans le XML SoumissionLabo, la ligne fusionnée est UN <Article> avec un seul <FichierImage> (jamais de <FichierImageVerso>) et sans balises de taille (<TailleCm>/<TaillePo> omises — la taille mono-face serait fausse pour la feuille double). Dans l'inventaire API : une seule entrée, side: null, identifiée par le suffixe _CARD.

Héritage — paire _FRONT/_BACK : les lignes de cartes antérieures à la migration déposent encore DEUX fichiers formant UN seul <Article> portant à la fois <FichierImage> (recto) et <FichierImageVerso> (verso) : une carte facturable reste un seul article. Dans l'inventaire API, ce sont deux entrées partageant le même orderRef, distinguées par side: "FRONT" / "BACK". Les deux sont pré-rendus aux dimensions d'impression — imprimer tels quels, sans recadrage. (Une carte recto seul reste un unique fichier _FRONT.)

Feuilles imposées

Les entrées lineType: "SHEET" sont des JPEG composites pré-imposés (300 ppp, prêts à imprimer tels quels) — exactement comme convenu dans notre échange du 2 juin sur les feuilles imposées.

Groupes de livraison (groupKey)

groupKeyÉquivalent SFTPSignification
batchdossier batch/Commandes en livraison à l'école, groupées pour le studio (un manifeste pour le groupe; adresse du point de groupage).
ORDER-{date}-{ref}dossier orders/Une commande en expédition directe au parent (son propre manifeste; adresse du parent).

Un lot mixte contient le groupe batch ET un groupe ORDER-… par commande en expédition directe — téléchargez chaque manifeste listé dans manifests[].

Le manifeste (XML SoumissionLabo)

Le manifeste de l'API est le XML SoumissionLabo détaillé — plus riche que le CSV Timestone simplifié déposé sur SFTP; les deux ne sont pas interchangeables. Élément racine <SoumissionLabo>; un <Article> par ligne d'impression (une carte fusionnée _CARD est un article avec un seul <FichierImage> et sans balises de taille; une paire héritée est un article portant à la fois <FichierImage> et <FichierImageVerso>). Les commandes familiales mélangent les enfants sous une même <Commande>, chaque article portant son propre élève. UTF-8, avec une déclaration XML.

ÉlémentEmplacementContenu
<Lot>racineEn-tête du lot : <NumeroLot>, <DateCreation>, <NombreCommandes>, <NombreArticles>, <NombreTirages>.
<Studio>racinePhotographe : <Nom>, <CodeStudio>, <CompteLaboYT> (votre numéro de compte labo pour ce studio).
<Commande>sous <Commandes>Une par commande : <NumeroCommande> + les blocs ci-dessous.
<Client>commande<Nom>, <Courriel>, <Telephone> du client.
<Livraison type="…">commandeecole (groupage école/studio — le groupe batch), expeditionDirecte (directe au parent — un groupe ORDER-…), ou numerique (numérique, sans adresse). Éléments d'adresse : <Ligne1>, <Ville>, <Province>, <CodePostal>, <Pays>.
<Article>sous <Articles>Un par ligne d'impression. Enfants clés : <CodeProduit> (VOTRE code de produit), <Quantite>, <FichierImage> (+ <FichierImageVerso> pour les versos de paires de cartes HÉRITÉES seulement — jamais sur une ligne fusionnée _CARD), <Recadrage applique="oui|non"> avec <X>/<Y>/<Largeur>/<Hauteur> quand recadré. Les balises <TailleCm>/<TaillePo> sont omises sur les lignes fusionnées _CARD (et sur les feuilles imposées).
balises d'article optionnellesarticle<Decor> (nom du décor/arrière-plan), <GroupeSource> (groupe source de photo de groupe), <CommentairesCommande> (commentaire de commande du parent, si activé).

Le manifeste obtenu suit les groupes de livraison (groupKey) : le manifeste du groupe batch porte <Livraison type="ecole">; chaque manifeste de groupe ORDER-… porte <Livraison type="expeditionDirecte">. (Le CSV Timestone simplifié est le format que nous déposons sur votre SFTP; cette API sert toujours le XML SoumissionLabo détaillé. Nous vous aviserions avant tout changement de l'un ou l'autre.)

8. Flux d'intégration recommandé

Sondez toutes les 5 minutes; persistez deux choses de votre côté : l'ensemble des ids de lots entièrement traités, et l'horodatage de votre dernier sondage réussi.

toutes les 5 minutes :
  since = dernier_sondage_réussi − 1 heure       # chevauchement volontaire; dédoublonnez par id
  page  = GET /batches?status=UPLOADED&since={since}
  boucle :
    pour chaque lot de page.batches :
      si lot.id déjà traité ou non lot.filesAvailable : continuer
      détail = GET /batches/{lot.id}              # 410 → marquer annulé, imprimer le lot remplaçant
      pour chaque m de détail.manifests : télécharger + vérifier sha256
      pour chaque f de détail.files :    télécharger + vérifier sha256   # ≤ 4 en parallèle
      remettre à la chaîne de production
      POST /batches/{lot.id}/confirm              # idempotent — sûr à répéter après un plantage
      marquer lot.id traité
    si non page.hasMore : sortir
    page = GET /batches?…&cursor={page.nextCursor}
  dernier_sondage_réussi = maintenant

9. Catalogue des erreurs

HTTPCode errorSignificationQuoi faire
401unauthorizedClé absente/invalide/révoquée/expirée (uniforme — aucun détail, par conception).Vérifier la clé et l'environnement; contacter Francis si persistant.
404not_foundLot/manifeste/fichier inconnu, fichiers purgés, ou ressource hors de votre périmètre.Sauter; re-lister. Pour un lot purgé, rien à récupérer.
405method_not_allowedMauvaise méthode HTTP (p. ex. GET sur confirm).Corriger la requête.
409invalid_statusLe lot existe mais ne peut pas être confirmé pour l'instant.Revérifier via le détail; contacter Francis si persistant.
410batch_supersededNe pas imprimer. Lot remplacé par supersededByBatchId.Annuler localement; traiter le lot remplaçant.
413payload_too_largeUn corps de requête dépasse 1 Ko (plafond du rapport de confirmation).Garder le rapport optionnel sous 1 Ko; corps vides ailleurs.
429rate_limitedLimite de débit dépassée (ou verrouillage d'échecs d'authentification).Attendre Retry-After secondes (aussi dans le JSON : retry_after).
429too_many_concurrent_streamsPlus de 16 téléchargements simultanés.Attendre la fin d'un transfert; réessayer après Retry-After.
503too_many_concurrent_streamsIndisponibilité transitoire du suivi des transferts côté serveur.Réessayer après Retry-After (secondes).
5xx—Erreur serveur transitoire.Backoff exponentiel et réessayer; nous alerter si soutenu.

10. Limites de débit et concurrence

Les limites s'appliquent par laboratoire (toutes vos clés actives partagent les mêmes compteurs), par minute (fenêtre fixe). Les limites de sondage/confirmation sont bien au-dessus de la cadence documentée. Le compteur de fichiers (600/min) est le mécanisme de régulation voulu pour les gros lots — en drainant un gros lot à 16 transferts parallèles, vous pouvez voir un 429 rate_limited occasionnel; honorez Retry-After et continuez.

Point d'accèsLimite
GET /batches (liste)120 / min
GET /batches/{id} (détail)240 / min
GET …/manifests/{name}120 / min
GET …/files/{filename}600 / min + max 16 transferts simultanés
POST …/confirm60 / min

Le découpage des lots utilise un plafond de 500 lignes image, auquel s'ajoutent les manifestes et certains fichiers supplémentaires; la durée du téléchargement dépend du volume de données et du débit.

Un plafond de sécurité additionnel par IP source (dimensionné bien au-dessus de la somme des compteurs) protège l'API en amont de l'authentification; il est hors d'atteinte aux limites documentées mais s'applique par IP — faites tourner votre sondeur comme un seul client coordonné plutôt que comme plusieurs processus parallèles derrière une même IP de sortie.

11. Conservation des données et cycle de vie des lots

12. Versionnement et soutien