Business API
Run a garage, office, apartment building or fleet depot? Keep your VoltShare listings in sync with your own systems: create chargers, change prices and hours, pause and delete them — all over a small JSON REST API.
Base URL https://voltshare.app/api/business/v1 · Version v1 · OpenAPI 3.1 spec
Quick start
- On your host dashboard, set I’m hosting as to a business and verify your email.
- Open Business API and create a key. Copy it straight away — it’s shown only once.
- Check it works:
curl https://voltshare.app/api/business/v1/me \
-H "Authorization: Bearer $VOLTSHARE_API_KEY"Authentication
Send your key as a bearer token on every request: Authorization: Bearer vsk_live_…. Keys start with vsk_live_, so they’re easy to spot if one leaks into a log or a repository.
A key acts as your business account, on every listing it owns. Keep keys on your servers — never ship one in a browser, mobile app or public repository. VoltShare stores only a hash of each key, so nobody (including us) can show it to you again.
| Scope | Allows |
|---|---|
| stations:read | Read listings |
| stations:write | Create, edit and delete listings |
Give read-only reporting jobs just stations:read. GET /me works with any valid key.
Managing keys
Keys are managed on the Business API page of the host dashboard:
- Create — name the key, pick its scopes, and optionally have it expire after 30, 90 or 365 days. An account can hold 10 active keys; give each integration its own.
- Revoke — the key stops working immediately and stays listed, with its last-used time, for your records.
- Delete — removes the key for good. Deleting an active key also stops it working.
To rotate a key, create the new one, deploy it, then revoke the old one. If the account is switched back to an individual host, its keys stop working until it’s a business again.
Requests & responses
- Send JSON with
Content-Type: application/json. Successful responses wrap the result indata; deletes return204 No Content. - Prices are US dollars as plain numbers (
3.5= $3.50). Timestamps are ISO 8601 UTC;HH:mmtimes and blocked dates are in the station’s owntimeZone, which VoltShare derives fromlat/lng. - Unknown fields are rejected with
unknown_fieldrather than ignored, so a typo never silently does nothing. PATCHchanges only the fields you send;nullclears an optional field.
Errors
Errors use standard HTTP status codes and one JSON shape. Branch on code — it’s stable; message is for people and may change.
HTTP/1.1 400 Bad Request
{
"error": {
"code": "validation_error",
"message": "Set a price per kWh or per hour, not both",
"details": [
{ "path": "pricePerKwh", "message": "Set a price per kWh or per hour, not both" }
]
}
}| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_json | The body isn’t a JSON object. |
| 400 | unknown_field | The body has a field the API doesn’t know (often a typo). |
| 400 | validation_error | A field is missing or invalid — see details. |
| 400 | invalid_cursor | The pagination cursor isn’t from this listing. |
| 401 | invalid_api_key | No key, a malformed key, or a key that doesn’t exist. |
| 401 | api_key_revoked | The key was revoked on the dashboard. |
| 401 | api_key_expired | The key passed its expiry date. |
| 403 | business_account_required | The account is no longer set up as a business host. |
| 403 | insufficient_scope | The key lacks the scope this endpoint needs. |
| 403 | email_unverified | Verify the account’s email before publishing chargers. |
| 403 | host_agreement_required | A new Host Agreement version needs accepting on the dashboard. |
| 404 | not_found | No such station on this account. |
| 409 | station_has_bookings | A station that was ever booked can’t be deleted — pause it with isActive: false. |
| 429 | rate_limited | Too many requests — wait Retry-After seconds. |
Rate limits & pagination
Each key may make 120 requests per minute. Past that you get 429 rate_limited with a Retry-After header (seconds) — wait, then retry.
GET /stations returns up to limit stations (default 50, max 100), newest first, plus a nextCursor. Pass it back as cursor to get the next page; it’s null on the last one.
Station fields
These are the fields you can send when creating (POST) or updating (PATCH) a station; responses return the same names plus read-only id, timeZone, status, availability, charger, createdAt and updatedAt. Listing rules are the same as on the dashboard: set exactly one price, publishing needs a verified email and the current Host Agreement (accepted when you create a key), and photos are public https URLs.
Pricing per kWh is only available where electricity resale by the kWh is permitted; everywhere else, use pricePerHour.
| Field | Type | Notes |
|---|---|---|
| titlerequired | string | |
| description | string | null | |
| addressrequired | string | |
| staterequired | string | Two-letter US state code of the address. Two-letter US state code. |
| latrequired | number | |
| lngrequired | number | |
| connectorTyperequired | string | One of J1772, CCS, CHAdeMO, NEMA_14_50, TESLA. |
| maxPowerKwrequired | number | |
| pricePerHour | number | null | Base rate in USD per hour. Set exactly one of pricePerHour / pricePerKwh. |
| pricePerKwh | number | null | Base rate in USD per kWh. Only accepted in states cleared for per-kWh resale; elsewhere the request fails with validation_error — use pricePerHour. |
| surgeMultiplier | number | null | Peak-hours factor on the base rate (e.g. 1.5). Surge is all-or-nothing: a multiplier above 1 needs surgeDays, surgeStartTime and surgeEndTime. On PATCH, null turns surge off and clears the window. |
| surgeDays | integer[] | Peak days, 0 = Sunday … 6 = Saturday. |
| surgeStartTime | string | null | Peak start, station local time. |
| surgeEndTime | string | null | Peak end, station local time. |
| accessType | string | INSTANT books immediately; REQUEST waits for you to approve each booking. One of INSTANT, REQUEST. Default INSTANT. |
| locationType | string | One of DRIVEWAY, CURBSIDE, PARKING_LOT, GARAGE, OTHER. Default DRIVEWAY. |
| isActive | boolean | Whether the listing is live on the map. false pauses it. Default true. |
| photos | string[] | Public https image URLs. |
| communityRules | string[] | Rules shown to drivers before and during a session. |
Endpoint reference
Account
- GET
/meCheck a keyany keyWho the key belongs to and its scopes. Any valid key may call it.
Stations
- GET
/stationsList stationsstations:readYour stations, newest first. Pass the previous page’s
nextCursorascursoruntil it isnull. - POST
/stationsCreate a stationstations:writeList a new charger. Geocode the address yourself and send
lat/lng; the timezone is derived from them. Requires a verified email and the current Host Agreement. - GET
/stations/{id}Get a stationstations:read - PATCH
/stations/{id}Update a stationstations:writeSend only the fields to change;
nullclears an optional field. Setting one price clears the other. Pause or publish withisActive. - DELETE
/stations/{id}Delete a stationstations:writePermanent, and unlinks its charger. Only stations that were never booked can be deleted; otherwise you get
409 station_has_bookings— PATCHisActive: falseto take it off the map instead.
Availability
- GET
/stations/{id}/availabilityGet hoursstations:readdataisnullwhen the station is bookable at any time. - PUT
/stations/{id}/availabilitySet hoursstations:writeReplace the weekly hours and blocked dates.
- DELETE
/stations/{id}/availabilityClear hoursstations:writeRemove the hours rule so the station is bookable at any time.
Examples
Create a station
curl -X POST https://voltshare.app/api/business/v1/stations \
-H "Authorization: Bearer $VOLTSHARE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Level 2 · Bay 4",
"address": "500 Market St, San Francisco, CA 94105",
"state": "CA",
"lat": 37.7897,
"lng": -122.3972,
"connectorType": "J1772",
"maxPowerKw": 7.4,
"pricePerHour": 3.5,
"locationType": "PARKING_LOT",
"accessType": "INSTANT"
}'HTTP/1.1 201 Created
Location: /api/business/v1/stations/clx9n2k4d0001qz8r5h7w3y1b
{
"data": {
"id": "clx9n2k4d0001qz8r5h7w3y1b",
"title": "Level 2 · Bay 4",
"timeZone": "America/Los_Angeles",
"pricePerHour": 3.5,
"pricePerKwh": null,
"isActive": true,
"status": "available",
"availability": null,
"charger": null,
…
}
}Update prices, or pause a listing
# Raise the rate and add weekday peak pricing
curl -X PATCH https://voltshare.app/api/business/v1/stations/$STATION_ID \
-H "Authorization: Bearer $VOLTSHARE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"pricePerHour": 4,
"surgeMultiplier": 1.5,
"surgeDays": [1, 2, 3, 4, 5],
"surgeStartTime": "17:00",
"surgeEndTime": "20:00"
}'
# Pause the listing (take it off the map)
curl -X PATCH https://voltshare.app/api/business/v1/stations/$STATION_ID \
-H "Authorization: Bearer $VOLTSHARE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "isActive": false }'Set opening hours
curl -X PUT https://voltshare.app/api/business/v1/stations/$STATION_ID/availability \
-H "Authorization: Bearer $VOLTSHARE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"dayOfWeek": [1, 2, 3, 4, 5],
"startTime": "07:00",
"endTime": "19:00",
"blockedDates": ["2026-12-25"]
}'List every station (Node.js)
const BASE = 'https://voltshare.app/api/business/v1';
const headers = {
Authorization: `Bearer ${process.env.VOLTSHARE_API_KEY}`,
'Content-Type': 'application/json',
};
// Walk every page of your stations.
async function listAllStations() {
const all = [];
let cursor = null;
do {
const url = new URL(`${BASE}/stations`);
url.searchParams.set('limit', '100');
if (cursor) url.searchParams.set('cursor', cursor);
const res = await fetch(url, { headers });
if (!res.ok) throw new Error((await res.json()).error.message);
const page = await res.json();
all.push(...page.data);
cursor = page.nextCursor;
} while (cursor);
return all;
}Questions or a feature you need? [email protected]