Klantly Developers

Arbeitsaufträge aus Ihrem ERP

Erstellen Sie Arbeitsaufträge aus Ihrem ERP, lassen Sie Ihre Techniker sie in Klantly abarbeiten und holen Sie die abgeschlossene Arbeit für die Rechnungsstellung ab.

Ihr ERP weiß, welche Arbeit erledigt werden muss; Ihre Techniker arbeiten in Klantly. Mit der API legen Sie den Arbeitsauftrag mit Positionen und Checkliste bereit und holen ihn zurück, sobald die Arbeit erledigt und unterschrieben ist.

Was Sie brauchen

Einen API-Schlüssel mit diesen Scopes:

Scope Wofür
customers.read Den Kunden anhand der E-Mail-Adresse suchen.
customers.write Einen Kunden anlegen, der noch nicht existiert.
users.read Optional: den Techniker suchen, der die Arbeit erledigt.
work_orders.read Abgeschlossene Arbeitsaufträge abrufen.
work_orders.write Arbeitsaufträge erstellen, bearbeiten und den Status ändern.

Die Funktion Arbeitsaufträge muss für Ihr Unternehmen aktiv sein.

Schritt 1: Kunde und Techniker

Ein Arbeitsauftrag gehört immer zu einem Kunden. Suchen Sie ihn mit filter[email] oder legen Sie ihn an, wie in Website-Formular zum Lead. Möchten Sie den Arbeitsauftrag direkt einem Techniker zuweisen, holen Sie seine ID mit GET /users ab und speichern Sie sie in Ihrem ERP.

Schritt 2: den Arbeitsauftrag erstellen

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": "Wartung Heizkessel",
    "type": "Wartung",
    "scheduled_at": "2026-10-06T08:00:00+02:00",
    "assigned_user_id": "usr_0k3j9x21m4zq8p",
    "items": [
      {"type": "labor", "name": "Wartung", "quantity": 1, "unit": "Stunde", "unit_price": 85},
      {"type": "material", "name": "Filterset", "sku": "FLT-200", "quantity": 1, "unit_price": 24.5}
    ],
    "checklist": [
      {"label": "Druck geprüft", "required": true},
      {"label": "Abgasmessung durchgeführt", "required": true}
    ]
  }'
  • Klantly vergibt eine Nummer (number, zum Beispiel WB-2026-00042) und berechnet die Summen. Preise sind ohne Umsatzsteuer. Einen Rabatt pro Position geben Sie als Prozentsatz (discount_percentage) oder als festen Betrag (discount_amount) an. Eine Position und die Summe dürfen 99.999.999,99 nicht übersteigen.
  • Name, Adresse und Kontaktdaten kommen vom Kunden, so wie sie in diesem Moment sind.
  • Ein neuer Arbeitsauftrag ist draft (Standard) oder planned.
  • Gehört der Arbeitsauftrag zu einem Termin, senden Sie appointment_id mit.
  • Setzen Sie die Auftragsnummer aus Ihrem ERP in den Idempotency-Key und speichern Sie id und number aus der Antwort in Ihrem ERP.

Schritt 3: Änderungen übertragen

Senden Sie nur, was sich ändert:

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 und checklist ersetzen immer die ganze Liste.

Warnung

Senden Sie checklist nur mit, wenn Sie sie wirklich ersetzen möchten: Sonst sind die Punkte weg, die Ihr Techniker bereits abgehakt hat. Dasselbe gilt für items und die Positionen, die er vor Ort hinzugefügt hat.

Findet die Arbeit nicht statt, stornieren Sie den Arbeitsauftrag über den 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"}'

Ist der Arbeitsauftrag in Klantly bereits abgerechnet (invoiced), kann er nicht mehr geändert werden: Sie erhalten 409 mit dem Code invalid_state_transition.

Schritt 4: die abgeschlossene Arbeit abrufen

Sobald Ihr Techniker den Arbeitsauftrag abschließt, sendet Klantly das Event work_order.completed. Unterschreibt der Kunde den Arbeitsauftrag, schließt ihn das sofort ab: Sie erhalten work_order.signed und work_order.completed zusammen, in beliebiger Reihenfolge. Hören Sie mit einem Webhook darauf, dann wissen Sie es sofort.

Rufen Sie lieber selbst ab, zum Beispiel alle fünfzehn Minuten, fragen Sie die Arbeitsaufträge ab, die seit Ihrem letzten Durchlauf abgeschlossen wurden:

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"

Blättern Sie mit next_cursor weiter, bis er null ist, siehe Paginierung. Merken Sie sich den Beginn Ihres Durchlaufs, abzüglich einer Minute Spielraum für Uhrabweichungen, und verwenden Sie ihn beim nächsten Mal als filter[updated_since]: So verpassen Sie nichts, was sich während des Abrufs ändert. Ein Arbeitsauftrag kann dadurch zweimal auftauchen, etwa wenn der Kunde später unterschreibt; entdoppeln Sie anhand der id.

Was Sie zurückbekommen:

Feld Inhalt
work_performed Was der Techniker erledigt hat.
items Die Positionen, auch die vor Ort hinzugefügten, mit minutes bei Arbeit.
checklist Was abgehakt wurde, mit Anmerkungen.
signature Ob der Kunde unterschrieben hat, mit Name und Zeitpunkt.
subtotal, tax_amount, total Die Summen, als Text mit zwei Dezimalstellen.

Rechnen Sie aus Ihrem ERP ab, bleibt der Arbeitsauftrag in Klantly auf completed: Den Status invoiced setzt nur Klantly selbst, wenn Sie in Klantly abrechnen.

Alles zusammen

PHP
use GuzzleHttp\Client;

/**
 * Ruft die seit $since abgeschlossenen Arbeitsaufträge ab und übergibt sie einzeln an $handle.
 * Gibt den Zeitpunkt zurück, den Sie beim nächsten Mal als $since verwenden.
 */
function fetchCompletedWorkOrders(Client $klantly, string $since, callable $handle): string
{
    $startedAt = gmdate('Y-m-d\TH:i:s\Z', time() - 60); // Eine Minute Spielraum für Uhrabweichungen.
    $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); // Zum Beispiel eine Rechnung in Ihrem ERP erstellen; entdoppeln anhand $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;
}

// Ruft die seit `since` abgeschlossenen Arbeitsaufträge ab und übergibt sie einzeln an `handle`.
// Gibt den Zeitpunkt zurück, den Sie beim nächsten Mal als `since` verwenden.
export async function fetchCompletedWorkOrders(since, handle) {
  const startedAt = new Date(Date.now() - 60_000).toISOString(); // Eine Minute Spielraum für Uhrabweichungen.
  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); // Zum Beispiel eine Rechnung in Ihrem ERP erstellen; entdoppeln anhand workOrder.id.
    }
    cursor = page.meta.next_cursor;
  } while (cursor);

  return startedAt;
}

Fehler abfangen

  • 422 mit validation_failed: zum Beispiel eine Position ohne name oder ein unbekanntes Feld in einer Position. In errors steht pro Feld, was falsch ist.
  • 409 mit invalid_state_transition: Der Arbeitsauftrag ist bereits abgerechnet.
  • 429 mit rate_limited: Warten Sie die Sekunden aus Retry-After ab und versuchen Sie es erneut. Siehe Rate limits.

Zuletzt aktualisiert am 15. September 2026