"> Aller au contenu principal
TypeScript TypeScript v1.1.0 PolyForm NC 1.0

RPPS API TypeScript SDK

@qrcommunication/rppsapi

Node.js 18+, zero dependencies

1 Installation

npm
npm install @qrcommunication/rppsapi
pnpm
pnpm add @qrcommunication/rppsapi
yarn
yarn add @qrcommunication/rppsapi
Zero dependencies. The SDK uses only the native fetch API (available natively since Node.js 18+). No external dependency is required.
ESM only. This package is distributed as ECMAScript Modules ("type": "module"). Make sure your project is configured to use ESM, or use a compatible bundler (Vite, webpack 5+, esbuild).

Prerequisites

Environment Minimum version
Node.js 18.0.0+
TypeScript 5.0+ (recommended 5.7+)
Deno 1.30+ (via npm specifier)
Bun 1.0+

2 Quick Start

TypeScript main.ts
import { RppsClient } from '@qrcommunication/rppsapi';
import type { RppsFullProfile, RppsSearchResponse } from '@qrcommunication/rppsapi';

// Initialise the client
// Without fhirApiKey, the FHIR source is automatically disabled
const client = new RppsClient({
  fhirApiKey: process.env.FHIR_API_KEY, // optional — get it at portal.api.esante.gouv.fr
  timeout: 30_000,
});

// ─── Full profile by RPPS number ───────────────────────────
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);
// → "Médecin"

console.log('Activities:');
for (const activite of profil.activites) {
  console.log(`  - ${activite.structure.raisonSociale}`);
  console.log(`    ${activite.structure.adresse.codePostal} ${activite.structure.adresse.libelleCommune}`);
}

console.log('Degrees:');
for (const da of profil.diplomesEtAutorisations) {
  if (da.diplome) {
    console.log(`  - [Degree] ${da.diplome.diplome.libelle}`);
  }
  if (da.autorisation) {
    console.log(`  - [Authorisation] ${da.autorisation.disciplineAutorisation.libelle}`);
  }
}

console.log('Skills:');
for (const sf of profil.savoirFaire) {
  console.log(`  - ${sf.savoirFaire.libelle} (${sf.typeSavoirFaire.libelle})`);
}

console.log('CPS cards:');
for (const carte of profil.cartesCps) {
  console.log(`  - ${carte.typeCarte.libelle} n°${carte.numeroCarte}`);
}

console.log('MSSanté mailboxes:');
for (const bal of profil.messageriesMssante) {
  console.log(`  - ${bal.adresseBal} (${bal.typeBal})`);
}

// Metadata
console.log(`Total duration: ${profil.metadata.totalDurationMs}ms`);
for (const source of profil.metadata.sources) {
  const status = source.success
    ? `OK (${source.rowCount} rows, ${source.durationMs}ms)`
    : `ERROR: ${source.error}`;
  console.log(`  ${source.source}: ${status}`);
}

// ─── Search by last name / first name / postal code ──────────────────
const resultats: RppsSearchResponse = await client.search(
  { nom: 'DUPONT', codePostal: '75' },
  { page: 1, pageSize: 20 },
);

console.log(`${resultats.total} results found`);
for (const r of resultats.results) {
  console.log(`  ${r.civilite} ${r.prenomExercice} ${r.nomExercice} — ${r.profession.libelle}`);
  console.log(`    RPPS: ${r.rpps} | ${r.structure.codePostal} ${r.structure.libelleCommune}`);
}

3 Configuration — RppsClientOptions

TypeScript
import { RppsClient } from '@qrcommunication/rppsapi';

const client = new RppsClient({
  // Base URL of the FHIR French Health Directory API (ANS)
  // Default: 'https://gateway.api.esante.gouv.fr/fhir/v2'
  fhirBaseUrl: 'https://gateway.api.esante.gouv.fr/fhir/v2',

  // FHIR API key (ESANTE-API-KEY)
  // Get it for free at https://portal.api.esante.gouv.fr
  // Without this key, the FHIR source is automatically disabled
  fhirApiKey: process.env.FHIR_API_KEY,

  // Base URL of the Tabular data.gouv.fr API
  // Default: 'https://tabular-api.data.gouv.fr/api/resources'
  tabularBaseUrl: 'https://tabular-api.data.gouv.fr/api/resources',

  // Timeout per request in milliseconds
  // Default: 30000 (30 seconds)
  timeout: 30_000,

  // Max rows per tabular request
  // Default: 100
  tabularPageSize: 100,

  // Disable certain sources (useful to reduce latency)
  // Possible values: 'fhir' | 'personne-activite' | 'diplomes'
  //                   | 'savoir-faire' | 'carte-cps' | 'mssante'
  disabledSources: ['fhir', 'mssante'],

  // Additional HTTP headers sent with every request
  headers: {
    'X-App-Name': 'mon-application',
  },
});

