Klantly Developers

Bons d'intervention depuis votre ERP

Créez des bons d'intervention depuis votre ERP, laissez vos techniciens les compléter dans Klantly et récupérez le travail terminé pour la facturation.

Votre ERP sait quel travail doit être fait ; vos techniciens travaillent dans Klantly. Avec l'API, vous préparez le bon d'intervention, avec ses lignes et sa check-list, et vous le récupérez dès que le travail est terminé et signé.

Ce dont vous avez besoin

Une clé API avec ces scopes :

Scope Pour quoi faire
customers.read Rechercher le client par adresse e-mail.
customers.write Créer un client qui n'existe pas encore.
users.read Facultatif : rechercher le technicien qui réalise le travail.
work_orders.read Récupérer les bons d'intervention terminés.
work_orders.write Créer et modifier des bons d'intervention et changer leur statut.

La fonctionnalité Bons d'intervention doit être activée pour votre entreprise.

Étape 1 : client et technicien

Un bon d'intervention appartient toujours à un client. Recherchez-le avec filter[email] ou créez-le, comme dans Du formulaire web au prospect. Pour assigner directement le bon à un technicien, récupérez son id avec GET /users et conservez-le dans votre ERP.

Étape 2 : créer le bon d'intervention

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": "Entretien chaudière",
    "type": "entretien",
    "scheduled_at": "2026-10-06T08:00:00+02:00",
    "assigned_user_id": "usr_0k3j9x21m4zq8p",
    "items": [
      {"type": "labor", "name": "Entretien", "quantity": 1, "unit": "heure", "unit_price": 85},
      {"type": "material", "name": "Kit de filtres", "sku": "FLT-200", "quantity": 1, "unit_price": 24.5}
    ],
    "checklist": [
      {"label": "Pression contrôlée", "required": true},
      {"label": "Analyse de combustion effectuée", "required": true}
    ]
  }'
  • Klantly attribue un numéro au bon (number, par exemple WB-2026-00042) et calcule les totaux. Les prix sont hors TVA. Une remise par ligne s'indique en pourcentage (discount_percentage) ou en montant fixe (discount_amount). Une ligne et le total ne peuvent pas dépasser 99 999 999,99.
  • Le nom, l'adresse et les coordonnées viennent du client, tels qu'ils sont à ce moment-là.
  • Un nouveau bon est draft (par défaut) ou planned.
  • Si le bon appartient à un rendez-vous, envoyez appointment_id.
  • Mettez le numéro de commande de votre ERP dans l'Idempotency-Key, et conservez l'id et le number de la réponse dans votre ERP.

Étape 3 : transmettre les modifications

Envoyez uniquement ce qui change :

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 et checklist remplacent toujours la liste entière.

Attention

N'envoyez checklist que si vous voulez vraiment la remplacer : sinon, les points que votre technicien a déjà cochés disparaissent. Il en va de même pour items et les lignes qu'il a ajoutées sur place.

Si le travail n'a pas lieu, annulez le bon via son statut :

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"}'

Une fois le bon facturé dans Klantly (invoiced), il ne peut plus être modifié : vous recevez une 409 avec le code invalid_state_transition.

Étape 4 : récupérer le travail terminé

Dès que votre technicien termine le bon, Klantly envoie l'événement work_order.completed. Si le client signe le bon, cela le termine aussitôt : vous recevez work_order.signed et work_order.completed ensemble, dans un ordre quelconque. Écoutez-les avec un webhook et vous êtes informé immédiatement.

Si vous préférez récupérer vous-même, par exemple tous les quarts d'heure, demandez les bons terminés depuis votre passage précédent :

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"

Poursuivez avec next_cursor jusqu'à ce qu'il soit null, voir Pagination. Retenez le moment où votre passage a commencé, moins une minute de marge pour le décalage d'horloge, et utilisez-le la fois suivante comme filter[updated_since] : vous ne manquez ainsi rien de ce qui change pendant la récupération. Un bon peut donc apparaître deux fois, par exemple si le client signe plus tard ; dédoublonnez sur l'id.

Ce que vous recevez :

Champ Contenu
work_performed Ce que le technicien a fait.
items Les lignes, y compris celles ajoutées sur place, avec minutes pour la main-d'œuvre.
checklist Ce qui a été coché, avec les remarques.
signature Si le client a signé, avec le nom et l'heure.
subtotal, tax_amount, total Les totaux, sous forme de texte avec deux décimales.

Si vous facturez depuis votre ERP, le bon reste completed dans Klantly : seul Klantly passe un bon au statut invoiced, lorsque vous facturez dans Klantly.

Tout ensemble

PHP
use GuzzleHttp\Client;

/**
 * Récupère les bons terminés depuis $since et les passe un par un à $handle.
 * Renvoie le moment à utiliser comme $since la fois suivante.
 */
function fetchCompletedWorkOrders(Client $klantly, string $since, callable $handle): string
{
    $startedAt = gmdate('Y-m-d\TH:i:s\Z', time() - 60); // Une minute de marge pour le décalage d’horloge.
    $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); // Par exemple, créer une facture dans votre ERP ; dédoublonner sur $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;
}

// Récupère les bons terminés depuis `since` et les passe un par un à `handle`.
// Renvoie le moment à utiliser comme `since` la fois suivante.
export async function fetchCompletedWorkOrders(since, handle) {
  const startedAt = new Date(Date.now() - 60_000).toISOString(); // Une minute de marge pour le décalage d’horloge.
  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); // Par exemple, créer une facture dans votre ERP ; dédoublonner sur workOrder.id.
    }
    cursor = page.meta.next_cursor;
  } while (cursor);

  return startedAt;
}

Gérer les erreurs

  • 422 avec validation_failed : par exemple une ligne sans name ou un champ inconnu dans une ligne. errors indique par champ ce qui ne va pas.
  • 409 avec invalid_state_transition : le bon a déjà été facturé.
  • 429 avec rate_limited : attendez le nombre de secondes indiqué dans Retry-After et réessayez. Voir Rate limits.

Dernière mise à jour le 15 septembre 2026