JouloDocs

Order Joulo's replacement offer for a customer

Places an order for one of your customers from Joulo's replacement offer (GET /partner/replacement-offers). Joulo stays the seller. The answer carries a checkout_url: open it in your app like the sign-in link of the onboarding flow. On that page the customer sees the price, the address and our terms, and pays through Mollie. Afterwards the page sends the customer to return_url with ?status=paid|cancelled|failed&order=<order_id>. The checkout URL opens no session on the customer's account: it reads and pays this one order, for 30 minutes. Only a customer with mid_status no_mid may order, and only a product_slug from the offer for that customer: the offer, an alternative, or one of their options. One open order per customer: a second request answers 409 order_open with the open order and a fresh checkout_url. external_ref makes the request idempotent: the same value returns the same order, whatever its status, with replayed: true. An unpaid order expires after 30 minutes unless the customer reached Mollie by then. No partner fee applies to the hardware.

POST
/partner/replacement-orders
AuthorizationBearer <token>

Partner API key, sent as Authorization: Bearer <key>. Production keys start with jpk_live_ and work only on https://api.joulo.nl. Sandbox keys start with jpk_test_ and work only on https://api-staging.joulo.nl. Sandbox keys minted before September 2026 also start with jpk_live_. A key used on the wrong host returns 401 with a detail. A key is bound to one partner and reaches only the Partner API routes. Any other route returns 404 when it does not exist. A route that needs a scope the key lacks, such as GET /sessions, returns 403 with error: Insufficient scope and a required_scope. Every other route returns 403 with error: partner_api_key_scope. Mint and revoke keys in the partner portal. A partner has one active key at a time.

In: header

customer_refstring

The pseudonymous reference of the customer, from GET /partner/customers.

Match^[0-9a-f]{8}$
product_slugstring

A product_slug from the offer for this customer: offer, an alternative, or one of their options.

external_ref?string

Your own reference for this order. Unique per partner: the same value returns the same order.

Lengthlength <= 120
phone?string

The customer's phone number, for the installer. Defaults to the number on the account. The customer can change it on the checkout page.

cable?string

The version of a laadstation that offers one (BlueSky): a fixed cable or a type 2 socket. Ignored on a product without that choice.

Value in"tethered" | "socket"
notes?string

A note for the installer, such as where the laadstation hangs.

Lengthlength <= 500
return_url?string

Where our page sends the customer after paying. Must extend the return URL registered in the partner portal. Omit it to use that URL.

Lengthlength <= 500

Response Body

curl -X POST "https://api.joulo.nl/functions/v1/api/partner/replacement-orders" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_ref": "3f9a1c2e",
    "product_slug": "vervanging-zaptec-go-2",
    "external_ref": "hw-order-8812",
    "phone": "+31612345678",
    "notes": "Laadstation hangt in de carport",
    "return_url": "homewise://auth/joulo-result?screen=vervanging"
  }'
{
  "ok": true,
  "order_id": "a1b2c3d4-0000-4000-8000-000000000001",
  "status": "awaiting_payment",
  "checkout_url": "https://joulo.nl/vervangen/afrekenen/9f1c…?embed=1",
  "checkout_expires_in": 1800,
  "amount_cents": 119900,
  "replayed": false
}
{
  "error": "return_url_not_allowed",
  "message": "return_url must start with the return URL registered in the partner portal."
}
{
  "error": "Missing or invalid API token"
}
{
  "error": "Not available for your partner type"
}
{
  "error": "customer_not_found"
}

{
  "error": "order_open",
  "order_id": "a1b2c3d4-0000-4000-8000-000000000001",
  "status": "awaiting_payment",
  "amount_cents": 119900,
  "checkout_url": "https://joulo.nl/vervangen/afrekenen/9f1c…?embed=1",
  "checkout_expires_in": 1800
}

{
  "error": "Too many requests",
  "retry_after_seconds": 3
}