For AI agents: the documentation index is at /docs/aidbox/llms.txt. A Markdown version of this page is available at /docs/aidbox/notebooks.md or by requesting it with the Accept: text/markdown header.
Aidbox Docs
All notebooks

SDC form packages: PlanDefinition to completion

A complete API walkthrough: reusable forms, FHIRPath branching, package prefills, draft saves, atomic completion, and extraction.

38 cells · updated Sep 8, 2026

This is a read-only view. Responses shown were saved when the notebook was published — open it in your own Aidbox to run the cells against live data.

SDC form packages: from PlanDefinition to completion

This walkthrough creates three reusable Questionnaires and a PlanDefinition, starts a CarePlan for a fictional patient, saves drafts, follows a FHIRPath branch, and completes the applicable responses together. First and last names flow from the intake response into later forms through rules on the PlanDefinition.

Run the REST cells in order. Use an Aidbox with this branch's SDC package operations and FHIR R4 or R4B. Requests use the current Aidbox session. The first run creates resources with the package-demo prefix.

Notebook cells do not substitute earlier responses automatically. Before running a cell, replace each {{PLACEHOLDER}} in that cell with the value described in the preceding instructions. In a Parameters response, locate resources by parameter name, such as carePlan or response.

The default path includes all three forms. For another package run, change the start request's requestId; repeating the same value resumes the existing package. Published definitions use business version 1.0.0; keep that version unchanged and publish another version when changing their meaning.

1. Create the reusable Questionnaires

The intake form uses ordinary SDC initial expressions to populate names from the Patient. The assessment and review forms declare their own name fields; the package will supply their values later. The pulse item also declares observation extraction, which runs when the package is completed.

Each request should return 200 or 201.

REST Send Cells run only inside Aidbox. Use Run in Aidbox above to open this notebook in your own instance.
PUT /fhir/Questionnaire/package-demo-intake
Accept: application/fhir+json
Content-Type: application/fhir+json

{
  "resourceType": "Questionnaire",
  "id": "package-demo-intake",
  "url": "https://example.org/fhir/Questionnaire/package-demo-intake",
  "version": "1.0.0",
  "name": "PackageDemoIntake",
  "title": "Package demo: intake",
  "status": "active",
  "subjectType": [
    "Patient"
  ],
  "item": [
    {
      "linkId": "first-name",
      "type": "string",
      "text": "First name",
      "required": true,
      "extension": [
        {
          "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression",
          "valueExpression": {
            "language": "text/fhirpath",
            "expression": "%patient.name.first().given.first()"
          }
        }
      ]
    },
    {
      "linkId": "last-name",
      "type": "string",
      "text": "Last name",
      "required": true,
      "extension": [
        {
          "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression",
          "valueExpression": {
            "language": "text/fhirpath",
            "expression": "%patient.name.first().family"
          }
        }
      ]
    },
    {
      "linkId": "needs-assessment",
      "type": "boolean",
      "text": "Include the pulse assessment?",
      "required": true
    }
  ]
}
REST Send Cells run only inside Aidbox. Use Run in Aidbox above to open this notebook in your own instance.
PUT /fhir/Questionnaire/package-demo-assessment
Accept: application/fhir+json
Content-Type: application/fhir+json

{
  "resourceType": "Questionnaire",
  "id": "package-demo-assessment",
  "url": "https://example.org/fhir/Questionnaire/package-demo-assessment",
  "version": "1.0.0",
  "name": "PackageDemoAssessment",
  "title": "Package demo: pulse assessment",
  "status": "active",
  "subjectType": [
    "Patient"
  ],
  "item": [
    {
      "linkId": "first-name",
      "type": "string",
      "text": "First name",
      "required": true
    },
    {
      "linkId": "last-name",
      "type": "string",
      "text": "Last name",
      "required": true
    },
    {
      "linkId": "pulse",
      "type": "integer",
      "text": "Pulse rate (beats per minute)",
      "required": true,
      "code": [
        {
          "system": "http://loinc.org",
          "code": "8867-4",
          "display": "Heart rate"
        }
      ],
      "extension": [
        {
          "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-observationExtract",
          "valueBoolean": true
        }
      ]
    }
  ]
}
REST Send Cells run only inside Aidbox. Use Run in Aidbox above to open this notebook in your own instance.
PUT /fhir/Questionnaire/package-demo-review
Accept: application/fhir+json
Content-Type: application/fhir+json

