The shape of a USDM JSON file
USDM's normative form is UML, but what you actually send over the wire is JSON, produced according to the USDM API specification. The API's one governing rule: never duplicate an instance. Every object is defined exactly once, nested under whichever class the API spec calls its "natural parent," and every other place in the model that needs to reference it does so with a cross-reference attribute instead of repeating the object.
StudyVersion and identifiers
{
"id": "StudyVersion_1",
"instanceType": "StudyVersion",
"versionIdentifier": "1.0",
"rationale": "First-in-human dose escalation study of Compound X.",
"titles": [
{
"id": "Title_1",
"instanceType": "StudyTitle",
"text": "A Phase 1 Study of Compound X in Healthy Adult Volunteers",
"type": { "code": "C207616", "decode": "Official Study Title" }
}
],
"studyIdentifiers": [
{ "id": "Identifier_1", "instanceType": "StudyIdentifier", "text": "PLAY-101" }
]
}
Notice instanceType on every object — an API-only attribute (not part of
the UML model) added purely so a JSON consumer can tell which USDM class each object represents without
inferring it from context. Also notice that titles is a nested array, because
StudyTitle's natural parent is StudyVersion —
it's never referenced from anywhere else, so it's simply nested rather than cross-referenced.
Arms, epochs and cells: nesting vs. cross-referencing
{
"id": "StudyEpoch_2",
"instanceType": "StudyEpoch",
"name": "Treatment",
"type": { "code": "C101526", "decode": "Treatment" },
"previousId": "StudyEpoch_1",
"nextId": "StudyEpoch_3"
}
Here, previous and next from the UML model become
previousId and nextId in JSON — textbook
cross-reference naming. Compare a StudyCell, which cross-references both an arm
and an epoch rather than nesting either (because both are shared, "natural parent" elsewhere):
{
"id": "StudyCell_2",
"instanceType": "StudyCell",
"armId": "StudyArm_1",
"epochId": "StudyEpoch_2",
"elementIds": ["StudyElement_2"]
}
Timeline, instances and timing
{
"id": "SAI_2",
"instanceType": "ScheduledActivityInstance",
"encounterId": "Encounter_2",
"epochId": "StudyEpoch_2",
"activityIds": ["Activity_2", "Activity_3"],
"defaultConditionId": "SAI_3"
}
And its paired Timing:
{
"id": "Timing_2",
"instanceType": "Timing",
"type": { "code": "C201357", "decode": "Fixed Reference" },
"value": "P0D",
"relativeToScheduledInstanceId": "SAI_2",
"relativeFromScheduledInstanceId": "SAI_2"
}
For a Fixed Reference (anchor) timing, relativeTo and relativeFrom point to the same instance, and the value is typically P0D. See USDM Relationships for how Before/After
timings differ.
Biomedical Concepts in JSON
{
"id": "BC_SystolicBP",
"instanceType": "BiomedicalConcept",
"name": "Systolic Blood Pressure",
"code": { "standardCode": { "code": "C25298", "decode": "Systolic Blood Pressure" } },
"properties": [
{ "id": "Prop_Result", "name": "Result", "isRequired": true, "isEnabled": true, "datatype": "float" },
{ "id": "Prop_Position", "name": "Body Position", "isRequired": false, "isEnabled": true,
"responseCodes": [{ "code": { "code": "C62165", "decode": "Sitting" } }] }
]
}
Full downloadable example
Download the sample — the complete two-arm study used throughout this page, ready to open in any JSON viewer or feed into your own parser.
Download the sample — an isolated Timing/ScheduleTimeline example (a PK sub-timeline) for focused study of the timing model.
Objectives, endpoints and estimands in JSON
Since estimand modeling is one of the areas newer USDM users find hardest to picture, here's a compact annotated example following ICH E9(R1)'s population/treatment/variable/summary/intercurrent-event structure:
{
"id": "Objective_1",
"instanceType": "Objective",
"level": { "code": "C85826", "decode": "Primary Objective" },
"text": "To assess the efficacy of Compound X versus placebo.",
"endpointIds": ["Endpoint_1"]
}
{
"id": "Estimand_1",
"instanceType": "Estimand",
"populationSummary": "Mean difference between treatment groups",
"interventionIds": ["StudyIntervention_1"],
"analysisPopulationId": "AnalysisPopulation_1",
"intercurrentEvents": [
{
"id": "ICE_1",
"instanceType": "IntercurrentEvent",
"strategy": "Treatment policy",
"text": "Rescue medication use will be handled using a treatment policy strategy."
}
]
}
Notice that the Estimand cross-references its analysis population and
interventions rather than nesting them — both are shared, "natural parent elsewhere" content, exactly
the pattern covered in USDM API Tutorial.
Eligibility criteria with syntax-template tagging
Eligibility criteria commonly need to reference other structured content (an age range, a lab threshold) inline within their text. USDM's syntax template mechanism handles this with an embedded tag plus a separate parameter map:
{
"id": "EligibilityCriterionItem_1",
"instanceType": "EligibilityCriterionItem",
"text": "<div>Subjects shall be between <usdm:tag name=\"min_age\"/> and <usdm:tag name=\"max_age\"/> years of age.</div>",
"dictionaryId": "Dictionary_1"
}
{
"id": "Dictionary_1",
"instanceType": "SyntaxTemplateDictionary",
"parameterMaps": [
{ "id": "Param_1", "tag": "min_age", "reference": "<usdm:ref klass=\"Range\" id=\"Range_3\" attribute=\"minValue\"/>" },
{ "id": "Param_2", "tag": "max_age", "reference": "<usdm:ref klass=\"Range\" id=\"Range_3\" attribute=\"maxValue\"/>" }
]
}
A rendering tool resolves both tags at display time, producing plain protocol text with the actual age values
inserted — while the underlying JSON keeps the age range as a single, structured, reusable Range instance elsewhere in the model.
A minimal checklist for writing your first USDM JSON by hand
- Start from the backbone: one
Study, oneStudyVersion, oneStudyDesign. - Add
instanceTypeto every object — it's easy to forget and it's required by the API. - Decide nesting vs. cross-reference by asking "does anything else need to point to this same object?" If
yes, give it a stable
idand reference it byId/Idselsewhere rather than copying it. - Add the root-level
usdmVersionattribute so any consumer knows which schema version to validate against. - Run it against the CORE rules before assuming it's correct — schema-valid JSON can still fail business-rule conformance.
Tools for working with USDM JSON
You don't need specialized software to start — any JSON viewer or code editor with JSON folding (VS
Code, for instance) is enough to explore the sample files on this page. For anything beyond casual inspection,
a few practical tips: use a JSON schema validator against the published USDM OpenAPI spec to catch structural
errors fast; write small scripts (Python's json module, or JSONata for query-style
extraction) rather than hand-editing large files, since cross-reference ids are easy to typo; and keep a
scratch mapping of id values to human-readable names while you work, since USDM
ids are opaque strings by design and not meant to be self-describing.
Frequently asked questions
What top-level fields does a USDM API JSON file have?
A root study object nests a study identity, one or more studyVersion objects (each with studyDesigns), plus root-level usdmVersion, systemName and systemVersion attributes that are API-only additions not present in the UML model itself.
Why do some fields end in 'Id' or 'Ids'?
Because the API avoids duplicating content: an instance is defined once, under its 'natural parent,' and every other place that needs it links via a cross-reference attribute with an Id (single) or Ids (plural) suffix instead of repeating the full object.
Is the JSON schema formally validated?
Yes. USDM publishes an OpenAPI specification, and the CORE conformance rules engine checks both structural conformance (schema, data types, cardinalities) and business-rule conformance (e.g. exactly one primary objective) against real USDM JSON files.
Can I add custom fields that aren't part of USDM?
Yes, using the extension mechanism: every class can carry an extensionAttributes array of ExtensionAttribute objects, each with an id, a URL that uniquely identifies the extension, and a single value. This lets implementers add proprietary data without breaking conformance.