---
title: "RPPS API TypeScript SDK — Documentation"
description: "SDK TypeScript/JavaScript pour interroger toutes les sources de données RPPS (Répertoire Partagé des Professionnels de Santé) de l'Annuaire Santé ANS."
package: "@qrcommunication/rppsapi"
version: "1.1.0"
language: "TypeScript"
license: "PolyForm Noncommercial 1.0.0"
requires: "Node.js 18+, zéro dépendance"
github: "https://github.com/QrCommunication/rppsapi-js"
npm: "https://www.npmjs.com/package/@qrcommunication/rppsapi"
last_updated: "2026-03-29"
tags:
  - rpps
  - annuaire-sante
  - ans
  - fhir
  - data-gouv
  - professionnel-sante
  - typescript
  - javascript
  - medecin
  - pharmacien
---

# RPPS API TypeScript SDK (`@qrcommunication/rppsapi`)

SDK TypeScript pour interroger l'ensemble des sources de données RPPS (Répertoire Partagé des Professionnels de Santé) publiées par l'ANS (Agence du Numérique en Santé) et data.gouv.fr.

En un seul appel, le SDK récupère et fusionne les données de **6 sources publiques** en parallèle pour constituer le profil complet d'un professionnel de santé : identité, activités, diplômes, savoir-faire, cartes CPS et messageries MSSanté.

---

## Table des matières

