Skip to content

OCPI handshake cookbook

A 10-step hand-rail for OCPI 2.2.1 peers (third-party CPOs + eMSPs) onboarding with the PixellEnergy CSMS. Walks the canonical Token A → Token B → Token C state machine, the IN-PXE / IN-PXM dual-role pattern, the six data modules at v1, and the revoke / rotate surfaces that keep the peer relationship operationally healthy.

1.Audience and prerequisites

This cookbook is for OCPI peers — third-party CPOs and eMSPs — establishing a roaming relationship with the PixellEnergy platform under OCPI 2.2.1 §7 (Credentials and Registration). Familiarity with OCPI module semantics is assumed; the cookbook walks the PixellEnergy-specific decisions only.

You will need, before you start:

  • A peer party_id (3 chars) + country_code (ISO 3166-1 alpha-2) issued by your roaming registry.
  • HTTPS-reachable endpoints implementing the OCPI 2.2.1 surface for each module you intend to enable (minimum: /versions + /credentials + at least one data module).
  • A bootstrap Token A issued out-of-band by PixellEnergy.
  • Operations contact + DPO contact (per OCPI 2.2.1 BusinessDetails and PixellEnergy's DPDP framework).

Onboarding status

The inbound OCPI handshake is currently disabled by default (deny-by-default) and is enabled per-peer at the point a roaming agreement is signed. There are no live signed peers today — this cookbook documents the contract you will integrate against once your peer is provisioned. To start an onboarding, contact ocpi-onboarding@pixellenergy.com; PixellEnergy enables the inbound surface for your party_id and issues your bootstrap Token A out-of-band.

2.PixellEnergy is a dual-role platform (IN-PXE + IN-PXM)

PixellEnergy operates as BOTH a CPO and an eMSP under separate party_ids on one platform:

PixellEnergy party IDs and their OCPI roles
FieldTypeRequiredNotes
IN-PXECPOrequiredPixellEnergy as charge-point operator — owns the physical infrastructure tenants connect via OCPP.
IN-PXMEMSPrequiredPixellEnergy as e-mobility service provider — issues driver tokens, settles roaming sessions on behalf of subscribers.

During the handshake response, PixellEnergy returns BOTH role entries in the roles[] array. If you only intend to roam with one of the two roles, you still receive both — the OCPI 2.2.1 spec allows you to ignore the unused role. Your own peer credentials row will list one or both of your roles depending on your business model.

Heads up

Do NOT collapse the two PixellEnergy roles into a single party_id in your peer table. They are independently revocable and the OCPI 2.2.1 spec treats (party_id, country_code) as the composite key.

3.Token A — bootstrap exchange (out-of-band)

Token A is the bearer the peer presents on the FIRST request to the PixellEnergy CSMS. It is issued out-of-band by PixellEnergy, via secure email to your DPO contact. Token A has a 30-day TTL after which it is invalidated even if unused.

Token-handling discipline

PixellEnergy stores all tokens in Supabase Vault; the peer row keeps only a Vault reference and, for the tokens it checks, a SHA-256 lookup hash. Token columns are not readable through the tenant API. Your own integration MUST store Token A / Token B / Token C in a secrets manager with equivalent guarantees — never in .env files, application logs, or version control. PixellEnergy rejects connections from peers that self-report plaintext-on-disk storage during the JT-A compliance interview.

Once you have Token A, the handshake state machine begins. The full sequence (per OCPI 2.2.1 §7 + the production reference at packages/api/src/ocpi/credentials.ts):

OCPI 2.2.1 handshake sequence (ASCII)
Peer (CPO/eMSP)                            PixellEnergy CSMS
      |                                          |
      |  (1)  Token A issued out-of-band (email/portal) ──────────►
      |                                          |
      |  (2)  GET  /ocpi/versions                |
      |       Authorization: Token <TOKEN_A>     |
      |  ────────────────────────────────────────►
      |       200 OK { versions: [2.2.1] }       |
      |  ◄────────────────────────────────────────
      |                                          |
      |  (3)  POST /ocpi/2.2.1/credentials       |
      |       Authorization: Token <TOKEN_A>     |
      |       body: { token: <YOUR_TOKEN_B>, .. }|
      |  ────────────────────────────────────────►
      |       200 OK { token_b: <PXE_TOKEN_B> }  |  (Token A invitation consumed)
      |  ◄────────────────────────────────────────
      |                                          |
      |  (4)  PUT  /ocpi/2.2.1/credentials       |
      |       Authorization: Token <PXE_TOKEN_B> |
      |       body: { token: <YOUR_TOKEN_C>, .. }|
      |  ────────────────────────────────────────►
      |       200 OK { credentials, no token }   |  (status=ACTIVE)
      |  ◄────────────────────────────────────────
      |                                          |
      |  (5+) Module calls (locations, sessions, |
      |       cdrs, tariffs, tokens, commands)   |
      |       Authorization: Token <YOUR_TOKEN_C>|
      |  ◄═════════════════════════════════════►

4.Discover versions via GET /ocpi/versions

With Token A in hand, your first call is GET /ocpi/versions (unversioned, per OCPI 2.2.1 §4.2 — it sits above the version-prefixed modules). This is the deliberate shape of OCPI — peers exchange a list of supported versions so a future v2.3 / v3.0 upgrade is non-breaking for older peers.

Shell
curl -X GET 'https://api.pixellenergy.com/ocpi/versions' \
  -H 'Authorization: Token <TOKEN_A>' \
  -H 'X-Request-ID: req-<your-ulid>'
Response
JSON
{
  "data": [
    {
      "version": "2.2.1",
      "url": "https://api.pixellenergy.com/ocpi/2.2.1"
    }
  ],
  "status_code": 1000,
  "status_message": "Success",
  "timestamp": "2026-05-18T08:00:00Z"
}

The PixellEnergy CSMS only supports 2.2.1 today. The response links the version-details URL /ocpi/2.2.1, but that version-details document is not served yet. The modules PixellEnergy receives are credentials, commands, tokens, locations, sessions, cdrs and tariffs, all under /ocpi/2.2.1/. OCPI peer endpoints are not part of the REST API reference.

Inbound OCPI is off until PixellEnergy enables it for your onboarding: until then every peer-facing route answers 503 OCPI_INBOUND_DISABLED. Once it is on, a Token A that was not issued as a PixellEnergy invitation is refused with 403 OCPI_INVITATION_REQUIRED.

5.POST /credentials — exchange Token A for Token B

The peer presents Token A in the Authorization header and YOUR freshly-minted Token B in the body. PixellEnergy creates an ocpi_credentials row (status: PENDING) holding YOUR Token B (vaulted) plus a freshly-minted PixellEnergy Token B which it returns in the response — this is the bearer PixellEnergy expects when YOU call IT.

Shell
curl -X POST 'https://api.pixellenergy.com/ocpi/2.2.1/credentials' \
  -H 'Authorization: Token <TOKEN_A>' \
  -H 'Idempotency-Key: ocpi-handshake-<your-ulid>' \
  -H 'Content-Type: application/json' \
  -d '{
    "token": "<YOUR_TOKEN_B_FOR_INBOUND_CALLS>",
    "url": "https://your-peer.example.com/ocpi/2.2.1/versions",
    "roles": [
      {
        "role": "CPO",
        "business_details": { "name": "Your CPO Name" },
        "party_id": "ABC",
        "country_code": "IN"
      }
    ]
  }'
