Enode API (1.3.10)

Download OpenAPI 3.0 Specification

Download Postman Collection

The Enode API is designed to make smart charging applications easy to develop. We provide an abstraction layer that reduces the complexity when extracting vehicle data and sending commands to vehicles from a variety of manufacturers.

The API has a RESTful architecture and utilizes OAuth2 authorization.

We are always available to handle any issues or just answer your questions. Feel free to reach out on post@enode.io

Registration for API access

In order to use the API you will need a client_id and client_secret. Please contact us if you are interested in using our API in production, and we will provide these credentials.

Authorization

Vehicle / hardware access via the Enode API is granted to your application by the User in a standard OAuth Authorization Code flow.

The authorization scheme documented here is the recommended approach for most situations. However, it is also possible to user other OAuth flows, non-confidential clients, and temporary users. Please feel free to contact us if you have any questions about your use-case or the integration of your existing infrastructure.

Preparation: Configure your OAuth client

Because Enode API implements the OAuth 2.0 spec completely and without modifications, you can avoid rolling your own OAuth client implementation and instead use a well-supported and battle-tested implementation. This is strongly recommended. Information on available OAuth clients for many languages is available here

To configure your chosen OAuth client, you will need these details:

  • Your client_id
  • Your client_secret
  • Authorization URL: https://link.test.enode.io/oauth2/auth
  • Token URL: https://link.test.enode.io/oauth2/token
// Node.js + openid-client example
const enodeIssuer = await Issuer.discover('https://link.test.enode.io');
const client = new enodeIssuer.Client({
  client_id: 'xyz',
  client_secret: 'shhhhh',
  redirect_uris: ['http://localhost:5000/callback'],
  response_types: ['code'],
});

Preparation: Obtain a client access token via OAuth Client Credentials Grant

Your OAuth client will have a method for using the OAuth 2.0 Client Credentials Grant to obtain an access token.

// Node.js + openid-client example
const clientAccessToken = await client.grant({grant_type: "client_credentials"});

This access token belongs to your client and is used for administrative actions, such as the next step.

This token should be cached by your server and reused until it expires, at which point your server should request a new one.

When your User indicates that they want to connect their hardware to your app, your server must call Link User to generate an Enode Link session for your User. The User ID can be any string that uniquely identifies the User, but it is recommended that you use the primary key by which you identify the User within your application.

Example Request:

POST /users/{userId}/link HTTP/1.1
Authorization: Bearer {access_token}
{
  "forceLanguage": "no-NB",
  "vendor": "Tesla",
}

Example Response:

{
    "linkState": "ZjE2MzMxMGFiYmU4MzcxOTU1ZmRjMTU5NGU2ZmE4YTU3NjViMzIwY2YzNG",
}

The returned linkState must be stored by your server, attached to the session of the authenticated user for which it was generated.

Your OAuth client will provide a method to construct an authorization URL for your user. That method will require these details:

  • Redirect URI - The URI to which your user should be redirected when the Oauth flow completes
  • Scope - The OAuth scope(s) you wish to request access to (see list of valid values here)
  • State - The value of linkState from the request above

To launch the OAuth flow, send your user to the authorization URL constructed by your OAuth client. This can be done in an embedded webview within a native iOS/Android app, or in the system's default browser.

// Node.js + openid-client + express example

// Construct an OAuth authorization URL
const authorizationUrl = client.authorizationUrl({
  scope: "offline_access all",
  state: linkState
});

// Redirect user to authorization URL
res.redirect(authorizationUrl);

In the Link UI webapp the user will follow 3 steps:

  1. Choose their hardware from a list of supported manufacturers (EVs and charging boxes). For certain EV makes it will be necessary to also select a charge box.
  2. For each selection, the user will be presented with the login screen for that particular hardware. The user must successfully log in.
  3. A summary of the requested scopes will be presented to the user. The user must choose whether to grant access to your application.

Step 3. OAuth flow concludes with a callback

When the user has completed their interactions, they will be redirected to the Redirect URI you provided in Step 1, with various metadata appended as query parameters.

Your OAuth client will have a method to parse and validate that metadata, and fetch the granted access and refresh tokens.

Among that metadata will be a state value - you must verify that it is equal to the linkState value persisted in Step 1, as a countermeasure against CSRF attacks.

// Node.js + openid-client + express example

// Fetch linkState from user session
const linkState = get(req, 'session.linkState');

// Parse relevant parameters from request URL
const params = client.callbackParams(req);

