Overview

The PEV Registry API is provided by PEV Connection, LLC and lets approved repair shops check whether a personal electric vehicle (e-bike, scooter, EUC, skateboard, etc.) has been registered, and whether it has been reported lost or stolen, by its serial number or motor code.

Base URL:

https://pevregistry.com/api/v1

Authentication

Every request must include your API key as a bearer token. Shop accounts are created only through the website at /portal — your business name, address, and contact info are required, and your contact email must be verified before the account is created. Account creation is not available as a public API call; it must be done through the Shop Portal UI. After verification, an API key is issued for partner API integrations.

Authorization: Bearer pevreg_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Keep your API key secret — it is only ever shown once, at creation. If it is compromised or lost, generate a new one from the Shop Portal.

You can view and update your business info, view your API key credentials, or generate a new key any time by signing in at /portal with your contact email and password.

SDKs

A Node.js SDK is published on npm as @pevregistry/sdk, and a PHP SDK is published on Packagist as pevregistry/sdk. Both cover lookup, possession reports, service logs, photo fetches, account updates, API-key rotation, and webhook configuration. Every code example below has Node SDK and PHP SDK tabs alongside the raw curl request.

npm install @pevregistry/sdk
const { PevRegistryClient } = require("@pevregistry/sdk");

const client = new PevRegistryClient({
  apiKey: process.env.PEV_REGISTRY_API_KEY,
});

Shop Account

Once you have a key (created via /portal), manage your account programmatically with:

GET   /api/partners/me            # returns your current business info (bearer auth)
PATCH /api/partners/me            # update business info (bearer auth)
POST  /api/partners/me/rotate-key # generates a brand new API key, immediately invalidating the old one (shop login required)

PATCH /api/partners/me accepts companyName, contactName, contactEmail, contactPhone, website, addressLine1, addressLine2, city, state, postalCode, and listedInDirectory (boolean, controls whether your shop appears on the public Partners directory).

Example: fetch your account

curl "https://pevregistry.com/api/partners/me" \
  -H "Authorization: Bearer pevreg_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Example: update business info

curl -X PATCH "https://pevregistry.com/api/partners/me" \
  -H "Authorization: Bearer pevreg_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "companyName": "Example Shop", "website": "https://example-shop.com" }'

Example: rotate your API key

curl -X POST "https://pevregistry.com/api/partners/me/rotate-key" \
  -H "Authorization: Bearer pevreg_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Data Sharing Disclosure

When a device owner registers their PEV, they are informed that if the device is later reported lost or stolen and a repair shop reports having it in their possession, PEV Connection may contact the owner directly (by SMS and/or email) with the device's location and the shop's contact information, solely for the purpose of verifying ownership and assisting with recovery.

To protect owner privacy, the API never discloses an owner's name or contact information to partners directly. Instead:

Partners must not use registry data for marketing, resale, or any purpose other than verifying ownership and assisting recovery.

Rate Limits

Each API key is limited to 60 requests per minute. Requests beyond that return an HTTP 429 with a Retry-After header (seconds).

Lookup a Device

GET /api/v1/lookup?identifier=<serial number or motor code>

Identifier matching is fuzzy-tolerant (case-insensitive, ignores punctuation/spacing, and tolerates a small number of misread characters), so partial OCR reads still match.

Example request

curl "https://pevregistry.com/api/v1/lookup?identifier=WT2500W26070581" \
  -H "Authorization: Bearer pevreg_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Response: not registered

{
  "registered": false
}

Response: registered, not reported lost/stolen

{
  "registered": true,
  "registrationId": "6650c2f1a2b3c4d5e6f70123"
}

Response: reported lost or stolen

{
  "registered": true,
  "registrationId": "6650c2f1a2b3c4d5e6f70123",
  "status": "stolen",
  "identifierType": "motorCode",
  "matchedIdentifier": "WT2500W26070581",
  "device": { "brand": "Example Brand", "model": "X2 Pro", "pevType": "E-Bike" },
  "reportedAt": "2026-09-01T14:32:00.000Z",
  "photoUrls": [
    "https://pevregistry.com/api/v1/registrations/6650.../photos/0"
  ],
  "disclosure": "This device was registered with PEV Registry, a service of PEV Connection, and has been reported stolen. Owner contact information is never shared with API partners directly — if your shop has this device, report it via POST /api/v1/possession and PEV Connection will contact the owner on your behalf."
}
FieldTypeNotes
registeredbooleanWhether any registration matches the identifier.
registrationIdstringReturned when registered. Can be used with service log and possession endpoints.
statusstringOnly present when reported: lost or stolen.
photoUrlsstring[]Only present when reported lost/stolen. Requires the same bearer token to fetch. No owner information is ever included in this response.

Device Photos

Photo URLs returned by /lookup require the same Authorization: Bearer header and return the raw image bytes. Photos are only ever served for devices that have been reported lost or stolen — registered-only devices never expose photos through the API.

GET /api/v1/registrations/{registrationId}/photo
GET /api/v1/registrations/{registrationId}/photos/{index}

Example request

curl "https://pevregistry.com/api/v1/registrations/6650c2f1a2b3c4d5e6f70123/photos/0" \
  -H "Authorization: Bearer pevreg_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -o photo-0.jpg

Report Possession

POST /api/v1/possession

If your shop has a device in your possession that has been reported lost or stolen, call this endpoint. PEV Connection will then contact the owner directly by SMS and/or email from our own systems — reporting that their device has potentially been located, the city/state on file for your business, and your shop's name and contact information so they can reach you. The device's status in the owner's account is also updated with the same information. You can also do this directly from your dashboard at /portal instead of calling the API.

