Skip to main content
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 opens Configure with the source retained. For schemaVersion 1 files with custom actions, edit existing values and times, duplicate or remove rows, and use Undo or Redo. You can also edit the JSON and choose Apply JSON. Preview checks the current applied definition before Run. 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

Replace the example target with your registered endpoint, or use Use selected endpoint after import. The starter has the required topology; server Preview still checks the selected endpoint and its actual capabilities.

The knobs that matter

  • journey: the template the custom journey starts from (e.g. first-successful-charge). It selects the story. An unavailable id stays in the imported source and cannot run here.
  • ocpiVersion and targetId stay as written on import. If the selected endpoint differs, choose Use selected endpoint to change only targetId. Correct a version mismatch in the JSON before Preview.
  • roleUnderTest: the role of the endpoint you are testing, which must match its registered role. The action picker lists each kind’s partner sides. Preview refuses a kind that cannot run against the selected role.
  • 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 below.
  • coverageDepth: happy-path, edge-cases, or adversarial.
  • seed: makes a run reproducible; keep it stable to replay the same story.
Unknown fields and unsupported source stay in the local draft, custom-journey save and export. Preview and preset Save use the strict server schema; a saved definition is not proof that it can execute. Pending JSON blocks Preview and both Save paths until you explicitly Apply it. Export pending JSON keeps its exact text, including unfinished input. Export original keeps the imported text. Recovery is scoped to the account, workspace and endpoint in the current tab; export before closing the tab or when recovery reports a storage failure.

How do I write my own timeline?

A file with its own timed actions list shows those actions in file order. Stock template presets and saved template scenarios reopen their setup knobs. Loading that saved configuration applies it to the selected endpoint while retaining other source fields. File import keeps its own target until you explicitly choose Use selected endpoint. Configure shows compact action headers with requested time, operation and a source summary. Choose Edit parameters to open one step’s fields. Enter or Tab applies a scalar to the local draft; Escape cancels that field edit. The type label describes the retained or locally typed JSON value, without guessing units. Empty strings remain editable, and long strings use a bounded multiline field. Add parameter keeps its Key and Value labels visible while open. Field details tells you where the spec defines a name. It does not establish that the action sends the typed value. Preview shows the compiler’s selected and generated payload fields. Keys the compiler ignores remain in the definition only. Objects, arrays and unsupported values bags remain intact. Edit in JSON opens Advanced definition JSON with the source path and focuses the code well; it does not guess a text position. Apply JSON changes the local draft, while Save persists a separate scenario or preset. While JSON is pending, parameter editing is disabled until you Apply or Discard it. Use the expanded step’s time control to retime it. Enter or Tab commits one edit; Escape cancels it. Unfinished time edits survive reload and block Preview and Save. Use Duplicate and Remove to change the list. The schedule uses atSeconds; do not infer equal-time execution order from source row position. Older stored plans retain their historical order. Kinds and checks remain in the JSON. Adding steps from a catalogue and choosing new checks await an authoring contract and are not offered by this editor. Empty custom lists and unsupported action kinds stay available for repair and export, with execution disabled. Definitions from a newer schema or an internal execution plan are not converted to a template or flattened into actions. Local editing, export and ordinary Preview have separate access from server writes. Saving a custom journey or preset requires the workspace’s saved-presets grant. Run uses the existing server admission result for the reviewed plan.

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 unavailable id remains editable and exportable, with Preview disabled until you choose an available journey in JSON. 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, roaming-hub-identity, roaming-hub-relay, and custom for a timeline you write yourself. roaming-hub-identity reads identity and configuration through one registered CPO counterparty. It names only targetId. roaming-hub-relay tests a roaming hub through two counterparties and is the one journey that names two targets: targetId is the CPO side of the pair and pairedTargetId the eMSP side.

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, roaming-hub.

Does an action take oracleIds?