// Exchange authorization code for access and refresh tokens
// In this example, openid-client does the linkState validation check for us
const tokenSet = await client.oauthCallback('http://localhost:5000/callback', params, {state: linkState})

With the access token in hand, you can now access resources on behalf of the user.

Errors And Problems

OAuth Authorization Request

When the User has completed the process of allowing/denying access in Enode Link, they will be redirected to your configured redirect URI. If something has gone wrong, query parameters error and error_description will be set as documented in Section 4.1.2.1 of the OAuth 2.0 spec:

error error_description
invalid_request The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed.
unauthorized_client The client is not authorized to request an authorization code using this method.
access_denied The resource owner or authorization server denied the request.
unsupported_response_type The authorization server does not support obtaining an authorization code using this method.
invalid_scope The requested scope is invalid, unknown, or malformed.
server_error The authorization server encountered an unexpected condition that prevented it from fulfilling the request.
temporarily_unavailable The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server

Example:

https://website.example/oauth_callback?state=e0a86fe5&error=access_denied&error_description=The+resource+owner+or+authorization+server+denied+the+request.

Errors when accessing a User's resources

When using an access_token to access a User's resources, the following HTTP Status Codes in the 4XX range may be encountered:

HTTP Status Code Explanation
400 Bad Request The request payload has failed schema validation / parsing
401 Unauthorized Authentication details are missing or invalid
403 Forbidden Authentication succeeded, but the authenticated user doesn't have access to the resource
404 Not Found A non-existent resource is requested
429 Too Many Requests Rate limiting by the vendor has prevented us from completing the request

In all cases, an RFC7807 Problem Details body is returned to aid in debugging.

Example:

HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
{
  "type": "https://docs.enode.io/problems/payload-validation-error",
  "title": "Payload validation failed",
  "detail": "\"authorizationRequest.scope\" is required",
}

Authentication

UserAccessToken

A UserAccessToken access token is obtained via the OAuth 2.0 Authorization Code grant

This token represents the authorization for the bearer to access resources on behalf of the user who authorized it. This authorization is further limited by scopes. When relevant, the scope required by each endpoint is listed in the documentation for that endpoint.

When accessing protected resources, the token should be provided in a Bearer authorization header as specified in RFC6750

Example:

GET /vehicles HTTP/1.1
Authorization: Bearer yQ89j3LAnqBx42gWGUl4v-jxoS1mfTAH3Q52WTGrExw.zgRcRrE6KRKUgVQEVr2Pifub8Z7trCZrobKjvhGIOTI

You can read more about obtaining this token in the Authorization section

Security Scheme Type OAuth2
authorizationCode OAuth Flow
Authorization URL: https://link.test.enode.io/oauth2/auth
Token URL: https://link.test.enode.io/oauth2/token
Scopes:
  • all -

    Read and write all resources

  • vehicle:smart_charging_policy -

    Read & write vehicle smart charging policy

  • vehicle:charge_state -

    Read vehicle charge state

  • vehicle:location -

    Read vehicle location

  • vehicle:odometer -

    Read vehicle odometer

  • vehicle:information -

    Read vehicle information

  • control:vehicle:charging -

    Control vehicle charging

  • charging_location -

    Read & write user's charging locations

ClientAccessToken

A ClientAccessToken access token is obtained via the OAuth 2.0 Client Credentials grant

When accessing protected resources, the token should be provided in a Bearer authorization header as specified in RFC6750

Example:

DELETE /users/xyz HTTP/1.1
Authorization: Bearer yQ89j3LAnqBx42gWGUl4v-jxoS1mfTAH3Q52WTGrExw.zgRcRrE6KRKUgVQEVr2Pifub8Z7trCZrobKjvhGIOTI

You can read more about obtaining this token in the Authorization section

