What registration actually establishes
Two things. First, a mutual authentication credential — each party ends up holding a token it uses to call the other. OCPI connections are not symmetric with a shared secret; each direction has its own token, and either side can rotate its own without coordinating.
Second, an identity. The Credentials object carries the party's country_code and party_id, and in OCPI 2.2.1 a roles array describing every role that party plays. That identity is what appears in routing headers on every subsequent request, and it is what your authorisation logic keys on.
The three tokens
The naming is confusing because the letters describe sequence, not ownership. Read it as: A is temporary, B is mine, C is theirs.
| Token | Created by | Purpose | Lifetime |
|---|---|---|---|
| Token A | Party X (the inviter) | Handed to Party Y out of band — email, onboarding portal, signed document. Y uses it for exactly two calls: read X's versions, then POST credentials. | Dies the instant registration completes |
| Token B | Party Y | Sent in Y's credentials POST body. X uses it for every call it makes to Y, forever. | Until rotated |
| Token C | Party X | Returned in the response to that POST. Y uses it for every call it makes to X, forever. | Until rotated |
The handshake, step by step
Assume a CPO (party X) is inviting an eMSP (party Y).
- Out of band: the CPO generates Token A and sends it to the eMSP with its versions URL. This is the only step that is not an API call.
- The eMSP reads the CPO's versions using Token A, picks a version, and fetches the endpoint directory to find the CPO's credentials URL.
- The eMSP POSTs its own Credentials object to that URL, authenticating with Token A. The body contains Token B and the eMSP's own versions URL.
- The CPO fetches the eMSP's versions using Token B, confirming the eMSP is reachable and speaks a compatible version.
- The CPO responds to the POST with its own Credentials object containing Token C. Token A is now invalid.
Step 4 is the one people skip. The CPO is expected to validate the eMSP before returning Token C. Skipping it means you can complete a registration with a partner whose endpoints do not work, and you will not discover it until the first live transaction.
The request and response
The eMSP's POST — note that roles is an array, which is what allows a single connection to represent a party operating as both CPO and eMSP:
POST /ocpi/2.2.1/credentials
Authorization: Token <base64 of Token A>
Content-Type: application/json
{
"token": "9b8c1e44-3a7d-4f21-a0c5-77e2d6f1b930",
"url": "https://emsp.example.com/ocpi/versions",
"roles": [
{
"role": "EMSP",
"party_id": "EMS",
"country_code": "IN",
"business_details": {
"name": "Example Mobility Services",
"website": "https://emsp.example.com",
"logo": {
"url": "https://emsp.example.com/logo.png",
"category": "OPERATOR",
"type": "png", "width": 512, "height": 512
}
}
}
]
}The CPO's response, carrying Token C:
HTTP/1.1 200 OK
{
"data": {
"token": "4d21f7a9-8e35-4c60-b1af-2ce9047ab5d1",
"url": "https://cpo.example.com/ocpi/versions",
"roles": [
{
"role": "CPO",
"party_id": "EFI",
"country_code": "IN",
"business_details": { "name": "Example Charging Operator" }
}
]
},
"status_code": 1000,
"status_message": "Success",
"timestamp": "2026-08-11T09:07:44Z"
}
Updating and revoking
The module supports four verbs on the same URL, and the distinction matters operationally:
| Verb | Meaning | When you use it |
|---|---|---|
GET | Return the credentials the other party is currently using for you | Diagnostics — confirming which token a partner holds |
POST | Register for the first time | Once per partner, with Token A |
PUT | Replace existing credentials — token rotation | Key rotation, or after a suspected leak |
DELETE | Unregister; both tokens become invalid | Offboarding a partner |
PUT is the rotation mechanism and it is under-used. A credentials token that has never been rotated since onboarding is a standing security finding. PUT lets either side issue a fresh token without tearing down and rebuilding the registration, so rotation costs one call and no downtime.
DELETE is genuinely destructive and asymmetric in its consequences: in-flight sessions and undelivered CDRs will fail once the tokens are void. Settle outstanding CDRs before you unregister a partner.
Error handling
| Status | Meaning | Typical cause |
|---|---|---|
2000 | Generic client error | Malformed credentials object |
2001 | Invalid or missing parameters | Missing roles, or party_id not exactly 3 characters |
2002 | Not enough information | url omitted, so the partner cannot call back |
3001 | Unable to use the client's API | Step 4 failed — your versions endpoint was unreachable with Token B |
3002 | Unsupported version | No common version between the parties |
HTTP 405 | Method not allowed | POST attempted on an already-registered connection — use PUT |
The 3001 case is worth calling out. It means the partner tried to validate you and could not reach you. Nine times out of ten it is a firewall rule or an IP allowlist that was never opened in the outbound direction, and it is diagnosed from their logs, not yours.
Production lessons
- Treat Token A as single-use and short-lived. Give it an expiry. A registration token sitting valid in an old onboarding email for a year is a credential leak waiting to be found in an audit.
- Never store Token A after registration completes. It has no further purpose. Teams that keep it around inevitably have code that falls back to it.
- Validate the partner in step 4 without exception. Fetching their versions endpoint before returning Token C is the only automated check that the connection works in both directions.
- Rotate on a schedule using PUT. Build rotation into the platform from day one; retrofitting it across thirty live partner connections is far harder than designing it in.
- Do not treat
party_idas globally unique. It is only unique within acountry_code. The primary key for a partner is the pair, and assuming otherwise breaks the first time you onboard across borders. - Log registration events immutably. When a partner disputes when a connection changed, an append-only registration log settles it. This is also what a compliance audit asks for first.
Frequently asked questions
Token A is a temporary registration token handed over out of band; it is used only to read the partner's versions and to POST credentials, then it expires. Token B is generated by the registering party and sent in that POST; the other side uses it thereafter. Token C is returned in the response and is what the registering party uses from then on.
No. Token A becomes invalid the moment registration completes successfully. It should be discarded, not stored. Any code path that falls back to Token A after registration is a defect.
Send a PUT to the credentials endpoint with a new token. This replaces the existing credentials without tearing down the registration, so there is no downtime and no need to repeat the Token A handshake.
It lets a single connection declare every role a party plays — CPO, EMSP, HUB, NSP, SCSP or OTHER — each with its own party_id, country_code and business details. Earlier OCPI versions supported only one role per connection.
Status 3001 means the partner could not reach your API when validating you with Token B. It is nearly always an outbound firewall rule or IP allowlist that was never opened, and it has to be diagnosed from the partner's logs.