# Onboarding vehicles, EVSEs and inverters

This article explains how to connect your users to their hardware. You will first need to create a user, which is described [here](/docs/howto/onboarding_users).

This guide covers three onboarding flows:

- **Vehicle onboarding** — 3-level hierarchy: make → model → trim
- **EVSE onboarding** — 2-level hierarchy: make → model (no trims)
- **Inverter onboarding** — follows the same 2-level pattern as EVSE onboarding


The vehicle flow is documented first in full detail. The EVSE section follows — it uses the same pattern but is simpler.

## Custom onboarding selector

### Requirements

To begin you will need:

- A valid Authorization token (see [this page](/docs/auth) for instructions)
- An onboarding redirect URL configured on your account. If you're not sure if this is done, contact [developers@ev.energy](mailto:developers@ev.energy).
- Your OAuth2 `client_id`, as provided during initial setup (see [Authentication](/docs/auth)).
- An ID for a user in the system which you have access to, see [here](/docs/howto/onboarding_users).


### User header

In order for the API to automatically construct onboarding urls for the correct user, you must tell it which user you are onboarding. For each of the following requests, ensure you have set the `EvEnergy-User` header to the user ID of the correct user:

```
EvEnergy-User: user01J4459WYAFXJH187M7XJ5FY6A
```

## Vehicle onboarding

### Select a make

Request the list of vehicle makes from `/vehicle_makes`. Show each of these to your user and have them select the right one. You can use the `icon` field to include a logo image to aid selection.

The individual resource for the make will look something like this:

```json
{
    "id": "vmak01HN2P75GBNFRJPCZJXEFVXH5E",
    "url": "https://api.ev.energy/v2/vehicle_makes/vmak01HN2P75GBNFRJPCZJXEFVXH5E",
    "name": "Volvo",
    "icon": "https://cdn.example.com/volvo.png",
    "onboarding_url": "https://api.ev.energy/v2/vehicle_onboarding?make_id=vmak01HN2P75GBNFRJPCZJXEFVXH5E&user_id=user01J4459WYAFXJH187M7XJ5FY6A",
    "models": "https://api.ev.energy/v2/vehicle_models?make_id=vmak01HN2P75GBNFRJPCZJXEFVXH5E"
}
```

If `onboarding_url` is not `null`, then great, you've got all the information you need to initiate onboarding. Take the URL and jump to the "Initiate Onboarding" section below!

If `onboarding_url` is `null`, then for that make of vehicle we need more information before we can continue. Take the url from the `models` field and go to the next step:

### Select a model

You now need to repeat the process above but for models instead of makes. The previous step will have provided you with a `models` url, which is a link to the `/vehicle_models` collection filtered to only return models for the make selected by the user.

```json
{
    "id": "vmod01HN2P436099PEQ20B7TKPGTEJ",
    "url": "https://api.ev.energy/v2/vehicle_models/vmod01HN2P436099PEQ20B7TKPGTEJ",
    "name": "C40",
    "make": "https://api.ev.energy/v2/vehicle_makes/vmak01HN2P75GBNFRJPCZJXEFVXH5E",
    "onboarding_url": "https://api.ev.energy/v2/vehicle_onboarding?model_id=vmod01HN2P436099PEQ20B7TKPGTEJ&user_id=user01J4459WYAFXJH187M7XJ5FY6A",
    "trims": "https://api.ev.energy/v2/vehicle_trims?model_id=vmod01HN2P436099PEQ20B7TKPGTEJ"
}
```

Request that URL and show the resulting list to your user. Once they select a model, check the `onboarding_url` field. If it is a url jump to the "Initiate Onboarding" section below. If it is `null`, then we still don't have enough information. Grab the url from the `trims` field and continue.

### Select a trim

Repeat the process one last time. The link from the previous step's `trims` field is the url for `/vehicle_trims` collection, filtered to just trims for the selected model.

```json
{
  "id": "trim01HN2NZ1040Y8E77CQGXADC2TZ",
  "url": "https://api.ev.energy/v2/vehicle_trim/trim01HN2NV5DY9C262PQKG75GHN8K",
  "name": "Recharge Core (69 kWh)",
  "model": "http://example.com",
  "onboarding_url": "https://api.ev.energy/v2/vehicle_onboarding?trim_id=trim01HN2NZ1040Y8E77CQGXADC2TZ&user_id=user01J4459WYAFXJH187M7XJ5FY6A&client_id=your_client_id"
}
```

Request that URL and show the resulting list to your user. Once they select their trim, check the `onboarding_url` field. If it is a url jump to the "Initiate Onboarding" section below.