Security Scheme Type OAuth2
clientCredentials OAuth Flow
Token URL: https://link.test.enode.io/oauth2/token
Scopes:

    Charging Locations

    Charging Locations are created by a user to denote locations where they pay for the power used to charge their vehicle. Smart Charging is active at these locations only.

    List Charging Locations

    Returns a list of Charging Locations registered to the User

    Authorizations:
    UserAccessToken (all) UserAccessToken (charging_location)

    Responses

    200

    Successful

    get /charging-locations
    https://api.test.enode.io/charging-locations

    Response samples

    Content type
    application/json
    Copy
    Expand all Collapse all
    [
    • {
      }
    ]

    Create Charging Location

    Authorizations:
    UserAccessToken (all) UserAccessToken (charging_location)
    Request Body schema: application/json
    name
    string

    User-supplied name for the Charging Location

    longitude
    number

    Longitude in degrees

    latitude
    number

    Latitude in degrees

    Responses

    201

    Created

    post /charging-locations
    https://api.test.enode.io/charging-locations

    Request samples

    Content type
    application/json
    Copy
    Expand all Collapse all
    {
    • "name": "Home",
    • "longitude": 10.757933,
    • "latitude": 59.911491
    }

    Response samples

    Content type
    application/json
    Copy
    Expand all Collapse all
    {
    • "id": "8d90101b-3f2f-462a-bbb4-1ed320d33bbe",
    • "name": "Home",
    • "longitude": 10.757933,
    • "latitude": 59.911491
    }

    Get Charging Location

    Authorizations:
    UserAccessToken (all) UserAccessToken (charging_location)
    path Parameters
    chargingLocationId
    required
    string

    ID of the Charging Location

    Responses

    200

    Successful

    get /charging-locations/{chargingLocationId}
    https://api.test.enode.io/charging-locations/{chargingLocationId}

    Response samples

    Content type
    application/json
    Copy
    Expand all Collapse all
    {
    • "id": "8d90101b-3f2f-462a-bbb4-1ed320d33bbe",
    • "name": "Home",
    • "longitude": 10.757933,
    • "latitude": 59.911491
    }

    Delete Charging Location

    Delete a Charging Location

    Authorizations:
    UserAccessToken (all) UserAccessToken (charging_location)
    path Parameters
    chargingLocationId
    required
    string

    ID of the Charging Location

    Responses

    204

    No Content

    delete /charging-locations/{chargingLocationId}
    https://api.test.enode.io/charging-locations/{chargingLocationId}

    Update Charging Location

    Updates a charging location with new configuration

    Authorizations:
    UserAccessToken (all) UserAccessToken (charging_location)
    path Parameters
    chargingLocationId
    required
    string

    ID of the Charging Location

    Request Body schema: application/json
    name
    string

    User-supplied name for the Charging Location

    longitude
    number

    Longitude in degrees

    latitude
    number

    Latitude in degrees

    Responses

    200

    Successful

    put /charging-locations/{chargingLocationId}
    https://api.test.enode.io/charging-locations/{chargingLocationId}

    Request samples

    Content type
    application/json
    Copy
    Expand all Collapse all
    {
    • "name": "Home",
    • "longitude": 10.757933,
    • "latitude": 59.911491
    }

    Response samples

    Content type
    application/json
    Copy
    Expand all Collapse all
    {
    • "id": "8d90101b-3f2f-462a-bbb4-1ed320d33bbe",
    • "name": "Home",
    • "longitude": 10.757933,
    • "latitude": 59.911491
    }

    Webhooks 🧪

    Webhooks are a mechanism that allows your server to recieve notifications of events from the Enode system.

    Currently, there is only one webhook available - a preconfigured webhook called Firehose that reports all supported events ocurring within the system. You can configure it using the Update Firehose Webhook endpoint.

    Supported events

    Name Description
    user:vehicle:updated One or more of a vehicle's properties (as listed in Get Vehicle) has been updated
    user:vehicle:discovered A new vehicle has been discovered attached to a user
    user:vehicle:deleted A vehicle has been deleted

    Implementing your Webhook endpoint

    Your webhook endpoint should expect to receive POST requests bearing the following headers:

    Header Description
    X-Enode-Delivery Unique ID identifying the delivered payload
    X-Enode-Signature Signature authenticating that Enode is the author of the delivery

    And an application/json body containing an array of Events, with the following schema:

    [
      {
        "event": "user:vehicle:updated", // String - name of the event
        "createdAt": "2020-04-07T17:04:26Z", // UTC ISO 8601 - time at which the event was triggered
      },
      ...
    ]

    Each Event object may contain additional properties, depending on the event.

    Generating a Secret

    A cryptographically secure secret should be generated and persisted on your server.

    You will provide it when configuring a webhook, such as Update Firehose Webhook, and you will again use it when verifying the signature of incoming webhook requests.

    It should be a pseudorandom value of at least 128 bits, produced by a secure generator.

    // Node.js example - 256 bits
    const crypto = require("crypto");
    const secret = crypto.randomBytes(32).toString("hex");

    Verifying a Payload Signature

    Requests made to your endpoint will bear a X-Enode-Signature header verifying that the request has come from us.

    The signature is the HMAC hex digest of the payload, where:

    • algorithm = sha1
    • key = your secret provided during webhook configuration
    • payload = The request body (a UTF-8 encoded string containing JSON - be sure to not deserialize it before signature computation)

    The signature is then prefixed with "sha1="

    In Javascript, the signature may be verified as follows:

    // Node.js + Express example
    
    // Read signature from request HTTP header
    const enodeSignature = Buffer.from(req.get("X-Enode-Signature"), "utf8");
    
    // Compute signature using your secret and the request payload
    const payload = req.body;
    const hmac = crypto.createHmac("sha1", <your secret>);
    const digest = Buffer.from("sha1=" + hmac.update(payload).digest("hex"), "utf8");
    
    // Check whether they match, using timing-safe equality (don't use ==)
    if (!crypto.timingSafeEqual(digest, enodeSignature)) {
      throw new Error("Signature invalid");
    }

    Example Payload

    user:vehicle:updated payload containing 1 event:

    [
      {
        "event": "user:vehicle:updated",
        "createdAt": "2020-04-07T17:04:26Z",
        "user": { // the user whose vehicle was updated
          "id": "8d90101b-3f2f-462a-bbb4-1ed320d33bbe"
        },
        "vehicle": { // the vehicle whose properties were updated
          "id": "e8af7ddf-01ce-4ff7-b850-45888708cc0a",
          "lastSeen": "2020-04-07T17:04:26Z",
          "chargeState": {
            "batteryLevel": "38",
            "range": "127.5",
          },
          "location": {
            "longitude": 10.757933,
            "latitude": 59.911491,
            "lastUpdated": "2020-04-07T17:04:26Z"
          },
        },
      }
    ]

    The vehicle property follows the same schema as Get Vehicle, and only changed fields will be populated.

    Test Firehose Webhook

    Trigger a test payload to be sent to your configured Firehose Webhook url.

    Authorizations:

    Responses

    default

    Successful

    post /webhooks/firehose/test
    https://api.test.enode.io/webhooks/firehose/test

    Response samples

    Content type
    application/json
    Copy
    Expand all Collapse all
    "string"

    Update Firehose Webhook

    Authorizations:
    Request Body schema: application/json
    secret
    string

    A cryptographically secure secret, generated and provided by your client

    url
    string

    The HTTPS url to which Enode should POST the event payload when a watched property changes

    Responses

    204

    No Content

    put /webhooks/firehose
    https://api.test.enode.io/webhooks/firehose

    Request samples

    Content type
    application/json
    Copy
    Expand all Collapse all
    {}

    Me

    The Me endpoint returns metadata about the authenticated User.

    Get My User

    Returns metadata about the authenticated User, including a list of vendors for which the user has provided credentials

    Authorizations:

    Responses

    200

    Successful

    get /me
    https://api.test.enode.io/me

    Response samples

    Content type
    application/json
    Copy
    Expand all Collapse all
    {
    • "id": "123456789-ABc",
    • "linkedVendors":
      [
      ]
    }

    Disconnect Vendor

    Disconnect a single Vendor from the User's account.

    All stored data about their Vendor account will be deleted, and any vehicles that were provided by that Vendor will disappear from the system.

    Authorizations:
    path Parameters
    vendor
    required
    string
    Enum: "TESLA" "BMW" "AUDI" "VOLKSWAGEN" "HYUNDAI" "PEUGEOT" "NISSAN"

    Vendor to be unlinked

    Responses

    204

    No Content

    delete /me/vendors/{vendor}
    https://api.test.enode.io/me/vendors/{vendor}

    Vehicles

    List Vehicles

    Authorizations:
    UserAccessToken (all) UserAccessToken (vehicle:location) UserAccessToken (vehicle:odometer) UserAccessToken (vehicle:information) UserAccessToken (vehicle:charge_state) UserAccessToken (vehicle:smart_charging_policy)
    query Parameters
    field
    Array of strings
    Items Enum: "smartChargingPolicy" "chargeState" "location" "information" "odometer"

    An optional array of Vehicle fields that should be included in the response, for example: ?field[]=information&field[]=location

    By default, no fields are included and only the Vehicle ID will be returned. Response time may be impacted by which fields you request.

    Responses

    200

    Successful

    get /vehicles
    https://api.test.enode.io/vehicles

    Response samples

    Content type
    application/json
    Copy
    Expand all Collapse all
    [