Developers

The Plug n Drive API

Read your sites, chargers, charging sessions and drivers from your own tools: CRM, accounting, spreadsheets or loyalty programmes. REST and JSON, with one key per integration.

Read-onlyv1
60 / minuteper key
Bearer keyauthentication
JSONUTC timestamps
Integrations

Connect the API to the tools your team already uses

Pipe sessions and drivers into your CRM, marketing and accounting tools, and reward drivers through loyalty programmes. The API is read-only today. We build the connection with you.

Plug n Drive connects to CRM, marketing, accounting and loyalty programmes through its read-only REST API and custom integrations built with each client.

CRM and sales: Salesforce, HubSpot, Microsoft Dynamics 365, Zoho CRM. Marketing: Mailchimp. Accounting: Xero, Sage. Rewards and loyalty: Pick n Pay Smart Shopper, Checkers Xtra Savings, Discovery Vitality, Clicks ClubCard, Woolworths WRewards, FNB eBucks.

All product names, logos and brands are the property of their respective owners. Plug n Drive is not affiliated with, endorsed or sponsored by any of them. They illustrate the kinds of systems a custom integration can target.

Quick start

You can make your first call in three minutes.

  1. Create a key. Sign in to the Plug n Drive console as a client administrator, open API & Keys and choose Create key. Give it the name of the tool that will use it. Copy the key straight away: it is shown only once.
  2. Call an endpoint. Send the key in the Authorization header.
  3. Read the result. Every list comes back as {"data": [...], "next_cursor": ...}.
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://ozlnxhtguaoeuhjeejxq.supabase.co/functions/v1/api-v1/v1/sessions?from=2026-10-01&limit=50"
const res = await fetch("https://ozlnxhtguaoeuhjeejxq.supabase.co/functions/v1/api-v1/v1/sessions?from=2026-10-01&limit=50", {
  headers: { Authorization: `Bearer ${process.env.PND_API_KEY}` },
});
if (!res.ok) throw new Error(`${res.status} ${(await res.json()).error.message}`);
const { data, next_cursor } = await res.json();
console.log(data.length, "sessions, next page:", next_cursor);
import os, requests

BASE = "https://ozlnxhtguaoeuhjeejxq.supabase.co/functions/v1/api-v1"
headers = {"Authorization": f"Bearer {os.environ['PND_API_KEY']}"}

sessions, cursor = [], None
while True:
    params = {"from": "2026-10-01", "limit": 200}
    if cursor:
        params["cursor"] = cursor
    r = requests.get(f"{BASE}/v1/sessions", headers=headers, params=params, timeout=30)
    r.raise_for_status()
    body = r.json()
    sessions += body["data"]
    cursor = body["next_cursor"]
    if not cursor:
        break

print(len(sessions), "sessions")

Keep your key on a server. Never put it in a web page, a mobile app or a public repository. If a key is exposed, revoke it in the console and create a new one. It takes effect immediately.

Base URL

https://ozlnxhtguaoeuhjeejxq.supabase.co/functions/v1/api-v1

All endpoints below are relative to this address and start with /v1. Keep the base URL in one configuration setting in your code, so a future change of address is a one-line update.

Authentication

Send your API key as a bearer token:

Authorization: Bearer pnd_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

You can also send it in an X-API-Key header. Keys begin with pnd_live_.

  • A key belongs to one client and only ever returns that client's data.
  • Keys are read-only. Every request must be a GET.
  • Create one key per tool (for example "CRM" and "Accounting"), so you can revoke one without affecting the other.
  • You can have up to 10 active keys. The console shows when each key was last used.

A missing, malformed or revoked key returns 401:

{
  "error": {
    "code": "invalid_api_key",
    "message": "Missing or invalid API key."
  }
}

Conventions

  • Format. Responses are JSON. Timestamps are ISO 8601 in UTC. Amounts are decimal numbers in the currency named in the same record, such as ZAR or MZN.
  • Identifiers. Ids are UUID strings and stay stable. Use them to link records, for example site_id on a charger or driver_id on a session.
  • Dates. from and to accept 2026-10-01 or a full timestamp such as 2026-10-01T00:00:00Z. from is inclusive and to is exclusive.
  • Missing values are returned as null.

Pagination

List endpoints return up to limit records (1 to 200, default 100). When more records exist, the response contains a next_cursor. Pass it back as cursor to get the next page. When next_cursor is null, you have everything.

GET /v1/sessions?from=2026-10-01&limit=200
GET /v1/sessions?from=2026-10-01&limit=200&cursor=2026-10-08T15:26:31.382851Z_9d3f5b1a-6c84-47e2-b0a9-2f7e1c4d8a63