This does not disclose any owner information back to your shop — it only confirms whether the owner was notified.

Example request

curl -X POST "https://pevregistry.com/api/v1/possession" \
  -H "Authorization: Bearer pevreg_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "identifier": "WT2500W26070581"
  }'

Response

{
  "success": true,
  "ownerNotified": true
}
FieldRequiredNotes
identifierOne of identifier / registrationIdThe same serial number or motor code used with /lookup.
registrationIdOne of identifier / registrationIdThe registry's internal ID, if already known from a prior lookup.

The city/state shared with the owner always comes from your account's registered business address (updatable any time via PATCH /api/partners/me or your dashboard) — it is never passed in the /possession request body.

ownerNotified is false if another possession report was already filed for this device in the last 15 minutes (to avoid duplicate notifications) — the location on file is still updated either way.

Service Logs

POST /api/v1/service-logs

Adds a repair or maintenance record to a registered PEV. The device must already exist in PEV Registry; unregistered identifiers return a failure and no record is saved. Shops can submit logs by identifier or by the registrationId returned from /lookup.

Example request

curl -X POST "https://pevregistry.com/api/v1/service-logs" \
  -H "Authorization: Bearer pevreg_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "identifier": "WT2500W26070581",
    "serviceType": "Brake adjustment",
    "notes": "Adjusted rear brake caliper and test rode successfully.",
    "odometerReading": 842,
    "servicedAt": "2026-09-22T14:30:00-04:00"
  }'

Success response

{
  "success": true,
  "registered": true,
  "registrationId": "6650c2f1a2b3c4d5e6f70123"
}

Unregistered response

{
  "error": "Device is not registered",
  "registered": false
}
FieldRequiredNotes
identifierOne of identifier / registrationIdSerial number or motor code for the registered device.
registrationIdOne of identifier / registrationIdThe registry ID returned by /lookup.
serviceTypeYesRepair or maintenance type, such as tire replacement, brake adjustment, or battery diagnostic.
notesNoAdditional service details, up to 3000 characters.
odometerReadingNoNon-negative number, if available.
servicedAtYesDate/time of service.

Webhooks

Configure a webhook URL and signing secret from your Shop Portal (Webhook tab) or via PATCH /api/partners/me/webhook / POST /api/partners/me/webhook/secret. PEV Registry will then push events to your endpoint instead of you having to poll.

Example: configure your webhook

curl -X PATCH "https://pevregistry.com/api/partners/me/webhook" \
  -H "Authorization: Bearer pevreg_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "webhookUrl": "https://your-shop-system.example.com/webhooks/pev-registry" }'

curl -X POST "https://pevregistry.com/api/partners/me/webhook/secret" \
  -H "Authorization: Bearer pevreg_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Event: device.possession_disputed

Sent when an owner marks a device your shop reported as "Not My Device" from their PEV Registry account. You'll also receive an email to your account's contact address for the same event.

POST https://your-shop-system.example.com/webhooks/pev-registry
Content-Type: application/json
X-PEV-Registry-Event: device.possession_disputed
X-PEV-Registry-Signature: sha256=<hex-encoded HMAC>

{
  "event": "device.possession_disputed",
  "sentAt": "2026-09-21T18:42:03.000Z",
  "registrationId": "6650c2f1a2b3c4d5e6f70123",
  "device": { "brand": "Example Brand", "model": "X2 Pro", "pevType": "E-Bike" },
  "reportedCity": "Clearwater",
  "reportedState": "FL",
  "reportedAt": "2026-09-20T14:10:00.000Z",
  "disputedAt": "2026-09-21T18:42:03.000Z"
}

Verifying the signature

Compute an HMAC-SHA256 of the raw request body using your webhook secret, then compare it (constant-time) to the value after sha256= in the X-PEV-Registry-Signature header.

const crypto = require("crypto");

function isValidPevRegistrySignature(rawBody, signatureHeader, webhookSecret) {
  const expected = crypto.createHmac("sha256", webhookSecret).update(rawBody).digest("hex");
  const provided = String(signatureHeader || "").replace("sha256=", "");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(provided));
}

Your endpoint should respond quickly with a 2xx status. Failed deliveries are not currently retried — the lookup/dispute data is always still visible via the API and the Shop Portal.

Registration Widget

Instead of a raw API call, new devices are registered through an embeddable widget your customers use directly on your own website. It's a themeable <iframe> that handles phone/email verification and the device form for you, and tags every registration with your shop's ID for your own analytics.

<iframe
  src="https://pevregistry.com/embed-register.html?shop=YOUR_SHOP_ID&primaryColor=ff8210&font=Inter"
  style="width:100%;max-width:520px;height:820px;border:0;"
  title="Register your PEV with PEV Registry"
></iframe>

Sign in to the Shop Portal and open Registration Widget to get your shop ID, customize colors/fonts, and copy your embed code. Registrations completed through your widget are counted on that page.

Errors

StatusMeaning
400Missing or invalid request fields.
401Missing, invalid, or revoked API key.
404Photo not found (or device not eligible for photo access).
409Conflict — e.g. that serial number is already registered, or the device you're reporting possession of is not currently marked lost/stolen.
429Rate limit exceeded (60 requests/minute per key).
500Unexpected server error. Safe to retry.

Questions about the API? Contact PEV Connection at [email protected].