> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chargerdojo.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Author a custom journey

> The custom journey JSON shape, the knobs that matter, and how to import, run, and save a journey you wrote by hand or generated from CI.

A **custom journey** is a charging journey described as JSON: the same thing the
Charging Journeys builder produces, written down so a human or an agent can version
it, diff it, and replay it from CI. Import one and it opens ready to run: a
template-shaped file opens with every knob restored, and a file with its own
hand-authored action list opens with that list shown, its values editable in the
builder (more on the difference below). Either way it streams evidence and lands a
report exactly like a catalogue journey.

The fastest way to get a valid file is **Download sample** on the Charging Journeys
page. It exports a ready-to-run custom journey for your selected target. Edit that, or
write one from the shape below.

## The shape

```json theme={null}
{
  "schemaVersion": 1,
  "journey": "first-successful-charge",
  "roleUnderTest": "EMSP",
  "journeyPack": "core-charging",
  "ocpiVersion": "2.2.1",
  "coverageDepth": "happy-path",
  "clock": { "mode": "accelerated", "durationSeconds": 3000 },
  "session": {
    "cadenceSeconds": 300,
    "jitterSeconds": 0,
    "strategy": "put-and-patch",
    "updates": ["kwh", "total_cost", "status", "charging_periods"]
  },
  "catalogue": { "locationUpdates": true, "tariffUpdates": true },
  "completion": { "cdrDelaySeconds": 300 },
  "contract": { "cdrWithinSeconds": 600 },
  "disturbances": [],
  "seed": "204"
}
```

## The knobs that matter

* **`journey`**: the template the custom journey starts from (e.g. `first-successful-charge`).
  It selects the story; an unknown id falls back to the recommended journey.
* **`ocpiVersion`** and **`targetId`** are advisory in the file. On import the builder
  runs the journey against whichever target you have selected, so a sample from one
  target still runs against another, and you do not need a real `targetId` to share a
  file.
* **`clock.mode`**: `accelerated` compresses a session into seconds; `real` runs it at
  wall-clock cadence for long-session testing.
* **`session.cadenceSeconds`** and **`jitterSeconds`**: how often meter updates are
  pushed, and how irregular they are (`jitterSeconds` above 0 means irregular).
* **`session.updates`**: which fields each update carries (`kwh`, `total_cost`,
  `status`, `charging_periods`).