Treat the cursor as an opaque value: copy it as it is, and keep the same filters between pages. Sessions are returned oldest first, so you can store the last cursor and continue from it later.

Sites

GET/v1/sites

Your sites, including sub-sites such as buildings or parking areas (parent_id links a sub-site to its parent).

Parameters

NameTypeDescription
limitinteger1 to 200. Default 100.
cursorstringFrom a previous next_cursor.

Fields

FieldTypeDescription
iduuidSite id.
namestringSite name.
parent_iduuid | nullParent site, or null for a top-level site.
typestringFor example Building or Parking.
citystring | null
addressstring | null
currencystringCurrency used for this site's tariffs and sessions.
timezonestringIANA time zone, for example Africa/Johannesburg.
statusstringActive, Pending install or Inactive.
latitude / longitudenumber | nullDecimal degrees.
capacity_kvanumber | nullGrid connection capacity.

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://ozlnxhtguaoeuhjeejxq.supabase.co/functions/v1/api-v1/v1/sites"
{
  "data": [
    {
      "id": "6f1c2a9e-3b7d-4d52-9a0e-1c8b5e7f2a41",
      "name": "Example Complex - Block A",
      "parent_id": null,
      "type": "Building",
      "city": "Sandton",
      "address": "1 Example Road, Sandton, 2196",
      "currency": "ZAR",
      "timezone": "Africa/Johannesburg",
      "status": "Active",
      "latitude": -26.107,
      "longitude": 28.056,
      "capacity_kva": 400
    }
  ],
  "next_cursor": null
}

Chargers

GET/v1/chargers

Your chargers and their current status.

Parameters

NameTypeDescription
site_iduuidOnly chargers of this site.
limitinteger1 to 200. Default 100.
cursorstringFrom a previous next_cursor.

Fields

FieldTypeDescription
iduuidCharger id.
namestring
site_iduuidThe site this charger belongs to.
power_kwnumberRated power in kW.
connectorstring | nullFor example Type 2 or CCS2.
statusstringFor example Available, Charging or Offline.
last_seen_attimestamp | nullLast time the charger contacted the platform.
listed_publiclybooleanWhether the charger appears on the public map.

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://ozlnxhtguaoeuhjeejxq.supabase.co/functions/v1/api-v1/v1/chargers?site_id=6f1c2a9e-3b7d-4d52-9a0e-1c8b5e7f2a41"
{
  "data": [
    {
      "id": "b2e8d4f6-71a3-4c95-8e10-5d9a3c7b1f20",
      "name": "A1 Fast 180 kW",
      "site_id": "6f1c2a9e-3b7d-4d52-9a0e-1c8b5e7f2a41",
      "power_kw": 180,
      "connector": "CCS2",
      "status": "Available",
      "last_seen_at": "2026-10-09T08:41:12.512+00:00",
      "listed_publicly": true
    }
  ],
  "next_cursor": null
}

Sessions

GET/v1/sessions

Charging sessions on your chargers, oldest first. Use it to feed accounting tools, reports or dashboards.

Parameters

NameTypeDescription
fromdateSessions that started on or after this moment.
todateSessions that started before this moment.
site_iduuidOnly sessions at this site.
limitinteger1 to 200. Default 100.
cursorstringFrom a previous next_cursor.

Fields

FieldTypeDescription
iduuidSession id.
started_at / ended_attimestampStart and end of the session.
duration_secondsnumberLength of the session.
kwhnumberEnergy delivered.
gross_amountnumberAmount charged for the session.
net_amountnumberYour share after the Plug n Drive commission, as shown in the Reporting page of the console.
currencystringCurrency of both amounts.
payment_methodstringHow the session was paid, for example RFID card.
charger_id / charger_nameuuid / string
site_id / site_nameuuid / string
driver_iduuid | nullLinks to id in /v1/drivers.

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://ozlnxhtguaoeuhjeejxq.supabase.co/functions/v1/api-v1/v1/sessions?from=2026-10-01&limit=1"
{
  "data": [
    {
      "id": "9d3f5b1a-6c84-47e2-b0a9-2f7e1c4d8a63",
      "started_at": "2026-10-08T15:26:31.382851+00:00",
      "ended_at": "2026-10-08T17:39:09.277588+00:00",
      "duration_seconds": 7957.89,
      "kwh": 21,
      "gross_amount": 100.8,
      "net_amount": 93.74,
      "currency": "ZAR",
      "payment_method": "RFID card",
      "charger_id": "b2e8d4f6-71a3-4c95-8e10-5d9a3c7b1f20",
      "charger_name": "A1 Fast 180 kW",
      "site_id": "6f1c2a9e-3b7d-4d52-9a0e-1c8b5e7f2a41",
      "site_name": "Example Complex - Block A",
      "driver_id": "e4a7c1d9-52b8-4f36-a1d0-8c3e6b9f7a25"
    }
  ],
  "next_cursor": "2026-10-08T15:26:31.382851Z_9d3f5b1a-6c84-47e2-b0a9-2f7e1c4d8a63"
}