{
  "resourceType": "Questionnaire",
  "id": "package-demo-review",
  "url": "https://example.org/fhir/Questionnaire/package-demo-review",
  "version": "1.0.0",
  "name": "PackageDemoReview",
  "title": "Package demo: review",
  "status": "active",
  "subjectType": [
    "Patient"
  ],
  "item": [
    {
      "linkId": "first-name",
      "type": "string",
      "text": "First name",
      "required": true
    },
    {
      "linkId": "last-name",
      "type": "string",
      "text": "Last name",
      "required": true
    },
    {
      "linkId": "note",
      "type": "text",
      "text": "Review note"
    }
  ]
}

2. Define order, branching, and prefills

The PlanDefinition below has three ordered actions:

  • intake: always available.
  • assessment: available when the intake answer needs-assessment is true.
  • review: always available.

The condition uses FHIRPath and refers to the earlier response as %intake. An unanswered condition is pending; false excludes the assessment.

Both later actions have two sdc-package-prefill extensions. Each names an earlier sourceAction, a destination targetLinkId, and a FHIRPath expression evaluated against that source response. These mappings belong to this package, so the same Questionnaires can participate in other packages with different mappings.

REST Send Cells run only inside Aidbox. Use Run in Aidbox above to open this notebook in your own instance.
PUT /fhir/PlanDefinition/package-demo
Accept: application/fhir+json
Content-Type: application/fhir+json

{
  "resourceType": "PlanDefinition",
  "id": "package-demo",
  "url": "https://example.org/fhir/PlanDefinition/package-demo",
  "version": "1.0.0",
  "name": "PackageDemo",
  "title": "Intake, pulse assessment, and review",
  "status": "active",
  "action": [
    {
      "id": "intake",
      "title": "Package demo: intake",
      "definitionCanonical": "https://example.org/fhir/Questionnaire/package-demo-intake|1.0.0"
    },
    {
      "id": "assessment",
      "title": "Package demo: pulse assessment",
      "definitionCanonical": "https://example.org/fhir/Questionnaire/package-demo-assessment|1.0.0",
      "condition": [
        {
          "kind": "applicability",
          "expression": {
            "language": "text/fhirpath",
            "expression": "%intake.item.where(linkId = 'needs-assessment').answer.value = true"
          }
        }
      ],
      "extension": [
        {
          "url": "http://health-samurai.io/fhir/sdc/StructureDefinition/sdc-package-prefill",
          "extension": [
            {
              "url": "sourceAction",
              "valueId": "intake"
            },
            {
              "url": "targetLinkId",
              "valueString": "first-name"
            },
            {
              "url": "expression",
              "valueExpression": {
                "language": "text/fhirpath",
                "expression": "item.where(linkId = 'first-name').answer.value"
              }
            }
          ]
        },
        {
          "url": "http://health-samurai.io/fhir/sdc/StructureDefinition/sdc-package-prefill",
          "extension": [
            {
              "url": "sourceAction",
              "valueId": "intake"
            },
            {
              "url": "targetLinkId",
              "valueString": "last-name"
            },
            {
              "url": "expression",
              "valueExpression": {
                "language": "text/fhirpath",
                "expression": "item.where(linkId = 'last-name').answer.value"
              }
            }
          ]
        }
      ]
    },
    {
      "id": "review",
      "title": "Package demo: review",
      "definitionCanonical": "https://example.org/fhir/Questionnaire/package-demo-review|1.0.0",
      "extension": [
        {
          "url": "http://health-samurai.io/fhir/sdc/StructureDefinition/sdc-package-prefill",
          "extension": [
            {
              "url": "sourceAction",
              "valueId": "intake"
            },
            {
              "url": "targetLinkId",
              "valueString": "first-name"
            },
            {
              "url": "expression",
              "valueExpression": {
                "language": "text/fhirpath",
                "expression": "item.where(linkId = 'first-name').answer.value"
              }
            }
          ]
        },
        {
          "url": "http://health-samurai.io/fhir/sdc/StructureDefinition/sdc-package-prefill",
          "extension": [
            {
              "url": "sourceAction",
              "valueId": "intake"
            },
            {
              "url": "targetLinkId",
              "valueString": "last-name"
            },
            {
              "url": "expression",
              "valueExpression": {
                "language": "text/fhirpath",
                "expression": "item.where(linkId = 'last-name').answer.value"
              }
            }
          ]
        }
      ]
    }
  ]
}

3. Create the fictional patient

The intake form will start with Alex Rivera, populated from this Patient.

REST Send Cells run only inside Aidbox. Use Run in Aidbox above to open this notebook in your own instance.
PUT /fhir/Patient/package-demo-patient
Accept: application/fhir+json
Content-Type: application/fhir+json

