Klantly Developers

Werkbonnen vanuit je ERP

Maak werkbonnen aan vanuit je ERP, laat je monteurs ze in Klantly afwerken en haal het afgeronde werk terug voor de facturatie.

Je ERP weet welk werk er gedaan moet worden; je monteurs werken in Klantly. Met de API zet je de werkbon klaar, met de regels en de checklist, en haal je hem terug zodra het werk af en ondertekend is.

Wat je nodig hebt

Een API-sleutel met deze scopes:

Scope Waarvoor
customers.read De klant opzoeken op e-mailadres.
customers.write Een klant aanmaken die nog niet bestaat.
users.read Optioneel: de monteur opzoeken die het werk doet.
work_orders.read Afgeronde werkbonnen ophalen.
work_orders.write Werkbonnen aanmaken, bijwerken en de status wijzigen.

De functie Werkbonnen moet actief zijn voor je bedrijf.

Stap 1: klant en monteur

Een werkbon hoort altijd bij een klant. Zoek hem op met filter[email] of maak hem aan, zoals in Websiteformulier naar lead. Wil je de werkbon meteen aan een monteur toewijzen, haal dan zijn id op met GET /users en bewaar die in je ERP.

Stap 2: de werkbon aanmaken

cURL
curl -X POST "https://app.klantly.com/api/v1/work-orders" \
  -H "Authorization: Bearer $KLANTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: erp-order-20260142" \
  -d '{
    "customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
    "status": "planned",
    "title": "Onderhoud cv-ketel",
    "type": "onderhoud",
    "scheduled_at": "2026-10-06T08:00:00+02:00",
    "assigned_user_id": "usr_0k3j9x21m4zq8p",
    "items": [
      {"type": "labor", "name": "Onderhoudsbeurt", "quantity": 1, "unit": "uur", "unit_price": 85},
      {"type": "material", "name": "Filterset", "sku": "FLT-200", "quantity": 1, "unit_price": 24.5}
    ],
    "checklist": [
      {"label": "Druk gecontroleerd", "required": true},
      {"label": "Rookgasmeting uitgevoerd", "required": true}
    ]
  }'
  • Klantly geeft de werkbon een nummer (number, bijvoorbeeld WB-2026-00042) en rekent de totalen uit. Prijzen zijn zonder btw. Een korting per regel geef je als percentage (discount_percentage) of als vast bedrag (discount_amount). Een regel en het totaal mogen niet boven 99.999.999,99 uitkomen.
  • Naam, adres en contactgegevens komen van de klant, zoals ze op dat moment zijn.
  • Een nieuwe werkbon is draft (standaard) of planned.
  • Hoort de werkbon bij een afspraak, stuur dan appointment_id mee.
  • Zet het ordernummer uit je ERP in de Idempotency-Key, en bewaar de id en het number uit het antwoord in je ERP.

Stap 3: wijzigingen doorzetten

Je stuurt alleen wat verandert:

cURL
curl -X PATCH "https://app.klantly.com/api/v1/work-orders/5e2c8a91-3f4b-4d6e-8a7c-1b2d3e4f5a60" \
  -H "Authorization: Bearer $KLANTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"scheduled_at": "2026-10-07T08:00:00+02:00"}'

items en checklist vervangen altijd de hele lijst.

Waarschuwing

Stuur checklist alleen mee als je hem echt wilt vervangen: punten die je monteur al heeft afgevinkt, zijn anders weg. Hetzelfde geldt voor items en de regels die hij ter plekke heeft toegevoegd.

Gaat het werk niet door, annuleer de werkbon dan met de status:

cURL
curl -X POST "https://app.klantly.com/api/v1/work-orders/5e2c8a91-3f4b-4d6e-8a7c-1b2d3e4f5a60/status" \
  -H "Authorization: Bearer $KLANTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: erp-order-20260142-cancel" \
  -d '{"status": "cancelled"}'

Is de werkbon in Klantly al gefactureerd (invoiced), dan kan hij niet meer veranderen: je krijgt 409 met de code invalid_state_transition.

Stap 4: het afgeronde werk ophalen

Zodra je monteur de werkbon afrondt, stuurt Klantly het event work_order.completed. Tekent de klant op de werkbon, dan rondt dat hem meteen af: je krijgt work_order.signed en work_order.completed tegelijk, in willekeurige volgorde. Luister daarnaar met een webhook, dan weet je het direct.

