---
sdk: rppsapi-php
package: qrcommunication/rppsapi
language: PHP
version: "1.x"
requires: "PHP 8.2+"
license: PolyForm Noncommercial 1.0.0
github: https://github.com/qrcommunication/rppsapi-php
packagist: https://packagist.org/packages/qrcommunication/rppsapi
description: >
  SDK PHP pour interroger l'ensemble des sources de données RPPS
  (Répertoire Partagé des Professionnels de Santé) en une seule requête.
  Agrège 6 sources : 5 ressources Tabular data.gouv.fr + API FHIR ANS.
tags:
  - rpps
  - sante
  - annuaire
  - professionnels-de-sante
  - fhir
  - mssante
  - php
updated_at: 2026-03-29
---

# SDK PHP — qrcommunication/rppsapi

SDK PHP pour interroger l'ensemble des sources de données RPPS (Répertoire Partagé des Professionnels de Santé) en une seule requête.

## Installation

```bash
composer require qrcommunication/rppsapi
```

Prérequis : PHP 8.2+, ext-json. La dépendance `guzzlehttp/guzzle` est incluse automatiquement.

---

## Quick Start

```php
<?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
$profil = $client->getByRpps('10005173140');

echo $profil->identite->civiliteExercice->libelle . ' ';
echo $profil->identite->prenomExercice . ' ' . $profil->identite->nomExercice . "\n";
echo $profil->profession->libelle . "\n";

foreach ($profil->activites as $activite) {
    echo $activite->structure->raisonSociale . "\n";
}

// Recherche par critères
$results = $client->search(new RppsSearchCriteria(
    nom: 'DUPONT',
    codePostal: '75',
));

echo "Total : {$results->total}\n";
foreach ($results->results as $r) {
    echo "{$r->civilite} {$r->prenomExercice} {$r->nomExercice} — {$r->profession->libelle}\n";
}
```

---

## Configuration — RppsClientOptions

`RppsClientOptions` est un objet `final readonly`. Tous les paramètres ont des valeurs par défaut.

```php
$options = new RppsClientOptions(
    fhirBaseUrl:     'https://gateway.api.esante.gouv.fr/fhir/v2',        // défaut
    fhirApiKey:      'votre-cle-esante-api-key',                          // null par défaut
    tabularBaseUrl:  'https://tabular-api.data.gouv.fr/api/resources',    // défaut
    timeout:         30.0,                                                  // secondes
    tabularPageSize: 100,                                                   // défaut
    disabledSources: ['fhir', 'mssante'],                                  // [] par défaut
);
```

| Paramètre | Type | Défaut | Description |
|-----------|------|--------|-------------|
| `fhirBaseUrl` | `string` | `'https://gateway.api.esante.gouv.fr/fhir/v2'` | URL de base de l'API FHIR ANS |
| `fhirApiKey` | `string\|null` | `null` | Clé API Gravitee (ESANTE-API-KEY). Sans clé, la source FHIR est ignorée. |
| `tabularBaseUrl` | `string` | `'https://tabular-api.data.gouv.fr/api/resources'` | URL de base de l'API Tabular data.gouv.fr |
| `timeout` | `float` | `30.0` | Timeout en secondes pour les requêtes HTTP Guzzle |
| `tabularPageSize` | `int` | `100` | Nombre de lignes par page pour les requêtes Tabular |
| `disabledSources` | `string[]` | `[]` | Sources à désactiver. Valeurs : `'fhir'`, `'personne-activite'`, `'diplomes'`, `'savoir-faire'`, `'carte-cps'`, `'mssante'` |

---

## Méthodes

### getByRpps

```php
public function getByRpps(string $rpps): RppsFullProfile
```

Récupère le profil complet d'un praticien par numéro RPPS (11 chiffres).
Interroge toutes les sources activées en séquentiel et retourne un objet agrégé.

**Paramètres**

| Paramètre | Type | Description |
|-----------|------|-------------|
| `$rpps` | `string` | Numéro RPPS à 11 chiffres. Les espaces en début/fin sont ignorés. |

**Lance** `RppsException` si le RPPS ne contient pas exactement 11 chiffres.

---

### search

```php
public function search(
    RppsSearchCriteria $criteria,
    int $page = 1,
    ?int $pageSize = null
): RppsSearchResponse
```

Recherche des professionnels par critères. Au moins un critère est obligatoire.
La recherche est partielle et insensible à la casse (`contains`).
Pour `codePostal`, une valeur de 5 caractères utilise un filtre exact.

**Paramètres**

