To test OCPI code, an eMSP needs a CPO on the other end that hands out credentials, answers like a real one and has a charger behind it. SimItNow can be that counterpart. It plays the CPO and your platform plays the eMSP. The connection is direct, there is no roaming hub, and the protocol version is OCPI 2.2.1.
This guide follows the order a developer would work in: set up a chargepoint, get Token A, run the credentials handshake, read locations and tariffs, push a token, start and stop a session, then check what arrived. It is a test environment, not a live roaming network. The locations are generated, there is one tariff, and no money changes hands.
What you need first
- A SimItNow account and API key. The free plan is enough for the whole sequence below.
- An eMSP that SimItNow can call. During the handshake SimItNow fetches your versions URL, and later it sends you command results, sessions and CDRs. It only calls public https addresses, so a server on localhost, a plain http URL or a private address is refused. If your eMSP runs on a laptop, put a tunnel in front of it. The tunnel guide is written for a CSMS, but ngrok and Cloudflare Tunnel do the same job here.
- An OCPP 1.6 chargepoint on a private hosted CSMS. Only those chargepoints take part in OCPI sessions. Step 1 sets one up with a simulated station, so you need no hardware.
1. Set up a chargepoint
OCPI locations come from the chargepoints registered on your private hosted CSMS. A chargepoint that connects to an open CSMS has no registration, so it cannot appear as a location. Switch your CSMS to private first. The hosted CSMS guide explains both modes.
curl -X PUT https://api.simitnow.com/v2/csms \
-H "SIMITNOW-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-d '{"public":false}'
Then register a chargepoint. The response contains a password that is shown once.
curl -X PUT https://api.simitnow.com/v2/csms/cp/CP-001 \
-H "SIMITNOW-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-d '{"displayName":"OCPI test chargepoint"}'
A real OCPP 1.6 charger gets the connection URL from that response. Without one, create a simulated station that dials in to your hosted CSMS with the same id and the password:
curl -X PUT https://api.simitnow.com/v2/cs/CP-001 \
-H "SIMITNOW-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"csmsUrl": "wss://csms.simitnow.com/ocpp/AbCdEf0123456789",
"protocol": "ocpp1.6",
"security": {
"basicAuth": {
"password": "password-from-the-registration"
}
}
}'
Put your own csmsId in the URL. It is in the response of the first call. The chargepoint id, CP-001 here, becomes the OCPI location id, and a chargepoint id longer than 34 characters is left out of the location list.
2. Get Token A and the versions URL
Open the OCPI tab on the dashboard. It shows the versions URL, https://ocpi.simitnow.com/ocpi/versions, and the country code and party ID SimItNow uses as the CPO. Choose Generate Token A and copy it. It is shown once, because only a hash is stored, and generating a new one invalidates the old one.
Token A is for the handshake. It can read the versions routes, and a successful credentials call spends it.
3. Run the credentials handshake
Your eMSP does this itself. The curl calls below show what it sends, so you can also try the handshake by hand. Authenticate with Authorization: Token <Token A>. A base64 encoded token, as OCPI 2.2.1 sends it, works too. First the versions list and the 2.2.1 endpoints:
curl https://ocpi.simitnow.com/ocpi/versions \
-H "Authorization: Token $TOKEN_A"
curl https://ocpi.simitnow.com/ocpi/2.2.1 \
-H "Authorization: Token $TOKEN_A"
Then post your credentials: a token of your own (Token C), your versions URL and your role. SimItNow sends Token C back when it calls you, base64 encoded as OCPI 2.2.1 does, so your eMSP has to decode it before it compares.
curl -X POST https://ocpi.simitnow.com/ocpi/2.2.1/credentials \
-H "Authorization: Token $TOKEN_A" \
-H "Content-Type: application/json" \
-d '{
"token": "your-token-c",
"url": "https://emsp.example.com/ocpi/versions",
"roles": [
{
"role": "EMSP",
"country_code": "DE",
"party_id": "ABC",
"business_details": {
"name": "Example eMSP"
}
}
]
}'
Before it accepts, SimItNow calls your versions URL with Token C, finds the 2.2.1 entry, reads your endpoints and checks that each one it uses is a public https address. The reply carries Token B. Your eMSP uses Token B for every later call, and Token A stops working. Token B is shown once in this reply, and SimItNow stores only a hash of it.
A handshake that SimItNow refuses or cannot complete does not spend Token A, so you can fix the problem and send the same call again. To receive sessions and CDRs later, your 2.2.1 module list must include the sessions and cdrs receiver endpoints.
4. Read locations and tariffs
From here on, send Token B.
curl https://ocpi.simitnow.com/ocpi/2.2.1/locations \
-H "Authorization: Token $TOKEN_B"
curl https://ocpi.simitnow.com/ocpi/2.2.1/tariffs \
-H "Authorization: Token $TOKEN_B"
Each registered chargepoint is one location with the chargepoint id as its id, one EVSE with the uid CP-001-1, and one connector with id 1, a Type 2 socket on three-phase AC at 400 V and 32 A. The EVSE is AVAILABLE while the chargepoint is connected. The address is an invented site in Istanbul, with coordinates derived from the id.
There is one tariff for everyone, SIMITNOW-DEFAULT: EUR 0.30 per kWh of energy and nothing else. Lists accept offset, limit, date_from and date_to, return X-Total-Count, and default to 50 items per page with a maximum of 100.
5. Push a token
SimItNow is the receiver for tokens, so your eMSP pushes them with PUT or PATCH. The country code and party ID in the path must be the ones you registered in the handshake, and the uid can have at most 36 characters.
curl -X PUT https://ocpi.simitnow.com/ocpi/2.2.1/tokens/DE/ABC/TOKEN-0001 \
-H "Authorization: Token $TOKEN_B" \
-H "Content-Type: application/json" \
-d '{
"country_code": "DE",
"party_id": "ABC",
"uid": "TOKEN-0001",
"type": "RFID",
"contract_id": "DE-ABC-C00000001-X",
"issuer": "Example eMSP",
"valid": true,
"whitelist": "ALLOWED",
"last_updated": "2026-10-30T10:00:00Z"
}'
All nine fields are required. A token with a missing or malformed field is refused with OCPI status 2001 and a message that names the fields without quoting their values. A push is not strictly needed before a start, because START_SESSION carries its own token. When you did push the token, SimItNow judges the pushed copy. Either way the token must be valid and its whitelist must not be NEVER.
6. Start a session
curl -X POST https://ocpi.simitnow.com/ocpi/2.2.1/commands/START_SESSION \
-H "Authorization: Token $TOKEN_B" \
-H "Content-Type: application/json" \
-d '{
"response_url": "https://emsp.example.com/ocpi/emsp/2.2.1/commands/START_SESSION/1",
"token": {
"country_code": "DE",
"party_id": "ABC",
"uid": "TOKEN-0001",
"type": "RFID",
"contract_id": "DE-ABC-C00000001-X",
"issuer": "Example eMSP",
"valid": true,
"whitelist": "ALLOWED",
"last_updated": "2026-10-30T10:00:00Z"
},
"location_id": "CP-001",
"authorization_reference": "AUTH-0001"
}'
The location_id is the chargepoint id. The response_url must be on the same origin (scheme, host and port) as your versions URL or one of your endpoints, because SimItNow sends Token C to it. Otherwise the call fails with 2001 and nothing runs.
The answer comes at once and only promises an attempt:
{"data":{"result":"ACCEPTED","timeout":30},"status_code":1000,"status_message":"Success","timestamp":"..."}
The result arrives later, as a CommandResult that SimItNow posts to your response_url within that timeout of 30 seconds. SimItNow asks the station to start remotely and reports what the station said:
- ACCEPTED: the station accepted the remote start.
- REJECTED: the station refused it.
- FAILED: the chargepoint is not connected, or SimItNow could not complete the call.
- TIMEOUT: the station did not answer in time.
- NOT_SUPPORTED: the chargepoint is OCPP 2.x.
Some starts are answered REJECTED immediately instead: no registered eMSP, a location that is not one of your chargepoints, a token that is not valid or is whitelisted NEVER, a session already pending or active on that chargepoint, or an authorization_reference that was used before. A reference is spent even by a start that failed, so use a new one for each attempt.
An accepted start is not yet a session. The station accepts the remote start at once, but the session only begins when an EV connects. Create a virtual EV and connect it to the chargepoint within 5 minutes. A session that has not begun by then is marked INVALID.
curl -X PUT https://api.simitnow.com/v2/ev/EV-01 \
-H "SIMITNOW-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-d '{"batteryCapacity":60}'
curl -X POST https://api.simitnow.com/v2/ev/EV-01/connection \
-H "SIMITNOW-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-d '{"cbId":"CP-001","connectorId":1}'
SimItNow pushes the session to your sessions endpoint while it is still PENDING, and again each time it changes: ACTIVE once the EV is connected, then updates about every meter sample (30 seconds by default on a simulated station), then COMPLETED, or INVALID if no EV came. Each push is a PUT to /{country_code}/{party_id}/{session_id} under your sessions endpoint, where the country code and party ID are SimItNow's CPO identity from the OCPI tab, not yours. Your eMSP has to handle all four statuses. You can also read sessions yourself, and date_from is required for that call:
curl "https://ocpi.simitnow.com/ocpi/2.2.1/sessions?date_from=2026-10-30T00:00:00Z" \
-H "Authorization: Token $TOKEN_B"
7. Stop the session and get the CDR
Take the session id from the session you received and send STOP_SESSION. Only an ACTIVE session can be stopped. For anything else the answer is UNKNOWN_SESSION.
curl -X POST https://ocpi.simitnow.com/ocpi/2.2.1/commands/STOP_SESSION \
-H "Authorization: Token $TOKEN_B" \
-H "Content-Type: application/json" \
-d '{
"response_url": "https://emsp.example.com/ocpi/emsp/2.2.1/commands/STOP_SESSION/1",
"session_id": "the-session-id"
}'
The CommandResult arrives the same way as the start result. The session becomes COMPLETED, and the CDR is posted to your cdrs endpoint, priced as the energy delivered at the default tariff. You can read it as well. A session that ends INVALID produces no CDR.
SimItNow counts a session or CDR push as delivered only when your endpoint answers HTTP 2xx with an OCPI status of 1000, or with no OCPI status at all. Anything else is retried with a growing delay, and each retry counts as a message. A CommandResult is posted once and not retried.
curl https://ocpi.simitnow.com/ocpi/2.2.1/cdrs \
-H "Authorization: Token $TOKEN_B"
8. Read the checklist
The OCPI tab turns your calls into a readiness checklist of ten steps: Credentials handshake, Versions, Locations, Tariffs, Token push, Start a session, Command result delivered, Session visible, Stop the session and CDR delivered. Each is marked Passed, Failed or Not run.
- The checklist is built from the calls SimItNow saw, and nothing on the tab sends a call.
- A step stays marked after its call has left the log, and the latest attempt wins. If your eMSP unregisters, the checklist starts over.
- A list read passes Locations, Session visible or CDR delivered only when it returned at least one item, because an empty first sync proves nothing.
- A start or stop passes on a delivered ACCEPTED result, not on the immediate answer.
Below the checklist, the tab lists your last 100 calls, in both directions, with method, path, HTTP and OCPI status and a note. Calls with a wrong or already used token are refused and not listed, because SimItNow cannot tell which account they belong to.
When something goes wrong
| What you see | Likely cause |
|---|---|
| Handshake answers 401 | Token A was wrong or already spent. Generate a new one. |
| Handshake answers 502 with status 3001 | SimItNow could not use your API: your versions URL or details did not answer, or had no 2.2.1 entry. |
| Handshake answers 400 with status 2001 | One of your URLs is not a public https address, or the credentials body is malformed. The message names what is wrong. |
| Handshake answers 405 | You are already registered. Send a PUT with Token B to update the credentials. |
| Locations is empty | The CSMS is open, the chargepoint is not registered, or its id is longer than 34 characters. |
| START_SESSION answers 2001 | The response_url is not on your registered origin. |
| CommandResult is FAILED | The chargepoint is not connected. Check the station before your eMSP. |
| Session is INVALID | No EV connected within 5 minutes, or the station refused the start. |
| Sessions and CDRs are not pushed | Your module list lacks the sessions or cdrs receiver endpoint. They stay available to read. |
The daily allowance
Every authenticated OCPI call counts as one message, in either direction, and OCPI shares one daily counter with your OCPP messages. That includes the command results and each session update SimItNow sends you, and the station's heartbeats count too. The free plan allows 500 messages a day for its one chargepoint. A short test session fits: SimItNow's own end-to-end test runs this whole sequence under 500 messages, heartbeats included. A station left connected all day does not fit. The hosted CSMS asks for a heartbeat every 5 minutes by default, and each heartbeat is two messages, so heartbeats alone come to 576 a day. While a session charges, meter values and session updates add more, about every 30 seconds by default.
A call past the allowance and its 10% grace is refused with HTTP 429, and going over also closes your OCPP chargepoints with WebSocket close code 4429. The Solo plan is the plan for real testing: 5 chargepoints and 2,500 messages a day.
If the allowance runs out during a session, the station is closed and cannot take STOP_SESSION. The command is accepted, its CommandResult is FAILED, and the session stays ACTIVE until it is marked INVALID after 6 hours, without a CDR.
What it does not do
- RESERVE_NOW, CANCEL_RESERVATION and UNLOCK_CONNECTOR answer NOT_SUPPORTED.
- On an OCPP 2.x chargepoint, START_SESSION is answered ACCEPTED and its CommandResult is NOT_SUPPORTED, and STOP_SESSION answers UNKNOWN_SESSION.
- It speaks OCPI 2.2.1 only, with one tariff and one connector per location.
- It is not a live roaming network. There is no roaming hub and no settlement.
The reference is in the OCPI section of the API docs. Create a free account to get your Token A.