ediverse Explorer la plateforme

À la une PEPPOL BIS Billing 3.0 L’obligation européenne d’e-invoicing arrive : France sept 2026, Belgique janv 2026, Allemagne 2025.

Bundle — Conteneur multi-ressources

L'enveloppe qui permet à FHIR de transporter plusieurs ressources d'un coup : résultat de recherche, transaction atomique, document patient, message technique.

Objet de la ressource

Bundle répond à une question simple : comment transmettre dix ressources FHIR dans une seule charge utile ? Que ce soit le résultat d'une search qui retourne quinze Patients, une transaction atomique qui crée un Patient et ses Observations associées, ou un document IPS qui consolide le résumé patient, Bundle est l'enveloppe universelle.

La discriminance se fait via le champ type, qui prend l'une des huit valeurs autorisées et dicte la sémantique du Bundle (avec ou sans transaction, persistance ou transit, paged ou non).

Les huit types de Bundle

TypeUsagePersistanceSémantique transactionnelle
documentDocument FHIR (résumé patient IPS, CDA-like)Oui — peut être stockéPas de transaction. Première entry = Composition.
messageÉchange asynchrone style HL7 v2TransitoirePas de transaction. Première entry = MessageHeader.
transactionCréation / mise à jour multi-ressources atomiqueTransitoireAtomique — toutes les entries ou aucune. Le serveur résout les références.
transaction-responseRéponse du serveur à une transactionTransitoireListe des résultats individuels.
batchOpérations multi-ressources indépendantesTransitoireNon atomique — chaque entry réussit ou échoue indépendamment.
batch-responseRéponse du serveur à un batchTransitoireListe des résultats individuels.
historyHistorique d'une ressource ou du serveurTransitoirePas de transaction.
searchsetRésultat d'une rechercheTransitoireInclut pagination via link.
collectionSac générique de ressources, libre d'interprétationVariablePas de sémantique technique imposée.
subscription-notificationNotification d'une Subscription R5TransitoireMécanisme push temps réel.

Champs clés

ChampTypeCardinalitéRôle
typecode1..1Mandatoire. L'un des huit (dix avec subscription-notification) types listés ci-dessus.
timestampinstant0..1Date/heure de génération du Bundle.
totalunsignedInt0..1Total de matches pour un searchset (peut différer de entry.length en mode paginé).
linkBackboneElement[]0..*Liens de pagination : self, next, previous, first, last.
entryBackboneElement[]0..*Liste des ressources transportées. Chaque entry contient :
entry.fullUrluri0..1URL absolue de la ressource. Critique pour la résolution des références.
entry.resourceResource0..1La ressource elle-même.
entry.searchBackboneElement0..1Pour searchset : mode (match, include, outcome) et score.
entry.requestBackboneElement0..1Pour transaction / batch : méthode HTTP (POST, PUT, DELETE, PATCH, GET) et URL.
entry.responseBackboneElement0..1Pour transaction-response / batch-response : status, location, etag, lastModified, outcome.
signatureSignature0..1Signature électronique du Bundle (pour les types document et message notamment).

Exemple — transaction

Bundle transaction qui crée un nouveau Patient, met à jour un Patient existant (PUT idempotent), et crée une Observation poids rattachée au premier Patient via une référence relative à urn:uuid: :

json bundle-transaction.json
{
  "resourceType": "Bundle",
  "id": "bundle-transaction",
  "type": "transaction",
  "entry": [
    {
      "fullUrl": "urn:uuid:88f151c0-a954-468a-88bd-5ae15c08e059",
      "resource": {
        "resourceType": "Patient",
        "identifier": [{
          "system": "http:/example.org/fhir/ids",
          "value": "234234"
        }],
        "active": true,
        "name": [{ "family": "Chalmers", "given": ["Peter", "James"] }],
        "gender": "male",
        "birthDate": "1974-12-25"
      },
      "request": {
        "method": "POST",
        "url": "Patient"
      }
    },
    {
      "fullUrl": "http://example.org/fhir/Patient/123",
      "resource": {
        "resourceType": "Patient",
        "id": "123",
        "active": true,
        "name": [{ "family": "Smith", "given": ["John"] }],
        "gender": "male",
        "birthDate": "1980-01-15"
      },
      "request": {
        "method": "PUT",
        "url": "Patient/123"
      }
    },
    {
      "fullUrl": "urn:uuid:79378cb8-8f72-4d33-8b8d-d6c0c5e1e3bb",
      "resource": {
        "resourceType": "Observation",
        "status": "final",
        "code": {
          "coding": [{
            "system": "http://loinc.org",
            "code": "29463-7",
            "display": "Body Weight"
          }]
        },
        "subject": {
          "reference": "urn:uuid:88f151c0-a954-468a-88bd-5ae15c08e059"
        },
        "effectiveDateTime": "2026-05-14T10:30:00Z",
        "valueQuantity": {
          "value": 72.5,
          "unit": "kg",
          "system": "http://unitsofmeasure.org",
          "code": "kg"
        }
      },
      "request": {
        "method": "POST",
        "url": "Observation"
      }
    }
  ]
}