Drivers

GET/v1/drivers

People who completed at least one session on your chargers, with totals over the period. Contact details are included only when the driver agreed to share them with you. See Personal data.

Parameters

NameTypeDescription
fromdateCount sessions that started on or after this moment.
todateCount sessions that started before this moment.
limitinteger1 to 200. Default 100.
cursorstringFrom a previous next_cursor.

Fields

FieldTypeDescription
iduuidDriver id, the same value as driver_id on sessions.
namestring
typestringapp_customer for drivers who use the Plug n Drive app or website, cardholder for people with an RFID card issued for your site.
sessionsintegerNumber of completed sessions in the period.
kwh_totalnumberEnergy delivered in the period.
spentobjectAmount charged in the period, per currency, for example {"ZAR": 885.6}.
first_session_at / last_session_attimestampFirst and last session in the period.
marketing_consentboolean | nulltrue or false for app customers, according to their choice for your organisation. null for cardholders.
email / phonestring | nullPresent only when allowed (see below), otherwise null.

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://ozlnxhtguaoeuhjeejxq.supabase.co/functions/v1/api-v1/v1/drivers?from=2026-08-01"
{
  "data": [
    {
      "id": "e4a7c1d9-52b8-4f36-a1d0-8c3e6b9f7a25",
      "name": "Jane Example",
      "type": "app_customer",
      "sessions": 12,
      "kwh_total": 184.5,
      "spent": {
        "ZAR": 885.6
      },
      "first_session_at": "2026-08-12T17:02:44+00:00",
      "last_session_at": "2026-10-08T15:26:31+00:00",
      "marketing_consent": true,
      "email": "jane@example.com",
      "phone": "+27 82 000 0000"
    },
    {
      "id": "a1b2c3d4-0000-4000-8000-0123456789ab",
      "name": "John Example",
      "type": "app_customer",
      "sessions": 3,
      "kwh_total": 41.2,
      "spent": {
        "ZAR": 197.8
      },
      "first_session_at": "2026-09-30T18:10:00+00:00",
      "last_session_at": "2026-10-05T18:40:00+00:00",
      "marketing_consent": false,
      "email": null,
      "phone": null
    }
  ],
  "next_cursor": null
}

Personal data

Drivers' details belong to the drivers, and your use of them is covered by data protection law, including POPIA in South Africa. The API is designed around that.

  • Contact details are gated. For app customers, email and phone are returned only if the driver agreed to receive offers and news from your organisation. Otherwise they are null.
  • Cardholders issued by you, such as residents or staff with an RFID card, are your own contacts, so their details are returned.
  • Consent is per organisation. A driver who accepted offers from one site operator has not accepted them from another.
  • You must honour changes. Drivers can switch their choice off at any time. Re-read marketing_consent before each campaign instead of keeping an old copy.
  • Never returned: wallet balances, saved payment cards and payment details.

This page describes how the API behaves. It is not legal advice. Your own privacy notice and your agreement with Plug n Drive set out what you may do with the data.

Errors

Errors use the usual HTTP status codes and a JSON body with a stable code and a readable message:

{
  "error": {
    "code": "invalid_api_key",
    "message": "Missing or invalid API key."
  }
}
StatusCodeMeaning
400invalid_parameterA parameter is malformed, for example limit=500 or a date that does not exist. The message says which one.
401invalid_api_keyThe key is missing, malformed, unknown or revoked.
404not_foundUnknown endpoint.
405method_not_allowedOnly GET is supported.
429rate_limitedToo many requests. Wait for the number of seconds in the Retry-After header.
500server_errorSomething went wrong on our side. Try again in a moment, and contact us if it persists.

Limits

LimitValue
Requests60 per minute per key
Page size200 records at most
Active keys10 per organisation
MethodsGET only

For regular synchronisation, fetch with limit=200, store the last next_cursor, and ask only for what is new with from. A schedule of every few minutes is far below the limit.

Changelog

v1, 9 October 2026

  • First release: /v1/sites, /v1/chargers, /v1/sessions and /v1/drivers.
  • Read-only access with one key per integration, managed in the console under API & Keys.

We add fields without notice, so ignore fields you do not know. Changes that could break an integration will arrive in a new version, with advance notice.

Need something else? Webhooks, write access and other endpoints are on our list. Tell us what you would build at hello@plugndrive.co.za.