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:
| Field | Type | Required | Notes |
|---|---|---|---|
| IN-PXE | CPO | required | PixellEnergy as charge-point operator — owns the physical infrastructure tenants connect via OCPP. |
| IN-PXM | EMSP | required | PixellEnergy 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):
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.
curl -X GET 'https://api.pixellenergy.com/ocpi/versions' \
-H 'Authorization: Token <TOKEN_A>' \
-H 'X-Request-ID: req-<your-ulid>'{
"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.
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"
}
]
}'{
"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
}| Field | Type | Required | Notes |
|---|---|---|---|
| token | string | required | Required 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. |
| url | string (uri) | required | Absolute URL of YOUR /versions endpoint. Must be HTTPS in production; HTTP rejected at PUT. |
| roles | array<Role> | required | Each role declares (role, party_id, country_code, business_details). For dual-role peers, list both CPO + EMSP rows. |
| roles[].party_id | string(3) | required | Uppercase 3-char identifier issued by your roaming registry. |
| roles[].country_code | string(2) | required | ISO 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.
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"
}
]
}'{
"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:
| Field | Type | Required | Notes |
|---|---|---|---|
| locations | CPO sender · eMSP receiver | required | PixellEnergy pushes the IN-PXE charge-point catalogue. Pull-by-eMSP supported as fallback. |
| sessions | CPO sender · eMSP receiver | required | Realtime session updates while active. |
| cdrs | CPO sender · eMSP receiver | required | Finalised charge-detail records, with SignedMeterValue for OCPP 2.0.1-driven sessions. |
| tariffs | CPO sender · eMSP receiver | required | KSERC-aware ToD tariffs with GST SAC 998714. |
| tokens | eMSP sender · CPO receiver | optional | Required if you are an eMSP issuing driver tokens redeemable at PixellEnergy IN-PXE stations. |
| commands | eMSP sender · CPO receiver | optional | Remote 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:
| Field | Type | Required | Notes |
|---|---|---|---|
| first attempt | t+0 | required | Sync, on the producing event handler thread. |
| retry 1 | t+30s | required | On 5xx / network timeout / 429. |
| retry 2 | t+5min | required | Same conditions. |
| retry 3 | t+30min | required | Final retry before DLQ. |
| DLQ | manual replay | required | Surfaced 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.
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."
}'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-IDdedup 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.com24h 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.