Response (PixellEnergy mints its own Token B, returned once as data.token_b)
JSON
{
  "data": {
    "credentials": {
      "ocpi_peer_id": "<uuid>",
      "tenant_id": "<uuid>",
      "party_id": "ABC",
      "country_code": "IN",
      "role": "CPO",
      "base_url": "https://your-peer.example.com/ocpi/2.2.1/versions",
      "business_details": { "name": "Your CPO Name", "website": null, "logo": null },
      "status": "PENDING",
      "last_handshake_at": "2026-05-18T08:00:01Z",
      "created_at": "2026-05-18T08:00:01Z",
      "updated_at": "2026-05-18T08:00:01Z"
    },
    "token_b": "<TOKEN_B_FROM_PIXELLENERGY>"
  },
  "error": null
}
POST /credentials request body
FieldTypeRequiredNotes
tokenstringrequiredRequired by OCPI 2.2.1 §7 (YOUR Token B). The current CSMS build validates the request without reading or storing this value; send a CSPRNG-generated, URL-safe base64 value of ≥ 16 bytes.
urlstring (uri)requiredAbsolute URL of YOUR /versions endpoint. Must be HTTPS in production; HTTP rejected at PUT.
rolesarray<Role>requiredEach role declares (role, party_id, country_code, business_details). For dual-role peers, list both CPO + EMSP rows.
roles[].party_idstring(3)requiredUppercase 3-char identifier issued by your roaming registry.
roles[].country_codestring(2)requiredISO 3166-1 alpha-2; e.g. IN, US, DE. Used together with party_id as the composite peer key.