{
  "resourceType": "Patient",
  "id": "package-demo-patient",
  "active": true,
  "name": [
    {
      "use": "official",
      "family": "Rivera",
      "given": [
        "Alex"
      ]
    }
  ]
}

4. Start a package instance

Start creates an active CarePlan, resolves the versioned definitions, and opens the first QuestionnaireResponse using the existing population mechanism. Responses are created when a form is first opened.

Expected: result = form, actionId = intake, populated names, and response status = in-progress.

Record these values from the returned Parameters:

  • CARE_PLAN_ID: the carePlan resource's id.
  • INTAKE_RESPONSE_ID: the response resource's id.
  • INTAKE_VERSION: that response's meta.versionId.

These resource versions are concurrency tokens; they are separate from a Questionnaire's business version.

REST Send Cells run only inside Aidbox. Use Run in Aidbox above to open this notebook in your own instance.
POST /fhir/PlanDefinition/package-demo/$sdc-package-start
Accept: application/fhir+json
Content-Type: application/fhir+json

{
  "resourceType": "Parameters",
  "parameter": [
    {
      "name": "subject",
      "valueReference": {
        "reference": "Patient/package-demo-patient"
      }
    },
    {
      "name": "requestId",
      "valueString": "package-demo-run-1"
    }
  ]
}

5. Observe the unanswered branch

Before saving the intake answer, try to advance. Expected: result = pending-input and assessment availability pending. The operation stops at that unresolved branch.

REST Send Cells run only inside Aidbox. Use Run in Aidbox above to open this notebook in your own instance.
POST /fhir/CarePlan/{{CARE_PLAN_ID}}/$sdc-package-next
Accept: application/fhir+json
Content-Type: application/fhir+json

{
  "resourceType": "Parameters",
  "parameter": [
    {
      "name": "currentActionId",
      "valueId": "intake"
    }
  ]
}

6. Save the intake draft

Correct the last name once to Rivera Chen and choose to include the pulse assessment. JSON Patch changes the answers while preserving the response's references and in-progress status.

Replace INTAKE_VERSION with the version returned by start. Expected: 200, with a new meta.versionId. Record that new value as INTAKE_VERSION for completion.

REST Send Cells run only inside Aidbox. Use Run in Aidbox above to open this notebook in your own instance.
PATCH /fhir/QuestionnaireResponse/{{INTAKE_RESPONSE_ID}}
Accept: application/fhir+json
Content-Type: application/json-patch+json
If-Match: W/"{{INTAKE_VERSION}}"

[
  {
    "op": "add",
    "path": "/item/0/answer",
    "value": [
      {
        "valueString": "Alex"
      }
    ]
  },
  {
    "op": "add",
    "path": "/item/1/answer",
    "value": [
      {
        "valueString": "Rivera Chen"
      }
    ]
  },
  {
    "op": "add",
    "path": "/item/2/answer",
    "value": [
      {
        "valueBoolean": true
      }
    ]
  }
]

7. Open the assessment

The saved Boolean now makes the assessment available. Expected: actionId = assessment, names Alex / Rivera Chen, and a blank pulse answer.

The corrected name comes from the intake draft. Opening this form leaves intake in-progress.

Record the returned response's id as ASSESSMENT_RESPONSE_ID and meta.versionId as ASSESSMENT_VERSION.

REST Send Cells run only inside Aidbox. Use Run in Aidbox above to open this notebook in your own instance.
POST /fhir/CarePlan/{{CARE_PLAN_ID}}/$sdc-package-next
Accept: application/fhir+json
Content-Type: application/fhir+json

{
  "resourceType": "Parameters",
  "parameter": [
    {
      "name": "currentActionId",
      "valueId": "intake"
    }
  ]
}

8. Save only the pulse answer

The third item in this Questionnaire is pulse. Patch only its answer; the prefilled first and last names stay in place.

Expected: 200, still in-progress. Record the returned meta.versionId as the new ASSESSMENT_VERSION. Saving the draft does not extract the Observation.

REST Send Cells run only inside Aidbox. Use Run in Aidbox above to open this notebook in your own instance.
PATCH /fhir/QuestionnaireResponse/{{ASSESSMENT_RESPONSE_ID}}
Accept: application/fhir+json
Content-Type: application/json-patch+json
If-Match: W/"{{ASSESSMENT_VERSION}}"

[
  {
    "op": "add",
    "path": "/item/2/answer",
    "value": [
      {
        "valueInteger": 70
      }
    ]
  }
]

9. Open the review form

Expected: actionId = review and names Alex / Rivera Chen, copied through this action's own prefill rules.