Interface RppsClientOptions

Property Type Default Description
fhirBaseUrl string? 'https://gateway.api.esante.gouv.fr/fhir/v2' Base URL of the FHIR French Health Directory API (ANS)
fhirApiKey string? '' FHIR API key (ESANTE-API-KEY). Without this key, the FHIR source is automatically disabled.
tabularBaseUrl string? 'https://tabular-api.data.gouv.fr/api/resources' Base URL of the Tabular data.gouv.fr API
timeout number? 30000 Timeout in milliseconds per request (AbortController)
tabularPageSize number? 100 Number of rows per page for tabular requests
disabledSources SourceName[]? [] List of sources to disable. See type SourceName.
headers Record<string, string>? {} Additional HTTP headers added to every request
FHIR key — Free to obtain.
  1. Create an account on portal.api.esante.gouv.fr
  2. Create an application
  3. Subscribe to API Annuaire Santé en libre accès
  4. Retrieve the ESANTE-API-KEY key from the Credentials tab
Without this key, the SDK works normally using the 5 data.gouv.fr sources only.

4 API Reference

async client.getByRpps(rpps)

Retrieves the complete profile of a healthcare professional from their RPPS number. Queries all enabled sources in parallel via Promise.all() and merges the results into a single structured object.

Signature

getByRpps(rpps: string): Promise<RppsFullProfile>

Parameters

Parameter Type Description
rpps string Practitioner RPPS number (exactly 11 digits). Leading/trailing spaces are trimmed.

Return value

Returns a Promise<RppsFullProfile>. Throws an Error if the RPPS number is invalid (incorrect format).

Example

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

// Identity
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"

// Activities (practice structures)
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"
});

// Degrees and authorisations
profil.diplomesEtAutorisations.forEach((da) => {
  if (da.diplome) console.log(da.diplome.diplome.libelle);
  if (da.autorisation) console.log(da.autorisation.disciplineAutorisation.libelle);
});

// Skills
profil.savoirFaire.forEach((sf) => {
  console.log(sf.savoirFaire.libelle, '—', sf.typeSavoirFaire.libelle);
});

// CPS/CPF cards
profil.cartesCps.forEach((c) => {
  console.log(c.typeCarte.libelle, c.numeroCarte);
  console.log(`Valid from ${c.dateDebutValidite} to ${c.dateFinValidite}`);
});

// MSSanté mailboxes
profil.messageriesMssante.forEach((m) => {
  console.log(m.adresseBal, m.typeBal);
  console.log('Dematerialisation:', m.dematerialisation);
});

// FHIR data (if API key provided)
if (profil.fhir.practitioner) {
  console.log('FHIR id :', profil.fhir.practitioner.id);
  console.log('PractitionerRoles :', profil.fhir.practitionerRoles.length);
}

// Request metadata
console.log(`Queried at: ${profil.metadata.queriedAt}`);
console.log(`Total duration: ${profil.metadata.totalDurationMs}ms`);

Searches for healthcare professionals by last name, first name and/or postal code. The search is partial and case-insensitive ( contains operator). At least one criterion is required.

Signature

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

Parameters

Parameter Type Description
criteria.nom string? Last name (partial search, case-insensitive)
criteria.prenom string? First name (partial search, case-insensitive)
criteria.codePostal string? Postal code (exact or partial match — e.g. '75' for Paris)
options.page number? Page to retrieve. Default: 1
options.pageSize number? Number of results per page. Default: value of tabularPageSize (100)

Example

TypeScript
// Search by last name + postal code (department)
const r1 = await client.search({ nom: 'DUPONT', codePostal: '75' });
console.log(`${r1.total} results, page ${r1.page}/${Math.ceil(r1.total / r1.pageSize)}`);