Token A invalidation

After the first successful POST, Token A is invalidated server-side. Re-presenting it returns HTTP 409 OCPI_PEER_ALREADY_INITIALIZED. If you need a fresh Token A (e.g. botched onboarding), contact PixellEnergy support to TERMINATE the peer row and reissue.

6.PUT /credentials — promote Token B to Token C

With BOTH parties holding the other's Token B, the peer comes back with PUT — presenting PixellEnergy's Token B in the Authorization header and YOUR Token C in the body. PixellEnergy stores YOUR Token C (in Supabase Vault, plus a SHA-256 lookup hash) and flips the peer row's status to ACTIVE.

Shell
curl -X PUT 'https://api.pixellenergy.com/ocpi/2.2.1/credentials' \
  -H 'Authorization: Token <TOKEN_B_FROM_PIXELLENERGY>' \
  -H 'Idempotency-Key: ocpi-token-c-<your-ulid>' \
  -H 'Content-Type: application/json' \
  -d '{
    "token": "<YOUR_TOKEN_C_FOR_INBOUND_CALLS>",
    "url": "https://your-peer.example.com/ocpi/2.2.1/versions",
    "roles": [
      {
        "role": "CPO",
        "business_details": { "name": "Your CPO Name" },
        "party_id": "ABC",
        "country_code": "IN"
      }
    ]
  }'
Response (no token is returned)
JSON
{
  "data": {
    "credentials": {
      "ocpi_peer_id": "<uuid>",
      "party_id": "ABC",
      "country_code": "IN",
      "role": "CPO",
      "status": "ACTIVE",
      "...": "same fields as the POST credentials object"
    }
  },
  "error": null
}

Note

The PUT response returns the credentials record only — it does not mint or return a PixellEnergy Token C. Present the Token C YOU sent in the PUT body (Authorization: Token <YOUR_TOKEN_C>) on every subsequent module call; PixellEnergy matches it against the stored hash.

7.Module enablement

With Token C exchanged, both sides MAY now call any of the OCPI 2.2.1 modules. PixellEnergy ships the following at v1:

OCPI 2.2.1 modules supported
FieldTypeRequiredNotes
locationsCPO sender · eMSP receiverrequiredPixellEnergy pushes the IN-PXE charge-point catalogue. Pull-by-eMSP supported as fallback.
sessionsCPO sender · eMSP receiverrequiredRealtime session updates while active.
cdrsCPO sender · eMSP receiverrequiredFinalised charge-detail records, with SignedMeterValue for OCPP 2.0.1-driven sessions.
tariffsCPO sender · eMSP receiverrequiredKSERC-aware ToD tariffs with GST SAC 998714.
tokenseMSP sender · CPO receiveroptionalRequired if you are an eMSP issuing driver tokens redeemable at PixellEnergy IN-PXE stations.
commandseMSP sender · CPO receiveroptionalRemote start/stop on behalf of subscribed drivers. Subject to per-tenant rate limit.

Each module call MUST present Token C as the bearer and SHOULD include an X-Request-ID header for correlation in support tickets.

8.Inbound + outbound flow patterns

OCPI is bidirectional. PixellEnergy publishes to YOUR endpoints (CPO → eMSP locations push; eMSP → CPO tokens push) and accepts inbound calls from your endpoints (eMSP → CPO commands; CPO → eMSP CDRs pull). The retry policy on PixellEnergy's outbound worker:

