Accounts
Create a concept account (customer + connection) and list your accounts with their status.
Accounts
An account is the administrative and legal anchor: the EAN, the address, the machtiging, the sessions, and the payout all hang off it. You create it as a concept — it exists and is attributed to you, but until the customer signs their machtiging it doesn't count toward anything.
Create an account
POST /v1/accounts — scope cpo:accounts:write
curl -X POST https://api.joulo.nl/functions/v1/api/v1/accounts \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"type": "particulier",
"display_name": "Jan de Vries",
"cpo_external_ref": "CUST-100482",
"payout_handler": "joulo_pays_customer",
"iban": "NL91ABNA0417164300",
"person": {
"full_name": "Jan de Vries",
"email": "jan@example.nl",
"phone": "+31612345678"
},
"connection": {
"ean_code": "871685920000123456",
"street": "Dorpsstraat",
"house_number": "1",
"postal_code": "6971AB",
"city": "Brummen"
}
}'{
"ok": true,
"account_id": "60092124-c768-4191-9c9d-55ef3fc49999",
"connection_id": "2eca20d8-06ed-4354-a80d-c8c840a1640d",
"location_id": "…",
"ean_validation_status": "matched",
"iban_stored": true,
"mandate_status": "pending",
"account": {
"id": "60092124-…",
"type": "particulier",
"display_name": "Jan de Vries",
"payout_handler": "joulo_pays_customer",
"cpo_external_ref": "CUST-100482",
"created_at": "2026-07-20T12:00:00Z"
}
}Body
| Field | Required | Description |
|---|---|---|
type | no | particulier (default), zakelijk, or vve. |
display_name | no | Human-readable name for the account. |
cpo_external_ref | recommended | Your customer number. Used as the idempotency key and to address later session pushes. |
payout_handler | no | joulo_pays_customer (default) — Joulo pays the customer directly. via_cpo_invoicing — the payout settles through you, and Joulo does not pay the customer directly. |
iban | for payout | The customer's IBAN (15–34 chars, spaces ignored). Required before a joulo_pays_customer account can become ERE-eligible; not needed for via_cpo_invoicing. Stored encrypted. You can send it here or add it later by re-posting with the same cpo_external_ref. |
person.full_name | yes | The natural person who will sign the machtiging. |
person.email | no | Signatory email. |
person.phone | no | Signatory phone. |
connection.ean_code | yes | 18-digit EAN of the connection. |
connection.street house_number postal_code city | yes | Address, validated against the EAN. |
connection.house_number_addition | no | Addition (e.g. A). |
Idempotent. Re-posting with the same cpo_external_ref returns the
existing account ("idempotent": true) instead of creating a duplicate. A
re-post that includes iban stores it on the existing account, so you can
add or correct the IBAN after creation.
iban_stored in the response tells you whether the IBAN was saved (false
if you sent none, or if storage failed and you should retry).
EAN exclusivity
An EAN can be booked by exactly one party. If the EAN is already registered
(on your side or elsewhere), you get 409 ean_in_use. The address is checked
against the EAN; a clear mismatch returns 422 ean_address_mismatch.
New-build addresses are accepted. When the Dutch EAN register holds no
connection at all for that postcode and house number — normal for months after
delivery — the account is created with the connection marked unverified and
queued for manual review. It is re-checked automatically until the register
catches up. The EAN must still look Dutch (871…), or you get
422 ean_not_dutch.
Errors
| HTTP | error |
|---|---|
| 400 | invalid_type, invalid_payout_handler, invalid_iban, person_required, invalid_ean, address_required |
| 404 | account_not_found (for a different CPO's account) |
| 409 | ean_in_use |
| 422 | ean_address_mismatch, ean_not_dutch |
List accounts
GET /v1/accounts — scope cpo:accounts:read
Paginated with ?limit (default 50, max 200) and ?offset. Returns each
account with its signatory and per-connection registration + mandate state — so
you can track a customer from concept to signed to registered.
To fetch one specific account, filter on your own customer number:
GET /v1/accounts?cpo_external_ref=your-crm-id-001. That is the same
cpo_external_ref you passed on create (the idempotency key), so a status
poll never has to page through the full list.
{
"accounts": [
{
"id": "60092124-…",
"type": "particulier",
"display_name": "Jan de Vries",
"payout_handler": "joulo_pays_customer",
"cpo_external_ref": "CUST-100482",
"created_at": "2026-07-20T12:00:00Z",
"signatory": { "full_name": "Jan de Vries", "email": "jan@example.nl" },
"connections": [
{
"connection_id": "2eca20d8-…",
"ean_code": "871685920000123456",
"registration_status": "concept",
"ean_validation_status": "matched",
"mandate_status": "pending",
"mandate_valid_from": null
}
]
}
],
"limit": 50,
"offset": 0,
"count": 1
}mandate_status is pending until the customer signs, then active.
registration_status moves concept → ingediend once signed and submitted
for review.