Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

TandemDrive API

Welcome to the TandemDrive API guide. This document provides a high-level overview and complements the detailed API specification.

The TandemDrive API enables seamless integration of your systems with the TandemDrive platform. By utilizing our API, you can extend and automate various aspects of TandemDrive’s capabilities. To get started, API credentials can be obtained from “TandemDrive Console”.

It is essential to thoroughly review this guide to prevent any potential mistakes when working with our API. Understanding the best practices and guidelines will help you fully leverage the API’s capabilities and avoid certain pitfalls.

If you have any questions or need assistance, please don’t hesitate to reach out to our support team. We are here to ensure you implement the API correctly and maximize its efficiency for your specific use case.

Components

TandemDrive’s architecture consists of the following key components:

  • Main Backend: Handles all processing and data storage operations. This component powers the API’s described in this guide.

  • Console: A web interface for configuring, managing, and monitoring TandemDrive.

    For ease of use during API development, the Console allows you to quickly view an entity’s unique ID. When viewing a single entity, simply press Shift + Y to display its ID.

API Overview

Our API endpoints are grouped into two main categories:

  • Admin API

    The Admin API is the core of our system, supporting a wide range of use cases, including data synchronization, automation, and system monitoring. It provides powerful tools for managing and interacting with your TandemDrive instance.

    You can access the full specifications for this API here.

  • App API

    The App API is specifically tailored for end user applications. It provides a set of dedicated endpoints that perform actions on behalf of end users, ensuring that data access is securely managed according to the user’s permissions.

    If you are interested in accessing the App API, please contact our support team.

API Specification

API Changelog