* **`disturbances`**: faults injected mid-journey to prove the endpoint fails safely.
  The full set is under [Disturbances](#disturbances) below.
* **`coverageDepth`**: `happy-path`, `edge-cases`, or `adversarial`.
* **`seed`**: makes a run reproducible; keep it stable to replay the same story.

Unknown or malformed fields fall back to sensible defaults, so a partial file still
imports and uses the defaults for anything you left out.

### How do I write my own timeline?

There is one exception to that. A file that spells out its own timed `actions` list
carries those **hand-authored actions** into the builder, as the timeline you wrote
rather than the knob editor above.

The Configure step then shows each action's time, kind, and values, in file order. Click
any value to change it in place. Where an OCPI version defines the field, the builder
names the spec file it comes from.

Timing comes from the file. You can still change the endpoint, the clock, and the seed,
and the run streams and grades exactly like any other custom journey. To change an
action's kind, its time, or the order of the list, edit the JSON and import it again.

One thing to watch: the run executes each action at its own `atSeconds`, not in list
order. Keep the list chronological if you want to read it the way it will run.

A file that says `journey: 'custom'` but lists no actions is refused with a message,
rather than imported and quietly emptied. Asking for a custom journey is not something
the importer should discard on your behalf.

## Journeys, packs and what grades them

Three ids in the file name what you are running rather than what it does.

### What can I put in `journey`?

`journey` picks the story to start from. An unknown id falls back to the recommended
journey rather than failing.

`first-successful-charge`, `rough-session`, `charger-reset`, `partner-connection`,
`catalogue-trust`, `remote-start-stop`, `live-price-parking`, `reservation`, `cdr-desk`,
`offline-catch-up`, `hub-routed-charge`, `book-arrive-charge`, `direct-terminal-payment`,
`smart-charging-control`, `hub-connection-status`, `invalid-charging-data`, and `custom`
for a timeline you write yourself.

### What does `journeyPack` group?

`journeyPack` groups journeys by the part of OCPI they exercise: `core-charging`,
`foundation`, `catalogue`, `commands`, `billing`, `recovery`, `hub`, `booking`,
`direct-payment`, `charging-profiles`, `hub-client-info`, `negative`.

### What does `oracleIds` do?

`oracleIds` on an action names the checks that judge it. An oracle is the thing that
decides whether what came back was right, so this is how a hand-authored action earns a
verdict instead of merely happening:

`ocpi_response_success`, `session_identity`, `session_progress`, `session_completed`,
`cdr_identity`, `session_cdr_reconciliation`, `catalogue_identity`, `command_response`,
`command_result`, `billing_reconciliation`, `pull_window`, `hub_routing`,
`booking_lifecycle`, `payment_reconciliation`, `charging_profile_result`,
`hub_client_status`.

An action may carry up to 12, and the field is optional: leave it out and the action runs
without a check of its own.

## The actions you can write

An action is one thing the runner does at one moment. Every entry needs three things: what
to do, when, and which scenarios it provides evidence for.

```json theme={null}
{ "action": "session_patch", "atSeconds": 300, "scenarioIds": ["S01"] }
```

### Why is `scenarioIds` required?

`scenarioIds` is required on every action, between 1 and 12 of them, each a capital letter
followed by two digits (`S01`, `G13`). They are what tie an action to the scenarios your report is
graded against: an action with no scenario would run and prove nothing, so the schema does
not accept one. A file missing them is refused on import rather than run.

`atSeconds` counts from the start of the run, up to 48 hours. Actions execute at their own
time, not in list order, so keep the list chronological if you want to read it the way it
will run.

Most kinds are named `module_verb` and send that request to that module, so `tariff_put`
puts a Tariff and `session_get` reads a Session back. Once you know the pattern you can
mostly guess the name you want:

**The handshake and discovery**
`versions_get`, `version_details_get`, `credentials_get`

**A charging session, start to finish**
`prepare_token`, `session_put`, `session_patch`, `session_complete`, `session_get`

**The catalogue your partner reads**
`location_get`, `location_put`, `location_patch`, `tariff_get`, `tariff_put`,
`tariff_delete`

**Billing**
`cdr_post`, `cdr_get`

**Remote control, and the callback it answers with**
`command_post`, `await_callback`, `charging_profile_get`, `charging_profile_put`,
`charging_profile_delete`

**Bookings** (OCPI 2.3.0)
`booking_get`, `booking_put`, `booking_patch`, `booking_post`

**Direct payment** (the Payments extension)
`payment_get`, `payment_post`, `payment_put`, `payment_patch`

**Hubs**
`hub_client_info_get`, `hub_client_info_put`

**Waiting for your system to call us**
`expect_inbound`, `await_inbound`

**Timing and transport**
`wait`, `transport_cut`, `transport_restore`, `transport_latency`, `sandbox_ptp_step`

Some of these change state on your side and some only read, which is worth knowing before
you point a list at a live endpoint: a `put` or a `post` writes a real object there.

## Disturbances

A disturbance is a fault the runner injects on purpose, to see whether your endpoint
survives something real rather than only the happy path. Adversity is opt-in: a journey
runs clean unless you ask for it.

```json theme={null}
"disturbances": ["skip_intermediate_update", "delay_cdr"]
```

The full set, by what each one disrupts:

* **Session updates**: `skip_intermediate_update`, `duplicate_full_put`,
  `zero_energy_pause`, `non_monotonic_meter`, `patch_before_put`, `pending_start`,
  `session_never_closes`, `session_invalid_close`, `reboot_energy_gap`,
  `offline_replay_burst`
* **Charge records**: `cdr_before_terminal_session`, `delay_cdr`, `early_cdr`,
  `duplicate_cdr`, `credit_cdr`, `corrected_cdr`
* **Pricing and parking**: `tariff_boundary`, `parking_period`
* **Callbacks**: `callback_timeout`, `callback_duplicate`, `callback_out_of_order`
* **Transport**: `transport_outage`

Toggle one in the builder and the Configure step describes what it will do before you
run it. An id the runner does not know is refused on import rather than ignored, so a
typo tells you at once.

## The values an action carries

Each hand-authored action can carry a bag of named values, in the file or added in the
builder:

```json theme={null}
{
  "action": "location_patch",
  "atSeconds": 0,
  "scenarioIds": ["G13"],
  "values": { "status": "CHARGING", "ocpp_status": "Preparing" }
}
```

On the Configure step every value is a control. Click one to edit it in place, or use
**+ Add a value** on the action to add a name the file did not carry. Enter saves,
Escape puts it back.

### What can a value be?

A value is text, a number, `true`, `false`, or `null`. A list or an object is refused
in the builder, because a structure belongs in the JSON file rather than in a
single-line editor. Types survive the round trip: `16` comes back as the number your
file held, not as the string `"16"`.

An action can hold up to 40 values, and a name can be up to 64 characters.

Six names belong to the server and an action cannot set them: `url`, `headers`,
`authorization`, `token`, `template`, and `expression`. These carry the request itself,
so a file that could overwrite them could point a run at somewhere you did not
choose.

### Which keys does the runner check?

Most keys are yours to name. A few are read by the runner, which means it also checks
them. Being on this list is not the same as being in the specification, and the two are
marked separately below because they are different kinds of thing:

| Action           | Key                  | Defined by                | Rule                                                        |
| ---------------- | -------------------- | ------------------------- | ----------------------------------------------------------- |
| `location_patch` | `status`             | OCPI, Locations module    | Text. Required, because the action will not run without it. |
| `location_patch` | `ocpp_status`        | Nobody. Partner extension | Text. Optional.                                             |
| `cdr_post`       | `total_parking_time` | OCPI, CDRs module         | A number of hours, no longer than the record covers.        |

`ocpp_status` is the case worth understanding. No OCPI version names it, and we do not
pretend otherwise: the editor labels it a partner extension exactly as it labels a name
you invent yourself. The runner reads it only so that a value you did supply is checked
rather than silently shipped empty, and so a PATCH can carry the CSMS status your
platform reports alongside the OCPI one. Nothing about it being on this list makes it
standard.

Where a key is one of these, the editor says so while you type, and an empty value is
refused rather than saved into a run that Preview would reject.

### Can I send a field OCPI does not define?

Any other name is yours. The editor labels it a **partner extension** and tells you it
goes on the wire exactly as you write it, so you can model something your platform sends
that the specification never named.

This is deliberate on our side and allowed on the receiving side. OCPI 2.3.0 puts it
plainly: "An OCPI Platform SHALL NOT reject request or response payloads based on the
presence of JSON object field names that are not documented in this specification." A
partner who refuses your extra field is the one out of line, and a run that sends one is
a fair way to find that out before production does.

Two things worth knowing. An extension is not graded, because there is no clause to grade
it against. And a key the runner does not read may be left empty, where a key it does
read may not.

## Import, run, and save

1. On **Charging Journeys**, choose **Import scenario JSON** and pick your file, or
   **Download sample** to start from a working one.
2. The custom journey opens in the builder on the **Configure** step with your knobs
   applied. Adjust anything, then **Preview** to compile the plan.
3. **Start the run.** It streams live and lands a graded report in **Runs**, stored on
   your account, replayable and runnable from CI with the same JSON.

You can annotate a working copy with `//` comments and trailing commas; the importer
strips them. The API and CI expect strict JSON.

See also [Run a charging journey](/guide/run-a-charging-journey) for the run, follow,
and recovery flow.