If it is `null`, then we are currently unable to integrate with that vehicle. Instead, we must create an "Unintegrated vehicle" in the system by POSTing to the `/vehicles` endpoint with `{"model": "<model_url>", "battery_size_watt_hours": 60000}`. `battery_size_watt_hours` is optional; if omitted, the backend uses the default for that model and marks the battery size as estimated. `description` may also be provided as a human-readable label. The backend resolves or creates a hidden trim under the hood.

## EVSE onboarding

EVSE onboarding follows the same pattern as vehicle onboarding but with only two levels (make and model — there are no trims).

### Select a make

Request the list of EVSE makes from `/evse_makes`. Show each of these to your user and have them select the right one.

The individual resource for the make will look something like this:

```json
{
    "id": "emak01HS8FRXKJD5JZZ7ZN6W8H21P8",
    "url": "https://api.ev.energy/v2/evse_makes/emak01HS8FRXKJD5JZZ7ZN6W8H21P8",
    "name": "BG SyncEV",
    "display_name": "BG SyncEV",
    "icon": "https://cdn.example.com/syncEV.png",
    "onboarding_url": "https://api.ev.energy/v2/evse_onboarding?make_id=emak01HS8FRXKJD5JZZ7ZN6W8H21P8&user_id=user01J4459WYAFXJH187M7XJ5FY6A",
    "is_beta": false,
    "evse_models": "https://api.ev.energy/v2/evse_models?make_id=emak01HS8FRXKJD5JZZ7ZN6W8H21P8"
}
```

If `onboarding_url` is not `null`, take the URL and jump to the "Initiate Onboarding" section below.

If `onboarding_url` is `null`, take the url from the `evse_models` field and go to the next step:

### Select a model

The previous step will have provided you with an `evse_models` url, which is a link to the `/evse_models` collection filtered to only return models for the make selected by the user.

```json
{
    "id": "emod01HS9A004X5TB5JCMHENB5Z446",
    "url": "https://api.ev.energy/v2/evse_models/emod01HS9A004X5TB5JCMHENB5Z446",
    "name": "EVWC2S22",
    "make": "https://api.ev.energy/v2/evse_makes/emak01HS8FRXKJD5JZZ7ZN6W8H21P8",
    "display_name": "EVWC2S22",
    "onboarding_url": "https://api.ev.energy/v2/evse_onboarding?model_id=emod01HS9A004X5TB5JCMHENB5Z446&user_id=user01J4459WYAFXJH187M7XJ5FY6A"
}
```

Show the models to your user. Once they select a model, check the `onboarding_url` field. If it is a url, jump to the "Initiate Onboarding" section below.

If it is `null`, then we are currently unable to integrate with that EVSE.

> **Note:** There is no trim level for EVSEs — the hierarchy stops at model. This is the key simplification versus vehicle onboarding.


### Import by serial number

Some EVSE providers (`Wallbox`, `Easee`, `Zaptec`, `ChargePoint`, `EO`, `SyncEV`, `EnelX`) onboard by serial number rather than via the OAuth `onboarding_url` flow — the user types the device's CPID/serial and we look it up at the provider using the authenticated user's email. Post the serial number to `POST /v2/evse_onboarding/import`:

```sh
curl --request POST \
     --url https://api.ev.energy/v2/evse_onboarding/import \
     --header 'authorization: Bearer <token>' \
     --header 'content-type: application/json' \
     --data '{"provider": "Wallbox", "serial_number": "WBX-12345678"}'
```

A `201` response carries the freshly created EVSE in the same shape as `GET /v2/evses/{evse_id}`, already linked to the authenticated user. Errors map to RFC 9457 problem responses: `404 evse-not-found-at-provider` when the provider has no matching device, `422 evse-already-imported` when the EVSE is already in our database, `422 evse-import-failed` for unsupported providers or generic provider errors.

## Initiate Onboarding

Send your user to the `onboarding_url` you got from the previous step. This could be in a web browser (which is also the easiest way for you to try it out yourself) or in an embedded web view in a mobile app. It should not be in an iframe embedded in a web page, as some of our third party providers do not function correctly within an iframe.

> **Important:** The `onboarding_url` points to an API endpoint (e.g. `/v2/vehicle_onboarding` or `/v2/evse_onboarding`) which responds with a **302 redirect**. The `Location` header in this redirect is a root-relative path such as `/vehicle-onboarding/start/<uuid>` or `/evse-onboarding/start/<uuid>`. This path is **not** under `/v2/` — do not prepend `/v2/` to it. Browsers and webviews will follow the redirect automatically.


#### Native mobile clients (`response_type=json`)

