> ## 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.

# Quickstart: your first run from the command line

> Run a complete OCPI conformance run against the deployed service from the command line with curl, jq and a cdojo_ API key.

This walks one complete loop against the deployed service: authenticate, pick a target,
run the conformance suite, read the verdict. It uses the built-in **sandbox** target, a
working OCPI peer that every account can run against, so you do not need a partner
endpoint yet.

## Prerequisites

* An API key in `DOJO_KEY`. See [Create an API key](/guide/create-an-api-key). **Keys need a
  paid plan.** The sandbox run below is free at every tier; it is the key that is not, because
  the whole API sits behind one. Without a key, run the same thing in the browser and see
  [what you can do without paying](/guide/what-is-free).
* `curl` and `jq`.

## One complete run

```bash theme={null}
#!/usr/bin/env bash
set -euo pipefail

BASE="https://chargerdojo.com"
AUTH="Authorization: Bearer $DOJO_KEY"

# 1. Pick a built-in sandbox connection. Several are provisioned per version (a CPO peer,
#    an eMSP peer, and a payment-terminal peer on 2.2.1 and 2.3.0), all already registered,
#    so narrow on the role you want rather than taking whichever comes back first.
CONNECTION_ID=$(curl -fsS "$BASE/api/v1/connections" -H "$AUTH" \
  | jq -r '[.data[] | select(.kind == "sandbox" and .ocpiVersion == "2.2.1"
           and .theirRole == "CPO" and .targetPersona != "PTP")][0]._id')

# 2. Start the full conformance suite and wait for the report. 55s is the maximum;
#    anything larger is clamped to it, because the proxy cuts an idle read at 60.
RUN=$(curl -sS --fail-with-body -X POST "$BASE/api/v1/testing/runs?wait=55" \
  -H "$AUTH" -H "Content-Type: application/json" \
  -d "{\"connectionId\": \"$CONNECTION_ID\"}") || {
  echo "Could not start the run: $RUN"
  exit 2
}

REPORT_ID=$(printf '%s' "$RUN" | jq -r '.data.reportId')

# 3. Poll until the run is over. A 202 means the run is still going: your wait expired,
#    not the run. It is also a 2xx, so `curl` is perfectly happy with it, and one extra
#    read is not enough either. The status in the body is the only thing that says the
#    run finished, so keep asking until it is one of the four terminal states.
while :; do
  RUN_STATUS=$(printf '%s' "$RUN" | jq -r '.data.status')
  case "$RUN_STATUS" in
    completed|failed|cancelled|interrupted) break ;;
  esac

  sleep 5
  # --retry honours the Retry-After header on a 429 and rides out a transient failure;
  # --fail-with-body makes every other non-2xx a non-zero exit with the body still readable.
  RUN=$(curl -sS --fail-with-body --retry 3 \
    "$BASE/api/v1/testing/runs/$REPORT_ID?wait=55" -H "$AUTH") || {
    echo "Could not read the run: $RUN"
    exit 2
  }
  printf '%s' "$RUN" | jq -e '.success' > /dev/null || {
    echo "The API answered something that is not a run: $RUN"
    exit 2
  }
done

# 4. Read the verdict. Over is not the same as graded: a run we cancelled or interrupted
#    carries no summary at all, and `jq` prints `null` for a summary that is not there.
SUMMARY=$(printf '%s' "$RUN" | jq '.data.summary')
if [ "$SUMMARY" = "null" ]; then
  echo "The run ended as '$RUN_STATUS' and was never graded. Run it again."
  exit 2
fi

printf '%s' "$SUMMARY" | jq .
```

The output is the run summary: how many checks passed, failed and warned. The full
per-check report, including the recorded request and response behind every check, is at
`GET /api/v1/testing/runs/:id`.

Every answer also carries a `seed`, and sending it back on a later run makes that run send
the same requests again. See [Run a suite](/guide/run-a-suite#repeat-a-run) for what a seed
does and does not reproduce.

Exit `2` means the script never got an answer: the API refused the call, or the run ended
without a verdict. It is deliberately not exit `1`, which the
[CI gate](/guide/run-a-suite#gate-your-build-on-it) keeps for the one thing that is your
partner's fault, a check that failed.

<Note>
  A `202` is active work, not an outcome. The run did not fail and did not pass; it simply
  outlived your wait. Treat it as "poll again", never as a red build. The only failure
  signal for a conformance run is `failed` checks in the summary of a finished run.
</Note>

## What just happened

The sandbox is a real OCPI peer, not a recording. The run discovered its versions and made
real HTTP calls to real module endpoints. Registration is the one shortcut: the sandbox's
credentials are written directly rather than exchanged over a handshake.
Every check in the report carries the exchange that produced it. A run against your
partner works exactly the same way; the only difference is the connection you pass.

## Next

<CardGroup cols={2}>
  <Card title="Connect a partner" icon="plug" href="/guide/connect-a-partner">
    Point ChargerDojo at a real endpoint.
  </Card>

  <Card title="Run a suite in CI" icon="gears" href="/guide/run-a-suite">
    Gate a build on the result, correctly.
  </Card>
</CardGroup>
