# OCPP CSMS Smoke Test — Virtual Charge Point (`tiger19935/ocpp-csms-smoke-test`) Actor

A virtual OCPP 1.6J charge point connects to your own CSMS, plays boot, charging and responder scenarios and returns a step-by-step PASS/WARN/FAIL report. Built for CSMS developers and integrators. Authorized use only; not a certification, load or security tool. Pay per completed run.

- **URL**: https://apify.com/tiger19935/ocpp-csms-smoke-test.md
- **Developed by:** [Andrew Boiko](https://apify.com/tiger19935) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $240.00 / 1,000 completed smoke runs

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## OCPP CSMS Smoke Test - Virtual Charge Point

**Connect a virtual OCPP 1.6J charge point to your own charging station management system (CSMS), play a real scenario - boot, heartbeat, a short charging session or a "stay connected and answer commands" window - and get a step-by-step PASS / WARN / FAIL report with request, response and latency of every message.**

> **Authorized use only.** Point this Actor **only at a CSMS that you own or that you have explicit written permission to test.** You must tick *"I am authorized to test this CSMS"* before anything runs; without it the Actor stops before any network connection is made. It is **not a load, stress, fuzzing or security testing tool**, it connects **one** virtual charge point, sends a handful of messages, and is limited to **3 runs per hour per target host**. See [Safe and responsible use](#safe-and-responsible-use).

### Who is it for?

- **CSMS vendors and in-house platform teams** - run a quick end-to-end check of staging or production after each deploy ("can a charge point still boot, authorize, start and stop?").
- **Charge point operators (CPOs)** who want a repeatable test charge point without a physical wallbox.
- **Integrators and QA teams** - put it in CI before a heavier certification effort, or schedule it hourly as a synthetic monitor of your own OCPP endpoint.
- **Developers building an OCPP backend** who need a second opinion on handshake, subprotocol, response shapes and timeouts.

### What it checks

| Step | OCPP message | What is verified |
|---|---|---|
| connect | WebSocket handshake | `Sec-WebSocket-Protocol: ocpp1.6` negotiated (fails if the server picks another or none), TLS, Basic auth accepted, no redirect, handshake latency |
| BootNotification | `BootNotification` | `status` is Accepted / Pending / Rejected, `currentTime` is an ISO 8601 date-time (a missing time zone is only a warning), `interval` is an integer >= 0. Pending / Rejected give a **warning** and the remaining steps are skipped with an explanation |
| StatusNotification | `StatusNotification` (Available, Preparing, Charging, Available) | response is an empty object |
| Heartbeat | `Heartbeat` | `currentTime` is a valid date-time |
| Authorize | `Authorize` | `idTagInfo.status` is valid and **Accepted** for your test tag |
| StartTransaction | `StartTransaction` | `transactionId` is an integer, `idTagInfo.status` Accepted |
| MeterValues | `MeterValues` x2 | both are accepted with an empty-object response (the Actor sends two increasing `Energy.Active.Import.Register` samples; it does not check what the CSMS stores) |
| StopTransaction | `StopTransaction` (reason `Local`) | accepted, optional `idTagInfo` is well-formed. If a step fails after the transaction was started, the Actor still sends `StopTransaction` (and a final `Available`) when the connection is alive, so the test does not leave an open transaction in your CSMS |
| Responder window | CSMS-initiated calls | `Reset`, `RemoteStartTransaction`, `RemoteStopTransaction`, `ChangeConfiguration`, `GetConfiguration`, `TriggerMessage`, `UnlockConnector`, `ChangeAvailability`, `DataTransfer`, `ClearCache` are answered with plausible confirmations; anything else gets a `CALLERROR NotImplemented`. Every call is listed in `received`. **Nothing is ever executed** (`TriggerMessage` is only honoured in the `responder` scenario) |

For every message the Actor also detects: `CALLERROR` replies, **10 s timeouts**, invalid JSON, wrong message ids or message types, wrong response shapes, a server that closes the connection mid-run, frames over 1 MB, and message floods (hard limits: **120 s, 200 messages, 1 MB total payload** - the run is then closed cleanly with code 1000).

#### Scenarios

| Scenario | Steps |
|---|---|
| `connect` | WebSocket handshake and subprotocol only |
| `boot` | connect, BootNotification, StatusNotification(Available), Heartbeat |
| `basic-charge` (default) | boot + Authorize, StatusNotification(Preparing), StartTransaction, StatusNotification(Charging), 2 x MeterValues over `chargeSeconds`, StopTransaction, StatusNotification(Available) |
| `responder` | boot, then stay connected for `responderSeconds` and answer CSMS commands (trigger Reset, RemoteStart, TriggerMessage ... from your CSMS back office while the test runs) |

### Not a certification tool

This Actor is a **smoke test**. It does **not** replace the official **OCA OCTT (OCPP Compliance Testing Tool)**, which is the Open Charge Alliance's official test suite, and passing it does not make a CSMS "OCPP compliant" or certified. It is independent, unofficial and not affiliated with or endorsed by the Open Charge Alliance. It contains no OCA schema files; the response validators are small hand-written checks of required fields and types.

### Safe and responsible use

- **Consent is mandatory.** `iAmAuthorized` must be `true`, otherwise the run ends before any network I/O and costs nothing.
- **Only TLS.** Only `wss://` endpoints on port **443** or **8000-9999** are accepted. No credentials inside the URL.
- **No internal targets.** Loopback, private, link-local, carrier-grade NAT, multicast and cloud-metadata addresses (IPv4, IPv6 and IPv4-mapped) are refused. The host name is resolved **once** and the connection goes to exactly that address, so DNS rebinding cannot redirect the test to an internal host.
- **No redirects** are followed during the handshake.
- **Rate limits** (counted per Apify account, stored as hashes): at most **3 runs per hour per target host** (host spelling, upper/lower case, a trailing dot, IPv6 notation and the port do not matter), **10 per hour per resolved server address** (so many host names pointing to one server do not help) and **30 connections per hour in total**. The first refused attempt returns `rate_limited` immediately, without connecting and without charge.
- **Identifiable.** Every handshake carries `User-Agent: ApifyOcppSmokeTest/1.0` and `X-Test-Source: apify-ocpp-smoke-test`, so your operations team can recognise and filter the traffic.
- **Small and bounded.** One connection, one charge point, a dozen messages. No fuzzing, flooding or load features, by design.
- **Use a dedicated test charge point id and test idTag.** Do not use the identity of a real station or a real customer's card.

### How to use it

1. Create a **test charge point** (and a test idTag) in your CSMS. If your CSMS needs authentication, create a **temporary test account**.
2. Enter the **endpoint**, e.g. `wss://csms.example.com/ocpp`, and the **charge point ID**. The Actor connects to `<endpoint>/<chargePointId>` (switch off *Append the charge point ID* if your URL already contains it).
3. Pick a **scenario**, tick **I am authorized to test this CSMS**, click **Start**.
4. Read the report in the **Report / Steps** dataset views, the Markdown summary (`RESULTS_MD`), or via API.

#### Example input - full charging session against a CSMS with Basic authentication

```json
{
  "endpoint": "wss://csms.example.com/ocpp",
  "chargePointId": "APIFY-SMOKE-01",
  "authType": "basic",
  "username": "APIFY-SMOKE-01",
  "password": "temporary-test-password",
  "scenario": "basic-charge",
  "idTag": "TESTTAG1",
  "connectorId": 1,
  "chargeSeconds": 5,
  "iAmAuthorized": true
}
```

#### Example input - answer CSMS commands for 90 seconds

```json
{
  "endpoint": "wss://csms.example.com:8443/ocpp/1.6",
  "chargePointId": "VCP-RESPONDER-1",
  "appendChargePointId": true,
  "scenario": "responder",
  "responderSeconds": 90,
  "iAmAuthorized": true
}
```

#### Try it with the prefilled input

The prefilled input points to `wss://csms.example.invalid/ocpp`, a host name that can never resolve. The run therefore ends within a few seconds with status `unreachable` (a "DNS lookup failed" report), is **free**, and still returns one non-empty dataset record so you can see the output format. Replace the endpoint with your own CSMS to run a real test.

### Output

One dataset record per run (also available in the `OUTPUT` and `RESULTS_MD` records of the key-value store):

```json
{
  "recordType": "smoke-run",
  "status": "warnings",
  "reason": "boot_not_accepted",
  "message": "BootNotification: CSMS answered Pending ...",
  "scenario": "basic-charge",
  "endpointHost": "csms.example.com",
  "chargePointId": "APIFY-SMOKE-01",
  "ocppVersion": "1.6J",
  "negotiatedSubprotocol": "ocpp1.6",
  "handshakeMs": 84.3,
  "durationMs": 412,
  "summary": { "pass": 1, "warn": 1, "fail": 0, "skipped": 10 },
  "steps": [
    {
      "name": "BootNotification",
      "action": "BootNotification",
      "request": { "chargePointVendor": "ApifySmoke", "chargePointModel": "VCP-1" },
      "response": { "status": "Pending", "currentTime": "2026-10-02T12:00:00.000Z", "interval": 60 },
      "error": null,
      "latencyMs": 41.7,
      "result": "warn",
      "notes": ["CSMS answered Pending: a real charge point must not send other messages until it is Accepted ..."]
    }
  ],
  "received": [],
  "charged": { "event": "smoke-run", "units": 1 },
  "reportMarkdown": "# OCPP 1.6J smoke test: PASSED WITH WARNINGS ...",
  "startedAt": "2026-10-02T12:00:00.000Z"
}
```

`status` is one of `passed`, `warnings`, `failed`, `unreachable`, `rejected_input`, `rate_limited`, `aborted`. `reason` is a short machine-readable code (`scenario_completed`, `step_failed`, `timeout`, `callerror`, `protocol_violation`, `connection_closed`, `subprotocol_mismatch`, `handshake_rejected_401`, `dns_failed`, `connect_failed`, `tls_error`, `message_limit`, `payload_limit`, `time_limit`, `consent_missing`, `blocked_target`, `rate_limited`, `budget` ...).

Step results: `pass`, `warn` (works but unusual, e.g. Pending boot), `fail` (CSMS error or invalid response), `skipped` (not run because an earlier step failed).

### Pricing

**Pay per event: one `smoke-run` per run** (current price and the discounts for paid Apify plans are shown on the Pricing tab), charged **once** per run, only when the WebSocket connection to your CSMS was actually established and the scenario finished or hit the first CSMS-side failure.

**Always free:** missing consent or invalid input, blocked targets, `rate_limited`, DNS failures, unreachable hosts, TLS or handshake failures before the connection is established, and the Actor's own errors. If your *maximum cost per run* is below the price of one run, nothing is started.

### Integrations and API

Use it from CI right after a deployment and fail the pipeline when `status` is not `passed`.

```bash
curl -s "https://api.apify.com/v2/acts/YOUR_USERNAME~ocpp-csms-smoke-test/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -X POST -H 'Content-Type: application/json' \
  -d '{"endpoint":"wss://csms.example.com/ocpp","chargePointId":"APIFY-SMOKE-01","scenario":"boot","iAmAuthorized":true}'
```

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('YOUR_USERNAME/ocpp-csms-smoke-test').call({ endpoint: 'wss://csms.example.com/ocpp', chargePointId: 'APIFY-SMOKE-01', scenario: 'basic-charge', iAmAuthorized: true });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
if (items[0].status !== 'passed') process.exit(1);
```

Schedule it hourly for a synthetic monitor of your own endpoint (mind the limit of 3 runs per hour per host), or connect it to Slack / e-mail / webhooks with Apify integrations.

### FAQ

**Does it work with my CSMS?** With any OCPP 1.6J (JSON over WebSocket) central system that accepts a charge point over `wss://` with the `ocpp1.6` subprotocol, optionally with HTTP Basic authentication (security profile 1 / 2 over TLS).

**What about my credentials?** Create a **temporary test account** for the run. The password is an Apify secret input, is sent **only** in the `Authorization` header of the WebSocket handshake, and is **never logged, stored in a dataset, key-value store or report**, or returned in error messages (every output is scrubbed even if your CSMS echoes it back). Delete the test account afterwards. Do not put tokens in the URL path or query: unlike the password they are part of the endpoint you typed.

**Why was my run `rate_limited`?** More than 3 runs against the same host within one hour were started from your Apify account. Wait for the time shown in the report.

**Why `rejected_input`?** Consent missing, an endpoint that is not `wss://`, a port outside 443 / 8000-9999, credentials inside the URL, or a private / internal address.

**Why is a failed connection sometimes free and sometimes not?** If your CSMS never answered `101 Switching Protocols` (DNS, TCP, TLS, HTTP 401/403/404/302) you are not charged. If it accepted the connection and then misbehaved (wrong subprotocol, CALLERROR, timeout, invalid response, closing mid-run) the test did its job and the run is charged.

**Does it test my CSMS' security?** No. It does not probe, fuzz or attack anything.

### Limitations

- OCPP **1.6J only**. OCPP 2.0.1 is on the roadmap.
- No client certificates (mTLS, security profile 3). Self-signed server certificates are not trusted.
- One connector and one transaction per run; no firmware, smart charging, reservation or local-list scenarios.
- Responses are checked for required fields and types only, not against the full official JSON schemas.
- The rate limits (3 runs per hour per target host, 10 per server address, 30 per account) are counted per Apify account. Connection attempts that fail (unreachable) also count.

### Support

Open an issue from the Actor page with the `reportMarkdown` of the run (it contains no secrets) and what you expected.

### More tools by the same author

- [EV Charging Stations Scraper](https://apify.com/tiger19935/ev-charging-stations-scraper): Official open data on EU EV charging stations

# Changelog

This Actor's version history is a separate document: https://apify.com/tiger19935/ocpp-csms-smoke-test/changelog.md

# Actor input Schema

## `endpoint` (type: `string`):

Base URL of the OCPP-J WebSocket endpoint, e.g. wss://csms.example.com/ocpp. The charge point id is appended to the path unless you switch that off below. Do not put credentials in the URL (use Basic authentication below).

## `chargePointId` (type: `string`):

Identity of the virtual charge point as your CSMS knows it (1-48 characters: letters, digits, dot, underscore, hyphen). Use a dedicated test charge point, never the id of a real station.

## `appendChargePointId` (type: `boolean`):

OCPP-J connects to <endpoint>/<chargePointId>. Switch off only if your endpoint URL already contains the id.

## `ocppVersion` (type: `string`):

Only OCPP 1.6J (JSON, subprotocol ocpp1.6) is supported. OCPP 2.0.1 is on the roadmap.

## `authType` (type: `string`):

HTTP Basic authentication in the WebSocket handshake (OCPP security profile 1 or 2 over TLS). Client certificates (mTLS) are not supported.

## `username` (type: `string`):

Username for HTTP Basic authentication (often the charge point id). Used only when Authentication is 'HTTP Basic'. Must not contain a colon.

## `password` (type: `string`):

Password or authorization key for HTTP Basic authentication. Used only when Authentication is 'HTTP Basic'. Never logged, stored or returned by this Actor.

## `scenario` (type: `string`):

connect = WebSocket handshake and subprotocol only. boot = + BootNotification, StatusNotification, Heartbeat. basic-charge = + Authorize, a short charging transaction with meter values and stop. responder = boot, then stay connected and answer CSMS-initiated commands.

## `idTag` (type: `string`):

RFID / idTag used for Authorize and StartTransaction (max 20 printable ASCII characters). It must be known and accepted by your CSMS for the basic-charge scenario.

## `connectorId` (type: `integer`):

Connector number used in StatusNotification, StartTransaction and MeterValues.

## `vendor` (type: `string`):

chargePointVendor sent in BootNotification (max 20 characters).

## `model` (type: `string`):

chargePointModel sent in BootNotification (max 20 characters).

## `meterStartWh` (type: `integer`):

Energy register value in Wh at the start of the simulated session. Two meter samples (+50 Wh, +100 Wh) follow.

## `chargeSeconds` (type: `integer`):

How long the simulated charging session lasts between StartTransaction and StopTransaction (basic-charge scenario).

## `responderSeconds` (type: `integer`):

How long the virtual charge point stays connected and answers CSMS commands (responder scenario). Commands are only acknowledged, nothing is executed.

## `iAmAuthorized` (type: `boolean`):

Tick to confirm that you own this CSMS or have the operator's permission to connect a test charge point to it. This Actor is not a load or security testing tool, and runs against one host are limited to 3 per hour.

## Actor input object example

```json
{
  "endpoint": "wss://csms.example.invalid/ocpp",
  "chargePointId": "APIFY-SMOKE-01",
  "appendChargePointId": true,
  "ocppVersion": "1.6J",
  "authType": "none",
  "scenario": "basic-charge",
  "idTag": "TESTTAG1",
  "connectorId": 1,
  "vendor": "ApifySmoke",
  "model": "VCP-1",
  "meterStartWh": 0,
  "chargeSeconds": 3,
  "responderSeconds": 60,
  "iAmAuthorized": true
}
```

# Actor output Schema

## `report` (type: `string`):

The step-by-step smoke test report of this run (one record).

## `steps` (type: `string`):

One record with the per-step requests, responses and latencies.

## `summary` (type: `string`):

Status, reason, pass/warn/fail counts, charged events, skipped items and warnings.

## `markdown` (type: `string`):

The same report as a short Markdown document.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "endpoint": "wss://csms.example.invalid/ocpp",
    "chargePointId": "APIFY-SMOKE-01",
    "iAmAuthorized": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("tiger19935/ocpp-csms-smoke-test").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "endpoint": "wss://csms.example.invalid/ocpp",
    "chargePointId": "APIFY-SMOKE-01",
    "iAmAuthorized": True,
}

# Run the Actor and wait for it to finish
run = client.actor("tiger19935/ocpp-csms-smoke-test").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "endpoint": "wss://csms.example.invalid/ocpp",
  "chargePointId": "APIFY-SMOKE-01",
  "iAmAuthorized": true
}' |
apify call tiger19935/ocpp-csms-smoke-test --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tiger19935/ocpp-csms-smoke-test"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/O0y0rfBG2635dCevF/builds/K7a3cQdRD7yZtFeh3/openapi.json