À retenir :

  • fullUrl en urn:uuid: : pour les ressources créées dans le bundle (pas encore d'id côté serveur), on utilise un UUID temporaire. Le serveur le remplace par l'id alloué et résout les références internes.
  • Référence interne : Observation.subject.reference = "urn:uuid:88f151c0..." pointe vers le Patient qui sera créé dans la même transaction. Le serveur garantit la cohérence référentielle.
  • Méthode HTTP par entry : POST pour les créations (id alloué par le serveur), PUT pour les mises à jour idempotentes (id fourni par le client).
  • Atomicité : si la moindre entry échoue, le serveur ROLLBACK l'intégralité. Aucun changement partiel.

Exemple — searchset

Bundle searchset retourné par GET /Patient?family=Chalmers avec deux matches et un lien next pour la pagination :

json bundle-searchset.json
{
  "resourceType": "Bundle",
  "id": "searchset-example",
  "type": "searchset",
  "total": 2,
  "link": [
    {
      "relation": "self",
      "url": "https://server/Patient?family=Chalmers"
    },
    {
      "relation": "next",
      "url": "https://server/Patient?family=Chalmers&_page=2"
    }
  ],
  "entry": [
    {
      "fullUrl": "https://server/Patient/example",
      "resource": {
        "resourceType": "Patient",
        "id": "example",
        "name": [{ "family": "Chalmers", "given": ["Peter"] }],
        "gender": "male",
        "birthDate": "1974-12-25"
      },
      "search": {
        "mode": "match",
        "score": 1.0
      }
    },
    {
      "fullUrl": "https://server/Patient/example2",
      "resource": {
        "resourceType": "Patient",
        "id": "example2",
        "name": [{ "family": "Chalmers", "given": ["Sarah"] }],
        "gender": "female",
        "birthDate": "1981-07-12"
      },
      "search": {
        "mode": "match",
        "score": 0.95
      }
    }
  ]
}
  • total : deux matches (peut être omis si serveur en mode count-disabled).
  • link[self] : la requête originale.
  • link[next] : page suivante dans le jeu de résultats. Le client suit ces liens pour paginer.
  • entry.search.mode : match = ressource qui correspond au critère ; include = ressource incluse via _include mais pas dans le critère initial.
  • entry.search.score : pertinence du match (0..1), uniquement quand le serveur supporte le ranking sémantique.

Transaction vs batch

Les deux types transaction et batch partagent la même structure mais ont une sémantique différente :

Aspecttransactionbatch
AtomicitéOui — tout ou rienNon — chaque entry indépendante
Résolution de référenceLe serveur résout les urn:uuid: et les références circulaires entre entriesPas de résolution — chaque entry comme une requête isolée
Ordre d'exécutionCôté serveur, déterministe (DELETE, POST, PUT/PATCH, GET, HEAD)Ordre des entries
Cas d'usageCréation d'un dossier patient complet, post-test génétique avec ses observationsBulk update indépendant : rafraîchir 50 Observations isolément
PerformancePlus coûteux côté serveur (verrou + rollback)Moins coûteux, paralélisable

Pièges courants

  • fullUrl manquant ou non-URN : dans une transaction, un fullUrl manquant rend impossible la résolution des références internes. Toujours fournir, soit en urn:uuid:<UUID>, soit en URL absolue.
  • Référence absolue dans une transaction : si l'Observation référence le Patient via "reference": "http://server/Patient/123" alors que le Patient est créé dans le même bundle sans cet ID, la transaction échoue.
  • Confusion searchset et collection : certains serveurs anciens retournent collection au lieu de searchset — le client doit traiter les deux. Ne pas confondre côté création.
  • Bundle > 100 entries : la spec ne fixe pas de limite, mais la plupart des serveurs imposent un plafond (HAPI : 1000 par défaut, Epic R5 : 100). Pagination obligatoire au-delà.
  • Bundle document non signé : un document IPS sans signature peut être refusé par certaines autorités nationales (FR, NL, UK). Toujours inclure pour les documents persistants.
  • type=transaction en POST sur /Patient : un Bundle de type transaction se poste à la racine du serveur (POST /), pas sur l'URL d'une ressource individuelle.

Ressources liées

  • Composition — première entry obligatoire d'un Bundle document.
  • MessageHeader — première entry obligatoire d'un Bundle message.
  • OperationOutcome — porte les erreurs et warnings dans les réponses transaction / batch.
  • Subscription et SubscriptionStatus — déclenchent les notifications de type subscription-notification.
  • Parameters — alternative à Bundle pour les paramètres d'opération ($everything, $lastn…).

Voir aussi : Patient — la ressource démographique fondamentale, le hub FHIR R5, et l'équivalent fonctionnel d'un Bundle multi-ORM en v2 : ORM^O01.