# Component token Source: https://docs.axle.energy/api-reference/auth/component-token POST /auth/component-token Authenticate on behalf of an end user by supplying their external user ID. This endpoint is used by authenticated API clients to create a user-scoped access token for a specific end user (referenced by their external ID) in the API client's organisation. If no such end user exists, one will be created on demand. Returns a bearer access token scoped to this end user upon success. This token is site-scoped and valid for 24 hours, longer-lived than the standard organisation token (1 hour) as it is intended for end-user sessions. Component tokens are site-scoped and valid for **24 hours** — longer-lived than the standard [organisation token](./token-form) (1 hour), since they're intended for end-user sessions. ## Supplying `user_email` A user email is required to process payments through our provider, Stripe. We also send payment-failure notifications and, if enabled, two-factor authentication codes to this address. The field upserts onto the user record, so it's safe to set on every call. # Authentication Source: https://docs.axle.energy/api-reference/auth/token-form POST /auth/token-form Authenticate using username and password submitted via form data. This endpoint is compatible with standard OAuth2 clients that use form-based authentication. Returns an access token upon successful authentication. The returned token is valid for 1 hour, after which a new token must be requested. Exchange your API credentials for a bearer token granting time-bound access to your organisation's resources. This token is valid for **1 hour**. Once it expires you'll need to request a new one. For longer-lived, site-scoped access for end users, see the [Component token](./component-token). # Send asset readings Source: https://docs.axle.energy/api-reference/data/readings POST /data/readings Send asset readings data to Axle using the generic readings format. This endpoint accepts compressed data using gzip encoding. Returns statuses of the processed readings, indicating whether each reading was accepted or rejected. **Direction convention:** for readings that can be either positive or negative (e.g. `boundary_import_kw`, `battery_inverter_import_kw`), a positive value denotes *import* (power flowing in from the grid, or into the battery) and a negative value denotes *export* (power flowing out to the grid, or out of the battery). All other reading types are unidirectional; the required sign for each `label` is documented in the `ReadingDataPointLabel` schema below. # Delete asset Source: https://docs.axle.energy/api-reference/entities/asset/delete DELETE /entities/asset/{asset_id} Delete an asset by its Axle UUID. The asset will be deleted if it exists. Returns a confirmation message upon successful deletion. Use this endpoint when a device is physically removed from a site — for example, when a charger is decommissioned, replaced, or uninstalled. **If a customer is opting out of the service, do not use this endpoint.** Call [unenrol](/api-reference/entities/site/unenrol) instead or in some specific situations use [Offboard site and assets](/api-reference/entities/site/offboard-site-and-assets) # Send charge-now event Source: https://docs.axle.energy/api-reference/entities/asset/event/charge-now POST /entities/asset/{asset_id}/event/charge-now Send notification of a charge now request for an asset. # Send charge-now-deleted event Source: https://docs.axle.energy/api-reference/entities/asset/event/charge-now-deleted POST /entities/asset/{asset_id}/event/charge-now-deleted Send notification of a charge now request being deleted for an asset. # Send intent update event Source: https://docs.axle.energy/api-reference/entities/asset/event/intent POST /entities/asset/{asset_id}/event/intent Send notification of an intent being updated on an asset. # Send plug-in event Source: https://docs.axle.energy/api-reference/entities/asset/event/plug-in POST /entities/asset/{asset_id}/event/plug-in Send notification of a car being plugged in. Tariff, vehicle and intent information may be optionally provided. If a tariff is included, it must comply with the OSM opening hours standard (https://wiki.openstreetmap.org/wiki/Key:opening_hours) for off-peak times and have a valid timezone, or a 400 error will be returned. # Send plug-out event Source: https://docs.axle.energy/api-reference/entities/asset/event/plug-out POST /entities/asset/{asset_id}/event/plug-out Send notification of a car being unplugged from an asset. # Send asset event Source: https://docs.axle.energy/api-reference/entities/asset/event/post POST /entities/asset/{asset_id}/event Send notification of an event happening on an asset. This endpoint allows reporting various events related to an asset, such as: - Plug-in/plug-out events - Updates to charging intent - Charge now events If a tariff is included, it must comply with the OSM opening hours standard (https://wiki.openstreetmap.org/wiki/Key:opening_hours) for off-peak times and have a valid timezone, or a 400 error will be returned. # Send tariff update event Source: https://docs.axle.energy/api-reference/entities/asset/event/tariff POST /entities/asset/{asset_id}/event/tariff-update Send notification of a tariff update for an asset. The `off_peak_times` field must comply with the OSM opening hours standard (https://wiki.openstreetmap.org/wiki/Key:opening_hours) and have a valid timezone, or a 400 error will be returned. The same off-peak times apply every day, day-of-week selectors are not supported. Use `,` to separate multiple time ranges. Common examples: - Same-day off-peak (e.g. midnight to 7am): `00:00-07:00` - Overnight off-peak (e.g. 11:30pm to 5:30am): `00:00-05:30, 23:30-24:00` # Get asset Source: https://docs.axle.energy/api-reference/entities/asset/get GET /entities/asset/{asset_id} Get a specific asset by its Axle UUID. Returns the asset details if it exists. # Get asset by external id Source: https://docs.axle.energy/api-reference/entities/asset/get-by-external-id GET /entities/asset/external-id/{external_id} Get a specific asset by its external ID (vendor-specific identifier). Returns the asset details if it exists. This is useful for integrations where the external system ID is known but not the Axle UUID. # Get all assets Source: https://docs.axle.energy/api-reference/entities/asset/list GET /entities/asset Get all assets with pagination. Returns a page of assets with the total count and the requested subset of asset records. Use the limit and offset parameters to navigate through pages of results. # Create asset Source: https://docs.axle.energy/api-reference/entities/asset/post POST /entities/asset Create a new asset. The asset must be associated with an existing site. Returns the created asset details including the assigned UUID. # Get asset price curve Source: https://docs.axle.energy/api-reference/entities/asset/price-curve GET /entities/asset/{asset_id}/price-curve Half-hourly data of prices per MWh of flexed energy for the asset. Prices are returned from the current Settlement Period until 23:00 UK time on the same day or next day. Next day prices are available from 14:00 UK time. # Update Asset Source: https://docs.axle.energy/api-reference/entities/asset/update PATCH /entities/asset/{asset_id} Update an existing asset's properties, asset model, and/or installation date. All fields are optional. Returns the updated asset details. # Update event status Source: https://docs.axle.energy/api-reference/entities/asset/update-status POST /entities/asset/{asset_id}/callback/{event_id} Provide an update on the handling of a given event by a given asset. This endpoint allows asset controllers to report back the status of event handling. The `asset_id` can be either the Axle UUID or an external ID if `asset_id_is_external` is true. Note: This will not work for non-UUID external asset IDs. # Add site dispatch consent Source: https://docs.axle.energy/api-reference/entities/site/add-consent POST /entities/site/{site_id}/dispatch-consent Add dispatch consent for a site to particpate in flex markets. This enables the site to participate with the specified dispatch consent methods. Returns the updated site details with the new dispatch consent information. # Add site boundary meter consent Source: https://docs.axle.energy/api-reference/entities/site/add-meter-consent POST /entities/site/{site_id}/meter-consent Add boundary meter consent for a site. Confirm that the site has consented for Axle to retrieve its boundary meter readings. This is required for participation in the wholesale market for assets that do not have CoP11 approval, but may also be provided via other endpoints (e.g. during site onboarding). Returns the updated site details with the new consent timestamp. # Add site auth token Source: https://docs.axle.energy/api-reference/entities/site/add-token POST /entities/site/{site_id}/token Provide an auth token to Axle, for dispatching the site's assets. This token should provide access to all of the assets registered against the specified site. This is only required where Axle is not able to access this site via an aggregated API in your systems. # Delete site Source: https://docs.axle.energy/api-reference/entities/site/delete DELETE /entities/site/{site_id} Delete an existing site by its UUID. The site will only be deleted if it has no associated assets or other objects. Returns a confirmation message upon successful deletion. # Check eligibility Source: https://docs.axle.energy/api-reference/entities/site/eligibility GET /entities/site/{site_id}/eligibility Read-only eligibility breakdown for a site and its assets. Returns the list of propositions the site can enrol in, along with per-asset eligibility detail. # Enrol site Source: https://docs.axle.energy/api-reference/entities/site/enrol POST /entities/site/{site_id}/enrol Enrol a site in a proposition. This is the commitment point — it activates the proposition for the site. Idempotent: if the site is already enrolled, returns `already_enrolled` without making changes. If the site previously withdrew from this proposition, returns 422. Set `override_withdrawal` to true to re-enrol. # Get site flex events Source: https://docs.axle.energy/api-reference/entities/site/flex-events GET /entities/site/{site_id}/flex-events Get info on flex events the site has participated in, including estimated and final gross revenues. These estimates can be used for a variety of purposes, including paying out end-users before the final settlement data is available.However, to avoid overpayments it is recommended to only pay out a percentage of the estimated revenue initially, and top-up once final revenues are available. Note: it is possible for gross revenues to be negative in some scenarios, e.g. if the site flexed in the wrong direction by consuming more energy while it was instructed to consume less. # Get site Source: https://docs.axle.energy/api-reference/entities/site/get GET /entities/site/{site_id} Retrieve a specific site by its UUID. Returns the site details if it exists. # Initialise site and assets Source: https://docs.axle.energy/api-reference/entities/site/initialise POST /entities/site/initialise Initialise a site with one or more assets. This endpoint upserts the site and assets (creating them if they don't exist, or filling in missing fields if they do), and returns eligibility information. **No enrollment side effects**: Use the `/enrol` endpoint to commit to enrollment. # Get all sites Source: https://docs.axle.energy/api-reference/entities/site/list GET /entities/site Retrieve sites, optionally filtering by MPAN or postcode. Returns a page of sites with the total count and the requested subset of site records. Use the limit and offset parameters to navigate through pages of results. # Offboard site and assets Source: https://docs.axle.energy/api-reference/entities/site/offboard POST /entities/site/{site_id}/offboard-site-and-assets Offboard a site and all its associated assets in a single operation. Unenrols from every proposition, and deletes the site and assets in accordance with data retention policies. Use when the user is leaving entirely — for example, they've moved house, or your support team is removing them. Payments can no longer be accessed afterwards. If the user is only opting out of one proposition (e.g. toggling Flex Lite off in your app), use `unenrol` instead — it preserves the site and any other propositions they're enrolled in. # Onboard Source: https://docs.axle.energy/api-reference/entities/site/onboard POST /entities/site/onboard Initialise a site and its assets, and enrol them in a proposition in a single call. This endpoint will: 1. Upsert the site and asset(s) 2. Enrol in the relevant flex proposition The response returns the site and asset IDs you'll use in subsequent calls, and the enrolment outcome. Onboarding is idempotent and atomic — sending the same site or asset returns the existing record with any new fields merged in; if enrolment fails, nothing is stored. Already integrated against [`/onboard-site-and-asset`](./onboard-site-and-asset)? It's still supported, but we'd recommend migrating to `/onboard` when convenient. # Onboard site and asset (legacy) Source: https://docs.axle.energy/api-reference/entities/site/onboard-site-and-asset POST /entities/site/onboard-site-and-asset **Deprecated** — prefer [`/onboard`](/api-reference/entities/site/onboard) instead. The new `/onboard` endpoint supports multiple assets per site in a single call and includes improvements in idempotency, resilience and response details. --- Register a site, asset, and dispatch consent in a single call. Creates the site and asset, grants consent, and enrols in the relevant flex proposition. This endpoint will: 1. Create the site 2. Create the asset associated with the site 3. Enrol in the relevant flex proposition Returns the created site and asset, including their IDs. Fails if the site or asset already exists. We'd recommend migrating to [`/onboard`](./onboard) — it supports multiple assets per site, and a wider variety of common use cases. Existing integrations against `/onboard-site-and-asset` will continue to be supported. # Create site Source: https://docs.axle.energy/api-reference/entities/site/post POST /entities/site Register a new site. The site must have a valid address and postcode. If a site with the same address already exists, a 409 error will be returned with the existing site ID. # Get site price curve Source: https://docs.axle.energy/api-reference/entities/site/price-curve GET /entities/site/{site_id}/price-curve Half-hourly data of prices per MWh of flexed energy for the asset. Prices are returned from the current Settlement Period until 23:00 UK time on the same day or next day. Next day prices are available from 14:00 UK time. # Remove site dispatch consent Source: https://docs.axle.energy/api-reference/entities/site/remove-consent POST /entities/site/{site_id}/dispatch-deconsent Remove dispatch consent for a site to stop participation in flex markets. This disables the site's participation with the specified dispatch consent methods. Returns the updated site details with the consent information removed. # Unenrol site Source: https://docs.axle.energy/api-reference/entities/site/unenrol POST /entities/site/{site_id}/unenrol Unenrol a site from a proposition. Withdraws the site from the proposition and prevents further scheduling. Does not delete the site or its assets. Use when the user is opting out of this proposition specifically — for example, they toggled it off in your app. The site and assets are kept, any other propositions they're enrolled in (such as Capacity Market) are unaffected, and the user can re-enrol later and still access their payments. If the user is leaving entirely (moving house, support-led removal, etc.), use `offboard-site-and-assets` instead. Idempotent: if the site is already unenrolled, returns `already_unenrolled` without making changes. # Update site Source: https://docs.axle.energy/api-reference/entities/site/update PATCH /entities/site/{site_id} Update an existing site's information. Only fields included in the request will be updated. Returns the updated site details. # Get example dispatch event Source: https://docs.axle.energy/api-reference/examples/get-example-dispatch-event GET /examples/dispatch-event Get an example dispatch event. Returns a pre-filled DispatchEvent object that can be used as a reference for the expected format and structure of dispatch events. # Get example OCPP charging profile Source: https://docs.axle.energy/api-reference/examples/get-example-ocpp-charging-profile GET /examples/ocpp-charging-profile Get an example OCPP charging profile. Returns a pre-filled SetChargingProfileRequest object that can be used as a reference for the expected format and structure of OCPP charging profiles. # Get meter info by address Source: https://docs.axle.energy/api-reference/meter/get-by-address POST /meter/by-address Retrieve detailed information about a meter using its address. This endpoint allows direct lookup of meter information by address, when the MPAN is not already known (if the MPAN is known, use the /meter/{mpan} endpoint). # Get meter info by MPAN Source: https://docs.axle.energy/api-reference/meter/get-by-mpan POST /meter/by-mpan Retrieve detailed information about a meter using its MPAN. This endpoint allows direct lookup of meter information when the MPAN is already known. # Get meter info by meter ref Source: https://docs.axle.energy/api-reference/meter/get-by-ref GET /meter/by-ref/{meter_ref} Retrieve meter information using an encrypted reference. # Search meters Source: https://docs.axle.energy/api-reference/meter/search GET /meter/search Search for all electricity meters in the vicinity of a given postcode/ address. This endpoint allows searching for meters by postcode and optionally by building identifier. For data protection and contractual reasons, meter details are returned in an encrypted format that can be used in subsequent API calls to retrieve the full meter information. The search works in three steps: 1. Search for meters at the specified address using this endpoint 2. Ask the end-user to select the correct address from the list 3. Call the `/meter/by-ref/{meter_ref}` endpoint with the corresponding reference to get the full meter details NB: This should not be used with procedural address matching; it is intended for end-user interaction only. For precise address matching, use the `/meter/by-address` endpoint instead, which will perform matching for you. # Get Balance Info Source: https://docs.axle.energy/api-reference/rewards/balance-info GET /rewards/{site_id}/info Get info about current balance and total earnings for the site. Requires an organisational bearer token. # Get transaction history Source: https://docs.axle.energy/api-reference/rewards/transactions GET /rewards/{site_id}/transactions Get history of transactions for the site. Requires an organisational bearer token. # Get Balance Withdrawal URL Source: https://docs.axle.energy/api-reference/rewards/withdrawal-url GET /rewards/{site_id}/url/balance Get a temporary link to an Axle-hosted form through which users can view and withdraw rewards. Requires an organisational bearer token. **Important**: Anyone with access to this link will be able to withdraw funds from the corresponding site. The URL contains a JWT valid for 60 minutes from the time of generation. Use the `url_expiry_timestamp` field to surface or enforce this deadline client-side. The ability to withdraw funds via API-generated links is disabled by default. [Contact us](mailto:support@axle.energy) to enable it for your organisation. # Setting up an environment Source: https://docs.axle.energy/api-reference/sandbox Get started with a sandbox environment # Environments Axle provides **sandbox** environments for testing and development purposes. The API docs are interactive; once you're authenticated, you will be able to make calls to `api-sandbox.axle.energy` via these docs. Please [get in touch](mailto:hello@axle.energy) to setup a new sandbox account. When you're ready to roll with a **production** environment, please [get in touch with the team](mailto:hello@axle.energy). ## Use your credentials to obtain a token We use token-based authentication, whereby you exchange your API credentials for a token which gives time bound access to specific resources. To get a token for your session, see the [Authentication](../api-reference/auth/token-form) page, using the `username` and `password` provided when you created your new sandbox. This token must be included in subsequent requests as a bearer token: ```bash theme={null} 'Authorization': 'Bearer ' ``` This token expires after 1 hour. You'll need to refresh it after that. # Validate an MPAN Source: https://docs.axle.energy/api-reference/validation/mpan POST /validation/mpan Validates the provided MPAN as one that is well-formatted and is associated with a connected import meter # Batteries Source: https://docs.axle.energy/assets/batteries ev-chargers Axle has integrated with a wide array of domestic and commercial battery systems. We've got a track record of operating large fleets of batteries in [demand response](https://www.axle.energy/blog/dfs-case-study) and constraint management. We're also the only provider of [frequency services to the UK grid](https://www.axle.energy/blog/frequency) using domestic batteries. Notably, we do this without any additional hardware, utilizing existing cloud APIs to provide swift and stable responses of several MW. # Integrating with Axle You can either integrate into the Axle API, or we'll integrate into an existing cloud API. ### Telemetry We typically gather: * State of charge (SOC) `%` * Inverter Power `kW` * Grid Power `kW` * Solar Power `kW` You can push these to our [Asset Readings](../api-reference/data/readings) endpoint. ### Dispatch You can register for either Delegated Control or Event-based dispatch as described [here](../tutorial/dispatch#types-of-dispatch) ### Telemetry We typically gather: * State of charge (SOC) `%` * Inverter Power `kW` * Grid Power `kW` * Solar Power `kW` We can retrieve these via polling or webhook. ### Dispatch We have extensive experience integrating with both event-based or schedule-based APIs. ## Next steps Optimize behind-the-meter batteries Powerful APIs for advanced integrations # Cars Source: https://docs.axle.energy/assets/cars ev-chargers Axle integrates directly with electric vehicles via your API, enabling smart charging control without requiring a connection to your charger. Your customers can participate in flexibility programmes regardless of which charger they use at home. ## Smart charging principles We always prioritise the customer's needs when optimising car charging. Our optimisation satisfies the following constraints, in order: 1. Ensuring the customer's desired State of Charge (SOC) is met 2. Minimising customer's bills 3. Maximising flexibility revenue ## Customer propositions ### The "free miles" proposition Car integrations are particularly well-suited to **free miles** propositions — where Axle pays the customer per kWh charged, offsetting their electricity costs. The cost of charging is calculated at a flat fee per kWh (e.g. 8p/kWh), and is paid by Axle to the customer's bank account. 1. Customers opt-in to smart charging, delivered via your app 2. Axle schedules charging via a backend integration, ensuring customer's needs are met whilst maximising flexibility value. 3. Axle pays customers per kWh of charging, offsetting their electricity costs. ### Custom propositions Axle's flexible payment rails allow you to build custom propositions for your customers, mixing up-front and recurring payments. See [paying users](/workflows/ev-charging/paying-users). ## Geofencing Axle only controls charging when the vehicle is at home. To determine this, we use a **"car is home" signal from your API** — we do not require location or GPS data, only a boolean indicating whether the car is at home. In practice, this often means: * Your API sends plug-in events to Axle only when the car is at home * Plug-in events at public or workplace chargers should not be forwarded to Axle Axle treats every plug-in event it receives as a home charge, so the simplest implementation is to filter at source. ## Next steps Choose your EV flexibility solution and start planning your integration [here](/workflows/ev-charging/overview). # Chargers Source: https://docs.axle.energy/assets/chargers ev-chargers Axle works with the world's most successful EV charger brands to build flexibility into their charging services. Chargers typically offer a lower-latency, higher-stability connection than vehicles, allowing them to participate in more stringent flexibilty markets such as Frequency Response. Axle also brings extensive experience in hardware and protocol qualification for chargers. We were the first to participate in the UK's [Capacity Market](https://www.axle.energy/blog/capacity-market) directly with EV chargers, and can advise and co-ordinate testing for protocols such as CoP11. [Asset Metering](../tutorial/telemetry) can unlock more value in the wholesale market. In the future, asset metering will likely also allow increased participation in the wholesale market, boosting eligibility from 25% to 100%. Code of Practice 11 (CoP11) is the qualification necessary to use Asset Metering in the wholesale market. CoP11 requires the testing of 2 sample devices. This test qualifies all devices with an equivalent hardware. The qualification process consists of 2 parts: (1) Compliance Testing; (2) Protocol Testing. Qualification for CoP 1, 2, 3, 5 or 10 exempts the need for Compliance Testing. **Compliance Testing** Compliance Testing verifies the meter hardware’s accuracy (+/-2%) and functionality. This process is expected to take 3-4 months, mostly driven by third-party testing turnaround times. Testing is only required for two sample devices - factory testing of all units is not required. **Protocol Testing** Protocol Testing ensures that the meter can be accessed by an Asset Metering Half-Hourly Data Collector, such as Axle. The process should take \~2 weeks to complete. Axle can help arrange compliance testing, and run protocol testing. [Get in touch](mailto:alice@axle.energy) to discuss how we can get your assets CoP11 certified. ## Next steps Choose your EV flexibility solution and start planning your integration [here](/workflows/ev-charging/overview). # HVAC Source: https://docs.axle.energy/assets/heating ev-chargers Axle integrates with electric heating and cooling systems to deliver thermal comfort at the minimum cost. # Control via thermostat We can either control the power of heating & cooling systems directly, or by using a set-level approach via a smart thermostat. Indirect control via the thermostat allows you to ensure that the dynamics of control remain within manufacturer's intended bounds. This can allay concerns around reduced operating efficiency (sCOP), particularly of heat pumps. We use Enode to connect to thermostats - see [their list of support hardware](https://developers.enode.com/api/capabilities/hvac). # Integrating with Axle You can either integrate into the Axle API, or we'll integrate into an existing cloud API. ### Telemetry If we are integrating directly with the heating system we'll need: * Power `kW` * Temperature `°C` * Outside temperature `°C` *Optional* Pushed to our [Asset Readings](../api-reference/data/) endpoint. If we're optimizing via the thermostat, temperature alone suffices. ### Dispatch You can register for either Delegated Control or Event-based dispatch as described [here](../tutorial/dispatch#types-of-dispatch). ### Telemetry If we are integrating directly with the heating system we'll need: * Power `kW` * Temperature `°C` * Outside temperature `°C` *Optional* If we're optimizing via the thermostat, temperature alone suffices. We can retrieve these via polling or webhook. ### Dispatch We'll either need to control power or temperature, if you're integrating [via thermostat](#control-via-thermostat). ## Next steps Get started dispatching assets Powerful APIs for advanced integrations # Address Collection Source: https://docs.axle.energy/components/components/address ``` All color values must be valid CSS colors (hex, rgb, rgba, hsl, hsla, oklch) and size values must include valid CSS units. # Introduction and Authentication Source: https://docs.axle.energy/components/getting-started/introduction Components are fully-integrated, customisable UI building blocks that can be used to build smart charging experiences within your app. They're built on top of Axle's existing API and allow your customers to onboard their smart assets easily. Axle also provides a pre-built "best-practice" smart charging app, which is designed to be embedded as a tab or seperate page in an existing app. The components are open source and built using React. You can either use them directly, or use our fully-hosted option and embed Components in your app through iFrame or mobile web views. Both options provide full theming customisation, and you can pick and choose which components to use. The smart charging app is available as a hosted option only, as Axle manages the whole customer integration for you. ## Choosing a rendering option | | React components | Hosted components | Smart charging app | | --------------------------------- | ---------------------------------------- | ------------------------------------------ | ----------------------------------------------- | | **Customisable theming** | ✅ | ✅ | ✅ | | **Plug and play** | ❌ - Use in existing React website | ❌ - You pick the components to use | ✅ - Axle provides the whole smart-charging flow | | **Build my own flow** | ✅ - Use what you need in an existing app | ✅ - Embed what you need in an existing app | ❌ | | **Enhance an existing React app** | ✅ - Drop in Axle React components | ❌ | ❌ | ## Authentication Regardless of how you choose to render, you'll need to obtain an end-user scoped component token that can be used to authenticate API calls made from the frontend. This is a token scoped to a specific user for use with components only, and must be created using your org scoped token. To get your org scoped auth token, follow the [authentication guide](../../api-reference/auth/token-form). **Never** use your org scoped token in an untrusted environment, including all client side code. To generate an end user scoped token, you'll need to specify an `external_user_id` and `allowed_origin`. These are required so Axle can reconcile your end users with the entries in our database, and to ensure that redirects or iFrame embeddings can only happen on domains you control. ```mermaid theme={null} sequenceDiagram actor user as End User participant be as Your Backend participant axle as Axle user->>be: Authenticates with you using
your existing auth flow be->>axle: Get component scoped token Note over axle: Axle creates a Site entity
using provided external_user_id axle->>be: Return end user scoped token be->>user: Return end user scoped token to frontend user<<->>axle: Frontend components talk directly to Axle using scoped token ``` You can obtain a component token by calling the `/auth/component-token/` endpoint: ```bash Request theme={null} curl -X 'POST' \ 'https://api.axle.energy/auth/component-token' \ -H 'accept: application/json' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/x-www-form-urlencoded' \ -d 'external_user_id=&allowed_origin=' ``` ```json Response theme={null} { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30" } ``` ### Refreshing tokens Component tokens are valid for **one hour**. You should regenerate component tokens before this to ensure user flows aren't interrupted. # React Components Source: https://docs.axle.energy/components/getting-started/react-components Axle publishes an NPM package (`@axle-energy/components`) which can be imported into your existing React project. These components are pre-configured to make authenticated API calls to Axle, and expose complete theming control through CSS variables, or a helper React context. ```tsx Example integration theme={null} import { TariffForm, IntentForm } from "@axle-energy/component/form"; import { UserInfoProvider } from "@axle-energy/core"; function MyApp() { return ( console.log("Tariff saved:", data)} onError={(error) => console.error("Error:", error)} /> ); } ``` ## Theming You can theme the components by either: * **Setting CSS variables**. Components use standard ShadCN/UI theming, so you're able to theme our components by setting the default [ShadCN/UI theming variables](https://ui.shadcn.com/docs/theming#list-of-variables). * **Using our `ColorThemeContextProvider` helper**. Axle also exposes a theming provider (which also works by setting CSS variables), with light/dark mode theming support: ```tsx Theming example theme={null} function MyApp() { const theme = { radius: "0.625rem", background: "#f0f0f0", foreground: "#000000", popover: "#ffffff", popoverForeground: "#000000", primary: "#007AFF", primaryForeground: "#ffffff", secondary: "#f2f2f7", secondaryForeground: "#000000", destructive: "#FF3B30", border: "#c6c6c8", ring: "#007AFF", muted: "#f2f2f7", mutedForeground: "#6d6d70", fontFamily: "sans-serif", } return // Components... } ``` # Integrating with native Enode Link SDK Source: https://docs.axle.energy/components/guides/enode-integration Axle works with [Enode](https://enode.com/) to provide access to a wide array of EVs, chargers, heat pumps, and batteries. Axle will call the Enode API directly on your behalf to read from and control assets. We provide a pre-built integration to help you gather credentials from your users, access assets, and confer the ability to control them to Axle. Enode host their own onboarding flow - [LinkUI](https://developers.enode.com/docs/link-ui/introduction). This guide will demonstrate how to use Axle Components and LinkUI to onboard and connect an asset. If you're developing in another platform, [get in touch](mailto:support@axle.energy) to discuss how we can provide a native integration. ## Step 1: Render the Axle Enode component This is a helper component, which will automatically generate and return correctly scoped Enode tokens. The end user will see a loading indicator, and you'll need to listen to messages to correctly show the Native Enode SDK. ```swift Example component view theme={null} import SwiftUI import WebKit struct WebView: UIViewRepresentable { let url: String func makeUIView(context: Context) -> WKWebView { let webView = WKWebView() return webView } func updateUIView(_ webView: WKWebView, context: Context) { guard let url = URL(string: url) else { return } let request = URLRequest(url: url) webView.load(request) } } struct AxleEnodeIntegration: View { var body: some View { NavigationView { WebView(url: "https://app.axle.energy/form/enode/vehicle") } } } ``` # Step 2: Listen for the Enode token from the component You'll need to listen for the Enode token once the component has authenticated and fetched from Axle. The Enode component automatically sends this token using `WKWebView` message handlers on iOS. We'll first register the message handlers in the WebView. This example is using SwiftUI, but it's also easy to do this in UIKit by implementing the `WKScriptMessageHandler` delegate. ```swift Register message handler in the WebView highlight={3-4, 8-11, 16-32} theme={null} // In EnodeWebView struct // Two way communication with parent view @Binding var messageHandler: ((String, Any?) -> Void)? func makeUIView(context: Context) -> WKWebView { let webView = WKWebView() // Register a message listener in the webview let userContentController = webView.configuration.userContentController userContentController.add(context.coordinator, name: "axleEnode") return webView } // Called automatically by SwiftUI when instantiating the view func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject, WKScriptMessageHandler { var parent: WebView init(_ parent: WebView) { self.parent = parent } func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { parent.messageHandler?(message.name, message.body) } } ``` ```swift Adding a JS listener lines highlight={2, 6, 8-10, 13-26} theme={null} struct AxleEnodeIntegration: View { @State private var messageHandler: ((String, Any?) -> Void)? var body: some View { NavigationView { WebView(url: "https://app.axle.energy/form/enode/vehicle", messageHandler: $messageHandler) } .onAppear { setupMessageHandler() } } private func setupMessageHandler() { messageHandler = { messageName, messageBody in DispatchQueue.main.async { switch messageName { case "axle:enode-handoff": handleSuccess(data: messageBody) default: print("Unknown message: \(messageName)") } } } } } ``` ## Step 3: Handle the Enode token and trigger LinkSDK Once you receive the Enode token from the Axle component, you'll need to present the LinkSDK to complete the asset connection. First, import LinkKit and add the necessary state variables. ```swift Import LinkKit and add state variables highlight={2, 5-6} theme={null} import SwiftUI import LinkKit struct AxleEnodeIntegration: View { @State private var isLinkKitPresented = false @State private var linkToken: String? = nil @State private var messageHandler: ((String, Any?) -> Void)? // ... rest of the view } ``` Next, update your message handler to extract the link token from the response and trigger the LinkSDK: ```swift Handle token and trigger LinkSDK highlight={6-11} theme={null} private func setupMessageHandler() { messageHandler = { messageName, messageBody in DispatchQueue.main.async { switch messageName { case "handoff": // Extract link token from the response if let responseData = messageBody as? [String: Any], let token = responseData["linkToken"] as? String { linkToken = token isLinkKitPresented = true } default: print("Unknown message: \(messageName)") } } } } ``` Finally, add the LinkKit sheet to your view and handle the results: ```swift Add LinkKit sheet highlight={8-16} theme={null} var body: some View { NavigationView { WebView(url: "https://app.axle.energy/form/enode/vehicle", messageHandler: $messageHandler) } .onAppear { setupMessageHandler() } .linkKitSheet(isPresented: $isLinkKitPresented, linkToken: linkToken) { linkResult in switch linkResult { case .success(let success): handleLinkSuccess(success) case .failure(let error): handleLinkError(error) } } } ``` Add the success and error handlers: ```swift Handle LinkSDK results theme={null} private func handleLinkSuccess(_ success: LinkSuccessMetadata) { print("LinkKit success: \(success)") // Move to the next stage in your onboarding! } private func handleLinkError(_ error: LinkError) { print("LinkKit error: \(error)") // Handle LinkKit error // Show error message to user or retry flow } ``` # Theming Source: https://docs.axle.energy/components/guides/theming Apply per-partner branding to the smart charging experience using CSS variables. The embed exposes a number of design tokens which we can customise to suit the branding needs of your application. Under the hood, these are set as CSS variables and piped into Tailwind CSS for use in our components. To create or edit your theme, please reach out to an Axle representative. For new clients, we'll action this as part of your onboarding. ## Design tokens All tokens are optional. Unset tokens fall back to our default theme. ### Colour tokens Colours are expressed as CSS colour strings. The codebase prefers [OKLCH](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch) for perceptual uniformity, but hex and HSL are also supported. Tokens are organised into base/foreground pairs. The base token (e.g. `--primary`) controls a surface colour, and its matching `-foreground` token (e.g. `--primary-foreground`) controls the text and icon colour that sits on that surface. Set them together so that content remains legible against each surface. #### Brand | CSS variable | Purpose | Default | | | ------------------------ | -------------------------------------- | ------------------ | -------- | | `--primary` | Main brand colour — buttons, key links | `oklch(0.205 0 0)` | | | `--primary-foreground` | On primary surfaces | `oklch(0.985 0 0)` | | | `--secondary` | Secondary brand colour | `#2e2f35` | | | `--secondary-foreground` | On secondary surfaces | `#e5e5e7` | | | `--accent` | Accent colour for emphasis | `#2e2f35` | | #### Surfaces | CSS variable | Purpose | Default | | | ---------------------- | ------------------------------------------ | ------------------ | -------- | | `--background` | Page or view background | `oklch(1 0 0)` | | | `--foreground` | Default body text | `oklch(0.145 0 0)` | | | `--card` | Card surfaces | `oklch(0.99 0 0)` | | | `--popover` | Popover / dropdown background | `oklch(1 0 0)` | | | `--popover-foreground` | On popover surfaces | `oklch(0.145 0 0)` | | | `--muted` | Subdued backgrounds — inputs, badges | `#f3f2f280` | | | `--muted-foreground` | On muted surfaces | `oklch(0.556 0 0)` | | | `--scrim` | Backdrop overlay behind sheets and dialogs | `oklch(0.975 0 0)` | | | `--pill` | Pill / chip backgrounds | `oklch(1 0 0)` | | | `--pill-foreground` | On pill surfaces | `oklch(0.205 0 0)` | | #### Feedback | CSS variable | Purpose | Default | | | ---------------------- | ------------------------------ | --------------------------- | -------- | | `--destructive` | Error / destructive actions | `oklch(0.577 0.245 27.325)` | | | `--warning` | Warning alert background | `rgba(254, 250, 231, 1)` | | | `--warning-foreground` | On warning surfaces | `rgba(72, 61, 4, 1)` | | | `--info` | Informational alert background | `rgba(235, 243, 250, 1)` | | | `--info-foreground` | On info surfaces | `rgba(27, 70, 101, 1)` | | #### Borders & focus | CSS variable | Purpose | Default | | | ------------ | --------------- | ------------------ | -------- | | `--border` | Default borders | `oklch(0.922 0 0)` | | | `--ring` | Focus ring | `oklch(0.708 0 0)` | | ### Radius A single `--radius` value drives the entire corner-radius scale. The derived values `--radius-sm`, `--radius-md`, `--radius-lg`, `--radius-xl`, and `--radius-2xl` are all calculated from `--radius`, so components scale proportionally — set the base value and everything from inputs to cards to sheets stays in tune. | CSS variable | Purpose | Default | | ------------ | ------------------ | -------- | | `--radius` | Base corner radius | `1.2rem` | ### Typography | CSS variable | Purpose | Default | | ------------------------ | --------------------------------------------------- | ------- | | *(inline `font-family`)* | Body text font stack, applied via an inline `style` | `Inter` | | `--font-heading` | Heading font stack, applied to `h1`–`h6` | `Inter` | ## Font customisation We support three font strategies. ### 1. External fonts We support the use of [Google Fonts](https://fonts.google.com/) for externally hosted, publicly licensed fonts. ### 2. Local fonts We support the use of custom fonts. You can provide us with font files, which we host alongside the embed. Themes that rely on brand fonts typically use this path, with one or more weights defined and `preload` enabled for primary weights. When using custom fonts, please ensure you have the required licenses for use in the app. ### 3. System fonts Themes may also choose not to ship any font files, instead using a system font stack. ## Copy and content configuration Various parts of the onboarding flow are configurable per partner requirements. This is handled as part of dedicated engineering effort as we support your integration. These theming options should cover the majority of needs for the majority of clients. If you have further requirements, please reach out to us via your customer support contact. # Participating in flexibility markets Source: https://docs.axle.energy/flex-lifecycle The lifecycle of a flexibility asset Although markets vary in their details, there are a number of common steps that most assets will go through. Axle manages this process on your behalf, from registering assets through to revenue. Typically the market operator will require the smart meter identifier (the [MPAN](https://smartmetercheck.citizensadvice.org.uk/meters/new?fuel_type=electricity) in the UK) and the asset type for us to register the asset. In some cases we'll need to know more, such as the asset's max power or capacity. There's typically a delay after an asset is registered before it can participate in the market. This is usually a few days, but can be longer in some cases. Axle will determine the optimal bidding strategy for your asset, based on the market rules and the asset's capabilities. Markets operate on different timescales. In some cases we'll be placing bids for the next day, in others we'll be placing bids weeks or months in advance. We'll deliver the contracted flexibility to the market operator by dispatching your assets, and collecting the requisite telemetry data. After a dispatch, we'll submit metering data to the market operator and collect the payment. We are typically able to estimate our earnings within 24hrs of a dispatch, but the final settlement can take weeks or months, depending on the market. # Access flexibility markets with ease Source: https://docs.axle.energy/introduction Register and prequalify assets, participate in flexibility markets, and monetize. Axle provides access to flexibility markets across Europe. Building on the Axle platform, you can craft user experiences around flex, register and prequalify your assets, and monetize your flexibility. You can find an overview of our currently supported markets in the [Markets](/markets) section. # What is flexibility? Flexibility is the ability to adjust energy consumption or generation in response to changing conditions. This can include shifting energy use to off-peak times, reducing demand during peak periods, or increasing generation when demand is high. Axle helps you realize the latent flexibility of assets you control, by providing components and hosted flows that you can use to sign up users and understand the flexibility in their energy usage. Axle will then help you monetize that flexibility by participating in flexibility markets on their behalf, by suggesting the optimal scheduling of those assets, and by monetizing the flexibility accordingly. ## Support Please [reach out](mailto:support@axle.energy) if there's anything we can help with. ## Where next? Learn more about [participating in markets](./flex-lifecycle) or view specific integration guides for [EV chargers](./workflows/ev-charging/overview) or [batteries](./workflows/axle-vpp/overview). # Denmark Source: https://docs.axle.energy/markets/denmark ### Overview Denmark is split into 2 zones: DK1 (West) is closely aligned with EU balancing arrangements, and DK2 (East) is integrated into the common Nordic balancing zone. The BSP role is available without BRP consent for FCR in DK1 and FFR and FCR-D in DK2. In DK2, the asymmetric FCR-D (Disturbance) product is particularly well-suited to DERs. FCR-D has small min bids (0.1MW), flexible bid durations (1 to 6 hours) and pooled measurement. Pools are capped at 3MW, adding a cost (metering) and administrative burden to large portfolios. FFR's 1 second response time limits many DERs, but is otherwise accessible. In DK1, BSP restrictions on pooling limit the market. Pooling is allowed, but only within the same BRP if mixing generation and demand. BSPs are able to participate in the FCR market, but only BRPs may realistically access the restricted aFRR and mFRR markets. ### Products | Product | DER suitability | Pooling | Direction | Bidding | Min bid (MW) | Pool size (MW) | Full Activation Time (s) | Bid duration (hrs) | Measurement granularity (s) | Payment | | ----------- | --------------- | ------- | ------------ | ---------- | ------------ | -------------- | ------------------------ | ------------------ | --------------------------- | --------------------- | | FCR (DK1) | 3 | Yes | Symmetrical | Daily | 1 | 3 | 30 | 4hrs | 1 | Availability | | aFRR (DK1) | 1 | Yes | Symmetrical | Monthly | 1 | 10 | 600 | | 1 | Delivery,Availability | | mFRR (DK1) | 2 | Yes | Asymmetrical | Year,Daily | 10 | 3 | 750 | | 1 | Delivery,Availability | | FCR-D (DK2) | 4 | Yes | Asymmetrical | Daily | 0.1 | 3 | 30 | 1-6hrs | 1 | Availability | | FCR-N (DK2) | 2 | Yes | Symmetrical | Daily | 0.1 | 3 | 30 | | 1 | Availability | | FFR (DK2) | 2 | Yes | Asymmetrical | Daily | 0.3 | 3 | 1 | | 1 | Availability | # France Source: https://docs.axle.energy/markets/france ### Overview France has a mixed balancing market. FCR is procured via the common EU framework, but strict symmetricality requirements limit DER participation. The aFRR market is now active and operational. mFRR and RR are not yet part of the common MARI platform, but do allow some DER access via specified programmes. France has a country-specific load-shedding product (NEBEF) well-suited to DERs. BSP participation is allowed. Under NEBEF, assets are paid to reduce their consumption compared to their day-ahead forecast. France also has time-based DSO charges, enabling further cost avoidance if in the BRP role. The French capacity mechanism (MECAPA - Mécanisme de Capacité) requires electricity suppliers to secure capacity certificates proportional to their customers' consumption during peak periods. This creates an opportunity for DERs to generate additional revenue streams by offering capacity during system stress events. The Call for Tenders for Demand Response (Appel d'Offres Effacement or AOE) is a specific French mechanism designed to promote demand response. The annual tender allows DERs to bid capacity that can be called upon during peak demand periods. The AOE provides a guaranteed revenue stream through capacity payments, supplementing energy market earnings. ### Products | Product | DER suitability | Pooling | Direction | Bidding | Min bid (MW) | Full Activation Time (s) | Measurement (s) | Payment | | ------- | --------------- | ------- | ------------ | ---------- | ------------ | ------------------------ | --------------- | --------------------- | | FCR | 3 | Yes | Symmetrical | Daily | 1 | 30 | 10 | Availability | | NEBEF | 4 | Yes | Asymmetrical | Daily | 0.1 | | | Delivery | | mFRR/RR | 2 | Yes | Asymmetrical | Year,Daily | 10 | 15 | 10 | Delivery,Availability | | aFRR | 2 | Yes | Asymmetrical | Daily | 1 | 300 | 10 | Availability | | MECAPA | 4 | Yes | Asymmetrical | Annual | 0.1 | N/A | 30 | Availability | | AOE | 4 | Yes | Asymmetrical | Annual | 0.1 | 120 | 10 | Availability,Delivery | # Great Britain Source: https://docs.axle.energy/markets/gb ### Overview Wholesale, balancing and flexibility markets in Great Britain are independent from the rest of Europe. There are a large range of programmes that are accessible and lucrative. The three markets that are best suited to DERs are: 1. The **wholesale** market. This is GB's primary electricity market, and contains deep value for DERs. It has complete geographic coverage of England, Scotland and Wales, and is available to trade all hours of the day. Since December 2024, the GB wholesale market is open to DERs traded by aggregators. Participating assets can be metered at the asset level (if CoP11 approved) or the boundary level (if half-hourly settled). 2. The **Capacity Market**, which offers DERs a lucrative payment for being on standby to turn down in the case of severe grid stress. The CM offers high rewards for minimal intrusion, whilst also using assets to help secure the GB power grid. 3. **Local network flex**, managed by the 6 DNOs in GB. This product is highly local (down to the LV-substation), but highly accessible. Lenient rules on measurement (sub-boundary, or asset), tolerant penalties, and acceptance of non-supplier participation make this a favourite. Additionally, the TSO (NESO) operates a centrally-dispatched Balancing Mechanism, technically accessible to all assets. However, dispatch today frequently deviates from merit order in favour of large generating units, which limit DER upside. Reforms to BM bid selection are slowly increasing the attractiveness of DER participation, but this is a slow process. Frequency products are today dominated by the Dynamic suite (DC, DM, DR), which aren't well-suited to most DERs due to a very fast (\1s) response time for the majority of volume. Slower products (e.g., SFFR) are accessible to DERs but are being phased out. ### Products | Name | DER suitability | Access | Min bid (MW) | Full Activation Time | Payment | | -------------------------------- | --------------- | ---------- | ------------ | -------------------- | -------------------- | | Wholesale | 5 | VTP | 0.1 | - | Energy | | Capacity Market | 4 | Any | 1.0 | 1h | Availability | | DNO flex | 4 | Any | 0.01 | 15m | Energy,Availability | | Demand Flexibility Service (DFS) | 4 | Mostly BRP | 1 | 4h | Energy | | Balancing Mechanism (BM) | 3 | BRP or VLP | 1 | - | Energy | | Quick Reserve | 3 | Any | 1 | 1m | Energy, Availability | | Balancing Reserve | 3 | Any | 1 | 10m | Energy, Availability | | Slow Reserve (future) | 3 | BRP or VLP | 1 | 15m | Energy, Availability | | FFR (Static) | 3 | Any | 1 | 30s | Availability | | Network charges | 3 | BRP | 0 | - | Energy | | Dx (DC, DM, and DR) | 2 | Any | 1 | 1-10s | Availability | ### Upcoming changes New Reserve products (Quick Reserve and Slow Reserve) are in development, and are becoming more accessible to DERs. # Germany Source: https://docs.axle.energy/markets/germany ### Overview The German balancing market participates in EU cooperation for FCR and aFRR products. The BSP role exists, but only BRPs are effectively able to participate. Pooling is allowed, within the same TSO area (4 TSOs in Germany). Burdensome prequalification and measurement requirements (including DSO approval) limit the scalability of DER participation. ### Products | Product | DER suitability | Pooling | Direction | Bidding | Min bid (MW) | Full Activation Time (s) | Measurement granularity (s) | Payment | | ------- | --------------- | ------- | ------------ | ---------- | ------------ | ------------------------ | --------------------------- | --------------------- | | FCR | 3 | Yes | Symmetrical | Daily | 1 | 30 | 1 | Availability | | aFRR | 2 | Yes | Asymmetrical | Daily | 1 | 600 | 1 | Delivery,Availability | | mFRR | 2 | Yes | Asymmetrical | Year,Daily | 1 | 750 | 1 | Delivery,Availability | # Netherlands Source: https://docs.axle.energy/markets/netherlands ### Overview The Dutch market is mostly aligned with the ENTSO-E standard balancing products. The BSP role enables participation in balancing products (minus wholesale and imbalance) for non-suppliers, and lenient pooling rules streamline DER access. Fast response times and symmetrical requirements for frequency products remain a barrier. Dutch balancing is mostly resolved passively, through imbalance pricing. The Dutch TSO (TenneT) publishes real-time imbalance prices, creating a market mechanism to resolve imbalance without calling upon balancing reserves. Imbalance prices are equivalent to the balancing energy price. DER assets may monetize their flexibility on the imbalance market if controlled via the BRP. When balancing isn't required, the price for balancing products is set at the midpoint of 'Up' and 'Down' bids. ### Products | Product | DER suitability | Access | Pooling | Direction | Min bid (MW) | Full Activation Time (s) | Measurement granularity (s) | Payment | | ------- | --------------- | ------ | ------- | ------------ | ------------ | ------------------------ | --------------------------- | ------------------- | | FCR | 3 | BSP | Yes | Symmetrical | 1 | 30 | 4 | Availability | | aFRR | 4 | BRP | Yes | Asymmetrical | 1 | 300 | 4 | Energy,Availability | | mFRR | 3 | BRP | Yes | Asymmetrical | 1 | 300 | 4 | Energy,Availability | # Sweden Source: https://docs.axle.energy/markets/sweden ### Overview Sweden's frequency and balancing products are broadly in-line with the Nordic cooperation model. FCR-D and aFRR are particularly well-suited to DERs due to small minimum bids, asymmetric acceptance, and flexible prequalification. Strict measurement rules require a wallbox connection for EV flexibility. In 2022, these products were typically more lucrative than in neighboring countries. SVK, Sweden's TSO, allows small (\<0.1MW) assets to be type-qualified and therefore added to a pool without pre-qualification. Although this type qualification is limited to 1MW of additional capacity, the relatively quick (\<8 week) pre-qualification process means this isn't an insurmountable barrier. The BSP role is not yet active is Sweden, unlike neighboring countries, limiting participation to BRPs. ### Products | Product | DER suitability | Pooling | Direction | Bidding | Min bid (MW) | Pool size (MW) | Full Activation Time (s) | Bid duration (hrs) | Measurement granularity (s) | Payment | | ------- | --------------- | ------- | ------------ | ------- | ------------ | -------------- | ------------------------ | ------------------ | --------------------------- | --------------------- | | FFR | 2 | Yes | Asymmetrical | Daily | 0.3 | 1 | 1 | | 1 | Availability | | FCR-D | 4 | Yes | Asymmetrical | Daily | 0.1 | 1 | 30 | 1-6hrs | 1 | Availability | | FCR-N | 2 | Yes | Symmetrical | Daily | 0.1 | 1 | 30 | | 1 | Availability | | aFRR | 3 | Yes | Symmetrical | Daily | 1 | 1 | 300 | Fixed | 1 | Availability | | mFRR | 2 | Yes | Asymmetrical | Daily | 5 | N/A | 900 | Fixed | 1 | Availability+Delivery | # Asset and boundary telemetry Source: https://docs.axle.energy/tutorial/telemetry What data we need to participate, and how to send it We need two kinds of data to participate in a market: * **operational metering**: readings from the asset to understand and optimize asset usage. We typically need these with as low latency as possible * **settlement metering**: readings from the asset or site boundary meter, which we send to the market operator as proof of delivery Operational metering always comes from the asset we're optimising. Settlement metering can also come from the asset (known as **asset-metering**), but in many markets we're required to obtain readings from the site boundary meter (**boundary-metering**). Axle can obtain boundary meter data for consenting households if you don't already have access to it. In cases where we use asset metering, we may have to pass additional qualification checks to confirm that the metering provided by the asset is sufficiently accurate. | Market | Asset Metering Allowed? | | --------------- | ----------------------- | | DNO Flex | Yes | | Capacity Market | Yes (1.5% accuracy) | | DFS | Yes | | Wholesale | Yes (CoP11 required) | [CoP11](../assets/chargers#cop11-qualification) is a UK standard which is required to use an asset's metering in the wholesale market. Axle has extensive experience qualifying assets for CoP11 - please [get in touch](mailto:hello@axle.energy) to discuss how we can qualify your asset # Communicating asset telemetry The kind of telemetry we gather varies by asset. * Charge Power (kW) * Plugged in (boolean) * State of charge (SOC) (%) * Plugged in (boolean) * Charge Power (kW) * State of charge (SOC) (%) * Inverter Power (kW) * Grid Power (kW) * Solar Power (kW) * Power (kW) * Temperature (°C) For sites with a CT clamp installed at the grid boundary, Axle can collect half-hourly CT clamp readings through supported asset integrations, or you can push them to the [Asset Readings](../api-reference/data/readings) endpoint as `boundary_import_energy_kwh` and `boundary_export_energy_kwh`. CT clamp data measures site-level grid I/O similarly to a boundary meter, and is treated as such when verifying delivery. ## Reading direction and sign convention When you send readings to the [Asset Readings](../api-reference/data/readings) endpoint, the sign of each value tells us its direction. For readings that can flow either way, the convention is **positive = import, negative = export**: * **Import** means power/energy flowing *in* — from the grid into the site, or into the battery (charging). * **Export** means power/energy flowing *out* — out to the grid, or out of the battery (discharging). All other reading types are unidirectional and must be non-negative. The table below summarises the `label` values accepted by the endpoint: | Reading (`label`) | Unit | Sign convention | | ----------------------------------------- | ---- | ----------------------------------------------------------------------------------- | | `boundary_import_kw` | kW | Positive = import from the grid, negative = export to the grid | | `battery_inverter_import_kw` | kW | Positive = into the battery (charging), negative = out of the battery (discharging) | | `solar_power_kw` | kW | Generation only — always `>= 0` | | `charger_consumption_energy_kwh` | kWh | Consumption only — always `>= 0` | | `charger_lifetime_consumption_energy_kwh` | kWh | Cumulative lifetime consumption — always `>= 0` | | `boundary_lifetime_import_energy_kwh` | kWh | Cumulative lifetime import across the site boundary — always `>= 0` | | `boundary_lifetime_export_energy_kwh` | kWh | Cumulative lifetime export across the site boundary — always `>= 0` | | `boundary_import_energy_kwh` | kWh | Half-hourly import across the site boundary — always `>= 0` | | `boundary_export_energy_kwh` | kWh | Half-hourly export across the site boundary — always `>= 0` | | `provisional_consumption_kwh` | kWh | Consumption only — always `>= 0` | | `battery_state_of_charge_pct` | % | Between 0 and 100 (inclusive) | | `battery_stored_energy_kwh` | kWh | Always `>= 0` | You can push telemetry to our [Asset Readings](../api-reference/data/readings) endpoint, where you'll find a list of accepted reading types We can poll your API or subscribe to webhooks to obtain telemetry. # Obtaining boundary meter data Axle can obtain boundary meter data on your behalf, with user's consent and proof of address. [Contact us](mailto:hello@axle.energy) to discuss the best way to incorporate this consent collection into your product. # Site and asset lifecycle Source: https://docs.axle.energy/workflows/asset-lifecycle How sites and assets move through the platform, and how to manage them as your users change A user owns a **site** — a physical property — and that site holds the **assets** (EV chargers, batteries and heat pumps) Axle can monitor and control. Enrolment, consents and payments all belong to the user via their site, so the operations below act on a **site**, not on individual users. Over their lifetime, sites move between different states depending on whether they exist on the platform and whether they're enrolled in a [proposition](/workflows/ev-charging/overview#choosing-a-product) — the agreement that lets Axle participate in a market on the user's behalf. This page describes those states, the API calls that move between them, and how to handle the everyday changes your users will throw at you: adding an asset, swapping a charger, opting out, or moving house. ## Managing sites ```mermaid theme={null} %%{init: {'theme': 'base', 'themeVariables': {'edgeLabelBackground': '#ffffff', 'fontSize': '14px'}}}%% flowchart LR classDef state fill:#ffffff,stroke:#222,stroke-width:1.5px,color:#222,rx:12,ry:12 A["Unseen / Deleted
Inaccessible
No active propositions"]:::state B["Inactive
Accessible
No active propositions"]:::state C["Active
Accessible
Active propositions"]:::state A -- "/initialise" --> B B -- "/enrol" --> C A -- "/onboard" --> C C -- "/unenrol" --> B C -- "/offboard" --> A linkStyle 0,1,2 stroke:#00C1FF,stroke-width:2px linkStyle 3,4 stroke:#FF3333,stroke-width:2px ``` | State | Exists on platform? | Enrolled in a proposition? | What it means | | -------------------- | :-----------------: | :------------------------: | ----------------------------------------------------------------------------------------------------------------------------------------------- | | **Unseen / Deleted** | No | No | We don't know about this site, or we've removed it. | | **Inactive** | Yes | No | The site and its assets exist — you can look them up, update them, and add or remove assets — but they're not participating in any proposition. | | **Active** | Yes | Yes | The site is enrolled and participating. Schedules, dispatches and rewards are all live. | ### Site transitions Each arrow in the diagram is a single API call. | Endpoint | From → To | Use it when | | ------------------------------------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | [`onboard`](/workflows/flex-lite/api-reference/onboard) | Unseen → Active | **Preferred** — the common path for opting-in. Creates the site and assets **and** enrols them in one call; reach for it by default. | | [`initialise`](/workflows/flex-lite/api-reference/initialise) | Unseen → Inactive | You want to create the site and assets, but do work before committing. | | [`enrol`](/workflows/flex-lite/api-reference/enrol) | Inactive → Active | The commitment point after `initialise` — this begins participation in a proposition (scheduling, readings, rewards). | | [`unenrol`](/workflows/flex-lite/api-reference/unenrol) | Active → Inactive | **Preferred** — the common path for opting-out. The user is leaving a proposition but staying on the platform. Easily reversible; payments still accessible. | | [`offboard`](/workflows/flex-lite/api-reference/offboard) | Active → Deleted | The user is leaving entirely. Removes the site and its assets. | ### Opting in Most integrations sign a user up with a single [`onboard`](/workflows/flex-lite/api-reference/onboard) call, which creates the site and its assets **and** enrols them in one go. Split it into [`initialise`](/workflows/flex-lite/api-reference/initialise) + [`enrol`](/workflows/flex-lite/api-reference/enrol) only when you need to do work between creating the site and committing it to a proposition — see [Advanced Onboarding](/workflows/flex-lite/advanced-integration) for details. ### Opting out A user can stop participating in two ways: [`unenrol`](/workflows/flex-lite/api-reference/unenrol) and [`offboard`](/workflows/flex-lite/api-reference/offboard) In most cases, **unenrol** is the best way for a user to opt-out, and should be the default for a user-initiated opt-out action. It cleanly stops participation — no more readings, scheduling, or rewards — while leaving the site and its assets in place. Because the user remains on the platform, their [payments](/workflows/payments/overview) remain accessible, and you can switch participation back on at any time by re-enrolling the same site. This is especially relevant when a site is participating in another proposition, like the **Capacity Market**. Unenrol only stops the proposition you name, so unenrolling Flex Lite leaves their Capacity Market participation untouched. **Offboard** is the heavy option, for when the user is gone for good: they've moved out, or your support team is removing them. It removes the site and its assets entirely, ends every proposition, and cuts off the user's access — including the payments portal. | | Unenrol | Offboard | | --------------------------------------------- | ---------- | -------- | | Participation (scheduling, readings, rewards) | Stopped | Stopped | | Other propositions (CM, etc.) | Unchanged | Stopped | | Site & assets | Accessible | Removed | | Payments | Accessible | Cut off | Always prompt users to withdraw their outstanding balance before you offboard them — once a site is offboarded, the user can no longer reach the payments portal. ## Managing assets within a site The lifecycle above acts on the **site**. But a site can gain or lose assets without changing state — a user might add a battery to a property that already has a charger, or replace a charger that's been swapped out. These are asset-level operations that leave the site enrolled and untouched: Add an asset to an existing site Remove a single asset from a site ## Common scenarios Use [Create asset](/api-reference/entities/asset/post) against the existing `site_id`. The site stays enrolled, and the new asset starts participating once it's eligible. There's no need to re-onboard or re-enrol the site. Save the returned `asset_id` — you'll need it to send events for that asset. Use [Delete asset](/api-reference/entities/asset/delete) with the `asset_id`. The rest of the site is unaffected. If the user is removing their *only* asset and won't be participating any more, consider whether you want to [unenrol](/workflows/flex-lite/api-reference/unenrol) the site instead — or [offboard](/workflows/flex-lite/api-reference/offboard) it if they're leaving for good. See [opting out](#opting-out) for which to reach for. Treat it as a create followed by a delete: [Create](/api-reference/entities/asset/post) the replacement asset first, then [Delete](/api-reference/entities/asset/delete) the old one. Use a fresh `external_id` for the new asset so your records line up with ours. The site stays enrolled throughout, so there's no gap in participation for its other assets. Update the email against the existing `site_id` with the [site `PATCH` endpoint](/api-reference/entities/site/update). Their site, assets, consents and payments all stay intact. See [Opting out](#opting-out) for the full guidance on choosing between unenrolling and offboarding a site, and on handling any outstanding payment balance. If you unenrolled the site, it's sitting in the **Inactive** state — the site and assets still exist. Call [enrol](/workflows/flex-lite/api-reference/enrol) with the existing `site_id` to bring it back to **Active**. No need to onboard again. If you offboarded instead, the site was deleted, so you'll need to [onboard](/workflows/flex-lite/api-reference/onboard) the user again. A site is tied to a physical property (and its MPAN), so a move is really two separate users at two properties: 1. **Offboard the old site.** The user no longer controls the assets at their previous address, so [offboard](/workflows/flex-lite/api-reference/offboard) it. Always prompt users to withdraw their outstanding balance before you offboard them — once a site is offboarded, the user can no longer reach the payments portal. 2. **Onboard them at the new property** as a fresh site, if they're taking eligible assets with them or the new home already has them. The previous occupant should have been [offboarded](/workflows/flex-lite/api-reference/offboard) when they left. The new occupant is a different user, so [onboard](/workflows/flex-lite/api-reference/onboard) them as a new site against the same MPAN. Don't re-use the old site — its consents and payments belong to the previous user, and it stays linked to their account. ## Where next? Insert pauses into existing charging schedules with a light integration Hand full charge-plan control to Axle for maximum flexibility revenue Split onboarding into initialise + enrol Full request and response specs for every endpoint # Authentication Source: https://docs.axle.energy/workflows/axle-vpp/api-reference/auth POST /auth/token-form Authenticate using username and password submitted via form data. This endpoint is compatible with standard OAuth2 clients that use form-based authentication. Returns an access token upon successful authentication. The returned token is valid for 1 hour, after which a new token must be requested. # Dispatch webhook Source: https://docs.axle.energy/workflows/axle-vpp/api-reference/dispatch The dispatch instruction Axle sends to your webhook during a grid event During a grid event, Axle sends a dispatch instruction to a webhook URL you provide, as the `vpp:dispatch:requested` event. One instruction covers every affected asset, setting a target power for each over a fixed window. You register your webhook URL when you set up your integration — [get in touch](mailto:hello@axle.energy) to configure it. The payload, the envelope it arrives in, the `x-axle-sig` signature, how to respond and the retry behaviour are all documented on the Webhooks page. The `vpp:dispatch:requested` payload and the full delivery contract # Offboard site and assets Source: https://docs.axle.energy/workflows/axle-vpp/api-reference/offboard POST /entities/site/{site_id}/offboard-site-and-assets Offboard a site and all its associated assets in a single operation. Unenrols from every proposition, and deletes the site and assets in accordance with data retention policies. Use when the user is leaving entirely — for example, they've moved house, or your support team is removing them. Payments can no longer be accessed afterwards. If the user is only opting out of one proposition (e.g. toggling Flex Lite off in your app), use `unenrol` instead — it preserves the site and any other propositions they're enrolled in. # Onboard site and asset Source: https://docs.axle.energy/workflows/axle-vpp/api-reference/onboard POST /entities/site/onboard-site-and-asset **Deprecated** — prefer [`/onboard`](/api-reference/entities/site/onboard) instead. The new `/onboard` endpoint supports multiple assets per site in a single call and includes improvements in idempotency, resilience and response details. --- Register a site, asset, and dispatch consent in a single call. Creates the site and asset, grants consent, and enrols in the relevant flex proposition. This endpoint will: 1. Create the site 2. Create the asset associated with the site 3. Enrol in the relevant flex proposition Returns the created site and asset, including their IDs. Fails if the site or asset already exists. # Send telemetry Source: https://docs.axle.energy/workflows/axle-vpp/api-reference/telemetry POST /data/readings Send asset readings data to Axle using the generic readings format. This endpoint accepts compressed data using gzip encoding. Returns statuses of the processed readings, indicating whether each reading was accepted or rejected. **Direction convention:** for readings that can be either positive or negative (e.g. `boundary_import_kw`, `battery_inverter_import_kw`), a positive value denotes *import* (power flowing in from the grid, or into the battery) and a negative value denotes *export* (power flowing out to the grid, or out of the battery). All other reading types are unidirectional; the required sign for each `label` is documented in the `ReadingDataPointLabel` schema below. # Integration Source: https://docs.axle.energy/workflows/axle-vpp/integration Integrate your battery fleet directly with the Axle VPP This pathway is for battery manufacturers (OEMs) who run their own cloud platform and want their users to be able to join the Axle VPP. You send us telemetry, we handle optimisation, market participation and customer relations. We'll send dispatch instructions to an endpoint you provide for you to execute on your own fleet. ```mermaid theme={null} sequenceDiagram participant OEM participant Axle Note over OEM,Axle: Setup OEM->>Axle: Register assets (onboarding) Note over OEM,Axle: Ongoing OEM->>Axle: POST /data/readings (telemetry) Note over OEM,Axle: Grid Event Axle->>OEM: POST dispatch instruction to your webhook OEM->>OEM: Apply target power to each asset ``` The integration has three parts: 1. [Onboarding](#1-onboarding) 2. [Telemetry](#2-telemetry) 3. [Dispatch](#3-dispatch) ## 1. Onboarding Each battery needs a `site_id` and `asset_id` in Axle's system before you can send telemetry or receive dispatch for it — these are the identifiers used throughout the rest of the integration. There are two ways to get there. **What we mean by a "battery".** Axle treats a battery as a complete battery *system* — the battery, its inverter and its meter. If a site has several battery units behind a single system, register them as one asset with the combined capacity. If you already collect address, consent and asset details yourself, call Axle's onboarding API directly. Register a site and battery, returns `site_id` and `asset_id`. Example request ```json theme={null} { "site": { "email": "example@example.com", "mpan": "1234567890123", "postcode": "SW1A 1AA", "street_address": "10 Downing Street", "gave_boundary_meter_consent_at": "2025-01-01T12:00:00Z" }, "asset": { "external_id": "battery-001", "type": "battery", "properties": { "power_kw": 5.0, "capacity_kwh": 13.5 } }, "dispatch_consent": { "dispatch_methods": ["vpp_limited_control"] } } ``` Your backend authenticates as described in [Authentication](./api-reference/auth). For events only mode you should use `vpp_limited_control` in the `dispatch_methods` array. If you want to allow full control of the battery, use `full_asset_schedule_control` instead. If you'd rather not build a signup UI, your customers can sign up on a flow Axle hosts and connect their battery by signing in with their existing account on your platform. Your platform acts as the OAuth2 provider (authorisation server) and Axle is the client — a standard authorisation-code flow, the same way we integrate with manufacturers today. The customer lands on Axle's signup page and begins signup. Axle creates the site at this point. The customer picks your brand as their battery manufacturer and clicks "Connect". Axle redirects them to your OAuth2 authorisation page with `client_id`, `redirect_uri`, `scope` and `state`. The customer signs in with their existing account and authorises access — no Axle password needed. You redirect them back to Axle's callback with an authorisation code, which Axle exchanges for an access token server-to-server at your token endpoint. Axle calls your device-list API with the access token to find the customer's devices. The customer confirms their battery and completes address, MPAN and consent — staying on Axle throughout, finishing on an Axle-hosted welcome page. Axle creates the asset with your device serial recorded against it. Axle POSTs the new asset to an endpoint on your API, authenticated with the same access token. The payload carries the `asset_id` you'll use for [Telemetry](#2-telemetry) and [Dispatch](#3-dispatch), together with the device serial so you can match it to your customer: ```json theme={null} { "asset_id": "3c361164-5187-4b4b-8e8a-7b3f67c4d5a8", "device_serial": "OEM-SERIAL-12345" } ``` ```mermaid theme={null} sequenceDiagram participant Customer participant Axle participant OEM Note over Axle,OEM: You are the OAuth2 provider Customer->>Axle: Start signup on Axle's page Axle->>Axle: Create site Customer->>Axle: Select manufacturer, click "Connect" Axle-->>Customer: Redirect to your OAuth2 sign-in Customer->>OEM: Sign in & authorise access OEM-->>Customer: Redirect back to Axle with auth code Axle->>OEM: Exchange code for access token Axle->>OEM: Fetch customer's devices (device-list API) Customer->>Axle: Confirm device, address & consent (stays on Axle) Axle->>Axle: Create asset Axle->>OEM: POST asset (asset_id + device serial) Axle-->>Customer: Show Axle welcome page ``` ### What you need to provide * A standard OAuth2 authorisation-code flow: a hosted sign-in page and a token endpoint * A `client_id` and `client_secret` issued to Axle * Axle's redirect URI whitelisted: `https://api.axle.energy/vpp/oauth/{provider}/callback` * A scope granting access to the customer's devices * A device-list API returning the customer's devices (serial, power/capacity) for a given access token * An endpoint on your API that accepts the asset registration shown above ## 2. Telemetry Send regular readings for each battery so we can optimise it and verify delivery. We rely mainly on two readings: * **Battery state of charge** — `battery_state_of_charge_pct` (0–100) * **Grid import/export** at the site boundary — `boundary_import_kw` Grid power is bidirectional, so the sign tells us the direction: **positive = import** from the grid, **negative = export** to the grid. If you can also send inverter power (`battery_inverter_import_kw`) and solar generation (`solar_power_kw`), we'll use them to improve optimisation and determine event earnings. Push readings to the readings endpoint, which lists every accepted `label` and its sign convention. Send **instantaneous readings** (a spot value at a single point in time) at a granularity of **5–10 minutes**. Because each reading is instantaneous, its start and end timestamps must be the same. Push battery and boundary readings to Axle. If pushing readings doesn't suit your platform, we can also pull readings from custom endpoints you provide — [get in touch](mailto:hello@axle.energy) to discuss your setup. ## 3. Dispatch During a grid event we send a dispatch instruction to a webhook URL you provide. Each instruction covers a single event and sets a target power for every affected asset over a fixed window. You supply the webhook URL when you set up your integration — [get in touch](mailto:hello@axle.energy) to register it. ```json theme={null} { "event_id": "eab8c867-7c4d-4116-a587-02c9d1f13a16", "issued_timestamp": "2026-06-29T17:02:37.448917+00:00", "start_time": "2026-06-30T20:30:00+00:00", "end_time": "2026-06-30T21:30:00+00:00", "assets": [ { "asset_id": "3c361164-5187-4b4b-8e8a-7b3f67c4d5a8", "power_kw": -6.915 }, { "asset_id": "1a161164-5187-4b4b-8e8a-7b3f67c4d5a8", "power_kw": -4.915 } ] } ``` `power_kw` uses the same sign convention as telemetry: **positive = charge** (import into the battery), **negative = discharge** (export). Apply the target power to each asset for the window between `start_time` and `end_time`. The target power applies at the **inverter**. We normally dispatch at the inverter's maximum power rating, and we account for the fact that household load can limit the power the battery actually delivers. We give you at least **30 minutes' notice** before an event (normally 4 hours to 1 day ahead) via the dispatch webhook. We do not currently run very-short-notice (sub-30-minute) events. Full schema for the dispatch instruction we send to your webhook. If you already have your own dispatch API, we can work with custom dispatch endpoints instead of the webhook above — [get in touch](mailto:hello@axle.energy) to discuss your setup. ### Important points * Customers should be able to override dispatch instructions at any time. * The battery should return to its original mode of operation after the dispatch event has finished # Overview Source: https://docs.axle.energy/workflows/axle-vpp/overview Optimise behind-the-meter batteries against tariffs and flexibility markets ## What is the Axle VPP? Axle's Virtual Power Plant (VPP) product provides the backend infrastructure to optimise behind-the-meter batteries against tariffs and flexibility markets. We handle battery dispatch, optimisation and market participation. ## How does the VPP work? Register your battery assets with us and we'll dispatch commands to optimise the battery in one of two modes: We send daily schedules optimising the battery's operation to maximise revenue and grid impact. We only send commands during periods when the grid is under stress and the battery can provide value. ### Events Grid events are times when the grid is under stress and the VPP takes action. These typically last between 30-60 minutes and we expect roughly 3-5 events per month. These are handled in both modes, but for optimised mode we will add these into the daily schedules. We can optionally notify the customer over email the day before an event, based on your preferences. ### Daily Schedules These are only sent daily in optimised dispatch mode and are designed to maximise the battery's revenue and grid impact. These are based on: * User's electricity tariff * Solar production forecast * Electricity consumption forecast * Battery cycling and other battery health considerations * Flexibility market prices ## Getting started You send us telemetry and execute our dispatch instructions on your own fleet. Best if you're a battery manufacturer running your own cloud platform. # Collecting and validating addresses Source: https://docs.axle.energy/workflows/collecting-addresses Market participation starts with an address we can associate to a meter To participate in a market, we need to know the [MPAN](https://smartmetercheck.citizensadvice.org.uk/meters/new?fuel_type=electricity) of the user's meter. This is the primary identifier that actors within the electricity system use to identify a consumer of electricity. This page describes how to collect addresses in a way that allows us to retrieve the MPANs associated with them, and fall back to asking users for their MPAN directly if necessary. If you've already collected addresses and would like to retrieve MPANs from them, see [Retrieving meter details](./find-mpan). # Recommended workflow Address Collection Workflow 1. Collect the user's postcode 2. Use that to retrieve a list of valid addresses for that postcode 3. Present the user with that list of addresses to choose from 4. If the user's address is not in the list, ask them to enter the MPAN directly # Implementing address collection 1. Use the [search meters](../api-reference/meter/search) endpoint to search by `postcode`. This returns a list of possible matches. 2. If the user selects an address from the list, you can use the `ref` from that address to retrieve the meter details using the [meter info by ref](../api-reference/meter/get-by-ref) endpoint. 3. If the user does not find their address in the list, you can ask them to enter their MPAN directly. You can validate this MPAN using the [validate MPAN](../api-reference/validation/mpan) endpoint. Once you've collected a valid MPAN, you can retrieve the meter details using the [meter info by mpan](../api-reference/meter/get-by-mpan) endpoint. You can use our address collection component to streamline collection of valid addresses. This will implement the logic [described above](#recommended-workflow) and provide a user-friendly interface for your users to select their address.