OCPI Credentials and Versions Modules: How Partners Find Each Other

Before any data can flow between an eMSP and a CPO via OCPI, they have to negotiate. The Credentials and Versions modules are the handshake.

Two people shaking hands over a laptop — the human agreement behind the OCPI handshake that connects a CPO and an eMSP
Photo by Radission US on Unsplash

Before two OCPI parties can exchange any business data — locations, sessions, CDRs, anything — they need to set up the connection. The Credentials and Versions modules are how this happens.

They’re often overlooked in OCPI articles because they’re not “the interesting bit,” but skipping them means nothing else works. This is the OCPI handshake explained.

The setup problem

When a CPO and an eMSP want to roam with each other (whether directly or via a hub), they need to know two things about each other:

  1. What OCPI versions and modules does the other side support? Version 2.1.1 with only Locations? 2.2.1 with the full module set? 2.3.0 with AFIR additions? Knowing this lets them negotiate the highest common version.

  2. How do we authenticate API calls? Each OCPI call carries an Authorization header with a token. Both sides need to know each other’s tokens — and how to rotate them when needed.

The Versions module handles (1). The Credentials module handles (2). Together they form the OCPI handshake.

The Versions module

The Versions module is simple in concept: each party exposes one endpoint that lists which OCPI versions it speaks. For each version, it lists the URLs where its module endpoints live.

The Versions endpoint

Every OCPI party (CPO or eMSP) exposes:

GET /ocpi/versions
Authorization: Token <credentials token>

→ returns:
{
  "data": [
    {
      "version": "2.2.1",
      "url": "https://cpo.example.com/ocpi/2.2.1"
    },
    {
      "version": "2.3.0",
      "url": "https://cpo.example.com/ocpi/2.3.0"
    }
  ],
  "status_code": 1000,
  "status_message": "Success",
  "timestamp": "2026-07-03T09:00:00Z"
}

This list tells the partner: “I support these OCPI versions; here are the URLs for the version-specific details.”

The Version Details endpoint

For each version listed above, the partner GETs the details. The example below is a CPO’s Version Details — note the cpo.example.com URLs and that the CPO advertises itself as SENDER for the modules it owns:

GET https://cpo.example.com/ocpi/2.2.1
Authorization: Token <credentials token>

→ returns:
{
  "data": {
    "version": "2.2.1",
    "endpoints": [
      {
        "identifier": "credentials",
        "role": "SENDER",
        "url": "https://cpo.example.com/ocpi/2.2.1/credentials"
      },
      {
        "identifier": "locations",
        "role": "SENDER",
        "url": "https://cpo.example.com/ocpi/2.2.1/locations"
      },
      {
        "identifier": "sessions",
        "role": "SENDER",
        "url": "https://cpo.example.com/ocpi/2.2.1/sessions"
      },
      {
        "identifier": "cdrs",
        "role": "SENDER",
        "url": "https://cpo.example.com/ocpi/2.2.1/cdrs"
      },
      {
        "identifier": "tariffs",
        "role": "SENDER",
        "url": "https://cpo.example.com/ocpi/2.2.1/tariffs"
      },
      {
        "identifier": "tokens",
        "role": "RECEIVER",
        "url": "https://cpo.example.com/ocpi/2.2.1/tokens"
      },
      {
        "identifier": "commands",
        "role": "RECEIVER",
        "url": "https://cpo.example.com/ocpi/2.2.1/commands"
      }
    ]
  },
  "status_code": 1000,
  "status_message": "Success",
  "timestamp": "2026-07-03T09:00:01Z"
}

Each module has its own URL. The role field says whether this party is the Sender or Receiver for that module — and which side plays which is fixed by the protocol, not chosen. Per the spec, the Sender is always the party that owns the data (the Receiver pulls from, or is pushed to by, the owner).

Here’s the full role map for a CPO ↔ eMSP connection:

ModuleCPO iseMSP is
LocationsSenderReceiver
SessionsSenderReceiver
CDRsSenderReceiver
TariffsSenderReceiver
TokensReceiverSender
CommandsReceiverSender
Charging ProfilesReceiverSender¹
Hub Client InfoReceiver²Receiver²
Credentialsn/a³n/a³

¹ For Charging Profiles the Sender is a Smart Charging Service Provider (SCSP) — a role an eMSP or a dedicated smart-charging party can hold; the CPO always receives. ² Hub Client Info only appears when a hub sits in the middle: the hub is the Sender, and both connected parties are Receivers. ³ Credentials (and the Versions discovery endpoint) are implemented identically by every party. The spec states the role field “is not relevant, as this module is the same for all roles,” so it’s the one module listed just once, with no Sender/Receiver split.

