Skip to main content
Once a connection is registered, you can run against it. A production endpoint receives GET requests only. A check with a write request or write setup is withheld before sending and is Not tested with this reason: Not run: this endpoint is marked production, so ChargerDojo sends it no writes. Passing reads leave Partial coverage. Manual writes and all current journeys, including scheduled journeys, are refused. Test-system runs retain their full behaviour. The report records the environment at admission. summary.readOnly identifies a read-only sweep; withheld checks stay in summary.unobservable and never increase summary.passed. MCP and the GitHub Action print the same reason and Not tested counts.

See what can run

Operation reference: GET /testing/suites.
Returns the available suites. A run only executes a protocol module the selected OCPI version advertises. If a version does not advertise one, the run refuses it rather than executing a suite that was never made to pass against those routes, so a module absent from a version has no suite there, not a failing one. Scenarios are the exception: you name one explicitly and it runs regardless, because it walks a flow rather than testing a module’s endpoints. OCPI 2.2.1 version details defines the advertised endpoint map. ChargerDojo sets its own suite admission rules and plan limits. The advertised modules differ by version. OCPI 2.3.0 advertises the widest set today, including the Bookings and Payments (Direct Payment) suites.

Run everything

Operation reference: POST /testing/runs.
Omit module and you get every advertised protocol-conformance suite your plan covers for the connection’s OCPI version. Scenarios are never part of a sweep, for any account: name one to run it. Direct Payment and Bookings are both on the Automate plan, so against a real endpoint their suites run only for accounts that have it. Against the sandbox they are open to everyone, because it costs us nothing to let you see what they do before you buy. When a run leaves them out, the report says which modules were withheld, so a smaller total is never a mystery. Test a payment terminal sets up a PTP endpoint for the Payments suites and the journey that goes with them. ?wait=<seconds> blocks for up to that long, to a maximum of 55; larger values are clamped to it, so a script asking for 120 gets 55 and should loop rather than wait longer. Five outcomes, and a CI script has to tell them apart: Read data.status before you read the counters. A 200 alone does not mean there is a verdict to read, and jq answers null for a summary that is not there, which shell arithmetic turns into an error rather than a number. The script below checks the status first for exactly this reason. Poll GET /api/v1/testing/runs/$REPORT_ID?wait=<seconds> until the run reaches a terminal state. Without ?wait, the start call returns immediately and you poll the same way. An interrupted report has no verdict, even if it later recovers. The CI gate below exits 2 for that interruption. Wait for the same sandbox report to finish recovery before reading its new results. ChargerDojo never automatically restarts a run against your own endpoint, because that would repeat requests there. When an interrupted report offers Restart this report, you can choose to restart it from the first check on the same reportId; requests already sent to your endpoint will be repeated. To retry a start whose reply never arrived, send an Idempotency-Key header with it: up to 128 characters, one fresh key per start, such as your CI job’s run id. A retry with the same key and the same body answers 202 with the run the first request started, once that run is queued, so a lost reply never starts a second run. A retry ignores ?wait and does not wait for the run to finish, and the run it returns may already be finished. If the first request is still starting, the retry answers 503 with Retry-After and code: "START_PENDING": that run may still start, so send it again with the same key, never a new one. If the first request gave up before its run was queued, the 503 has no code and the same key starts one. The same key with a different body answers 409. A key names its run for 24 hours; after that, the same key starts a new run.

Repeat a run

A run creates objects on your endpoint, and the ids it uses are derived from a seed, a string that names the run. Start another run on the same connection, over the same modules, with the same seed, while ChargerDojo runs the same build with the same set of checks, and it sends the requests the first one sent, byte for byte, apart from the differences described below. That is what turns “it failed yesterday” into something you can put in front of a partner. Operation reference: POST /testing/runs.
seed is optional and is 1 to 128 characters. Omit it and one is generated for you; either way the seed comes back in data.seed on every answer the start call gives, so keep it with the report. To repeat a run, send its reportId as replayOf and leave seed out: the new run takes that run’s seed. Only a completed, graded run can be repeated this way, on the same connection and with the same module (or none, for a whole sweep); a run that does not match is refused with 400. Run again with this seed on a report does the same. A finished report carries a request fingerprint in requestStreamHash: a SHA-256 hash of every request the run issued, in order. Locally cancelled exchanges are excluded because their outcome was not assessed. A request we retried after a 429 counts once. Two runs that sent the same requests have the same fingerprint, and a replay’s report compares the two check by check. Where a request differs, it names the check and says which side changed it: us, your endpoint, or unclear. If ChargerDojo was updated between the two runs, the report says the runs cannot be compared rather than judging them. Some requests also carry values your endpoint chose. A run copies the id of a session your endpoint started, or the URL it gave for a new object, into the requests that follow. If your endpoint answers the replay with a different one, the requests that carry it differ too, and the report puts that difference on your side. It can only tell while ChargerDojo is still on the build that ran both runs: after an update, the report marks such a difference Unclear. Apart from those, two runs of one seed on the same build may differ in these ways, and only these:
  • We send a fresh request id every time, and OCPI 2.2 and later require it to be unique.
  • We send a fresh correlation id every time, and OCPI 2.2 and later require it to be unique.
  • A reservation ends 24 hours after the run that asked for it, so this moves with the run. A value that is not exactly that run start plus 24 hours is kept and reported.
  • A booking period starts an hour after the run that asked for it and lasts an hour, so it moves with the run. A period that is not exactly that is kept and reported.
  • Credentials, the Token a command carries, and any other field this installation is configured to hide are masked before the fingerprint sees them, so a difference confined to one of them is invisible to it.
