JouloDocs

API changelog

Every change to the Partner API, the CPO API and the personal API that you may need to act on, newest first.

We change the API often. Almost every change adds something: a new endpoint, a new optional field, a new value in a response. Those changes need nothing from you, as long as your client ignores fields and values it does not know.

A breaking change removes or renames something, or makes a request stricter. We announce each one here first, with the date it takes effect on production. We agree that date with the partners who use the endpoint. Until that date the current behaviour keeps working.

The sandbox runs the same code as production, so it changes on the same day. There is no preview period on the sandbox.

The API reference comes from one OpenAPI description. CI compares every change to it with oasdiff and refuses a breaking change that has no entry on this page.

2026-11-06

  • New: a replacement product can cost more in some postal code areas, because installers there ask more. GET /partner/replacement-offers gives base_price_cents, regional_extra_cents and regional_prices on each product. For one customer, price_cents is now the price at their postal code, and POST /partner/replacement-orders charges that price. Nothing changes for a customer outside every area.

2026-11-04

  • Changed: we lifted the restriction on customers whose laadstation Road manages, which we announced on 23 October. GET /partner/ere-positions includes them again, with your external_ref. GET /partner/customers shows them in full again, and managed_by_road is false for every customer. Treat a customer who is missing from GET /partner/ere-positions as hidden, not as a failed registration: the customer can have objected to sharing, or revoked your app. Read GET /partner/drafts for the claim status.
  • New: POST /partner/chargers/disable, for a pre-coupled BlueBird charger that changed owner. It disables the charger on your customer's account and frees its charger_id for your next upload. See When a charger changes owner.
  • Changed: POST /partner/customers no longer rejects a charger_id as charger_claimed when the only account that held it has disabled it.

2026-11-03

  • New: phone and iban on POST /partner/customers and POST /partner/drafts/import. At activation we put both on the customer's account while it holds none, so the registration form opens with them filled in. A value that fails the check is dropped and named in the new contact_ignored response field. See Sending the phone and the IBAN.
  • Changed: an auth-link resumes an activated draft while the IBAN on the account is still the one you sent. Before, any stored IBAN refused the link with account_has_details.

2026-11-02

  • New: first_name and last_name on POST /partner/customers and POST /partner/drafts/import. At activation we put the name in the customer's profile, so the registration form opens with it filled in. A name alone does the same when it looks like a first and a last name. See Sending the name.
  • Changed: in the in-app flow, a customer who couples a charger on our coupling page now returns to the flow by themselves. Before, they stayed in the Joulo dashboard and never reached your return URL. This holds for every brand except BlueBird Power, which couples inside the flow.

2026-10-31

  • New: connection_status in GET /chargers can be awaiting_feed, for a SolarEdge charger that waits for its first weekly file from SolarEdge. It read never_connected before. SolarEdge chargers also carry feed, with the Sunday the first file should come. See connection status.

2026-10-30

  • Changed: a draft whose email address belongs to a customer who already chose you as energy supplier now becomes active within 15 minutes. Before, we checked only when you uploaded the draft or requested an auth-link, so a customer who missed one condition at that moment stayed on invited. See the app flow.

2026-10-29

  • Changed: when a row in GET /partner/ere-positions carries your external_ref with external_ref_match: "ean", and the customer signed the authorisation and submitted their registration, we activate your draft on that account. The draft then has status active in GET /partner/drafts, also when it was claimed, conflict or active on an empty account. The row moves to external_ref_match: "claim" and source: partner_upload. We check every 15 minutes and when you request an auth-link. See the app flow.

2026-10-28

  • New: external_ref in GET /partner/ere-positions also appears on a customer who did not claim your draft but registered on another Joulo account with the EAN of that draft. We show it only when exactly one Joulo account holds that EAN, our EAN check matched it, the customer picked you as their energy supplier, and the surname in your upload matches the surname on that account. The new field external_ref_match says how the reference got there: claim or ean. source does not change. See ERE positions.
  • Changed: a customer who revokes your app under Connected apps in their Joulo account drops out of GET /partner/ere-positions. Their row in GET /partner/customers keeps only customer_ref, attached_month and partner_share_pct, and has_charger is absent. Your share on the customer stays. Signing in to your app with Joulo again brings both rows back. See ERE positions.

2026-10-25

  • New: optional location on POST /v1/chargers, the formal address of the charge point: postal_code and house_number, plus house_number_addition when there is one. street and city are optional. Joulo compares it with the address of the account's connection and returns location_check (match, mismatch or unchecked) with location_check_reason. A mismatch never rejects the request: the charger is stored and Joulo reviews it. See Location check.
  • New: the CSV delivery documents the address columns Street, House Number, Postcode and City, plus an optional House Number Addition. See Charge point location.

2026-10-23

  • New: managed_by_road on each customer in GET /partner/customers. It is true when Road manages the customer's laadstation, through E-Flux or a portal that Road runs. Road's terms keep that data inside the ERE service, so such a row carries only customer_ref, attached_month and partner_share_pct. The other fields are empty, source is self_signup and has_charger is absent. Your share on the customer stays.
  • Changed: GET /partner/ere-positions leaves out a customer whose laadstation Road manages.