If your mobile app needs to decide between launching a provider's native SDK (e.g. Smartcar Connect for BMW/MINI) and opening a WebView before navigating to the onboarding URL, append `&response_type=json` to the vehicle `onboarding_url`. The endpoint then returns `200` with a JSON payload instead of a `302` redirect:

```json
{
    "authorization_request_id": "4d4ce3d9-8a16-46a8-99e1-3f3f9b8a3a5b",
    "url": "https://connect.smartcar.com/oauth/authorize?...",
    "auth_method": "native",
    "scopes": ["read_vehicle_info", "read_charge", "control_charge"],
    "single_select": true
}
```

- Open `url` in a WebView when `auth_method` is `"api"`.
- Hand `url`, `scopes` and `single_select` to the provider's native SDK when `auth_method` is `"native"`.


The JSON variant requires at least one of `make_id`, `model_id` or `trim_id` (it does not fall back to the make-picker screen).

EVSE onboarding (`/v2/evse_onboarding`) supports the same `&response_type=json` toggle but with a smaller payload — only `url` (no `auth_method`/`scopes`/`single_select`, since no EVSE provider currently exposes a native SDK). The URL is the absolute onboarding wrapper that you open in a WebView (mobile) or redirect the browser to (web). Like the vehicle variant, the JSON path requires at least one of `make_id` or `model_id`.

```json
{
    "url": "https://api.ev.energy/evse-onboarding/start/abc12345-..."
}
```

##### Completing the native SDK flow (`POST /v2/vehicle_onboarding/complete/`)

When the SDK returns an `auth_code`, exchange it for the list of created vehicles by POSTing to `/v2/vehicle_onboarding/complete/`:

```json
{
    "authorization_request_uid": "<the authorization_request_id returned by init>",
    "auth_code": "<code returned by the SDK>"
}
```

The endpoint always returns an array — even when only one vehicle is created — and you can fetch each vehicle's details via `GET /v2/vehicles/{id}` using the IDs from the response. Like the init endpoint, no OAuth bearer token is required: the `authorization_request_uid` itself binds the call to the originating onboarding session.

### Connect a vehicle

The user will be prompted to pick their vehicle manufacturer. Based on their choice and their location, we will then redirect them to the appropriate log-in service for their vehicle. They will need to enter their credentials and grant access to their account. The exact steps here vary quite a lot between manufacturers but we will guide the user through the process.

### Connect an EVSE

The user will be guided through connecting their charger. Depending on the manufacturer, this may involve entering their charger's serial number or CPID, connecting via OAuth, or configuring a WiFi connection. We will guide the user through the process.

## Finishing up

Once the user's hardware is set up in our system, we will redirect their browser (or webview) back to your onboarding redirect url with a query parameter identifying the new resource.

**For vehicles**, if your redirect url was `https://example.com/redirect`, we would send them to:

```
https://example.com/redirect?vehicle_id=vhcl01HN2NV5DY9C262PQKG75GHN8K
```

Your client can extract the `vehicle_id` query parameter to get the ID of the new Vehicle.

**For EVSEs**, we would send them to:

```
https://example.com/redirect?evse_id=12345
```

Your client can extract the `evse_id` query parameter to get the ID of the new EVSE. Note that the EVSE ID returned here is an integer database ID, not the ULID format used elsewhere in the v2 API.

If onboarding fails, the user will be redirected with a failure indicator:

```
https://example.com/redirect?failure=true
```

For EVSEs, every redirect back (success, failure, abandoned or back) also carries `authorization_request_id`, the UUID of the onboarding attempt. It matches the `authorization_request_id` returned by `GET /v2/evse_onboarding?response_type=json`.

## Summary

### Vehicle onboarding flow

```mermaid
graph TD
  make[Select make] --> check1{onboarding_url is null?}
  check1 -- Yes --> model[Select model]
  check1 -- No --> onboard[Initiate onboarding]
  model --> check2{onboarding_url is null?}
  check2 -- Yes --> trim[Select trim]
  check2 -- No --> onboard[Initiate onboarding]
  trim --> check3{onboarding_url is null?}
  check3 -- Yes --> unintegrated[Create unintegrated vehicle]
  check3 -- No --> onboard[Initiate onboarding]
  onboard --> stop[User has a vehicle]
  unintegrated --> stop
```

### EVSE onboarding flow

```mermaid
graph TD
  make[Select EVSE make] --> check1{onboarding_url is null?}
  check1 -- Yes --> model[Select EVSE model]
  check1 -- No --> onboard[Initiate onboarding]
  model --> check2{onboarding_url is null?}
  check2 -- Yes --> unsupported[EVSE not supported]
  check2 -- No --> onboard[Initiate onboarding]
  onboard --> stop[User has an EVSE]
```