Build with USDM

USDM API Tutorial

The USDM API turns the UML logical model into exchangeable JSON. Here's exactly how serialization, cross-references and extensions work.

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.

USDM APIJSONOpenAPIinstanceTypeSerializationExtension Mechanism

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:

  1. 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: Endpoint is referenced from both Objective and Estimand; Objective is treated as the natural parent.
  2. 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) or Ids (plural) suffix, with the datatype changed to string.
  3. 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: BiomedicalConcept can be referenced from many Activity instances, so a biomedicalConcepts collection is placed directly on StudyDesign, with each Activity cross-referencing into it.
StudyVersionid: sv-1 StudyDesignid: sd-1 StudyArm[]nested array Encounter[]nested, id: enc-2 ScheduledActivityInstanceencounterId: \"enc-2\" reference (Id) Activity[]id: act-7 Timing[] relativeToScheduledInstanceId
The USDM API avoids duplication: each instance is defined once and referenced elsewhere with an *Id / *Ids cross-reference (dashed amber lines).

Required content for a conformant payload

When sending USDM data through the API, three baseline rules apply:

  • There is exactly one StudyVersion in the payload.
  • There is exactly one StudyIdentifier within that StudyVersion, scoped by an organization holding the Clinical Study Sponsor (C70793) role.
  • There is at least one StudyDesign within the StudyVersion.

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 extensionAttributes array.
  • Each entry is an ExtensionAttribute: an id, a url that uniquely identifies the extension, and exactly one value (a simple datatype, a complex datatype, or zero/one/more nested ExtensionClass instances).
  • 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.

usdm-sample-study.json
Download JSON

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:

  1. Identify the natural parent: Objective is chosen, since an endpoint conceptually "belongs to" the objective it supports.
  2. Nest under the natural parent: the full Endpoint object (id, text, level, purpose) is nested inside Objective.endpoints in the JSON.
  3. Cross-reference everywhere else: since Estimand also needs to reference the same endpoint (as its variable of interest), it does so with an endpointId string attribute pointing to the endpoint's id — 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.