Outbound retry policy
FieldTypeRequiredNotes
first attemptt+0requiredSync, on the producing event handler thread.
retry 1t+30srequiredOn 5xx / network timeout / 429.
retry 2t+5minrequiredSame conditions.
retry 3t+30minrequiredFinal retry before DLQ.
DLQmanual replayrequiredSurfaced in the operator portal at /operations/dlq for super_admin review + targeted replay.

Heads up

Idempotency is YOUR responsibility per OCPI 2.2.1 §11.4 — the X-Request-ID header is the dedup key on PUT/POST paths. Honour it strictly; PixellEnergy's outbound worker replays on transient failures, so you WILL see duplicate X-Request-IDs.

9.Revoke + rotate credentials

The OCPI 2.2.1 spec is sparse on credential rotation; PixellEnergy ships explicit super_admin admin routes (Wk 15 D4 + Wk 16 D2) that a tenant can use to revoke or rotate a peer's Token C without a full TERMINATE + new-onboarding cycle.

Revoke (per-peer; effective immediately)
Shell
curl -X POST 'https://api.pixellenergy.com/v1/ocpi/peers/ABC/revoke' \
  -H 'Authorization: Bearer <your-super-admin-jwt>' \
  -H 'Idempotency-Key: ocpi-revoke-<your-ulid>' \
  -H 'Content-Type: application/json' \
  -d '{
    "country_code": "IN",
    "reason": "Peer migrated to a new party_id; rotating per OCPI 2.2.1 §4.4.5."
  }'
Rotate (mints a new Token C; the old Token C stops working immediately)
Shell
curl -X POST 'https://api.pixellenergy.com/v1/ocpi/peers/ABC/credentials/rotate' \
  -H 'Authorization: Bearer <your-super-admin-jwt>' \
  -H 'Idempotency-Key: ocpi-rotate-<your-ulid>' \
  -H 'Content-Type: application/json' \
  -d '{
    "country_code": "IN"
  }'

Security

Rotation is currently disabled: the route answers 503 OCPI_ROTATE_DISABLED. Rotation responses never include the plaintext Token C (Hard Rule #8), and PixellEnergy does not yet deliver a rotated token to the peer (no outbound OCPI credentials update). Until that ships, rotating would lock the peer out, so the route stays off. To change a peer's Token C today, the peer re-runs PUT /credentials.

10.Settlement statements

Monthly OCPI settlement statements roll up the prior month's roaming CDRs into one net-settlement statement per peer. PixellEnergy's settlement generator produces them on the 1st of each month at 04:00 IST. Each statement is sealed (immutable) and carries a per-CDR CSV + JSON artefact and an audit-chain anchor hash. PixellEnergy operators read them via GET /v1/settlement-statements and GET /v1/settlement-statements/{statement_id}.

There is no peer-facing endpoint for statements or disputes. Ask your PixellEnergy contact for the statement, and raise any dispute with them, citing the statement and the disputed CDR ids.

11.Compliance and reconciliation expectations

  • Reconcile your CDR ledger against PixellEnergy's settlement statement within 5 business days of receipt.
  • India-domestic peers MUST honour Mumbai-only data residency and 30-day DSR resolution per DPDP.
  • Honour the OCPI 2.2.1 X-Request-ID dedup key on EVERY mutating call (PUT/POST).
  • For OCPI peers operating as a CPO under PixellEnergy's eMSP badge: enforce SignedMeterValue on OCPP 2.0.1 sessions — required for IRN issuance.
  • Rotate Token C every 12 months minimum. PixellEnergy will issue a deprecation notice 30 days before forced rotation if a token exceeds the limit.
  • Outage notification: any peer planning ≥ 30 minutes of unavailability for the OCPI inbound surface MUST notify ocpi-ops@pixellenergy.com 24h in advance — avoids spurious DLQ accumulation.

For onboarding queries, write to ocpi-onboarding@pixellenergy.com; for production incidents, the same address routes to the on-call rotation. Cross-link the OCPP onboarding cookbook if your peer also owns physical chargers connecting to the PixellEnergy gateway.