Record the returned response's id as REVIEW_RESPONSE_ID and meta.versionId as REVIEW_VERSION.

REST Send Cells run only inside Aidbox. Use Run in Aidbox above to open this notebook in your own instance.
POST /fhir/CarePlan/{{CARE_PLAN_ID}}/$sdc-package-next
Accept: application/fhir+json
Content-Type: application/fhir+json

{
  "resourceType": "Parameters",
  "parameter": [
    {
      "name": "currentActionId",
      "valueId": "assessment"
    }
  ]
}

10. Save the review note

The third item is note. Again, only the new answer is sent. Record the returned version as the new REVIEW_VERSION; the response remains in-progress.

REST Send Cells run only inside Aidbox. Use Run in Aidbox above to open this notebook in your own instance.
PATCH /fhir/QuestionnaireResponse/{{REVIEW_RESPONSE_ID}}
Accept: application/fhir+json
Content-Type: application/json-patch+json
If-Match: W/"{{REVIEW_VERSION}}"

[
  {
    "op": "add",
    "path": "/item/2/answer",
    "value": [
      {
        "valueString": "Forms reviewed with the patient."
      }
    ]
  }
]

11. Revisit the assessment

Use actionId to select a form directly. Expected: the same assessment response, its saved pulse 70, and the existing names. Revisiting does not populate a second response or overwrite saved answers.

REST Send Cells run only inside Aidbox. Use Run in Aidbox above to open this notebook in your own instance.
POST /fhir/CarePlan/{{CARE_PLAN_ID}}/$sdc-package-next
Accept: application/fhir+json
Content-Type: application/fhir+json

{
  "resourceType": "Parameters",
  "parameter": [
    {
      "name": "actionId",
      "valueId": "assessment"
    }
  ]
}

12. Finish navigation

Advance after the final action. Expected: result = ready-for-review. This finishes navigation; required-answer validation happens during completion.

REST Send Cells run only inside Aidbox. Use Run in Aidbox above to open this notebook in your own instance.
POST /fhir/CarePlan/{{CARE_PLAN_ID}}/$sdc-package-next
Accept: application/fhir+json
Content-Type: application/fhir+json

{
  "resourceType": "Parameters",
  "parameter": [
    {
      "name": "currentActionId",
      "valueId": "review"
    }
  ]
}

13. Read the package before completion

The batch below reads the CarePlan and all three responses. Expected: CarePlan active; every response in-progress.

Before completing, finish pending saves and copy the current meta.versionId from each entry.resource:

  • Entry 1, CarePlan → CARE_PLAN_VERSION.
  • Entry 2, intake response → INTAKE_VERSION.
  • Entry 3, assessment response → ASSESSMENT_VERSION.
  • Entry 4, review response → REVIEW_VERSION.

Use the CarePlan's latest version, which changes as responses are linked to it.

REST Send Cells run only inside Aidbox. Use Run in Aidbox above to open this notebook in your own instance.
POST /fhir
Accept: application/fhir+json
Content-Type: application/fhir+json

{
  "resourceType": "Bundle",
  "type": "batch",
  "entry": [
    {
      "request": {
        "method": "GET",
        "url": "CarePlan/{{CARE_PLAN_ID}}"
      }
    },
    {
      "request": {
        "method": "GET",
        "url": "QuestionnaireResponse/{{INTAKE_RESPONSE_ID}}"
      }
    },
    {
      "request": {
        "method": "GET",
        "url": "QuestionnaireResponse/{{ASSESSMENT_RESPONSE_ID}}"
      }
    },
    {
      "request": {
        "method": "GET",
        "url": "QuestionnaireResponse/{{REVIEW_RESPONSE_ID}}"
      }
    }
  ]
}

14. Complete the package

Fill in the versions just read. The operation reevaluates applicability, validates all included answers, and atomically completes the CarePlan and its applicable responses together with extraction outputs.

Expected: 200, result = completed, and CarePlan status = completed. If a version is stale, read the resources again before retrying. If answers are incomplete, save the corrected draft first.

REST Send Cells run only inside Aidbox. Use Run in Aidbox above to open this notebook in your own instance.
POST /fhir/CarePlan/{{CARE_PLAN_ID}}/$sdc-package-complete
Accept: application/fhir+json
Content-Type: application/fhir+json