The pattern is easy to remember: the CPO owns the physical infrastructure, so it’s the Sender for everything about charging — Locations, Sessions, CDRs, Tariffs. The eMSP owns the driver relationship, so it’s the Sender for Tokens and for the Commands that remotely start and stop a charge.

The Credentials module

Authentication in OCPI uses bearer tokens. Every API call carries an Authorization: Token <secret> header. The Credentials module is how parties initially exchange those tokens.

The two-step credential exchange

The exchange follows a specific sequence to allow each party to set up auth in their own infrastructure:

Step 1: Party A (let’s say a CPO) generates an initial token (Token A) out-of-band. Through email, signed agreement, secure portal — somewhere outside OCPI. Party A gives this Token A to Party B (the eMSP) along with their /ocpi/versions URL.

Step 2: Party B uses Token A to fetch Party A’s Versions and Version Details endpoints. Now Party B knows what URLs and modules Party A supports.

Step 3: Party B generates Token B and POSTs it to Party A’s Credentials endpoint:

POST https://cpo.example.com/ocpi/2.2.1/credentials
Authorization: Token <Token A>

{
  "token": "<Token B>",        // Party B's new token for Party A to use
  "url": "https://emsp.example.com/ocpi/versions",
  "roles": [
    {
      "role": "EMSP",
      "business_details": {
        "name": "The Mobility Network",
        "website": "https://themobility.network",
        "logo": {
          "url": "https://themobility.network/assets/logo.png",
          "thumbnail": "https://themobility.network/assets/logo-thumb.png",
          "category": "OPERATOR",
          "type": "png",
          "width": 512,
          "height": 512
        }
      },
      "party_id": "TNM",
      "country_code": "NL"
    }
  ]
}

Step 4: Party A responds with Token C and Party A’s own details:

{
  "data": {
    "token": "<Token C>",       // Party A's new token for Party B to use
    "url": "https://cpo.example.com/ocpi/versions",
    "roles": [
      {
        "role": "CPO",
        "business_details": {
          "name": "Fast Charge Networks",
          "website": "https://fastcharge.example.com",
          "logo": {
            "url": "https://fastcharge.example.com/brand/logo.svg",
            "thumbnail": "https://fastcharge.example.com/brand/logo-64.png",
            "category": "OPERATOR",
            "type": "svg",
            "width": 240,
            "height": 80
          }
        },
        "party_id": "ABC",
        "country_code": "NL"
      }
    ]
  },
  "status_code": 1000,
  "status_message": "Success",
  "timestamp": "2026-07-03T09:00:02Z"
}

Step 5: Token A is now invalidated. Going forward:

  • Party A uses Token B to authenticate calls to Party B
  • Party B uses Token C to authenticate calls to Party A

This three-token dance (A → B → C) ensures both sides have control over their own authentication and that the initial out-of-band token (A) is short-lived.

sequenceDiagram
    participant A as Party A (CPO)
    participant B as Party B (eMSP)
    Note over A,B: Out-of-band: signed agreement
    A->>B: Token A + /ocpi/versions URL<br/>(email or portal)
    B->>A: GET /ocpi/versions (auth: Token A)
    A-->>B: Supported versions list
    B->>A: GET /ocpi/2.2.1 (auth: Token A)
    A-->>B: Module endpoints
    B->>A: POST /credentials<br/>Token B + B's roles
    A-->>B: Response: Token C + A's roles
    Note over A,B: Token A invalidated
    Note over A,B: A→B calls use Token B<br/>B→A calls use Token C

Token rotation

The Credentials module supports rotation. Either party can update their token by PUTing a new credentials object. The old token is invalidated; the new one is used going forward.

In practice, token rotation happens:

  • After a security event (suspected compromise)
  • On a schedule (some implementations rotate every 90 days)
  • During credential migration to a new system

Modules within the Version Details — what’s available

The Version Details response lists all modules each party supports. Standard module identifiers:

  • credentials
  • versions
  • locations
  • sessions
  • cdrs
  • tariffs
  • tokens
  • commands
  • chargingprofiles (smart charging)
  • hubclientinfo (for hubs)

Not every party implements every module. An eMSP might not implement chargingprofiles. A small CPO might not implement commands. The Version Details endpoint is how each party publishes what they actually do.

