meta { name: 02 Upload (multipart Mistral) type: http seq: 2 } post { url: {{baseUrl}}/api/v1/invoices/upload body: multipartForm auth: inherit } body:multipart-form { files: @file() files: @file() } script:post-response { if (res.getStatus() === 201) { const batch = res.getBody().data; bru.setEnvVar("batchId", batch.id); if (batch.drafts && batch.drafts.length > 0) { const firstPending = batch.drafts.find(d => d.status === "pending") || batch.drafts[0]; bru.setEnvVar("draftId", firstPending.id); } } } tests { test("201 Created", function () { expect(res.getStatus()).to.equal(201); }); test("drafts ont un pdfStorageKey non null", function () { const drafts = res.getBody().data.drafts; if (drafts.length > 0) { expect(drafts[0].pdfStorageKey).to.not.be.null; } }); } docs { POST /api/v1/invoices/upload (multipart/form-data) Vrai upload OCR : un ou plusieurs fichiers PDF (ou PNG/JPG) sont uploadés sur MinIO, puis l'OCR provider configuré (`OCR_PROVIDER` du .env) extrait les champs. ## Setup côté Bruno Dans le bloc `body:multipart-form` ci-dessus, clique sur le `@file()` pour sélectionner un PDF depuis ton disque. Tu peux ajouter d'autres champs `files: @file()` pour uploader plusieurs PDFs en une fois. ## Setup côté API Dans `apps/api/.env` : ``` OCR_PROVIDER=mistral MISTRAL_API_KEY=ms_xxx... ``` Si `OCR_PROVIDER=mock`, l'upload multipart fonctionne aussi mais le PDF n'est pas analysé — le MockOcrProvider invente des champs depuis le nom du fichier (le PDF est juste stocké pour la suite). ## Stockage MinIO Les fichiers atterrissent dans `import-drafts//.`. Visibles via la console MinIO http://localhost:9101 (login `rubis` / `rubis-dev-secret`). ## Validation - Extensions : pdf, png, jpg, jpeg - Taille max : 10 MB par fichier ## Limites Mistral Le provider fait 2 appels (OCR → extraction structurée). Latence ~3-8s par PDF. Si l'extraction échoue, l'erreur remonte en 500 — pas de retry V1 (à mettre dans BullMQ pour la prod). }