1. [Installation](#installation)
2. [Quick Start](#quick-start)
3. [Configuration — RppsClientOptions](#configuration)
4. [Méthodes du client](#methodes-client)
   - [getByRpps](#getbyrpps)
   - [search](#search)
5. [Fonctions sources individuelles](#sources-individuelles)
6. [validateRpps](#validateRpps)
7. [Interfaces TypeScript](#interfaces-typescript)
8. [Sources de données](#sources-donnees)
9. [Différences vs SDK PHP](#differences-vs-sdk-php)
10. [Gestion des erreurs](#gestion-erreurs)
11. [Exemples avancés](#exemples-avances)

---

## Installation

```bash
npm install @qrcommunication/rppsapi
# ou
pnpm add @qrcommunication/rppsapi
# ou
yarn add @qrcommunication/rppsapi
```

**Prérequis :**
- Node.js 18.0.0+ (fetch natif requis)
- TypeScript 5.0+ (recommandé)
- Compatible Deno 1.30+ (via `npm:` specifier) et Bun 1.0+

**Important :**
- Package distribué en **ESM uniquement** (`"type": "module"`)
- **Zéro dépendance externe** — utilise `fetch` natif

---

## Quick Start

```typescript
import { RppsClient } from '@qrcommunication/rppsapi';
import type { RppsFullProfile, RppsSearchResponse } from '@qrcommunication/rppsapi';

// Initialiser le client
// Sans fhirApiKey, la source FHIR est automatiquement désactivée
const client = new RppsClient({
  fhirApiKey: process.env.FHIR_API_KEY, // optionnel
  timeout: 30_000,
});

// Profil complet par numéro RPPS (6 sources interrogées en parallèle)
const profil: RppsFullProfile = await client.getByRpps('10005173140');

console.log(
  profil.identite.civiliteExercice.libelle,
  profil.identite.prenomExercice,
  profil.identite.nomExercice,
);
// → "Docteur Jean DUPONT"

console.log('Profession :', profil.profession.libelle);
console.log('Activités :', profil.activites.length);
console.log('Diplômes :', profil.diplomesEtAutorisations.length);
console.log('Durée :', profil.metadata.totalDurationMs, 'ms');

// Recherche par nom + code postal
const resultats: RppsSearchResponse = await client.search(
  { nom: 'DUPONT', codePostal: '75' },
  { page: 1, pageSize: 20 },
);

console.log(`${resultats.total} résultats`);
resultats.results.forEach((r) => {
  console.log(`  ${r.civilite} ${r.prenomExercice} ${r.nomExercice} — ${r.profession.libelle}`);
  console.log(`    RPPS: ${r.rpps} | ${r.structure.codePostal} ${r.structure.libelleCommune}`);
});
```

---

## Configuration

### `RppsClientOptions`

```typescript
interface RppsClientOptions {
  fhirBaseUrl?: string;           // Défaut: 'https://gateway.api.esante.gouv.fr/fhir/v2'
  fhirApiKey?: string;            // Clé ESANTE-API-KEY (sans clé = FHIR désactivé automatiquement)
  tabularBaseUrl?: string;        // Défaut: 'https://tabular-api.data.gouv.fr/api/resources'
  timeout?: number;               // En millisecondes. Défaut: 30000
  tabularPageSize?: number;       // Lignes par requête tabular. Défaut: 100
  disabledSources?: SourceName[]; // Sources à désactiver. Ex: ['fhir', 'mssante']
  headers?: Record<string, string>; // Headers HTTP supplémentaires
}
```

| Propriété | Type | Défaut | Description |
|-----------|------|--------|-------------|
| `fhirBaseUrl` | `string?` | `'https://gateway.api.esante.gouv.fr/fhir/v2'` | URL API FHIR ANS |
| `fhirApiKey` | `string?` | `''` | Clé ESANTE-API-KEY. Sans clé = FHIR désactivé automatiquement |
| `tabularBaseUrl` | `string?` | `'https://tabular-api.data.gouv.fr/api/resources'` | URL API Tabular data.gouv.fr |
| `timeout` | `number?` | `30000` | Timeout en ms par requête (AbortController) |
| `tabularPageSize` | `number?` | `100` | Nombre de lignes par page tabular |
| `disabledSources` | `SourceName[]?` | `[]` | Sources à désactiver |
| `headers` | `Record<string,string>?` | `{}` | Headers HTTP supplémentaires |

**Obtenir la clé FHIR (gratuit) :**
1. Créer un compte sur https://portal.api.esante.gouv.fr
2. Créer une application
3. S'abonner à "API Annuaire Santé en libre accès"
4. Récupérer la clé `ESANTE-API-KEY` dans l'onglet Credentials

Sans clé FHIR, le SDK fonctionne normalement avec les 5 sources data.gouv.fr.

---

## Méthodes du client

### `getByRpps`

```typescript
client.getByRpps(rpps: string): Promise<RppsFullProfile>
```

Récupère le profil complet d'un professionnel de santé. Interroge toutes les sources activées **en parallèle** via `Promise.all()` et fusionne les résultats.

**Paramètres :**
- `rpps` — Numéro RPPS (exactement 11 chiffres). Les espaces en début/fin sont ignorés.

**Retour :** `Promise<RppsFullProfile>`

**Erreurs :**
- Lance `Error` si le RPPS est invalide (format incorrect)
- Les erreurs de sources individuelles sont capturées silencieusement (voir `metadata.sources`)

**Exemple :**

```typescript
const profil = await client.getByRpps('10005173140');

// Identité
console.log(profil.identite.civiliteExercice.libelle); // "Docteur"
console.log(profil.identite.nomExercice);              // "DUPONT"
console.log(profil.identite.prenomExercice);           // "Jean"

// Profession
console.log(profil.profession.libelle);           // "Médecin"
console.log(profil.profession.code);              // "10"
console.log(profil.profession.categorie.libelle); // "Civil"

// Activités (structures d'exercice)
profil.activites.forEach((a) => {
  console.log(a.structure.raisonSociale);
  console.log(`${a.structure.adresse.codePostal} ${a.structure.adresse.libelleCommune}`);
  console.log(a.modeExercice.libelle); // "Libéral"
});

// Diplômes et autorisations
profil.diplomesEtAutorisations.forEach((da) => {
  if (da.diplome) console.log(da.diplome.diplome.libelle);
  if (da.autorisation) console.log(da.autorisation.disciplineAutorisation.libelle);
});

// Savoir-faire
profil.savoirFaire.forEach((sf) => {
  console.log(`${sf.savoirFaire.libelle} — ${sf.typeSavoirFaire.libelle}`);
});

// Cartes CPS/CPF
profil.cartesCps.forEach((c) => {
  console.log(`${c.typeCarte.libelle} n°${c.numeroCarte}`);
  console.log(`Valide du ${c.dateDebutValidite} au ${c.dateFinValidite}`);
});

// Messageries MSSanté
profil.messageriesMssante.forEach((m) => {
  console.log(`${m.adresseBal} (${m.typeBal})`);
});

// Données FHIR brutes (si clé API fournie)
if (profil.fhir.practitioner) {
  console.log('FHIR id :', profil.fhir.practitioner.id);
  console.log('PractitionerRoles :', profil.fhir.practitionerRoles.length);
}

// Métadonnées
console.log(`Durée totale : ${profil.metadata.totalDurationMs}ms`);
profil.metadata.sources.forEach((s) => {
  const status = s.success ? `OK (${s.rowCount} lignes, ${s.durationMs}ms)` : `ERREUR: ${s.error}`;
  console.log(`  ${s.source}: ${status}`);
});
```

---

### `search`

```typescript
client.search(
  criteria: RppsSearchCriteria,
  options?: { page?: number; pageSize?: number }
): Promise<RppsSearchResponse>
```

Recherche des professionnels de santé par nom, prénom et/ou code postal. Recherche partielle et insensible à la casse. Au moins un critère obligatoire.

**Paramètres :**
- `criteria.nom` — Nom d'exercice (partiel, insensible à la casse)
- `criteria.prenom` — Prénom d'exercice (partiel, insensible à la casse)
- `criteria.codePostal` — Code postal (ex: `'75'` pour Paris, `'69'` pour le Rhône)
- `options.page` — Page à récupérer (défaut: `1`)
- `options.pageSize` — Résultats par page (défaut: `tabularPageSize`, soit `100`)

**Erreurs :** Lance `Error` si aucun critère n'est fourni.

**Exemple :**

```typescript
// Par nom + département
const r1 = await client.search({ nom: 'DUPONT', codePostal: '75' });
console.log(`${r1.total} résultats, page ${r1.page}/${Math.ceil(r1.total / r1.pageSize)}`);

r1.results.forEach((r) => {
  console.log(`${r.civilite} ${r.prenomExercice} ${r.nomExercice}`);
  console.log(`  ${r.profession.libelle} | ${r.structure.raisonSociale}`);
  console.log(`  ${r.structure.codePostal} ${r.structure.libelleCommune}`);
  console.log(`  RPPS: ${r.rpps}`);
});

// Pagination
const page2 = await client.search({ nom: 'DUPONT', codePostal: '75' }, { page: 2, pageSize: 20 });

// Enchaîner recherche → profil complet
if (r1.results.length > 0) {
  const profil = await client.getByRpps(r1.results[0].rpps);
  console.log(`Diplômes : ${profil.diplomesEtAutorisations.length}`);
}
```

---

## Fonctions sources individuelles

Pour un usage avancé, chaque source peut être interrogée directement.

```typescript
import {
  fetchFhirByRpps,
  fetchPersonneActivite,
  searchPersonneActivite,
  fetchDiplomes,
  fetchSavoirFaire,
  fetchCartesCps,
  fetchMssante,
} from '@qrcommunication/rppsapi';
```

| Fonction | Source | Retour |
|----------|--------|--------|
| `fetchFhirByRpps(rpps, opts)` | API FHIR ANS | `Promise<FhirData>` |
| `fetchPersonneActivite(rpps, opts)` | data.gouv.fr Tabular | `Promise<{ identite, identificationNationale, profession, activites }>` |
| `searchPersonneActivite(criteria, opts)` | data.gouv.fr Tabular | `Promise<{ results, total }>` |
| `fetchDiplomes(rpps, opts)` | data.gouv.fr Tabular | `Promise<DiplomeEtAutorisation[]>` |
| `fetchSavoirFaire(rpps, opts)` | data.gouv.fr Tabular | `Promise<SavoirFaire[]>` |
| `fetchCartesCps(rpps, opts)` | data.gouv.fr Tabular | `Promise<CarteCps[]>` |
| `fetchMssante(rpps, opts)` | data.gouv.fr Tabular | `Promise<MessagerieMssante[]>` |

**Options communes pour les fonctions tabular :**
```typescript
{
  baseUrl: string;   // URL de base de l'API Tabular
  timeout?: number;  // Timeout en ms
  pageSize: number;  // Lignes par page
  headers?: Record<string, string>;
}
```

**Options pour fetchFhirByRpps :**
```typescript
{
  baseUrl?: string;  // URL de base FHIR
  timeout?: number;
  headers?: Record<string, string>; // Doit inclure 'ESANTE-API-KEY' pour l'auth
}
```

**Exemple :**

```typescript
// Interroger uniquement la source FHIR
const fhirData = await fetchFhirByRpps('10005173140', {
  baseUrl: 'https://gateway.api.esante.gouv.fr/fhir/v2',
  timeout: 15_000,
  headers: { 'ESANTE-API-KEY': process.env.FHIR_API_KEY ?? '' },
});

// Interroger uniquement les diplômes
const diplomes = await fetchDiplomes('10005173140', {
  baseUrl: 'https://tabular-api.data.gouv.fr/api/resources',
  timeout: 20_000,
  pageSize: 100,
  headers: {},
});
```

---

## validateRpps

```typescript
import { validateRpps } from '@qrcommunication/rppsapi';

validateRpps(rpps: string): string
```

Valide et normalise un numéro RPPS. Retourne le RPPS nettoyé (trim). Lance `Error` si invalide.

```typescript
validateRpps('10005173140');     // → '10005173140'
validateRpps('  10005173140 '); // → '10005173140'
validateRpps('123');             // Lance Error: 'Numéro RPPS invalide : "123"...'
```

---

## Interfaces TypeScript

Toutes les interfaces sont exportées depuis le package principal.

### `RppsFullProfile`

Profil complet retourné par `getByRpps()`.

```typescript
interface RppsFullProfile {
  rpps: string;                                    // Numéro RPPS (11 chiffres)
  identificationNationale: string;                 // Identification nationale PP
  identite: Identite;                              // Identité du praticien
  profession: Profession;                          // Profession principale
  activites: Activite[];                           // Structures d'exercice
  diplomesEtAutorisations: DiplomeEtAutorisation[]; // Diplômes et autorisations
  savoirFaire: SavoirFaire[];                      // Savoir-faire et compétences
  cartesCps: CarteCps[];                           // Cartes CPS/CPF
  messageriesMssante: MessagerieMssante[];          // Messageries MSSanté
  fhir: FhirData;                                  // Données FHIR brutes
  metadata: {
    sources: SourceStatus[];                       // Statut de chaque source
    queriedAt: string;                             // ISO 8601
    totalDurationMs: number;                       // Durée totale en ms
  };
}
```

### `Identite`

```typescript
interface Identite {
  civilite: CodeLabel;          // Civilité administrative
  civiliteExercice: CodeLabel;  // Civilité d'exercice (ex: 'Docteur')
  nomExercice: string;
  prenomExercice: string;
}
```

### `Profession`

```typescript
interface Profession {
  code: string;          // Code profession (ex: '10' = Médecin)
  libelle: string;       // Libellé (ex: 'Médecin')
  categorie: CodeLabel;  // Catégorie professionnelle
}
```

### `Activite`

```typescript
interface Activite {
  modeExercice: CodeLabel;               // 'Libéral', 'Salarié', etc.
  savoirFaire: CodeLabel;
  typeSavoirFaire: CodeLabel;
  secteurActivite: CodeLabel;
  sectionTableauPharmaciens: CodeLabel;
  role: CodeLabel;
  genreActivite: CodeLabel;
  autoriteEnregistrement: string;
  structure: Structure;
}
```

### `Structure`

```typescript
interface Structure {
  identifiantTechnique: string;
  raisonSociale: string;
  enseigneCommerciale: string;
  numeroSiret: string;
  numeroSiren: string;
  numeroFinessSite: string;
  numeroFinessEtablissementJuridique: string;
  telephone: string;
  telephone2: string;
  telecopie: string;
  email: string;
  codeDepartement: string;
  libelleDepartement: string;
  ancienIdentifiant: string;
  adresse: AdresseStructure;
}
```

### `AdresseStructure`

```typescript
interface AdresseStructure {
  complementDestinataire: string;
  complementPointGeographique: string;
  numeroVoie: string;
  indiceRepetitionVoie: string;
  codeTypeVoie: string;
  libelleTypeVoie: string;
  libelleVoie: string;
  mentionDistribution: string;
  bureauCedex: string;
  codePostal: string;
  codeCommune: string;
  libelleCommune: string;
  codePays: string;
  libellePays: string;
}
```

### `DiplomeEtAutorisation`

```typescript
interface DiplomeEtAutorisation {
  diplome: {
    typeDiplome: CodeLabel;
    diplome: CodeLabel;
  } | null;
  autorisation: {
    typeAutorisation: CodeLabel;
    disciplineAutorisation: CodeLabel;
  } | null;
}
```

### `SavoirFaire`

```typescript
interface SavoirFaire {
  profession: CodeLabel;
  categorieProfessionnelle: CodeLabel;
  typeSavoirFaire: CodeLabel;  // 'Spécialité ordinale', 'DESC', etc.
  savoirFaire: CodeLabel;      // Ex: 'Cardiologie', 'Pédiatrie'
}
```

### `CarteCps`

```typescript
interface CarteCps {
  typeCarte: CodeLabel;              // CPS, CPF, CPE...
  numeroCarte: string;
  identifiantNationalCarte: string;
  dateDebutValidite: string;         // Format AAAA-MM-JJ
  dateFinValidite: string;           // Format AAAA-MM-JJ
  dateOpposition: string;
  dateMiseAJour: string;
}
```

### `MessagerieMssante`

```typescript
interface MessagerieMssante {
  typeBal: string;             // 'Personnelle', 'Organisationnelle'
  adresseBal: string;          // Adresse email MSSanté
  dematerialisation: boolean;
  savoirFaire: CodeLabel;
  structure: {
    identifiant: string;
    typeIdentifiant: string;
    serviceRattachement: string;
    raisonSociale: string;
    enseigneCommerciale: string;
    adresse: {
      complementLocalisation: string;
      complementDistribution: string;
      numeroVoie: string;
      complementNumeroVoie: string;
      typeVoie: string;
      libelleVoie: string;
      lieuDit: string;
      ligneAcheminement: string;
      codePostal: string;
      departement: string;
      pays: string;
    };
  };
}
```

### `FhirData`

```typescript
interface FhirData {
  practitioner: FhirPractitioner | null;      // Null si non trouvé ou FHIR désactivé
  practitionerRoles: FhirPractitionerRole[];  // PractitionerRole associés
}
```

### `CodeLabel`

```typescript
// Type de base omniprésent dans les données RPPS
interface CodeLabel {
  code: string;    // Code court (ex: '10', 'M')
  libelle: string; // Libellé complet (ex: 'Médecin', 'M.')
}
```

### `SourceStatus`

```typescript
interface SourceStatus {
  source: SourceName;   // Identifiant de la source
  success: boolean;     // true si la requête a réussi
  rowCount: number;     // Nombre de lignes/ressources retournées
  durationMs: number;   // Durée en ms
  error?: string;       // Message d'erreur (ou 'disabled' si source désactivée)
}

type SourceName =
  | 'fhir'
  | 'personne-activite'
  | 'diplomes'
  | 'savoir-faire'
  | 'carte-cps'
  | 'mssante';
```

### `RppsSearchCriteria`

```typescript
// Au moins un champ obligatoire
interface RppsSearchCriteria {
  nom?: string;        // Recherche partielle, insensible à la casse
  prenom?: string;     // Recherche partielle, insensible à la casse
  codePostal?: string; // Ex: '75' pour Paris, '69001' pour Lyon 1er
}
```

### `RppsSearchResponse`

```typescript
interface RppsSearchResponse {
  results: RppsSearchResult[];  // Résultats de la page courante
  total: number;                // Nombre total de résultats
  page: number;                 // Page courante
  pageSize: number;             // Taille de la page
  durationMs: number;           // Durée de la requête en ms
  criteria: RppsSearchCriteria; // Critères utilisés (écho)
}
```

### `RppsSearchResult`

```typescript
interface RppsSearchResult {
  rpps: string;
  identificationNationale: string;
  civilite: string;
  nomExercice: string;
  prenomExercice: string;
  profession: CodeLabel;
  categorieProfessionnelle: CodeLabel;
  modeExercice: CodeLabel;
  savoirFaire: CodeLabel;
  structure: {
    raisonSociale: string;
    codePostal: string;
    libelleCommune: string;
    departement: string;
  };
}
```

---

## Sources de données

Le SDK agrège 6 sources publiques en parallèle.

| Source (SourceName) | API | Données fournies | Clé requise |
|---------------------|-----|-----------------|-------------|
| `personne-activite` | data.gouv.fr Tabular API | Identité, profession, structures d'exercice, adresses | Non |
| `diplomes` | data.gouv.fr Tabular API | Diplômes d'État, autorisations d'exercice | Non |
| `savoir-faire` | data.gouv.fr Tabular API | Spécialités, DESC, savoir-faire ordinaux | Non |
| `carte-cps` | data.gouv.fr Tabular API | Cartes CPS, CPF, CPE | Non |
| `mssante` | data.gouv.fr Tabular API | Boîtes aux lettres MSSanté | Non |
| `fhir` | API FHIR ANS (gateway.api.esante.gouv.fr) | Practitioner + PractitionerRole FHIR R4 | **Oui** (ESANTE-API-KEY) |

**Comportement en cas d'échec partiel :** Si une ou plusieurs sources échouent, le SDK retourne quand même un `RppsFullProfile` avec les données disponibles. Aucune exception n'est levée pour les erreurs partielles.

---

## Différences vs SDK PHP

| Aspect | SDK TypeScript/JS | SDK PHP |
|--------|-------------------|---------|
| Package | `@qrcommunication/rppsapi` | `qrcommunication/rppsapi` |
| Runtime | Node.js 18+ / Bun / Deno | PHP 8.1+ |
| Format de module | ESM uniquement (`"type": "module"`) | PSR-4 Composer |
| Dépendances | **Zéro** — fetch natif | `guzzlehttp/guzzle ^7.0` |
| Parallélisme | `Promise.all()` natif | Pool Guzzle |
| Timeout | En **millisecondes** (`timeout: 30_000`) | En **secondes** (`timeout: 30.0`) |
| Critères de recherche | Objet littéral `{ nom, prenom, codePostal }` | Classe DTO `RppsSearchCriteria` |
| Exceptions | `Error` native JavaScript | `RppsException` (classe personnalisée) |
| Sources individuelles | Fonctions exportées directement | Usage interne au client |

```typescript
// TypeScript
const client = new RppsClient({
  fhirApiKey: process.env.FHIR_API_KEY,
  timeout: 30_000,            // millisecondes
  disabledSources: ['mssante'],
});
```

```php
// PHP (équivalent)
$client = new RppsClient(new RppsClientOptions(
    fhirApiKey: $_ENV['FHIR_API_KEY'],
    timeout: 30.0,             // secondes
    disabledSources: ['mssante'],
));
```

---

## Gestion des erreurs

### Erreurs de validation (levées immédiatement)

```typescript
try {
  await client.getByRpps('123'); // RPPS invalide
} catch (error) {
  // error.message → 'Numéro RPPS invalide : "123". Le RPPS doit contenir exactement 11 chiffres.'
}

try {
  await client.search({}); // Aucun critère
} catch (error) {
  // error.message → 'Au moins un critère de recherche est obligatoire (nom, prenom ou codePostal).'
}
```

### Erreurs partielles (capturées silencieusement)

Les erreurs de sources individuelles sont capturées et stockées dans `metadata.sources`. Le profil est retourné avec les données disponibles.

```typescript
const profil = await client.getByRpps('10005173140');

profil.metadata.sources.forEach((s) => {
  if (!s.success) {
    console.warn(`Source "${s.source}" indisponible : ${s.error}`);
  }
});

// Détecter une source désactivée (error === 'disabled')
const fhir = profil.metadata.sources.find((s) => s.source === 'fhir');
if (fhir?.error === 'disabled') {
  console.info('Source FHIR désactivée (pas de clé API)');
}
```

### Timeout

```typescript
const client = new RppsClient({ timeout: 10_000 }); // 10 secondes

try {
  const profil = await client.getByRpps('10005173140');
} catch (error) {
  if (error instanceof Error && error.name === 'AbortError') {
    console.error('Requête annulée (timeout)');
  }
}
```

---

## Exemples avancés

### Désactiver des sources pour accélérer la réponse

```typescript
// Uniquement identité + activités (source la plus rapide)
const clientRapide = new RppsClient({
  disabledSources: ['fhir', 'diplomes', 'savoir-faire', 'carte-cps', 'mssante'],
});

const profil = await clientRapide.getByRpps('10005173140');
// < 1 seconde — seule personne-activite est interrogée
```

### Profils en parallèle

```typescript
const client = new RppsClient();
const rppsList = ['10005173140', '10003704284', '10006847543'];

const profils = await Promise.all(rppsList.map((rpps) => client.getByRpps(rpps)));
profils.forEach((p) => console.log(p.identite.nomExercice));
```

### Parcourir toutes les pages de résultats

```typescript
async function fetchAllResults(nom: string): Promise<RppsSearchResult[]> {
  const pageSize = 100;
  let page = 1;
  const all: RppsSearchResult[] = [];

  while (true) {
    const response = await client.search({ nom }, { page, pageSize });
    all.push(...response.results);
    if (all.length >= response.total || response.results.length === 0) break;
    page++;
  }

  return all;
}
```

### API Express

```typescript
import express from 'express';
import { RppsClient, validateRpps } from '@qrcommunication/rppsapi';

const router = express.Router();
const client = new RppsClient({ fhirApiKey: process.env.FHIR_API_KEY });

router.get('/rpps/:rpps', async (req, res) => {
  try {
    const rpps = validateRpps(req.params.rpps);
    const profil = await client.getByRpps(rpps);
    res.json(profil);
  } catch (error) {
    const message = error instanceof Error ? error.message : 'Erreur interne';
    const status = message.includes('invalide') ? 400 : 500;
    res.status(status).json({ error: message });
  }
});
```

### Deno

```typescript
import { RppsClient } from 'npm:@qrcommunication/rppsapi';

const client = new RppsClient({ fhirApiKey: Deno.env.get('FHIR_API_KEY') });
const profil = await client.getByRpps('10005173140');
console.log(profil.identite.nomExercice);
```

---

## Liens

- **npm** : https://www.npmjs.com/package/@qrcommunication/rppsapi
- **GitHub** : https://github.com/QrCommunication/rppsapi-js
- **CHANGELOG** : https://github.com/QrCommunication/rppsapi-js/blob/main/CHANGELOG.md
- **Portail FHIR ANS** : https://portal.api.esante.gouv.fr
- **Data RPPS data.gouv.fr** : https://www.data.gouv.fr/fr/datasets/repertoire-partage-des-professionnels-intervenant-dans-le-systeme-de-sante/