The changelog references the specific TandemDrive product versions where changes were introduced. You can find the current version on the About page of TandemDrive Console. Additionally, all API responses include an X-TD-Version header, which indicates the version of your TandemDrive instance. The API itself is also versioned, but it uses a single version number that rarely changes.

  • v3.55.0:
    • Add some filter query parameters to the GET /cpo-charge-session{,-reimbursement} endpoints.
  • v3.53.1:
    • Fix erroneous documentation for get /msp-roaming-command/{id}.
  • v3.53.0:
    • In the responses of the GET /{end_user_id}/msp-charge-session/{chargesession_id} and the GET /{end_user_id}/msp-charge-session endpoints:
      • customer_total has been deprecated and replaced by the total_amount_excl_vat
      • and the total_amount_incl_vat as been added.
    • The subtotals field in the response of GET /{end_user_id}/msp-charge-session now uses amount_excl_vat instead of the deprecated value and includes the new field vat_amount_unrounded.
  • v3.52.0:
    • The POST /msp-token-subscription/{id}/token-fulfilment endpoint now only allows RFID form factors (which are card and keychain) and no longer allows unknown form factors.
    • Add energy_credit and monetary_credit properties to the GET /{end_user_id}/msp-token-subscription/{subscription_id} response.
  • v3.51.0:
    • Stabilize POST /charge-session endpoint and add the following fields: total_roaming_amount_incl_vat, total_roaming_flat_amount_excl_vat, total_roaming_energy_amount_excl_vat, total_roaming_time_amount_excl_vat, total_roaming_reservation_amount_excl_vat, session_id and connector_format.
  • v3.50.0:
    • In the POST /charge-session endpoint: deprecate roaming_connection in favor of roaming_connection_id, deprecate total_cost_excl_vat in favor of total_roaming_amount_excl_vat and deprecate total_cost_incl_vat in favor of total_roaming_amount_incl_vat.
  • v3.47.0:
    • Add connector_standard to GET /{end_user_id}/msp-charge-session/{chargesession_id} endpoint.
  • v3.46.0:
    • Add GET /msp-charge-cost-calculation endpoint.
    • Add the POST msp-token-fulfilment/activate-token endpoint.
    • Added EVSE information to GET /cpo-charge-session-reimbursement (and webhook).
  • v3.45.1:
    • Add POST /cpo-reimbursement-arrangement/{id}/switch-plan endpoint.
  • v3.43.0:
    • Add seller_county to GET /administration response.
  • v3.41.2:
    • Fix specification of billing rollup group by in GET /billing-box and GET /billing-box/{id}
  • v3.41.0:
    • Add App API endpoints:
      • POST /{end_user_id}/msp-token-subscription/{subscription_id}/cancel
      • POST /{end_user_id}/msp-token-subscription/{subscription_id}/switch-plan.
    • Add deactivated_at to EVSE location.
    • Add optional new_plan_external_reference to POST /msp-token-subscription/{id}/plan-or-customer-correction request body.
  • v3.39.0:
    • Fix specification of GET /billing-box-delivery-state to return list of billing_box_delivery.
    • Add POST /msp-token-subscription/{id}/token-fulfilment endpoint.
  • v3.37.0:
    • Promoted EVCO ID validation endpoint to “Stable”
      • The endpoint is no longer in “Preview”.
      • The response now includes a normalized ID for system integrations and a formatted ID for user display.
  • v3.36.0:
    • Add billing_rollup_group_by to billing box export, and average_amount_per_kwh_excl_tax to rollup.
  • v3.35.0:
    • Add POST /msp-token-subscription/{subscription_id}/plug-and-charge-vehicle endpoint.
    • Add external_reference to billing_box_delivery.
  • v3.34.0:
    • Remove reservation_id and expiry_date from the /msp-roaming-command and /msp-roaming-command/{id} endpoints, since they are only used for unsupported roaming commands.
    • Add endpoints to manage billing box delivery state:
      • GET /billing-box-delivery-state
      • POST /billing-box-delivery-state
      • POST /billing-box-delivery-state/{id}
  • v3.33.0:
    • Remove deprecated endpoints, which are currently not used:
      • PUT /main/v1/cpo-evse/{evse_uid}/reimbursement-mandatory
      • PUT /main/v1/cpo-evse/{evse_uid}
      • PUT /main/v1/cpo-evse-location/{location_id}
      • PUT /main/v1/cpo-subscription-plan/{id}/{valid_from}
  • v3.30.0:
    • add the id and the context of the rollup item to the GET /billing-box/{id} endpoint.
  • v3.28.0:
    • Add evse_uid and updated_at properties to the GET /cpo-dropout-case/{id} response.
    • Add an optional reason query parameter to the GET /cpo-dropout-case endpoint.
    • Add the updated_at property to the GET /cpo-dropout-case response.
    • Remove the following properties from the GET /cpo-dropout-case response:
      • unknown_connector_id
      • unknown_evse_uid
      • unknown_location_id.
  • v3.27.0:
    • Add endpoints to manage calculation inputs on a reimbursement plan:
      • GET /cpo-reimbursement-plan/{id}/calculation-input,
      • PUT /cpo-reimbursement-plan/{id}/calculation-input/{valid_from}/{component_type},
      • PUT /cpo-reimbursement-plan/{id}/calculation-input/{valid_from}/custom/{var_name},
      • DELETE /cpo-reimbursement-plan/{id}/calculation-input/{valid_from}/{component_type},
      • DELETE /cpo-reimbursement-plan/{id}/calculation-input/{valid_from}/custom/{var_name}.
    • Changed the endpoint /msp-token-subscription/{id}/defunct so that also it’s token links are marked defunct.
    • Changed the endpoint /cpo-reimbursement-arrangement/{id}/defunct so that also it’s EVSE/reimbursement links are marked defunct.
    • Changed the endpoint /cpo-subscription/{id}/defunct so that also it’s EVSE/subscription links are marked defunct.
  • v3.26.0:
    • Add a code field to the billing_series.
    • Add the following fields to the response of the GET /billing-box/{id} endpoint:
      • billing_reviewset_code
      • billing_reviewset_id
      • billing_box_reviewset_sequence
      • billing_series_code
      • billing_series_external_reference
      • billing_series_id
    • Add GET /billing-review-set endpoint.
    • Add the POST /msp-token-subscription/add-virtual-token endpoint.
  • v3.21.0:
    • Add addressee to billing address in various endpoints.
  • v3.20.0:
    • Add the customer_external_reference field to the GET /cpo-charge-session-reimbursement and GET /cpo-charge-session-reimbursement/{id} endpoints.
    • Deprecate all “Replace-Update” PUT-endpoints and some related endpoints which are no longer necessary. See the migration guide for more details.
    • Add the party_external_reference query parameter to the GET /billing-box endpoint.
  • v3.18.0:
    • Deprecate preferred_locale_ids of billing-box of GET /billing-box/{id} endpoint.
    • Add locale_id to billing-box of GET /billing-box/{id} and GET /billing-box endpoints.
  • v3.17.0:
    • Add new merge endpoints:
      • POST /end-user/{id}/authorization-customer/{customer_id}
      • POST /end-user/{id}/authorization-msp-token-subscription/{subscription_id}
      • POST /cpo-subscription-plan/{id}/{valid_from}
    • Update POST /msp-token/{evco_id} to support merging.
    • Add charge_periods to POST /cpo-charge-session.
  • v3.16.0:
    • App API: Add a new billing box endpoint GET /{end_user_id}/customer/{customer_id}/billing-box.
    • App API: Add a new billing document endpoint GET /{end_user_id}/billing-box/{id}/document/{filename}.
  • v3.15.0:
    • Remove validation on billing_phone fields, increase max length to 40.
  • v3.12.0:
    • Add two new merge endpoints
      • POST /cpo-evse/{evse_uid}
      • POST /cpo-evse-location/{location_id}
  • v3.11.0:
    • Update the documentation of implicit fields. It should now be clearer how the API works with regard to optional fields.
  • v3.10.0:
  • v3.8.0:
    • Add total_roaming_amount_excl_vat to cpo_chargesession and deprecate total_sales_amount_excl_vat, and adapt the POST /cpo-charge-session similarly.
  • v3.6.0:
    • Add tariff_code to {GET, POST} /cpo-chargesession and GET /cpo-charge-session-reimbursement{/:id} endpoints.
  • v3.4.0:
    • Move:
      • PUT /msp-token-subscription/{id}/plan-or-customer-correction
      • PUT /msp-token-subscription/{id}/energy-credit
      • PUT /msp-token-subscription/{id}/monetary-credit to:
      • POST /msp-token-subscription/{id}/plan-or-customer-correction
      • POST /msp-token-subscription/{id}/energy-credit
      • POST /msp-token-subscription/{id}/monetary-credit, for backwards compatibility the old versions remain functional.
  • v3.3.0:
    • Extend the billing box filter of GET /billing-box and billing-box with reviewset_id.
  • v3.0.0:
    • Add foreign currency totals to billing_box (#5494).
  • v2.79.0:
    • Add the billing_series_external_reference field to GET /billing-box.
    • Add the series_external_reference query filter to GET /billing-box.
    • Add connector_id property to msp_roaming_command_start_session.
    • Add mandatory time_zone to /biling-custom-item.
  • v2.78.0:
    • Add the cdr_id and cpo_id to GET /billing-box/{id}/cpo-charge-session-reimbursement.
    • Add the cpo_cdr_id and cpo_id to GET /billing-box/{id}/msp-charge-session.
    • Added a list of roaming_agreements for each roaming partner to the GET /roaming-partner endpoint.
  • v2.76.0:
    • Volumes in GET /billing-box/{id} are now maps with units as their keys.
  • v2.75.0:
    • Added several new update endpoints to the admin API that allow updating only the specified fields. This makes them more versatile for partial updates and, in many cases, a better alternative to the corresponding PUT endpoints.
      • POST /billing-address/{id}
      • POST /billing-direct-debit-mandate/{id}
      • POST /customer/{id}
      • POST /msp-token-link/{id}
      • POST /msp-token-subscription/{id}
      • POST /cpo-reimbursement-arrangement/{id}
      • POST /cpo-reimbursement-link/{id}
      • POST /cpo-subscription/{id}
      • POST /cpo-subscription-evse-link/{id}
      • POST /end-user/{id}
    • Various changes to the GET /billing-box and GET /billing-box/{id} endpoints.
  • v2.74.0:
    • Change GET /msp-charge-session-priced and GET /cpo-charge-session-reimbursement from optional fields to nullable.
  • v2.73.0:
    • Add explicit billing box state which also contains ready to billing_box schema.
    • Add deferred to billing_box schema.
    • Adapt the GET /billing-box endpoint to the explicit billing box state and the new ready state.
    • Add the GET /billing-box/{id} endpoint, which retrieves a box and its details.
  • v2.72.0:
    • Rename the GET /billing-box/{id}/cpo-charge-session endpoint to /billing-box/{id}/cpo-charge-session-reimbursement.
    • Add the GET /cpo-charge-session-reimbursement/{id} endpoint.
    • Add vat_country, vat_kind, vat_percentage to the cpo_chargesession_reimbursement.
    • Add the rate_label, rate_unit and rate_represent_as_percentage fields to the following endpoints:
      • GET /msp-charge-session-priced
      • GET /msp-charge-session-priced/{id}
      • GET /{end_user_id}/msp-charge-session/{chargesession_id}.
    • Add username property to enduser.
    • Add mark_transferred query parameter to /billing-box/{id}/finalize which if set to true marks the box as transferred if not already done.
  • v2.70.0:
    • Rename the GET /billing-box/{id}/cpo-charge-session-export endpoint to /billing-box/{id}/cpo-charge-session, and add vat_country field. The vat_country field has been added to this endpoint and in the subtotals field, the label and unit have been removed, and the vat_kind field has been added.
    • Rename the GET /billing-box/{id}/msp-charge-session-export endpoint to /billing-box/{id}/msp-charge-session.
    • Add the GET /billing-box/{id}/cpo-charge-session-roaming endpoint.
    • Improve the documentation about eMobility identifiers, and EVSE ID and EVCO ID specifically. In practice we already accepted identifiers without separators, which is now better documented. In a future release we want to follow the recommendation by IDACS to not use separators in machine-to-machine communication.
    • Rename document_type property of billing_series to primary_document_type.
    • Add explicit billing box state which also contains ready.
    • Adapt the GET /billing-box endpoint to the explicit billing box state.
  • v2.69.0:
    • /billing-box/{id}/finalize will not mark the box transferred if not already done. /billing-box/{id}/transferred has to be explicitly called.
    • Add the GET /billing-box endpoint.
    • Rename the GET /billing-box/:id/msp-charge-session-export endpoint to /billing-box/:id/msp-charge-session. In the subtotals field of the endpoint, vat_rate_country_code is renamed to vat_country, vat_rate_kind to vat_kind and vat_rate_percent to vat_percent. The label and unit fields of the subtotals are removed.
  • v2.66.0:
    • Remove details query parameter from, GET /msp-charge-session-priced/{id}, GET /msp-charge-session-priced/, endpoints.
    • Add the GET /billing-product-category endpoint.
  • v2.64.0:
    • Add the GET /billing-box/{id}/document endpoint.
    • Remove connection_pub_id from evse_group.
  • v2.63.0
    • Add vat_country to billing_box_msp_chargesession.yaml and vat_country on it’s subtotal is deprecated.
    • Add vat_country to msp_chargesession_priced.yaml and vat_country on it’s subtotal is deprecated.
    • Add vat_country to msp-charge-session.yaml and vat_country on it’s subtotal is deprecated.
  • v2.61.0
    • Add rollup_group_by to billing-box.
    • Deprecate rollup_method of billing-box.
    • Add billing_rollup_group_by to customer.
    • Deprecate billing_rollup_method of customer.
    • Add plan to rollup_group_by.
  • v2.57.0
    • Add period_from, period_until, volume and item_count to rollup line of billing-box.
  • v2.56.0
    • Deprecate the received_at field on POST /cpo-charge-session.
    • Add the unit and label properties to billing_item_cpo_chargesession.
    • Add the unit, var_name and label properties to billing_box_msp_chargesession.
    • Add billing document upload to billing-box finalize endpoint.
    • Add a defunct property to billing_direct_debit_mandate.
    • Add the PUT /billing-direct-debit-mandate/{id}/defunct endpoint.
    • Certain identifier fields which were previously uppercased by TD, will be returned in their original casing. This change will happen in the next several API releases. Uppercasing these fields was an oversight from our side, since some parties expect us to return the original, unaltered value. These identifiers are still treated as being case insensitive (uniqueness and equality).
  • v2.52.0
    • Add total_idle_seconds and remark properties to msp_chargesession.
    • Add evse_id to /msp-charge-session-progress response.
  • v2.50.0
    • Add new optional property brand_code to msp_token and PUT /msp-token/{evco_id} and POST /msp-token/{evco_id} request bodies.
    • Add code property to GET /msp-token-brand response.
    • Make EVSE uid optional for /msp-roaming-command/start-session.
    • Add session status, total_energy_kwh, and last_update to /msp-charge-session-progress response.
    • Add X-TD-Unknown-Fields header.
  • v2.48.0
    • Add the cpo_id, cpo_cdr_id and roaming_connection properties to msp_chargesession_original.
    • Add latitude and longitude to cpo_evse_location, add these fields as optional parameters to PUT /cpo-evse-location/{id} and POST /cpo-evse-location.
    • Add the publish_from and publish_until properties to the following endpoints:
      • GET /cpo-subscription-plan
      • GET /cpo-subscription-plan/{id}
      • GET /cpo-reimbursement-plan
      • GET /cpo-reimbursement-plan/{id}
      • GET /msp-token-subscription-plan
    • Deprecate the valid_from and valid_until properties of the GET /cpo-reimbursement-plan and GET /cpo-subscription-plan/{id} endpoints.
  • v2.47.0
    • Add an optional used query parameter to the GET /msp-token endpoint.
    • Remove id property from msp_token, since it could not be used anywhere.
    • Add optional token_series_id and token_uid_type_id to POST /msp-token-subscription to also create a token when creating a subscription.
    • Add the arrangement_external_reference property to cpo_chargesession_reimbursement, deprecating arrangement_reference.
    • Deprecate the POST /cpo-evse endpoint.
    • Deprecate the POST /cpo-evse-location endpoint.
    • Add the plan_external_reference property to cpo_subscription.
    • Add an optional body containing an external_updated_at property to the following endpoints:
      • DELETE cpo-reimbursement-arrangement/{id}/valid-until
      • PUT cpo-reimbursement-arrangement/{id}/defunct
      • DELETE cpo-reimbursement-link/{id}/valid-from
      • DELETE cpo-reimbursement-link/{id}/valid-until
      • PUT cpo-reimbursement-link/{id}/defunct
      • DELETE cpo-subscription/{id}/valid-until
      • PUT cpo-subscription/{id}/defunct
      • DELETE cpo-subscription-evse-link/{id}/valid-from
      • DELETE cpo-subscription-evse-link/{id}/valid-until
      • PUT cpo-subscription-evse-link/{id}/defunct
      • DELETE msp-token-link/{id}/valid-from
      • DELETE msp-token-link/{id}/valid-until
      • PUT msp-token-link/{id}/defunct
      • DELETE msp-token-subscription/{id}/valid-until
      • PUT msp-token-subscription/{id}/defunct
    • Add an optional field external_updated_at to the request body of the following endpoints:
      • PUT cpo-reimbursement-arrangement/{id}/valid-from
      • PUT cpo-reimbursement-arrangement/{id}/valid-until
      • PUT cpo-reimbursement-link/{id}/valid-from
      • PUT cpo-reimbursement-link/{id}/valid-until
      • PUT cpo-subscription-evse-link/{id}/valid-from
      • PUT cpo-subscription-evse-link/{id}/valid-until
      • PUT cpo-subscription/{id}/valid-from
      • PUT cpo-subscription/{id}/valid-until
      • PUT msp-token-link/{id}/valid-from
      • PUT msp-token-link/{id}/valid-until
      • PUT msp-token-subscription/{id}/valid-from
      • PUT msp-token-subscription/{id}/valid-until
    • Make the request body for the following endpoints optional:
      • PUT cpo-reimbursement-link/{id}/valid-until
      • PUT cpo-subscription-evse-link/{id}/valid-until
      • PUT cpo-subscription/{id}/valid-until
      • PUT msp-token-link/{id}/valid-until
      • PUT msp-token-subscription/{id}/valid-until
  • v2.45.0
    • Add GET, PUT /end-user/{id}/authorization/customer endpoints.
    • Add GET, PUT /end-user/{id}/authorization/msp-token-subscription endpoints.
    • Add GET /end-user and POST /end-user/ endpoints.
    • Add GET /end-user/{id}, PUT /end-user/{id} and GET /end-user-site endpoints.
    • Add GET /end-user and POST /end-user endpoints.
    • Add an optional form_factor property to msp_token, add GET /msp-token-form-factor endpoint.
    • Add abbility to try API calls from within the documentation.
    • Change **STATE: PREVIEW**, to a preview x-badge.
  • v2.42.0
    • Add GET /msp-dropout-case, GET /msp-dropout-case/{id}, GET / cpo-dropout-case, GET /cpo-dropout-case/{id} endpoints.
  • v2.40.0
    • Add GET /msp-roaming-command/{id}, GET /msp-charge-session-progress, make roaming commands explicit in the URL.
  • v2.39.0
    • Add optional party_id parameter to POST /billing-custom-item.
    • Add optional page_size parameter to routes support paging.
    • Add links property when retrieving data from msp-token-subscription.
    • Add GET and POST /msp-token-series endpoint.
    • Add POST /msp-token-evco-id/validate endpoint.
  • v2.36.0:
    • Rename revision to reversion in Category line of billing_box.
    • Add optional box_category_description to billing_custom_item endpoint.
    • Add GET /msp-roaming-command, POST /msp-roaming-command and GET /msp-roaming-command/{id} endpoints.
    • Add PUT /cpo-evse/{evse_uid}/reimbursement-mandatory endpoint.
    • Add PUT /cpo-evse/{evse_uid}/dropout-all-chargesessions endpoint.
    • Add the admin_id property to billing_box, cpo_chargesession_reimbursement, msp_chargesession_priced, cpo_reimbursement_arrangement, cpo_subscription, msp_token_subscription.
    • Prefer 400 for any client error instead of using other HTTP status codes.
  • v2.32.0:
    • Add a demo-mode to the MSP EVSE-group endpoint. This allows one to get demo responses when no EVSE information is available, such as when testing.
  • v2.31.0:
    • Add PUT /msp-token-subscription/{id}/plan-or-customer-correction endpoint.
  • v2.30.0:
    • Add links property to both cpo_reimbursement_arrangement and cpo_subscription.
    • Add DELETE /cpo-reimbursement-arrangement/{id}/calculation-input/{valid_from}/{component_type} and DELETE /cpo-reimbursement-arrangement/{id}/calculation-input/{valid_from}/custom/{var_name}, endpoints.
    • Add bank_account_name, bic and iban properties to /direct-debit-mandate endpoints. See the specifications of the endpoint on how this works.
  • v2.29.0:
    • Add PUT /cpo-reimbursement-arrangement/defunct.
    • Add GET /billing-series, add ext_ref. support to GET /billing-series/{id}.
    • Add GET /msp-charge-session-original and GET /msp-charge-session-original/{id}.
    • Add POST /cpo-charge-session.
    • Remove paging from GET /cpo-reimbursement-plan.
    • Add links property when retrieving data from cpo_reimbursement_arrangement and cpo_subscription.
    • Remove token_evco_id from POST /cpo-charge-session.
  • v2.27.0:
    • Add vat_kind, vat_percent and vat_country to msp_chargesession_priced_subtotal.
    • Add external_updated_at to /cpo-evse endpoints.
    • Add upsert behavior to PUT /cpo-evse/{evse_uid}.
    • Add connectors to PUT /cpo-evse/{evse_uid}, POST /cpo-evse, GET /cpo-evse, GET /cpo-evse/{evse_uid}.
    • The status code 422 was sometimes used for validation errors while in many cases we used 400. From now on we only use status code 400 when we can’t parse or validate the incoming data.
  • v2.26.0:
    • Add remimbursement_vat field to customers.
    • Add buyer_reference to MSP token subscriptions, CPO reimbursement arrangements, and CPO subscriptions.
    • Add administration_locale_id to GET /billing_box_transfer.
    • Add locale_id to GET /administration.
    • Add external_updated_at to /cpo-evse-location endpoints.
    • Add upsert behavior to PUT /cpo-evse-location/{location_id}.
  • v2.22.0
    • Add GET /cpo-reimbursement-link.
    • Add upsert behavior to PUT /cpo-reimbursement-link.
    • Add PUT /cpo-subscription-evse-link, incl. upsert behavior.
    • Add invalidate_overlapping_mandate property to POST and PUT billing-direct-debit-mandate.
    • Add subscription_external_reference property to msp_chargesession_priced.
    • Add chargesession_id to pricing results of MSP charge sessions.
  • v2.21.0
    • Add PUT /msp-token-subscription/{id}.
    • Add PUT /msp-token-link/{id}.
    • Add upsert behavior to PUT /cpo-reimbursement-arrangement/{id}.
  • v2.13.0
    • Replace POST /cpo-reimbursement-arrangement/{id}/calculation-input with PUT /cpo-reimbursement-arrangement/{id}/calculation-input/{valid_from}/{component_type} and PUT /cpo-reimbursement-arrangement/{id}/calculation-input/{valid_from}/custom/{var_name}.
    • Remove DELETE /cpo-reimbursement-calculation-input/{id}, inputs can now be removed by explicitly setting a value to null using the new PUT endpoints.
    • Change response GET /cpo-reimbursement-arrangement/{id}/calculation-input to return an array of calculation inputs.
  • v2.11.0
    • Rename total_excl_vat to total_amount_excl_vat of cpo_chargesession_reimbursement
    • Change cpo_id of cpo_evse_location to be optional
  • v2.8.0
    • Add charging_periods to reimbursement.
  • v2.7.0 2023-08-25:
    • Add GET /msp-token-subscription-plan endpoint.
    • Add support for adjusting the page size (preview).
    • Add support for determining the rate and tariff at an location (preview).
  • v2.6.0 2023-08-10:
    • Add subtotal_incl_vat to rollup of billing box.
    • Fix specification of latitude and longitude properties to be decimals.
  • v2.1.0 2023-07-10:
    • Add support for using prefixed external references to the customer, billing-address and direct-debit-mandate endpoints.
    • Extend a number of request and response bodies with an external_reference or .._external_reference property.
    • Make valid_until an optional parameter for POST /billing-direct-debit-mandate.
    • Add optional locale_id to customer.
    • Add external_updated_at to direct_debit_mandate.
    • Add GET direct-debit-mandate/{id}.
    • Add PUT direct-debit-mandate, including upsert behavior.
    • Add upsert support to PUT /billing-address.
    • Add external_updated_at to billing_address, and ignore updates older than last observed external_updated_at.
    • Add external_updated_at to customer, and ignore updates older than last observed external_updated_at.
    • Add GET billing_address/{id}.
  • v1.43.0 2023-06-14:
    • Add customer_id, currency_code to msp-priced-charge-session.
    • Add currency_code to cpo-charge-session-reimbursement.
    • Add enduser/register endpoint
    • Add id, customer_id, currency_code to msp-priced-charge-session.
    • Add currency_code to cpo-charge-session-reimbursement.
    • Add id to msp-charge-session.
    • Add GET /msp-charge-session-priced/{id}.
    • Add possibility to use id as after value for GET /msp-charge-session-priced and GET /msp-charge-session.
  • v1.39.0 2023-05-25:
    • First end user endpoints (preview).
  • v1.37.0 2023-05-11
    • Further improvement on billing endpoints (preview).
  • v1.36.0 2023-05-02
    • CPO endpoints for EVSEs, reimbursements and charge sessions.
    • Customer data has been extended with billing data (preview).
    • Several billing endpoints (preview).
  • v1.34.0 2023-04-07
    • Finalized CPO arrangement related endpoints.
  • v1.31.0 2023-02-21
    • Add GET roaming-partner endpoint.
  • v1.28.1 2023-01-04
    • Remove country as required from MSP charge sessions. This was a mistake in the previous API specifications.
  • v1.26.0 2022-12-06
    • Add token_evco_id to msp-priced-charge-session, add rate to subtotal. Add is_currently_valid, and current_admin_id properties to msp-token. Add endpoints:
    • GET customer/{id},
    • GET msp-token/{evco_id}, containing a links property, next to the msp-token properties,
    • GET msp-token-link/{id},
    • GET msp-token-subscription/{id},
    • PUT msp-token-link/{id}/defunct.
    • Remove pagination from: GET administration, GET msp-token-uid-type, GET msp-token-brand, since the number of items returned from these endpoints will not reach the default page size. This is considered backward compatible because the paging URL parameters are optional. Do note that the page_size property has been removed from the response object, since it is no longer needed.
  • v1.24.0 2022-11-14
    • Add initial_admin_id to msp-token.
  • v1.23.0 2022-10-18
    • Change the valid_from field of an msp_token_link to be optional, deferring validity to the msp_token_subscription.
    • Add: DELETE /msp-token-link/{id}/valid-from, DELETE /msp-token-link/{id}/valid-until, DELETE /msp-token-subscription/{id}/valid-until.
  • v1.21.0 2022-09-26
    • Add GET version, GET /msp-charge-session/{id} endpoints add X-TD-Version header. Add optional details parameter to GET /msp-charge-session-priced. Change after parameter value to evco_id for GET /msp-token. Add ac_2phase_split to power_type.
  • v1.20.0 2022-09-14
    • Rename the street property of msp-charge-session to address, this is perceived as a non breaking change since no production implementations are present at the moment.
  • v1.19.0 2022-09-05
    • Add time_zone to msp-charge-session. Add received_at to msp-charge-session, fix several regular expressions and types in the schemas, fix parameter links for msp-token-uid-type and msp-token-brand.
  • v1.17.0 2022-08-18
    • Add endpoints for customers, MSP tokens, token subscriptions, token links, several setup related endpoints and alternate pagination.
  • v1.7.0 2021-12-14
    • Endpoints to retrieve MSP-side charge sessions.

TandemDrive general

Instance

A TandemDrive instance refers to a fully isolated deployment of the TandemDrive platform, for clients who have a Software-as-a-Service (SaaS) agreement. Each instance operates independently, ensuring complete data and operational isolation for its users.

Sellers

A seller is the entity responsible for offering electric vehicle (EV) services, which can include Mobility Service Providers (MSPs) and Charge Point Operators (CPOs).

Within a TandemDrive instance, multiple sellers can coexist, allowing for flexible service offerings and branding strategies.

Administrations

An administration in TandemDrive is a method of organizing and structuring data within a single instance. Administrations can be set up based on criteria like organization, country, or brand, creating distinct segments within the platform, each with its own operational rules. The use of separate administrations has several key implications:

  • Each administration operates with a single currency.
  • Billing configurations are unique to each administration. For instance, invoicing series are not shared between administrations.
  • Administrations are linked to a specific seller, though multiple administrations can share the same seller.
  • Subscription plans are managed independently within each administration.
  • API access can be limited to one or more administrations, enabling tailored data visibility and security controls.

Some information can be shared across multiple administrations under certain conditions:

  • MSP rate plans can be shared between administrations, provided they use the same currency.
  • Customers are primarily associated with one administration to facilitate API segmentation but can hold subscriptions in multiple administrations. The billing for these subscriptions is processed according to the rules of its administration.
  • The Roaming setup is shared across administrations.

Parties and Customers

In TandemDrive, parties represent entities (referred to as “accounts” or “relations” in other systems) that hold predefined roles, such as Customer or Roaming partner. Parties share a unified data structure, allowing them to participate in billing activities.

However, when using the TandemDrive Console or API, you often interact with more specific concepts like Customer or Roaming partner, rather than directly with the broader concept of a Party.

A Customer is a specific type of Party that utilizes one or more services managed within TandemDrive. These services might include token subscriptions, reimbursements, and charge point subscriptions.

Mobility Service Provider (MSP)

This chapter describes TandemDrive services where you act as an MSP.

Subscription

A subscription represents an agreement between a Customer and a Seller, allowing the Customer to charge their vehicle using the Seller’s services. Each subscription is tied to a specific Subscription Plan, which outlines the financial terms and conditions of the agreement.

Every subscription has a defined start date and time and may optionally include an end date and time when the subscription expires. During the subscription period, one or more tokens (such as charge cards) can be linked to the subscription. Each token can have its own start and/or end validity period.

Token Validity

A token is considered valid (i.e., able to initiate a charge session) when all of the following conditions are met:

  • The token is linked to a subscription, and the token’s validity period is currently active. Additionally, the token link must not be marked as defunct.
  • The linked subscription is also within its validity period and is not marked as defunct.
  • The conditions set in the charge authorization rules must permit the token to initiate a charge session.

For more details, see “validity periods”.

A token can only be associated with one subscription at any given time, regardless of the subscription’s validity. To transfer a token from one subscription to another, the following steps must be taken:

  1. Set a valid_until date on the current token link.

    It’s recommended to choose a future date to avoid a brief period where the token becomes inactive.

  2. Create a new token link for the new subscription, using the same token, and set a valid_from date.

    To ensure a seamless transition, you can set the valid_until date of the old token link and the valid_from date of the new token link to the same value.

For corrections, including retroactive changes, use the /msp-token-subscription/{id}/plan-or-customer-correction endpoint. This handles subscription corrections, including moving tokens and repricing charge sessions, with minimal manual intervention.

Subscription plan

A subscription plan includes a name that should clearly reflect how the plan is marketed to customers.

The publishing period of a subscription plan indicates the intended timeframe for when new subscriptions can be initiated. However, TandemDrive does not enforce this property when creating new subscriptions.

Each subscription plan specifies the rate plan used to calculate the pricing for charge sessions.

Additionally, a subscription plan can include recurring costs, though this is optional.

Subscription plan: use customer reimbursement VAT

An MSP subscription plan includes a property called “Use Customer Reimbursement VAT”, which can be enabled to apply VAT exempt to charge sessions in certain cases. When this property is enabled, the following rules apply:

  1. For every charge sessions which is being priced (on the MSP side) we will check if a matching local EVSE can be found (on the CPO side). When no EVSE was found then the normal VAT determination will be used.

  2. Valid reimbursement arrangements for the identified EVSE are determined based on the charge session’s start date and time.

    • When exactly one arrangement was found, then the “reimbursement VAT” property of the customer of that arrangement will be used to determine the VAT. This property can have the following values:

      • Applies: The normal VAT determination applies.

      • Exempt: The charge session is exempt from VAT.

      • Unknown: The charge session will drop-out as VAT cannot be determined. This case can be solved by changing the “Reimbursement VAT” property of the customer to “Applies” or “Exempt”.

    • When no arrangement was found, then the “reimbursement mandatory” property of the EVSE will determine what happens. If the “reimbursement mandatory” is enabled then the charge session will drop-out. If this property is disabled then the charge session will have the normal VAT determination applied.

    • When multiple arrangements were found, then the charge session will drop-out because this situation is currently not supported by TandemDrive. With multiple arrangements it’s potentially confusing if VAT should be applied or not.

Rate plan

Rate plans define the pricing structure for charge sessions. Each rate plan is tied to a specific currency and can be shared across multiple administrations, as long as these administrations use the same currency. This allows businesses to use a consistent rate setup across administrations.

Key components of a rate plan:

  1. Rates:

    • Specify pricing for various components like energy consumption (kWh), charging time (minutes), or flat fees per session.
  2. Rate Rules:

    • Determine the conditions under which specific rates are applied.
    • Rules are sorted from most specific to most generic.
    • The first matching rule determines the rate used to price a charge session.

Advanced Pricing:

For complex pricing scenarios, scripts can be used to implement custom pricing logic. Scripts can be used to set annotations based on custom conditions, and scripts can be used to calculate subtotals for custom rate components. Contact TandemDrive support for assistance with script development.

Charge Sessions

Incoming charge sessions undergo basic validation checks to ensure data integrity. If a charge session fails these checks, it will be categorized as an Invalid charge session, along with the technical reason for its rejection.

If the session passes validation, it proceeds to the pricing phase. During this process, the system determines the applicable subscription and roaming partner, and the associated rate plan is used to calculate the appropriate rates.

Various pricing checks are applied, which can be customized based on administration settings and roaming partner configurations. If pricing is successful, a “pricing result” is generated for the charge session. If there are issues, the session is flagged in a drop-out case for further review.

Charge sessions can be repriced, where the original pricing result is preserved, and a new one is added. As a result, a single charge session may have multiple pricing results. The pricing result endpoint returns these records in chronological order, based on when each pricing result was created.

Each pricing result is assigned a version number. The first pricing result has version = 1, and subsequent results increment the version number for that specific charge session.

It’s important to note that a “pricing result” and a “charge session” are distinct entities, each with its own unique ID.

Drop-outs

Charge sessions that cannot be priced are stored in Drop-out Cases. Each case groups charge sessions together that failed for the same reason and share similar properties. The system allows for the reprocessing of these charge sessions, with the option to include additional processing instructions.

Resolving a drop-out case may require changes in the setup, such as adjusting a rate plan or creating a roaming agreement. In many instances, retrying the session after making these adjustments is sufficient. In other cases, it may be necessary to include specific instructions, such as bypassing certain checks or discarding the charge session entirely.

For traceability, resolved drop-out cases are retained for some time.

Charge credits

A token subscription can be assigned charge credits, which can be used to pay for charge sessions. These credits can be allocated either as monetary value or energy (kWh). Once credits are assigned, they will automatically be used for all charge sessions until the credits are fully depleted. After that, the customer is charged as usual. Currently, customers cannot be restricted to pay exclusively with credits (prepaid-only is not supported).

The way credits are applied depends on the components of the rate being charged. Some components, like parking fees, can be excluded from credit usage. Each component also specifies how credits are applied when the customer’s credit balance is insufficient to cover the full session.

Monetary credit values are calculated excluding VAT.

Charge Point Operators (CPO)

This chapter describes TandemDrive services where you act as a CPO.

Charge points and locations

An EVSE (Electric Vehicle Supply Equipment) is an independently operated and managed component of a charging station that can provide energy to one electric vehicle (EV) at a time. Sometimes, EVSEs are informally referred to as “charge points”, but this term can also be used more broadly to refer to entire charging stations.

Each EVSE has a defined location, specifying the geographical place where it is installed. A single location can contain multiple EVSEs.

The “EVSE UID” uniquely identifies an EVSE within the CPO’s platform. Do not confuse this with the “EVSE ID”. The “EVSE ID” can be reassigned between different EVSEs. EVSE IDs typically look like this: FR*EDF*E2542AX8769, with the * separator being optional.

An EVSE can have one or more connectors, which are the sockets or plugs used to connect and charge an electric vehicle.

For more information, see our guide on E-Mobility identifiers.

Safe EVSE configuration

Adding and configuring an EVSE in TandemDrive during active charge sessions can lead to unexpected behavior. To mitigate this risk, enable the dropout_all_chargesessions property for the EVSE. This action will gracefully move incoming charge sessions associated with that EVSE into a dropout case.

After the EVSE setup is fully complete, use the PUT /main/v1/cpo-evse/{evse_uid}/dropout-all-chargesessions endpoint to disable dropout_all_chargesessions. Use the reprocess_dropouts attribute to ensure that all previously dropped charge sessions are automatically retried.

Charge sessions

TandemDrive processes incoming charge sessions using the following steps:

  • Basic checks

    If a charge session lacks critical information or is malformed, TandemDrive will reject it. When rejected via our API, the response will indicate the specific issue.

    If a charge session is received via other methods (such as OCPI) and is invalid, it will be stored as an “invalid charge session”. The same charge session can later be resubmitted with corrected data, and any matching “invalid charge session” will be marked as resolved.

  • Match location, EVSE and connector

    Before further processing can occur, TandemDrive must match the EVSE mentioned in the charge session to one in its repository. If a matching EVSE is not found, applicable reimbursement arrangements or roaming agreements cannot be determined.

    This step will be retried multiple times. If unsuccessful after repeated attempts, the charge session will be marked as a drop-out, with a drop-out reason such as “Location not found,” “EVSE not found,” or “Connector not found”.

  • Validity checks

    Several checks are performed to verify the validity of the charge session, such as:

    • Is the session start time earlier than the end time?
    • Is the amount of energy consumed reasonable?
    • Is the session end time in the past?
    • Is the charge session too old?

    If any of these checks fail, the charge session is added to a drop-out case. Many thresholds for these checks can be configured.

  • Reimbursements

    All reimbursement arrangements valid at the start of the charge session will be applied. Reimbursement amounts are calculated, and the appropriate VAT is determined. If this process fails, the charge session is added to a drop-out case.

    Reimbursements can be calculated in two ways, using a setting on the reimbursement plan:

    • Using the CPMS provided reimbursement.

    • Using TandemDrive to calculate the reimbursement.

      This setup involves the following concepts:

      Cost Components: The specific cost components available for calculations, as defined by the reimbursement plan.

      Calculation input: The actual prices associated with each cost component. Calculation inputs can be set in either the reimbursement plan or the individual reimbursement arrangements. The inputs for the arrangement take precedence over the inputs for the plan for the same cost component.

      For more complex setups scripts and custom components can be used. Contact TandemDrive support for assistance with script development.

    Each customer in TandemDrive has a ‘Reimbursement VAT’ property to determine VAT applicability for reimbursements. This property may also affect the VAT applicability on the MSP side to ensure VAT exempt is applied to both “sides” of a charge session.

  • Roaming

    TandemDrive calculates the roaming costs that you, as the CPO, charge to the MSP. To activate roaming calculations and billing, this functionality must first be enabled in your TandemDrive instance.

    Any roaming agreements valid at the start of the charge session are identified. Roaming costs are then calculated, and the appropriate VAT is determined. If this process fails, the charge session is added to a drop-out case.

    Roaming partners generally receive one invoice per billing period, and, if there are items involving different VAT jurisdictions, separate invoices for each VAT country.

Subscriptions

A CPO subscription can be used to charge recurring fees for services such as charge point hosting and maintenance.

A CPO subscription plan includes a name that clearly reflects how the plan is marketed to customers. Subscriptions are managed separately from reimbursement arrangements.

A subscription plan can include recurring costs, which may be billed either per subscription or per linked, valid EVSE.

The publishing period of a subscription plan indicates when new subscriptions can be initiated. However, TandemDrive does not enforce this restriction when creating new subscriptions.

Roaming

In the context of Electric Vehicles (EVs), roaming refers to the cooperation and data exchange between different EV service providers. This ensures that EV drivers can use any charging station with minimal technological, financial, or legal barriers.

Roaming Connection

A roaming connection is a technical link with an external roaming platform. This platform can represent a single roaming partner (peer-to-peer) or multiple partners (a hub). Roaming connections rely on communication protocols, often using OCPI (Open Charge Point Interface). Roaming connections are sometimes also used for internal charge sessions, where the MSP and the CPO are the same organization. TandemDrive operators manage the technical configuration and infrastructure of these roaming connections.

Roaming Partner

A roaming partner is an entity with whom you can establish a roaming agreement. This partner can be either an MSP, a CPO, or both. Roaming partners exchange data such as authorized charging tokens, charge station information and charge session details. A roaming partner can have multiple CPO ID’s and/or multiple MSP ID’s.

Roaming Agreement

A roaming agreement is a formal contract between different EV service providers, such as MSPs and CPOs, that allows customers from one provider to access the charging infrastructure of another.

Roaming CPO ID and MSP ID

Each roaming partner is identified by one or more “party IDs”, which are registered with EV identifier providers. These IDs consist of a country code followed by three characters (e.g., NL-TDR), with an optional separator between the two. These party IDs are crucial as they are used to assign tokens and charge points, enabling roaming partners to communicate with the correct entity.

Billing and VAT

TandemDrive organizes billable items, like charge sessions, into billing boxes, each representing the contents of a (future) billing document. These boxes are then grouped into review-sets, allowing users to review, approve, and invoice related boxes all at once.

When (almost) all billable items for a given period have been received, it’s time to close the corresponding review-set. Once closed, you can review each box within the set. If any box appears to have an issue, you can mark it as “deferred”, enabling you to approve the rest of the review-set while deferring specific boxes until they are ready. Based on your configuration, TandemDrive will proceed to generate billing documents and mark the approved boxes as “finalized”. Deferred boxes can be approved individually when they are ready for invoicing.

A more in-depth explanation of the individual concepts is provided in the sections below.

Positive and negative monetary amounts

Note: This section applies only to billing API endpoints. Other endpoints may interpret monetary amounts differently.

In TandemDrive, a “seller” (MSP or CPO) provides services to customers and roaming partners via the TandemDrive platform.

  • Positive amounts: indicate revenue to the seller (e.g., customer charging fees).

  • Negative amounts: indicate expenses from the seller (e.g., energy reimbursements or reverted charging fees).

Exception: Some billing documents generated by TandemDrive, such as purchase orders or self-billing invoices, may display negated monetary amounts. This negation only applies to these documents, while the API retains the original amounts. When this occurs, the negate_document_amounts property in the billing box is set to true.

Review-sets

Billing boxes are organized into review-sets, with all boxes in a review-set belonging to the same billing series. A review-set groups billing boxes from the same invoice period. These sets are automatically created when a new period begins (e.g., a new month, quarter, or year). Review-sets can be closed and approved via TandemDrive Console. Approval should only occur after the review-set is thoroughly reviewed, as invoicing begins immediately upon approval.

Besides closing and approving all billing boxes of one review-set in one go, it’s also possible to close and approve a single billing box via TandemDrive Console.

Billing boxes

In TandemDrive, a billing box represents the contents of a billing document, such as an invoice, debit note, or credit note. It is used to gather billable items for a specific party (a customer or a roaming partner), and is tied to a particular invoicing period.

A billing box contains one or more roll-ups, which correspond to the lines that will appear on the invoice. Each roll-up has unique properties, such as product category and VAT kind, ensuring that only similar items are grouped together.

Billable items, such as a charge session, are referred to as billing items. A billing item can have one or more sub-items, representing different cost components. For example, a charge session might include sub-items for energy costs and a one-time fee.

Items with different VAT countries are stored in separate billing boxes.

Box state

A billing box in TandemDrive typically progresses through the following states:

  • Open: TandemDrive can continue adding billing items to this box. It remains open until manually closed.

  • Closed: No new items will be added by TandemDrive. However, manual changes are still possible through the API or Console. The “closed” state allows the box to be reviewed before approval.

  • Approved: The box has been approved, either via the API or Console. Only closed boxes can be approved. Once approved, the box is ready for generating the final billing document, such as an invoice, credit note, or debit note. Document generation can be done either by TandemDrive or an external system.

  • Ready: TandemDrive is done generating documents and the box is ready for invoicing.

  • Finalized: The primary billing document has been created, and the invoice code and date have been set. Only approved boxes can reach this state. If TandemDrive generates the document, it will be available for download.

Transferring boxes

Typically, you’ll want to copy the contents of a billing box into a financial system, such as an invoicing or bookkeeping system.

You can use our API to request boxes that haven’t been transferred yet. Once you’ve successfully stored the box’s content in your system, use the API to mark the box as “transferred”. This process works as a queue, with documents ready for transfer. To prevent duplication, process each box individually, ensuring no box is transferred more than once.

Only approved boxes should be transferred, as boxes that haven’t been approved are still subject to change.

The “transferred” state is distinct from the “finalized” state, as the timing of transfer may vary based on your specific use case. For example:

  • If TandemDrive generates the primary billing document and you’re transferring it to a bookkeeping system that also needs to store the document, the transfer should occur after the box is finalized.

  • If invoices are created outside of TandemDrive, you may want to transfer the box immediately after approval. Later, once the invoice is created, you can mark the box as finalized.

Deferred

The “deferred” state signifies that the box is not yet ready for invoicing. TandemDrive Console users have the option to manually mark boxes as “deferred.” When a box is deferred, it will not be included in the approval process of its associated review-set.

Use this state if there are issues with the box that need resolution before proceeding.

Once any issues with a deferred box have been resolved, the deferred state can be removed, allowing the box to be approved individually.

Withhold

A box may be placed in the “withhold” state to prevent TandemDrive from generating the primary billing document (e.g., the invoice PDF) upon the box’s approval. Other documents will still be generated as needed.

The “withhold” state is helpful for special agreements that require specific formats or contents for their primary billing documents, making the standard document inappropriate. Each party in TandemDrive has a “withhold billing” property, which automatically marks new billing boxes for that party as “withhold.”

When a box is marked as “withhold”, it is still possible to finalize it, using the API, by assigning an invoice code and date and submitting an externally created document.

Unlike “deferred”, which postpones invoicing, “withhold” restricts the generation of the primary billing document by TandemDrive. Once a box is approved, the “withhold” state cannot be newly applied but can still be removed, even after approval, allowing TandemDrive to proceed with generating the primary billing document.

Billing series

A billing series allows you to manage separate collections of billing boxes within the same administration. Each billing series defines its own billing properties, such as:

  • Document type (e.g., invoice, debit note, credit note)
  • Payment period
  • Item roll-up method
  • Billing document templates (such as an invoice PDF template)
  • Invoice code numbering and pattern

Any administration must have at least one billing series to enable billing. The appropriate billing series is selected according to the billing series rules, which can be viewed and modified in TandemDrive Console.

The billing series rules define if a product item is billed. A billing series rule is defined by an administration, validity period, product category and optionally a series indicator and customer reimbursement VAT. For charge sessions the end timestamp of the session has to match the validity period. For recurring costs the start timestamp of the recurring period has to match the validity period.

VAT

A VAT rate defines the VAT percentage for a specific country and VAT kind. Common VAT kinds include: “standard”, “reduced”, “zero”, and “exempt”. VAT rates have validity periods, allowing for percentage changes over time.

The final VAT percentage is re-determined when a billing box is approved, as billing documents are typically sent post-approval and must use the valid percentage at that time. For instance, if the VAT percentage changes on January 1st and a billing box with many items from December is approved in January, the percentage valid in January will be used.

Determining VAT

To calculate VAT, the following steps are performed:

  1. Determine the VAT country
  2. Determine the VAT kind
  3. Determine the VAT rate

In certain cases, this process can be overridden, such as:

  • EU reverse charge applies.
  • The reimbursement for a charge session is VAT exempt. In such cases, the charge session pricing may also be VAT exempt.

VAT country

VAT Country determination

Each seller within TandemDrive can set VAT policies for different countries. These policies define how VAT is handled for various foreign countries. Sellers can choose to use the VAT of the origin country (e.g., the location of a charge session) or the VAT from the seller’s local country. Using the origin VAT country generally means the seller must pay VAT in that country and likely requires a VAT number for that country.

If no policy is defined for a particular country, it can either be flagged as an error that needs resolution or default to the seller’s VAT country. This behavior is configurable for each seller.

VAT kind

Once the VAT country is determined, VAT rules are applied to identify the appropriate VAT kind. VAT rules select the appropriate VAT kind for each item, based on the product category, and optionally the VAT country or VAT indicators. These rules apply to each item’s subtotal. VAT rules have specific validity periods, allowing for changes over time.

VAT rate

Once the VAT country and VAT kind are identified, the VAT rate (percentage) is determined. VAT rates also have validity periods as they may change over time.

VAT examples

Below are examples based on the following setup:

  • The MSP is based in the Netherlands.
  • The MSP has the following VAT policies:
    • Belgium: Use the original VAT country.
    • Norway: Use the original VAT country.
  • The MSP has a fallback VAT policy to apply VAT from the seller’s country (Netherlands).
  • A rate rule for charge sessions applies the standard VAT rate, regardless of the country.
  • The VAT rates for each country are as follows:
    • Netherlands “standard” VAT: 21%
    • Belgium “standard” VAT: 21%
    • Norway “standard” VAT: 25%
    • Germany “standard” VAT: 19%

Examples of charge sessions based on the location of the session:

  • A session in Norway will result in the standard Norwegian VAT rate of 25%.
  • A session in Belgium will result in the Belgian VAT rate of 21%.
  • A session in Germany will result in the Dutch VAT rate of 21% being applied because no VAT policy for Germany was defined.

Corrections in Billing Boxes

Charge sessions appear only once within a single billing box. However, due to corrections, a single charge session might appear in multiple billing boxes.

To illustrate how corrections are handled, let’s consider a scenario with an initial billing box containing a single priced charge session:

Billing Box 1 (Open):

  • Category: MSP Charge session, Count: 1, Volume: 30
    • Charge Session: A, 30.0 kWh, € 18.00

Later, this charge session (A) is repriced to € 17.00. The way this correction is applied depends on whether the original billing box has already been closed (i.e., the invoice has been generated).

Scenario 1: Invoice Not Yet Generated (Billing Box 1 is Open)

If Billing Box 1 is still open when the repricing occurs, the charge session within that box will be directly updated:

Billing Box 1 (Open):

  • Category: MSP Charge session, Count: 1, Volume: 30
    • Charge Session: A, 30.0 kWh, € 17.00

Scenario 2: Invoice Already Generated (Billing Box 1 is NOT open)

If the invoice for Billing Box 1 has already been created, the correction will be handled differently. The original charge session will remain as it was in the closed box, and a reversal and the corrected charge session will be added to the next open billing box:

Billing Box 1 (Closed):

  • Category: MSP Charge session, Count: 1, Volume: 30
    • Charge Session: A, 30.0 kWh, € 18.00

Billing Box 2 (Open):

  • Category: MSP Charge session, reversion, Count: 1, Volume: 30
    • Billing Item: revert, revises A, € -18.00
  • Category: MSP Charge session, Count: 1, Volume: 30
    • Charge Session: A, 30.0 kWh, € 17.00

In this second scenario, Billing Box 2 contains two entries related to the corrected charge session: a reversal of the original charge (€ -18.00) and the new, correctly priced charge session (€ 17.00). This ensures that the financial records are accurate while maintaining a history of the original billing. Consequently, when a correction occurs after an invoice has been generated,
the same charge session (A) will be present in both the original, closed
billing box (Billing box 1) and the subsequent open billing box (Billing box 2).

End users

End users are individuals who utilize apps built on top of the TandemDrive App-API. These apps enable users to view data and perform specific actions such as managing subscriptions, downloading invoices, request charge point information and charging vehicles.

Sites

End users are organized into sites. A site is an external back-end system that provides a user interface for end users, typically in the form of web or mobile applications. Each end user belongs to exactly one site.

End user authorizations

End users can have one or more of the following authorizations:

  • MSP token subscriptions

    The end user is authorized to use this subscription, enabling them to charge a vehicle and view charging session details. However, they may not necessarily be the owner of the contract.

  • Customers

    The end user is authorized to act on behalf of these customers. This includes actions such as adding or canceling subscriptions, viewing invoices, and updating billing information.

Implementation guide

This document serves as a comprehensive resource for developers looking to integrate with our API. The API provides access to key features and services, allowing seamless interaction with the core functionality of the application. Whether you’re building new applications, automating workflows, or integrating third-party services, this guide will walk you through all the essential information to get started.

In the following sections, you’ll find detailed descriptions of the technical details, authentication methods, and best practices to ensure a smooth integration process. We recommend reviewing this guide thoroughly to understand how to implement this API effectively. Do not hesitate to contact us, if you have any questions.

Maintenance

To ensure the stability, security, and performance of our services, the API undergoes regular maintenance, including updates, security patches, and system enhancements. During scheduled maintenance periods, the API may be temporarily unavailable.

When maintenance is in progress, the API will attempt to return an HTTP status code of 503 (Service Unavailable) to indicate downtime. We strive to minimize any disruption, and in most cases, maintenance is completed within minutes.

We recommend that you design your integration to handle brief outages and gracefully retry requests during maintenance windows.

Identifiers

TandemDrive uses universally unique identifiers (UUIDs) for most data records. We use UUID version 7, as defined in RFC 9562. However, records created before July 1, 2023, may have been assigned UUID version 4.

In addition to the TandemDrive-generated identifiers, you can assign your own identifiers to certain types of data. These are called external references, as they originate outside of TandemDrive. External references must be unique, though they can be left empty if not needed. Note that these external_reference fields are case-sensitive.

To view TandemDrive-generated identifiers in the TandemDrive Console, press Shift + Y.

Identifier or prefixed external reference

To simplify API usage without requiring storage of TandemDrive identifiers in your client system, several endpoints accept parameters that can either be a TandemDrive identifier or an external reference. When using an external reference, it must be prefixed with the string ext_ref. — that is, the literal string ext_ref followed by a period (.). This prefix helps distinguish external references.

Identifier or external reference in request body

Similar to the URL parameters, some request bodies can refer to entities using either an .._id or an .._external_reference property. If both options are available in the request body specification, you must provide exactly one of them.

In the following two examples, both creating a billing_address, the reference to a specific party can be specified using either party_external_reference or party_id:

{
  "party_external_reference": "ABC-123",
  "country": "NL",
  "external_reference": "ABC-123",
  "locality": "Amsterdam",
  "valid_from": "2022-06-01T12:00:00+02:00"
}
{
  "party_id": "2479037a-21d1-4c09-827b-5922b8a16a80",
  "country": "NL",
  "external_reference": "ABC-123",
  "locality": "Amsterdam",
  "valid_from": "2022-06-01T12:00:00+02:00"
}

eMobility Identifiers

For the official definition of EVCO ID (also known as eMA ID) and EVSE ID, please refer to the IDACS e-Mobility Identifiers document, d.d. September 2023.

For machine-to-machine communication, IDACS strongly advise against using separators in identifiers like EVSE and EVCO IDs. Separators should only be used to improve human readability in a user interface.

Our API accepts IDs with or without separators. To align with IDACS guidelines, API responses will be updated to return both the original value and a normalized version.

EVCO ID (EV Contract ID), also known as eMA ID

This identifier represents an EV Contract or Electric Mobility Account (EMA). It should not be confused with the Token UID.

For tokens currently issued by TandemDrive, this identifier uniquely represents each token, to avoid revealing the relationship between tokens and end-customer contracts to roaming partners. TandemDrive adheres to the eMA-ID standard, incorporating a check-digit in all issued tokens. For details on its syntax, please refer to the IDACS document mentioned above.

For EVCO IDs issued by external platforms, these requirements may not apply, though compliance with the eMA-ID standard is recommended.

This identifier is sometimes visible to the end user, unlike the Token UID, which is often not displayed.

Examples of syntactically valid EVCO IDs which comply with eMA-ID:

  • NLABCC11222344X
  • NL-TDR-CA1B2C3D4-E

Token UID

The Token UID is a unique identifier used by charging systems to recognize a specific charging token.

It’s crucial to distinguish the UID from the EVCO ID / eMA ID.

  • For RFID Tokens (like a key fob or card), the UID is the serial number physically read by the RFID scanner at the charge point.

    For RFID tokens (NFC / smartcards), the RFID UID typically serves as the Token UID, following the ISO/IEC 14443 standard. Refer to the “AN10927 MIFARE product and handling of UIDs” document published by NXP for more information about NFC UIDs (ISO/IEC 14443).

  • For ISO 15118 Tokens (used for “Plug & Charge”), the UID is the same as the eMA ID, but without any separators.

  • For virtual Tokens (used for remote start/stop), the UID can be randomly generated. Some CPMSs may not support UIDs longer than 20 characters.

Please be aware that this field is case-insensitive. Only printable ASCII characters, excluding spaces, are allowed. Specifically, all characters in these strings must have Unicode code points ranging from U+0021 to U+007E, inclusive.

EVSE ID

The EVSE ID is a reusable identifier for a charging station (EVSE). For example if a charging station is physically replaced or revised, the replacement may retain the same EVSE ID. For details on its syntax, please refer to the IDACS document mentioned above.

This is different from the EVSE UID.

Examples of syntactically valid EVSE IDs:

  • NLABCE987ABC654DEF
  • NL*TDR*E1234567*890

EVSE UID

The EVSE UID provides a unique, permanent identifier for an EVSE across all locations associated with the same CPO ID. It cannot be changed, modified, or renamed. This identifier is intended for technical purposes and should not be confused with the more user-friendly EVSE ID.

EVSE UIDs must be handled in a case-insensitive manner (as per OCPI 2.2 and later versions). Only printable ASCII characters, excluding spaces, are allowed. Specifically, all characters in these strings must have Unicode code points ranging from U+0021 to U+007E, inclusive.

Pagination

Some endpoints support pagination, and to ensure stable, reliable, and efficient performance, we use ‘seek’ pagination. The main advantage of ‘seek’ pagination is that the items within a given page remain stable, even when new data is added, allowing for better performance optimization.

The after query parameter is used to retrieve all items that come after a specified item. In most cases, the id of an item is used for the after parameter, as shown in the example below. The documentation for each endpoint specifies which field should be used for the after parameter.

When polling our paginated endpoints periodically, use the last retrieved value for the next request. Since the after parameter is interpreted in a strict “greater than” (>) sense, an empty page might be returned, indicating there are no new items.

For consecutive pages, the link_next field in the response contains the relative URI to fetch the next set of results. Additionally, the Link: header provides the same relative URI for the next page.

Example of a current page result:

{
    "items": [...],
    "page_size": 500,
    "link_next": "/main/v1/msp-chargesession?after=NLTD1_608C9A13-E3DC-4353-8BEE-7437305BC8D7"
}

To retrieve the next page, use the provided link_next value for your next request: GET https://example-api.on-tandemdrive.com/main/v1/msp-chargesession?after=NLTD1_608C9A13-E3DC-4353-8BEE-7437305BC8D7

Be aware that providing an invalid after value, such as a non-existent identifier, will also result in a 400 Bad Request error.

Initial request

If you want to retrieve all items from the start, you can omit the after parameter in your initial request. Alternatively, if you only want to fetch items created after a specific date and time, use the created_gt query parameter. In this case, the response will not contain any items, but it will include a link_next to the first page of results after the given date-time. If no such items exist, the API will return a “400 Bad Request” error.

It’s crucial to understand that the created_gt parameter is mutually exclusive with all other parameters, including after. Apply any further filtering criteria by appending them as query parameters to the received link_next URL in subsequent requests.

Keep in mind that the created_gt parameter should only be used for the very first request; subsequent pagination and polling should be handled using the after parameter. This requires the API client to maintain a record of the last item id received and include it in the after parameter for the next iteration.

Page size

If one wishes to use a page_size different from the default (currently 500 for most endpoints), the page_size parameter can optionally be specified. See the specification for minimum, maximum and default values for this parameter.

Pagination how-to

Below you’ll find a set of basic pagination examples. All examples use the GET /customer endpoint, but the techniques apply to all endpoints that use pagination.

First endpoint use

To start pagination through a set of results provided by a particular endpoint, use the endpoint URL without any parameters.

$ curl -u $public_id:$secret "$base_url/main/v1/customer"

This results in a page of customer data:

{
  "items": [
    {
      "id": "cd1ddcf5-9858-4f90-b132-094e505c0725",
      "admin_id": "graant",
      "name": "Mr. Grain",
      "country": "NL",
      "entity_type": "person",
      "external_reference": "R00001"
    },
    ...
    {
      "id": "8bb33876-d098-429e-b9ed-77f81ce6db00",
      "admin_id": "silaye",
      "name": "Gardening Enterprise",
      "country": "GB",
      "entity_type": "organisation",
      "company_reg_num": "6984621"
    }
  ],
  "page_size": 500,
  "link_next": "/main/v1/customer?after=8bb33876-d098-429e-b9ed-77f81ce6db00"
}

Retrieve the next page using the link_next value if more data is available. If the number of items is less than page_size, no more data is available. It is important to store the id of the last item so that data retrieval can be continued without duplicates.

Retrieving data starting at a certain moment in time

The created_gt parameter can be used to retrieve data created after the provided moment in time. This is only meant to be used once, normal pagination must use the after parameter.

Note that since the created_gt parameter is used in a strict > sense, it may make sense to choose a moment which is a bit earlier. This helps with including all required records in subsequent requests.

$ curl -u $public_id:$secret "$base_url/main/v1/customer?created_gt=2021-12-20T00%3A00%3A00Z"
{
  "items": [],
  "link_next": "/main/v1/customer?after=cd1ddcf5-9858-4f90-b132-094e505c0725",
  "page_size": 500
}

The first page of customers created after a given moment can be retrieved using:

$ curl -u $public_id:$secret "$base_url/main/v1/customer?after=cd1ddcf5-9858-4f90-b132-094e505c0725"

The created_at parameter of the customer can be used to filter any customers that were created between 20221-12-20 and the 2022-01-01. From this point onward the scenario “First endpoint use” can be used.

Retrieving new data

This scenario implies one has already obtained data using either of the scenarios above. Use the id from your local data to fetch the last customer retrieved. We represent the id as 9999. New data can be retrieved using 9999 as the value for the after parameter:

$ curl -u $public_id:$secret "$base_url/main/v1/customer?after=9999"

Data types

Decimals

For certain values, it’s crucial to avoid treating them as floating-point numbers, which cannot accurately represent all decimal numbers. Floating-point arithmetic can introduce precision errors, making it unsuitable for precise values such as monetary amounts.

To ensure precision, we return decimal values as JSON strings. This forces API users to explicitly handle conversion, ensuring that they choose a data type that preserves decimal accuracy. For more information, refer to RFC7493.

  • Fractional Separator: A . (dot) is used for the fractional part, and no thousands separator is included.
  • Precision:
    • API responses return decimal values with up to 12 decimal places in the fractional part.
    • Inputs can include up to 28 digits, and will be rounded to 12 decimal places.
  • Range:
    • Maximum value: 10¹⁶
    • Minimum value: -10¹⁶
    • Exceptions may apply, and will be explicitly noted when applicable.

Strings

Most string values in the API are automatically trimmed of leading and trailing whitespace. Additionally, these string values must not contain any control characters, except some cases when tabs (\t) and newlines (\n) are allowed.

Each string field has specific minimum and maximum length requirements, which are detailed in the relevant API specifications.

Validity periods

In the API, several entities have an associated “validity period” which defines the time range during which the entity is considered valid. This period is represented by two fields: valid_from and valid_until.

  • Time bounds:

    • The valid_from field indicates when the validity period starts (inclusive).
    • The valid_until field indicates when the validity period ends (exclusive).
    • For example, if a subscription starts at 2023-11-01T00:00+01:00 and ends at 2023-12-01T00:00+01:00, it will be valid throughout November but not in December.
    • The valid_until date must always be later than the valid_from date.
    • In some cases a validity period cannot be “empty”, meaning valid_from and valid_until cannot be the same value.
    • The valid_until field can be null, which means the validity period continues indefinitely (i.e., valid until further notice).
    • In some cases, valid_from can also be null, meaning the validity period is valid from the past indefinitely. If both valid_from and valid_until are null, the entity is considered valid indefinitely (i.e., always valid).
  • Granularity:

    • Validity periods must be specified in whole minutes. Any seconds or fractions will be truncated by the API.
    • Note: For example, if you submit 2023-11-01T23:45:67.912343+01:00, the API will truncate the seconds, and it will become 2023-11-01T23:45:00+01:00.
    • This minute-level precision is intentional because the validity of things like subscriptions and tokens doesn’t benefit from precision beyond minutes. Finer granularity may cause confusion, especially in systems that don’t display seconds or fractions of seconds. As a result, subscriptions cannot begin and end within the same minute.

Stale update prevention

To prevent clients from unintentionally overwriting data due to out-of-order API calls, several endpoints include an optional external_updated_at field in the request body, which is a date-time type.

Since external_updated_at can never be reset, there is an extra check that the field is never set to a date-time in the future. This prevents accidentally making a record immutable.

When an external_updated_at value (e.g., a) is provided, TandemDrive will compare it to the last stored external_updated_at value (e.g., b) for the corresponding record. The request will be processed only if a is more recent than or equal to b (a >= b). If not, the request will be ignored, and a 204 No Content response will be returned. This mechanism ensures that outdated updates do not overwrite newer changes.

Merge endpoints

The response contains a new field called stale_update if the request was an update. This field specifies whether the request was a stale update or not.

Update Endpoints

TandemDrive provides two types of endpoints for performing updates:

  1. Merge-Update Endpoints: These endpoints merge the provided values with existing field values using the POST HTTP method, enabling partial updates for greater flexibility and less room for mistakes.

  2. Replace-Update Endpoints: These endpoints replace all field values in the target resource and use the PUT HTTP method. Since this approach can be challenging to implement correctly, we introduced the Merge-Update alternative for updating records.

    These endpoints replace all values for fields defined in the endpoint schema. Optional fields omitted from the request are reset to their default values, which is null unless otherwise specified.

    These endpoints are deprecated and should not be used for new implementations. A migration guide is provided for existing usage of these endpoints. TandemDrive plans to remove these endpoints once all customers have migrated to using the ‘Merge-Update’ alternative.


Merge-Update Endpoints

Merge-Update endpoints allow updates to only the fields specified in the request body. These endpoints use the POST HTTP method and conform to the JSON Merge Patch RFC.

Behavior

  • Fields not included in the request body remain unchanged.
  • Fields that cannot be modified:
    • Immutable fields are ignored if an update is attempted. For example, this applies to fields which can only be set upon creation.
    • In a future version of our API, attempts to modify these fields may result in an error unless the provided value matches the field’s current state, ensuring consistency and reducing the likelihood of mistakes.
    • Updating an immutable field to its existing value is always fine.

Refer to the endpoint-specific documentation for details on this behavior.


Replace-Update Endpoints (deprecated)

Warning: Replace-Update endpoints are more challenging to use correctly. We highly recommend using Merge-Update endpoints for simpler and safer updates. We intend to remove the Replace-Update endpoints once they are no longer in use. A migration guide is provided for existing usage of these endpoints.

Replace-Update endpoints use the PUT HTTP method. According to the HTTP RFC on Idempotent Methods, PUT is idempotent, meaning repeated identical requests yield the same result.

A PUT request replaces all values for fields defined in the endpoint schema. Optional fields omitted from the request are reset to their default values, which is null unless otherwise specified.

Example Workflow:

To update only a subset of fields:

  1. Use a GET request to retrieve the current values.
  2. Modify the desired fields.
  3. Submit the updated data via a PUT request.

External References vs TandemDrive IDs

TandemDrive allows customers to use their own unique identifiers, referred to as external_reference, for nearly all stored records. For more information, see Identifiers.

Specifying a Unique Identifier

To update records via the update endpoints, you must provide a unique identifier in the path parameter. The behavior depends on whether the identifier is a TandemDrive ID or an external_reference.

Using a TandemDrive ID as the Path Parameter

  • If the specified record does not exist, the update will fail, and no new record will be created.
  • You can modify the external_reference of the record when using a TandemDrive ID as the path parameter.

Using an External Reference as the Path Parameter

  • Use the prefix ext_ref. (including the dot) for the path parameter which supports using an external_reference.
  • If the record does not exist, TandemDrive will create it automatically.
  • When an external reference is used as the path parameter:
    • It overrides any external_reference specified in the request body.
    • The record will inherit the path parameter’s value as its external_reference.
    • It is not possible to update the external_reference to a different value.

Migration Guide: Transitioning from Replace-Update to Merge-Update

TandemDrive is streamlining its API by replacing the “Replace-Update” mechanism with a more robust and user-friendly “Merge-Update” approach. The “Replace-Update” method, while functional, presents complexities in implementation and potential for unintended data loss.

Action Required: Migrate to Merge-Update Endpoints

We strongly encourage all customers to transition from the existing “Replace-Update” endpoints to the new “Merge-Update” equivalents. In many cases, the migration is as simple as changing HTTP PUT requests to POST requests.

Key Change: Handling Unspecified Fields

The primary difference lies in how unspecified fields are handled. Under “Replace-Update,” omitted fields were often reset to their default values (typically null). This behaviour was documented but this is easily missed, and it’s challenging to implement correctly. With “Merge-Update,” only the fields explicitly included in the request are modified. Existing data in unmentioned fields remains unchanged.

Simplifying Specialized Endpoints

We’ve also consolidated several specialized endpoints that targeted single or small sets of fields. These are now superseded by the more versatile “Merge-Update” endpoints. To achieve the same functionality, simply include only the desired fields in your POST request.

The endpoints mentioned in the two lists below will be removed, once everyone has migrated to the “Merge-Update” endpoints.

“Replace-Update” endpoints

The endpoints in the list below use the “Replace-Update” approach. In most cases you can switch to the “Merge-Update” approach by replacing PUT with POST:

  • PUT /main/v1/customer/{id}

  • PUT /main/v1/billing-address/{id}

  • PUT /main/v1/billing-direct-debit-mandate/{id}

  • PUT /main/v1/cpo-reimbursement-arrangement/{id}

  • PUT /main/v1/cpo-reimbursement-link/{id}

  • PUT /main/v1/cpo-subscription/{id}

  • PUT /main/v1/cpo-subscription-evse-link/{id}

  • PUT /main/v1/msp-token/{evco_id}

  • PUT /main/v1/msp-token-subscription/{id}

  • PUT /main/v1/msp-token-link/{id}

  • PUT /main/v1/end-user/{id}

  • PUT /main/v1/end-user/{id}/authorization-customer becomes POST /main/v1/end-user/{id}/authorization-customer/{customer_id}

  • PUT /main/v1/end-user/{id}/authorization-msp-token-subscription becomes POST /main/v1/end-user/{id}/authorization-msp-token-subscription/{subscription_id}

Endpoints which are no longer necessary

The endpoints below were designed to update only a single field, or a select set of fields. This can now be achieved with our “Merge-Update” endpoints by only mentioning the fields that should be updated. The DELETE variants of the endpoints below can be replaced by explicitly setting a field to the null value.

  • PUT /main/v1/cpo-reimbursement-arrangement/{id}/valid-from

  • PUT /main/v1/cpo-reimbursement-arrangement/{id}/valid-until

  • DELETE /main/v1/cpo-reimbursement-arrangement/{id}/valid-until

  • PUT /main/v1/cpo-reimbursement-link/{id}/valid-from

  • PUT /main/v1/cpo-reimbursement-link/{id}/valid-until

  • DELETE /main/v1/cpo-reimbursement-link/{id}/valid-until

  • DELETE /main/v1/cpo-reimbursement-link/{id}/valid-from

  • PUT /main/v1/cpo-subscription/{id}/valid-until

  • PUT /main/v1/cpo-subscription/{id}/valid-from

  • DELETE /main/v1/cpo-subscription/{id}/valid-until

  • PUT /main/v1/cpo-subscription-evse-link/{id}/valid-until

  • PUT /main/v1/cpo-subscription-evse-link/{id}/valid-from

  • DELETE /main/v1/cpo-subscription-evse-link/{id}/valid-until

  • DELETE /main/v1/cpo-subscription-evse-link/{id}/valid-from

  • PUT /main/v1/msp-token-subscription/{id}/valid-from

  • PUT /main/v1/msp-token-subscription/{id}/valid-until

  • DELETE /main/v1/msp-token-subscription/{id}/valid-until

  • PUT /main/v1/msp-token-link/{id}/valid-until

  • PUT /main/v1/msp-token-link/{id}/valid-from

  • DELETE /main/v1/msp-token-link/{id}/valid-until

  • DELETE /main/v1/msp-token-link/{id}/valid-from

Unknown fields

When the API receives JSON containing fields that are not recognized or used by the endpoint, it will — on a best-effort basis — return an X-TD-Unknown-Fields header. This header contains a comma-separated list of the unrecognized fields.

While receiving unknown fields is not considered an error, it may indicate a potential issue, such as incorrect or misspelled field names. Monitoring this header can help identify and correct such mistakes.

For example, if the header returns valid_fro,name, it suggests that the valid_fro field was misspelled (it should be valid_from), and the field name is not used by this endpoint.

Implicit fields

When the API does not receive a field which is documented to be optional, it will use the default value for that field. If the default value of a field is undocumented, then the default value of that field is null.

Some fields that are optional in the API should not have been optional to begin with as not specifying these fields can have unintended results. The most significant examples of this are fields on the “Replace or Create” endpoints. See Replace-Update endpoints for more details.

Fields can not be made required without breaking users that rely on the fields being optional. That is why we have added the X-TD-Implicit-Fields header. This header is added to the response when a field was not provided even though the field should be required (much like the X-TD-Unknown-Fields header). Importantly, this allows users to detect potential issues without breaking anyones implementation.

In the future, we intend to make these fields required from the start. Existing fields can be made required gradually when there is enough evidence that no user relies on the field being optional.

Advanced data corrections

The API provides powerful mechanisms to ensure data consistency, but not all actions automatically bring the complete system into a consistent state. For certain complex scenarios, the API offers advanced endpoints that provide enhanced consistency guarantees.

For example:

If you assigned a token to the wrong subscription, you would typically correct this by making two API requests: first, marking the incorrect token link as defunct, and then creating the correct token link. In these cases, the API will not automatically reprice the affected charge sessions after the first request, as it doesn’t know when you’ve completed the correction process.

For such advanced corrections, a special endpoint is available that allows you to complete the entire process in a single request. This endpoint not only performs the correction but also ensures that related data, such as pricing for charge sessions, is automatically updated for consistency.

It’s important to note that these automatic corrections are an advanced feature and are not available for all operations. For many tasks, you’ll need to follow the manual process to ensure data integrity.

Compatibility

RFC 1122/1123: “Adaptability to change must be designed into all levels of Internet host software.”

Our API follows a single versioning scheme. A new version is only introduced when major, backwards-incompatible changes are necessary. The API version is included in the URL path, so all paths in the specification are prefixed with /main/v1.

API and product versioning

  • API Version:

    The version number is part of the URL (e.g., /main/v1).

    To view the available API versions, use the /main/version endpoint. Note: This specific endpoint does not include the version prefix (/vN).

  • Product Version:

    To retrieve the current product version (distinct from the API version), check the X-TD-Version custom header in responses.

Versioning Notifications

TandemDrive will notify customers in advance when a new API version is planned. During transition periods, TandemDrive supports two concurrent API versions to ensure a smooth migration.

Backwards-compatible changes

A change is considered backwards-compatible if it does not break or require modifications to existing client implementations that conform to previous API releases. As the API evolves to support new features and services, we strive to maintain compatibility with previous releases. The following changes are considered backwards-compatible:

  • New resources: Adding new API endpoints.

  • Optional parameters: Introducing new optional request parameters to existing API methods.

  • Response properties: Adding new properties to existing API responses.

  • Enumerated fields: Extending predefined sets of values in enumerated fields. API clients should handle new values gracefully.

  • Property order: The order of properties in API responses is arbitrary and should not be relied upon.

  • String formatting: Changes in string formatting that don’t alter meaning (e.g., changes in case-insensitive identifiers or optional separators).

  • Human-readable messages: Modifying error messages or other human-readable strings.

  • Webhooks: Adding new event types. Your webhook listener should handle unfamiliar event types without failure.

  • Unused endpoints: Modifying endpoints that have not yet been used in production allows for iterative improvements based on feedback.

  • Required fields: Changing previously required parameters or fields to optional, as long as it doesn’t compromise the integrity or functionality of the API.

Preview endpoints

New features and endpoints in TandemDrive often start in preview mode, clearly marked as such in the API specification. See Preview mode for what the implications of using such features and endpoints are.

Handling breaking changes

While we prioritize backwards compatibility, there are rare occasions where breaking changes are required to enhance the API or introduce significant new features without warranting a full version change. In these cases, we take a customer-first approach to minimize disruption:

  1. Coordination and support: We work closely with each customer to coordinate the timing of the deployment, ensuring it aligns with their schedules and needs. Our team provides guidance and assistance to facilitate a smooth transition.

  2. Transition period: When possible, we provide a transition period where both the old and new versions are supported, allowing customers to gradually adapt to the changes without immediate impact.

  3. Testing and feedback: We encourage customers to test changes in their TandemDrive test instance before full deployment, and we remain open to feedback to address any concerns or issues that arise.

By taking this collaborative approach, we aim to ensure that breaking changes are handled as smoothly and efficiently as possible, minimizing impact on our customers’ operations.

Security

We take the security of our HTTP API seriously to ensure the protection of your data and operations.

Secure Communication

All API communications are secured using Transport Layer Security (TLS) to safeguard data in transit. We enforce TLS to prevent eavesdropping and tampering by third parties. Ensure that your HTTP client supports the latest TLS versions (TLS 1.2 or higher) and is updated regularly to stay protected against known vulnerabilities.

Authentication

Access to the API requires authentication through a dedicated API account. Each account is issued a pair of credentials, consisting of an API ID (similar to a username) and a secret key. It is crucial that the secret key remains confidential and is not exposed to unauthorized individuals or systems. We recommend storing the secret key securely, to avoid unintentional exposure. Never embed credentials directly in client-side code or public repositories.

These credentials must be included in each API request using the HTTP Basic Authentication scheme.

Authorization

Each API account is restricted to a specific set of functionalities and access to TandemDrive administrations. It’s recommended to setup your API accounts with the minimum necessary privileges to fulfill their designated tasks.

Server-to-Server Interaction

Our APIs are designed for server-to-server communication. It is recommended that API access is limited to trusted back-end systems and services. Avoid making direct API requests from client-facing applications like web browsers or mobile apps.

Incident Response and Vulnerability Reporting

If you suspect that your API key has been compromised or if there is any uncertainty about its security, contact us immediately. Quick action can help mitigate potential risks. For security incidents, vulnerability reports, or any concerns related to the security of the API, please reach out to our security team at security@tandemdrive.com.

By adhering to these practices, you can help maintain the integrity and security of your integration with our API.

HTTP and JSON

The API relies on HTTP for transport and uses JSON for data serialization.

JSON

All API messages must comply with RFC7493: “The I-JSON Message Format”. This RFC outlines key improvements for JSON interoperability. For reliable API usage, ensure that the JSON data you send adheres to this standard.

HTTP

We recommend using HTTP/1.1 or HTTP/2 for optimal performance. To improve efficiency, we encourage clients to maintain persistent connections between requests and limit the number of simultaneous open connections.

Key considerations for interacting with the API over HTTP:

  • URL structure: Our API endpoints do not include a trailing slash (/).
  • Case-insensitive headers: HTTP header names are case-insensitive, so clients should not rely on the case of header names.
  • Standard HTTP requirements: Ensure proper encoding of query string parameters and compliance with standard HTTP protocols.
  • Filtering results: When using query parameters to filter results, the conditions are combined using a logical AND operation.

HTTP Status Codes and Error Responses

Understanding the first digit of the HTTP status code is essential. Refer to RFC9110 for more detailed information.

Here are the most common status codes returned by the API:

200 - OK                The request was successful.
400 - Bad Request       The request is invalid, typically due to missing or
                        incorrect parameters, or referencing a non-existent
                        object. The response body will provide more details.
401 - Unauthorized      No valid API key was provided.
403 - Forbidden         The API key does not have sufficient permissions to
                        complete the request.
404 - Not Found         The requested resource with that URI doesn't exist.
                        Because a non-existing URL pattern was used.
429 - Too Many Requests The client has sent too many requests in a short period.
                        We recommend implementing exponential backoff strategies.
500, 502, 503, 504      Server Errors: Something went wrong on TandemDrive's
                        side. The `503` code is returned during maintenance.
                        In all cases, the client can retry the request later.

For 4xx status codes, the API aims to return a detailed error response in JSON format. We recommend that clients use the information in the response body, rather than relying solely on the HTTP status code, to decide on the next course of action. Check for the Content-Type: application/json header to determine if a structured JSON error message is being returned.

Rate limits

To ensure system stability and protect against overloading, our API enforces rate limits. If these limits are exceeded, the API will return an HTTP status code 429 - Too Many Requests.

Rate-limited Actions

The following actions are subject to rate limiting:

  • Simultaneous connections from the same IP address.
  • Simultaneous connections from the same API account.
  • Requests per second from the same IP address.
  • Requests per second from the same API account.
  • New connections per second from the same IP address.

Best practices

To avoid reaching the rate limits and to maximize performance:

  • Minimize simultaneous connections: Typically, using more than a few concurrent connections will not significantly improve performance.
  • Use persistent connections: Reuse existing connections for consecutive API requests to reduce overhead and improve efficiency.

Default rate limits (as of 2024-02-29)

  • Requests per second: The default limit is 200 requests per second, with allowance for bursts of up to 500 requests per second.
  • Simultaneous connections: The default maximum is 64 concurrent connections.

Need higher limits?

If your application requires higher rate limits, please contact us to discuss increasing your allocated resources.

App-API

Architecture

Each end user application must have its own dedicated back-end service. This is essential because the App-API does not provide direct user authentication. Instead, the app’s back-end is responsible for:

  • Authenticating the end user.
  • Interfacing with the TandemDrive App-API on behalf of the authenticated user.

By separating authentication from the API, the architecture allows each app to manage user sessions while the App-API handles data and actions related to those authenticated users. The app back-end can also provide services not offered by the TD App-API.

The API automatically restricts the data returned to only what the end user is authorized to access. If the user attempts to access or modify unauthorized data, the system will return an appropriate error (bad request).

Most endpoints require the end user identifier as part of the URL path to ensure that operations are tied to the correct user.

Diagram of the App-API

Offset Pagination

The app API uses offset-based pagination, in contrast to the seek-based pagination used by the admin API. Offset-based pagination is straightforward because the client does not need to track the ID of the last item and can navigate to any page by specifying the page number. Page numbering starts from 1.

Page Parameter

The optional page query parameter specifies the page to retrieve. It defaults to the first page (page=1).

Page Size Parameter

The optional page_size query parameter adjusts the number of items returned per page, with a default of 50. Check the specifications for the maximum allowed value of this parameter.

Example Response

The following json data is an example of an offset-based pagination response:

{
    "items": [...],
    "page": 1,
    "page_size": 50,
    "has_next_page": true
}
  • items: Contains the list of items returned by the request, with a maximum number of items equal to page_size.
  • page: Shows the current page number.
  • page_size: Shows the number of items on the current page.
  • has_next_page: Returns true if there is a page after the current one with at least one item, making it useful for “Next Page” buttons in user interfaces.

Preview Mode

Preview Mode

Our API offers preview endpoints and optional preview features within existing endpoints. These are new functionalities we believe are ready for initial use. Your feedback can help us refine the feature before we remove the preview mode. Note that backwards-incompatible changes may be introduced during the preview period.

Contact us

We highly recommend reaching out to us before using any preview endpoint or feature. Collaborating with us provides the following benefits:

  • Insight into our plans: We’ll provide insights into the feature’s intended purpose and highlight any aspects still under consideration.
  • Shaping the feature: Share your use case and requirements with us to help shape the endpoint’s final design and functionality, ensuring it better serves your needs.

Webhooks

Note: This is a “preview” feature and may be subject to change.

Introduction

Webhooks allow API users to receive timely updates without constant polling. You can register a URL to receive a POST request whenever there are changes in the data exposed by the API.

Message structure

Each message has a generic envelope that wraps the data of the event. The data uses the same schema as the API’s endpoints. Here’s an example of a customer update message:

{
  "id": "5615894c-56ba-73ac-8fc8-4120262d7fd9",
  "occurred_at": "2022-11-01T01:02:45.123Z",
  "modification_kind": "update",
  "kind": "customer",
  "data": {
    "id": "ca904ea7-4281-714e-a3cd-72df1d61907b",
    "admin_id": "tdr-france",
    "name": "TandemDrive BV",
    "country": "NL",
    "entity_type": "organisation",
    "company_reg_num": "C123456",
    "external_reference": "ABC-123",
    "created_at": "2022-11-01T01:01:45.123Z",
    "updated_at": "2022-11-01T01:02:45.123Z",
    "billing_email": "info@tandemdrive.com",
    "billing_phone": "+31123456789",
...
  }
}

Object deletions, contain properties to identify the object that was deleted in data. See Deletions below.

For the details about the structure of the envelope see the API specification.

Webhook registration

When subscribing to a webhook, you can specify which event kinds to receive. If different endpoints need updates for different kinds, multiple webhook subscriptions can be registered. While messages for each subscription are guaranteed to be sequential, there is no guarantee of the order between different subscriptions.

Messages may be sent concurrently to different webhook subscribers. However, for each individual subscriber, only one message is sent at a time to ensure that events are processed in the exact order they occurred.

Responses

The HTTP response code confirms the receipt of the update. Non-2xx responses will trigger retries by TandemDrive.

As a webhook receiver, you should reject messages only in rare cases like authentication failures or system errors. In all other situations, respond with a 2xx status code to avoid disrupting the delivery of subsequent messages due to a single rejection.

Webhook endpoints must support at least HTTP 1.1 and TLS 1.3. Accepting compressed HTTP requests is recommended for efficiency.

Authentication

To ensure security, you must verify that events originate from TandemDrive. This can be done using configurable headers like X-Api-Key or Authorization. The receiving URL should use https:// with a valid certificate.

Rapid Events

For rapid successive updates it may be possible that we either de-duplicate events of the same object as a single event, or send one event per update but each with the same state of the object.

Deletions

In deletion events, only the object identifier and external reference (if available) are included:

{
  "id": "5615894c-56ba-43ac-8fc8-4120262d7fd9",
  "occurred_at": "2022-11-01T01:02:45.123Z",
  "modification_kind": "delete",
  "kind": "customer",
  "data": {
    "id": "ca904ea7-4281-414e-a3cd-72df1d61907b",
    "external_reference": "C0001234"
  }
}

However this rule has some notable exceptions, for objects where there exists a domain identifier that is used instead of the id generated by TandemDrive. The list of object to which this exception applies:

ObjectIdentifying property name
msp-tokenevco_id
cpo-evseevse_uid
cpo-evse-locationlocation_id
cpo-evse-connectorconnector_id

Performance

Prepare to handle potentially high volumes of messages. TandemDrive sends data in batches (e.g., every 10 seconds) via a single TCP+TLS connection. Be sure to quickly deliver a 2xx response to avoid timeouts.

Handling duplicate events

Our webhook system will retry delivery until your endpoint returns a successful response. If we don’t fully process your response, the same message may be sent more than once. To handle potential duplicates, ensure your event processing is idempotent by tracking the ids of received events and skipping those already processed.