What a seed reproduces is the requests we send, not your endpoint: replaying a seed against a target that already holds those objects re-sends ids it created the first time, and an immutable object such as a settled CDR will rightly refuse the second POST. A replaceable one is updated instead, and where a check recommends 201 Created for a new object, it reports your 200 as a warning. A new seed creates new objects, and some of them have no DELETE in OCPI (CDRs, Hub client info parties, and 2.1.1 Locations and Sessions), so they stay on your endpoint after the run. The seed on a Charging Journey is a different field with the same name. It belongs to the journey manifest, it shapes the scenario itself rather than object ids, and it is set when you compile the plan. See Run a charging journey.

Run one module or scenario

Operation reference: POST /testing/runs.
Useful when you are iterating on one thing and do not want to wait for the rest. A scenario walks an end-to-end story (a session that produces a CDR, a command with its asynchronous callback) rather than checking endpoints in isolation, and it chains its own output: each step feeds the next. That is why a scenario can fail on a late step with a perfectly healthy endpoint; something upstream handed it nothing to work with. Read the exchange on the first red step, not the last.

Send one manual OCPI command

Before scripting a whole flow, it is often worth firing a single request and reading the exact exchange: Operation reference: POST /testing/command.
The response includes the request that was sent, the response status and body, and the latency.

Manage your runs

Operation reference: GET /testing/runs, DELETE /testing/runs/{id}.
Runs and reports are shared within your organisation. A run id from another organisation answers 404.

Gate your build on it

Exit 1 means “your partner broke a rule”. Exit 2 means “we did not get an answer”. Those are different problems and a build log should not conflate them, and an ungraded run belongs in the second group: it says nothing about your partner. The GitHub Action and the script below both exit this way. Gate on failed, not on warnings. A warning means a recommendation was declined, and a declined recommendation breaks no rule. If you fail your build on warnings you will be fixing code that already works, which is the exact mistake this tool refuses to make on your behalf. See Read your report. Store the key as a repository or organisation secret so the runner masks it. See Create an API key.

On GitHub Actions

From a new key to a check on every pull request, in four steps:
  1. Create a key. In ChargerDojo, open Settings, then Credentials, and choose Create key. Keys come with the Integrate plan and above. See Create an API key.
  2. Store it in the repository. In GitHub, open the repository’s Settings, then Secrets and variables, then Actions. Add a secret named CHARGERDOJO_API_KEY holding the key, and a variable named CHARGERDOJO_CONNECTION_ID holding the connection to test. GET /api/v1/connections lists your connections with their ids.
  3. Add the workflow. Save this as .github/workflows/ocpi-conformance.yml:
  4. Open a pull request. The conformance check runs the suite against the connection. When a required check fails, the check turns red and its log names the count and the report: OCPI conformance: 2 required checks failed. Report: https://chargerdojo.com/ocpi-testing-tool/runs?run=.... Open the report to see which rule broke and the exchange that broke it.
The ChargerDojo OCPI conformance Action starts the run, polls it to the end, prints the verdict with a link to the report, and publishes status, passed, failed, report_id and report_url as step outputs. Add module to run one module or scenario, or seed to repeat a run. Its README lists every input and output, and it is listed on the GitHub Marketplace.

Scope a CI run to one module

Add module: locations to the Action inputs above to test only Locations on the selected connection:
A failure exits 1; a run that never produces a usable verdict exits 2. Not-tested checks and withheld modules remain named in the Action log and report. A completed partial run with no failures exits 0, which is a scoped pass for the graded checks. It proves nothing about other modules or untested checks. Read the report’s coverage before using that job as a release gate.

On any other CI

The same exit codes from a shell script, for any runner with curl and jq: Operation reference: POST /testing/runs.

Keep it green without a pipeline

In the testing tool’s Endpoints tab, open Schedule on a registered external endpoint. Choose daily or weekly, an hour in UTC, and a weekday for weekly runs. Save to run the same conformance sweep on that cadence. Scheduling is included in Automate and Certified. Choose an active organisation member with a verified email for notices, or Nobody to keep the runs without email. When email is enabled, a regression alert names checks that previously passed and now fail, cites the saved evidence, and links to the authenticated report. An unchanged failure does not produce another alert. Warning-only and unobserved checks do not trigger a regression alert. If a recipient is added later, a continuing unmailed regression produces one notice within report retention. Report retention limits how long that earlier run remains available for comparison. Once it expires, the next usable run starts a new comparison. If the endpoint cannot be reached, the notice says that. Recovery needs a run with full coverage of the saved comparison. It is reported once every previously alerted check is observed passing, or when reachability returns with no unresolved alerted checks. The report identifies a change of grader build when two runs used different builds. If a run grades nothing after an earlier run did, one notice says so. It is neither a failure nor a recovery, and the earlier run stays the comparison. A run that grades fewer checks than the earlier one also keeps that run as the comparison. The same applies when a check still in the run loses its earlier grade, even if new checks keep the total equal or larger. The endpoint row says its coverage is partial, and a check that passed before and fails now is still alerted. The endpoint row records each run outcome and the latest alert attempt separately, including suppression, disabled email, recipient problems and provider errors. Provider acceptance does not confirm inbox delivery. Clear the recipient to stop notices, or remove the schedule to stop scheduled runs. You can change or clear the recipient while email is retrying. Cadence and time changes wait until the pending occurrence settles. A send already underway can still finish. Read your report explains the saved verdicts.

Next

Read your report.