cxk_test_…). Everything a sandbox key does happens in the sandbox; everything a live key (cxk_live_…) does happens on your real fleet. The base URL is the same for both.
Test and live keys
You choose the environment for each key when you create it: in the fleet portal open Developers → API keys → Create key and pick Live or Sandbox.
Sandbox and live are fully separate. Keys, webhook endpoints, events, drivers, vehicles, sessions and the wallet belong to one environment and are never visible from the other. A webhook endpoint registered with a sandbox key only receives sandbox events, and the portal’s Developers pages show one environment at a time.
The sandbox is created the first time you use a sandbox key, so your first request may take a moment longer.
What’s in the sandbox
A new sandbox (and one you’ve just reset) starts with:- Three drivers, Ama Owusu, Kwame Asante and Efua Mensah (employee ids
SBX-001toSBX-003). They have placeholder emails ending in@sandbox.chargeexpress.invalid, no logins, and are never emailed. - A vehicle for each driver, registered
SBX 1001-26,SBX 1002-26andSBX 1003-26. - A wallet with 500 kWh of prepaid credit and a GHS 1,000 credit limit, at your live fleet’s unit price.
POST /drivers also get a placeholder email, whatever address you send, and the response has login_invite.status: "skipped":
GET /fleet in the sandbox returns the sandbox copy of your fleet. Its id is different from your live fleet’s id, and it is the fleet_id on sandbox events.
Simulated chargers
With a sandbox key,GET /charge_points returns only these four charge points, each with simulated: true. Real chargers can’t be reached from the sandbox: sandbox sessions only run on these four.
How the simulation runs
Sandbox sessions run on a simulated meter at 60 times real time:- Energy delivered is
max_power_kw × elapsed time × 60, capped by the session’s budget. One real second is one simulated minute, soSIM-DC-60delivers 1 kWh per second andSIM-AC-22about 0.37 kWh per second. - A server job advances every running sandbox session every 5 seconds, so meter readings and events move in 5-second steps.
- A session ends on its own after about 2 minutes (two simulated hours), or as soon as it reaches its budget, whichever comes first.
- When a session reaches its budget you get
session.stopped_by_limitwithstop_reason: "budget_reached", thensession.completed. - Stopping a session in the portal ends it right away and settles the energy delivered so far.
limit.reached events all behave as they do live. Sandbox session ids look like sim_17918822001234567; treat them as opaque, like every id.
Walkthrough
This walkthrough lists the simulated chargers, runs a session onSIM-DC-60 until it hits a 10 kWh cap, and tops up the wallet. It needs a sandbox key with fleet:read and wallet:write, and a webhook endpoint subscribed to session.* and wallet.* (register one with the sandbox key as in the Quickstart).
1
List the charge points
Response
2
Start a session with a 10 kWh cap
In the fleet portal, open Developers → Sandbox and choose Start session. Pick a driver and their vehicle, charge point
SIM-DC-60, connector 1, and a max kWh of 10.The simulated charger starts at once, so session.started (data.object.status: "charging") is sent to your endpoint straight away. Note the session id from data.object.id, or find it with GET /sessions?status=charging.3
Watch it hit the cap
At 1 kWh per second, the session reaches 10 kWh after about 10 seconds and is stopped at the next 5-second step. Your endpoint receives followed by
session.stopped_by_limit:session.stopped_by_limit data.object
session.completed with status: "completed" and the final energy_kwh and cost. The 10 kWh are debited from the sandbox wallet. You can also poll GET /sessions/{sessionId}.To end a session early instead, select it in Developers → Sandbox and choose Stop. In the sandbox it settles immediately, and session.completed follows.4
Create a top-up
Response (201)
checkout_url is null and sandbox is true. The funding stays pending until you settle it.5
Mark it paid
status: "success". The wallet is credited (any credit used is repaid first, as live) and wallet.funded is sent. To simulate a failed payment, call /fail instead: the funding becomes failed and wallet.funding_failed is sent. Settling a funding that is no longer pending returns 409 funding_not_pending.POST /invoices/usage/pay with a sandbox key returns a pending funding with checkout_url: null and sandbox: true, which you settle with /sandbox/fundings/{fundingId}/succeed or /fail.
Both settle endpoints need the wallet:write scope.
Test failure paths
Start sessions on the two misbehaving chargers from Developers → Sandbox to exercise your error handling:SIM-OFFLINEis never connected, so no session starts and nothing is reserved.SIM-REJECTrejects the start: the wallet reservation is released andsession.start_failedcarries the session withstatus: "cancelled", as when a real charger rejects a start.
PUT /limits to get limit.reached, or fail a top-up to get wallet.funding_failed.
Send test events of any type
POST /webhook_endpoints/{endpointId}/test sends one signed event to an endpoint straight away. Pass type to send any event type with a realistic sample payload; without it you get a ping:
Response
invoice.created or invitation.accepted. Test events aren’t stored, retried or listed in /events, and they work with live keys too.
Reset the sandbox
POST /sandbox/reset clears the sandbox’s sessions, top-ups, drivers, vehicles, limits and events, then seeds fresh drivers, vehicles and wallet credit. Your keys and webhook endpoints are kept, so your integration keeps working.
Response
In the portal
Developers → Sandbox in the fleet portal does all of this without code: see the sandbox’s drivers, vehicles, wallet and recent activity, start and stop sessions on the simulated chargers, simulate paid or failed top-ups, and reset the sandbox.What the sandbox never touches
- Billing: nothing in the sandbox is billed. Sandbox credit and debt are not real money.
- Invoicing: sandbox sessions and top-ups are never invoiced, so
invoice_numberstaysnullandinvoice.createdisn’t sent for sandbox activity. - Payments: sandbox top-ups and invoice payments never reach ExpressPay.
- Real chargers: sandbox sessions never send commands to a charger.
- Real people: sandbox drivers have no logins and are never emailed.
/sandbox/* endpoints only work with a sandbox key. With a live key they return 403 sandbox_only.
Going live
When your integration works in the sandbox, create a live key in the production fleet portal and swap it in. Then register your webhook endpoints again with the live key (sandbox endpoints don’t receive live events), and checklivemode in your webhook handler if the same server receives both.
