1 Installation
npm install @qrcommunication/rppsapi
pnpm add @qrcommunication/rppsapi
yarn add @qrcommunication/rppsapi
fetch API (available natively since Node.js 18+). No external dependency is required.
"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
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
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 |
- Create an account on portal.api.esante.gouv.fr
- Create an application
- Subscribe to API Annuaire Santé en libre accès
- Retrieve the
ESANTE-API-KEYkey from the Credentials tab
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
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`);
async
client.search(criteria, options?)
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
// 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().
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
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.
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.
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) |
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
const client = new RppsClient({
fhirApiKey: process.env.FHIR_API_KEY,
timeout: 30_000, // milliseconds
disabledSources: ['mssante'],
});
// 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.
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).
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
// 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
// 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
// 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
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
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
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
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);