{
  "resourceType": "Parameters",
  "parameter": [
    {
      "name": "carePlanVersion",
      "valueId": "{{CARE_PLAN_VERSION}}"
    },
    {
      "name": "responseVersion",
      "part": [
        {
          "name": "actionId",
          "valueId": "intake"
        },
        {
          "name": "versionId",
          "valueId": "{{INTAKE_VERSION}}"
        }
      ]
    },
    {
      "name": "responseVersion",
      "part": [
        {
          "name": "actionId",
          "valueId": "assessment"
        },
        {
          "name": "versionId",
          "valueId": "{{ASSESSMENT_VERSION}}"
        }
      ]
    },
    {
      "name": "responseVersion",
      "part": [
        {
          "name": "actionId",
          "valueId": "review"
        },
        {
          "name": "versionId",
          "valueId": "{{REVIEW_VERSION}}"
        }
      ]
    },
    {
      "name": "requestId",
      "valueString": "package-demo-complete-1"
    }
  ]
}

15. Verify the final state

Expected: the CarePlan and all three QuestionnaireResponses are now completed. Each response still points to its versioned Questionnaire and to this CarePlan through basedOn.

REST Send Cells run only inside Aidbox. Use Run in Aidbox above to open this notebook in your own instance.
POST /fhir
Accept: application/fhir+json
Content-Type: application/fhir+json

{
  "resourceType": "Bundle",
  "type": "batch",
  "entry": [
    {
      "request": {
        "method": "GET",
        "url": "CarePlan/{{CARE_PLAN_ID}}"
      }
    },
    {
      "request": {
        "method": "GET",
        "url": "QuestionnaireResponse/{{INTAKE_RESPONSE_ID}}"
      }
    },
    {
      "request": {
        "method": "GET",
        "url": "QuestionnaireResponse/{{ASSESSMENT_RESPONSE_ID}}"
      }
    },
    {
      "request": {
        "method": "GET",
        "url": "QuestionnaireResponse/{{REVIEW_RESPONSE_ID}}"
      }
    }
  ]
}

16. Inspect the extracted pulse Observation

Completion extracts the pulse 70 with LOINC code 8867-4. The query returns pulse Observations for this demo patient, including any from earlier completed runs.

REST Send Cells run only inside Aidbox. Use Run in Aidbox above to open this notebook in your own instance.
GET /fhir/Observation?subject=Patient/package-demo-patient&code=http%3A%2F%2Floinc.org%7C8867-4
Accept: application/fhir+json

17. Replay the same completion request

Use the same requestId and the same completion body as step 14. Expected: 200, result = completed. This replay does not extract another Observation. Run the preceding Observation query again to inspect the result.

REST Send Cells run only inside Aidbox. Use Run in Aidbox above to open this notebook in your own instance.
POST /fhir/CarePlan/{{CARE_PLAN_ID}}/$sdc-package-complete
Accept: application/fhir+json
Content-Type: application/fhir+json

{
  "resourceType": "Parameters",
  "parameter": [
    {
      "name": "carePlanVersion",
      "valueId": "{{CARE_PLAN_VERSION}}"
    },
    {
      "name": "responseVersion",
      "part": [
        {
          "name": "actionId",
          "valueId": "intake"
        },
        {
          "name": "versionId",
          "valueId": "{{INTAKE_VERSION}}"
        }
      ]
    },
    {
      "name": "responseVersion",
      "part": [
        {
          "name": "actionId",
          "valueId": "assessment"
        },
        {
          "name": "versionId",
          "valueId": "{{ASSESSMENT_VERSION}}"
        }
      ]
    },
    {
      "name": "responseVersion",
      "part": [
        {
          "name": "actionId",
          "valueId": "review"
        },
        {
          "name": "versionId",
          "valueId": "{{REVIEW_VERSION}}"
        }
      ]
    },
    {
      "name": "requestId",
      "valueString": "package-demo-complete-1"
    }
  ]
}

Try the excluded branch in another run

Start a fresh package with a different requestId, record its new IDs, and save needs-assessment = false in step 6.

The advance-after-intake request in step 7 now returns actionId = review. Record that response as REVIEW_RESPONSE_ID and its version as REVIEW_VERSION. Skip steps 8 and 9, save the review note in step 10, skip the assessment revisit in step 11, and continue from step 12.

Remove the assessment GET entry from both resource-read batches in steps 13 and 15. Complete with only the intake and review responseVersion entries in steps 14 and 17. No assessment response is created, and this package extracts no pulse Observation.

If an assessment was already opened before an intake answer changed to false, its response stays in-progress, retaining its answers. Include its current version in completion too: versions cover every opened response, including excluded ones. Completion marks the excluded activity cancelled while completing only applicable responses.

The package lifecycle ends at completion. Ordinary resource CRUD remains available, with callers responsible for consistency when editing package resources directly.

Reference: Aidbox Notebook resource.