| Paramètre | Type | Défaut | Description |
|-----------|------|--------|-------------|
| `$criteria` | `RppsSearchCriteria` | — | Critères de recherche |
| `$page` | `int` | `1` | Numéro de page (commence à 1) |
| `$pageSize` | `int\|null` | `null` | Résultats par page. Si `null`, utilise `tabularPageSize` des options. |

**Lance** `RppsException` si aucun critère n'est fourni.

---

### validateRpps

```php
public static function validateRpps(string $rpps): string
```

Valide un numéro RPPS. Retourne le numéro nettoyé (trim) si valide.
Lance `RppsException` si le format est incorrect.

```php
$clean = RppsClient::validateRpps('10005173140'); // '10005173140'
RppsClient::validateRpps('123'); // Lance RppsException
```

---

## DTOs

Tous les DTOs sont des classes `final readonly`. Les valeurs absentes sont des chaînes vides `''`
ou des instances par défaut — jamais `null`, sauf `DiplomeEtAutorisation` et `FhirData`.

### RppsFullProfile

Profil agrégé retourné par `getByRpps()`.

| Propriété | Type | Description |
|-----------|------|-------------|
| `rpps` | `string` | Numéro RPPS à 11 chiffres |
| `identificationNationale` | `string` | Identifiant national PP |
| `identite` | `Identite` | Civilité, nom et prénom d'exercice |
| `profession` | `Profession` | Code, libellé profession et catégorie |
| `activites` | `Activite[]` | Activités et structures (une par structure avec identifiant technique non vide) |
| `diplomesEtAutorisations` | `DiplomeEtAutorisation[]` | Diplômes et autorisations d'exercice |
| `savoirFaire` | `SavoirFaire[]` | Spécialités et compétences |
| `cartesCps` | `CarteCps[]` | Cartes CPS |
| `messageriesMssante` | `MessagerieMssante[]` | Adresses MSSanté |
| `fhir` | `FhirData` | Données FHIR brutes (vide si pas de clé API) |
| `sources` | `SourceStatus[]` | Statut de chaque source interrogée |
| `queriedAt` | `string` | Horodatage ISO 8601 de la requête |
| `totalDurationMs` | `float` | Durée totale en millisecondes |

---

### Identite

| Propriété | Type | Description |
|-----------|------|-------------|
| `civilite` | `CodeLabel` | Civilité administrative |
| `civiliteExercice` | `CodeLabel` | Civilité d'exercice (ex : `DR` / `Docteur`) |
| `nomExercice` | `string` | Nom d'exercice |
| `prenomExercice` | `string` | Prénom d'exercice |

---

### Profession

| Propriété | Type | Description |
|-----------|------|-------------|
| `code` | `string` | Code profession (ex : `10`) |
| `libelle` | `string` | Libellé profession (ex : `Médecin`) |
| `categorie` | `CodeLabel` | Catégorie professionnelle |

---

### Activite

| Propriété | Type | Description |
|-----------|------|-------------|
| `modeExercice` | `CodeLabel` | Mode d'exercice (libéral, salarié…) |
| `savoirFaire` | `CodeLabel` | Savoir-faire associé à l'activité |
| `typeSavoirFaire` | `CodeLabel` | Type de savoir-faire |
| `secteurActivite` | `CodeLabel` | Secteur d'activité |
| `sectionTableauPharmaciens` | `CodeLabel` | Section du tableau des pharmaciens |
| `role` | `CodeLabel` | Rôle dans la structure |
| `genreActivite` | `CodeLabel` | Genre d'activité |
| `autoriteEnregistrement` | `string` | Autorité d'enregistrement |
| `structure` | `Structure` | Structure d'exercice rattachée |

---

### Structure

| Propriété | Type | Description |
|-----------|------|-------------|
| `identifiantTechnique` | `string` | Identifiant technique de la structure |
| `raisonSociale` | `string` | Raison sociale du site |
| `enseigneCommerciale` | `string` | Enseigne commerciale |
| `numeroSiret` | `string` | Numéro SIRET |
| `numeroSiren` | `string` | Numéro SIREN |
| `numeroFinessSite` | `string` | Numéro FINESS du site |
| `numeroFinessEtablissementJuridique` | `string` | Numéro FINESS de l'établissement juridique |
| `telephone` | `string` | Téléphone principal |
| `telephone2` | `string` | Téléphone secondaire |
| `telecopie` | `string` | Télécopie |
| `email` | `string` | E-mail de la structure |
| `codeDepartement` | `string` | Code département |
| `libelleDepartement` | `string` | Libellé département |
| `ancienIdentifiant` | `string` | Ancien identifiant de la structure |
| `adresse` | `AdresseStructure` | Adresse postale complète |

