@reqvet-sdk/sdk 2.2.2 → 2.2.3
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/README.md +71 -0
- package/SDK_REFERENCE.md +229 -1
- package/SECURITY.md +40 -0
- package/package.json +2 -8
- package/src/index.d.ts +70 -0
- package/src/index.js +81 -0
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@ SDK JavaScript/TypeScript officiel pour l'API [ReqVet](https://reqvet.com) — g
|
|
|
16
16
|
- **Reformuler** pour une audience spécifique — propriétaire, référé, spécialiste (`reformulateReport`)
|
|
17
17
|
- **Gérer les templates** (`listTemplates`, `createTemplate`, `updateTemplate`, `deleteTemplate`)
|
|
18
18
|
- **Vérifier les webhooks** avec HMAC (`@reqvet-sdk/sdk/webhooks`)
|
|
19
|
+
- **Provisionner et gérer des cliniques** en mode revendeur multi-tenant (`createOrganization`, `listOrganizations`, `updateOrganization`, `deactivateOrganization`)
|
|
19
20
|
|
|
20
21
|
> **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
22
|
|
|
@@ -141,6 +142,8 @@ export async function POST(req: NextRequest) {
|
|
|
141
142
|
|
|
142
143
|
## API
|
|
143
144
|
|
|
145
|
+
### Génération de comptes rendus
|
|
146
|
+
|
|
144
147
|
| Méthode | Description |
|
|
145
148
|
|---------|-------------|
|
|
146
149
|
| `getSignedUploadUrl(fileName, contentType)` | URL signée Supabase pour upload direct (recommandé serveur) |
|
|
@@ -161,6 +164,18 @@ export async function POST(req: NextRequest) {
|
|
|
161
164
|
| `deleteTemplate(templateId)` | Supprimer un template |
|
|
162
165
|
| `health()` | Vérification de l'état de l'API |
|
|
163
166
|
|
|
167
|
+
### API Partenaire / Revendeur
|
|
168
|
+
|
|
169
|
+
> Ces méthodes nécessitent une clé API revendeur (`rqv_live_...` avec `role='reseller'`), distincte de la clé d'une clinique standard. Contactez votre responsable de compte ReqVet pour obtenir une clé revendeur.
|
|
170
|
+
|
|
171
|
+
| Méthode | Description |
|
|
172
|
+
|---------|-------------|
|
|
173
|
+
| `listOrganizations()` | Lister toutes les cliniques provisionnées par le revendeur |
|
|
174
|
+
| `createOrganization(params)` | Provisionner une nouvelle clinique (génère sa clé API et son webhook secret) |
|
|
175
|
+
| `getOrganization(orgId)` | Obtenir le détail et l'usage du mois d'une clinique |
|
|
176
|
+
| `updateOrganization(orgId, updates)` | Modifier le quota, le statut ou le webhook d'une clinique |
|
|
177
|
+
| `deactivateOrganization(orgId)` | Suspendre une clinique et révoquer ses clés API (soft delete) |
|
|
178
|
+
|
|
164
179
|
## Événements webhook
|
|
165
180
|
|
|
166
181
|
ReqVet déclenche 5 types d'événements : `job.completed`, `job.failed`, `job.amended`, `job.amend_failed`, `job.regenerated`.
|
|
@@ -169,6 +184,56 @@ Les livraisons échouées sont retentées 3 fois (0s, 2s, 5s). Implémentez l'id
|
|
|
169
184
|
|
|
170
185
|
Voir [SDK_REFERENCE.md §6](./SDK_REFERENCE.md#6-webhook-events) pour la structure complète des payloads de chaque événement.
|
|
171
186
|
|
|
187
|
+
## Intégration revendeur (multi-tenant)
|
|
188
|
+
|
|
189
|
+
Si vous êtes un éditeur logiciel intégrant ReqVet pour vos clients (cliniques), utilisez une clé API revendeur pour provisionner et gérer les organisations de manière programmatique.
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
import ReqVet from '@reqvet-sdk/sdk';
|
|
193
|
+
|
|
194
|
+
// Instancier avec la clé revendeur (role='reseller')
|
|
195
|
+
const reseller = new ReqVet(process.env.REQVET_RESELLER_KEY!);
|
|
196
|
+
|
|
197
|
+
// Provisionner une clinique à l'onboarding
|
|
198
|
+
const result = await reseller.createOrganization({
|
|
199
|
+
name: 'Clinique du Parc',
|
|
200
|
+
contactEmail: 'contact@clinique-du-parc.fr',
|
|
201
|
+
externalId: 'votre_id_interne_4892', // votre ID — garantit l'idempotence
|
|
202
|
+
monthlyQuota: 500,
|
|
203
|
+
webhookUrl: 'https://votre-app.com/webhooks/reqvet',
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// ⚠️ Stocker immédiatement ces valeurs — elles ne sont retournées qu'une seule fois
|
|
207
|
+
await db.saveClinicCredentials({
|
|
208
|
+
clinicId: result.organization.id,
|
|
209
|
+
apiKey: result.api_key, // rqv_live_...
|
|
210
|
+
webhookSecret: result.webhook_secret, // whsec_...
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// La clinique utilise ensuite son propre client ReqVet avec sa clé
|
|
214
|
+
const clinic = new ReqVet(result.api_key);
|
|
215
|
+
const { system } = await clinic.listTemplates();
|
|
216
|
+
const job = await clinic.createJob({ audioFile, animalName, templateId: system[0].id });
|
|
217
|
+
|
|
218
|
+
// Lister toutes les cliniques avec leur usage du mois
|
|
219
|
+
const { organizations } = await reseller.listOrganizations();
|
|
220
|
+
// [{ id, name, is_active, monthly_quota, usage: { jobs_this_month, quota_remaining } }]
|
|
221
|
+
|
|
222
|
+
// Modifier le quota d'une clinique
|
|
223
|
+
await reseller.updateOrganization(orgId, { monthlyQuota: 1000 });
|
|
224
|
+
|
|
225
|
+
// Suspendre une clinique (révoque aussi ses clés API)
|
|
226
|
+
await reseller.updateOrganization(orgId, { isActive: false });
|
|
227
|
+
|
|
228
|
+
// Réactiver
|
|
229
|
+
await reseller.updateOrganization(orgId, { isActive: true });
|
|
230
|
+
|
|
231
|
+
// Désactiver définitivement (soft delete — données conservées pour le RGPD)
|
|
232
|
+
await reseller.deactivateOrganization(orgId);
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
> **Idempotence** : si `createOrganization` est appelé plusieurs fois avec le même `externalId`, l'organisation existante est retournée sans créer de doublon. Utile pour rendre votre processus d'onboarding sûr en cas de retry.
|
|
236
|
+
|
|
172
237
|
## TypeScript
|
|
173
238
|
|
|
174
239
|
Définitions TypeScript complètes incluses :
|
|
@@ -181,6 +246,12 @@ import type {
|
|
|
181
246
|
Template,
|
|
182
247
|
ReqVetReformulation,
|
|
183
248
|
ExtractedFields,
|
|
249
|
+
// Partner / Reseller
|
|
250
|
+
PartnerOrganization,
|
|
251
|
+
OrganizationUsage,
|
|
252
|
+
CreateOrganizationParams,
|
|
253
|
+
UpdateOrganizationParams,
|
|
254
|
+
CreateOrganizationResult,
|
|
184
255
|
} from '@reqvet-sdk/sdk';
|
|
185
256
|
```
|
|
186
257
|
|
package/SDK_REFERENCE.md
CHANGED
|
@@ -570,7 +570,227 @@ try {
|
|
|
570
570
|
|
|
571
571
|
---
|
|
572
572
|
|
|
573
|
-
## 9)
|
|
573
|
+
## 9) API Partenaire / Revendeur
|
|
574
|
+
|
|
575
|
+
Ces méthodes sont réservées aux revendeurs (éditeurs logiciels) qui provisionnent et administrent des cliniques clientes. Elles nécessitent une clé API avec `role='reseller'`, distincte des clés d'organisation standard.
|
|
576
|
+
|
|
577
|
+
> **Isolation garantie** : un revendeur ne peut accéder qu'aux organisations qu'il a lui-même créées. La contrainte est appliquée en base de données (`parent_org_id = reseller.orgId`) — il n'est pas possible d'accéder aux données d'un autre revendeur, même avec un `orgId` connu.
|
|
578
|
+
|
|
579
|
+
---
|
|
580
|
+
|
|
581
|
+
### `listOrganizations()`
|
|
582
|
+
|
|
583
|
+
Lister toutes les organisations (cliniques) provisionnées par le revendeur, enrichies de l'usage du mois courant.
|
|
584
|
+
|
|
585
|
+
**Réponse :**
|
|
586
|
+
|
|
587
|
+
```ts
|
|
588
|
+
{
|
|
589
|
+
organizations: Array<{
|
|
590
|
+
id: string;
|
|
591
|
+
name: string;
|
|
592
|
+
contact_email: string | null;
|
|
593
|
+
is_active: boolean;
|
|
594
|
+
monthly_quota: number | null;
|
|
595
|
+
external_id: string | null;
|
|
596
|
+
created_at: string;
|
|
597
|
+
usage: {
|
|
598
|
+
jobs_this_month: number;
|
|
599
|
+
quota_remaining: number | 'unlimited';
|
|
600
|
+
};
|
|
601
|
+
}>;
|
|
602
|
+
}
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
**Exemple :**
|
|
606
|
+
|
|
607
|
+
```ts
|
|
608
|
+
const reseller = new ReqVet(process.env.REQVET_RESELLER_KEY!);
|
|
609
|
+
|
|
610
|
+
const { organizations } = await reseller.listOrganizations();
|
|
611
|
+
|
|
612
|
+
for (const org of organizations) {
|
|
613
|
+
console.log(`${org.name} — ${org.usage.jobs_this_month} jobs ce mois / quota ${org.monthly_quota}`);
|
|
614
|
+
}
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
---
|
|
618
|
+
|
|
619
|
+
### `createOrganization(params)`
|
|
620
|
+
|
|
621
|
+
Provisionner une nouvelle organisation (clinique) sous le compte revendeur.
|
|
622
|
+
|
|
623
|
+
Cette méthode crée automatiquement :
|
|
624
|
+
- l'enregistrement de l'organisation dans ReqVet
|
|
625
|
+
- une clé API `rqv_live_...` pour la clinique (stockée hashée côté serveur)
|
|
626
|
+
- un secret de signature webhook `whsec_...`
|
|
627
|
+
|
|
628
|
+
**Paramètres :**
|
|
629
|
+
|
|
630
|
+
| Nom | Type | Requis | Description |
|
|
631
|
+
|-----|------|--------|-------------|
|
|
632
|
+
| `name` | `string` | ✅ | Nom de la clinique |
|
|
633
|
+
| `contactEmail` | `string` | — | Email de contact |
|
|
634
|
+
| `externalId` | `string` | — | Votre identifiant interne (active l'idempotence — voir ci-dessous) |
|
|
635
|
+
| `monthlyQuota` | `number` | — | Nombre max de jobs par mois (défaut : 100, max : 10 000) |
|
|
636
|
+
| `webhookUrl` | `string` | — | URL de webhook pour les événements de jobs de cette clinique |
|
|
637
|
+
|
|
638
|
+
**Idempotence via `externalId`**
|
|
639
|
+
|
|
640
|
+
Si `externalId` est fourni et qu'une organisation avec ce même identifiant existe déjà sous ce revendeur, la méthode retourne l'organisation existante sans créer de doublon. Le champ `message` est alors présent dans la réponse (`'Organization already exists (idempotent)'`), et `api_key` / `webhook_secret` sont absents (ils ne peuvent pas être récupérés après la création initiale).
|
|
641
|
+
|
|
642
|
+
**Réponse (statut 201 — première création) :**
|
|
643
|
+
|
|
644
|
+
```ts
|
|
645
|
+
{
|
|
646
|
+
organization: {
|
|
647
|
+
id: string;
|
|
648
|
+
name: string;
|
|
649
|
+
monthly_quota: number;
|
|
650
|
+
external_id: string | null;
|
|
651
|
+
};
|
|
652
|
+
api_key: string; // rqv_live_... — à stocker immédiatement, non récupérable ensuite
|
|
653
|
+
webhook_secret: string; // whsec_... — à stocker immédiatement, non récupérable ensuite
|
|
654
|
+
warning: string; // "Save api_key and webhook_secret now — they cannot be retrieved later!"
|
|
655
|
+
}
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
**Réponse (statut 200 — idempotent, organisation déjà existante) :**
|
|
659
|
+
|
|
660
|
+
```ts
|
|
661
|
+
{
|
|
662
|
+
message: 'Organization already exists (idempotent)';
|
|
663
|
+
organization: { id, name, monthly_quota, external_id, is_active };
|
|
664
|
+
// api_key et webhook_secret absents
|
|
665
|
+
}
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
**Exemple :**
|
|
669
|
+
|
|
670
|
+
```ts
|
|
671
|
+
const result = await reseller.createOrganization({
|
|
672
|
+
name: 'Clinique du Parc',
|
|
673
|
+
contactEmail: 'contact@clinique-du-parc.fr',
|
|
674
|
+
externalId: 'votre_id_interne_4892',
|
|
675
|
+
monthlyQuota: 500,
|
|
676
|
+
webhookUrl: 'https://votre-app.com/webhooks/reqvet',
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
if (!result.api_key) {
|
|
680
|
+
// Organisation déjà existante (idempotent) — récupérer la clé depuis votre propre stockage
|
|
681
|
+
const storedKey = await db.getApiKey(result.organization.id);
|
|
682
|
+
} else {
|
|
683
|
+
// Première création — stocker la clé et le secret immédiatement
|
|
684
|
+
await db.saveClinicCredentials({
|
|
685
|
+
orgId: result.organization.id,
|
|
686
|
+
apiKey: result.api_key,
|
|
687
|
+
webhookSecret: result.webhook_secret,
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
---
|
|
693
|
+
|
|
694
|
+
### `getOrganization(orgId)`
|
|
695
|
+
|
|
696
|
+
Obtenir les détails et l'usage du mois courant d'une organisation spécifique.
|
|
697
|
+
|
|
698
|
+
**Paramètres :**
|
|
699
|
+
|
|
700
|
+
| Nom | Type | Requis | Description |
|
|
701
|
+
|-----|------|--------|-------------|
|
|
702
|
+
| `orgId` | `string` | ✅ | UUID de l'organisation |
|
|
703
|
+
|
|
704
|
+
**Réponse :** `PartnerOrganization` (même structure que les éléments de `listOrganizations`, avec `usage`)
|
|
705
|
+
|
|
706
|
+
**Exemple :**
|
|
707
|
+
|
|
708
|
+
```ts
|
|
709
|
+
const org = await reseller.getOrganization('uuid-de-la-clinique');
|
|
710
|
+
console.log(`Quota restant : ${org.usage.quota_remaining}`);
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
---
|
|
714
|
+
|
|
715
|
+
### `updateOrganization(orgId, updates)`
|
|
716
|
+
|
|
717
|
+
Modifier le quota mensuel, l'état d'activation, ou l'URL de webhook d'une organisation.
|
|
718
|
+
|
|
719
|
+
Tous les champs sont optionnels — seuls les champs fournis sont mis à jour.
|
|
720
|
+
|
|
721
|
+
**Paramètres :**
|
|
722
|
+
|
|
723
|
+
| Nom | Type | Description |
|
|
724
|
+
|-----|------|-------------|
|
|
725
|
+
| `orgId` | `string` | UUID de l'organisation |
|
|
726
|
+
| `updates.monthlyQuota` | `number` | Nouveau quota mensuel (1–10 000) |
|
|
727
|
+
| `updates.isActive` | `boolean` | `false` pour suspendre la clinique (révoque aussi ses clés API) |
|
|
728
|
+
| `updates.webhookUrl` | `string` | Nouvelle URL de webhook (`null` pour supprimer) |
|
|
729
|
+
|
|
730
|
+
> **Suspension** : passer `isActive: false` désactive l'organisation **et** révoque toutes ses clés API en cascade. Les jobs déjà en cours ne sont pas interrompus. Pour réactiver, passer `isActive: true` — les clés API restent révoquées et doivent être régénérées manuellement si nécessaire.
|
|
731
|
+
|
|
732
|
+
**Réponse :**
|
|
733
|
+
|
|
734
|
+
```ts
|
|
735
|
+
{
|
|
736
|
+
id: string;
|
|
737
|
+
name: string;
|
|
738
|
+
is_active: boolean;
|
|
739
|
+
monthly_quota: number;
|
|
740
|
+
external_id: string | null;
|
|
741
|
+
}
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
**Exemples :**
|
|
745
|
+
|
|
746
|
+
```ts
|
|
747
|
+
// Augmenter le quota
|
|
748
|
+
await reseller.updateOrganization(orgId, { monthlyQuota: 1000 });
|
|
749
|
+
|
|
750
|
+
// Suspendre une clinique (non-paiement, etc.)
|
|
751
|
+
await reseller.updateOrganization(orgId, { isActive: false });
|
|
752
|
+
|
|
753
|
+
// Réactiver
|
|
754
|
+
await reseller.updateOrganization(orgId, { isActive: true });
|
|
755
|
+
|
|
756
|
+
// Mettre à jour le webhook
|
|
757
|
+
await reseller.updateOrganization(orgId, {
|
|
758
|
+
webhookUrl: 'https://votre-app.com/webhooks/reqvet/v2',
|
|
759
|
+
});
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
---
|
|
763
|
+
|
|
764
|
+
### `deactivateOrganization(orgId)`
|
|
765
|
+
|
|
766
|
+
Désactiver définitivement une organisation et révoquer toutes ses clés API.
|
|
767
|
+
|
|
768
|
+
Il s'agit d'un **soft delete** : les données (jobs, transcriptions, comptes rendus) sont conservées en base pour respecter les obligations RGPD et permettre un audit trail. L'organisation ne peut plus créer de nouveaux jobs.
|
|
769
|
+
|
|
770
|
+
**Paramètres :**
|
|
771
|
+
|
|
772
|
+
| Nom | Type | Requis | Description |
|
|
773
|
+
|-----|------|--------|-------------|
|
|
774
|
+
| `orgId` | `string` | ✅ | UUID de l'organisation |
|
|
775
|
+
|
|
776
|
+
**Réponse :**
|
|
777
|
+
|
|
778
|
+
```ts
|
|
779
|
+
{ success: true; message: 'Organization and API keys deactivated' }
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
**Exemple :**
|
|
783
|
+
|
|
784
|
+
```ts
|
|
785
|
+
await reseller.deactivateOrganization(orgId);
|
|
786
|
+
// L'organisation est désormais inactive, ses clés API sont révoquées
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
---
|
|
790
|
+
|
|
791
|
+
## 10) Checklist d'intégration
|
|
792
|
+
|
|
793
|
+
**Intégration standard (clinique)**
|
|
574
794
|
|
|
575
795
|
- [ ] SDK utilisé **côté serveur uniquement** — clé API jamais dans les bundles navigateur
|
|
576
796
|
- [ ] `listTemplates()` appelé au démarrage pour découvrir les `templateId` disponibles
|
|
@@ -580,3 +800,11 @@ try {
|
|
|
580
800
|
- [ ] Vérification anti-replay du timestamp activée (`maxSkewMs`)
|
|
581
801
|
- [ ] Idempotence implémentée — dédoublonnage sur `job_id + event`
|
|
582
802
|
- [ ] `REQVET_API_KEY` et `REQVET_WEBHOOK_SECRET` stockés dans des variables d'environnement, jamais en dur dans le code
|
|
803
|
+
|
|
804
|
+
**Intégration revendeur (multi-tenant)**
|
|
805
|
+
|
|
806
|
+
- [ ] Clé revendeur (`REQVET_RESELLER_KEY`) distincte et stockée séparément des clés cliniques
|
|
807
|
+
- [ ] `externalId` systématiquement fourni à `createOrganization` pour garantir l'idempotence des onboardings
|
|
808
|
+
- [ ] `api_key` et `webhook_secret` retournés par `createOrganization` stockés immédiatement et de façon sécurisée — ils ne sont affichés qu'une seule fois
|
|
809
|
+
- [ ] Suspension de clinique gérée via `updateOrganization(orgId, { isActive: false })` (et non `deactivateOrganization` qui est irréversible)
|
|
810
|
+
- [ ] Usage mensuel (`usage.jobs_this_month`, `usage.quota_remaining`) consulté via `listOrganizations()` ou `getOrganization()` pour le monitoring et la facturation
|
package/SECURITY.md
CHANGED
|
@@ -117,6 +117,46 @@ await cache.set(key, true, { ttl: 86400 });
|
|
|
117
117
|
// process...
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
+
## Clés revendeur et credentials cliniques
|
|
121
|
+
|
|
122
|
+
Les revendeurs manipulent deux types de secrets supplémentaires qui exigent une attention particulière.
|
|
123
|
+
|
|
124
|
+
### Clé API revendeur
|
|
125
|
+
|
|
126
|
+
La clé revendeur (`rqv_live_...` avec `role='reseller'`) donne accès à l'ensemble des cliniques que vous administrez. Elle est plus sensible qu'une clé clinique standard.
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# Variable d'environnement dédiée, distincte de la clé clinique
|
|
130
|
+
REQVET_RESELLER_KEY=rqv_live_...
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Ne jamais** utiliser la clé revendeur côté client ni la partager avec les cliniques.
|
|
134
|
+
|
|
135
|
+
### `api_key` et `webhook_secret` retournés par `createOrganization`
|
|
136
|
+
|
|
137
|
+
Ces deux valeurs sont **affichées une seule fois** au moment de la création. Après la réponse HTTP initiale, elles ne peuvent pas être récupérées (seul le hash SHA-256 de la clé est stocké côté serveur).
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
const result = await reseller.createOrganization({ name: 'Clinique du Parc', externalId: 'id_4892' });
|
|
141
|
+
|
|
142
|
+
// ✅ Stocker immédiatement dans votre vault ou base chiffrée
|
|
143
|
+
await secretsVault.store({
|
|
144
|
+
orgId: result.organization.id,
|
|
145
|
+
apiKey: result.api_key, // non récupérable après cette réponse
|
|
146
|
+
webhookSecret: result.webhook_secret, // non récupérable après cette réponse
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// ✅ Transmettre la clé à la clinique de manière sécurisée (canal chiffré, jamais par email)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Si une clé clinique est perdue ou compromise, le seul recours est de désactiver l'organisation via `deactivateOrganization()` et d'en créer une nouvelle.
|
|
153
|
+
|
|
154
|
+
### Isolation entre revendeurs
|
|
155
|
+
|
|
156
|
+
L'API Partner filtre toutes les requêtes sur `parent_org_id = reseller.orgId` côté base de données. Il n'est pas possible d'accéder aux cliniques d'un autre revendeur, même en connaissant un `orgId` valide. Ne pas tenter de contourner cette isolation.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
120
160
|
## API key rotation
|
|
121
161
|
|
|
122
162
|
If a key is compromised:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reqvet-sdk/sdk",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.3",
|
|
4
4
|
"description": "Official JavaScript SDK for the ReqVet veterinary report generation API.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -18,13 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
"./package.json": "./package.json"
|
|
20
20
|
},
|
|
21
|
-
"files": [
|
|
22
|
-
"src/",
|
|
23
|
-
"README.md",
|
|
24
|
-
"SDK_REFERENCE.md",
|
|
25
|
-
"CHANGELOG.md",
|
|
26
|
-
"SECURITY.md"
|
|
27
|
-
],
|
|
21
|
+
"files": ["src/", "README.md", "SDK_REFERENCE.md", "CHANGELOG.md", "SECURITY.md"],
|
|
28
22
|
"keywords": [
|
|
29
23
|
"reqvet",
|
|
30
24
|
"veterinary",
|
package/src/index.d.ts
CHANGED
|
@@ -188,6 +188,52 @@ export interface ReqVetReformulation {
|
|
|
188
188
|
created_at: string;
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
// ─── Partner / Reseller Types ────────────────────────────────
|
|
192
|
+
|
|
193
|
+
export interface OrganizationUsage {
|
|
194
|
+
jobs_this_month: number;
|
|
195
|
+
quota_remaining: number | 'unlimited';
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export interface PartnerOrganization {
|
|
199
|
+
id: string;
|
|
200
|
+
name: string;
|
|
201
|
+
contact_email: string | null;
|
|
202
|
+
is_active: boolean;
|
|
203
|
+
monthly_quota: number | null;
|
|
204
|
+
external_id: string | null;
|
|
205
|
+
created_at: string;
|
|
206
|
+
usage?: OrganizationUsage;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export interface CreateOrganizationParams {
|
|
210
|
+
name: string;
|
|
211
|
+
contactEmail?: string;
|
|
212
|
+
/** Your internal ID — enables idempotency (same externalId returns the existing org) */
|
|
213
|
+
externalId?: string;
|
|
214
|
+
/** Max jobs per month (default: 100, max: 10 000) */
|
|
215
|
+
monthlyQuota?: number;
|
|
216
|
+
webhookUrl?: string;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export interface UpdateOrganizationParams {
|
|
220
|
+
monthlyQuota?: number;
|
|
221
|
+
/** Set to false to suspend the clinic and revoke its API keys */
|
|
222
|
+
isActive?: boolean;
|
|
223
|
+
webhookUrl?: string;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export interface CreateOrganizationResult {
|
|
227
|
+
organization: Pick<PartnerOrganization, 'id' | 'name' | 'monthly_quota' | 'external_id'>;
|
|
228
|
+
/** The clinic's API key — returned only once, store it securely! */
|
|
229
|
+
api_key: string;
|
|
230
|
+
/** Webhook signing secret — returned only once, store it securely! */
|
|
231
|
+
webhook_secret: string;
|
|
232
|
+
warning: string;
|
|
233
|
+
/** Returned instead of api_key/webhook_secret when the org already exists (idempotent) */
|
|
234
|
+
message?: string;
|
|
235
|
+
}
|
|
236
|
+
|
|
191
237
|
// ─── Client ──────────────────────────────────────────────────
|
|
192
238
|
|
|
193
239
|
export declare class ReqVetError extends Error {
|
|
@@ -272,6 +318,30 @@ export declare class ReqVet {
|
|
|
272
318
|
/** Delete a template */
|
|
273
319
|
deleteTemplate(templateId: string): Promise<{ success: boolean }>;
|
|
274
320
|
|
|
321
|
+
// ─── Partner / Reseller (requires role='reseller' API key) ──
|
|
322
|
+
|
|
323
|
+
/** List all organizations provisioned by the reseller */
|
|
324
|
+
listOrganizations(): Promise<{ organizations: PartnerOrganization[] }>;
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Provision a new organization/clinic.
|
|
328
|
+
* Idempotent via externalId — returns existing org if already provisioned.
|
|
329
|
+
* ⚠️ api_key and webhook_secret are returned only once.
|
|
330
|
+
*/
|
|
331
|
+
createOrganization(params: CreateOrganizationParams): Promise<CreateOrganizationResult>;
|
|
332
|
+
|
|
333
|
+
/** Get details and current month usage of a specific organization */
|
|
334
|
+
getOrganization(orgId: string): Promise<PartnerOrganization>;
|
|
335
|
+
|
|
336
|
+
/** Update an organization's quota, status, or webhook URL */
|
|
337
|
+
updateOrganization(
|
|
338
|
+
orgId: string,
|
|
339
|
+
updates: UpdateOrganizationParams,
|
|
340
|
+
): Promise<Pick<PartnerOrganization, 'id' | 'name' | 'is_active' | 'monthly_quota' | 'external_id'>>;
|
|
341
|
+
|
|
342
|
+
/** Deactivate an organization and revoke all its API keys (soft delete) */
|
|
343
|
+
deactivateOrganization(orgId: string): Promise<{ success: boolean; message: string }>;
|
|
344
|
+
|
|
275
345
|
/** Health check */
|
|
276
346
|
health(): Promise<{ status: string; services: Record<string, string> }>;
|
|
277
347
|
}
|
package/src/index.js
CHANGED
|
@@ -416,6 +416,87 @@ class ReqVet {
|
|
|
416
416
|
return this._fetch('DELETE', `/api/v1/templates/${templateId}`);
|
|
417
417
|
}
|
|
418
418
|
|
|
419
|
+
// ─── Partner / Reseller ────────────────────────────────────
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* List all organizations provisioned by the reseller.
|
|
423
|
+
* Requires a reseller API key (role='reseller').
|
|
424
|
+
*
|
|
425
|
+
* @returns {Promise<{organizations: Object[]}>}
|
|
426
|
+
*/
|
|
427
|
+
async listOrganizations() {
|
|
428
|
+
return this._fetch('GET', '/api/v1/partner/orgs');
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Provision a new organization (clinic) under the reseller account.
|
|
433
|
+
* Requires a reseller API key (role='reseller').
|
|
434
|
+
*
|
|
435
|
+
* Idempotent via externalId: if an org with the same externalId already
|
|
436
|
+
* exists under this reseller, the existing one is returned (no duplicate).
|
|
437
|
+
*
|
|
438
|
+
* ⚠️ The returned api_key and webhook_secret are shown only once — store them securely.
|
|
439
|
+
*
|
|
440
|
+
* @param {Object} params
|
|
441
|
+
* @param {string} params.name - Clinic name
|
|
442
|
+
* @param {string} [params.contactEmail]
|
|
443
|
+
* @param {string} [params.externalId] - Your internal ID (enables idempotency)
|
|
444
|
+
* @param {number} [params.monthlyQuota] - Job quota per month (default: 100, max: 10 000)
|
|
445
|
+
* @param {string} [params.webhookUrl] - Webhook URL for job results
|
|
446
|
+
* @returns {Promise<Object>}
|
|
447
|
+
*/
|
|
448
|
+
async createOrganization({ name, contactEmail, externalId, monthlyQuota, webhookUrl }) {
|
|
449
|
+
return this._fetch('POST', '/api/v1/partner/orgs', {
|
|
450
|
+
name,
|
|
451
|
+
contact_email: contactEmail,
|
|
452
|
+
external_id: externalId,
|
|
453
|
+
monthly_quota: monthlyQuota,
|
|
454
|
+
webhook_url: webhookUrl,
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Get details and current month usage of a specific organization.
|
|
460
|
+
* Requires a reseller API key (role='reseller').
|
|
461
|
+
*
|
|
462
|
+
* @param {string} orgId
|
|
463
|
+
* @returns {Promise<Object>}
|
|
464
|
+
*/
|
|
465
|
+
async getOrganization(orgId) {
|
|
466
|
+
return this._fetch('GET', `/api/v1/partner/orgs/${orgId}`);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Update an organization's quota, status, or webhook URL.
|
|
471
|
+
* Requires a reseller API key (role='reseller').
|
|
472
|
+
*
|
|
473
|
+
* @param {string} orgId
|
|
474
|
+
* @param {Object} updates
|
|
475
|
+
* @param {number} [updates.monthlyQuota]
|
|
476
|
+
* @param {boolean} [updates.isActive] - Set to false to suspend the clinic
|
|
477
|
+
* @param {string} [updates.webhookUrl]
|
|
478
|
+
* @returns {Promise<Object>}
|
|
479
|
+
*/
|
|
480
|
+
async updateOrganization(orgId, { monthlyQuota, isActive, webhookUrl } = {}) {
|
|
481
|
+
return this._fetch('PATCH', `/api/v1/partner/orgs/${orgId}`, {
|
|
482
|
+
monthly_quota: monthlyQuota,
|
|
483
|
+
is_active: isActive,
|
|
484
|
+
webhook_url: webhookUrl,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Deactivate an organization and revoke all its API keys.
|
|
490
|
+
* Soft delete — data is preserved for audit/GDPR purposes.
|
|
491
|
+
* Requires a reseller API key (role='reseller').
|
|
492
|
+
*
|
|
493
|
+
* @param {string} orgId
|
|
494
|
+
* @returns {Promise<{success: boolean, message: string}>}
|
|
495
|
+
*/
|
|
496
|
+
async deactivateOrganization(orgId) {
|
|
497
|
+
return this._fetch('DELETE', `/api/v1/partner/orgs/${orgId}`);
|
|
498
|
+
}
|
|
499
|
+
|
|
419
500
|
// ─── Health ────────────────────────────────────────────────
|
|
420
501
|
|
|
421
502
|
/**
|