Developers

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

  1. On your host dashboard, set I’m hosting as to a business and verify your email.
  2. Open Business API and create a key. Copy it straight away — it’s shown only once.
  3. 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.

ScopeAllows
stations:readRead listings
stations:writeCreate, 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 in data; deletes return 204 No Content.
  • Prices are US dollars as plain numbers (3.5 = $3.50). Timestamps are ISO 8601 UTC; HH:mm times and blocked dates are in the station’s own timeZone, which VoltShare derives from lat/lng.
  • Unknown fields are rejected with unknown_field rather than ignored, so a typo never silently does nothing.
  • PATCH changes only the fields you send; null clears 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" }
    ]
  }
}
StatusCodeMeaning
400invalid_jsonThe body isn’t a JSON object.
400unknown_fieldThe body has a field the API doesn’t know (often a typo).
400validation_errorA field is missing or invalid — see details.
400invalid_cursorThe pagination cursor isn’t from this listing.
401invalid_api_keyNo key, a malformed key, or a key that doesn’t exist.
401api_key_revokedThe key was revoked on the dashboard.
401api_key_expiredThe key passed its expiry date.
403business_account_requiredThe account is no longer set up as a business host.
403insufficient_scopeThe key lacks the scope this endpoint needs.
403email_unverifiedVerify the account’s email before publishing chargers.
403host_agreement_requiredA new Host Agreement version needs accepting on the dashboard.
404not_foundNo such station on this account.
409station_has_bookingsA station that was ever booked can’t be deleted — pause it with isActive: false.
429rate_limitedToo 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.

FieldTypeNotes
titlerequiredstring
descriptionstring | null
addressrequiredstring
staterequiredstringTwo-letter US state code of the address. Two-letter US state code.
latrequirednumber
lngrequirednumber
connectorTyperequiredstringOne of J1772, CCS, CHAdeMO, NEMA_14_50, TESLA.
maxPowerKwrequirednumber
pricePerHournumber | nullBase rate in USD per hour. Set exactly one of pricePerHour / pricePerKwh.
pricePerKwhnumber | nullBase rate in USD per kWh. Only accepted in states cleared for per-kWh resale; elsewhere the request fails with validation_error — use pricePerHour.
surgeMultipliernumber | nullPeak-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.
surgeDaysinteger[]Peak days, 0 = Sunday … 6 = Saturday.
surgeStartTimestring | nullPeak start, station local time.
surgeEndTimestring | nullPeak end, station local time.
accessTypestringINSTANT books immediately; REQUEST waits for you to approve each booking. One of INSTANT, REQUEST. Default INSTANT.
locationTypestringOne of DRIVEWAY, CURBSIDE, PARKING_LOT, GARAGE, OTHER. Default DRIVEWAY.
isActivebooleanWhether the listing is live on the map. false pauses it. Default true.
photosstring[]Public https image URLs.
communityRulesstring[]Rules shown to drivers before and during a session.

Endpoint reference

Account

  • GET/meCheck a keyany key

    Who the key belongs to and its scopes. Any valid key may call it.

Stations

  • GET/stationsList stationsstations:read

    Your stations, newest first. Pass the previous page’s nextCursor as cursor until it is null.

  • POST/stationsCreate a stationstations:write

    List 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:write

    Send only the fields to change; null clears an optional field. Setting one price clears the other. Pause or publish with isActive.

  • DELETE/stations/{id}Delete a stationstations:write

    Permanent, and unlinks its charger. Only stations that were never booked can be deleted; otherwise you get 409 station_has_bookings — PATCH isActive: false to take it off the map instead.

Availability

  • GET/stations/{id}/availabilityGet hoursstations:read

    data is null when the station is bookable at any time.

  • PUT/stations/{id}/availabilitySet hoursstations:write

    Replace the weekly hours and blocked dates.

  • DELETE/stations/{id}/availabilityClear hoursstations:write

    Remove 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]