for (const r of r1.results) {
  console.log(`${r.civilite} ${r.prenomExercice} ${r.nomExercice}`);
  console.log(`  Profession: ${r.profession.libelle}`);
  console.log(`  Structure: ${r.structure.raisonSociale}`);
  console.log(`  Address: ${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 },
);

// Search by first name only
const r2 = await client.search({ prenom: 'Nicolas' }, { pageSize: 10 });

// Chain: search → full profile
if (r1.results.length > 0) {
  const profil = await client.getByRpps(r1.results[0].rpps);
  console.log(`Degrees: ${profil.diplomesEtAutorisations.length}`);
}

Individual Sources

For advanced use, each source can be queried directly by importing the individual functions. These functions are the internal building blocks used by RppsClient.getByRpps().

TypeScript
import {
  fetchFhirByRpps,
  fetchPersonneActivite,
  searchPersonneActivite,
  fetchDiplomes,
  fetchSavoirFaire,
  fetchCartesCps,
  fetchMssante,
} from '@qrcommunication/rppsapi';
Function Source Return Description
fetchFhirByRpps(rpps, opts) API FHIR ANS Promise<FhirData> Practitioner + PractitionerRoles FHIR. Requires an API key in the headers (ESANTE-API-KEY).
fetchPersonneActivite(rpps, opts) data.gouv.fr Tabular Promise<{ identite, identificationNationale, profession, activites }> Identity, profession and practice structures.
searchPersonneActivite(criteria, opts) data.gouv.fr Tabular Promise<{ results, total }> Search by last name / first name / postal code.
fetchDiplomes(rpps, opts) data.gouv.fr Tabular Promise<DiplomeEtAutorisation[]> Degrees and practice authorisations.
fetchSavoirFaire(rpps, opts) data.gouv.fr Tabular Promise<SavoirFaire[]> Skills and specific competencies.
fetchCartesCps(rpps, opts) data.gouv.fr Tabular Promise<CarteCps[]> CPS and CPF cards (professional health cards).
fetchMssante(rpps, opts) data.gouv.fr Tabular Promise<MessagerieMssante[]> MSSanté secure messaging mailboxes.

Direct usage example

TypeScript
import { fetchFhirByRpps, fetchDiplomes } from '@qrcommunication/rppsapi';

// Query only the FHIR source
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 ?? '',
  },
});

console.log(fhirData.practitioner?.id);
console.log(fhirData.practitionerRoles.length);

// Query only degrees
const diplomes = await fetchDiplomes('10005173140', {
  baseUrl: 'https://tabular-api.data.gouv.fr/api/resources',
  timeout: 20_000,
  pageSize: 100,
  headers: {},
});

console.log(`${diplomes.length} degree(s)`);
diplomes.forEach((da) => {
  if (da.diplome) console.log(da.diplome.diplome.libelle);
});

validateRpps(rpps)

Validates and normalises an RPPS number. The RPPS must contain exactly 11 digits. Leading and trailing whitespace is stripped. Throws an Error if the format is invalid.

TypeScript
import { validateRpps } from '@qrcommunication/rppsapi';

// Valid (returns the cleaned RPPS)
const rpps = validateRpps('10005173140');  // → '10005173140'
const rppsWithSpaces = validateRpps('  10005173140  ');  // → '10005173140'

// Invalid (throws an Error)
try {
  validateRpps('123');  // Fewer than 11 digits
} catch (error) {
  console.error(error.message);
  // → 'Numéro RPPS invalide : "123". Le RPPS doit contenir exactement 11 chiffres.'
}

// Use to pre-validate before an API call
function isValidRpps(rpps: string): boolean {
  try {
    validateRpps(rpps);
    return true;
  } catch {
    return false;
  }
}

5 TypeScript Interfaces

All interfaces are exported from the main package and can be used directly.

TypeScript Type imports
import type {
  RppsFullProfile,
  RppsClientOptions,
  RppsSearchCriteria,
  RppsSearchResponse,
  RppsSearchResult,
  Identite,
  Profession,
  Activite,
  Structure,
  AdresseStructure,
  DiplomeEtAutorisation,
  SavoirFaire,
  CarteCps,
  MessagerieMssante,
  FhirData,
  CodeLabel,
  SourceStatus,
  SourceName,
} from '@qrcommunication/rppsapi';

RppsFullProfile

Complete profile of a healthcare professional. Returned by getByRpps().

interface RppsFullProfile {
  rpps: string;                              // RPPS number (11 digits)
  identificationNationale: string;           // National PP identifier (type prefix + RPPS)
  identite: Identite;                        // Practitioner identity
  profession: Profession;                    // Main profession
  activites: Activite[];                     // Practice structures
  diplomesEtAutorisations: DiplomeEtAutorisation[]; // Degrees and authorisations
  savoirFaire: SavoirFaire[];               // Skills and competencies
  cartesCps: CarteCps[];                     // CPS/CPF cards
  messageriesMssante: MessagerieMssante[];   // MSSanté mailboxes (personal)
  fhir: FhirData;                            // Raw FHIR data (advanced use)
  metadata: {
    sources: SourceStatus[];                 // Status of each queried source
    queriedAt: string;                       // ISO 8601 — request date/time
    totalDurationMs: number;                 // Total duration in milliseconds
  };
}

Identite

interface Identite {
  civilite: CodeLabel;          // Administrative title (e.g. { code: 'M', libelle: 'M.' })
  civiliteExercice: CodeLabel;  // Practice title (e.g. { code: 'DR', libelle: 'Docteur' })
  nomExercice: string;          // Practice last name
  prenomExercice: string;       // Practice first name
}

Profession

interface Profession {
  code: string;          // Profession code (e.g. '10' = Médecin)
  libelle: string;       // Profession label (e.g. 'Médecin')
  categorie: CodeLabel;  // Professional category (e.g. { code: 'C', libelle: 'Civil' })
}

Activite

interface Activite {
  modeExercice: CodeLabel;           // Practice mode (e.g. 'Libéral', 'Salarié')
  savoirFaire: CodeLabel;            // Main skill of the activity
  typeSavoirFaire: CodeLabel;        // Skill type
  secteurActivite: CodeLabel;        // Activity sector (e.g. 'Cabinet libéral')
  sectionTableauPharmaciens: CodeLabel; // Pharmacist register section (if applicable)
  role: CodeLabel;                   // Role in the structure
  genreActivite: CodeLabel;          // Activity type
  autoriteEnregistrement: string;    // Registration authority
  structure: Structure;              // Associated practice structure
}

Structure

interface Structure {
  identifiantTechnique: string;                       // Technical identifier
  raisonSociale: string;                              // Corporate name
  enseigneCommerciale: string;                        // Trade name
  numeroSiret: string;                                // SIRET number
  numeroSiren: string;                                // SIREN number
  numeroFinessSite: string;                           // FINESS site number
  numeroFinessEtablissementJuridique: string;         // FINESS legal entity number
  telephone: string;
  telephone2: string;
  telecopie: string;
  email: string;
  codeDepartement: string;
  libelleDepartement: string;
  ancienIdentifiant: string;
  adresse: AdresseStructure;
}

AdresseStructure

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

interface DiplomeEtAutorisation {
  diplome: {
    typeDiplome: CodeLabel;  // Degree type (e.g. 'Doctorat d'État de médecine')
    diplome: CodeLabel;      // Degree (code + label)
  } | null;
  autorisation: {
    typeAutorisation: CodeLabel;        // Authorisation type
    disciplineAutorisation: CodeLabel;  // Concerned discipline
  } | null;
}

SavoirFaire

interface SavoirFaire {
  profession: CodeLabel;               // Associated profession
  categorieProfessionnelle: CodeLabel; // Professional category
  typeSavoirFaire: CodeLabel;          // Type (e.g. 'Spécialité ordinale', 'DESC')
  savoirFaire: CodeLabel;              // Skill (e.g. 'Cardiologie')
}

CarteCps

interface CarteCps {
  typeCarte: CodeLabel;              // Card type (CPS, CPF, CPE...)
  numeroCarte: string;               // Card number
  identifiantNationalCarte: string;  // National identifier
  dateDebutValidite: string;         // Validity start date (YYYY-MM-DD)
  dateFinValidite: string;           // Validity end date (YYYY-MM-DD)
  dateOpposition: string;            // Opposition date (if applicable)
  dateMiseAJour: string;             // Last update date
}

MessagerieMssante

interface MessagerieMssante {
  typeBal: string;             // Mailbox type ('Personnelle', 'Organisationnelle')
  adresseBal: string;          // MSSanté email address (e.g. jean.dupont@medecin.mssante.fr)
  dematerialisation: boolean;  // Whether the mailbox accepts dematerialisation
  savoirFaire: CodeLabel;      // Associated skill
  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

interface FhirData {
  practitioner: FhirPractitioner | null;    // FHIR Practitioner resource (null if not found)
  practitionerRoles: FhirPractitionerRole[]; // Associated FHIR PractitionerRole resources
}

CodeLabel

// Base type ubiquitous in RPPS data
interface CodeLabel {
  code: string;    // Short code (e.g. '10', 'M', 'C')
  libelle: string; // Full label (e.g. 'Médecin', 'M.', 'Civil')
}

SourceStatus

interface SourceStatus {
  source: SourceName;    // Identifier of the queried source
  success: boolean;      // true if the request succeeded
  rowCount: number;      // Number of rows/resources returned
  durationMs: number;    // Request duration in milliseconds
  error?: string;        // Error message if success === false (or 'disabled')
}

// Possible values for SourceName:
type SourceName =
  | 'fhir'
  | 'personne-activite'
  | 'diplomes'
  | 'savoir-faire'
  | 'carte-cps'
  | 'mssante';

RppsSearchCriteria

// At least one field required
interface RppsSearchCriteria {
  nom?: string;        // Last name (partial search, case-insensitive)
  prenom?: string;     // First name (partial search, case-insensitive)
  codePostal?: string; // Postal code (e.g. '75' for Paris, '69001' for Lyon 1st)
}

RppsSearchResponse

interface RppsSearchResponse {
  results: RppsSearchResult[]; // Results for the current page
  total: number;               // Total number of matching results
  page: number;                // Current page number
  pageSize: number;            // Page size
  durationMs: number;          // Request duration in milliseconds
  criteria: RppsSearchCriteria; // Criteria used (echo)
}

RppsSearchResult

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;
  };
}

6 Data Sources

The SDK aggregates 6 public sources in parallel to build the complete profile.

Source API Data provided API key required
personne-activite data.gouv.fr Tabular API Identity, profession, practice structures, addresses No
diplomes data.gouv.fr Tabular API State degrees, practice authorisations No
savoir-faire data.gouv.fr Tabular API Specialties, DESC, ordinal skills No
carte-cps data.gouv.fr Tabular API CPS, CPF, CPE cards (professional health cards) No
mssante data.gouv.fr Tabular API MSSanté secure messaging mailboxes No
fhir FHIR French Health Directory API (ANS) FHIR R4 resources: Practitioner + PractitionerRole Yes (ESANTE-API-KEY)
Behaviour on partial failure. If one or more sources fail (network error, timeout, API unavailable), the SDK still returns an RppsFullProfile with the available data. Failed sources have success: false in metadata.sources. No exception is thrown for partial errors.

7 Differences vs PHP SDK

Aspect TypeScript/JS SDK PHP SDK
Package @qrcommunication/rppsapi qrcommunication/rppsapi
Runtime Node.js 18+ / Bun / Deno PHP 8.1+
Module format ESM only ("type": "module") PSR-4 Composer
Dependencies Zero dependencies — native fetch guzzlehttp/guzzle ^7.0
Parallelism Promise.all() — native concurrent requests Guzzle concurrent requests (pool)
Timeout In milliseconds (timeout: 30_000) In seconds (timeout: 30.0)
Search criteria Plain object { nom, prenom, codePostal } DTO class RppsSearchCriteria
Exceptions Native JavaScriptError RppsException (custom class)
Type exports Exported TypeScript interfaces PHP DTOs with typed properties
Individual sources Directly exported functions Class methods (internal use)

Configuration equivalents

TypeScript
// TypeScript
const client = new RppsClient({
  fhirApiKey: process.env.FHIR_API_KEY,
  timeout: 30_000,   // milliseconds
  disabledSources: ['mssante'],
});
PHP
// PHP (equivalent)
$client = new RppsClient(new RppsClientOptions(
    fhirApiKey: $_ENV['FHIR_API_KEY'],
    timeout: 30.0,      // seconds
    disabledSources: ['mssante'],
));

8 Error Handling

Validation errors

Validation errors (invalid RPPS, no search criteria) throw a native JavaScript Error immediately, before any network request.

TypeScript
try {
  const profil = await client.getByRpps('123'); // Invalid RPPS
} catch (error) {
  if (error instanceof Error) {
    console.error(error.message);
    // → 'Numéro RPPS invalide : "123". Le RPPS doit contenir exactement 11 chiffres.'
  }
}

try {
  const resultats = await client.search({}); // No criteria
} catch (error) {
  if (error instanceof Error) {
    console.error(error.message);
    // → 'Au moins un critère de recherche est obligatoire (nom, prenom ou codePostal).'
  }
}

Partial errors (sources)

Network errors on an individual source do not propagate an exception. The source is marked success: false in the metadata, and the corresponding data is replaced with default values (empty arrays, empty objects).

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

// Check the status of each source
for (const source of profil.metadata.sources) {
  if (!source.success) {
    console.warn(
      `Source "${source.source}" unavailable: ${source.error}`
    );
    // Continue anyway with available data
  }
}

// Check that a critical source succeeded
const fhirSource = profil.metadata.sources.find((s) => s.source === 'fhir');
if (fhirSource && !fhirSource.success && fhirSource.error !== 'disabled') {
  console.warn('FHIR unavailable — partial data');
}

Timeout

TypeScript
// Configure a short timeout for latency-sensitive environments
const client = new RppsClient({ timeout: 10_000 }); // 10 seconds

try {
  const profil = await client.getByRpps('10005173140');
} catch (error) {
  if (error instanceof Error && error.name === 'AbortError') {
    console.error('Request aborted (timeout)');
  }
}

HTTP errors

TypeScript
// HTTP errors (4xx, 5xx) on an individual source are caught
// and stored in metadata.sources[n].error
// Error message format: "HTTP 503: Service Unavailable — https://..."

const profil = await client.getByRpps('10005173140');
const mssanteStatus = profil.metadata.sources.find((s) => s.source === 'mssante');

if (mssanteStatus?.error?.startsWith('HTTP 5')) {
  console.warn('MSSanté server temporarily unavailable');
}

9 Advanced Examples

Disable sources to speed up the response

TypeScript
// Only retrieve identity and activities (fastest sources)
const clientRapide = new RppsClient({
  disabledSources: ['fhir', 'diplomes', 'savoir-faire', 'carte-cps', 'mssante'],
});

const profil = await clientRapide.getByRpps('10005173140');
// Only the personne-activite source is queried → < 1 second

Parallel profiles for a list of RPPS numbers

TypeScript
const client = new RppsClient();

const rppsList = ['10005173140', '10003704284', '10006847543'];

// Query all 3 profiles simultaneously
const [profil1, profil2, profil3] = await Promise.all(
  rppsList.map((rpps) => client.getByRpps(rpps))
);

console.log(profil1.identite.nomExercice);
console.log(profil2.identite.nomExercice);
console.log(profil3.identite.nomExercice);

Paginate through all result pages

TypeScript
import { RppsClient } from '@qrcommunication/rppsapi';
import type { RppsSearchResult } from '@qrcommunication/rppsapi';

const client = new RppsClient();

async function fetchAllResults(nom: string): Promise {
  const pageSize = 100;
  let page = 1;
  const allResults: RppsSearchResult[] = [];

  while (true) {
    const response = await client.search({ nom }, { page, pageSize });
    allResults.push(...response.results);

    if (allResults.length >= response.total || response.results.length === 0) {
      break;
    }
    page++;
  }

  return allResults;
}

const tousLesDupont = await fetchAllResults('DUPONT');
console.log(`${tousLesDupont.length} practitioners named DUPONT`);

Usage in an Express / Fastify API

TypeScript routes/rpps.ts
import express from 'express';
import { RppsClient, validateRpps } from '@qrcommunication/rppsapi';

const router = express.Router();

// Instantiate once (connection reused)
const client = new RppsClient({
  fhirApiKey: process.env.FHIR_API_KEY,
  timeout: 20_000,
});

// GET /api/rpps/:rpps
router.get('/:rpps', async (req, res) => {
  try {
    const rpps = validateRpps(req.params.rpps);
    const profil = await client.getByRpps(rpps);
    res.json(profil);
  } catch (error) {
    if (error instanceof Error && error.message.includes('invalide')) {
      res.status(400).json({ error: error.message });
    } else {
      res.status(500).json({ error: 'Internal error' });
    }
  }
});

// GET /api/rpps/search?nom=DUPONT&codePostal=75
router.get('/search', async (req, res) => {
  const { nom, prenom, codePostal, page = '1', pageSize = '20' } = req.query as Record;

  try {
    const resultats = await client.search(
      { nom, prenom, codePostal },
      { page: parseInt(page), pageSize: parseInt(pageSize) },
    );
    res.json(resultats);
  } catch (error) {
    if (error instanceof Error) {
      res.status(400).json({ error: error.message });
    } else {
      res.status(500).json({ error: 'Internal error' });
    }
  }
});

export default router;

Usage with Deno

TypeScript main.ts (Deno)
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);