# SimItNow quickstart for AI agents

Run a real test against a CSMS with REST calls: create a simulated charging station and an electric vehicle, charge, break things on purpose, verify, clean up. Everything below is an operation in https://www.simitnow.com/openapi.json; read the spec for fields this page leaves out. Start with https://www.simitnow.com/llms.txt if you have not read it: it lists what SimItNow can and cannot do.

## Rules

1. Never write the API key into a file, a URL or a log. Read it from `SIMITNOW_API_KEY`.
2. A 201 or 200 means the request was accepted, not that the simulator finished. Poll for the state you expect, with a timeout.
3. SimItNow reports what the simulator did. Check your CSMS for what it recorded. Do not say a test passed until you looked at both sides.
4. Name everything with your own prefix, delete it when you finish (also on failure), and delete only what you created.
5. Every OCPP message counts against a daily allowance (Free: 500, Solo: 2,500, Pro: 10,000, resets 00:00 UTC), Heartbeats included. Start small.

## 0. Setup

You need an API key from the account owner (sign up at https://www.simitnow.com/signup, confirm the email, create the key on the dashboard). You cannot create one through the API.

```bash
export SIMITNOW_API_KEY="..."     # from the account owner
export CSMS_URL="wss://csms.example.com/ocpp"   # YOUR CSMS, without the chargepoint id

# Prints the body and the HTTP status of every call.
sim() { curl -sS -w '\nHTTP %{http_code}\n' -H "SIMITNOW-API-KEY: $SIMITNOW_API_KEY" -H "Content-Type: application/json" "$@"; }
```

`CSMS_URL` is the address your CSMS listens on. SimItNow appends the station id to it, so do not put the id in the URL: `.../ocpp/CP-001` becomes `.../ocpp/CP-001/CP-001` and the CSMS closes the connection after BootNotification.

## 1. Authenticate

Every request carries `SIMITNOW-API-KEY`. This call changes nothing and checks the key:

```bash
sim -X GET https://api.simitnow.com/v2/csms
```

- `HTTP 200`, or `HTTP 404` with `No CSMS instance provisioned yet`: the key is valid.
- `HTTP 401` with `API key missing` or `Invalid API key`: stop and ask for a valid key.

Do not use a station or EV call to test the key. A wrong key on those paths answers `404 No cs simulator found`, not 401, because the request is routed by key and id first.

## 2. Check that your CSMS is reachable

The probe makes one WebSocket handshake and sends no BootNotification, so it leaves nothing on your CSMS. It always answers 200; read `ok` and `code` in the body. It is limited to 20 a minute.

```bash
sim -X POST https://api.simitnow.com/v1/cs/probe -d "{\"csmsUrl\":\"$CSMS_URL\",\"id\":\"ai-cp-1\",\"protocol\":\"ocpp1.6\"}"
```

If `ok` is false, `cause` and `action` say what to fix. Do not create stations until `ok` is true.

### No CSMS of your own?

Your account has a hosted CSMS that a simulated station can dial. Creating it makes it public at once: any chargepoint that has the address can connect with no credentials. Tell the account owner first, and switch it to private when you finish.

```bash
sim -X PUT https://api.simitnow.com/v2/csms -d '{}'
```

The reply carries `csmsId` and `wsHost`. Use `wss://csms.simitnow.com/ocpp/<csmsId>` as `CSMS_URL` (the station id is appended, as above). Messages to and from the hosted CSMS count against the same daily allowance. When you are done:

```bash
sim -X PUT https://api.simitnow.com/v2/csms -d '{"public":false}'
```

## 3. Create a station (OCPP 1.6)

```bash
sim -X PUT https://api.simitnow.com/v2/cs/ai-cp-1 -d "{\"csmsUrl\":\"$CSMS_URL\",\"protocol\":\"ocpp1.6\",\"connectorCount\":1,\"chargepointType\":\"AC\"}"
```

Expected: `HTTP 201` and `{"id": "ai-cp-1"}`. `protocol` is `ocpp1.6`, `ocpp2.0.1` or `ocpp2.1`. A 409 means the id is already in use; pick another. A 429 is a limit (see Errors).

The station sends BootNotification to your CSMS now, but the 201 does not wait for it. Poll until it is connected:

```bash
sim -X GET https://api.simitnow.com/v2/cs/ai-cp-1
```

Until the station has connected to your CSMS, this call answers `HTTP 409` with `"connectionStatus": "connecting"` and a `Retry-After` header (seconds): wait that long and ask again. So does every other call that names the station, such as plugging an EV in. A 409 whose body has a `quotaPause` object and no `Retry-After` means the daily allowance is used up and the station is held back until it resets. Once the call answers 200, wait for `network.connected` to be `true` and `connector["1"].status` to be `Available`. Status values are OCPP status names with a capital first letter (`Available`, `Preparing`, `Charging`). If that has not happened after 60 seconds, read your CSMS's logs: a station that cannot connect keeps retrying with a growing delay and never gives up, so a bad URL or password shows up here as a station that stays on 409 `connecting`. Delete it and fix the cause.

## 4. Create an EV, plug it in, charge (OCPP 1.6)

```bash
sim -X PUT https://api.simitnow.com/v2/ev/ai-ev-1 -d '{"batteryCapacity":60,"initialSoc":20}'
sim -X POST https://api.simitnow.com/v2/ev/ai-ev-1/connection -d '{"cbId":"ai-cp-1","connectorId":1}'
```

Both return success (201, then 200). The connector changes to `preparing` a moment later: poll `GET https://api.simitnow.com/v2/cs/ai-cp-1` until `connector["1"].status` is `Preparing`. Then start the transaction:

```bash
sim -X POST https://api.simitnow.com/v2/cs/ai-cp-1/transactions -d '{"connectorId":1}'
```

Save `transactionId` from the reply. A 400 `Connector not in preparing state` means you did not wait long enough. Watch the vehicle charge:

```bash
sim -X GET https://api.simitnow.com/v2/ev/ai-ev-1
```

`soc` should rise between two reads. Stop the transaction with the id you saved (here `1`):

```bash
sim -X DELETE https://api.simitnow.com/v2/cs/ai-cp-1/transactions/1
```

### Read the results

After the stop, read what the simulator recorded:

```bash
sim -X GET https://api.simitnow.com/v2/ev/ai-ev-1
sim -X GET https://api.simitnow.com/v2/cs/ai-cp-1
```

The vehicle's `soc` is the final state of charge. The station's `connector["1"].status` shows where the connector ended up, and `network.queuedMessages` of 0 means it holds no backlog. If `network.connected` stays `false` and a `network.quotaPause` object is present, the daily allowance is used up: the station stays disconnected until `quotaPause.until` (UTC). The dashboard's activity log lists every OCPP message of the run. What your CSMS stored (the transaction, its meter values, the final energy) can only be read on your CSMS: check it now, it should show a transaction that started and stopped with meter values in between.

### OCPP 2.0.1 and 2.1 differ

Create the station with `"protocol":"ocpp2.0.1"` (or `ocpp2.1`). Plugging the EV in already starts a transaction there, so do not call `transactions` afterwards: you would get a 409 with `runningTransactionId`. The station sends Authorize first and starts only if your CSMS answers Accepted; on a 400 `Authorize was not accepted` read `authorizationStatus`. That call waits up to 10 seconds for your CSMS, so give your HTTP client a timeout above 10 seconds. Create the station with `"authorizeLocalStart":false` to skip the Authorize step.

## 5. Break things on purpose

Fault a connector, then clear it:

```bash
sim -X POST https://api.simitnow.com/v2/cs/ai-cp-1/fault -d '{"connectorId":1,"errorCode":"GroundFailure"}'
sim -X DELETE https://api.simitnow.com/v2/cs/ai-cp-1/fault/1
```

A transaction running on that connector ends with the fault. Check that your CSMS raised the alert and closed the session, and that the connector returns to available after the reset. `errorCode` is an OCPP 1.6 ChargePointErrorCode such as `GroundFailure`, `HighTemperature`, `OverCurrentFailure` or `ReaderFailure`.

Drop the connection for 30 seconds, or until you reconnect:

```bash
sim -X POST https://api.simitnow.com/v2/cs/ai-cp-1/network/disconnect -d '{"duration":30}'
sim -X POST https://api.simitnow.com/v2/cs/ai-cp-1/network/connect
```

While it is offline the station keeps running and queues its transaction messages, then delivers them in order when the connection returns. `GET /v2/cs/ai-cp-1` shows `network.connected` and `network.queuedMessages`. A 409 on connect or disconnect means it was already in that state.

## 6. Clean up

Delete the vehicle, then the station. Do this in a `finally` block or a trap so a failed test still cleans up. A 404 means it is already gone.

```bash
sim -X DELETE https://api.simitnow.com/v2/ev/ai-ev-1
sim -X DELETE https://api.simitnow.com/v2/cs/ai-cp-1
```

## 7. The same flow in Python

```python
import os
import time

import requests

API = os.environ.get("SIMITNOW_API", "https://api.simitnow.com")
HEADERS = {"SIMITNOW-API-KEY": os.environ["SIMITNOW_API_KEY"]}
CSMS_URL = os.environ["CSMS_URL"]  # your CSMS, without the chargepoint id


def call(method, path, **kwargs):
    return requests.request(method, API + path, headers=HEADERS, timeout=30, **kwargs)


def wait_for(check, what, timeout=60):
    deadline = time.time() + timeout
    while time.time() < deadline:
        if check():
            return
        time.sleep(float(os.environ.get("POLL_SECONDS", "2")))
    raise TimeoutError(what)


def station():
    # The station's info, or None while it is still connecting (HTTP 409 with Retry-After).
    reply = call("GET", "/v2/cs/ai-cp-1")
    if reply.status_code == 409:
        return None
    reply.raise_for_status()
    return reply.json()


created = []
try:
    r = call("PUT", "/v2/cs/ai-cp-1", json={"csmsUrl": CSMS_URL, "protocol": "ocpp1.6"})
    r.raise_for_status()
    created.append("/v2/cs/ai-cp-1")
    wait_for(lambda: (info := station()) is not None and info["network"]["connected"], "station never connected")

    call("PUT", "/v2/ev/ai-ev-1", json={"batteryCapacity": 60, "initialSoc": 20}).raise_for_status()
    created.insert(0, "/v2/ev/ai-ev-1")
    call("POST", "/v2/ev/ai-ev-1/connection", json={"cbId": "ai-cp-1", "connectorId": 1}).raise_for_status()
    wait_for(lambda: (info := station()) is not None and info["connector"]["1"]["status"] == "Preparing",
             "connector never reached Preparing")

    r = call("POST", "/v2/cs/ai-cp-1/transactions", json={"connectorId": 1})
    r.raise_for_status()
    transaction_id = r.json()["transactionId"]

    first = call("GET", "/v2/ev/ai-ev-1").json()["soc"]
    wait_for(lambda: call("GET", "/v2/ev/ai-ev-1").json()["soc"] > first, "state of charge did not rise")

    call("DELETE", f"/v2/cs/ai-cp-1/transactions/{transaction_id}").raise_for_status()
    print("simulator side verified; now check the CSMS for the session")
finally:
    for path in created:
        call("DELETE", path)
```

## 8. Complete example: 10 stations, sessions, results, cleanup

This script creates 10 stations on OCPP 1.6, waits until each is connected to your CSMS, plugs in one vehicle per station, starts a session on each, checks that every vehicle charges, stops the sessions, prints a result per station and deletes everything, also when a step fails. It exits 0 only when all 10 sessions charged. Each station adds Heartbeats and meter values to your daily allowance: on a Free account run it with `STATIONS=2` first, and read the allowance rules above before a larger run.

```python
import os
import sys
import time

import requests

API = os.environ.get("SIMITNOW_API", "https://api.simitnow.com")
HEADERS = {"SIMITNOW-API-KEY": os.environ["SIMITNOW_API_KEY"]}
CSMS_URL = os.environ["CSMS_URL"]  # your CSMS, without the chargepoint id
COUNT = int(os.environ.get("STATIONS", "10"))
RUN = os.environ.get("RUN_ID", str(int(time.time())))
POLL = float(os.environ.get("POLL_SECONDS", "2"))

STATIONS = [f"ai-{RUN}-cp-{n}" for n in range(1, COUNT + 1)]
VEHICLES = [f"ai-{RUN}-ev-{n}" for n in range(1, COUNT + 1)]


def call(method, path, **kwargs):
    return requests.request(method, API + path, headers=HEADERS, timeout=30, **kwargs)


def wait_for(check, what, timeout=90):
    deadline = time.time() + timeout
    while time.time() < deadline:
        if check():
            return
        time.sleep(POLL)
    raise TimeoutError(what)


def station(cp):
    # The station's info, or None while it is still connecting (HTTP 409 with Retry-After).
    reply = call("GET", f"/v2/cs/{cp}")
    if reply.status_code == 409:
        return None
    reply.raise_for_status()
    return reply.json()


def soc(ev):
    return call("GET", f"/v2/ev/{ev}").json()["soc"]


created = []  # deleted in this order: vehicles before the stations they are plugged into
results = {}
all_charged = False
try:
    for cp in STATIONS:
        body = {"csmsUrl": CSMS_URL, "protocol": "ocpp1.6", "connectorCount": 1, "chargepointType": "AC"}
        call("PUT", f"/v2/cs/{cp}", json=body).raise_for_status()
        created.append(f"/v2/cs/{cp}")
    for cp in STATIONS:
        wait_for(lambda cp=cp: (info := station(cp)) is not None and info["network"]["connected"],
                 f"{cp} never connected to the CSMS")

    for cp, ev in zip(STATIONS, VEHICLES):
        call("PUT", f"/v2/ev/{ev}", json={"batteryCapacity": 60, "initialSoc": 20}).raise_for_status()
        created.insert(0, f"/v2/ev/{ev}")
        call("POST", f"/v2/ev/{ev}/connection", json={"cbId": cp, "connectorId": 1}).raise_for_status()
    for cp in STATIONS:
        wait_for(lambda cp=cp: (info := station(cp)) is not None and info["connector"]["1"]["status"] == "Preparing",
                 f"{cp} never reached Preparing")

    for cp, ev in zip(STATIONS, VEHICLES):
        reply = call("POST", f"/v2/cs/{cp}/transactions", json={"connectorId": 1})
        reply.raise_for_status()
        results[cp] = {"ev": ev, "transactionId": reply.json()["transactionId"], "soc_start": soc(ev)}
    for cp, result in results.items():
        wait_for(lambda r=result: soc(r["ev"]) > r["soc_start"], f"{cp}: the vehicle did not charge")

    for cp, result in results.items():
        call("DELETE", f"/v2/cs/{cp}/transactions/{result['transactionId']}").raise_for_status()
        result["soc_end"] = soc(result["ev"])
        result["queued"] = station(cp)["network"]["queuedMessages"]

    print("station | transactionId | soc start -> end | queued messages")
    for cp, r in results.items():
        print(f"{cp} | {r['transactionId']} | {r['soc_start']} -> {r['soc_end']} | {r['queued']}")
    all_charged = len(results) == COUNT and all(r["soc_end"] > r["soc_start"] for r in results.values())
    print("simulator side:", "all sessions charged" if all_charged else "NOT all sessions charged")
    print("Now check your CSMS: it should list one finished transaction per station.")
finally:
    for path in created:
        call("DELETE", path)
sys.exit(0 if all_charged else 1)
```

The simulator side is verified when the script exits 0. Your CSMS side is not: compare its transaction list with the table above before you report a pass.

## Errors

Errors are JSON, `{"error": "..."}`, with an HTTP status.

| Status | Meaning | What to do |
|---|---|---|
| 400 | A field is invalid, the connector is not ready, the CSMS did not accept Authorize (2.x) or the fault code is invalid | Read `error`; fix the request or wait for the state, then retry once |
| 401 | `API key missing` or `Invalid API key` | Stop; ask for a valid key |
| 404 | The station or EV does not exist, or the path is not a real operation. The body says `Simulator ... not found` or `No cs simulator found` | Check the id, the path against the spec and that the key is the account that created it |
| 409 | The id exists, the thing is already connected or disconnected, on 2.x a transaction is already running on the EVSE, or the station is still connecting (`connectionStatus` `connecting`, with `Retry-After`) | Use another id, read the state first, or wait for `Retry-After` and ask again |
| 429 | `quota_exceeded` (today's allowance is used up, resets 00:00 UTC), `Charging station limit reached` (Free safety limit) or `probe rate limit exceeded (20/min)` | Do not retry in a loop. Stop, delete what you do not need, or tell the account owner |
| 502 | 2.x only: your CSMS did not answer Authorize within 10 seconds | Fix the CSMS, or create the station with `authorizeLocalStart` false |
| 503 | The station is offline and its queue is full, or the service is busy. The busy case sends `Retry-After` | Wait, then retry with a growing delay |

## More

- Full API reference: https://www.simitnow.com/docs
- Machine-readable spec: https://www.simitnow.com/openapi.json
- Overview and limits: https://www.simitnow.com/llms.txt
- Seven scenarios (100 stations, OCPP 2.0.1, concurrency, reconnection, CI, OCPI, remote start): https://www.simitnow.com/ai-use-cases.md
- Hosted CSMS (your chargepoint dials in): https://www.simitnow.com/docs#csms-create
- OCPI 2.2.1 for eMSP testing: https://www.simitnow.com/docs#ocpi
- Release notes: https://www.simitnow.com/news
- Support: info@simitnow.com
