@reqvet-sdk/sdk 2.2.0 → 2.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +85 -66
- package/SDK_REFERENCE.md +259 -193
- package/SECURITY.md +16 -2
- package/package.json +8 -2
- package/src/index.d.ts +19 -1
- package/src/index.js +28 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.2.1
|
|
4
|
+
|
|
5
|
+
- **Feat** : ajout de `getSignedUploadUrl(fileName, contentType)` — obtenir une URL presignée Supabase pour uploader l'audio directement, sans passer par `/api/v1/upload` (Vercel Serverless Function, limite ~4.5 MB). Recommandé pour les proxies serveur (Next.js, Express…) gérant des fichiers > 4 MB.
|
|
6
|
+
- **Types** : ajout de `SignedUploadResult` dans `index.d.ts`.
|
|
7
|
+
- **Docs** : `uploadAudio()` annotée avec l'avertissement Vercel dans `index.js`, `index.d.ts`, `SDK_REFERENCE.md` et `README.md`.
|
|
8
|
+
- **Examples** : `nextjs/route-generate.ts` et `nextjs/route-generate.mjs` mis à jour pour utiliser `getSignedUploadUrl()`.
|
|
9
|
+
- **Security** : exemple proxy dans `SECURITY.md` mis à jour.
|
|
10
|
+
|
|
3
11
|
## 2.2.0
|
|
4
12
|
|
|
5
13
|
- **Feat** : ajout de `listJobs(options?)` — liste les jobs avec pagination (`limit`, `offset`) et filtres (`status`, `sort`, `order`). Aligne le SDK sur `GET /api/v1/jobs`.
|
package/README.md
CHANGED
|
@@ -1,78 +1,96 @@
|
|
|
1
|
-
# @reqvet/sdk
|
|
1
|
+
# @reqvet-sdk/sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
SDK JavaScript/TypeScript officiel pour l'API [ReqVet](https://reqvet.com) — génération de comptes rendus vétérinaires par IA à partir d'enregistrements audio.
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/@reqvet/sdk)
|
|
5
|
+
[](https://www.npmjs.com/package/@reqvet-sdk/sdk)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](https://nodejs.org)
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Fonctionnalités
|
|
10
10
|
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
18
|
-
- **
|
|
11
|
+
- **Uploader** un enregistrement audio (`uploadAudio`)
|
|
12
|
+
- **Générer** un compte rendu vétérinaire (`createJob`, `generateReport`)
|
|
13
|
+
- **Suivre** les jobs — via webhook ou polling (`getJob`, `waitForJob`, `listJobs`)
|
|
14
|
+
- **Amender** un compte rendu terminé avec un audio complémentaire (`amendJob`)
|
|
15
|
+
- **Régénérer** avec de nouvelles instructions (`regenerateJob`)
|
|
16
|
+
- **Reformuler** pour une audience spécifique — propriétaire, référé, spécialiste (`reformulateReport`)
|
|
17
|
+
- **Gérer les templates** (`listTemplates`, `createTemplate`, `updateTemplate`, `deleteTemplate`)
|
|
18
|
+
- **Vérifier les webhooks** avec HMAC (`@reqvet-sdk/sdk/webhooks`)
|
|
19
19
|
|
|
20
|
-
> **Note
|
|
20
|
+
> **Note** : ce SDK n'inclut pas d'enregistreur audio. Votre application gère l'enregistrement et passe un `File`, `Blob` ou `Buffer` au SDK.
|
|
21
21
|
|
|
22
22
|
## Installation
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
npm install @reqvet/sdk
|
|
25
|
+
npm install @reqvet-sdk/sdk
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
Nécessite Node.js ≥ 18. Fonctionne dans les navigateurs modernes pour les méthodes client (Blob/FormData requis pour l'upload).
|
|
29
29
|
|
|
30
|
-
##
|
|
30
|
+
## Avant votre premier appel
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
Votre responsable de compte ReqVet vous fournira trois variables d'environnement :
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
35
|
REQVET_API_KEY=rqv_live_...
|
|
36
36
|
REQVET_BASE_URL=https://api.reqvet.com
|
|
37
|
-
REQVET_WEBHOOK_SECRET=... #
|
|
37
|
+
REQVET_WEBHOOK_SECRET=... # uniquement si vous utilisez les webhooks
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
Chaque job nécessite un `templateId`. **Appelez `listTemplates()` en premier** pour découvrir ce qui est disponible :
|
|
41
41
|
|
|
42
42
|
```ts
|
|
43
43
|
const { system, custom } = await reqvet.listTemplates();
|
|
44
|
-
// system = ReqVet
|
|
45
|
-
// custom = templates
|
|
44
|
+
// system = templates fournis par ReqVet, visibles par toutes les organisations (lecture seule)
|
|
45
|
+
// custom = templates créés par votre organisation
|
|
46
46
|
|
|
47
47
|
const templateId = system[0].id;
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
##
|
|
50
|
+
## Démarrage rapide
|
|
51
51
|
|
|
52
|
-
###
|
|
52
|
+
### Flux webhook (recommandé)
|
|
53
|
+
|
|
54
|
+
Pour les intégrations serveur (Next.js, Express…), utilisez `getSignedUploadUrl()` qui uploade le fichier directement dans Supabase sans passer par une Vercel Serverless Function — **pas de limite de taille**.
|
|
53
55
|
|
|
54
56
|
```ts
|
|
55
|
-
import ReqVet from '@reqvet/sdk';
|
|
57
|
+
import ReqVet from '@reqvet-sdk/sdk';
|
|
56
58
|
|
|
57
59
|
const reqvet = new ReqVet(process.env.REQVET_API_KEY!, {
|
|
58
60
|
baseUrl: process.env.REQVET_BASE_URL,
|
|
59
61
|
});
|
|
60
62
|
|
|
61
|
-
// 1.
|
|
62
|
-
const { path } = await reqvet.
|
|
63
|
+
// 1. Obtenir une URL signée Supabase (requête JSON légère, pas de fichier)
|
|
64
|
+
const { uploadUrl, path } = await reqvet.getSignedUploadUrl(
|
|
65
|
+
'consultation.webm',
|
|
66
|
+
'audio/webm',
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// 2. Uploader directement vers Supabase (contourne Vercel, pas de limite de taille)
|
|
70
|
+
await fetch(uploadUrl, {
|
|
71
|
+
method: 'PUT',
|
|
72
|
+
headers: { 'Content-Type': 'audio/webm' },
|
|
73
|
+
body: audioBuffer, // Buffer | Blob | File
|
|
74
|
+
});
|
|
63
75
|
|
|
64
|
-
//
|
|
76
|
+
// 3. Créer un job — ReqVet POSTera le résultat sur votre webhook quand il sera prêt
|
|
65
77
|
const job = await reqvet.createJob({
|
|
66
78
|
audioFile: path,
|
|
67
79
|
animalName: 'Rex',
|
|
68
80
|
templateId: 'your-template-uuid',
|
|
69
81
|
callbackUrl: 'https://your-app.com/api/reqvet/webhook',
|
|
70
|
-
metadata: { consultationId: 'abc123' },
|
|
82
|
+
metadata: { consultationId: 'abc123' },
|
|
71
83
|
});
|
|
72
84
|
// { job_id: '...', status: 'pending' }
|
|
73
85
|
```
|
|
74
86
|
|
|
75
|
-
|
|
87
|
+
> **`uploadAudio()` vs `getSignedUploadUrl()`**
|
|
88
|
+
>
|
|
89
|
+
> `uploadAudio()` est pratique pour des fichiers légers (< 4 MB) ou des contextes navigateur.
|
|
90
|
+
> Pour les proxies serveur, préférez `getSignedUploadUrl()` : le fichier va directement dans Supabase,
|
|
91
|
+
> sans passer par `/api/v1/upload` (Vercel Serverless Function, limite ~4.5 MB).
|
|
92
|
+
|
|
93
|
+
Votre webhook reçoit un événement `job.completed` :
|
|
76
94
|
|
|
77
95
|
```json
|
|
78
96
|
{
|
|
@@ -86,7 +104,7 @@ Your webhook receives a `job.completed` event:
|
|
|
86
104
|
}
|
|
87
105
|
```
|
|
88
106
|
|
|
89
|
-
###
|
|
107
|
+
### Flux polling (plus simple pour le développement)
|
|
90
108
|
|
|
91
109
|
```ts
|
|
92
110
|
const report = await reqvet.generateReport({
|
|
@@ -99,10 +117,10 @@ const report = await reqvet.generateReport({
|
|
|
99
117
|
// { jobId, html, fields, transcription, cost, metadata }
|
|
100
118
|
```
|
|
101
119
|
|
|
102
|
-
###
|
|
120
|
+
### Vérifier un webhook entrant
|
|
103
121
|
|
|
104
122
|
```ts
|
|
105
|
-
import { verifyWebhookSignature } from '@reqvet/sdk/webhooks';
|
|
123
|
+
import { verifyWebhookSignature } from '@reqvet-sdk/sdk/webhooks';
|
|
106
124
|
|
|
107
125
|
export async function POST(req: NextRequest) {
|
|
108
126
|
const rawBody = await req.text();
|
|
@@ -123,36 +141,37 @@ export async function POST(req: NextRequest) {
|
|
|
123
141
|
|
|
124
142
|
## API
|
|
125
143
|
|
|
126
|
-
|
|
|
127
|
-
|
|
128
|
-
| `
|
|
129
|
-
| `
|
|
130
|
-
| `
|
|
131
|
-
| `
|
|
132
|
-
| `
|
|
133
|
-
| `
|
|
134
|
-
| `
|
|
135
|
-
| `
|
|
136
|
-
| `
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
139
|
-
| `
|
|
140
|
-
| `
|
|
141
|
-
| `
|
|
142
|
-
| `
|
|
143
|
-
| `
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
144
|
+
| Méthode | Description |
|
|
145
|
+
|---------|-------------|
|
|
146
|
+
| `getSignedUploadUrl(fileName, contentType)` | URL signée Supabase pour upload direct (recommandé serveur) |
|
|
147
|
+
| `uploadAudio(audio, fileName?)` | Uploader un fichier audio via ReqVet (limite Vercel ~4.5 MB) |
|
|
148
|
+
| `generateReport(params)` | Upload + création de job (helper tout-en-un) |
|
|
149
|
+
| `createJob(params)` | Créer un job de génération |
|
|
150
|
+
| `listJobs(options?)` | Lister les jobs avec pagination et filtre par statut |
|
|
151
|
+
| `getJob(jobId)` | Obtenir le statut et le résultat d'un job |
|
|
152
|
+
| `waitForJob(jobId, onStatus?)` | Attendre en polling la fin d'un job |
|
|
153
|
+
| `regenerateJob(jobId, options?)` | Régénérer un compte rendu terminé |
|
|
154
|
+
| `amendJob(jobId, params)` | Ajouter un audio complémentaire à un job terminé |
|
|
155
|
+
| `reformulateReport(jobId, params)` | Générer une version adaptée à une audience |
|
|
156
|
+
| `listReformulations(jobId)` | Lister toutes les reformulations d'un job |
|
|
157
|
+
| `listTemplates()` | Lister les templates disponibles (`{ system, custom }`) |
|
|
158
|
+
| `getTemplate(templateId)` | Obtenir un template par son ID |
|
|
159
|
+
| `createTemplate(params)` | Créer un template personnalisé |
|
|
160
|
+
| `updateTemplate(templateId, updates)` | Mettre à jour un template |
|
|
161
|
+
| `deleteTemplate(templateId)` | Supprimer un template |
|
|
162
|
+
| `health()` | Vérification de l'état de l'API |
|
|
163
|
+
|
|
164
|
+
## Événements webhook
|
|
165
|
+
|
|
166
|
+
ReqVet déclenche 5 types d'événements : `job.completed`, `job.failed`, `job.amended`, `job.amend_failed`, `job.regenerated`.
|
|
167
|
+
|
|
168
|
+
Les livraisons échouées sont retentées 3 fois (0s, 2s, 5s). Implémentez l'idempotence dans votre handler — dédoublonnez sur `job_id + event`.
|
|
169
|
+
|
|
170
|
+
Voir [SDK_REFERENCE.md §6](./SDK_REFERENCE.md#6-webhook-events) pour la structure complète des payloads de chaque événement.
|
|
152
171
|
|
|
153
172
|
## TypeScript
|
|
154
173
|
|
|
155
|
-
|
|
174
|
+
Définitions TypeScript complètes incluses :
|
|
156
175
|
|
|
157
176
|
```ts
|
|
158
177
|
import type {
|
|
@@ -162,18 +181,18 @@ import type {
|
|
|
162
181
|
Template,
|
|
163
182
|
ReqVetReformulation,
|
|
164
183
|
ExtractedFields,
|
|
165
|
-
} from '@reqvet/sdk';
|
|
184
|
+
} from '@reqvet-sdk/sdk';
|
|
166
185
|
```
|
|
167
186
|
|
|
168
|
-
##
|
|
187
|
+
## Pour aller plus loin
|
|
169
188
|
|
|
170
|
-
- [SDK_REFERENCE.md](./SDK_REFERENCE.md) —
|
|
171
|
-
- [SECURITY.md](./SECURITY.md) —
|
|
189
|
+
- [SDK_REFERENCE.md](./SDK_REFERENCE.md) — documentation complète des paramètres et réponses, tous les payloads webhook, schéma des champs, codes d'erreur
|
|
190
|
+
- [SECURITY.md](./SECURITY.md) — bonnes pratiques de sécurité, pattern proxy, exemple complet de vérification webhook
|
|
172
191
|
|
|
173
|
-
##
|
|
192
|
+
## Sécurité
|
|
174
193
|
|
|
175
|
-
**
|
|
194
|
+
**Ne jamais** exposer votre clé API dans du code côté client. Utilisez toujours le SDK côté serveur et proxifiez les requêtes depuis votre frontend. Voir [SECURITY.md](./SECURITY.md).
|
|
176
195
|
|
|
177
|
-
##
|
|
196
|
+
## Licence
|
|
178
197
|
|
|
179
198
|
MIT
|
package/SDK_REFERENCE.md
CHANGED
|
@@ -1,64 +1,65 @@
|
|
|
1
|
-
# @reqvet/sdk —
|
|
1
|
+
# @reqvet-sdk/sdk — Référence technique
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Documentation complète des paramètres et réponses pour toutes les méthodes du SDK.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
## 1)
|
|
7
|
+
## 1) Instanciation
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
|
-
import ReqVet from '@reqvet/sdk';
|
|
10
|
+
import ReqVet from '@reqvet-sdk/sdk';
|
|
11
11
|
|
|
12
12
|
const reqvet = new ReqVet(process.env.REQVET_API_KEY!, {
|
|
13
13
|
baseUrl: process.env.REQVET_BASE_URL ?? 'https://api.reqvet.com',
|
|
14
|
-
pollInterval: 5000,
|
|
15
|
-
timeout: 5 * 60 * 1000,
|
|
14
|
+
pollInterval: 5000, // intervalle de polling en ms (défaut : 5000)
|
|
15
|
+
timeout: 5 * 60 * 1000, // attente maximale en polling en ms (défaut : 300 000 = 5 min)
|
|
16
16
|
});
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
La clé API doit commencer par `rqv_`. Une `Error` est levée immédiatement dans le cas contraire.
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
-
## 2)
|
|
23
|
+
## 2) Avant votre premier appel
|
|
24
24
|
|
|
25
|
-
###
|
|
25
|
+
### Obtenir vos identifiants
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
- `REQVET_API_KEY` — your org API key (`rqv_live_...`)
|
|
29
|
-
- `REQVET_BASE_URL` — the API base URL
|
|
30
|
-
- `REQVET_WEBHOOK_SECRET` — your webhook signing secret (if using webhooks)
|
|
27
|
+
Votre responsable de compte ReqVet vous fournira :
|
|
31
28
|
|
|
32
|
-
|
|
29
|
+
- `REQVET_API_KEY` — votre clé API d'organisation (`rqv_live_...`)
|
|
30
|
+
- `REQVET_BASE_URL` — l'URL de base de l'API
|
|
31
|
+
- `REQVET_WEBHOOK_SECRET` — votre secret de signature webhook (si vous utilisez les webhooks)
|
|
33
32
|
|
|
34
|
-
|
|
33
|
+
### Découvrir vos templates
|
|
34
|
+
|
|
35
|
+
Chaque job nécessite un `templateId`. Avant de générer des comptes rendus, listez les templates disponibles pour votre organisation :
|
|
35
36
|
|
|
36
37
|
```ts
|
|
37
38
|
const { custom, system } = await reqvet.listTemplates();
|
|
38
|
-
// system = templates
|
|
39
|
-
// custom = templates
|
|
40
|
-
const templateId = system[0].id; //
|
|
39
|
+
// system = templates créés par ReqVet, disponibles pour toutes les organisations (lecture seule)
|
|
40
|
+
// custom = templates créés par votre organisation
|
|
41
|
+
const templateId = system[0].id; // ou custom[0].id
|
|
41
42
|
```
|
|
42
43
|
|
|
43
44
|
---
|
|
44
45
|
|
|
45
|
-
## 3)
|
|
46
|
+
## 3) Patterns d'intégration
|
|
46
47
|
|
|
47
|
-
### A) Webhook
|
|
48
|
+
### A) Webhook en priorité (recommandé pour la production)
|
|
48
49
|
|
|
49
50
|
```
|
|
50
|
-
uploadAudio() → createJob({ callbackUrl }) → ReqVet
|
|
51
|
+
uploadAudio() → createJob({ callbackUrl }) → ReqVet POSTe le résultat sur votre endpoint
|
|
51
52
|
```
|
|
52
53
|
|
|
53
|
-
|
|
54
|
+
L'utilisateur peut fermer le navigateur — le résultat arrive sur votre serveur de manière asynchrone.
|
|
54
55
|
|
|
55
|
-
### B) Polling (
|
|
56
|
+
### B) Polling (développement / intégrations simples)
|
|
56
57
|
|
|
57
58
|
```
|
|
58
|
-
uploadAudio() → createJob() → waitForJob() →
|
|
59
|
+
uploadAudio() → createJob() → waitForJob() → rapport
|
|
59
60
|
```
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
Ou utilisez le wrapper pratique :
|
|
62
63
|
|
|
63
64
|
```ts
|
|
64
65
|
const report = await reqvet.generateReport({ audio, animalName, templateId, waitForResult: true });
|
|
@@ -66,92 +67,143 @@ const report = await reqvet.generateReport({ audio, animalName, templateId, wait
|
|
|
66
67
|
|
|
67
68
|
---
|
|
68
69
|
|
|
69
|
-
## 4)
|
|
70
|
+
## 4) Méthodes
|
|
71
|
+
|
|
72
|
+
### `getSignedUploadUrl(fileName, contentType)` ⭐ recommandé serveur
|
|
73
|
+
|
|
74
|
+
Obtenir une URL signée Supabase pour uploader le fichier audio directement, sans passer par une Vercel Serverless Function.
|
|
75
|
+
|
|
76
|
+
**Quand l'utiliser :** intégrations serveur (Next.js proxy, Express, etc.), fichiers > 4 MB.
|
|
77
|
+
|
|
78
|
+
**Flow :**
|
|
79
|
+
```
|
|
80
|
+
getSignedUploadUrl() → PUT uploadUrl (Supabase direct) → createJob({ audioFile: path })
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Paramètres :**
|
|
84
|
+
| Nom | Type | Requis | Description |
|
|
85
|
+
|-----|------|--------|-------------|
|
|
86
|
+
| `fileName` | `string` | ✅ | Nom du fichier (ex. `consultation.webm`) |
|
|
87
|
+
| `contentType` | `string` | ✅ | Type MIME (ex. `audio/webm`) |
|
|
88
|
+
|
|
89
|
+
**Réponse :**
|
|
90
|
+
```ts
|
|
91
|
+
{
|
|
92
|
+
uploadUrl: string; // URL presignée Supabase — à utiliser avec PUT
|
|
93
|
+
path: string; // chemin de stockage — à passer à createJob()
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Exemple :**
|
|
98
|
+
```ts
|
|
99
|
+
const { uploadUrl, path } = await reqvet.getSignedUploadUrl('consultation.webm', 'audio/webm');
|
|
100
|
+
|
|
101
|
+
await fetch(uploadUrl, {
|
|
102
|
+
method: 'PUT',
|
|
103
|
+
headers: { 'Content-Type': 'audio/webm' },
|
|
104
|
+
body: audioBuffer,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const job = await reqvet.createJob({ audioFile: path, animalName, templateId });
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
70
111
|
|
|
71
112
|
### `uploadAudio(audio, fileName?)`
|
|
72
113
|
|
|
73
|
-
|
|
114
|
+
Uploader un fichier audio vers le stockage ReqVet via `/api/v1/upload`.
|
|
74
115
|
|
|
75
|
-
**
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
116
|
+
> ⚠️ **Limite serveur** : `/api/v1/upload` est une Vercel Serverless Function avec une limite de payload de ~4.5 MB. Pour les proxies serveur gérant des fichiers > 4 MB, utilisez [`getSignedUploadUrl()`](#getsigneduploadurlfilename-contenttype--recommandé-serveur) à la place.
|
|
117
|
+
>
|
|
118
|
+
> `uploadAudio()` reste adapté pour les contextes navigateur (Blob/File natif, pas de limite Vercel) ou les fichiers légers.
|
|
119
|
+
|
|
120
|
+
**Paramètres :**
|
|
121
|
+
| Nom | Type | Requis | Description |
|
|
122
|
+
|-----|------|--------|-------------|
|
|
123
|
+
| `audio` | `Blob \| File \| Buffer` | ✅ | Données audio |
|
|
124
|
+
| `fileName` | `string` | — | Nom du fichier, utilisé pour inférer le type MIME (défaut : `audio.webm`) |
|
|
125
|
+
|
|
126
|
+
**Réponse :**
|
|
80
127
|
|
|
81
|
-
**Response:**
|
|
82
128
|
```ts
|
|
83
129
|
{
|
|
84
|
-
audio_file: string;
|
|
85
|
-
path: string;
|
|
130
|
+
audio_file: string; // chemin de stockage canonique — à passer à createJob()
|
|
131
|
+
path: string; // alias de audio_file
|
|
86
132
|
size_bytes: number;
|
|
87
133
|
content_type: string;
|
|
88
134
|
}
|
|
89
135
|
```
|
|
90
136
|
|
|
91
|
-
|
|
137
|
+
Formats supportés : `mp3`, `wav`, `webm`, `ogg`, `m4a`, `aac`, `flac`. Taille max : 100 Mo.
|
|
92
138
|
|
|
93
139
|
---
|
|
94
140
|
|
|
95
141
|
### `generateReport(params)`
|
|
96
142
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
**
|
|
100
|
-
|
|
|
101
|
-
|
|
102
|
-
| `audio` | `Blob \| File \| Buffer` | ✅ |
|
|
103
|
-
| `animalName` | `string` | ✅ |
|
|
104
|
-
| `templateId` | `string` | ✅ |
|
|
105
|
-
| `fileName` | `string` | — |
|
|
106
|
-
| `callbackUrl` | `string` | — |
|
|
107
|
-
| `metadata` | `Record<string, unknown>` | — |
|
|
108
|
-
| `extraInstructions` | `string` | — |
|
|
109
|
-
| `waitForResult` | `boolean` | — |
|
|
110
|
-
| `onStatus` | `(status: string) => void` | — |
|
|
111
|
-
|
|
112
|
-
**
|
|
113
|
-
|
|
114
|
-
- `waitForResult:
|
|
143
|
+
Wrapper pratique : `uploadAudio → createJob`. Attend optionnellement la fin du traitement.
|
|
144
|
+
|
|
145
|
+
**Paramètres :**
|
|
146
|
+
| Nom | Type | Requis | Description |
|
|
147
|
+
|-----|------|--------|-------------|
|
|
148
|
+
| `audio` | `Blob \| File \| Buffer` | ✅ | Données audio |
|
|
149
|
+
| `animalName` | `string` | ✅ | Nom de l'animal |
|
|
150
|
+
| `templateId` | `string` | ✅ | UUID du template (depuis `listTemplates()`) |
|
|
151
|
+
| `fileName` | `string` | — | Nom du fichier |
|
|
152
|
+
| `callbackUrl` | `string` | — | Votre endpoint webhook (HTTPS, accessible publiquement) |
|
|
153
|
+
| `metadata` | `Record<string, unknown>` | — | Données passthrough (ex. `{ consultationId, vetId }`) |
|
|
154
|
+
| `extraInstructions` | `string` | — | Instructions de génération supplémentaires injectées dans le prompt |
|
|
155
|
+
| `waitForResult` | `boolean` | — | Si `true`, poll et retourne le rapport final. Défaut : `false` |
|
|
156
|
+
| `onStatus` | `(status: string) => void` | — | Appelé à chaque poll (uniquement si `waitForResult: true`) |
|
|
157
|
+
|
|
158
|
+
**Réponse :**
|
|
159
|
+
|
|
160
|
+
- `waitForResult: false` (défaut) : `{ job_id: string, status: 'pending' }`
|
|
161
|
+
- `waitForResult: true` : `ReqVetReport` (voir `waitForJob`)
|
|
115
162
|
|
|
116
163
|
---
|
|
117
164
|
|
|
118
165
|
### `createJob(params)`
|
|
119
166
|
|
|
120
|
-
|
|
167
|
+
Démarrer un pipeline de transcription + génération de compte rendu.
|
|
168
|
+
|
|
169
|
+
**Paramètres :**
|
|
170
|
+
| Nom | Type | Requis | Description |
|
|
171
|
+
|-----|------|--------|-------------|
|
|
172
|
+
| `audioFile` | `string` | ✅ | Valeur de `uploadAudio().path` |
|
|
173
|
+
| `animalName` | `string` | ✅ | Nom de l'animal |
|
|
174
|
+
| `templateId` | `string` | ✅ | UUID du template |
|
|
175
|
+
| `callbackUrl` | `string` | — | URL webhook (HTTPS, accessible publiquement). Utilise le webhook par défaut de l'organisation si omis. |
|
|
176
|
+
| `metadata` | `Record<string, unknown>` | — | Données passthrough — pour corréler avec vos propres enregistrements |
|
|
177
|
+
| `extraInstructions` | `string` | — | Instructions de génération supplémentaires (max 5 000 caractères) |
|
|
121
178
|
|
|
122
|
-
**
|
|
123
|
-
| Name | Type | Required | Description |
|
|
124
|
-
|------|------|----------|-------------|
|
|
125
|
-
| `audioFile` | `string` | ✅ | Value of `uploadAudio().path` |
|
|
126
|
-
| `animalName` | `string` | ✅ | Name of the animal |
|
|
127
|
-
| `templateId` | `string` | ✅ | Template UUID |
|
|
128
|
-
| `callbackUrl` | `string` | — | Webhook URL (HTTPS, publicly reachable). Falls back to the org default webhook if omitted. |
|
|
129
|
-
| `metadata` | `Record<string, unknown>` | — | Passthrough data — correlate with your own records |
|
|
130
|
-
| `extraInstructions` | `string` | — | Extra generation instructions (max 5 000 chars) |
|
|
179
|
+
**Réponse :**
|
|
131
180
|
|
|
132
|
-
**Response:**
|
|
133
181
|
```ts
|
|
134
|
-
{
|
|
182
|
+
{
|
|
183
|
+
job_id: string;
|
|
184
|
+
status: 'pending';
|
|
185
|
+
}
|
|
135
186
|
```
|
|
136
187
|
|
|
137
|
-
> **
|
|
188
|
+
> **Limite de débit** : 10 000 requêtes/minute par organisation.
|
|
138
189
|
|
|
139
190
|
---
|
|
140
191
|
|
|
141
192
|
### `listJobs(options?)`
|
|
142
193
|
|
|
143
|
-
|
|
194
|
+
Lister les jobs de l'organisation authentifiée, avec pagination et filtrage.
|
|
144
195
|
|
|
145
|
-
**
|
|
146
|
-
|
|
|
147
|
-
|
|
148
|
-
| `limit` | `number` | `20` |
|
|
149
|
-
| `offset` | `number` | `0` |
|
|
150
|
-
| `status` | `string` | — |
|
|
151
|
-
| `sort` | `string` | `created_at` |
|
|
152
|
-
| `order` | `string` | `desc` | Direction: `asc`
|
|
196
|
+
**Paramètres :**
|
|
197
|
+
| Nom | Type | Défaut | Description |
|
|
198
|
+
|-----|------|--------|-------------|
|
|
199
|
+
| `limit` | `number` | `20` | Résultats par page (1–100) |
|
|
200
|
+
| `offset` | `number` | `0` | Décalage de pagination |
|
|
201
|
+
| `status` | `string` | — | Filtre : `pending` `transcribing` `generating` `completed` `failed` `amending` |
|
|
202
|
+
| `sort` | `string` | `created_at` | Champ de tri : `created_at` ou `updated_at` |
|
|
203
|
+
| `order` | `string` | `desc` | Direction : `asc` ou `desc` |
|
|
204
|
+
|
|
205
|
+
**Réponse :**
|
|
153
206
|
|
|
154
|
-
**Response:**
|
|
155
207
|
```ts
|
|
156
208
|
{
|
|
157
209
|
jobs: JobSummary[];
|
|
@@ -163,25 +215,26 @@ List jobs for the authenticated organization, with pagination and filtering.
|
|
|
163
215
|
|
|
164
216
|
### `getJob(jobId)`
|
|
165
217
|
|
|
166
|
-
|
|
218
|
+
Obtenir l'état actuel et le résultat d'un job.
|
|
219
|
+
|
|
220
|
+
**Champs de réponse par statut :**
|
|
167
221
|
|
|
168
|
-
|
|
222
|
+
| Champ | `pending` | `transcribing` | `generating` | `completed` | `failed` |
|
|
223
|
+
| --------------- | :-------: | :------------: | :----------: | :---------: | :------: |
|
|
224
|
+
| `job_id` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
225
|
+
| `status` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
226
|
+
| `animal_name` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
227
|
+
| `metadata` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
228
|
+
| `transcription` | — | — | ✅ | ✅ | — |
|
|
229
|
+
| `result.html` | — | — | — | ✅ | — |
|
|
230
|
+
| `result.fields` | — | — | — | ✅\* | — |
|
|
231
|
+
| `cost` | — | — | — | ✅ | — |
|
|
232
|
+
| `error` | — | — | — | — | ✅ |
|
|
169
233
|
|
|
170
|
-
|
|
171
|
-
|-------|:---------:|:--------------:|:------------:|:-----------:|:--------:|
|
|
172
|
-
| `job_id` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
173
|
-
| `status` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
174
|
-
| `animal_name` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
175
|
-
| `metadata` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
176
|
-
| `transcription` | — | — | ✅ | ✅ | — |
|
|
177
|
-
| `result.html` | — | — | — | ✅ | — |
|
|
178
|
-
| `result.fields` | — | — | — | ✅* | — |
|
|
179
|
-
| `cost` | — | — | — | ✅ | — |
|
|
180
|
-
| `error` | — | — | — | — | ✅ |
|
|
234
|
+
\*`result.fields` n'est présent que si votre organisation a un `field_schema` configuré (extraction de données structurées). Vaut `null` sinon. Voir [Schéma de champs](#5-schéma-de-champs) ci-dessous.
|
|
181
235
|
|
|
182
|
-
|
|
236
|
+
**Structure du coût (jobs terminés) :**
|
|
183
237
|
|
|
184
|
-
**Cost structure (completed jobs):**
|
|
185
238
|
```ts
|
|
186
239
|
cost: {
|
|
187
240
|
transcription_usd: number;
|
|
@@ -190,91 +243,104 @@ cost: {
|
|
|
190
243
|
}
|
|
191
244
|
```
|
|
192
245
|
|
|
193
|
-
> **Note
|
|
246
|
+
> **Note** : `cost` est disponible via `getJob()` et `waitForJob()`, mais n'est **pas** inclus dans les payloads webhook. Récupérez-le avec `getJob()` après réception d'un événement `job.completed` si nécessaire.
|
|
194
247
|
|
|
195
248
|
---
|
|
196
249
|
|
|
197
250
|
### `waitForJob(jobId, onStatus?)`
|
|
198
251
|
|
|
199
|
-
|
|
252
|
+
Poller jusqu'à ce qu'un job atteigne `completed` ou `failed`. Respecte `pollInterval` et `timeout`.
|
|
253
|
+
|
|
254
|
+
**Réponse (`ReqVetReport`) :**
|
|
200
255
|
|
|
201
|
-
**Response (`ReqVetReport`):**
|
|
202
256
|
```ts
|
|
203
257
|
{
|
|
204
258
|
jobId: string;
|
|
205
|
-
html: string;
|
|
206
|
-
fields: ExtractedFields | null;
|
|
259
|
+
html: string; // HTML du compte rendu généré
|
|
260
|
+
fields: ExtractedFields | null; // null si aucun field_schema configuré
|
|
207
261
|
transcription: string;
|
|
208
262
|
animalName: string;
|
|
209
|
-
cost: {
|
|
263
|
+
cost: {
|
|
264
|
+
transcription_usd: number;
|
|
265
|
+
generation_usd: number;
|
|
266
|
+
total_usd: number;
|
|
267
|
+
}
|
|
210
268
|
metadata: Record<string, unknown>;
|
|
211
269
|
}
|
|
212
270
|
```
|
|
213
271
|
|
|
214
|
-
|
|
272
|
+
Lève une `ReqVetError` si le job échoue ou si le timeout est dépassé.
|
|
215
273
|
|
|
216
274
|
---
|
|
217
275
|
|
|
218
276
|
### `regenerateJob(jobId, options?)`
|
|
219
277
|
|
|
220
|
-
|
|
278
|
+
Régénérer le compte rendu d'un job terminé — par exemple avec des instructions différentes ou un autre template.
|
|
279
|
+
|
|
280
|
+
**Paramètres :**
|
|
281
|
+
| Nom | Type | Description |
|
|
282
|
+
|-----|------|-------------|
|
|
283
|
+
| `extraInstructions` | `string` | Nouvelles instructions (max 2 000 caractères) |
|
|
284
|
+
| `templateId` | `string` | Basculer vers un autre template |
|
|
221
285
|
|
|
222
|
-
**
|
|
223
|
-
| Name | Type | Description |
|
|
224
|
-
|------|------|-------------|
|
|
225
|
-
| `extraInstructions` | `string` | New instructions (max 2 000 chars) |
|
|
226
|
-
| `templateId` | `string` | Switch to a different template |
|
|
286
|
+
**Réponse :**
|
|
227
287
|
|
|
228
|
-
**Response:**
|
|
229
288
|
```ts
|
|
230
289
|
{ job_id: string; status: 'completed'; result: { html: string; fields?: ExtractedFields } }
|
|
231
290
|
```
|
|
232
291
|
|
|
233
|
-
|
|
292
|
+
Déclenche un événement webhook `job.regenerated` si un `callbackUrl` est configuré.
|
|
234
293
|
|
|
235
|
-
> **
|
|
294
|
+
> **Limite de débit** : 30 requêtes/minute par organisation.
|
|
236
295
|
|
|
237
296
|
---
|
|
238
297
|
|
|
239
298
|
### `amendJob(jobId, params)`
|
|
240
299
|
|
|
241
|
-
|
|
300
|
+
Ajouter un audio complémentaire à un job terminé. Le nouvel audio est transcrit, fusionné avec la transcription existante, et le compte rendu est régénéré.
|
|
242
301
|
|
|
243
|
-
**
|
|
244
|
-
|
|
|
245
|
-
|
|
246
|
-
| `audioFile` | `string` | ✅ |
|
|
247
|
-
| `templateId` | `string` | — |
|
|
302
|
+
**Paramètres :**
|
|
303
|
+
| Nom | Type | Requis | Description |
|
|
304
|
+
|-----|------|--------|-------------|
|
|
305
|
+
| `audioFile` | `string` | ✅ | Valeur de `uploadAudio().path` |
|
|
306
|
+
| `templateId` | `string` | — | Basculer vers un autre template |
|
|
307
|
+
|
|
308
|
+
**Réponse :**
|
|
248
309
|
|
|
249
|
-
**Response:**
|
|
250
310
|
```ts
|
|
251
|
-
{
|
|
311
|
+
{
|
|
312
|
+
job_id: string;
|
|
313
|
+
status: 'amending';
|
|
314
|
+
amendment_number: number;
|
|
315
|
+
message: string;
|
|
316
|
+
}
|
|
252
317
|
```
|
|
253
318
|
|
|
254
|
-
|
|
319
|
+
Le job repasse à `completed` quand l'amendement est terminé. Utilisez `waitForJob()` ou écoutez l'événement webhook `job.amended`. Plusieurs amendements sont supportés — chacun est ajouté à la transcription complète.
|
|
255
320
|
|
|
256
321
|
---
|
|
257
322
|
|
|
258
323
|
### `reformulateReport(jobId, params)`
|
|
259
324
|
|
|
260
|
-
|
|
325
|
+
Générer une version alternative d'un compte rendu terminé pour une audience spécifique.
|
|
261
326
|
|
|
262
|
-
**
|
|
263
|
-
|
|
|
264
|
-
|
|
327
|
+
**Paramètres :**
|
|
328
|
+
| Nom | Type | Requis | Description |
|
|
329
|
+
|-----|------|--------|-------------|
|
|
265
330
|
| `purpose` | `string` | ✅ | `owner` `referral` `summary` `custom` `diagnostic_hypothesis` |
|
|
266
|
-
| `customInstructions` | `string` |
|
|
267
|
-
|
|
268
|
-
**
|
|
269
|
-
|
|
|
270
|
-
|
|
271
|
-
| `owner` |
|
|
272
|
-
| `referral` |
|
|
273
|
-
| `summary` |
|
|
274
|
-
| `diagnostic_hypothesis` |
|
|
275
|
-
| `custom` |
|
|
276
|
-
|
|
277
|
-
**
|
|
331
|
+
| `customInstructions` | `string` | Si `purpose: 'custom'` | Instructions de reformulation |
|
|
332
|
+
|
|
333
|
+
**Valeurs de `purpose` :**
|
|
334
|
+
| Valeur | Résultat |
|
|
335
|
+
|--------|----------|
|
|
336
|
+
| `owner` | Version simplifiée pour le propriétaire de l'animal |
|
|
337
|
+
| `referral` | Résumé clinique pour un spécialiste |
|
|
338
|
+
| `summary` | Note interne courte |
|
|
339
|
+
| `diagnostic_hypothesis` | Liste de diagnostics différentiels |
|
|
340
|
+
| `custom` | Défini par `customInstructions` |
|
|
341
|
+
|
|
342
|
+
**Réponse (`ReqVetReformulation`) :**
|
|
343
|
+
|
|
278
344
|
```ts
|
|
279
345
|
{
|
|
280
346
|
id: string;
|
|
@@ -287,13 +353,13 @@ Generate an alternative version of a completed report for a specific audience.
|
|
|
287
353
|
}
|
|
288
354
|
```
|
|
289
355
|
|
|
290
|
-
> **
|
|
356
|
+
> **Limite de débit** : 30 requêtes/minute par organisation.
|
|
291
357
|
|
|
292
358
|
---
|
|
293
359
|
|
|
294
360
|
### `listReformulations(jobId)`
|
|
295
361
|
|
|
296
|
-
**
|
|
362
|
+
**Réponse :** `{ reformulations: ReqVetReformulation[] }`
|
|
297
363
|
|
|
298
364
|
---
|
|
299
365
|
|
|
@@ -301,23 +367,23 @@ Generate an alternative version of a completed report for a specific audience.
|
|
|
301
367
|
|
|
302
368
|
#### `listTemplates()` → `{ custom: Template[], system: Template[] }`
|
|
303
369
|
|
|
304
|
-
- **`system`** — templates
|
|
305
|
-
- **`custom`** — templates
|
|
370
|
+
- **`system`** — templates créés par ReqVet, visibles par toutes les organisations. Lecture seule. Commencez ici pour trouver les `templateId` disponibles.
|
|
371
|
+
- **`custom`** — templates créés par votre organisation. Modifiables via `createTemplate` / `updateTemplate`.
|
|
306
372
|
|
|
307
373
|
#### `getTemplate(templateId)` → `Template`
|
|
308
374
|
|
|
309
375
|
#### `createTemplate(params)` → `Template`
|
|
310
376
|
|
|
311
|
-
|
|
|
312
|
-
|
|
313
|
-
| `name`
|
|
314
|
-
| `content`
|
|
315
|
-
| `description` | `string`
|
|
316
|
-
| `is_default`
|
|
377
|
+
| Nom | Type | Requis |
|
|
378
|
+
| ------------- | --------- | ------ |
|
|
379
|
+
| `name` | `string` | ✅ |
|
|
380
|
+
| `content` | `string` | ✅ |
|
|
381
|
+
| `description` | `string` | — |
|
|
382
|
+
| `is_default` | `boolean` | — |
|
|
317
383
|
|
|
318
384
|
#### `updateTemplate(templateId, updates)` → `Template`
|
|
319
385
|
|
|
320
|
-
|
|
386
|
+
Tous les champs sont optionnels (mise à jour partielle). Mêmes champs que `createTemplate`.
|
|
321
387
|
|
|
322
388
|
#### `deleteTemplate(templateId)` → `{ success: true }`
|
|
323
389
|
|
|
@@ -325,15 +391,15 @@ All fields optional (partial update). Same fields as `createTemplate`.
|
|
|
325
391
|
|
|
326
392
|
### `health()`
|
|
327
393
|
|
|
328
|
-
**
|
|
394
|
+
**Réponse :** `{ status: 'ok' | 'degraded'; timestamp: string }`
|
|
329
395
|
|
|
330
396
|
---
|
|
331
397
|
|
|
332
|
-
## 5)
|
|
398
|
+
## 5) Schéma de champs
|
|
333
399
|
|
|
334
|
-
|
|
400
|
+
Si votre organisation a un `field_schema` configuré, ReqVet extrait des champs structurés de chaque consultation en plus de générer le compte rendu HTML.
|
|
335
401
|
|
|
336
|
-
|
|
402
|
+
Exemple de `result.fields` pour un bilan de santé standard :
|
|
337
403
|
|
|
338
404
|
```json
|
|
339
405
|
{
|
|
@@ -347,23 +413,23 @@ Example `result.fields` for a standard checkup:
|
|
|
347
413
|
}
|
|
348
414
|
```
|
|
349
415
|
|
|
350
|
-
`fields`
|
|
416
|
+
`fields` vaut `null` si aucun `field_schema` n'est configuré pour votre organisation. Contactez votre responsable de compte ReqVet pour activer et configurer l'extraction structurée.
|
|
351
417
|
|
|
352
418
|
---
|
|
353
419
|
|
|
354
|
-
## 6)
|
|
420
|
+
## 6) Événements webhook
|
|
355
421
|
|
|
356
|
-
ReqVet
|
|
422
|
+
ReqVet POSTe sur votre `callbackUrl` quand un job change d'état. Tous les événements partagent le même format.
|
|
357
423
|
|
|
358
|
-
###
|
|
424
|
+
### En-têtes
|
|
359
425
|
|
|
360
426
|
```
|
|
361
427
|
Content-Type: application/json
|
|
362
|
-
X-ReqVet-Signature: sha256=<hex> (
|
|
363
|
-
X-ReqVet-Timestamp: <unix_ms> (
|
|
428
|
+
X-ReqVet-Signature: sha256=<hex> (uniquement si l'organisation a un webhook_secret)
|
|
429
|
+
X-ReqVet-Timestamp: <unix_ms> (uniquement si l'organisation a un webhook_secret)
|
|
364
430
|
```
|
|
365
431
|
|
|
366
|
-
###
|
|
432
|
+
### Types d'événements et payloads
|
|
367
433
|
|
|
368
434
|
#### `job.completed`
|
|
369
435
|
|
|
@@ -379,7 +445,7 @@ X-ReqVet-Timestamp: <unix_ms> (only if org has a webhook_secret)
|
|
|
379
445
|
}
|
|
380
446
|
```
|
|
381
447
|
|
|
382
|
-
> `fields`
|
|
448
|
+
> `fields` est absent si l'organisation n'a pas de `field_schema`. `cost` n'est pas dans le webhook — récupérez-le avec `getJob()` si nécessaire.
|
|
383
449
|
|
|
384
450
|
---
|
|
385
451
|
|
|
@@ -399,14 +465,14 @@ X-ReqVet-Timestamp: <unix_ms> (only if org has a webhook_secret)
|
|
|
399
465
|
|
|
400
466
|
#### `job.amended`
|
|
401
467
|
|
|
402
|
-
|
|
468
|
+
Envoyé quand un amendement (`amendJob`) se termine avec succès.
|
|
403
469
|
|
|
404
470
|
```json
|
|
405
471
|
{
|
|
406
472
|
"event": "job.amended",
|
|
407
473
|
"job_id": "a1b2c3d4-...",
|
|
408
474
|
"animal_name": "Rex",
|
|
409
|
-
"transcription": "...
|
|
475
|
+
"transcription": "...transcription complète incluant l'amendement...",
|
|
410
476
|
"html": "<section class=\"cr\">...</section>",
|
|
411
477
|
"amendment_number": 1,
|
|
412
478
|
"fields": { "espece": "Chien", "poids": 28.5 },
|
|
@@ -418,7 +484,7 @@ Sent when an amendment (`amendJob`) completes successfully.
|
|
|
418
484
|
|
|
419
485
|
#### `job.amend_failed`
|
|
420
486
|
|
|
421
|
-
|
|
487
|
+
Envoyé quand la transcription d'un amendement échoue. Le compte rendu original est préservé.
|
|
422
488
|
|
|
423
489
|
```json
|
|
424
490
|
{
|
|
@@ -434,7 +500,7 @@ Sent when amendment transcription fails. The original report is preserved.
|
|
|
434
500
|
|
|
435
501
|
#### `job.regenerated`
|
|
436
502
|
|
|
437
|
-
|
|
503
|
+
Envoyé quand `regenerateJob()` se termine.
|
|
438
504
|
|
|
439
505
|
```json
|
|
440
506
|
{
|
|
@@ -449,68 +515,68 @@ Sent when `regenerateJob()` completes.
|
|
|
449
515
|
|
|
450
516
|
---
|
|
451
517
|
|
|
452
|
-
###
|
|
518
|
+
### Politique de retry
|
|
453
519
|
|
|
454
|
-
ReqVet
|
|
520
|
+
ReqVet retente les livraisons webhook échouées **3 fois** avec des délais de 0s, 2s et 5s. Après 3 échecs, l'événement est marqué comme non livré. Implémentez l'idempotence dans votre handler (dédoublonnez sur `job_id + event`).
|
|
455
521
|
|
|
456
522
|
---
|
|
457
523
|
|
|
458
|
-
## 7)
|
|
524
|
+
## 7) Vérification des webhooks
|
|
459
525
|
|
|
460
526
|
```ts
|
|
461
|
-
import { verifyWebhookSignature } from '@reqvet/sdk/webhooks';
|
|
527
|
+
import { verifyWebhookSignature } from '@reqvet-sdk/sdk/webhooks';
|
|
462
528
|
|
|
463
529
|
const { ok, reason } = verifyWebhookSignature({
|
|
464
530
|
secret: process.env.REQVET_WEBHOOK_SECRET!,
|
|
465
|
-
rawBody,
|
|
466
|
-
signature,
|
|
467
|
-
timestamp,
|
|
468
|
-
maxSkewMs: 5 * 60 * 1000,
|
|
531
|
+
rawBody, // corps brut de la requête — à lire AVANT JSON.parse
|
|
532
|
+
signature, // valeur de l'en-tête X-ReqVet-Signature
|
|
533
|
+
timestamp, // valeur de l'en-tête X-ReqVet-Timestamp
|
|
534
|
+
maxSkewMs: 5 * 60 * 1000, // rejeter les événements de plus de 5 min (défaut)
|
|
469
535
|
});
|
|
470
536
|
```
|
|
471
537
|
|
|
472
|
-
|
|
538
|
+
Raisons de rejet : `missing_headers` `invalid_timestamp` `stale_timestamp` `invalid_signature`
|
|
473
539
|
|
|
474
|
-
|
|
540
|
+
Voir [SECURITY.md](./SECURITY.md) pour un exemple d'implémentation complet avec Next.js.
|
|
475
541
|
|
|
476
542
|
---
|
|
477
543
|
|
|
478
|
-
## 8)
|
|
544
|
+
## 8) Gestion des erreurs
|
|
479
545
|
|
|
480
|
-
|
|
546
|
+
Toutes les méthodes lèvent une `ReqVetError` en cas d'erreur HTTP ou de panne réseau :
|
|
481
547
|
|
|
482
548
|
```ts
|
|
483
|
-
import { ReqVetError } from '@reqvet/sdk';
|
|
549
|
+
import { ReqVetError } from '@reqvet-sdk/sdk';
|
|
484
550
|
|
|
485
551
|
try {
|
|
486
552
|
const report = await reqvet.waitForJob(jobId);
|
|
487
553
|
} catch (err) {
|
|
488
554
|
if (err instanceof ReqVetError) {
|
|
489
|
-
console.error(err.message);
|
|
490
|
-
console.error(err.status);
|
|
491
|
-
console.error(err.body);
|
|
555
|
+
console.error(err.message); // message lisible par un humain
|
|
556
|
+
console.error(err.status); // statut HTTP (0 pour les erreurs réseau/timeout)
|
|
557
|
+
console.error(err.body); // corps brut de la réponse
|
|
492
558
|
}
|
|
493
559
|
}
|
|
494
560
|
```
|
|
495
561
|
|
|
496
|
-
|
|
|
497
|
-
|
|
498
|
-
| `400`
|
|
499
|
-
| `401`
|
|
500
|
-
| `403`
|
|
501
|
-
| `404`
|
|
502
|
-
| `429`
|
|
503
|
-
| `500`
|
|
562
|
+
| Statut | Signification |
|
|
563
|
+
| ------ | ------------------------------------------------- |
|
|
564
|
+
| `400` | Erreur de validation — vérifiez `err.body.issues` |
|
|
565
|
+
| `401` | Clé API invalide ou manquante |
|
|
566
|
+
| `403` | Quota mensuel dépassé |
|
|
567
|
+
| `404` | Job ou template introuvable |
|
|
568
|
+
| `429` | Limite de débit dépassée — attendez et réessayez |
|
|
569
|
+
| `500` | Erreur interne ReqVet |
|
|
504
570
|
|
|
505
571
|
---
|
|
506
572
|
|
|
507
|
-
## 9)
|
|
573
|
+
## 9) Checklist d'intégration
|
|
508
574
|
|
|
509
|
-
- [ ] SDK
|
|
510
|
-
- [ ] `listTemplates()`
|
|
511
|
-
- [ ] `metadata`
|
|
512
|
-
- [ ]
|
|
513
|
-
- [ ]
|
|
514
|
-
- [ ]
|
|
515
|
-
- [ ]
|
|
516
|
-
- [ ] `REQVET_API_KEY`
|
|
575
|
+
- [ ] SDK utilisé **côté serveur uniquement** — clé API jamais dans les bundles navigateur
|
|
576
|
+
- [ ] `listTemplates()` appelé au démarrage pour découvrir les `templateId` disponibles
|
|
577
|
+
- [ ] `metadata` utilisé pour corréler les jobs ReqVet avec vos propres enregistrements (`consultationId`, `vetId`, etc.)
|
|
578
|
+
- [ ] L'endpoint webhook gère les 5 types d'événements : `job.completed`, `job.failed`, `job.amended`, `job.amend_failed`, `job.regenerated`
|
|
579
|
+
- [ ] Signature webhook vérifiée sur chaque événement entrant
|
|
580
|
+
- [ ] Vérification anti-replay du timestamp activée (`maxSkewMs`)
|
|
581
|
+
- [ ] Idempotence implémentée — dédoublonnage sur `job_id + event`
|
|
582
|
+
- [ ] `REQVET_API_KEY` et `REQVET_WEBHOOK_SECRET` stockés dans des variables d'environnement, jamais en dur dans le code
|
package/SECURITY.md
CHANGED
|
@@ -29,7 +29,7 @@ Example proxy route (Next.js App Router):
|
|
|
29
29
|
```ts
|
|
30
30
|
// app/api/reqvet/generate/route.ts
|
|
31
31
|
import { NextRequest, NextResponse } from 'next/server';
|
|
32
|
-
import ReqVet from '@reqvet/sdk';
|
|
32
|
+
import ReqVet from '@reqvet-sdk/sdk';
|
|
33
33
|
|
|
34
34
|
const reqvet = new ReqVet(process.env.REQVET_API_KEY!);
|
|
35
35
|
|
|
@@ -37,7 +37,21 @@ export async function POST(req: NextRequest) {
|
|
|
37
37
|
const form = await req.formData();
|
|
38
38
|
const audio = form.get('audio') as File;
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
// Use getSignedUploadUrl() instead of uploadAudio() for server-side proxies.
|
|
41
|
+
// uploadAudio() posts to /api/v1/upload (Vercel Serverless Function, ~4.5 MB limit).
|
|
42
|
+
// getSignedUploadUrl() uploads directly to Supabase — no size limit.
|
|
43
|
+
const { uploadUrl, path } = await reqvet.getSignedUploadUrl(
|
|
44
|
+
audio.name || 'consultation.webm',
|
|
45
|
+
audio.type || 'audio/webm',
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const audioBuffer = Buffer.from(await audio.arrayBuffer());
|
|
49
|
+
await fetch(uploadUrl, {
|
|
50
|
+
method: 'PUT',
|
|
51
|
+
headers: { 'Content-Type': audio.type || 'audio/webm' },
|
|
52
|
+
body: audioBuffer,
|
|
53
|
+
});
|
|
54
|
+
|
|
41
55
|
const job = await reqvet.createJob({
|
|
42
56
|
audioFile: path,
|
|
43
57
|
animalName: form.get('animalName') as string,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reqvet-sdk/sdk",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.2",
|
|
4
4
|
"description": "Official JavaScript SDK for the ReqVet veterinary report generation API.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -18,7 +18,13 @@
|
|
|
18
18
|
},
|
|
19
19
|
"./package.json": "./package.json"
|
|
20
20
|
},
|
|
21
|
-
"files": [
|
|
21
|
+
"files": [
|
|
22
|
+
"src/",
|
|
23
|
+
"README.md",
|
|
24
|
+
"SDK_REFERENCE.md",
|
|
25
|
+
"CHANGELOG.md",
|
|
26
|
+
"SECURITY.md"
|
|
27
|
+
],
|
|
22
28
|
"keywords": [
|
|
23
29
|
"reqvet",
|
|
24
30
|
"veterinary",
|
package/src/index.d.ts
CHANGED
|
@@ -15,6 +15,13 @@ export interface UploadResult {
|
|
|
15
15
|
content_type: string;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
export interface SignedUploadResult {
|
|
19
|
+
/** Presigned URL for direct PUT upload to Supabase storage */
|
|
20
|
+
uploadUrl: string;
|
|
21
|
+
/** Storage path to pass to createJob({ audioFile: path }) */
|
|
22
|
+
path: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
18
25
|
export interface JobResult {
|
|
19
26
|
job_id: string;
|
|
20
27
|
status: 'pending' | 'transcribing' | 'generating' | 'completed' | 'failed' | 'amending';
|
|
@@ -201,7 +208,18 @@ export declare class ReqVet {
|
|
|
201
208
|
generateReport(params: GenerateReportParams & { waitForResult?: false }): Promise<JobResult>;
|
|
202
209
|
generateReport(params: GenerateReportParams): Promise<JobResult | ReqVetReport>;
|
|
203
210
|
|
|
204
|
-
/**
|
|
211
|
+
/**
|
|
212
|
+
* Get a presigned URL for direct upload to Supabase storage.
|
|
213
|
+
* Recommended for server-side proxies — bypasses Vercel's ~4.5 MB payload limit.
|
|
214
|
+
* PUT the audio buffer to uploadUrl, then pass path to createJob().
|
|
215
|
+
*/
|
|
216
|
+
getSignedUploadUrl(fileName: string, contentType: string): Promise<SignedUploadResult>;
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Upload an audio file via ReqVet's /api/v1/upload endpoint.
|
|
220
|
+
* ⚠️ Subject to Vercel Serverless Function payload limit (~4.5 MB).
|
|
221
|
+
* For files > 4 MB in server-side contexts, use getSignedUploadUrl() instead.
|
|
222
|
+
*/
|
|
205
223
|
uploadAudio(audio: Blob | Buffer | File, fileName?: string): Promise<UploadResult>;
|
|
206
224
|
|
|
207
225
|
/** Create a generation job */
|
package/src/index.js
CHANGED
|
@@ -108,9 +108,37 @@ class ReqVet {
|
|
|
108
108
|
|
|
109
109
|
// ─── Upload ────────────────────────────────────────────────
|
|
110
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Get a presigned upload URL for direct upload to ReqVet storage (Supabase).
|
|
113
|
+
*
|
|
114
|
+
* Recommended for server-side integrations (e.g. Next.js proxy routes).
|
|
115
|
+
* The file goes directly to Supabase — it never passes through a Vercel
|
|
116
|
+
* Serverless Function, so there is no ~4.5 MB payload limit.
|
|
117
|
+
*
|
|
118
|
+
* Flow:
|
|
119
|
+
* 1. getSignedUploadUrl(fileName, contentType) — tiny JSON request, no file.
|
|
120
|
+
* 2. PUT the audio buffer to uploadUrl (direct to Supabase).
|
|
121
|
+
* 3. Pass path to createJob({ audioFile: path }).
|
|
122
|
+
*
|
|
123
|
+
* @param {string} fileName - File name (e.g. 'consultation.webm')
|
|
124
|
+
* @param {string} contentType - MIME type (e.g. 'audio/webm')
|
|
125
|
+
* @returns {Promise<{uploadUrl: string, path: string}>}
|
|
126
|
+
*/
|
|
127
|
+
async getSignedUploadUrl(fileName, contentType) {
|
|
128
|
+
return this._fetch('POST', '/api/v1/storage/signed-upload', {
|
|
129
|
+
file_name: fileName,
|
|
130
|
+
content_type: contentType,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
111
134
|
/**
|
|
112
135
|
* Upload an audio file to ReqVet storage.
|
|
113
136
|
*
|
|
137
|
+
* ⚠️ This method POSTs the file to /api/v1/upload, which runs as a
|
|
138
|
+
* Vercel Serverless Function (~4.5 MB request limit). For server-side
|
|
139
|
+
* proxies (Next.js, Express…) handling files > 4 MB, prefer
|
|
140
|
+
* getSignedUploadUrl() + a direct PUT to avoid this limit.
|
|
141
|
+
*
|
|
114
142
|
* @param {Blob|Buffer|File} audio - The audio file
|
|
115
143
|
* @param {string} [fileName] - File name
|
|
116
144
|
* @returns {Promise<{audio_file: string, size_bytes: number}>}
|