No. An action’s verdicts come from the rules in its scenarioIds. A manifest whose action carries an oracleIds value other than an empty list is refused by server validation, with a message naming the field. Remove it before Preview. An empty oracleIds list says nothing and is dropped. The field is also dropped when older stored manifests are read. The editor offers no custom check picker; inspect Preview to see the planned assertions.

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.

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 remains editable, but Preview rejects it before a run can start. 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. A cdr_post before the first session_put is refused, because the record would close before its Session opened. A cdr_post before the session_complete is allowed: it sends the CDR ahead of the final Session update. OCPI sends a CDR only after the charge has ended, so the charge then ends when the CDR goes out, and the final Session update carries that end time. The current custom compiler executes these kinds:
  • prepare_token and wait for preparation and timing.
  • session_put, session_patch and session_complete for Session writes.
  • location_put, location_patch, location_get and location_serve for Location state and read-back.
  • cdr_post for the charging record, cdr_get to pull a partner CPO’s records and cdr_delete to probe immutability.
  • command_post for remote start, stop, unlock and reservations, and token_put for the Token a start sends (2.2 on).
  • token_get and token_authorize to authorize a Token listed by a partner eMSP.
  • versions_get, version_details_get and credentials_get for discovery and registration reads.
  • tariff_get, tariff_put and tariff_delete for Tariff reads and writes.
  • charging_profile_put, charging_profile_get and charging_profile_delete for profiles on a Session this run started.
  • hub_client_info_put, hub_client_info_get and hub_client_info_delete for HubClientInfo pushes, read-back and the unsupported-verb probe.
  • booking_get and booking_post for Booking search, requests and read-back.
  • expect_inbound and await_inbound to watch partner pulls.
  • transport_cut and transport_restore for a bounded transport outage.
  • session_get to pull a partner CPO’s Sessions or read back the Session we pushed.
location_serve sends nothing. It changes the Location we serve your partner, for a partner with no Locations Receiver that has to pull. See Push or serve?. The schema also recognizes the following names, but the current custom compiler refuses them. Their presence in the schema does not make them executable: await_callback, booking_put, booking_patch, payment_get, payment_post, payment_put, payment_patch, transport_latency, sandbox_ptp_step. A hand-authored list runs prepare_token, wait, session_put, session_patch, session_complete, cdr_post, location_put, location_patch, location_get, location_serve, command_post, token_put, session_get, tariff_get, tariff_put, tariff_delete, cdr_get, versions_get, version_details_get, credentials_get, charging_profile_put, charging_profile_get, charging_profile_delete, expect_inbound, await_inbound, transport_cut, transport_restore, hub_client_info_put, hub_client_info_get, booking_get, booking_post, token_get, token_authorize, cdr_delete and hub_client_info_delete today. A file with any other kind is refused with a message that names it.

Which scenarios can an action carry?

Each scenario is checked on the kind of step that drives it, so an action carries only the scenarios whose check reads that kind. S11 reads a Session PATCH, for example, so it rides session_patch and nothing else. A scenario rides every action its check reads: N16 reads the envelope of any answer, so it rides every action that sends a request. Put a scenario on an action its check does not read and the file is refused, with a message naming the actions that scenario can ride. transport_cut, transport_restore and expect_inbound grade nothing, so the scenario IDs the schema requires on these steps get no result. Some scenarios name more than the step: S10 reads an update to a Session you pushed earlier that sends its energy, cost or status, and B13 a CDR sent again with an earlier CDR’s id. On a step without that, the scenario is not tested, with the reason, rather than passed because the step was accepted. prepare_token and wait send nothing, so every scenario on them is not tested.

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.
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
Only these journeys take disturbances: rough-session, charger-reset, cdr-desk, offline-catch-up and remote-start-stop. Any other journey refuses a file that lists one. In the builder you do not pick them one by one: rough-session starts with its own set, and on charger-reset the failure variant you choose picks them for you. Each of those journeys uses only some of the set:
  • rough-session: skip_intermediate_update, duplicate_full_put, zero_energy_pause, non_monotonic_meter, cdr_before_terminal_session. An empty list turns all five on.
  • charger-reset: session_never_closes, reboot_energy_gap, session_invalid_close, offline_replay_burst, pending_start, duplicate_cdr. It refuses any other.
  • remote-start-stop: callback_timeout.
  • cdr-desk and offline-catch-up: none.
A disturbance one of these journeys accepts but does not use still runs, without that fault. The preview, the run you start and the builder all name it: the plan and the start response carry ignoredDisturbances, with the ids and a sentence saying the run goes ahead without them. To deliver the CDR late, raise completion.cdrDelaySeconds, the seconds between the Session closing and the CDR being sent. delay_cdr changes nothing: the journeys above run without it, and charger-reset refuses it. No verdict fails a CDR for arriving late: OCPI lets partners agree to send CDRs as rarely as once a month, so a receiver that accepts a late one has broken no rule. An id the runner does not know is rejected during Preview, before a run can start. Import retains the source so you can correct the id.

The values an action carries

Each hand-authored action can carry a bag of named values, in the file or added in the builder:
On Configure, choose Edit parameters to open one step. Scalar values use labelled fields with their actual JSON type. Add parameter adds a name the file did not carry. Enter or Tab applies a field to the local draft; Escape cancels it. Save a scenario or preset separately to keep it beyond the tab.

What can a value be?

