JouloDocs

Push charging sessions

POST /partner/sessions • deliver your customers' charging sessions yourself, one by one, with transaction id and meter readings

POST /partner/sessions is for a partner that runs the back office of its customers' laadstations. You already receive every session from the charger, so you deliver them to Joulo yourself. We do not pull them from your system, and the customer never logs in with a charger brand.

We book each session on the customer's laadstation, exactly as the sessions of a charger we read ourselves. Every session counts toward the ERE position in GET /partner/ere-positions, and Joulo pays the customer every quarter.

We enable this per partner. Until we do, the endpoint answers 403 session_push_not_enabled, and an upload with charger_brand push is rejected as charger_brand_unsupported.

The sequence

  1. Upload the customer with POST /partner/customers and pre-couple the charge point. See below.
  2. Let the customer onboard in your app with an auth-link. See Onboarding inside your app. When they activate, we attach the charge point to their account.
  3. Push the history. Once GET /partner/customers shows the customer, send every finished session since 1 January 2026. Did the MID meter come later? Then start there.
  4. Keep pushing. Send each session once it has stopped. Once a day is enough. Sooner is fine.

A session for a charge point we have not attached yet comes back as unknown_charger. Nothing is lost: send it again after step 2.

Pre-coupling the charge point

Send the charge point with the customer at upload. charger_brand is push, charger_id your own id for the charge point:

{
  "consent": true,
  "send_invite": false,
  "customers": [
    {
      "name": "Jan de Vries",
      "email": "[email protected]",
      "external_ref": "KLANT-8842",
      "postal_code": "6971LB",
      "house_number": "12",
      "charger_brand": "push",
      "charger_id": "PF-000123",
      "charger_vendor": "Alfen BV",
      "charger_model": "NG910-60027",
      "charger_serial": "ACE0012345",
      "charger_location": { "postal_code": "6971LB", "house_number": "12", "street": "Dorpsstraat", "city": "Brummen" }
    }
  ]
}
FieldRequiredWhat it does
charger_brandyespush.
charger_idyesYour charge point id, kept as sent, at most 120 characters. Send the same id with every session.
charger_vendor, charger_modelrecommendedchargePointVendor and chargePointModel from the BootNotification. They decide the catalog model, and so whether the charger has a MID meter. Without them we review the charger by hand.
charger_serialrecommendedThe charger's serial number. A serial that is already on a Joulo laadstation is rejected as charger_claimed: the charger would reach us twice and count twice.
charger_locationrecommendedThe address of the charge point: postal_code and house_number required, house_number_addition, street and city optional. We compare it with the address of the EAN. A mismatch never refuses the row. Without a postcode or house number we ignore the address and say so in the answer.

The upload answers with chargers: per created row the catalog model we matched and whether it has a MID meter. catalog_model: null means no match, and we review that charger by hand. location says whether we stored the address: used, ignored (no postcode or house number) or missing. mid_meter: false means the charger earns no ERE. A MID meter outside the charger, such as one in the meter cupboard, never counts for home charging.

Push sessions

Send at most 100 sessions per request, for any of your customers and in any order.

curl -X POST https://api.joulo.nl/functions/v1/api/partner/sessions \
  -H "Authorization: Bearer jpk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "sessions": [
      {
        "charger_id": "PF-000123",
        "transaction_id": "88213",
        "connector_id": 1,
        "started_at": "2026-10-01T18:02:11+02:00",
        "stopped_at": "2026-10-01T23:40:09+02:00",
        "meter_start_wh": 1203400,
        "meter_stop_wh": 1214950
      }
    ]
  }'
FieldRequiredWhat it is
charger_idyesYour charge point id, as sent at upload.
transaction_idyesYour id for this session, unique per charge point. A string or a number, at most 100 characters.
started_at, stopped_atyesISO 8601 with a time zone: Z or an offset such as +02:00.
meter_start_wh, meter_stop_whyesThe register of the MID meter at start and stop, in Wh: meterStart and meterStop from StartTransaction and StopTransaction.
connector_idnoThe connector the session ran on.
kwhnoWhen you send it, it must match the meter range within 0.1 kWh.

We book the meter range, never a number next to it. The verifier asks for the source data of the MID meter per session: transaction id, start, stop and the charge point. That is why the meter readings are required. We keep every session you send as evidence, also after a correction.

A time without a zone is rejected with naive_timestamp. We never guess the zone: a wrong guess moves every session an hour or two.

The answer

{
  "ok": true,
  "received": 2,
  "inserted": 1,
  "updated": 0,
  "unchanged": 0,
  "rejected": 1,
  "failed": 0,
  "results": [
    { "charger_id": "PF-000123", "transaction_id": "88213", "status": "inserted" },
    {
      "charger_id": "PF-000999",
      "transaction_id": "1204",
      "status": "rejected",
      "reason": "unknown_charger",
      "detail": "No active laadstation with this charger_id for your customers. Push sessions once the customer finished onboarding."
    }
  ]
}

results holds one entry per session, in request order. A rejected session never fails the request.

statusMeaning
insertedA new session, now booked.
updatedWe held this session and took your new values.
unchangedWe already held this session with these values.
rejectedWe did not book it. reason says why.
failedWe could not store it. Send it again.

Resending and corrections

transaction_id is the key. Resending is always safe:

  • The same values: unchanged. Resend a whole month if that is easier.
  • More energy or a later stop: updated. We raise the booking.
  • Less energy: rejected with lower_kwh. ERE may already be sold on the old value, so a correction downward goes through us.
  • Another start: rejected with started_at_changed. A session is booked on its start, so a new start would count it twice. Contact us.

Rejection reasons

reasonMeaning
invalidA required field is missing or cannot be read. detail names it.
naive_timestampA time without a zone.
no_energyThe meter did not move.
before_2026The session started before 1 January 2026.
in_futurestopped_at lies in the future.
meter_range_invalidmeter_stop_wh is lower than meter_start_wh. A replaced or reset meter goes through us.
kwh_meter_mismatchkwh differs from the meter range by more than 0.1 kWh.
implausibleMore than 25 kW on average: check the readings and the times.
unknown_chargerNo active laadstation with this charger_id for your customers. Usually the customer has not finished onboarding.
started_at_changedThis transaction_id is booked with another start.
lower_kwhThis transaction_id is booked with more energy.
duplicate_startAnother transaction_id on this laadstation already holds this session.

Not every session earns ERE

We book every session you send. Later we decide per session whether it counts, with the same rules as for every customer:

  • the charger has a MID meter, and the session started after it was installed;
  • the customer signed the machtiging, and the session falls inside it;
  • the customer confirmed that the charger sits behind their EAN, and we approved the registration.

The ERE position in GET /partner/ere-positions shows the result per quarter. A session from before the MID meter never counts, also not when you send it later.

Errors

HTTPerror
400Invalid JSON body, sessions_required, too_many_sessions
403session_push_not_enabled
500lookup_failed, evidence_failed. Send the same request again: it is idempotent.

On the sandbox

The sandbox works the same. Mint your sandbox key in the partner portal after we enabled you. The key carries the same permission, and GET /partner/me shows it as session_push_source. Then upload a customer with a charge point, finish the onboarding with the sandbox helpers, and push sessions. Nothing reaches the NEa.