OpenUJI Specs
UJM 1.0-draft

This specification defines UJM (User Journey Mapping), a data model and serialization for describing user journeys as graphs of Steps connected by Transitions, optionally grouped into Phases and Sessions. It provides a JSON-LD context for semantic interoperability and an illustrative JSON Schema.

This Editor’s Draft incorporates corrections to JSON-LD keyword aliasing, clarifies Channel/Touchpoint semantics, and introduces an optional CallStep mechanism for nested journeys without cross-graph transitions.

The key words MUST, MUST NOT, SHOULD, and MAY are to be interpreted as described in .

Data Model

Core classes

ujm:Journey

PropertyCardinalityNotes
id1IRI of the journey.
type1..*MUST include ujm:Journey.
title1Human-readable.
goal0..1Free text.
created, modified0..1 eachISO 8601 date-time.
personas0..*Array of Persona.
tags0..*Array of strings.
steps1..*Array of Step (and optionally CallStep).
transitions0..*Array of Transitions (intra-journey only).
phases, sessions0..*Grouping via StepRef.
start, end0..1 eachIRI of Step within this Journey.

ujm:Step

PropertyCardinalityNotes
id, type, name1, 1..*, 1type includes ujm:Step.
description0..1Free text.
touchpoint0..1IRI to a Touchpoint node or an external URL.
channel0..1Controlled term via @vocab (e.g., ujm:Web).
persona0..1IRI of a Persona.
emotions0..*Array of Emotion.
metrics0..*Array of Metric.
preconditions, postconditions0..*Model assumptions/outcomes.
timestamp, duration0..1 eachISO date-time; ISO 8601 duration.

ujm:Transition

Connects two Steps within the same Journey. MUST resolve to Step IRIs defined in steps of the same Journey.

PropertyCardinalityNotes
from, to1 eachIRI of Steps in this Journey.
label0..1Outcome label (e.g., success, retry).
probability0..10..1.

ujm:Phase, ujm:Session, and ujm:StepRef

Phases and Sessions group Steps using members of type StepRef.

ujm:Channel

Controlled term (e.g., ujm:Web, ujm:MobileApp, ujm:Phone, ujm:InPerson, ujm:Email, ujm:SMS). MAY be extended. In JSON-LD, channel values SHOULD be terms resolved via @vocab (e.g., "Web"https://www.w3.org/ns/ujm#Web).

ujm:Touchpoint

Properties: id (MUST), name (MUST), uri (MAY), location (MAY), owner (MAY). A Step’s touchpoint MAY reference a Touchpoint node by IRI or an external URL; in JSON-LD, coerce touchpoint to @id.

ujm:Persona, ujm:Emotion, ujm:Metric

Emotion includes optional evidence. Metric includes name, value, optional unit, method, and sampleSize.

JSON-LD

Minimal Example

Adding Phases and Sessions without duplicating Steps

Nested Journeys (Proposed)

This specification defines an optional mechanism to reuse one Journey from within another without cross-graph Transitions. Nesting is modeled as a special kind of Step that calls another Journey and returns a result.

ujm:CallStep

A CallStep is a Step with additional properties:

PropertyCardinalityConstraints
calls (Journey IRI)1MUST reference an existing Journey. JSON-LD: coerce to @id.
entry (Step IRI)0..1Overrides callee start if provided; otherwise MUST use callee start.
exitMap0..*Array mapping callee end labels to parent transition labels (e.g., {"calleeEnd":"success","label":"success"}). If absent, a single unlabeled return is assumed.
parameters0..1Arbitrary object passed as runtime hints to the callee (e.g., channel, experiment).
returns0..1Arbitrary object for callee outputs (informative; consumers SHOULD ignore unknown fields).

CallStep inherits all Step properties and participates in Phases/Sessions via StepRef.

Constraints

  • Transitions in the parent Journey MUST connect to the CallStep (not directly into the callee). Cross-Journey Transitions remain disallowed.
  • The callee Journey MUST define start and end. If multiple ends exist, they SHOULD carry labels that the parent references via exitMap.
  • Nesting MAY be recursive but MUST be acyclic at runtime.

Processing Model Additions

  1. When encountering a CallStep, processors MAY inline the callee Journey for visualization/analysis using the specified entry and end steps.
  2. Aggregate metrics (duration, completion probability) from callee to the CallStep when feasible; provenance of aggregation SHOULD be recorded.

JSON-LD

Schema Additions (Illustrative)

{
  "$defs": {
    "CallStep": {
      "type": "object",
      "allOf": [{ "$ref": "#/$defs/Step" }],
      "properties": {
        "type": {
          "oneOf": [
            { "const": "CallStep" },
            { "type": "array", "items": { "type": "string" }, "contains": { "const": "CallStep" } }
          ]
        },
        "calls": { "type": "string", "format": "uri" },
        "entry": { "type": "string", "format": "uri" },
        "exitMap": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": { "calleeEnd": { "type": "string" }, "label": { "type": "string" } },
            "required": ["calleeEnd","label"]
          }
        },
        "parameters": { "type": "object" },
        "returns": { "type": "object" }
      },
      "required": ["calls"]
    }
  }
}