A value is text, a number, true, false, or null. A list or an object stays out of the scalar editor and is refused by the strict server schema. An imported structure stays in the local draft, custom save and export; use Edit in JSON to repair it. 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?

The compiler reads selected keys and generates other fields. Numeric kwh and total_cost override generated totals on Session and CDR actions; bad or empty scalar values use generated totals. On session_patch, presence of status or charging_periods selects generated values, rather than sending the scalar you typed. Other keys can remain in the definition without reaching a payload. session_patch, session_complete and cdr_post can set an explicit kwh or total_cost. A value set at or before the charge end applies to every later charging action that leaves it out. The charge end is the earlier of session_complete and the first cdr_post, or the first cdr_post when there is no session_complete. Generated totals and CDR dates stop there, so a re-sent CDR repeats the first one. When the journey is compiled, the compiler checks that fully generated Session/CDR pairs agree; explicit overrides still let you test deliberate disagreement. These keys have additional value checks. A spec field name and a compiler input are separate facts: 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 PUT, PATCH or the Location we serve 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.

Push or serve?

It depends on whether your partner advertises a Locations Receiver, not on its role.
  • It has a Locations Receiver. Use location_patch to PATCH each state to it, and location_get straight after to read the EVSE back. The read-back shows whether it kept the value it last accepted: a receiver still holding Preparing after it took Finishing shows a driver the wrong state. This is the standard OCPI push to an eMSP, and it is also how a Direct Payment terminal provider works: it pulls its Locations first, then takes a PATCH evse on every status change. Our sandbox keeps ocpp_status when you push it, so you can run the push against the sandbox first and see it in the read-back before you point it at a partner.
  • It has none, so it can only pull. Use location_serve: it changes the EVSE-JOURNEY we serve at LOC-JOURNEY, moves both last_updated values so a delta pull sees the change, and records what your system pulled while that state was current. This is the shape for a 2.3.0 terminal provider, whose Payments module has it “pull the current connector information”. It runs on OCPI 2.2.1 and 2.3.0. If your terminal only pulls Locations assigned to it, assign LOC-JOURNEY first.
Pick the clock to match. A push lands the moment it is sent, so accelerated time is fine. A partner that pulls reads on its own schedule, so a served state needs to last longer than its polling interval, and real time is the honest choice. Whether your system read a served state is an observation, never a failure: if it did not pull while Finishing was being served, the report says so and warns, because nothing in OCPI says when a partner must pull. The Choose page’s custom journey card downloads a ready parking-fee journey for the endpoint you have selected: the pushed version if it advertises a Locations Receiver, the served one if not.

Can I send a field OCPI does not define?

You can retain a scalar under another name in the definition. The current compiler selects payload fields explicitly, so an arbitrary key does not become a wire field. For an arbitrary extension name, OCPI defines no field semantics. An extension is not graded, because there is no clause to grade it against. ocpp_status on location_put, location_patch and location_serve is a supported partner extension; it is not an OCPI field. Inspect Preview’s generated payload before running. OCPI 2.3.0 says a platform must not reject a payload solely because it contains undocumented field names. That OCPI requirement does not require it to retain an arbitrary extension. Two things worth knowing. A field you invent is not graded, because there is no clause to grade it against. ocpp_status is the one extension the report lists, under Partner compatibility rather than OCPI. G15 checks that each status pair you wrote is on the agreed table, before the run starts. G13, that a push carrying it is accepted, and G14, that the read-back keeps it, report not tested, because no partner profile names them yet. And a key the runner does not read may be left empty, where a key it does read may not. The current G15 partner table permits these pairs. This table is the agreed profile used by ChargerDojo, not a mapping required by OCPI:

Import, run, and save

Time fields accept decimal whole seconds. Scalar value fields show the parsed JSON type: numeric-looking text becomes a number; use JSON to retain a numeric-looking string. Shift+Enter inserts a line in a scalar value.
  1. On Charging journeys, choose Import scenario JSON and pick your file, or Download sample to start from a working one.
  2. A template-shaped file opens its matching settings on Configure. Choose Edit definition JSON to open and focus the source in one click. Its original target remains unchanged until you choose Use selected endpoint; selecting an endpoint alone is not that consent. Other files open the source editor. Edit existing rows or the JSON, choose Apply JSON for text changes, then Preview to compile the applied definition.
  3. Start the run. It streams live and lands a graded report in Runs, shared with your organisation, replayable and runnable from CI with the same JSON.
You can annotate a working copy with // comments and trailing commas; the importer reads them while retaining the original text for export. The API and CI expect strict JSON. See also Run a charging journey for the run, follow, and recovery flow.