How this all connects

The full setup flow when two parties (say a CPO and an eMSP) want to roam:

  1. Out-of-band: they sign a roaming agreement. The CPO emails the eMSP a one-time Token A and the /ocpi/versions URL.

  2. eMSP fetches Versions: uses Token A to call GET /ocpi/versions. Learns the CPO supports 2.1.1 and 2.2.1.

  3. eMSP picks the highest common version: the eMSP also supports 2.2.1, so that’s the version they’ll use.

  4. eMSP fetches Version Details: calls GET /ocpi/2.2.1 to get the list of module endpoints.

  5. eMSP POSTs credentials: generates Token B and POSTs it to the CPO with their own details.

  6. CPO responds with Token C: the CPO generates Token C and responds.

  7. The connection is established. From now on, every API call carries either Token B (eMSP→CPO) or Token C (CPO→eMSP). The initial Token A is invalidated.

  8. They start exchanging real data: Locations, Tariffs, Sessions, CDRs, Tokens, Commands — whatever modules both parties support.

A complete worked example: the full handshake, wire by wire

The snippets above are trimmed for clarity. Here is the whole handshake end to end — every request and its response — with the details that trip people up called out in the comments. Party A is the CPO; Party B is the eMSP. (Comments use // and # for teaching; real JSON has no comments.)

1. eMSP fetches the CPO’s supported versions (GET)

# ── eMSP ──▶ CPO ────────────────────────────────────────────────────────────
# Out-of-band, the CPO already handed the eMSP a one-time registration token
# (Token A) and its /ocpi/versions URL — by signed email or a portal, never
# over OCPI. The eMSP's first move is to discover which versions the CPO speaks.

GET /ocpi/versions HTTP/1.1
Host: cpo.example.com
# Auth scheme is the literal word "Token" — NOT "Bearer" — then the token value.
# In OCPI 2.2+ the token VALUE is base64-encoded; here that is Token A, the
# single-use registration token. This is the ONLY thing Token A is used for.
Authorization: Token <base64(Token A)>
# Recommended so both sides can trace one call across their logs; echo them back.
X-Request-ID: 12345
X-Correlation-ID: 67890
# ── CPO ──▶ eMSP (the response) ─────────────────────────────────────────────
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-ID: 12345
X-Correlation-ID: 67890

{
  "data": [                                      // array of Version objects — one per version the CPO speaks
    {
      "version": "2.1.1",                        // required — VersionNumber the CPO supports
      "url": "https://cpo.example.com/ocpi/2.1.1" // required — where THAT version's details live
    },
    {
      "version": "2.2.1",                        // required — VersionNumber the CPO supports
      "url": "https://cpo.example.com/ocpi/2.2.1" // required — where THAT version's details live
    },
    {
      "version": "2.3.0",                        // required — VersionNumber the CPO supports
      "url": "https://cpo.example.com/ocpi/2.3.0" // required — where THAT version's details live
    }
    // Each element has exactly two fields (version, url), both required — nothing
    // else lives on a Version object. The eMSP picks the highest version BOTH speak.
  ],
  // THE key OCPI gotcha: HTTP 200 only means the HTTP call arrived. You must
  // ALSO check status_code INSIDE the envelope. 1000 = success. You can get
  // HTTP 200 with, say, status_code 2001 (invalid parameters) — always read the
  // envelope, never trust the HTTP status alone. 2xxx = client error, 3xxx = server.
  "status_code": 1000,
  "status_message": "Success",
  // RFC 3339, UTC ("Z").
  "timestamp": "2026-07-03T09:00:00Z"
}

The eMSP picks the highest version both sides support and follows that version’s url.

2. eMSP fetches the version details (GET)

# ── eMSP ──▶ CPO ────────────────────────────────────────────────────────────
# The URL comes straight from the "url" in the previous response — do NOT
# hand-build it. This returns the per-module endpoints for version 2.2.1.

GET /ocpi/2.2.1 HTTP/1.1
Host: cpo.example.com
# Still Token A — the credentials handshake below has not happened yet.
Authorization: Token <base64(Token A)>
# ── CPO ──▶ eMSP (the response) ─────────────────────────────────────────────
HTTP/1.1 200 OK
Content-Type: application/json

{
  "data": {                                     // a VersionDetails object
    "version": "2.2.1",                          // required — the VersionNumber these endpoints belong to
    "endpoints": [                               // required — one Endpoint per module/interface-role the CPO exposes
      {
        "identifier": "credentials",           // required — ModuleID; the module we POST to next
        "role": "SENDER",                       // required — InterfaceRole. For credentials the role is NOT meaningful (spec: "the same for all roles"); it's listed once, and SENDER here is just a conventional placeholder
        "url": "https://cpo.example.com/ocpi/2.2.1/credentials" // required — where to reach this module
      },
      {
        "identifier": "locations",             // required — ModuleID
        "role": "SENDER",                       // required — the CPO is the Sender for locations (it owns the data)
        "url": "https://cpo.example.com/ocpi/2.2.1/locations"
      },
      {
        "identifier": "sessions",              // required — ModuleID
        "role": "SENDER",                       // required — the CPO is the Sender for sessions
        "url": "https://cpo.example.com/ocpi/2.2.1/sessions"
      },
      {
        "identifier": "cdrs",                  // required — ModuleID
        "role": "SENDER",                       // required — the CPO is the Sender for CDRs
        "url": "https://cpo.example.com/ocpi/2.2.1/cdrs"
      },
      {
        "identifier": "tariffs",               // required — ModuleID
        "role": "SENDER",                       // required — the CPO is the Sender for tariffs
        "url": "https://cpo.example.com/ocpi/2.2.1/tariffs"
      },
      {
        "identifier": "tokens",                // required — ModuleID
        "role": "RECEIVER",                     // required — the CPO RECEIVES tokens (the eMSP owns them, so it is the Sender)
        "url": "https://cpo.example.com/ocpi/2.2.1/tokens"
      },
      {
        "identifier": "commands",              // required — ModuleID
        "role": "RECEIVER",                     // required — the CPO RECEIVES remote commands (start/stop/reserve)
        "url": "https://cpo.example.com/ocpi/2.2.1/commands"
      },
      {
        "identifier": "chargingprofiles",      // required — ModuleID (smart charging)
        "role": "RECEIVER",                     // required — the CPO RECEIVES charging-profile requests
        "url": "https://cpo.example.com/ocpi/2.2.1/chargingprofiles"
      }
      // Every Endpoint has exactly three required fields (identifier, role, url) and
      // nothing else. A single module can appear TWICE — once as SENDER and once as
      // RECEIVER — when a party implements both interfaces; this CPO does not, so
      // each module is listed once. hubclientinfo would appear here only for a hub.
    ]
  },
  "status_code": 1000,                          // again: check this, not just the HTTP 200
  "status_message": "Success",
  "timestamp": "2026-07-03T09:00:01Z"
}

3. eMSP POSTs its credentials to the CPO (the token exchange)

# ── eMSP ──▶ CPO ────────────────────────────────────────────────────────────
# This is the heart of the handshake. The eMSP sends the CPO a NEW token
# (Token B) for the CPO to use on future calls TO the eMSP, plus the eMSP's own
# /ocpi/versions URL so the CPO can discover the eMSP in turn.

POST /ocpi/2.2.1/credentials HTTP/1.1
Host: cpo.example.com
# Still authenticated with Token A — this POST is the LAST time Token A is valid.
Authorization: Token <base64(Token A)>
Content-Type: application/json

{
  "token": "<Token B>",                         // required — eMSP's token; the CPO will send THIS on calls to the eMSP
  "url": "https://emsp.example.com/ocpi/versions", // required — gotcha: the eMSP's OWN versions URL, not the CPO's
  "roles": [                                    // required — array of CredentialsRole; one per role this party plays
    {
      "role": "EMSP",                           // required — CPO | EMSP | HUB | NAP | NSP | OTHER | SCSP
      "business_details": {                     // required — BusinessDetails
        "name": "The Mobility Network",         // required — display name (max 100 chars)
        "website": "https://themobility.network", // optional — the party's website URL
        "logo": {                               // optional — an Image object; shown here in full
          "url": "https://themobility.network/assets/logo.png", // required within Image
          "thumbnail": "https://themobility.network/assets/logo-thumb.png", // optional — smaller preview
          "category": "OPERATOR",               // required within Image — CHARGER | ENTRANCE | LOCATION | NETWORK | OPERATOR | OTHER | OWNER
          "type": "png",                        // required within Image — file extension, e.g. png | jpeg | gif | svg
          "width": 512,                          // optional — pixel width
          "height": 512                          // optional — pixel height
        }
      },
      "party_id": "TNM",                        // required — the eMSP's party id (CiString, 3 chars)
      "country_code": "NL"                       // required — ISO 3166-1 alpha-2 (CiString, 2 chars)
    }
    // A party that plays multiple roles (e.g. CPO + EMSP) lists one CredentialsRole
    // per role in this array; this eMSP plays a single role, so there is one entry.
  ]
}
# ── CPO ──▶ eMSP (the response) ─────────────────────────────────────────────
# The response body is the OTHER party's credentials: the CPO hands back its
# OWN new token (Token C) and its OWN roles/url. This single response completes
# the exchange.
HTTP/1.1 200 OK
Content-Type: application/json

{
  "data": {
    "token": "<Token C>",                       // required — CPO's token; the eMSP will send THIS on calls to the CPO
    "url": "https://cpo.example.com/ocpi/versions", // required — the CPO's own versions URL
    "roles": [                                  // required — array of CredentialsRole
      {
        "role": "CPO",                          // required — CPO | EMSP | HUB | NAP | NSP | OTHER | SCSP
        "business_details": {                   // required — BusinessDetails
          "name": "Fast Charge Networks",       // required — display name (max 100 chars)
          "website": "https://fastcharge.example.com", // optional — the party's website URL
          "logo": {                             // optional — an Image object; shown here in full
            "url": "https://fastcharge.example.com/brand/logo.svg", // required within Image
            "thumbnail": "https://fastcharge.example.com/brand/logo-64.png", // optional — smaller preview
            "category": "OPERATOR",             // required within Image — CHARGER | ENTRANCE | LOCATION | NETWORK | OPERATOR | OTHER | OWNER
            "type": "svg",                      // required within Image — file extension
            "width": 240,                        // optional — pixel width
            "height": 80                         // optional — pixel height
          }
        },
        "party_id": "ABC",                      // required — the CPO's party id (CiString, 3 chars)
        "country_code": "NL"                     // required — ISO 3166-1 alpha-2 (CiString, 2 chars)
      }
    ]
  },
  "status_code": 1000,                          // 1000 here means the registration succeeded
  "status_message": "Success",
  "timestamp": "2026-07-03T09:00:02Z"
}

The moment this response is sent, Token A is invalidated. From here on: the eMSP authenticates its calls to the CPO with Token C, and the CPO authenticates its calls to the eMSP with Token B. A → B → C, and A never works again.

4. Later: rotating or tearing down the connection

# ── Rotate: replace the token you gave the other side ───────────────────────
# Authenticate with your CURRENT token, and send a fresh credentials object.
# The old token is invalidated once this returns 1000.
PUT /ocpi/2.2.1/credentials HTTP/1.1
Host: cpo.example.com
Authorization: Token <base64(current token)>
Content-Type: application/json

{
  "token": "<new token>",                       // required — replaces the previous one
  "url": "https://emsp.example.com/ocpi/versions", // required — your OWN versions URL
  "roles": [                                    // required — a full CredentialsRole, identical shape to the POST above
    {
      "role": "EMSP",                           // required — CPO | EMSP | HUB | NAP | NSP | OTHER | SCSP
      "business_details": {                     // required — BusinessDetails
        "name": "The Mobility Network",         // required — display name (max 100 chars)
        "website": "https://themobility.network", // optional — the party's website URL
        "logo": {                               // optional — an Image object
          "url": "https://themobility.network/assets/logo.png", // required within Image
          "thumbnail": "https://themobility.network/assets/logo-thumb.png", // optional — smaller preview
          "category": "OPERATOR",               // required within Image — CHARGER | ENTRANCE | LOCATION | NETWORK | OPERATOR | OTHER | OWNER
          "type": "png",                        // required within Image — file extension
          "width": 512,                          // optional — pixel width
          "height": 512                          // optional — pixel height
        }
      },
      "party_id": "TNM",                        // required — party id (CiString, 3 chars)
      "country_code": "NL"                       // required — ISO 3166-1 alpha-2 (CiString, 2 chars)
    }
  ]
}

# ── Unregister: tear the whole connection down ──────────────────────────────
DELETE /ocpi/2.2.1/credentials HTTP/1.1
Host: cpo.example.com
Authorization: Token <base64(current token)>
# After this, both tokens are dead and the parties must handshake again from
# Token A to reconnect.

Common implementation gotchas

A few things that catch real-world implementations:

The three-token dance ordering — the #1 mistake

The single most common failure is mishandling the token sequence. Token A — the out-of-band registration token — is single-use. It exists only to bootstrap the handshake, and it MUST be invalidated the instant the credentials exchange completes. Going forward, each side uses the token the OTHER side gave it:

Out-of-band:  CPO ──▶ eMSP   Token A  (email / portal — single-use bootstrap)

Handshake:    eMSP ──▶ CPO   POST /credentials  { token: Token B, … }
              CPO  ──▶ eMSP  response            { token: Token C, … }

Afterwards:   eMSP ──▶ CPO   auth with Token C   (the token the CPO gave it)
              CPO  ──▶ eMSP  auth with Token B   (the token the eMSP gave it)

              Token A is now DEAD. A → B → C.

Where people go wrong:

  • Reusing Token A for ongoing calls after the handshake — including for the Versions endpoint. Token A is not “kept alive” for discovery. During registration it authenticates exactly three calls (GET /versions, GET the version details, and the POST /credentials); the instant that POST returns, it is invalidated for everything — Versions included. All later calls use Token C (eMSP→CPO) or Token B (CPO→eMSP). Reconnecting after a DELETE /credentials needs a fresh Token A, issued out-of-band. It’s invalidated by design — a compromise of the original email must not grant lasting API access.
  • Authenticating with your OWN token instead of the one the other side issued. The eMSP calls the CPO with Token C (the CPO’s token), NOT with Token B (its own). Swapping these is the classic “401 Unauthorized” cause.
  • Sending the wrong url. In the POST body you send your OWN /ocpi/versions URL, not the other party’s — it is how they discover you back.

The Authorization header carries the credentials token

Every OCPI call authenticates with Authorization: Token <value> — the literal word Token, never Bearer. The value is whichever credentials token applies to the direction of the call (A, then B or C). In OCPI 2.2 and later the token value is base64-encoded before it goes in the header; forgetting to encode (or double-encoding) it is a common source of silent auth failures.

last_updated and status_code still apply

As with every module: HTTP 200 only means the call arrived — you must ALSO check the status_code inside the OCPI envelope (1000 = success, 2xxx = client error, 3xxx = server), and all timestamps are RFC 3339 in UTC (the trailing Z).

For the authoritative field-by-field definitions, see the OCPI 2.2.1 specification’s Credentials and Versions modules in the OCPI GitHub repository.

Real-world gotchas

Token A leakage

The out-of-band Token A is the most fragile link. If it’s emailed in plaintext and intercepted, an attacker could complete the handshake themselves. Mitigations: rotate Token A frequently, deliver it through secure channels (signed emails, secure portals), expire it quickly.

Version mismatch

If two parties don’t share a common version, they can’t connect. Workaround: either upgrade one side, or run a translation proxy (some hubs do this).

Endpoint mismatch

A party might advertise a module endpoint in Version Details that they don’t actually support correctly. Calls fail with 404 or 501. Testing is essential before going to production.

Token rotation while sessions are in flight

If you rotate tokens while there are in-flight requests, some requests may fail. Best practice: graceful overlap (accept both old and new tokens for a transition window).

Hub routing complexity

When a hub sits between two parties, the credentials dance happens twice — once between each party and the hub. The hub maintains independent credentials with each side. This is standard but adds complexity.

Three things to take from this

  1. Credentials and Versions are the foundation. Nothing else works without them.

  2. The three-token dance is the security pattern. A → B → C, with A then invalidated. It gives both sides control and limits the lifetime of any single token.

  3. Each version’s module endpoints are independent. A party can support different modules at different versions. The Version Details is the source of truth.

These modules are unglamorous but the OCPI ecosystem depends on them. Without working Credentials and Versions, you can’t even start to roam.

Quick check

Q1. What does the Versions module do?

Frequently asked questions

What do the OCPI Credentials and Versions modules do?

They handle the initial setup between two OCPI parties. Versions module tells each side what OCPI versions and modules the other supports. Credentials module exchanges authentication tokens for the ongoing connection.

Do I need to implement these before any other module?

Yes. Credentials and Versions are foundational — without a successful handshake, no other module can be used between two parties.

After the handshake, can Token A still be used for the Versions endpoint?

No. Token A is single-use for registration only. During the handshake it authenticates exactly three calls — GET /versions, GET the version details, and POST /credentials — then is invalidated the moment that POST succeeds. Every call afterward, including GET /versions, uses the exchanged tokens (Token B and Token C). Reconnecting after a DELETE requires a brand-new Token A issued out-of-band.

Found this useful? Share it.