Installation
composer require qrcommunication/rppsapi
guzzlehttp/guzzle dependency is included automatically.
Quick Start
Two main patterns: retrieve a full profile by RPPS number, or search for practitioners by last name, first name and/or postal code.
<?php
use QrCommunication\RppsApi\RppsClient;
use QrCommunication\RppsApi\RppsClientOptions;
use QrCommunication\RppsApi\Dto\RppsSearchCriteria;
use QrCommunication\RppsApi\Exception\RppsException;
$client = new RppsClient(new RppsClientOptions(
fhirApiKey: $_ENV['FHIR_API_KEY'] ?? null,
));
// ── Profil complet par numéro RPPS ─────────────────────────────
try {
$profil = $client->getByRpps('10005173140');
echo $profil->identite->civiliteExercice->libelle . ' ';
echo $profil->identite->prenomExercice . ' ' . $profil->identite->nomExercice . "\n";
echo 'Profession : ' . $profil->profession->libelle . "\n";
foreach ($profil->activites as $activite) {
echo ' Structure : ' . $activite->structure->raisonSociale . "\n";
echo ' Adresse : ' . $activite->structure->adresse->libelleCommune . "\n";
}
foreach ($profil->diplomesEtAutorisations as $da) {
if ($da->diplome) {
echo ' Diplôme : ' . $da->diplome->libelle . "\n";
}
}
foreach ($profil->savoirFaire as $sf) {
echo ' Savoir-faire : ' . $sf->savoirFaire->libelle . "\n";
}
foreach ($profil->messageriesMssante as $mss) {
echo ' MSSanté : ' . $mss->adresseBal . "\n";
}
// Statut des sources interrogées
foreach ($profil->sources as $source) {
$status = $source->success ? 'OK' : 'ERREUR';
echo " [{$status}] {$source->source->value} — {$source->durationMs}ms\n";
}
} catch (RppsException $e) {
echo 'Erreur RPPS : ' . $e->getMessage() . "\n";
}
// ── Recherche par critères ──────────────────────────────────────
$results = $client->search(new RppsSearchCriteria(
nom: 'DUPONT',
codePostal: '75',
));
echo "Total trouvé : {$results->total}\n";
echo "Durée : {$results->durationMs}ms\n";
foreach ($results->results as $r) {
echo " {$r->civilite} {$r->prenomExercice} {$r->nomExercice}";
echo " — {$r->profession->libelle}";
echo " — {$r->libelleCommune}\n";
}
// ── Pagination ──────────────────────────────────────────────────
$page2 = $client->search(new RppsSearchCriteria(nom: 'MARTIN'), page: 2, pageSize: 50);
Configuration
RppsClientOptions is a readonly object passed to the RppsClient constructor. All parameters have working default values.
use QrCommunication\RppsApi\RppsClientOptions;
$options = new RppsClientOptions(
fhirBaseUrl: 'https://gateway.api.esante.gouv.fr/fhir/v2', // URL de base FHIR ANS
fhirApiKey: 'votre-cle-esante-api-key', // Clé API Gravitee (optionnel)
tabularBaseUrl: 'https://tabular-api.data.gouv.fr/api/resources', // URL Tabular data.gouv.fr
timeout: 30.0, // Timeout Guzzle en secondes
tabularPageSize: 100, // Taille de page pour les requêtes Tabular
disabledSources: ['fhir', 'mssante'], // Sources à désactiver
);
| Parameter | Type | Default | Description |
|---|---|---|---|
fhirBaseUrl |
string | 'https://gateway.api.esante.gouv.fr/fhir/v2' |
Base URL of the ANS FHIR API |
fhirApiKey |
string|null | null |
Gravitee API key (ESANTE-API-KEY). Without this key, the FHIR source is ignored. |
tabularBaseUrl |
string | 'https://tabular-api.data.gouv.fr/api/resources' |
Base URL of the Tabular data.gouv.fr API |
timeout |
float | 30.0 |
Timeout in seconds for Guzzle HTTP requests |
tabularPageSize |
int | 100 |
Number of rows per page for Tabular requests (used by search) |
disabledSources |
string[] | [] |
List of sources to disable. Accepted values: 'fhir', 'personne-activite', 'diplomes', 'savoir-faire', 'carte-cps', 'mssante' |
Methods
getByRpps
PUBLICgetByRpps(string $rpps): RppsFullProfile
Retrieves the full profile of a practitioner from their RPPS number (11 digits). Queries all enabled sources (identity, activities, degrees, skills, CPS cards, MSSanté mailboxes, FHIR) and returns an aggregated object.
| Parameter | Type | Description |
|---|---|---|
$rpps |
string | 11-digit RPPS number. Leading/trailing spaces are ignored. |
RppsException if the RPPS number does not contain exactly 11 digits.
$profil = $client->getByRpps('10005173140');
// Retourne RppsFullProfile
search
PUBLICsearch(RppsSearchCriteria $criteria, int $page = 1, ?int $pageSize = null): RppsSearchResponse
Searches for healthcare professionals by criteria. At least one criterion is required. The search is partial and case-insensitive (contains). For codePostal, a 5-character value uses an exact filter.
| Parameter | Type | Default | Description |
|---|---|---|---|
$criteria |
RppsSearchCriteria | — | Search criteria (last name, first name, postal code) |
$page |
int | 1 |
Page number (starts at 1) |
$pageSize |
int|null | null |
Results per page. If null, uses tabularPageSize from options. |
RppsException if no criterion is provided.
$results = $client->search(
new RppsSearchCriteria(nom: 'DUPONT', prenom: 'JEAN', codePostal: '75001'),
page: 1,
pageSize: 50,
);
// Retourne RppsSearchResponse
validateRpps
STATICstatic validateRpps(string $rpps): string
Validates an RPPS number. Returns the cleaned number (without spaces) if valid. Throws RppsException if the format is incorrect (must contain exactly 11 digits).
// Validation statique (pas besoin d'instancier RppsClient)
$clean = RppsClient::validateRpps('10005173140'); // '10005173140'
$clean = RppsClient::validateRpps(' 10005173140 '); // '10005173140' (trim)
// Lance RppsException :
RppsClient::validateRpps('123'); // Trop court
RppsClient::validateRpps('1234567890A'); // Contient une lettre
DTOs
All DTOs are final readonly classes. Missing values are empty strings '' or default instances (never null), unless otherwise noted.
RppsFullProfile
Aggregated profile returned by getByRpps().
| Property | Type | Description |
|---|---|---|
rpps | string | 11-digit RPPS number |
identificationNationale | string | National PP identifier |
identite | Identite | Title, last name and first name of practice |
profession | Profession | Profession code and label, professional category |
activites | Activite[] | Activities and linked structures (one entry per structure with technical identifier) |
diplomesEtAutorisations | DiplomeEtAutorisation[] | Obtained degrees and practice authorizations |
savoirFaire | SavoirFaire[] | Declared specialties and competencies |
cartesCps | CarteCps[] | CPS cards (Healthcare Professional Card) |
messageriesMssante | MessagerieMssante[] | MSSanté secure messaging addresses |
fhir | FhirData | FHIR data (Practitioner + PractitionerRoles). Empty if no API key. |
sources | SourceStatus[] | Status of each queried source (success, duration, possible error) |
queriedAt | string | ISO 8601 timestamp of the request |
totalDurationMs | float | Total execution time in milliseconds |
Identite
Civil identity of the practitioner's practice.
| Property | Type | Description |
|---|---|---|
civilite | CodeLabel | Administrative title (e.g. M / Mr.) |
civiliteExercice | CodeLabel | Practice title (e.g. DR / Doctor) |
nomExercice | string | Practice last name |
prenomExercice | string | Practice first name |
Profession
Profession declared in the RPPS.
| Property | Type | Description |
|---|---|---|
code | string | Profession code (e.g. 10) |
libelle | string | Profession label (e.g. Médecin) |
categorie | CodeLabel | Professional category (code + label) |
Activite
Professional activity linked to a structure.
| Property | Type | Description |
|---|---|---|
modeExercice | CodeLabel | Practice mode (e.g. self-employed, salaried) |
savoirFaire | CodeLabel | Skill associated with the activity |
typeSavoirFaire | CodeLabel | Type of skill |
secteurActivite | CodeLabel | Activity sector |
sectionTableauPharmaciens | CodeLabel | Section of the pharmacists' register |
role | CodeLabel | Role within the structure |
genreActivite | CodeLabel | Activity type |
autoriteEnregistrement | string | Registration authority |
structure | Structure | Linked practice structure |
Structure
Healthcare facility or practice office.
| Property | Type | Description |
|---|---|---|
identifiantTechnique | string | Technical identifier of the structure |
raisonSociale | string | Legal name of the site |
enseigneCommerciale | string | Commercial name of the site |
numeroSiret | string | SIRET number |
numeroSiren | string | SIREN number |
numeroFinessSite | string | FINESS number of the site |
numeroFinessEtablissementJuridique | string | FINESS number of the legal entity |
telephone | string | Primary phone number |
telephone2 | string | Secondary phone number |
telecopie | string | Fax number |
email | string | Email address of the structure |
codeDepartement | string | Department code |
libelleDepartement | string | Department label |
ancienIdentifiant | string | Former identifier of the structure |
adresse | AdresseStructure | Full postal address |
AdresseStructure
Normalized postal address of a structure.
| Property | Type | Description |
|---|---|---|
complementDestinataire | string | Addressee complement |
complementPointGeographique | string | Geographic location complement |
numeroVoie | string | Street number |
indiceRepetitionVoie | string | Street number repetition index (bis, ter…) |
codeTypeVoie | string | Street type code |
libelleTypeVoie | string | Street type label (Rue, Avenue…) |
libelleVoie | string | Street name |
mentionDistribution | string | Distribution note |
bureauCedex | string | Cedex office |
codePostal | string | Postal code |
codeCommune | string | INSEE municipality code |
libelleCommune | string | Municipality label |
codePays | string | Country code |
libellePays | string | Country label |
DiplomeEtAutorisation
Obtained degree or practice authorization. Each entry represents either a degree or an authorization (never both at once). Properties are null if the value is absent.
| Property | Type | Description |
|---|---|---|
typeDiplome | CodeLabel|null | Type of degree obtained (non-null if degree) |
diplome | CodeLabel|null | Degree obtained (code + label) |
typeAutorisation | CodeLabel|null | Authorization type (non-null if authorization) |
disciplineAutorisation | CodeLabel|null | Authorization discipline |
SavoirFaire
Specialty or competency declared in the RPPS.
| Property | Type | Description |
|---|---|---|
profession | CodeLabel | Profession associated with the skill |
categorieProfessionnelle | CodeLabel | Professional category |
typeSavoirFaire | CodeLabel | Type of skill (specialty, capacity…) |
savoirFaire | CodeLabel | Skill (code + label) |
CarteCps
Healthcare Professional Card (CPS) associated with the practitioner.
| Property | Type | Description |
|---|---|---|
typeCarte | CodeLabel | CPS card type |
numeroCarte | string | Card number |
identifiantNationalCarte | string | National identifier stored in the card |
dateDebutValidite | string | Validity start date |
dateFinValidite | string | Validity end date |
dateOpposition | string | Opposition date (if card is opposed) |
dateMiseAJour | string | Last update date |
MessagerieMssante
MSSanté secure messaging mailbox.
| Property | Type | Description |
|---|---|---|
typeBal | string | MSSanté mailbox type |
adresseBal | string | MSSanté address (e.g. jean.dupont@medecin.mssante.fr) |
dematerialisation | bool | true if dematerialization is enabled |
savoirFaire | CodeLabel | Skill associated with this mailbox |
structureIdentifiant | string | Identifier of the linked structure |
structureTypeIdentifiant | string | Identifier type of the structure |
serviceRattachement | string | Attached service |
raisonSociale | string | Legal name of the mailbox structure |
codePostal | string | Postal code of the mailbox structure |
departement | string | Department of the mailbox structure |
FhirData
Raw FHIR data returned by the ANS API. Available only if fhirApiKey is configured and the source is not disabled.
| Property | Type | Description |
|---|---|---|
practitioner | array|null | Raw FHIR Practitioner resource (decoded from JSON) |
practitionerRoles | array[] | Array of FHIR PractitionerRole resources (up to 50) |
CodeLabel
Code/label pair reused in all DTOs for reference data (title, profession, practice mode, etc.).
| Property | Type | Description |
|---|---|---|
code | string | Value code (e.g. '10', 'DR') |
libelle | string | Human-readable label (e.g. 'Médecin', 'Docteur') |
$profil->profession->libelle; // 'Médecin'
$profil->profession->code; // '10'
$profil->identite->civiliteExercice->libelle; // 'Docteur'
$arr = $profil->profession->categorie->toArray();
// ['code' => 'C', 'libelle' => 'Civil']
SourceStatus
Execution status of a data source.
| Property | Type | Description |
|---|---|---|
source | SourceName | Source identifier (enum) |
success | bool | true if the request succeeded |
rowCount | int | Number of rows returned |
durationMs | float | Execution time in milliseconds |
error | string|null | Error message if success === false, or reason for disabling |
RppsSearchCriteria
Criteria passed to search(). At least one non-null, non-empty criterion is required.
| Property | Type | Description |
|---|---|---|
nom | string|null | Practice last name (partial contains search) |
prenom | string|null | Practice first name (partial contains search) |
codePostal | string|null | Postal code — exact filter if 5 characters, otherwise contains |
RppsSearchResponse
Paginated response returned by search().
| Property | Type | Description |
|---|---|---|
results | RppsSearchResult[] | Results for the current page (deduplicated by RPPS) |
total | int | Total number of results (all pages) |
page | int | Current page number |
pageSize | int | Number of results per page |
durationMs | float | Execution time in milliseconds |
criteria | RppsSearchCriteria | Criteria used for this request |
RppsSearchResult
Lightweight search result (fewer fields than RppsFullProfile). For a full profile, call getByRpps($result->rpps).
| Property | Type | Description |
|---|---|---|
rpps | string | RPPS number |
identificationNationale | string | National PP identifier |
civilite | string | Practice title label |
nomExercice | string | Practice last name |
prenomExercice | string | Practice first name |
profession | CodeLabel | Profession (code + label) |
categorieProfessionnelle | CodeLabel | Professional category |
modeExercice | CodeLabel | Practice mode |
savoirFaire | CodeLabel | Primary skill |
raisonSociale | string | Legal name of the structure |
codePostal | string | Postal code of the structure |
libelleCommune | string | Municipality of the structure |
departement | string | Department of the structure |
Data Sources
The SDK aggregates 6 sources queried on each call to getByRpps(). Each source corresponds to a resource on data.gouv.fr or the ANS FHIR API.
| Source | Enum SourceName |
Resource ID | Provided data |
|---|---|---|---|
personne-activite |
PersonneActivite |
fffda7e9-0ea2-4c35-bba0-4496f3af935d |
Identity, profession, activities, structures |
diplomes |
Diplomes |
41ae70ac-90c8-4c4e-8644-4ef1b100f045 |
Obtained degrees and practice authorizations |
savoir-faire |
SavoirFaire |
fb55f15f-bd61-4402-b551-51ef387f2fab |
Declared specialties and competencies |
carte-cps |
CarteCps |
210eb05e-564b-42be-994a-d1800b63e9b7 |
CPS cards (type, number, validity) |
mssante |
Mssante |
afe01105-d9a1-41fe-921f-e40ea48b2ba6 |
MSSanté secure messaging |
fhir |
Fhir |
— ANS FHIR API | Practitioner + PractitionerRoles FHIR (API key required) |
tabular-api.data.gouv.fr/api/resources/{resource-id}/data/ with an exact filter on Identifiant PP (RPPS column). The FHIR source uses gateway.api.esante.gouv.fr/fhir/v2/Practitioner.
Enum SourceName
PHP backed enum used in SourceStatus::$source and RppsClientOptions::$disabledSources.
use QrCommunication\RppsApi\Enum\SourceName;
SourceName::Fhir->value; // 'fhir'
SourceName::PersonneActivite->value; // 'personne-activite'
SourceName::Diplomes->value; // 'diplomes'
SourceName::SavoirFaire->value; // 'savoir-faire'
SourceName::CarteCps->value; // 'carte-cps'
SourceName::Mssante->value; // 'mssante'
| Case | Value |
|---|---|
SourceName::Fhir | 'fhir' |
SourceName::PersonneActivite | 'personne-activite' |
SourceName::Diplomes | 'diplomes' |
SourceName::SavoirFaire | 'savoir-faire' |
SourceName::CarteCps | 'carte-cps' |
SourceName::Mssante | 'mssante' |
Error Handling
The SDK throws RppsException (extends \RuntimeException) in two cases: invalid RPPS number or missing search criteria. Network or API errors are silently caught and recorded in RppsFullProfile::$sources with success === false.
use QrCommunication\RppsApi\Exception\RppsException;
// ── Cas 1 : RPPS invalide ───────────────────────────────────────
try {
$profil = $client->getByRpps('123');
} catch (RppsException $e) {
// "Numéro RPPS invalide : "123". Le RPPS doit contenir exactement 11 chiffres."
echo $e->getMessage();
}
// ── Cas 2 : aucun critère de recherche ─────────────────────────
try {
$results = $client->search(new RppsSearchCriteria());
} catch (RppsException $e) {
// "Au moins un critère de recherche est obligatoire (nom, prenom ou codePostal)."
echo $e->getMessage();
}
// ── Cas 3 : erreur source (silencieuse, consultable après coup) ─
$profil = $client->getByRpps('10005173140');
foreach ($profil->sources as $source) {
if (!$source->success) {
// La source a échoué, mais le reste du profil est disponible
echo "[{$source->source->value}] ERREUR : {$source->error}\n";
}
}
Examples
Full profile by RPPS
$client = new RppsClient(new RppsClientOptions(
fhirApiKey: getenv('ESANTE_API_KEY'),
));
$profil = $client->getByRpps('10005173140');
// Identité
echo $profil->identite->civiliteExercice->libelle; // 'Docteur'
echo $profil->identite->prenomExercice; // 'JEAN'
echo $profil->identite->nomExercice; // 'DUPONT'
// Profession
echo $profil->profession->libelle; // 'Médecin'
echo $profil->profession->code; // '10'
// Structures d'exercice
foreach ($profil->activites as $activite) {
echo $activite->structure->raisonSociale . "\n";
echo $activite->structure->adresse->libelleCommune . ' ';
echo $activite->structure->adresse->codePostal . "\n";
echo $activite->modeExercice->libelle . "\n";
}
// Diplômes
foreach ($profil->diplomesEtAutorisations as $da) {
if ($da->diplome !== null) {
echo '[Diplôme] ' . $da->diplome->libelle . "\n";
}
if ($da->typeAutorisation !== null) {
echo '[Autorisation] ' . $da->typeAutorisation->libelle . "\n";
}
}
// Messageries MSSanté
foreach ($profil->messageriesMssante as $mss) {
echo $mss->adresseBal . ' (' . $mss->typeBal . ")\n";
}
// FHIR (si clé configurée)
if ($profil->fhir->practitioner !== null) {
echo 'ID FHIR : ' . $profil->fhir->practitioner['id'] . "\n";
echo count($profil->fhir->practitionerRoles) . " rôle(s) FHIR\n";
}
Search and enrichment
// Recherche initiale
$results = $client->search(new RppsSearchCriteria(
nom: 'MARTIN',
codePostal: '69',
));
echo "Trouvé : {$results->total} praticiens\n";
// Pagination
if ($results->total > $results->pageSize) {
$totalPages = ceil($results->total / $results->pageSize);
echo "Affichage page 1/{$totalPages}\n";
}
// Afficher les résultats
foreach ($results->results as $r) {
echo "{$r->civilite} {$r->prenomExercice} {$r->nomExercice}";
echo " — {$r->profession->libelle} ({$r->modeExercice->libelle})";
echo " — {$r->libelleCommune} {$r->codePostal}\n";
}
// Enrichir le premier résultat avec le profil complet
if (!empty($results->results)) {
$profilComplet = $client->getByRpps($results->results[0]->rpps);
}
Disable sources
Useful to speed up requests when certain data is not needed.
// Identité + profession uniquement (sans CPS, MSSanté, FHIR)
$clientLeger = new RppsClient(new RppsClientOptions(
disabledSources: ['carte-cps', 'mssante', 'fhir'],
));
// Identité seulement (désactiver tout sauf personne-activite)
$clientMinimal = new RppsClient(new RppsClientOptions(
disabledSources: ['carte-cps', 'mssante', 'fhir', 'diplomes', 'savoir-faire'],
));
// Vérifier quelles sources ont été désactivées dans la réponse
$profil = $clientLeger->getByRpps('10005173140');
foreach ($profil->sources as $src) {
if (!$src->success && $src->error === 'disabled') {
echo "[DESACTIVE] {$src->source->value}\n";
}
}
FHIR Configuration
The ANS FHIR API is free but requires a Gravitee API key. Without a key, the FHIR source is ignored (no error, the profile remains complete from other sources).
- Create an account on portal.api.esante.gouv.fr
- Create an application
- Subscribe to the API Annuaire Santé en libre accès offer
- Retrieve the
ESANTE-API-KEYkey
$client = new RppsClient(new RppsClientOptions(
fhirApiKey: $_ENV['ESANTE_API_KEY'], // ou getenv('ESANTE_API_KEY')
));
ESANTE-API-KEY on each request to gateway.api.esante.gouv.fr/fhir/v2. It is never transmitted to the Tabular APIs.
AI Skill
The SDK includes an AI skill that installs complete documentation directly into your code agent (Claude Code, Cursor, Codex, Windsurf, Cline, Aider, Gemini CLI).
bash vendor/qrcommunication/rppsapi/skill/install.sh
Manual installation:
cp -r vendor/qrcommunication/rppsapi/skill ~/.claude/skills/sdk-rpps