Profiles

Profiles add constraints beyond the base model. For example, the Minimal Profile requires ≥ 1 Transition even though the base model allows zero.

Appendix A: JSON Schema (Illustrative)

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://www.w3.org/ns/ujm/schema.json",
  "title": "User Journey Mapping 1.0",
  "type": "object",
  "required": ["id", "type", "title", "steps"],
  "properties": {
    "id": { "type": "string", "format": "uri" },
    "type": {
      "oneOf": [
        { "const": "Journey" },
        { "type": "array", "items": { "type": "string" }, "contains": { "const": "Journey" } }
      ]
    },
    "title": { "type": "string" },
    "goal": { "type": "string" },
    "created": { "type": "string", "format": "date-time" },
    "modified": { "type": "string", "format": "date-time" },
    "personas": { "type": "array", "items": { "$ref": "#/$defs/Persona" } },
    "tags": { "type": "array", "items": { "type": "string" } },
    "steps": {
      "type": "array",
      "items": { "oneOf": [ { "$ref": "#/$defs/Step" }, { "$ref": "#/$defs/CallStep" } ] },
      "minItems": 1
    },
    "transitions": { "type": "array", "items": { "$ref": "#/$defs/Transition" } },
    "phases": { "type": "array", "items": { "$ref": "#/$defs/Phase" } },
    "sessions": { "type": "array", "items": { "$ref": "#/$defs/Session" } },
    "start": { "type": "string", "format": "uri", "description": "IRI of a Step in this Journey." },
    "end": { "type": "string", "format": "uri", "description": "IRI of a Step in this Journey." }
  },
  "$defs": {
    "Step": {
      "type": "object",
      "required": ["id", "type", "name"],
      "properties": {
        "id": { "type": "string", "format": "uri" },
        "type": {
          "oneOf": [
            { "const": "Step" },
            { "type": "array", "items": { "type": "string" }, "contains": { "const": "Step" } }
          ]
        },
        "name": { "type": "string" },
        "description": { "type": "string" },
        "touchpoint": { "type": "string", "format": "uri", "description": "IRI to a Touchpoint node or external URL." },
        "channel": { "type": "string", "description": "Controlled term; commonly 'Web','MobileApp','Phone','InPerson','Email','SMS'." },
        "persona": { "type": "string", "format": "uri" },
        "emotions": { "type": "array", "items": { "$ref": "#/$defs/Emotion" } },
        "metrics": { "type": "array", "items": { "$ref": "#/$defs/Metric" } },
        "preconditions": { "type": "array", "items": { "type": "string" } },
        "postconditions": { "type": "array", "items": { "type": "string" } },
        "timestamp": { "type": "string", "format": "date-time" },
        "duration": { "type": "string", "description": "ISO 8601 duration (e.g., 'PT30S')." }
      }
    },
    "Transition": {
      "type": "object",
      "required": ["from", "to"],
      "properties": {
        "from": { "type": "string", "format": "uri", "description": "IRI of Step in same Journey." },
        "to": { "type": "string", "format": "uri", "description": "IRI of Step in same Journey." },
        "label": { "type": "string" },
        "probability": { "type": "number", "minimum": 0, "maximum": 1 }
      }
    },
    "StepRef": {
      "type": "object",
      "required": ["step"],
      "properties": {
        "step": { "type": "string", "format": "uri" },
        "position": { "oneOf": [ { "type": "number" }, { "type": "string" } ] },
        "required": { "type": "boolean" },
        "visibilityRule": {},
        "validFrom": { "type": "string", "format": "date-time" },
        "validTo": { "type": "string", "format": "date-time" },
        "variant": { "type": "string" }
      }
    },
    "Phase": {
      "type": "object",
      "required": ["id", "type", "name"],
      "properties": {
        "id": { "type": "string", "format": "uri" },
        "type": {
          "oneOf": [
            { "const": "Phase" },
            { "type": "array", "items": { "type": "string" }, "contains": { "const": "Phase" } }
          ]
        },
        "name": { "type": "string" },
        "description": { "type": "string" },
        "order": { "oneOf": [ { "type": "number" }, { "type": "string" } ] },
        "members": { "type": "array", "items": { "$ref": "#/$defs/StepRef" } }
      }
    },
    "Session": {
      "type": "object",
      "required": ["id", "type", "name"],
      "properties": {
        "id": { "type": "string", "format": "uri" },
        "type": {
          "oneOf": [
            { "const": "Session" },
            { "type": "array", "items": { "type": "string" }, "contains": { "const": "Session" } }
          ]
        },
        "name": { "type": "string" },
        "description": { "type": "string" },
        "environment": { "type": "object" },
        "members": { "type": "array", "items": { "$ref": "#/$defs/StepRef" } }
      }
    },
    "Persona": {
      "type": "object",
      "required": ["id", "name"],
      "properties": {
        "id": { "type": "string", "format": "uri" },
        "name": { "type": "string" },
        "description": { "type": "string" },
        "attributes": { "type": "object" }
      }
    },
    "Touchpoint": {
      "type": "object",
      "required": ["id", "name"],
      "properties": {
        "id": { "type": "string", "format": "uri" },
        "name": { "type": "string" },
        "uri": { "type": "string", "format": "uri" },
        "location": {},
        "owner": { "type": "string" }
      }
    },
    "Emotion": {
      "type": "object",
      "required": ["term"],
      "properties": {
        "term": { "type": "string" },
        "intensity": { "type": "number", "minimum": 0, "maximum": 1 },
        "evidence": {}
      }
    },
    "Metric": {
      "type": "object",
      "required": ["name", "value"],
      "properties": {
        "name": { "type": "string" },
        "value": {},
        "unit": { "type": "string" },
        "method": { "type": "string" },
        "sampleSize": { "type": "number", "minimum": 0 }
      }
    },
    "CallStep": {
      "type": "object",
      "allOf": [ { "$ref": "#/$defs/Step" } ],
      "properties": {
        "type": {
          "oneOf": [
            { "const": "CallStep" },
            { "type": "array", "items": { "type": "string" }, "contains": { "const": "CallStep" } }
          ]
        },
        "calls": { "type": "string", "format": "uri" },
        "entry": { "type": "string", "format": "uri" },
        "exitMap": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": { "calleeEnd": { "type": "string" }, "label": { "type": "string" } },
            "required": ["calleeEnd","label"]
          }
        },
        "parameters": { "type": "object" },
        "returns": { "type": "object" }
      },
      "required": ["calls"]
    }
  }
}