2026-10-21

  • New: GET /partner/replacement-orders gives one order or a page of them, with one status ladder (awaiting_payment to completed) and the installer's name and phone from scheduled on. POST /partner/replacement-orders/cancel closes an unpaid order. On the sandbox, POST /sandbox/v1/replacement-orders/{id}/advance walks the ladder without Mollie. See Status.
  • New: POST /partner/replacement-orders orders Joulo's replacement offer for one of your customers. The answer carries a checkout_url: a Joulo page your app opens, where the customer pays through Mollie and comes back to your app with ?status=paid|cancelled|failed&order=<order_id>. One open order per customer, idempotent on external_ref, 30 minutes to pay. See Ordering.
  • New: GET /partner/replacement-offers gives Joulo's own offer for replacing a laadstation without a MID meter, the same products and prices as joulo.nl/vervangen. With a customer_ref it names the laadstation for that customer, with same_brand first and regional alternatives only inside their service area. Without one it lists the catalogue. Read-only: ordering through the API follows later. See Replacement offer.

2026-10-19

  • Breaking for energy partners: POST /partner/customers and POST /partner/drafts/import refuse a row from an energy partner without an EAN, with reason missing_ean and a count in skipped.missing_ean. Without an EAN an energy supplier no longer vouches for the customer, so they get the iDIN step. An address alone does not vouch anymore. See bank verification.
  • For an energy partner the upload names rows without a tenaamstelling or a contract start date in missing_recommended. A warning: the draft exists.
  • POST /partner/customers and POST /partner/drafts/import take three optional fields per customer: account_holder_name (the tenaamstelling), supply_start_date (the start date of the energy contract on the EAN) and move_in_date (the day the customer moved to the address). GET /partner/drafts returns all three. A date we cannot read never rejects the row: it is named in dates_ignored. See Tenaamstelling, contract start and move-in date.

2026-10-17

  • Breaking, effective today: the brand added on 2026-10-08 is now called label. On the same customer row, brand_asked_at and replacement_offer.brand are about the laadstation, so one word had two meanings. No partner had sent a brand yet. What changes:
    • GET /partner/brands is now GET /partner/labels, and answers { "labels": [{ "label", "name" }] }.
    • POST /partner/customers and POST /partner/drafts/import take label per row, return label in created[] and report the match in labels.
    • GET /partner/drafts, GET /partner/customers and GET /partner/ere-positions return label instead of brand.
  • POST /partner/customers and POST /partner/drafts/import fail with HTTP 500 when Joulo cannot check the label values on the rows. Nothing is stored, so you can send the request again. Until today the rows went in without a label, every label was reported as unknown, and a resend was refused as already_drafted.
  • Without an upload, a customer gets a label only when their choice of energy supplier on joulo.nl links them to you. Not every label in GET /partner/labels does that yet. See Labels.

2026-10-15

  • An energy supplier that uploads a customer with their EAN or address vouches for who they are. That customer never gets the iDIN step: not in your app, not on the claim page and not in the sandbox. Until today the sandbox asked iDIN of every new customer, so POST /sandbox/v1/drafts/{id}/claim with submit: true could fail with submit_failed and reason idin_required. For other partners nothing changes. See bank verification.

2026-10-13

  • An activation that ends in ean_conflict no longer changes the customer's account. Since 2026-10-08 it could still make your company their energy company in Joulo, so the customer appeared in your GET /partner/ere-positions while their draft was on conflict. We now check the EAN before anything is written. See what comes back to your app.

2026-10-12

  • GET /chargers and the MCP tool list_chargers return access on OCPP chargers: the "Laden & toegang" choice the dashboard shows (no_tag, any_tag or own_tags), who chose it, and what the charger itself reported at its last reading. See Chargers.
  • GET /chargers and the MCP tool list_chargers say whether Joulo hears from a charger. status keeps its meaning (the record is active), and its description now says so: it is not the link. New: connection_status, last_seen_at, last_seen_basis, history_paused_since, connection_help_url and connection_action_url, with the rules of the dashboard card. docs_url now appears on every charger whose brand has a manual. See Chargers.
  • GET /sessions returns meter_start_wh and meter_stop_wh, and current_session in GET /chargers returns meter_start_wh. See Sessions.
  • New: GET /registration-status and the MCP tool get_registration_status. Per step, what is done and what is still open before Joulo can sell your ERE, in the order of blocked in GET /ere-position. Requires position:read, so existing connections need no new permission. See Get where your ERE registration stands.
  • GET /chargers and the MCP tool list_chargers tell an open session apart from energy flowing. is_charging keeps its meaning (a session is open). New: charging_state (charging, paused, idle, unknown), power_kw and last_energy_at, all from the OCPP meter. On a Sunday morning only 75 of 516 open sessions had a moving meter; the rest were full cars. See Chargers.

