Upload customers
POST /partner/customers • push customers as claimable drafts, with or without Joulo sending the invite
POST /partner/customers turns each entry into a customer_draft through the
same validation, dedupe, cap and attribution path as the portal's CSV import.
Name and email are enough; the customer completes the rest (address, charger,
authorization) in the claim flow.
Request
curl -X POST https://api.joulo.nl/functions/v1/api/partner/customers \
-H "Authorization: Bearer jpk_live_..." \
-H "Content-Type: application/json" \
-d '{
"consent": true,
"send_invite": true,
"customers": [
{ "name": "Jan de Vries", "email": "[email protected]" },
{ "name": "Bedrijf B.V.", "email": "[email protected]", "type": "zakelijk", "kvk": "12345678" }
]
}'| Field | Required | Notes |
|---|---|---|
consent | yes | Must be true: you confirm you may supply these customers and pre-register them with Joulo. Otherwise the request fails with 400 consent_required. |
customers[] | yes | Non-empty array of { name, email }. Optional per row: type (particulier • default, zakelijk, vve), ean, kvk, btw. |
send_invite | no | Default true: Joulo emails each customer their claim link. Set to false to deliver the links yourself • they are returned in the response. |
Limits: at most 1.000 rows per request and a rolling cap of 5.000 rows
per 30 days per partner. Rows over either limit are reported in skipped,
never silently dropped.
Response
{
"ok": true,
"added": 2,
"batch_id": "…",
"claim_urls": [
{ "email": "[email protected]", "claim_url": "https://joulo.nl/claim/…" }
],
"skipped": {
"invalid": 0,
"missing_kvk": 0,
"already_drafted": 0,
"existing_user": 0,
"suppressed": 0,
"conflict": 0,
"over_request_limit": 0,
"over_cap": 0
},
"cap": { "rolling_days": 30, "rolling_cap": 5000, "remaining_before": 4998 }
}claim_urlsis only present whensend_inviteisfalse.skippedexplains every row that did not become a draft: invalid email, business rows without KvK, already drafted, already a Joulo user, and rows over the per-request or rolling cap.capshows the rolling upload window so your integration can pace batches.
The claim flow
Each draft resolves to a personal link on joulo.nl/claim/…. The customer
confirms who they are, connects their charger and signs the ERE authorization
themselves.
Only the holder of the connection (EAN) can sign the authorization • an upload never creates an authorization by itself. Drafts that are never claimed expire without side effects.
Embedding in a webview (auth links)
The claim link identifies the draft, not the user: opening it still requires a sign-in. Inside an embedded webview that is a problem — Google blocks OAuth in webviews, and an emailed magic link forces the user out to their mail client.
For that case, mint a short-lived auth link just-in-time, the moment the user opens the screen:
POST /partner/drafts/auth-link
{ "draft_id": "…" } // or { "email": "[email protected]" }{ "ok": true, "auth_url": "https://joulo.nl/claim-auth/…", "expires_in": 300 }Load auth_url straight into the webview. Joulo signs the customer in
server-side and lands them on the claim page — no OAuth, no email round-trip.
- The link is valid for 5 minutes and is a login credential: never store it, never email it, request a fresh one per screen-open. Re-minting invalidates the previous link.
- If the customer is new to Joulo, an account is created on the uploaded email address (confirmed, passwordless).
- If the email — or the uploaded EAN — already belongs to an existing Joulo account, the auth link deliberately does not sign in. The customer lands on the normal claim page and signs in with their own account. A partner-delivered link can never grant access to an existing account.
- Works only for drafts in
invitedorclaimedstatus; activated, expired or conflicted drafts return an error.
Reading back status
GET /partner/drafts• uploaded rows and their claim status.GET /partner/customers• claimed, attributed customers, including the effective fee and your partner share per customer. Self-signup customers are pseudonymised (city + month granularity); customers you uploaded yourself include contact details for follow-up.