---

### AdresseStructure

| Propriété | Type | Description |
|-----------|------|-------------|
| `complementDestinataire` | `string` | Complément destinataire |
| `complementPointGeographique` | `string` | Complément point géographique |
| `numeroVoie` | `string` | Numéro de voie |
| `indiceRepetitionVoie` | `string` | Indice répétition voie (bis, ter…) |
| `codeTypeVoie` | `string` | Code type de voie |
| `libelleTypeVoie` | `string` | Libellé type de voie (Rue, Avenue…) |
| `libelleVoie` | `string` | Nom de la voie |
| `mentionDistribution` | `string` | Mention de distribution |
| `bureauCedex` | `string` | Bureau cedex |
| `codePostal` | `string` | Code postal |
| `codeCommune` | `string` | Code INSEE commune |
| `libelleCommune` | `string` | Libellé commune |
| `codePays` | `string` | Code pays |
| `libellePays` | `string` | Libellé pays |

---

### DiplomeEtAutorisation

Représente soit un diplôme, soit une autorisation. Les propriétés sont `null` si absentes.

| Propriété | Type | Description |
|-----------|------|-------------|
| `typeDiplome` | `CodeLabel\|null` | Type de diplôme (non nul si entrée diplôme) |
| `diplome` | `CodeLabel\|null` | Diplôme obtenu (code + libellé) |
| `typeAutorisation` | `CodeLabel\|null` | Type d'autorisation (non nul si entrée autorisation) |
| `disciplineAutorisation` | `CodeLabel\|null` | Discipline de l'autorisation |

```php
foreach ($profil->diplomesEtAutorisations as $da) {
    if ($da->diplome !== null) {
        echo '[Diplôme] ' . $da->diplome->libelle;
    }
    if ($da->typeAutorisation !== null) {
        echo '[Autorisation] ' . $da->typeAutorisation->libelle;
    }
}
```

---

### SavoirFaire

| Propriété | Type | Description |
|-----------|------|-------------|
| `profession` | `CodeLabel` | Profession associée |
| `categorieProfessionnelle` | `CodeLabel` | Catégorie professionnelle |
| `typeSavoirFaire` | `CodeLabel` | Type (spécialité, capacité…) |
| `savoirFaire` | `CodeLabel` | Savoir-faire (code + libellé) |

---

### CarteCps

| Propriété | Type | Description |
|-----------|------|-------------|
| `typeCarte` | `CodeLabel` | Type de carte CPS |
| `numeroCarte` | `string` | Numéro de carte |
| `identifiantNationalCarte` | `string` | Identifiant national contenu dans la carte |
| `dateDebutValidite` | `string` | Date de début de validité |
| `dateFinValidite` | `string` | Date de fin de validité |
| `dateOpposition` | `string` | Date d'opposition (si carte opposée) |
| `dateMiseAJour` | `string` | Date de mise à jour |

---

### MessagerieMssante

| Propriété | Type | Description |
|-----------|------|-------------|
| `typeBal` | `string` | Type de BAL MSSanté |
| `adresseBal` | `string` | Adresse MSSanté (ex : `jean.dupont@medecin.mssante.fr`) |
| `dematerialisation` | `bool` | `true` si dématérialisation activée |
| `savoirFaire` | `CodeLabel` | Savoir-faire associé à cette boîte |
| `structureIdentifiant` | `string` | Identifiant de la structure rattachée |
| `structureTypeIdentifiant` | `string` | Type d'identifiant de la structure |
| `serviceRattachement` | `string` | Service de rattachement |
| `raisonSociale` | `string` | Raison sociale de la structure BAL |
| `codePostal` | `string` | Code postal de la structure BAL |
| `departement` | `string` | Département de la structure BAL |

---

### FhirData

Disponible uniquement si `fhirApiKey` est configurée et la source non désactivée.

