The shape
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.ocpiVersionandtargetIdstay as written on import. If the selected endpoint differs, choose Use selected endpoint to change onlytargetId. 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:acceleratedcompresses a session into seconds;realruns it at wall-clock cadence for long-session testing.session.cadenceSecondsandjitterSeconds: how often meter updates are pushed, and how irregular they are (jitterSecondsabove 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, oradversarial.seed: makes a run reproducible; keep it stable to replay the same story.
How do I write my own timeline?
A file with its own timedactions 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_tokenandwaitfor preparation and timing.session_put,session_patchandsession_completefor Session writes.location_put,location_patch,location_getandlocation_servefor Location state and read-back.cdr_postfor the charging record,cdr_getto pull a partner CPO’s records andcdr_deleteto probe immutability.command_postfor remote start, stop, unlock and reservations, andtoken_putfor the Token a start sends (2.2 on).token_getandtoken_authorizeto authorize a Token listed by a partner eMSP.versions_get,version_details_getandcredentials_getfor discovery and registration reads.tariff_get,tariff_putandtariff_deletefor Tariff reads and writes.charging_profile_put,charging_profile_getandcharging_profile_deletefor profiles on a Session this run started.hub_client_info_put,hub_client_info_getandhub_client_info_deletefor HubClientInfo pushes, read-back and the unsupported-verb probe.booking_getandbooking_postfor Booking search, requests and read-back.expect_inboundandawait_inboundto watch partner pulls.transport_cutandtransport_restorefor a bounded transport outage.session_getto 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 ridessession_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.- 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
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-deskandoffline-catch-up: none.
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: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. Numerickwh 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_patchto PATCH each state to it, andlocation_getstraight after to read the EVSE back. The read-back shows whether it kept the value it last accepted: a receiver still holdingPreparingafter it tookFinishingshows 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 aPATCH evseon every status change. Our sandbox keepsocpp_statuswhen 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 theEVSE-JOURNEYwe serve atLOC-JOURNEY, moves bothlast_updatedvalues 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, assignLOC-JOURNEYfirst.
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.- On Charging journeys, choose Import scenario JSON and pick your file, or Download sample to start from a working one.
- 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.
- 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.
// 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.