Haal je liever zelf op, bijvoorbeeld elk kwartier, vraag dan de werkbonnen op die sinds je vorige ronde zijn afgerond:

cURL
curl --globoff "https://app.klantly.com/api/v1/work-orders?filter[status]=completed&filter[updated_since]=2026-10-06T00:00:00Z&limit=100" \
  -H "Authorization: Bearer $KLANTLY_API_KEY"

Blader verder met next_cursor tot die null is, zie Paginering. Onthoud het tijdstip waarop je ophaalronde begon, min een minuut marge voor klokverschil, en gebruik dat de volgende keer als filter[updated_since]: zo mis je niets wat tijdens het ophalen verandert. Een werkbon kan daardoor twee keer langskomen, bijvoorbeeld als de klant later nog tekent; ontdubbel op id.

Wat je terugkrijgt:

Veld Wat erin staat
work_performed Wat de monteur heeft gedaan.
items De regels, ook wat de monteur ter plekke heeft toegevoegd, met minutes bij arbeid.
checklist Wat is afgevinkt, met opmerkingen.
signature Of de klant heeft getekend, met naam en tijdstip.
subtotal, tax_amount, total De totalen, als tekst met twee decimalen.

Factureer je vanuit je ERP, dan blijft de werkbon in Klantly op completed staan: de status invoiced zet alleen Klantly zelf, als je in Klantly factureert.

Alles samen

PHP
use GuzzleHttp\Client;

/**
 * Haalt de werkbonnen op die sinds $since zijn afgerond en geeft ze één voor één aan $handle.
 * Geeft het tijdstip terug dat je de volgende keer als $since gebruikt.
 */
function fetchCompletedWorkOrders(Client $klantly, string $since, callable $handle): string
{
    $startedAt = gmdate('Y-m-d\TH:i:s\Z', time() - 60); // Een minuut marge voor klokverschil.
    $cursor = null;

    do {
        $page = json_decode((string) $klantly->get('work-orders', [
            'query' => array_filter([
                'filter' => ['status' => 'completed', 'updated_since' => $since],
                'limit' => 100,
                'cursor' => $cursor,
            ]),
        ])->getBody(), true);

        foreach ($page['data'] as $workOrder) {
            $handle($workOrder); // Bijvoorbeeld een factuur maken in je ERP; ontdubbel op $workOrder['id'].
        }

        $cursor = $page['meta']['next_cursor'];
    } while ($cursor !== null);

    return $startedAt;
}
Node.js
const BASE_URL = 'https://app.klantly.com/api/v1';

async function klantly(path) {
  const response = await fetch(`${BASE_URL}/${path}`, {
    headers: { Authorization: `Bearer ${process.env.KLANTLY_API_KEY}` },
  });

  const json = await response.json();
  if (!response.ok) throw new Error(json.detail ?? json.title);
  return json;
}

// Haalt de werkbonnen op die sinds `since` zijn afgerond en geeft ze één voor één aan `handle`.
// Geeft het tijdstip terug dat je de volgende keer als `since` gebruikt.
export async function fetchCompletedWorkOrders(since, handle) {
  const startedAt = new Date(Date.now() - 60_000).toISOString(); // Een minuut marge voor klokverschil.
  let cursor = null;

  do {
    const params = new URLSearchParams({ 'filter[status]': 'completed', 'filter[updated_since]': since, limit: '100' });
    if (cursor) params.set('cursor', cursor);

    const page = await klantly(`work-orders?${params}`);
    for (const workOrder of page.data) {
      await handle(workOrder); // Bijvoorbeeld een factuur maken in je ERP; ontdubbel op workOrder.id.
    }
    cursor = page.meta.next_cursor;
  } while (cursor);

  return startedAt;
}

Fouten opvangen

  • 422 met validation_failed: bijvoorbeeld een regel zonder name of een onbekend veld in een regel. In errors staat per veld wat er mis is.
  • 409 met invalid_state_transition: de werkbon is al gefactureerd.
  • 429 met rate_limited: wacht het aantal seconden uit Retry-After en probeer het opnieuw. Zie Rate limits.

Laatst bijgewerkt op 15 september 2026