| Propriété | Type | Description |
|-----------|------|-------------|
| `practitioner` | `array\|null` | Ressource FHIR `Practitioner` brute (décodée depuis JSON) |
| `practitionerRoles` | `array[]` | Tableau de ressources FHIR `PractitionerRole` (jusqu'à 50) |

---

### CodeLabel

Paire code/libellé réutilisée dans tous les DTOs.

| Propriété | Type | Description |
|-----------|------|-------------|
| `code` | `string` | Code de la valeur (ex : `'10'`, `'DR'`) |
| `libelle` | `string` | Libellé humain (ex : `'Médecin'`, `'Docteur'`) |

Méthode utilitaire : `toArray(): array` retourne `['code' => ..., 'libelle' => ...]`.

---

### SourceStatus

| Propriété | Type | Description |
|-----------|------|-------------|
| `source` | `SourceName` | Identifiant de la source (enum) |
| `success` | `bool` | `true` si la requête a réussi |
| `rowCount` | `int` | Nombre de lignes retournées |
| `durationMs` | `float` | Durée d'exécution en millisecondes |
| `error` | `string\|null` | Message d'erreur ou raison de désactivation (`'disabled'`) |

---

### RppsSearchCriteria

| Propriété | Type | Description |
|-----------|------|-------------|
| `nom` | `string\|null` | Nom d'exercice (recherche partielle `contains`) |
| `prenom` | `string\|null` | Prénom d'exercice (recherche partielle `contains`) |
| `codePostal` | `string\|null` | Code postal — filtre `exact` si 5 caractères, sinon `contains` |

Au moins une propriété non nulle et non vide est requise.

---

### RppsSearchResponse

| Propriété | Type | Description |
|-----------|------|-------------|
| `results` | `RppsSearchResult[]` | Résultats de la page courante (dédupliqués par RPPS) |
| `total` | `int` | Nombre total de résultats (toutes pages) |
| `page` | `int` | Numéro de page courant |
| `pageSize` | `int` | Nombre de résultats par page |
| `durationMs` | `float` | Durée d'exécution en millisecondes |
| `criteria` | `RppsSearchCriteria` | Critères utilisés pour cette requête |

---

### RppsSearchResult

Résultat allégé. Pour un profil complet, appeler `getByRpps($result->rpps)`.

| Propriété | Type | Description |
|-----------|------|-------------|
| `rpps` | `string` | Numéro RPPS |
| `identificationNationale` | `string` | Identifiant national PP |
| `civilite` | `string` | Libellé civilité d'exercice |
| `nomExercice` | `string` | Nom d'exercice |
| `prenomExercice` | `string` | Prénom d'exercice |
| `profession` | `CodeLabel` | Profession (code + libellé) |
| `categorieProfessionnelle` | `CodeLabel` | Catégorie professionnelle |
| `modeExercice` | `CodeLabel` | Mode d'exercice |
| `savoirFaire` | `CodeLabel` | Savoir-faire principal |
| `raisonSociale` | `string` | Raison sociale de la structure |
| `codePostal` | `string` | Code postal de la structure |
| `libelleCommune` | `string` | Commune de la structure |
| `departement` | `string` | Département de la structure |

---

## Sources de données

| Source | Enum `SourceName` | Resource ID (Tabular) | Données |
|--------|-------------------|-----------------------|---------|
| `personne-activite` | `PersonneActivite` | `fffda7e9-0ea2-4c35-bba0-4496f3af935d` | Identité, profession, activités, structures |
| `diplomes` | `Diplomes` | `41ae70ac-90c8-4c4e-8644-4ef1b100f045` | Diplômes et autorisations |
| `savoir-faire` | `SavoirFaire` | `fb55f15f-bd61-4402-b551-51ef387f2fab` | Spécialités et compétences |
| `carte-cps` | `CarteCps` | `210eb05e-564b-42be-994a-d1800b63e9b7` | Cartes CPS |
| `mssante` | `Mssante` | `afe01105-d9a1-41fe-921f-e40ea48b2ba6` | Messageries MSSanté |
| `fhir` | `Fhir` | — API FHIR ANS | Practitioner + PractitionerRoles FHIR |

Les 5 sources Tabular interrogent `tabular-api.data.gouv.fr/api/resources/{resource-id}/data/`
avec un filtre exact sur la colonne `Identifiant PP`.
La source FHIR interroge `gateway.api.esante.gouv.fr/fhir/v2/Practitioner?identifier={rpps}`.

---

## Enum SourceName

Backed enum PHP (`:string`) utilisé dans `SourceStatus::$source`.

```php
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'
```

---

## Gestion des erreurs

`RppsException` étend `\RuntimeException`.

**Cas déclencheurs :**

```php
// 1. Numéro RPPS invalide
RppsClient::validateRpps('123');
// "Numéro RPPS invalide : "123". Le RPPS doit contenir exactement 11 chiffres."

// 2. Aucun critère de recherche
$client->search(new RppsSearchCriteria());
// "Au moins un critère de recherche est obligatoire (nom, prenom ou codePostal)."
```

**Erreurs réseau (silencieuses) :**
Les erreurs HTTP/réseau par source sont capturées silencieusement. Le profil est retourné
avec les données disponibles. Inspecter `RppsFullProfile::$sources` pour le détail :

```php
$profil = $client->getByRpps('10005173140');

foreach ($profil->sources as $source) {
    if (!$source->success) {
        echo "[{$source->source->value}] ERREUR : {$source->error}\n";
    }
}
```

---

## Exemples complets

### Profil complet avec toutes les données

```php
$client = new RppsClient(new RppsClientOptions(
    fhirApiKey: getenv('ESANTE_API_KEY'),
));

$profil = $client->getByRpps('10005173140');

// Identité
echo $profil->identite->civiliteExercice->libelle . ' ';
echo $profil->identite->prenomExercice . ' ' . $profil->identite->nomExercice . "\n";

// Structures
foreach ($profil->activites as $activite) {
    echo $activite->structure->raisonSociale . "\n";
    echo $activite->structure->adresse->numeroVoie . ' ';
    echo $activite->structure->adresse->libelleTypeVoie . ' ';
    echo $activite->structure->adresse->libelleVoie . "\n";
    echo $activite->structure->adresse->codePostal . ' ';
    echo $activite->structure->adresse->libelleCommune . "\n";
}

// Diplômes
foreach ($profil->diplomesEtAutorisations as $da) {
    if ($da->diplome !== null) {
        echo '[Diplôme] ' . $da->diplome->libelle . "\n";
    }
}

// Messageries MSSanté
foreach ($profil->messageriesMssante as $mss) {
    echo $mss->adresseBal . "\n";
}

// FHIR (si clé configurée)
if ($profil->fhir->practitioner !== null) {
    echo 'ID FHIR : ' . $profil->fhir->practitioner['id'] . "\n";
}

// Durée totale et statuts
echo "Durée totale : {$profil->totalDurationMs}ms\n";
foreach ($profil->sources as $src) {
    $status = $src->success ? 'OK' : 'ERR';
    echo "  [{$status}] {$src->source->value} {$src->durationMs}ms\n";
}
```

### Recherche paginée

```php
$page = 1;
$pageSize = 50;

do {
    $results = $client->search(
        new RppsSearchCriteria(nom: 'MARTIN', codePostal: '69'),
        page: $page,
        pageSize: $pageSize,
    );

    foreach ($results->results as $r) {
        echo "{$r->prenomExercice} {$r->nomExercice} — {$r->profession->libelle}\n";
    }

    $page++;
} while (($page - 1) * $pageSize < $results->total);
```

### Désactiver des sources pour alléger les requêtes

```php
// Identité + profession uniquement
$clientLeger = new RppsClient(new RppsClientOptions(
    disabledSources: ['carte-cps', 'mssante', 'fhir'],
));

// Profil minimal (personne-activite seulement)
$clientMinimal = new RppsClient(new RppsClientOptions(
    disabledSources: ['carte-cps', 'mssante', 'fhir', 'diplomes', 'savoir-faire'],
));
```

---

## Configuration de la clé FHIR

L'API FHIR ANS est gratuite mais nécessite une clé d'API Gravitee.

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`

```php
$client = new RppsClient(new RppsClientOptions(
    fhirApiKey: $_ENV['ESANTE_API_KEY'],
));
```

La clé est transmise dans le header HTTP `ESANTE-API-KEY`. Sans clé, la source FHIR
est ignorée sans erreur — `fhir.success === true` avec `error === 'disabled: no API key'`.

---

## Injection de client HTTP personnalisé

`RppsClient` accepte un `GuzzleHttp\ClientInterface` personnalisé en second argument.
Utile pour les tests ou pour configurer des proxies, middlewares, etc.

```php
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;

$stack = HandlerStack::create();
$stack->push(Middleware::log($logger, new MessageFormatter()));

$http = new Client(['handler' => $stack, 'timeout' => 10.0]);

$client = new RppsClient(
    options: new RppsClientOptions(fhirApiKey: '...'),
    http: $http,
);
```

---

## Skill AI

Le SDK inclut un skill AI pour Claude Code, Cursor, Codex, Windsurf, Cline, Aider et Gemini CLI.

```bash
bash vendor/qrcommunication/rppsapi/skill/install.sh
```

Installation manuelle : `cp -r vendor/qrcommunication/rppsapi/skill ~/.claude/skills/sdk-rpps`

---

## Licence

PolyForm Noncommercial 1.0.0 — Usage non commercial uniquement.
Voir https://github.com/qrcommunication/rppsapi-php/blob/main/LICENSE