2026-10-11

  • In the dashboard the MCP connector card moved from Developer > API to its own tab, Developer > MCP. The server and the API do not change. See AI assistants (MCP).
  • GET /ere-position names why a forecast is 0 while the quarters show production. blocked.reason was iban or null; it now also takes registration, mandate, ean and review, and blocked.by_reason lists every reason with its ERE. An account that only misses its IBAN reads exactly as before.
  • GET /sessions reports the live kWh of a running session, read from the same meter as GET /chargers. It said 0 until the session ended.
  • GET /chargers returns brand and model for each charger, and the MCP tool list_chargers returns them too. Both are null when Joulo does not know them. The reference now also lists mid_eligible, which the endpoint already returned. See Chargers.

2026-10-10

  • AI assistants can read the personal API through the Joulo MCP server at https://joulo.nl/mcp. It offers five read-only tools on top of GET /me, /chargers, /sessions, /energy and /ere-position. See AI assistants (MCP).
  • The OAuth2 server accepts dynamic client registration and public clients without a client_secret. Clients that Joulo created for you work as before. See OAuth2.

2026-10-09

  • GET /partner/ere-positions and GET /partner/customers say why a connection yields no ERE. mid_status is mid, no_mid, unknown or none. With no_mid, replacement_offer names the laadstation with a MID meter from your own store that fits this customer, with its price and product page, so your app can show the customer what to do next. Both fields are new and optional to read. See ERE positions.

2026-10-08

  • A partner with several brands can send a brand per customer. New: GET /partner/brands lists your brands. POST /partner/customers and POST /partner/drafts/import accept an optional brand and report the match in brands. GET /partner/drafts, GET /partner/customers and GET /partner/ere-positions return brand. The brand is display only: the fee, the payout and the attribution follow the partner. See Labels.
  • A customer who is already with Joulo can activate from your app. With send_invite: false, POST /partner/customers and POST /partner/drafts/import no longer refuse an address whose Joulo account holds data. The row becomes a draft, and the answer is the same as for a new address. In your app the customer signs in with their own account and confirms. With send_invite: true such an address still gets existing_user. See the app flow.
  • Activating the draft of an energy supplier makes that supplier the customer's energy company in Joulo, also when another one was set. The customer then appears in your GET /partner/ere-positions with your external_ref.
  • An energy supplier sees a customer only while it is that customer's energy company in Joulo. A customer who switches supplier leaves the old supplier's GET /partner/ere-positions and GET /partner/customers, also when they first came in through that supplier. Until today such a customer stayed in both. Installers and other partners keep their customers. See what a switch of energy supplier changes.
  • A customer who activated one of your drafts is in your GET /partner/customers, also when another partner brought them in first. Until today such a customer was missing from the list.
  • POST /partner/customers and POST /partner/drafts/import no longer refuse a row for its EAN. conflict no longer comes back, and skipped.conflict is always 0. Only the activation checks the EAN: when another customer holds it, the app flow ends in ean_conflict and the draft moves to conflict. Another partner's draft of the same customer never blocks an activation.

2026-10-05

  • POST /partner/customers and POST /partner/drafts/import refuse fewer rows with existing_user. An existing Joulo account without a charger, charging sessions, a signed authorisation or a submitted registration no longer blocks the row. When the customer opens your auth-link, we replace that empty account and the flow continues. Nothing changes in the request or the response. See the app flow.
  • POST /partner/drafts/withdraw now works with a partner API key. It deletes a draft that is not active yet, so the claim link and our reminders stop. The address is free again for a new upload. Errors carry a code: draft_id_required, draft_not_found or draft_active. See Withdrawing a draft.

2026-10-01

  • GET /partner/ere-positions: a customer can turn off sharing with the energy supplier they picked in their account. From then on the row drops out of that supplier's feed, and count drops with it. The row stays in the feed of a partner the customer signed up through, unless that is the same supplier. Delete a missing EAN from your own copy as well. The response shape does not change.
  • GET /partner/ere-positions: each row now carries customer_ref and source. customer_ref is the same pseudonymous reference as in GET /partner/customers, and tells two customers on one EAN apart. source has a new value, supplier_choice, for a customer linked to you only through their choice of energy supplier.
  • GET /partner/ere-positions no longer returns a customer who made themselves invisible to partners (a GDPR objection). Before, the row stayed with external_ref set to null. count drops by the same number. See Linking a row to your own customer.

2026-09-24

  • GET /partner/ere-positions answers 403 to a partner in the installer work model (installer, garage, distributeur). An installer reads its customers through GET /partner/customers. Energy partners and platforms see no change.

2026-09-23

  • GET /v1/accounts: each connection now carries status, active or archived. After the customer signs, we re-check an unverified connection about once a week. A correction through POST /v1/connections:update now keeps the check result it returned.
  • The API reference now describes the Partner API, the CPO API and the sandbox helpers. It comes from the same OpenAPI description that CI checks.
  • New sandbox helper: POST /sandbox/v1/drafts/{id}/claim claims a draft as the customer, and submits the registration if you ask. See test values and helpers.
  • A repeat POST /v1/accounts now returns the account's current mandate_status. Before, it returned pending even after the customer signed.