What the USDM API is (and isn't) for
The USDM API is designed as a mechanism for bulk transfer — reading, updating, or creating an entire study within a target solution (a study design tool, a data repository, an SDR). It is defined using the OpenAPI Specification, and no granular, per-field API is defined at this time: you exchange a whole study version's worth of JSON, not individual field-level PATCH requests.
The serialization algorithm, step by step
Because the USDM logical model is built from class references, naively serializing it into a monolithic JSON/XML document would repeat the same element many times over. The API spec avoids that with a specific three-step algorithm:
- Identify the natural parent. Where content is shared — referenced from two or more
places — the API designers identify which relationship is the "natural" owner. Example:
Endpointis referenced from bothObjectiveandEstimand;Objectiveis treated as the natural parent. - Nest under the natural parent; cross-reference elsewhere. The child's content is nested
inside the natural parent's JSON (attribute names unchanged), and every other relationship to it becomes a
cross-reference with an
Id(singular) orIds(plural) suffix, with the datatype changed to string. - If there's no natural parent, form a collection. A collection is created at a higher
logical level, and a new relationship is added from that higher class down to the collection. Example:
BiomedicalConceptcan be referenced from manyActivityinstances, so abiomedicalConceptscollection is placed directly onStudyDesign, with each Activity cross-referencing into it.
Required content for a conformant payload
When sending USDM data through the API, three baseline rules apply:
- There is exactly one
StudyVersionin the payload. - There is exactly one
StudyIdentifierwithin thatStudyVersion, scoped by an organization holding the Clinical Study Sponsor (C70793) role. - There is at least one
StudyDesignwithin theStudyVersion.
Beyond these, root-level attributes appear that exist only in the API, not the UML model itself: usdmVersion (required — the USDM version the payload conforms to), systemName (optional — the generating system), and systemVersion (optional).
The extension mechanism
Real implementations inevitably need to carry information the standard model doesn't (yet) cover. Rather than forcing a fork of the schema, USDM defines a first-class extension mechanism, available only in the API (it is not part of the logical model):
- Every class instance can carry an
extensionAttributesarray. - Each entry is an
ExtensionAttribute: anid, aurlthat uniquely identifies the extension, and exactly one value (a simple datatype, a complex datatype, or zero/one/more nestedExtensionClassinstances). - The URL is what lets two different implementers avoid collisions — it namespaces the extension to whoever defined it.
{
"id": "1",
"code": "Code", "codeSystem": "System", "codeSystemVersion": "Version", "decode": "decode",
"instanceType": "Code",
"extensionAttributes": [
{
"id": "ExtensionAttributeValue_1",
"url": "http://example.org/usdm/extensions/strAttribute1",
"valueString": "Extra value",
"instanceType": "ExtensionAttribute"
}
]
}
Logically, that extension is equivalent to just adding "strAttribute1": "Extra value" directly to the Code object — the extensionAttributes wrapper is what keeps it clearly separated from standard,
conformance-checked USDM content.
Rule of thumb
If a field name in a USDM JSON payload ends in Id or Ids, it's a cross-reference to an instance defined elsewhere — go find the
object with that id value rather than expecting inline detail. If it doesn't,
the content is nested right there.
Download the sample — a complete payload that demonstrates natural-parent nesting and Id/Ids cross-referencing throughout.
Worked example: applying the serialization algorithm step by step
Take the Endpoint example from the serialization algorithm above and follow it
all the way through. In the UML model, Endpoint is referenced from both Objective.endpoints and (indirectly, via the variable of interest) from Estimand. Applying the three-step algorithm:
- Identify the natural parent:
Objectiveis chosen, since an endpoint conceptually "belongs to" the objective it supports. - Nest under the natural parent: the full
Endpointobject (id, text, level, purpose) is nested insideObjective.endpointsin the JSON. - Cross-reference everywhere else: since
Estimandalso needs to reference the same endpoint (as its variable of interest), it does so with anendpointIdstring attribute pointing to the endpoint'sid— it does not repeat the endpoint's content.
{
"id": "Objective_1",
"instanceType": "Objective",
"endpoints": [
{ "id": "Endpoint_1", "instanceType": "Endpoint", "text": "Change from baseline in systolic blood pressure at Week 12", "level": { "code": "C94496", "decode": "Primary Endpoint" } }
]
}
{
"id": "Estimand_1",
"instanceType": "Estimand",
"endpointId": "Endpoint_1"
}
Building a "collection" when there's no natural parent
The BiomedicalConcept case mentioned earlier deserves a full worked example,
since it's the clearest instance of step 3 of the algorithm. A single BC (say, "Heart Rate") might be
referenced from a dozen different Activity instances across a study design —
screening, every treatment visit, and follow-up. None of those Activities is more "natural" a parent than any
other, so the API instead creates a biomedicalConcepts collection directly on
StudyDesign, and every Activity that needs the concept references it by id:
{
"id": "StudyDesign_1",
"biomedicalConcepts": [
{ "id": "BC_HeartRate", "instanceType": "BiomedicalConcept", "name": "Heart Rate" }
]
}
{ "id": "Activity_ScreenVS", "biomedicalConceptIds": ["BC_HeartRate"] }
{ "id": "Activity_Day1VS", "biomedicalConceptIds": ["BC_HeartRate"] }
{ "id": "Activity_Day8VS", "biomedicalConceptIds": ["BC_HeartRate"] }
The same collection pattern is used for Activity itself (collected on StudyDesign.activities), for Encounter, and for most of
the other classes referenced from many different points in a timeline.
API-only attributes: a complete list
It's worth keeping a short, definitive list of exactly what the API adds that the UML model itself does not define, since these are easy to mistake for logical-model attributes:
| Attribute | Where it appears | Purpose |
|---|---|---|
instanceType |
Every object | States the USDM class name so a consumer doesn't have to infer it. |
usdmVersion |
Root node | Required. The USDM schema version the payload conforms to. |
systemName |
Root node | Optional. Name of the system that generated the export. |
systemVersion |
Root node | Optional. Version of the generating system. |
extensionAttributes |
Any class instance | The extension mechanism described above. |
Frequently asked questions
Is the USDM API a REST API you can call?
No — despite the name, the USDM 'API' is a data exchange specification (an OpenAPI-defined JSON structure) for bulk transfer of a whole study version between systems, not a live, callable REST service with granular endpoints. It's designed for reading, updating, or creating a complete study within a target solution.
What does 'natural parent' mean in the API spec?
When a class is referenced from two or more places in the logical model, the API designers pick one referencing class as the 'natural parent' and nest the shared content there; every other reference becomes a cross-reference (Id/Ids) instead of a duplicate copy.
What happens when there's no natural parent?
The API forms a 'collection' at a higher logical level and adds a relationship from that higher class to the collection. The classic example is BiomedicalConcept: since it can be referenced from many Activities, a biomedicalConcepts collection is placed directly on StudyDesign, and each Activity cross-references into it.
How do I add data that isn't in the standard USDM schema?
Use the extension mechanism: add an extensionAttributes array to any class instance, where each entry has an id, a url uniquely identifying your extension, and one value (simple, complex, or a nested ExtensionClass). This is an API-only feature, not part of the logical UML model.