Skip to main content
A conformance suite checks that each endpoint obeys the specification. A charging journey drives a whole story against your partner: a session that starts, meters energy, completes and produces a CDR, with the tester judging the sequence as well as each exchange. Journeys run against a registered connection, take minutes rather than seconds, and stream evidence while they go. Command journeys send START_SESSION, STOP_SESSION, UNLOCK_CONNECTOR or RESERVE_NOW to chargers your partner serves, so a real charger may start, stop, unlock or reserve. Run them against a test system. The flow is always the same four steps: browse the catalogue, preview a plan, start the run, follow it to a verdict.
Journeys do not run over a connector. They execute in a separate worker process that cannot reach the socket a connector holds, so the app will not offer a connector-backed endpoint for a journey and the API refuses one. Conformance runs are what a connector carries today. If you are testing a local service and want journeys, use a tunnel instead.

1. Browse the catalogue

Operation reference: GET /testing/journeys.
Returns the journey packs, scenarios and templates. With targetId, the list is filtered to what that connection can actually run, based on its OCPI version, its role and the modules it advertises, and the response includes the target’s capability summary. Omit targetId to see the full catalogue.

2. Preview a plan

A journey is described by a manifest: which template, against which target, on what clock. Previewing compiles it into an immutable plan and tells you whether it can run, before anything touches your partner. This example needs a registered eMSP target on OCPI 2.2.1. Operation reference: POST /testing/journeys/preview.
The response carries the compiled plan and the two values the start call needs:
A plan expires after an hour. runAllowed: false means the preflight found a reason the run cannot proceed (an unregistered target, a module the target does not advertise); the plan spells out which.

3. Start the run, idempotently

Operation reference: POST /testing/journey-runs.
A 202 means the run was admitted and is active work, not a verdict:
Always send Idempotency-Key from CI. A repeated start with the same key returns the original run with repeated: true instead of creating a second one, so a start request that is retried cannot double-charge your quota or hit your partner twice. The same key with a different body answers 409. Every preview compiles a new plan, so key a start by the job that compiled its plan: a job that is run again starts a new run. The starts that do not admit a run:

4. Follow it to a verdict

Operation reference: GET /testing/journey-runs/{id}.
?wait=<seconds> long-polls until the run reaches a terminal state, pauses for a recovery decision, or the wait expires. The response carries status, the compiled plan, the current stage, the run’s verdicts once they exist, and the two fields the next two sections explain: runIntegrity and recoveryOptions.

What each status means

Run integrity is about our evidence, not their conduct

runIntegrity states how complete our record of the run is: coverage.outcome is nothing_graded when no assertion reached a verdict, partial when a check or planned step was not exercised, and graded when every planned step and check ran. Missing premises do not fail your partner. A degraded or invalid run is never a statement that your partner failed. Target failures show up as failed verdicts with the exchange attached, exactly as in a conformance run.

How a journey verdict is graded

Every check in a journey answers to one of five authorities, and the authority decides what the check may do to your verdict. An expanded row in the report names it beside the rule id. The OCPI keyword distinction follows OCPI 2.2.1 requirement keywords. Partner-profile and business rules have their own authority; they are not additional OCPI requirements. Not tested is never a pass. The report marks a check not tested when a step it needs was refused or never answered, or the response carried nothing to grade. Each row says what was missing. A run with no tested checks proves nothing either way. The clause is one click from the verdict. Under the rule id and authority, an expanded row prints the address of the clause the check was graded on and, where its rule records the clause, what binds in it (the modal word, or “required by its table” for a schema row) and the clause itself. Reports saved before clauses were recorded show the address alone. When a rule’s grading changes, the changelog says which rules moved; an old report keeps the verdict it was given, so run the journey again to grade it under the current rules. Read your report explains the verdict words and why a warning is not a failure.

Stream the events

Page through events with the sequence number as the cursor: Operation reference: GET /testing/journey-runs/{id}/events.
The response’s meta.nextAfter tells you where to resume; pass it as the next after. Or hold one connection open with server-sent events: Operation reference: GET /testing/journey-runs/{id}/stream.
Every SSE message carries its sequence as the event id. If the connection drops, resume without a gap by reconnecting with the standard Last-Event-ID header set to the last id you saw; the stream replays from there and closes itself when the run reaches a terminal state.

Cancel a run

Operation reference: POST /testing/journey-runs/{id}/cancel.
Answers 202: cancellation is a request, and the run winds down at the next safe point rather than mid-exchange.

When a run pauses for a recovery decision

Sometimes the runner sends a mutating OCPI request and cannot learn whether it arrived: the connection died between dispatch and response. Resending it automatically could duplicate a session or a CDR on your partner’s side, so the tester never replays an uncertain mutation on its own. The run pauses as recovery_required, holds its slot, and asks you to choose. Read the run to see the choices. They are computed by the server for that specific uncertain action; only what the run read offers is valid: Operation reference: GET /testing/journey-runs/{id}.
Apply one with the recoveryVersion from the same read: Operation reference: POST /testing/journey-runs/{id}/recovery.
A 409 means your recoveryVersion is stale: the run moved on since you read it, perhaps because a colleague decided first. Re-read the run and decide against the current state. This is deliberate; two people cannot apply conflicting decisions to the same uncertain action.

Put it in CI

A journey grades conformance, business checks and observations separately. Gate on conformance, which is the one that says your partner broke a rule; read the rest on the report. A passing CI gate requires explicit full coverage with complete, consistent check and step counts. Graded checks come from planned actions, so a pass also requires at least one planned action. Historical reports may omit step counts; those reports exit 2 because full coverage is unknown. That is a reporting prerequisite, not a failed OCPI requirement. Operation reference: POST /testing/journeys/preview, POST /testing/journey-runs, GET /testing/journey-runs/{id}.
Exit 1 is “the target broke a rule”. Exit 2 is “we do not have an answer”, which covers our own unavailability, a cancellation, a pending recovery decision, a run that did not finish within the loop’s sixty polls, and a run that graded nothing or only part of its plan. Missing or malformed verdict counters or coverage also exit 2; only an explicit graded coverage outcome can pass. A build log should never turn “we do not know” into “your partner is broken”, or into a pass. The script needs DOJO_KEY and CI_JOB_ID. Save the manifest as a readable JSON file named journey-manifest.json beside the script. It writes preview.json, start.json and run.json into the working directory, so a failed job keeps the response it stopped on.

Next

Read your report, and see Troubleshooting for the status codes journeys can answer. When the catalogue has no journey for the case you are chasing, author a custom one: the same JSON the builder produces, written down so you can version it, diff it and replay it from CI.