KostenKlar API

Mit der KostenKlar API lesen Sie Kunden, Rechnungen, Angebote und Produkte aus Ihrem Konto, legen Kunden und Rechnungsentwürfe an und lassen sich per Webhook benachrichtigen, wenn sich etwas ändert. So verbinden Sie KostenKlar mit Ihrem Shop, Ihrer Warenwirtschaft, Ihrem CRM oder einem eigenen Skript.

Überblick

  • REST und JSON. Alle Antworten sind JSON (außer dem PDF-Download). Daten werden mit GET, POST und PATCH übertragen.
  • Basis-Adresse: https://api.kosten-klar.de. Alle Pfade beginnen mit /v1. Nur https wird angenommen.
  • Authentifizierung per API-Schlüssel im Header Authorization.
  • Maschinenlesbare Beschreibung: openapi.json (OpenAPI 3.1). Damit erzeugen Sie Clients mit gängigen Generatoren oder importieren die API in Postman und Insomnia.

Was die API bewusst nicht tut

Die API legt Rechnungen, Angebote und Gutschriften nur als Entwurf an und ändert nur Entwürfe. Rechnungsnummer, Versand und das Festschreiben bleiben in KostenKlar. Das schützt die lückenlose Nummernfolge, die Sie nach GoBD brauchen, und verhindert Fehlbuchungen durch fremden Code. Belege, Zahlungen und Ausgaben lassen sich nicht löschen. Zahlungseingänge und Ausgaben laufen durch dieselben Prüfungen wie in der App, etwa für festgeschriebene Monate und den Kassenabschluss. Buchungssätze der doppelten Buchführung sind nicht über die API erreichbar.

Zugang und Schlüssel

Die API ist in jedem Konto zunächst aus. Sie brauchen zwei Dinge:

  1. Den Business-Tarif.
  2. Eine Freischaltung. Sie beantragen sie in KostenKlar unter Einstellungen, Entwickler & API mit einer kurzen Begründung, wofür Sie die API nutzen wollen. Wir prüfen den Antrag und schalten das Konto frei.

Nach der Freischaltung legen Sie an derselben Stelle Schlüssel an. Nur der Kontoinhaber kann Schlüssel erzeugen, bearbeiten und widerrufen.

  • Pro Schlüssel wählen Sie einen Namen, die Berechtigungen und optional ein Ablaufdatum (höchstens 5 Jahre).
  • Der Schlüssel beginnt mit kk_live_ und wird genau einmal angezeigt. Wir speichern nur einen Hash. Ist er weg, legen Sie einen neuen an und widerrufen den alten.
  • Sie können bis zu 10 Schlüssel gleichzeitig aktiv haben. Legen Sie pro Integration einen eigenen Schlüssel an, dann lässt sich jeder einzeln widerrufen.
  • Jede Änderung über die API erscheint im Änderungsprotokoll mit dem Schlüsselnamen. Anlage, Änderung und Widerruf eines Schlüssels melden wir Ihnen per E-Mail.

Behandeln Sie Schlüssel wie Passwörter. Nicht in Quellcode einchecken, nicht in Browser-Code oder Apps einbauen, nicht per Chat verschicken. Rufen Sie die API immer von Ihrem Server aus auf. Wird ein Schlüssel öffentlich, widerrufen Sie ihn sofort.

Fällt der Business-Tarif weg, ruht der Zugang sofort und alle Aufrufe antworten mit API_NOT_AVAILABLE. Ihre Schlüssel bleiben erhalten und funktionieren wieder, sobald der Tarif aktiv ist.

Schnellstart

In fünf Minuten von null zur ersten Rechnung im Entwurf. Legen Sie vorher einen Schlüssel mit den Berechtigungen customers:read, customers:write und invoices:write an und setzen Sie ihn als Umgebungsvariable:

export KOSTENKLAR_API_KEY="kk_live_..."

1. Verbindung testen

curl

curl https://api.kosten-klar.de/v1/me \
  -H "Authorization: Bearer $KOSTENKLAR_API_KEY"

Node.js

const BASE = 'https://api.kosten-klar.de';
const headers = { Authorization: `Bearer ${process.env.KOSTENKLAR_API_KEY}` };

const res = await fetch(`${BASE}/v1/me`, { headers });
console.log(res.status, await res.json());

PHP

<?php
$base = 'https://api.kosten-klar.de';
$ch = curl_init("$base/v1/me");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('KOSTENKLAR_API_KEY')],
]);
$body = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $body;

Python

import os, requests

BASE = 'https://api.kosten-klar.de'
headers = {'Authorization': f"Bearer {os.environ['KOSTENKLAR_API_KEY']}"}

res = requests.get(f'{BASE}/v1/me', headers=headers)
print(res.status_code, res.json())

Antwort:

{
  "keyId": "0c1c8a3e-5b0e-4d52-9a57-6b2f3f6a9d10",
  "name": "Shop-Anbindung",
  "prefix": "kk_live_AbCd1234",
  "scopes": ["customers:read", "customers:write", "invoices:write"],
  "scopeLabels": {
    "customers:read": "Kunden lesen",
    "customers:write": "Kunden anlegen und ändern",
    "invoices:write": "Rechnungsentwürfe anlegen und ändern"
  },
  "expiresAt": null
}

Bekommen Sie 401 oder 403, schauen Sie in die Fehlertabelle.

2. Kunden anlegen

Pflicht sind ein Name (company oder firstName plus lastName), street, postalCode (genau fünf Ziffern), city und email. Jeder POST braucht einen Idempotency-Key, siehe Idempotenz.

curl

curl -X POST https://api.kosten-klar.de/v1/customers \
  -H "Authorization: Bearer $KOSTENKLAR_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "customerType": "business",
    "company": "Beispiel GmbH",
    "email": "buchhaltung@beispiel.de",
    "street": "Musterstraße 1",
    "postalCode": "10115",
    "city": "Berlin"
  }'

Node.js

import { randomUUID } from 'node:crypto';

const res = await fetch(`${BASE}/v1/customers`, {
  method: 'POST',
  headers: {
    ...headers,
    'Content-Type': 'application/json',
    'Idempotency-Key': randomUUID(),
  },
  body: JSON.stringify({
    customerType: 'business',
    company: 'Beispiel GmbH',
    email: 'buchhaltung@beispiel.de',
    street: 'Musterstraße 1',
    postalCode: '10115',
    city: 'Berlin',
  }),
});
const customer = await res.json();
console.log(res.status, customer.id);

PHP

<?php
$payload = json_encode([
    'customerType' => 'business',
    'company' => 'Beispiel GmbH',
    'email' => 'buchhaltung@beispiel.de',
    'street' => 'Musterstraße 1',
    'postalCode' => '10115',
    'city' => 'Berlin',
]);
$ch = curl_init("$base/v1/customers");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $payload,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('KOSTENKLAR_API_KEY'),
        'Content-Type: application/json',
        'Idempotency-Key: ' . bin2hex(random_bytes(16)),
    ],
]);
$customer = json_decode(curl_exec($ch), true);
echo $customer['id'];

Python

import uuid

res = requests.post(
    f'{BASE}/v1/customers',
    headers={**headers, 'Idempotency-Key': str(uuid.uuid4())},
    json={
        'customerType': 'business',
        'company': 'Beispiel GmbH',
        'email': 'buchhaltung@beispiel.de',
        'street': 'Musterstraße 1',
        'postalCode': '10115',
        'city': 'Berlin',
    },
)
customer = res.json()
print(res.status_code, customer['id'])

Die Antwort hat Status 201 und enthält den Kunden mit seiner id (gekürzt):

{
  "id": "5d1f3a52-8f0c-4c0e-9d0a-2f8d6a7b9c11",
  "customerNumber": "K-0042",
  "type": "business",
  "company": "Beispiel GmbH",
  "email": "buchhaltung@beispiel.de",
  "address": { "street": "Musterstraße 1", "postalCode": "10115", "city": "Berlin", "country": null },
  "createdAt": "2026-10-06T10:15:00.000Z",
  "updatedAt": "2026-10-06T10:15:00.000Z"
}

3. Rechnungsentwurf anlegen

curl

curl -X POST https://api.kosten-klar.de/v1/invoices \
  -H "Authorization: Bearer $KOSTENKLAR_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "5d1f3a52-8f0c-4c0e-9d0a-2f8d6a7b9c11",
    "issueDate": "2026-10-06",
    "dueDate": "2026-10-20",
    "taxRate": 19,
    "items": [
      { "description": "Beratung Oktober", "quantity": 8, "unit": "Std.", "unitPrice": 95 }
    ]
  }'

Node.js

const res = await fetch(`${BASE}/v1/invoices`, {
  method: 'POST',
  headers: { ...headers, 'Content-Type': 'application/json', 'Idempotency-Key': randomUUID() },
  body: JSON.stringify({
    customerId: customer.id,
    issueDate: '2026-10-06',
    dueDate: '2026-10-20',
    taxRate: 19,
    items: [{ description: 'Beratung Oktober', quantity: 8, unit: 'Std.', unitPrice: 95 }],
  }),
});
const invoice = await res.json();
console.log(invoice.status, invoice.total); // draft 904.4

PHP

<?php
$payload = json_encode([
    'customerId' => $customer['id'],
    'issueDate' => '2026-10-06',
    'dueDate' => '2026-10-20',
    'taxRate' => 19,
    'items' => [['description' => 'Beratung Oktober', 'quantity' => 8, 'unit' => 'Std.', 'unitPrice' => 95]],
]);
$ch = curl_init("$base/v1/invoices");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $payload,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('KOSTENKLAR_API_KEY'),
        'Content-Type: application/json',
        'Idempotency-Key: ' . bin2hex(random_bytes(16)),
    ],
]);
$invoice = json_decode(curl_exec($ch), true);
echo $invoice['status'], ' ', $invoice['total'];

Python

res = requests.post(
    f'{BASE}/v1/invoices',
    headers={**headers, 'Idempotency-Key': str(uuid.uuid4())},
    json={
        'customerId': customer['id'],
        'issueDate': '2026-10-06',
        'dueDate': '2026-10-20',
        'taxRate': 19,
        'items': [{'description': 'Beratung Oktober', 'quantity': 8, 'unit': 'Std.', 'unitPrice': 95}],
    },
)
invoice = res.json()
print(invoice['status'], invoice['total'])

Die Rechnung liegt jetzt in KostenKlar als Entwurf ohne Rechnungsnummer. Prüfen und versenden Sie sie dort. Die Summen berechnet KostenKlar selbst: 8 Stunden zu 95 Euro netto ergeben 760 Euro netto, 144,40 Euro Umsatzsteuer und 904,40 Euro brutto.

Authentifizierung

Senden Sie den Schlüssel bei jedem Aufruf im Header:

Authorization: Bearer kk_live_...

Der Schlüssel gehört zu einem Konto und sieht ausschließlich dessen Daten. Fremde Datensätze antworten wie nicht vorhandene mit 404, nie mit 403, damit sich IDs nicht erraten lassen.

Name, Berechtigungen und Ablaufdatum eines Schlüssels lassen sich nachträglich ändern, der Schlüssel selbst bleibt gleich. Änderungen gelten ab dem nächsten Aufruf. Ein abgelaufener Schlüssel funktioniert mit einem neuen Ablaufdatum wieder; ein widerrufener Schlüssel kann nicht reaktiviert werden. Ein widerrufener, abgelaufener oder unbekannter Schlüssel antwortet mit 401.

Berechtigungen

Jeder Schlüssel hat genau die Berechtigungen, die Sie beim Anlegen oder später beim Bearbeiten wählen. Vergeben Sie so wenig wie möglich. Fehlt eine Berechtigung, antwortet die API mit 403 und API_SCOPE_MISSING.

BerechtigungErlaubt
customers:readGET /v1/customers, GET /v1/customers/{id}
customers:writePOST /v1/customers, PATCH /v1/customers/{id}
invoices:readGET /v1/invoices, GET /v1/invoices/{id}, GET /v1/invoices/{id}/pdf
invoices:writePOST /v1/invoices, PATCH /v1/invoices/{id} (nur Entwürfe)
quotes:readGET /v1/quotes, GET /v1/quotes/{id}, GET /v1/quotes/{id}/pdf
quotes:writePOST /v1/quotes, PATCH /v1/quotes/{id} (nur Entwürfe), POST /v1/quotes/{id}/accept, POST /v1/quotes/{id}/reject
products:readGET /v1/products, GET /v1/products/{id}
products:writePOST /v1/products, PATCH /v1/products/{id}, POST /v1/products/{id}/stock-movements
projects:readGET /v1/projects, GET /v1/projects/{id}, GET /v1/projects/{id}/milestones, GET /v1/projects/{id}/summary, GET /v1/projects/{id}/acceptances, GET /v1/projects/{id}/acceptances/{acceptanceId}/pdf
projects:writePOST /v1/projects, PATCH /v1/projects/{id}
tasks:readGET /v1/tasks, GET /v1/tasks/{id}
tasks:writePOST /v1/tasks, PATCH /v1/tasks/{id}, DELETE /v1/tasks/{id}, Teilschritte unter /v1/tasks/{id}/checklist
credit-notes:readGET /v1/credit-notes, GET /v1/credit-notes/{id}, GET /v1/credit-notes/{id}/pdf
credit-notes:writePOST /v1/credit-notes (nur Entwürfe)
delivery-notes:readGET /v1/delivery-notes, GET /v1/delivery-notes/{id}, GET /v1/delivery-notes/{id}/pdf
dunnings:readGET /v1/dunnings, GET /v1/dunnings/{id}, GET /v1/dunnings/{id}/pdf
contracts:readGET /v1/contracts, GET /v1/contracts/{id}, GET /v1/contracts/{id}/usage, GET /v1/contracts/{id}/pdf
payments:readGET /v1/payments, GET /v1/payments/{id}
payments:writePOST /v1/invoices/{id}/payments
expenses:readGET /v1/expenses, GET /v1/expenses/{id}
expenses:writePOST /v1/expenses, PATCH /v1/expenses/{id}, POST /v1/expenses/{id}/receipt
time-entries:readGET /v1/time-entries, GET /v1/time-entries/{id}
time-entries:writePOST /v1/time-entries, PATCH /v1/time-entries/{id}, DELETE /v1/time-entries/{id} (nur offene Buchungen)
calendar:readGET /v1/calendar/busy, GET /v1/calendar/events/{id}
calendar:writePOST /v1/calendar/events, PATCH /v1/calendar/events/{id}, DELETE /v1/calendar/events/{id}

Projekte und Zeitbuchungen gehören zum Modul Zeiterfassung, Termine zum Modul Kalender. Ist das Modul im Konto ausgeschaltet, antworten die Endpunkte unter /v1/projects und /v1/time-entries bzw. /v1/calendar mit 403 und FEATURE_DISABLED, auch mit passender Berechtigung.

GET /v1/me braucht keine bestimmte Berechtigung. Schreibende Berechtigungen schließen das Lesen nicht ein: Wer Kunden per POST anlegt und die Antwort auswertet, braucht dafür kein customers:read, wer sie später abrufen will, schon.

Seiten und Filter

Listen kommen seitenweise im selben Umschlag:

{
  "data": [ { "id": "...", "...": "..." } ],
  "nextCursor": "NWQxZjNhNTItOGYwYy00YzBlLTlkMGEtMmY4ZDZhN2I5YzEx"
}
ParameterBedeutung
limitDatensätze pro Seite, 1 bis 100, Standard 50.
cursorDer Wert nextCursor der vorigen Antwort. Auf der letzten Seite ist nextCursor null.
updatedSinceNur Datensätze, die seit diesem Zeitpunkt geändert wurden (ISO 8601, z. B. 2026-10-01T00:00:00Z).
status, customerIdNur bei Rechnungen und Angeboten: nach Status oder Kunde filtern.

Die Reihenfolge ist nach id sortiert, nicht chronologisch. Das macht das Blättern stabil: Es gibt keine Lücken und keine Doppelten, auch wenn sich Daten währenddessen ändern. Für eine laufende Synchronisation merken Sie sich den Zeitpunkt Ihres letzten vollständigen Durchlaufs und fragen mit updatedSince nur das Neue ab.

Node.js

async function* alleRechnungen(params = {}) {
  let cursor;
  do {
    const query = new URLSearchParams({ limit: '100', ...params, ...(cursor ? { cursor } : {}) });
    const res = await fetch(`${BASE}/v1/invoices?${query}`, { headers });
    if (!res.ok) throw new Error(`KostenKlar ${res.status}`);
    const page = await res.json();
    yield* page.data;
    cursor = page.nextCursor;
  } while (cursor);
}

for await (const invoice of alleRechnungen({ status: 'overdue' })) {
  console.log(invoice.number, invoice.total);
}

Python

def alle_rechnungen(**params):
    cursor = None
    while True:
        query = {'limit': 100, **params, **({'cursor': cursor} if cursor else {})}
        res = requests.get(f'{BASE}/v1/invoices', headers=headers, params=query)
        res.raise_for_status()
        page = res.json()
        yield from page['data']
        cursor = page['nextCursor']
        if not cursor:
            break

for invoice in alle_rechnungen(status='overdue'):
    print(invoice['number'], invoice['total'])

Fehler

Fehler erkennen Sie am HTTP-Status (4xx Ihre Anfrage, 5xx unser Problem). Der Body hat immer dieselbe Form:

{
  "statusCode": 403,
  "code": "API_SCOPE_MISSING",
  "message": "Dem API-Schlüssel fehlt die Berechtigung \"invoices:write\".",
  "hint": "Legen Sie in KostenKlar einen Schlüssel mit dieser Berechtigung an.",
  "details": { "requiredScope": "invoices:write" },
  "timestamp": "2026-10-06T10:15:00.000Z",
  "path": "/v1/invoices"
}

Programmieren Sie auf code und den HTTP-Status. Die Texte in message und hint sind für Menschen gedacht und können sich ändern.

StatuscodeBedeutung und was zu tun ist
400BAD_REQUESTDie Anfrage ist ungültig: Pflichtfeld fehlt, Feld hat das falsche Format, unbekanntes Feld oder ungültiger cursor. message nennt die betroffenen Felder. Unbekannte Felder werden abgelehnt, nicht ignoriert.
400API_IDEMPOTENCY_KEY_REQUIREDBei einem POST fehlt der Header Idempotency-Key.
401API_KEY_INVALIDHeader fehlt, Format falsch, Schlüssel unbekannt oder widerrufen.
401API_KEY_EXPIREDDer Schlüssel ist abgelaufen. Legen Sie einen neuen an.
403API_SCOPE_MISSINGDem Schlüssel fehlt eine Berechtigung. details.requiredScope nennt sie.
403API_NOT_AVAILABLEDie API ist für das Konto nicht freigeschaltet oder der Business-Tarif ist nicht aktiv.
404NOT_FOUNDDen Datensatz gibt es nicht oder er gehört nicht zu Ihrem Konto.
409API_IDEMPOTENCY_IN_PROGRESSDie erste Anfrage mit diesem Idempotency-Key läuft noch. Kurz warten, mit demselben Key wiederholen.
422API_IDEMPOTENCY_MISMATCHDer Idempotency-Key wurde schon mit anderem Inhalt verwendet. Nehmen Sie für jede neue Anfrage einen neuen Key.
422NOT_FOUNDBeim Anlegen einer Rechnung existiert der Kunde (customerId) oder ein Produkt (items.productId) nicht. details.field nennt das Feld.
429API_RATE_LIMITEDZu viele Anfragen, siehe Limits. Warten Sie die Sekunden aus dem Header Retry-After.
5xxINTERNAL_ERROREin Fehler auf unserer Seite. Wiederholen Sie den Aufruf mit Pause. Bei POST mit demselben Idempotency-Key.

Limits

WasGrenze
Lesende Aufrufe (GET)60 pro Minute und Schlüssel
Schreibende Aufrufe (POST, PATCH)20 pro Minute und Schlüssel
Datensätze pro Seite100
Positionen pro Rechnung200
Aktive Schlüssel pro Konto10
Webhook-Endpunkte pro Konto5

Das Limit gilt je Schlüssel, nicht je IP-Adresse. Jede Antwort enthält:

  • X-RateLimit-Limit: erlaubte Aufrufe im aktuellen Fenster
  • X-RateLimit-Remaining: noch verbleibende Aufrufe
  • X-RateLimit-Reset: Sekunden bis zum Beginn des nächsten Fensters

Bei Überschreitung antwortet die API mit 429 und Retry-After. Bauen Sie Wiederholungen mit Wartezeit ein, statt sofort erneut zu senden. Für große Abgleiche nutzen Sie limit=100, updatedSince und Webhooks, statt regelmäßig alles abzufragen.

Idempotenz

Netzwerke fallen aus. Wenn Ihr Aufruf abbricht, wissen Sie nicht, ob die Rechnung angelegt wurde. Damit eine Wiederholung keinen zweiten Entwurf erzeugt, verlangt jeder POST den Header Idempotency-Key:

Idempotency-Key: 6f9c1c9a-3a1e-4a8e-9a53-0c2b6f6a1d44
  • Wählen Sie einen eindeutigen Wert je beabsichtigter Aktion, am einfachsten eine UUID. Erlaubt sind 8 bis 100 Zeichen: Buchstaben, Ziffern sowie . : _ -.
  • Wiederholen Sie bei einem Abbruch die Anfrage mit demselben Key und demselben Inhalt. Sie erhalten die gespeicherte Antwort, erkennbar am Header Idempotent-Replayed: true, und es entsteht nichts doppelt.
  • Derselbe Key mit anderem Inhalt ist ein Fehler (422), damit Sie keine falsche Antwort bekommen.
  • Fehlgeschlagene Anfragen werden nicht gespeichert. Nach einem 4xx korrigieren Sie den Inhalt und senden mit einem neuen Key.
  • Keys gelten 24 Stunden und gehören zu Ihrem Konto.

PATCH und GET sind von Natur aus wiederholbar und brauchen den Header nicht.

Datenformate

  • IDs sind UUIDs und bleiben für einen Datensatz dauerhaft gleich.
  • Beträge sind Zahlen in Euro (904.4), kein Text und keine Cent-Werte. Die Währung ist immer EUR. Rechnen Sie in Ihrem System mit Dezimalzahlen, nicht mit Fließkomma.
  • Datum ohne Uhrzeit (issueDate, dueDate) kommt als 2026-10-06, Zeitstempel (createdAt, updatedAt) als ISO 8601 in UTC (2026-10-06T10:15:00.000Z).
  • Fehlende Werte sind null, das Feld fehlt nicht.
  • Neue Felder können in Antworten jederzeit hinzukommen. Ihr Code darf unbekannte Felder nicht ablehnen. Bestehende Felder ändern ihre Bedeutung nicht, siehe Versionierung.
  • Preisart: priceMode bestimmt, ob Einzelpreise netto (net, Umsatzsteuer kommt obendrauf) oder brutto (gross, Umsatzsteuer ist enthalten) sind. Bei Kleinunternehmern, Reverse-Charge und steuerfreien Kunden gibt es keine Umsatzsteuer, dort gilt immer net.
  • Rechnungsstatus: draft, sent, paid, partially_paid, overdue, dunning_level_1 bis dunning_level_3, payment_plan, cancelled, cancellation (Stornorechnung) und archived. Neue Status können dazukommen.

Webhooks: Ereignisse empfangen

Statt regelmäßig nachzufragen, lassen Sie sich benachrichtigen. KostenKlar sendet bei einem Ereignis einen POST mit JSON an Ihre Adresse.

Einrichten

  1. Öffnen Sie in KostenKlar Einstellungen, Entwickler & API und legen Sie unter Webhooks einen Endpunkt an: Ihre https-Adresse und die gewünschten Ereignisse.
  2. Kopieren Sie das Signatur-Secret (whsec_...). Es wird nur einmal angezeigt. Speichern Sie es wie einen API-Schlüssel.
  3. Mit Test senden schickt KostenKlar sofort ein ping-Ereignis und zeigt Ihnen die Antwort Ihres Servers.

Der Endpunkt muss öffentlich per https erreichbar sein. Adressen im lokalen Netz oder auf den eigenen Rechner (localhost, 192.168.x.x) werden aus Sicherheitsgründen abgelehnt. Zum Entwickeln nutzen Sie einen Tunnel-Dienst, der eine öffentliche https-Adresse bereitstellt. Weiterleitungen (3xx) werden nicht verfolgt.

Ereignisse

EreignisWanndata
invoice.createdEine Rechnung wurde angelegt, auch als EntwurfInvoice
invoice.sentEine Rechnung wurde versendetInvoice
invoice.paidEine Rechnung wurde vollständig bezahltInvoice
invoice.overdueEine Rechnung ist überfällig gewordenInvoice
quote.createdEin Angebot wurde angelegt, auch als Entwurf oder neue FassungQuote
quote.acceptedEin Angebot wurde angenommenQuote
credit-note.createdEine Gutschrift wurde angelegt, auch als EntwurfCreditNote
payment.receivedEin Zahlungseingang wurde erfasst: per Hand, aus dem Bankabgleich oder über die APIPayment
expense.createdEine Ausgabe oder Einnahme ohne Rechnung wurde angelegtExpense
time-entry.createdEine Zeitbuchung wurde angelegt, auch per StoppuhrTimeEntry
customer.createdEin Kunde wurde angelegtCustomer
customer.updatedEin Kunde wurde geändertCustomer
project.createdEin Projekt wurde angelegtProject
project.updatedEin Projekt wurde geändert, auch beim Statuswechsel und wenn Meilensteine angelegt, geändert, erledigt, abgerechnet oder gelöscht werdenProject
project.deletedEin Projekt wurde gelöscht. data enthält nur id, customerId, name und status vor dem LöschenDeletedProject
task.createdEine Aufgabe wurde angelegtTask
task.completedEine Aufgabe wurde erledigtTask
workflow.triggeredEine eigene Workflow-Regel mit der Aktion „Webhook auslösen“ hat gegriffen (Einstellungen > Automatisierung)WorkflowTrigger
pingTest aus den Einstellungen{ "message": "..." }

Webhooks entstehen auch bei Änderungen, die Sie selbst über die API auslösen, zum Beispiel bei customer.created nach einem POST /v1/customers. Das müssen Sie in Ihrer Anwendung berücksichtigen, damit keine Schleife entsteht.

Aufbau einer Zustellung

POST /webhooks/kostenklar HTTP/1.1
Content-Type: application/json
User-Agent: KostenKlar-Webhooks/1
KostenKlar-Event: invoice.paid
KostenKlar-Delivery: 9b6a2c0e-6d62-4a4b-8c53-5a0f2c3d1e77
KostenKlar-Signature: t=1790000000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd

{
  "id": "9b6a2c0e-6d62-4a4b-8c53-5a0f2c3d1e77",
  "type": "invoice.paid",
  "createdAt": "2026-10-06T10:15:00.000Z",
  "data": { "id": "...", "number": "RE-2026-0042", "status": "paid", "total": 904.4, "...": "..." }
}

data hat dieselbe Form wie die Antwort der jeweiligen Abruf-Endpunkte. id ist je Zustellung eindeutig und bei Wiederholungen identisch. Merken Sie sich die id, um doppelte Zustellungen zu erkennen.

Signatur prüfen

Jede Zustellung ist signiert. Prüfen Sie die Signatur immer, sonst kann jeder, der Ihre Adresse kennt, Ereignisse vortäuschen.

Der Header hat die Form t=<Unix-Zeit>,v1=<Signatur>. So prüfen Sie ihn:

  1. Lesen Sie t und v1 aus dem Header.
  2. Bilden Sie den Text <t>.<Body>. Der Body ist der unveränderte Rohtext der Anfrage, nicht neu serialisiertes JSON.
  3. Berechnen Sie HMAC-SHA256 über diesen Text mit Ihrem Secret (einschließlich des Präfixes whsec_) als Schlüssel, als Hex-Text.
  4. Vergleichen Sie das Ergebnis zeitkonstant mit v1.
  5. Lehnen Sie Zustellungen ab, deren t mehr als 5 Minuten von Ihrer Uhrzeit abweicht. Das verhindert, dass jemand eine abgefangene Zustellung später erneut einspielt.

Node.js (Express)

import crypto from 'node:crypto';
import express from 'express';

const app = express();

// Wichtig: den Rohtext behalten, nicht express.json() davor schalten.
app.post('/webhooks/kostenklar', express.raw({ type: 'application/json' }), (req, res) => {
  const header = req.get('KostenKlar-Signature') ?? '';
  const parts = Object.fromEntries(header.split(',').map((p) => p.trim().split('=')));
  const body = req.body.toString('utf8');

  const expected = crypto
    .createHmac('sha256', process.env.KOSTENKLAR_WEBHOOK_SECRET)
    .update(`${parts.t}.${body}`)
    .digest('hex');

  const given = Buffer.from(parts.v1 ?? '');
  const valid = given.length === expected.length && crypto.timingSafeEqual(given, Buffer.from(expected));
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) <= 300;
  if (!valid || !fresh) return res.sendStatus(400);

  const event = JSON.parse(body);
  // Erst speichern, dann antworten. Doppelte Zustellungen anhand von event.id ignorieren.
  res.sendStatus(200);
});

PHP

<?php
$secret = getenv('KOSTENKLAR_WEBHOOK_SECRET');
$body = file_get_contents('php://input');          // Rohtext, nicht json_decode und wieder encode
$header = $_SERVER['HTTP_KOSTENKLAR_SIGNATURE'] ?? '';

$parts = [];
foreach (explode(',', $header) as $part) {
    [$key, $value] = array_pad(explode('=', trim($part), 2), 2, '');
    $parts[$key] = $value;
}

$expected = hash_hmac('sha256', ($parts['t'] ?? '') . '.' . $body, $secret);
$valid = hash_equals($expected, $parts['v1'] ?? '');
$fresh = abs(time() - (int) ($parts['t'] ?? 0)) <= 300;

if (!$valid || !$fresh) {
    http_response_code(400);
    exit;
}

$event = json_decode($body, true);
// Erst speichern, dann antworten. Doppelte Zustellungen anhand von $event['id'] ignorieren.
http_response_code(200);

Python (Flask)

import hashlib, hmac, os, time
from flask import Flask, request, abort

app = Flask(__name__)

@app.post('/webhooks/kostenklar')
def kostenklar():
    body = request.get_data()                       # Rohbytes
    parts = dict(p.strip().split('=', 1) for p in request.headers.get('KostenKlar-Signature', '').split(',') if '=' in p)

    signed = parts.get('t', '').encode() + b'.' + body
    expected = hmac.new(os.environ['KOSTENKLAR_WEBHOOK_SECRET'].encode(), signed, hashlib.sha256).hexdigest()

    valid = hmac.compare_digest(expected, parts.get('v1', ''))
    fresh = abs(time.time() - int(parts.get('t', '0') or 0)) <= 300
    if not (valid and fresh):
        abort(400)

    event = request.get_json()
    # Erst speichern, dann antworten. Doppelte Zustellungen anhand von event['id'] ignorieren.
    return '', 200

Zustellung und Wiederholung

  • Antworten Sie innerhalb von 5 Sekunden mit einem Status 2xx. Verarbeiten Sie aufwendige Arbeit danach im Hintergrund.
  • Jede andere Antwort, ein Timeout oder ein Verbindungsfehler zählt als Fehlschlag. KostenKlar versucht es dann bis zu fünfmal erneut, nach 1 Minute, 5 Minuten, 30 Minuten, 2 Stunden und 12 Stunden. Danach gilt die Zustellung als fehlgeschlagen.
  • Wiederholungen tragen dieselbe id. Verarbeiten Sie jede id nur einmal.
  • Die Reihenfolge ist nicht garantiert. Ein invoice.paid kann nach einem späteren Ereignis eintreffen. Fragen Sie im Zweifel die aktuellen Daten über die API ab.
  • Schlagen 20 Zustellungen in Folge fehl, deaktivieren wir den Endpunkt automatisch und informieren den Kontoinhaber per E-Mail. In den Einstellungen aktivieren Sie ihn wieder, sobald Ihr Empfänger erreichbar ist.
  • In den Einstellungen sehen Sie die letzten Zustellungen mit Status und Antwortcode und können einzelne erneut senden.
  • Ereignisse, die auftraten, während der Endpunkt deaktiviert war, werden nicht nachgeliefert. Holen Sie sie mit updatedSince nach.

Referenz: Endpunkte

Diese Referenz wird aus openapi.json erzeugt und stimmt daher mit der API überein. Alle Pfade beziehen sich auf https://api.kosten-klar.de.

Konto

GET/v1/me

Verbindungstest

Zeigt, mit welchem Schlüssel Sie sprechen und welche Berechtigungen er hat. Braucht keine bestimmte Berechtigung und eignet sich als erster Aufruf.

Keine bestimmte Berechtigung nötig

Antworten

StatusInhaltBedeutung
200MeDer Schlüssel ist gültig.
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

Kunden

GET/v1/customers

Kunden auflisten

Seitenweise, aufsteigend nach id. Für eine Synchronisation den Filter updatedSince nutzen.

Berechtigung: customers:read

Parameter

NameOrtTypBeschreibung
limitQueryintegerDatensätze pro Seite (1 bis 100, Standard 50).
cursorQuerystringDer Wert nextCursor der vorigen Antwort.
updatedSinceQuerystring (date-time)Nur Datensätze, die seit diesem Zeitpunkt geändert wurden (ISO 8601).

Antworten

StatusInhaltBedeutung
200objectEine Seite mit Datensätzen.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

POST/v1/customers

Kunden anlegen

Pflicht sind ein Name (company oder firstName plus lastName), street, postalCode (genau 5 Ziffern), city und email. Bei type: business ist company Pflicht. Ohne customerNumber vergibt KostenKlar die nächste freie Nummer.

Berechtigung: customers:write

Parameter

NameOrtTypBeschreibung
Idempotency-Key PflichtHeaderstringEindeutiger Wert pro Anfrage, 8 bis 100 Zeichen (Buchstaben, Ziffern, ., :, _, -). Eine Wiederholung mit demselben Key und Inhalt liefert die gespeicherte Antwort statt einen zweiten Datensatz zu erzeugen.

Body (JSON)

Schema CustomerInput

Antworten

StatusInhaltBedeutung
201CustomerDer Kunde wurde angelegt.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
409ErrorDie erste Anfrage mit diesem Idempotency-Key läuft noch (API_IDEMPOTENCY_IN_PROGRESS). Kurz warten und mit demselben Key wiederholen.
422ErrorDer Idempotency-Key wurde bereits mit anderem Inhalt verwendet (API_IDEMPOTENCY_MISMATCH), oder der Kunde beziehungsweise ein Produkt der Rechnung existiert nicht (NOT_FOUND, details.field nennt das Feld).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

GET/v1/customers/{id}

Einen Kunden abrufen

Berechtigung: customers:read

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Antworten

StatusInhaltBedeutung
200CustomerDer Kunde.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

PATCH/v1/customers/{id}

Kunden ändern

Geändert werden nur die gesendeten Felder. Dasselbe Schema wie beim Anlegen, aber alle Felder sind optional. Die Pflichtfelder müssen nach der Änderung weiterhin gefüllt sein.

Berechtigung: customers:write

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Body (JSON)

Schema CustomerInput

Antworten

StatusInhaltBedeutung
200CustomerDer geänderte Kunde.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

Rechnungen

GET/v1/invoices

Rechnungen auflisten

Seitenweise, aufsteigend nach id, mit Positionen.

Berechtigung: invoices:read

Parameter

NameOrtTypBeschreibung
limitQueryintegerDatensätze pro Seite (1 bis 100, Standard 50).
cursorQuerystringDer Wert nextCursor der vorigen Antwort.
updatedSinceQuerystring (date-time)Nur Datensätze, die seit diesem Zeitpunkt geändert wurden (ISO 8601).
statusQuerydraft | sent | paid | overdue | cancelled | archived | cancellation | payment_plan | partially_paid | dunning_level_1 | dunning_level_2 | dunning_level_3Nur Rechnungen mit diesem Status.
customerIdQuerystring (uuid)Nur Rechnungen dieses Kunden.

Antworten

StatusInhaltBedeutung
200objectEine Seite mit Datensätzen.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

POST/v1/invoices

Rechnungsentwurf anlegen

Legt ausschließlich einen Entwurf an (status: draft). Rechnungsnummer, Versand und Festschreiben bleiben in KostenKlar, damit die lückenlose Nummernfolge erhalten bleibt. Summen berechnet KostenKlar selbst.

Berechtigung: invoices:write

Parameter

NameOrtTypBeschreibung
Idempotency-Key PflichtHeaderstringEindeutiger Wert pro Anfrage, 8 bis 100 Zeichen (Buchstaben, Ziffern, ., :, _, -). Eine Wiederholung mit demselben Key und Inhalt liefert die gespeicherte Antwort statt einen zweiten Datensatz zu erzeugen.

Body (JSON)

Schema InvoiceInput

Antworten

StatusInhaltBedeutung
201InvoiceDer Entwurf wurde angelegt.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
409ErrorDie erste Anfrage mit diesem Idempotency-Key läuft noch (API_IDEMPOTENCY_IN_PROGRESS). Kurz warten und mit demselben Key wiederholen.
422ErrorDer Idempotency-Key wurde bereits mit anderem Inhalt verwendet (API_IDEMPOTENCY_MISMATCH), oder der Kunde beziehungsweise ein Produkt der Rechnung existiert nicht (NOT_FOUND, details.field nennt das Feld).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

GET/v1/invoices/{id}

Eine Rechnung abrufen

Berechtigung: invoices:read

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Antworten

StatusInhaltBedeutung
200InvoiceDie Rechnung mit Positionen.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

PATCH/v1/invoices/{id}

Rechnungsentwurf ändern

Ändert nur die gesendeten Felder; items ersetzt alle Positionen. Nur Entwürfe: ausgestellte Rechnungen antworten mit 403 INVOICE_CANNOT_EDIT_NON_DRAFT. Summen berechnet KostenKlar neu.

Berechtigung: invoices:write

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Body (JSON)

Schema InvoiceUpdate

Antworten

StatusInhaltBedeutung
200InvoiceDer geänderte Entwurf.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
422ErrorEin Verweis im Body existiert nicht (NOT_FOUND, details.field nennt das Feld, z. B. customerId).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

GET/v1/invoices/{id}/pdf

Rechnung als PDF herunterladen

Festgeschriebene Rechnungen liefern das unveränderliche PDF, Entwürfe werden beim Abruf frisch erzeugt. Dateiname Rechnung_<Nummer>.pdf.

Berechtigung: invoices:read

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Antworten

StatusInhaltBedeutung
200PDFDas PDF (Content-Disposition: attachment).
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

Angebote

GET/v1/quotes

Angebote auflisten

Seitenweise, aufsteigend nach id, mit Positionen.

Berechtigung: quotes:read

Parameter

NameOrtTypBeschreibung
limitQueryintegerDatensätze pro Seite (1 bis 100, Standard 50).
cursorQuerystringDer Wert nextCursor der vorigen Antwort.
updatedSinceQuerystring (date-time)Nur Datensätze, die seit diesem Zeitpunkt geändert wurden (ISO 8601).
statusQuerydraft | sent | accepted | rejected | archivedNur Angebote mit diesem Status.
customerIdQuerystring (uuid)Nur Angebote dieses Kunden.

Antworten

StatusInhaltBedeutung
200objectEine Seite mit Datensätzen.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

POST/v1/quotes

Angebot anlegen

Legt ein Angebot mit Angebotsnummer an, standardmäßig als Entwurf. Mit markSent: true wird es sofort als versendet markiert (status: sent, Versanddatum gesetzt), ohne E-Mail an den Kunden; das PDF liefert GET /v1/quotes/{id}/pdf. Summen berechnet KostenKlar. Bei priceMode: gross werden die Einzelpreise mit taxRate in Nettopreise umgerechnet (4 Nachkommastellen), Angebote speichern immer netto. Unbekannte Kunden antworten mit 422 und details.field: customerId.

Berechtigung: quotes:write

Parameter

NameOrtTypBeschreibung
Idempotency-Key PflichtHeaderstringEindeutiger Wert pro Anfrage, 8 bis 100 Zeichen (Buchstaben, Ziffern, ., :, _, -). Eine Wiederholung mit demselben Key und Inhalt liefert die gespeicherte Antwort statt einen zweiten Datensatz zu erzeugen.

Body (JSON)

Schema QuoteInput

Antworten

StatusInhaltBedeutung
201QuoteDas Angebot wurde angelegt.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
409ErrorDie erste Anfrage mit diesem Idempotency-Key läuft noch (API_IDEMPOTENCY_IN_PROGRESS). Kurz warten und mit demselben Key wiederholen.
422ErrorDer Idempotency-Key wurde bereits mit anderem Inhalt verwendet (API_IDEMPOTENCY_MISMATCH), oder der Kunde beziehungsweise ein Produkt der Rechnung existiert nicht (NOT_FOUND, details.field nennt das Feld).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

GET/v1/quotes/{id}

Ein Angebot abrufen

Berechtigung: quotes:read

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Antworten

StatusInhaltBedeutung
200QuoteDas Angebot mit Positionen.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

PATCH/v1/quotes/{id}

Angebotsentwurf ändern

Ändert nur die gesendeten Felder; items ersetzt alle Positionen. Nur Entwürfe: versendete Angebote antworten mit 403 QUOTE_CANNOT_EDIT_NON_DRAFT.

Berechtigung: quotes:write

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Body (JSON)

Schema QuoteUpdate

Antworten

StatusInhaltBedeutung
200QuoteDer geänderte Entwurf.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
422ErrorEin Verweis im Body existiert nicht (NOT_FOUND, details.field nennt das Feld, z. B. customerId).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

POST/v1/quotes/{id}/accept

Angebot annehmen

Nur aus status: sent. Ist das Angebot bereits angenommen, kommt es unverändert zurück (200). Andere Status antworten mit 409 QUOTE_INVALID_STATUS_TRANSITION, ein abgelaufenes Angebot (validUntil überschritten) mit 403 QUOTE_EXPIRED. Die Annahme läuft wie in der App: Webhook quote.accepted, Workflows und, falls eingestellt, die automatische Rechnung.

Berechtigung: quotes:write

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)
Idempotency-Key PflichtHeaderstringEindeutiger Wert pro Anfrage, 8 bis 100 Zeichen (Buchstaben, Ziffern, ., :, _, -). Eine Wiederholung mit demselben Key und Inhalt liefert die gespeicherte Antwort statt einen zweiten Datensatz zu erzeugen.

Antworten

StatusInhaltBedeutung
200QuoteDas angenommene Angebot.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
409ErrorDer Status erlaubt die Aktion nicht (QUOTE_INVALID_STATUS_TRANSITION, details.status nennt den aktuellen), oder die erste Anfrage mit diesem Idempotency-Key läuft noch (API_IDEMPOTENCY_IN_PROGRESS).
422ErrorDer Idempotency-Key wurde bereits mit anderem Inhalt verwendet (API_IDEMPOTENCY_MISMATCH), oder der Kunde beziehungsweise ein Produkt der Rechnung existiert nicht (NOT_FOUND, details.field nennt das Feld).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

POST/v1/quotes/{id}/reject

Angebot ablehnen

Nur aus status: sent. Ist das Angebot bereits abgelehnt, kommt es unverändert zurück (200). Andere Status antworten mit 409 QUOTE_INVALID_STATUS_TRANSITION.

Berechtigung: quotes:write

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)
Idempotency-Key PflichtHeaderstringEindeutiger Wert pro Anfrage, 8 bis 100 Zeichen (Buchstaben, Ziffern, ., :, _, -). Eine Wiederholung mit demselben Key und Inhalt liefert die gespeicherte Antwort statt einen zweiten Datensatz zu erzeugen.

Antworten

StatusInhaltBedeutung
200QuoteDas abgelehnte Angebot.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
409ErrorDer Status erlaubt die Aktion nicht (QUOTE_INVALID_STATUS_TRANSITION, details.status nennt den aktuellen), oder die erste Anfrage mit diesem Idempotency-Key läuft noch (API_IDEMPOTENCY_IN_PROGRESS).
422ErrorDer Idempotency-Key wurde bereits mit anderem Inhalt verwendet (API_IDEMPOTENCY_MISMATCH), oder der Kunde beziehungsweise ein Produkt der Rechnung existiert nicht (NOT_FOUND, details.field nennt das Feld).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

GET/v1/quotes/{id}/pdf

Angebot als PDF herunterladen

Das PDF wird beim Abruf mit dem aktuellen Stand und Layout erzeugt, wie in der App. Dateiname Angebot_<Nummer>.pdf.

Berechtigung: quotes:read

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Antworten

StatusInhaltBedeutung
200PDFDas PDF (Content-Disposition: attachment).
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

Gutschriften

GET/v1/credit-notes

Gutschriften auflisten

Seitenweise, aufsteigend nach id, mit Positionen. Alle Status einschließlich Entwürfen.

Berechtigung: credit-notes:read

Parameter

NameOrtTypBeschreibung
limitQueryintegerDatensätze pro Seite (1 bis 100, Standard 50).
cursorQuerystringDer Wert nextCursor der vorigen Antwort.
updatedSinceQuerystring (date-time)Nur Datensätze, die seit diesem Zeitpunkt geändert wurden (ISO 8601).
statusQuerydraft | issued | applied | refunded | cancelledNur Gutschriften mit diesem Status.
customerIdQuerystring (uuid)Nur Gutschriften dieses Kunden.

Antworten

StatusInhaltBedeutung
200objectEine Seite mit Datensätzen.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

POST/v1/credit-notes

Gutschriftentwurf zu einer Rechnung anlegen

Rechnungskorrektur zu einer ausgestellten, nicht stornierten Rechnung, immer als Entwurf. Kunde und Anschrift kommen von der Rechnung, die Nummer vergibt KostenKlar. Ausstellen, Verrechnen und Erstatten bleiben in KostenKlar. Webhook credit-note.created.

Berechtigung: credit-notes:write

Parameter

NameOrtTypBeschreibung
Idempotency-Key PflichtHeaderstringEindeutiger Wert pro Anfrage, 8 bis 100 Zeichen (Buchstaben, Ziffern, ., :, _, -). Eine Wiederholung mit demselben Key und Inhalt liefert die gespeicherte Antwort statt einen zweiten Datensatz zu erzeugen.

Body (JSON)

Schema CreditNoteInput

Antworten

StatusInhaltBedeutung
201CreditNoteDer Gutschriftentwurf.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
409ErrorDie erste Anfrage mit diesem Idempotency-Key läuft noch (API_IDEMPOTENCY_IN_PROGRESS). Kurz warten und mit demselben Key wiederholen.
422ErrorDer Idempotency-Key wurde bereits mit anderem Inhalt verwendet (API_IDEMPOTENCY_MISMATCH), oder der Kunde beziehungsweise ein Produkt der Rechnung existiert nicht (NOT_FOUND, details.field nennt das Feld).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

GET/v1/credit-notes/{id}

Eine Gutschrift abrufen

Berechtigung: credit-notes:read

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Antworten

StatusInhaltBedeutung
200CreditNoteDie Gutschrift mit Positionen.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

GET/v1/credit-notes/{id}/pdf

Gutschrift als PDF herunterladen

Das PDF wird beim Abruf erzeugt, wie in der App. Dateiname Gutschrift_<Nummer>.pdf.

Berechtigung: credit-notes:read

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Antworten

StatusInhaltBedeutung
200PDFDas PDF (Content-Disposition: attachment).
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

Lieferscheine

GET/v1/delivery-notes

Lieferscheine auflisten

Seitenweise, mit Positionen. Modul Lieferscheine, sonst 403 FEATURE_DISABLED.

Berechtigung: delivery-notes:read

Parameter

NameOrtTypBeschreibung
limitQueryintegerDatensätze pro Seite (1 bis 100, Standard 50).
cursorQuerystringDer Wert nextCursor der vorigen Antwort.
updatedSinceQuerystring (date-time)Nur Datensätze, die seit diesem Zeitpunkt geändert wurden (ISO 8601).
statusQuerydraft | issued | cancelled
customerIdQuerystring (uuid)

Antworten

StatusInhaltBedeutung
200objectEine Seite mit Datensätzen.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

GET/v1/delivery-notes/{id}

Einen Lieferschein abrufen

Berechtigung: delivery-notes:read

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Antworten

StatusInhaltBedeutung
200DeliveryNoteDer Lieferschein mit Positionen.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

GET/v1/delivery-notes/{id}/pdf

Lieferschein als PDF herunterladen

Dateiname Lieferschein_<Nummer>.pdf.

Berechtigung: delivery-notes:read

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Antworten

StatusInhaltBedeutung
200PDFDas PDF (Content-Disposition: attachment).
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

Mahnungen

GET/v1/dunnings

Mahnungen auflisten

Seitenweise. Mahnläufe und Versand bleiben in KostenKlar. Modul Mahnwesen, sonst 403 FEATURE_DISABLED.

Berechtigung: dunnings:read

Parameter

NameOrtTypBeschreibung
limitQueryintegerDatensätze pro Seite (1 bis 100, Standard 50).
cursorQuerystringDer Wert nextCursor der vorigen Antwort.
updatedSinceQuerystring (date-time)Nur Datensätze, die seit diesem Zeitpunkt geändert wurden (ISO 8601).
statusQuerypending | sent | paid | cancelled | escalated
levelQueryfirst | second | final | collections
invoiceIdQuerystring (uuid)
customerIdQuerystring (uuid)

Antworten

StatusInhaltBedeutung
200objectEine Seite mit Datensätzen.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

GET/v1/dunnings/{id}

Eine Mahnung abrufen

Berechtigung: dunnings:read

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Antworten

StatusInhaltBedeutung
200DunningDie Mahnung.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

GET/v1/dunnings/{id}/pdf

Mahnung als PDF herunterladen

Dateiname Mahnung_<Rechnungsnummer>_<Stufe>.pdf.

Berechtigung: dunnings:read

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Antworten

StatusInhaltBedeutung
200PDFDas PDF (Content-Disposition: attachment).
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

Verträge

GET/v1/contracts

Verträge auflisten

Seitenweise, aufsteigend nach id, mit Positionen. Alle Status einschließlich Entwürfen; filtern Sie bei Bedarf selbst oder mit status.

Berechtigung: contracts:read

Parameter

NameOrtTypBeschreibung
limitQueryintegerDatensätze pro Seite (1 bis 100, Standard 50).
cursorQuerystringDer Wert nextCursor der vorigen Antwort.
updatedSinceQuerystring (date-time)Nur Datensätze, die seit diesem Zeitpunkt geändert wurden (ISO 8601).
statusQuerydraft | sent | signed | active | expired | cancelledNur Verträge mit diesem Status.
customerIdQuerystring (uuid)Nur Verträge dieses Kunden.

Antworten

StatusInhaltBedeutung
200objectEine Seite mit Datensätzen.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

GET/v1/contracts/{id}

Einen Vertrag abrufen

Berechtigung: contracts:read

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Antworten

StatusInhaltBedeutung
200ContractDer Vertrag mit Positionen.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

GET/v1/contracts/{id}/usage

Zeitverbrauch eines Vertrags

Inklusivzeit und gebuchte Zeit im aktuellen Abrechnungszeitraum, z. B. für Wartungsverträge. Der Zeitraum endet mit der nächsten Rechnung (nextInvoiceDate); bei einmaligen Verträgen oder ohne nächstes Rechnungsdatum ist es die gesamte Laufzeit. Gezählt werden alle Zeitbuchungen für den Kunden des Vertrags im Zeitraum, bei mehreren gleichzeitig laufenden Verträgen desselben Kunden also auch Zeit für die anderen. Enthält bewusst keine Beträge oder Stundensätze.

Berechtigung: contracts:read

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Antworten

StatusInhaltBedeutung
200ContractUsageDer Zeitverbrauch.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

GET/v1/contracts/{id}/pdf

Vertrag als PDF herunterladen

Das PDF mit Vertragstext und, falls vorhanden, digitaler Unterschrift wird beim Abruf erzeugt. Dateiname Vertrag_<Nummer>.pdf.

Berechtigung: contracts:read

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Antworten

StatusInhaltBedeutung
200PDFDas PDF (Content-Disposition: attachment).
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

Zahlungen

POST/v1/invoices/{id}/payments

Zahlungseingang zu einer Rechnung erfassen

Wie „Zahlung erfassen“ in KostenKlar: Teil- oder Vollzahlung bis zum offenen Betrag. Bei Vollzahlung wird die Rechnung bezahlt (Webhook invoice.paid). Die Zahlung landet in der EÜR (Webhook payment.received). Nicht möglich bei Entwürfen, stornierten oder bereits bezahlten Rechnungen (403) und in festgeschriebenen Monaten.

Berechtigung: payments:write

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)
Idempotency-Key PflichtHeaderstringEindeutiger Wert pro Anfrage, 8 bis 100 Zeichen (Buchstaben, Ziffern, ., :, _, -). Eine Wiederholung mit demselben Key und Inhalt liefert die gespeicherte Antwort statt einen zweiten Datensatz zu erzeugen.

Body (JSON)

Schema PaymentInput

Antworten

StatusInhaltBedeutung
201InvoiceDie Rechnung mit neuem Zahlstand.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
409ErrorDie erste Anfrage mit diesem Idempotency-Key läuft noch (API_IDEMPOTENCY_IN_PROGRESS). Kurz warten und mit demselben Key wiederholen.
422ErrorDer Idempotency-Key wurde bereits mit anderem Inhalt verwendet (API_IDEMPOTENCY_MISMATCH), oder der Kunde beziehungsweise ein Produkt der Rechnung existiert nicht (NOT_FOUND, details.field nennt das Feld).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

GET/v1/payments

Zahlungen auflisten

Zahlungen der Einnahmen-Überschuss-Rechnung mit Zuordnung zu Rechnungen und Gutschriften, seitenweise. Modul EÜR, sonst 403 FEATURE_DISABLED.

Berechtigung: payments:read

Parameter

NameOrtTypBeschreibung
limitQueryintegerDatensätze pro Seite (1 bis 100, Standard 50).
cursorQuerystringDer Wert nextCursor der vorigen Antwort.
updatedSinceQuerystring (date-time)Nur Datensätze, die seit diesem Zeitpunkt geändert wurden (ISO 8601).
directionQueryin | out
fromQuerystring (date)Erster Tag (JJJJ-MM-TT, einschließlich).
toQuerystring (date)Letzter Tag (JJJJ-MM-TT, einschließlich).

Antworten

StatusInhaltBedeutung
200objectEine Seite mit Datensätzen.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

GET/v1/payments/{id}

Eine Zahlung abrufen

Berechtigung: payments:read

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Antworten

StatusInhaltBedeutung
200PaymentDie Zahlung mit Zuordnungen.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

Ausgaben

GET/v1/expenses

Ausgaben auflisten

Ausgaben und Einnahmen ohne Rechnung, seitenweise, ohne Belegdatei. Modul Ausgaben, sonst 403 FEATURE_DISABLED.

Berechtigung: expenses:read

Parameter

NameOrtTypBeschreibung
limitQueryintegerDatensätze pro Seite (1 bis 100, Standard 50).
cursorQuerystringDer Wert nextCursor der vorigen Antwort.
updatedSinceQuerystring (date-time)Nur Datensätze, die seit diesem Zeitpunkt geändert wurden (ISO 8601).
typeQueryexpense | income
categoryQueryoffice | travel | equipment | software | marketing | training | utilities | rent | insurance | taxes | salary | meals | entertainment | other
fromQuerystring (date)Erster Tag (JJJJ-MM-TT, einschließlich).
toQuerystring (date)Letzter Tag (JJJJ-MM-TT, einschließlich).

Antworten

StatusInhaltBedeutung
200objectEine Seite mit Datensätzen.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

POST/v1/expenses

Ausgabe anlegen

Mit denselben Prüfungen wie in KostenKlar (§ 13b nur mit Steuersatz, Privat ohne Steuer, festgeschriebene Monate, Kassenabschluss bei Barzahlung). Aufteilung auf mehrere Steuersätze und Anlagegüter bleiben in KostenKlar. Webhook expense.created.

Berechtigung: expenses:write

Parameter

NameOrtTypBeschreibung
Idempotency-Key PflichtHeaderstringEindeutiger Wert pro Anfrage, 8 bis 100 Zeichen (Buchstaben, Ziffern, ., :, _, -). Eine Wiederholung mit demselben Key und Inhalt liefert die gespeicherte Antwort statt einen zweiten Datensatz zu erzeugen.

Body (JSON)

Schema ExpenseInput

Antworten

StatusInhaltBedeutung
201ExpenseDie angelegte Ausgabe.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
409ErrorDie erste Anfrage mit diesem Idempotency-Key läuft noch (API_IDEMPOTENCY_IN_PROGRESS). Kurz warten und mit demselben Key wiederholen.
422ErrorDer Idempotency-Key wurde bereits mit anderem Inhalt verwendet (API_IDEMPOTENCY_MISMATCH), oder der Kunde beziehungsweise ein Produkt der Rechnung existiert nicht (NOT_FOUND, details.field nennt das Feld).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

GET/v1/expenses/{id}

Eine Ausgabe abrufen

Berechtigung: expenses:read

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Antworten

StatusInhaltBedeutung
200ExpenseDie Ausgabe.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

PATCH/v1/expenses/{id}

Ausgabe ändern

Ändert nur die gesendeten Felder, mit denselben Prüfungen wie beim Anlegen.

Berechtigung: expenses:write

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Body (JSON)

Schema ExpenseUpdate

Antworten

StatusInhaltBedeutung
200ExpenseDie geänderte Ausgabe.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
422ErrorEin Verweis im Body existiert nicht (NOT_FOUND, details.field nennt das Feld, z. B. customerId).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

POST/v1/expenses/{id}/receipt

Beleg hochladen

Beleg als PDF, PNG, JPG oder E-Rechnung (XML) im Feld file (multipart/form-data), höchstens 10 MB. Ein vorhandener Beleg wird als Version archiviert (GoBD).

Berechtigung: expenses:write

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Antworten

StatusInhaltBedeutung
200ExpenseDie Ausgabe mit Beleg.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

Produkte

GET/v1/products

Produkte und Leistungen auflisten

Seitenweise, nur nicht archivierte Einträge.

Berechtigung: products:read

Parameter

NameOrtTypBeschreibung
limitQueryintegerDatensätze pro Seite (1 bis 100, Standard 50).
cursorQuerystringDer Wert nextCursor der vorigen Antwort.
updatedSinceQuerystring (date-time)Nur Datensätze, die seit diesem Zeitpunkt geändert wurden (ISO 8601).

Antworten

StatusInhaltBedeutung
200objectEine Seite mit Datensätzen.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

POST/v1/products

Produkt anlegen

Wie im Produktkatalog: code ist je Konto eindeutig. Ohne type ist es eine Leistung (service, 60 Minuten je Einheit). Mit trackStock wird stockQuantity als Anfangsbestand gebucht. Schreiben verlangt das Modul Produktkatalog (403 FEATURE_DISABLED).

Berechtigung: products:write

Parameter

NameOrtTypBeschreibung
Idempotency-Key PflichtHeaderstringEindeutiger Wert pro Anfrage, 8 bis 100 Zeichen (Buchstaben, Ziffern, ., :, _, -). Eine Wiederholung mit demselben Key und Inhalt liefert die gespeicherte Antwort statt einen zweiten Datensatz zu erzeugen.

Body (JSON)

Schema ProductInput

Antworten

StatusInhaltBedeutung
201ProductDas angelegte Produkt.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
409ErrorDie erste Anfrage mit diesem Idempotency-Key läuft noch (API_IDEMPOTENCY_IN_PROGRESS). Kurz warten und mit demselben Key wiederholen.
422ErrorDer Idempotency-Key wurde bereits mit anderem Inhalt verwendet (API_IDEMPOTENCY_MISMATCH), oder der Kunde beziehungsweise ein Produkt der Rechnung existiert nicht (NOT_FOUND, details.field nennt das Feld).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

GET/v1/products/{id}

Ein Produkt abrufen

Berechtigung: products:read

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Antworten

StatusInhaltBedeutung
200ProductDas Produkt.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

PATCH/v1/products/{id}

Produkt ändern

Ändert nur die gesendeten Felder. Den Bestand ändern Sie über POST /v1/products/{id}/stock-movements.

Berechtigung: products:write

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Body (JSON)

Schema ProductUpdate

Antworten

StatusInhaltBedeutung
200ProductDas geänderte Produkt.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
422ErrorEin Verweis im Body existiert nicht (NOT_FOUND, details.field nennt das Feld, z. B. customerId).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

POST/v1/products/{id}/stock-movements

Lagerbewegung buchen

Zugang (in), Abgang (out) oder Inventur (correction: quantity ist der gezählte Bestand, gebucht wird die Differenz). Nur bei Produkten mit trackStock.

Berechtigung: products:write

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)
Idempotency-Key PflichtHeaderstringEindeutiger Wert pro Anfrage, 8 bis 100 Zeichen (Buchstaben, Ziffern, ., :, _, -). Eine Wiederholung mit demselben Key und Inhalt liefert die gespeicherte Antwort statt einen zweiten Datensatz zu erzeugen.

Body (JSON)

Schema StockMovementInput

Antworten

StatusInhaltBedeutung
201ProductDas Produkt mit neuem Bestand.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
409ErrorDie erste Anfrage mit diesem Idempotency-Key läuft noch (API_IDEMPOTENCY_IN_PROGRESS). Kurz warten und mit demselben Key wiederholen.
422ErrorDer Idempotency-Key wurde bereits mit anderem Inhalt verwendet (API_IDEMPOTENCY_MISMATCH), oder der Kunde beziehungsweise ein Produkt der Rechnung existiert nicht (NOT_FOUND, details.field nennt das Feld).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

Projekte

GET/v1/projects

Projekte auflisten

Seitenweise, aufsteigend nach id. Projekte gehören zum Modul Zeiterfassung; ist es im Konto aus, antworten alle Projekt-Endpunkte mit 403 FEATURE_DISABLED.

Berechtigung: projects:read

Parameter

NameOrtTypBeschreibung
limitQueryintegerDatensätze pro Seite (1 bis 100, Standard 50).
cursorQuerystringDer Wert nextCursor der vorigen Antwort.
updatedSinceQuerystring (date-time)Nur Datensätze, die seit diesem Zeitpunkt geändert wurden (ISO 8601).
statusQueryactive | completed | archivedNur Projekte mit diesem Status.
customerIdQuerystring (uuid)Nur Projekte dieses Kunden.

Antworten

StatusInhaltBedeutung
200objectEine Seite mit Datensätzen.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

POST/v1/projects

Projekt anlegen

Neue Projekte starten mit status: active. Der Kunde muss bereits existieren.

Berechtigung: projects:write

Parameter

NameOrtTypBeschreibung
Idempotency-Key PflichtHeaderstringEindeutiger Wert pro Anfrage, 8 bis 100 Zeichen (Buchstaben, Ziffern, ., :, _, -). Eine Wiederholung mit demselben Key und Inhalt liefert die gespeicherte Antwort statt einen zweiten Datensatz zu erzeugen.

Body (JSON)

Schema ProjectInput

Antworten

StatusInhaltBedeutung
201ProjectDas Projekt wurde angelegt.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
409ErrorDie erste Anfrage mit diesem Idempotency-Key läuft noch (API_IDEMPOTENCY_IN_PROGRESS). Kurz warten und mit demselben Key wiederholen.
422ErrorDer Idempotency-Key wurde bereits mit anderem Inhalt verwendet (API_IDEMPOTENCY_MISMATCH), oder der Kunde beziehungsweise ein Produkt der Rechnung existiert nicht (NOT_FOUND, details.field nennt das Feld).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

GET/v1/projects/{id}

Ein Projekt abrufen

Berechtigung: projects:read

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Antworten

StatusInhaltBedeutung
200ProjectDas Projekt.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

PATCH/v1/projects/{id}

Projekt ändern

Geändert werden nur die gesendeten Felder. Abschließen mit status: completed, statt Löschen status: archived. null entfernt Datum oder Budget.

Berechtigung: projects:write

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Body (JSON)

Schema ProjectUpdate

Antworten

StatusInhaltBedeutung
200ProjectDas geänderte Projekt.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
422ErrorEin Verweis im Body existiert nicht (NOT_FOUND, details.field nennt das Feld, z. B. customerId).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

GET/v1/projects/{id}/milestones

Meilensteine eines Projekts

Meilensteine bzw. Abschläge in ihrer Reihenfolge, mit Betrag und Abrechnungsstand.

Berechtigung: projects:read

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Antworten

StatusInhaltBedeutung
200objectDie Meilensteine.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

GET/v1/projects/{id}/summary

Fortschritt eines Projekts

Budget, erfasste Zeiten, abgerechneter Betrag, Meilensteine und Aufgaben auf einen Blick. Enthält bewusst keine Kosten, Margen oder Stundensätze.

Berechtigung: projects:read

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Antworten

StatusInhaltBedeutung
200ProjectSummaryDer Projektfortschritt.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

GET/v1/projects/{id}/acceptances

Abnahmeprotokolle eines Projekts

Alle Abnahmeprotokolle, aufsteigend nach protocolDate. Ohne Unterschriftsbilder; das unterschriebene Dokument liefert der PDF-Endpunkt.

Berechtigung: projects:read

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Antworten

StatusInhaltBedeutung
200objectDie Abnahmeprotokolle.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

GET/v1/projects/{id}/acceptances/{acceptanceId}/pdf

Abnahmeprotokoll als PDF herunterladen

Protokoll mit Mängelliste und Unterschriften. Dateiname Abnahme_<protocolDate>.pdf.

Berechtigung: projects:read

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)
acceptanceId PflichtPfadstring (uuid)ID des Abnahmeprotokolls aus GET /v1/projects/{id}/acceptances.

Antworten

StatusInhaltBedeutung
200PDFDas PDF (Content-Disposition: attachment).
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

Aufgaben

GET/v1/tasks

Aufgaben auflisten

Seitenweise, aufsteigend nach id, mit Teilschritten. Ohne status kommen offene und erledigte Aufgaben.

Berechtigung: tasks:read

Parameter

NameOrtTypBeschreibung
limitQueryintegerDatensätze pro Seite (1 bis 100, Standard 50).
cursorQuerystringDer Wert nextCursor der vorigen Antwort.
updatedSinceQuerystring (date-time)Nur Datensätze, die seit diesem Zeitpunkt geändert wurden (ISO 8601).
statusQueryopen | done | allopen, done oder all (Standard).
customerIdQuerystring (uuid)Nur Aufgaben dieses Kunden.
projectIdQuerystring (uuid)Nur Aufgaben dieses Projekts.

Antworten

StatusInhaltBedeutung
200objectEine Seite mit Datensätzen.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

POST/v1/tasks

Aufgabe anlegen

Optional einem Kunden, Projekt und einer Person zugeordnet. Unbekannte Verweise antworten mit 422 und details.field.

Berechtigung: tasks:write

Parameter

NameOrtTypBeschreibung
Idempotency-Key PflichtHeaderstringEindeutiger Wert pro Anfrage, 8 bis 100 Zeichen (Buchstaben, Ziffern, ., :, _, -). Eine Wiederholung mit demselben Key und Inhalt liefert die gespeicherte Antwort statt einen zweiten Datensatz zu erzeugen.

Body (JSON)

Schema TaskInput

Antworten

StatusInhaltBedeutung
201TaskDie Aufgabe wurde angelegt.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
409ErrorDie erste Anfrage mit diesem Idempotency-Key läuft noch (API_IDEMPOTENCY_IN_PROGRESS). Kurz warten und mit demselben Key wiederholen.
422ErrorDer Idempotency-Key wurde bereits mit anderem Inhalt verwendet (API_IDEMPOTENCY_MISMATCH), oder der Kunde beziehungsweise ein Produkt der Rechnung existiert nicht (NOT_FOUND, details.field nennt das Feld).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

GET/v1/tasks/{id}

Eine Aufgabe abrufen

Berechtigung: tasks:read

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Antworten

StatusInhaltBedeutung
200TaskDie Aufgabe mit Teilschritten.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

PATCH/v1/tasks/{id}

Aufgabe ändern

Geändert werden nur die gesendeten Felder. status: done erledigt die Aufgabe; bei einer Wiederholung legt KostenKlar die nächste an. null löst Zuordnungen bzw. entfernt Datum und Aufwand.

Berechtigung: tasks:write

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Body (JSON)

Schema TaskUpdate

Antworten

StatusInhaltBedeutung
200TaskDie geänderte Aufgabe.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
422ErrorEin Verweis im Body existiert nicht (NOT_FOUND, details.field nennt das Feld, z. B. customerId).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

POST/v1/tasks/{id}/checklist

Teilschritt hinzufügen

Hängt einen Teilschritt am Ende an.

Berechtigung: tasks:write

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)
Idempotency-Key PflichtHeaderstringEindeutiger Wert pro Anfrage, 8 bis 100 Zeichen (Buchstaben, Ziffern, ., :, _, -). Eine Wiederholung mit demselben Key und Inhalt liefert die gespeicherte Antwort statt einen zweiten Datensatz zu erzeugen.

Body (JSON)

Schema ChecklistItemInput

Antworten

StatusInhaltBedeutung
201ChecklistItemDer Teilschritt wurde angelegt.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
409ErrorDie erste Anfrage mit diesem Idempotency-Key läuft noch (API_IDEMPOTENCY_IN_PROGRESS). Kurz warten und mit demselben Key wiederholen.
422ErrorDer Idempotency-Key wurde bereits mit anderem Inhalt verwendet (API_IDEMPOTENCY_MISMATCH), oder der Kunde beziehungsweise ein Produkt der Rechnung existiert nicht (NOT_FOUND, details.field nennt das Feld).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

PATCH/v1/tasks/{id}/checklist/{itemId}

Teilschritt ändern

Titel ändern oder mit done abhaken.

Berechtigung: tasks:write

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)
itemId PflichtPfadstring (uuid)

Body (JSON)

Schema ChecklistItemUpdate

Antworten

StatusInhaltBedeutung
200ChecklistItemDer geänderte Teilschritt.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
422ErrorEin Verweis im Body existiert nicht (NOT_FOUND, details.field nennt das Feld, z. B. customerId).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

Zeiterfassung

GET/v1/time-entries

Zeitbuchungen auflisten

Seitenweise, aufsteigend nach id. Gelöschte Buchungen fehlen. Ohne Stundensätze, Kosten und Mitarbeiter. Zeitbuchungen gehören zum Modul Zeiterfassung; ist es im Konto aus, antworten die Endpunkte mit 403 FEATURE_DISABLED.

Berechtigung: time-entries:read

Parameter

NameOrtTypBeschreibung
limitQueryintegerDatensätze pro Seite (1 bis 100, Standard 50).
cursorQuerystringDer Wert nextCursor der vorigen Antwort.
updatedSinceQuerystring (date-time)Nur Datensätze, die seit diesem Zeitpunkt geändert wurden (ISO 8601).
projectIdQuerystring (uuid)Nur Buchungen dieses Projekts.
customerIdQuerystring (uuid)Nur Buchungen dieses Kunden.
fromQuerystring (date)Erster Tag (einschließlich), bezogen auf den Beginn der Buchung in Europe/Berlin.
toQuerystring (date)Letzter Tag (einschließlich).

Antworten

StatusInhaltBedeutung
200objectEine Seite mit Datensätzen.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

POST/v1/time-entries

Zeitbuchung anlegen

Bucht für den Kontoinhaber (sein Mitarbeiterprofil) mit denselben Prüfungen wie die App: Beginn nicht in der Zukunft, keine Überschneidung, Woche nicht eingereicht, höchstens 16 Stunden. Stundensatz und Kosten kommen aus dem Mitarbeiterprofil. Ohne Mitarbeiterprofil: 422. Webhook time-entry.created.

Berechtigung: time-entries:write

Parameter

NameOrtTypBeschreibung
Idempotency-Key PflichtHeaderstringEindeutiger Wert pro Anfrage, 8 bis 100 Zeichen (Buchstaben, Ziffern, ., :, _, -). Eine Wiederholung mit demselben Key und Inhalt liefert die gespeicherte Antwort statt einen zweiten Datensatz zu erzeugen.

Body (JSON)

Schema TimeEntryInput

Antworten

StatusInhaltBedeutung
201TimeEntryDie angelegte Buchung.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
409ErrorDie erste Anfrage mit diesem Idempotency-Key läuft noch (API_IDEMPOTENCY_IN_PROGRESS). Kurz warten und mit demselben Key wiederholen.
422ErrorDer Idempotency-Key wurde bereits mit anderem Inhalt verwendet (API_IDEMPOTENCY_MISMATCH), oder der Kunde beziehungsweise ein Produkt der Rechnung existiert nicht (NOT_FOUND, details.field nennt das Feld).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

GET/v1/time-entries/{id}

Eine Zeitbuchung abrufen

Berechtigung: time-entries:read

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Antworten

StatusInhaltBedeutung
200TimeEntryDie Zeitbuchung.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

PATCH/v1/time-entries/{id}

Zeitbuchung ändern

Nur offene Buchungen (editable: true). Ändert nur die gesendeten Felder; das Ende rechnet KostenKlar aus Beginn, Dauer und Pause neu.

Berechtigung: time-entries:write

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Body (JSON)

Schema TimeEntryUpdate

Antworten

StatusInhaltBedeutung
200TimeEntryDie geänderte Buchung.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
422ErrorEin Verweis im Body existiert nicht (NOT_FOUND, details.field nennt das Feld, z. B. customerId).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

Kalender

GET/v1/calendar/busy

Belegte Zeiten

Alle eigenen Termine des Kontos, die den Zeitraum berühren, ohne abgesagte (cancelled). Serien sind in einzelne Termine ausgeklappt. Fristen aus Rechnungen, Aufgaben usw. zählen nicht. Bewusst ohne Titel und Inhalte. Ganztägige Termine reichen von 00:00 Uhr des ersten bis 00:00 Uhr nach dem letzten Tag in Europe/Berlin. Sortiert nach Beginn. Der Zeitraum darf höchstens 62 Tage umfassen. Kalender ist ein Modul; ist es im Konto aus, antworten alle Kalender-Endpunkte mit 403 FEATURE_DISABLED.

Berechtigung: calendar:read

Parameter

NameOrtTypBeschreibung
from PflichtQuerystring (date-time)Beginn des Zeitraums (ISO 8601 mit Uhrzeit).
to PflichtQuerystring (date-time)Ende des Zeitraums (ausschließlich), nach from, höchstens 62 Tage später.

Antworten

StatusInhaltBedeutung
200objectDie belegten Zeiten.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

POST/v1/calendar/events

Termin anlegen

Legt einen Termin mit Uhrzeit an (keine Serie, nicht ganztägig), standardmäßig als tentative. Das Ende muss nach dem Beginn liegen, höchstens 12 Stunden. Es gelten die Grenzen des Tarifs (Free-Plan: Zahl künftiger Termine, 403 FREE_PLAN_CALENDAR_LIMIT_REACHED). Eine Verknüpfung (linkType und linkId nur gemeinsam) muss auf einen eigenen Kunden bzw. ein eigenes Projekt zeigen, sonst 422 mit details.field: linkId.

Berechtigung: calendar:write

Parameter

NameOrtTypBeschreibung
Idempotency-Key PflichtHeaderstringEindeutiger Wert pro Anfrage, 8 bis 100 Zeichen (Buchstaben, Ziffern, ., :, _, -). Eine Wiederholung mit demselben Key und Inhalt liefert die gespeicherte Antwort statt einen zweiten Datensatz zu erzeugen.

Body (JSON)

Schema CalendarEventInput

Antworten

StatusInhaltBedeutung
201CalendarEventDer Termin wurde angelegt.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
409ErrorDie erste Anfrage mit diesem Idempotency-Key läuft noch (API_IDEMPOTENCY_IN_PROGRESS). Kurz warten und mit demselben Key wiederholen.
422ErrorDer Idempotency-Key wurde bereits mit anderem Inhalt verwendet (API_IDEMPOTENCY_MISMATCH), oder der Kunde beziehungsweise ein Produkt der Rechnung existiert nicht (NOT_FOUND, details.field nennt das Feld).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

GET/v1/calendar/events/{id}

Einen Termin abrufen

Berechtigung: calendar:read

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Antworten

StatusInhaltBedeutung
200CalendarEventDer Termin. Bei Serien die Zeiten des ersten Termins.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

PATCH/v1/calendar/events/{id}

Termin ändern

Geändert werden nur die gesendeten Felder. Absagen mit status: cancelled. Wird nur start gesendet, bleibt die Dauer erhalten. Höchstens 12 Stunden. Serien und das Verschieben ganztägiger Termine antworten mit 400.

Berechtigung: calendar:write

Parameter

NameOrtTypBeschreibung
id PflichtPfadstring (uuid)

Body (JSON)

Schema CalendarEventUpdate

Antworten

StatusInhaltBedeutung
200CalendarEventDer geänderte Termin.
400ErrorDie Anfrage ist ungültig (Format, Pflichtfeld, unbekanntes Feld, ungültiger Cursor oder fehlender Idempotency-Key).
401ErrorSchlüssel fehlt, ist ungültig, widerrufen oder abgelaufen (API_KEY_INVALID, API_KEY_EXPIRED).
403ErrorDem Schlüssel fehlt die Berechtigung (API_SCOPE_MISSING) oder die API ist für das Konto nicht freigeschaltet (API_NOT_AVAILABLE).
404ErrorDen Datensatz gibt es nicht. Fremde Datensätze liefern ebenfalls 404, nie 403.
422ErrorEin Verweis im Body existiert nicht (NOT_FOUND, details.field nennt das Feld, z. B. customerId).
429ErrorZu viele Anfragen (API_RATE_LIMITED). Der Header Retry-After nennt die Wartezeit in Sekunden.

Referenz: Objekte

Die Felder der Antworten und der Eingaben. Typ string | null bedeutet, dass der Wert fehlen kann und dann null ist.

Customer

FeldTypHinweis
idstring (uuid)
customerNumberstring | null
typeprivate | business | null
partyTypecustomer | prospect | supplier | partner | other | null
companystring | null
salutationstring | null
firstNamestring | null
lastNamestring | null
emailstring | null
phonestring | null
mobilePhonestring | null
websitestring | null
contactPersonstring | null
addressAddress
shippingAddressnull | objectAbweichende Lieferadresse, sonst null.
contactsobject[]
taxNumberstring | null
vatIdstring | null
leitwegIdstring | null
taxModestandard | reverse_charge | tax_exempt | null
tagsstring[]
defaultPaymentDaysnumber | null
isActiveboolean
isBlockedboolean
createdAtstring (date-time) | null
updatedAtstring (date-time) | null

CustomerInput

FeldTypHinweis
salutationstringBeispiel Frau
customerTypeprivate | business
partyTypecustomer | prospect | supplier | partner | other
customerNumberstringOptional. Ohne Angabe vergibt KostenKlar die nächste Nummer.
firstNamestring
lastNamestring
companystring
contactPersonstring
emailstring (email)
phonestring
mobilePhonestring
websitestring
streetstring
postalCodestringGenau 5 Ziffern.
citystring
countrystring
taxNumberstring
vatIdstring
leitwegIdstringLeitweg-ID für Rechnungen an öffentliche Auftraggeber (XRechnung).. höchstens 46 Zeichen
taxModestandard | reverse_charge | tax_exempt
defaultPaymentDaysnumberZahlungsziel in Tagen.. mindestens 0
tagsstring[]höchstens 25 Einträge

Invoice

FeldTypHinweis
idstring (uuid)
numberstring | nullnull bei Entwürfen. Die Nummer vergibt KostenKlar beim Festschreiben.
statusdraft | sent | paid | overdue | cancelled | archived | cancellation | payment_plan | partially_paid | dunning_level_1 | dunning_level_2 | dunning_level_3
customerIdstring (uuid) | null
customerNamestring | null
issueDatestring (date) | null
dueDatestring (date) | null
serviceDatestring (date) | null
paidDatestring (date) | null
currencystringBeispiel EUR
priceModenet | gross
subtotalnumber | null
taxRatenumber | null
taxAmountnumber | null
totalnumber | null
paidAmountnumber | null
reverseChargeboolean
isCancellationboolean
cancelledInvoiceIdstring (uuid) | null
poNumberstring | null
projectReferencestring | null
projectIdstring (uuid) | nullZugeordnetes Projekt in KostenKlar.
paymentReferencestring | null
propertyServicebooleanLeistung am Grundstück: Bei Privatkunden steht der Aufbewahrungshinweis (§ 14b Abs. 1 Satz 5 UStG) auf der Rechnung.
ossCountrystring | nullOSS-Bestimmungsland (ISO 3166-1 alpha-2). null = Umsatz im Inland.
ossSupplyTypegoods | digital | null
laborCostobject | nullArbeits-, Maschinen- und Fahrtkosten (§ 35a EStG) der labor-Positionen nach Rabatt, inklusive Umsatzsteuer. null ohne solche Positionen. Auf dem PDF steht der Betrag nur bei Privatkunden.
itemsInvoiceItem[]
createdAtstring (date-time) | null
updatedAtstring (date-time) | null

InvoiceItem

FeldTypHinweis
positioninteger | null
descriptionstring
quantitynumber | null
unitstring | null
unitPricenumber | nullEinzelpreis in Euro, netto oder brutto je nach priceMode.
discountPercentnumber | null
taxRatenumber | null
totalnumber | null
productIdstring (uuid) | null
costTypelabor | material | nullKostenart nach § 35a EStG: labor = Arbeits-, Maschinen- und Fahrtkosten.

InvoiceInput

FeldTypHinweis
customerId Pflichtstring (uuid)Der Kunde muss bereits existieren (siehe POST /v1/customers).
issueDate Pflichtstring (date)Beispiel 2026-10-06
dueDate Pflichtstring (date)Beispiel 2026-10-20
serviceDatestring (date)
servicePeriodStartstring (date)
servicePeriodEndstring (date)
taxRatenumbermindestens 0. höchstens 100. Beispiel 19
priceModenet | grossStandard net
introTextstringhöchstens 5000 Zeichen
customerNotesstringHinweis an den Kunden, wird auf dem PDF gedruckt.. höchstens 5000 Zeichen
poNumberstringhöchstens 100 Zeichen
projectReferencestringhöchstens 100 Zeichen
projectIdstring (uuid)Optional: Projekt des Kunden, dem die Rechnung zugeordnet wird.
paymentReferencestringhöchstens 140 Zeichen
propertyServicebooleanLeistung am Grundstück. Fehlt der Wert, gilt der Standard aus dem Profil; bei OSS-Rechnungen immer false.
ossCountryAT | BE | BG | CY | CZ | DK | EE | GR | ES | FI | FR | HR | HU | IE | IT | LT | LU | LV | MT | NL | PL | PT | RO | SE | SI | SK | nullOSS: Bestimmungsland bei Verkauf an EU-Privatkunden. Voraussetzungen: OSS im Profil eingeschaltet, kein Kleinunternehmer, Buchungsautomatik aus, Privatkunde. Ohne taxRate gilt der Normalsatz des Landes; jeder Steuersatz muss im Land gelten.
ossSupplyTypegoods | digital | nullPflicht bei ossCountry: goods (Fernverkauf, § 3c UStG) oder digital (§ 3a Abs. 5 UStG).
discountValuenumberRabatt auf den ganzen Beleg (Prozent oder Betrag je nach discountType).. mindestens 0
discountTypepercent | absolute
taxExemptionReasonnone | ust_4_nr_21 | ust_4_nr_22 | ust_4_nr_14 | ust_4_nr_16 | ust_4_nr_18 | ust_4_nr_20 | ust_4_nr_23 | ust_4_nr_26 | ust_4_nr_12 | ust_4_nr_11 | ust_4_nr_8 | ust_4_nr_1a | ust_4_nr_1b | ust_3a_abs_2 | ust_3a_abs_2_eu | custom | nullGrund der Umsatzsteuerbefreiung. none schaltet den Standard aus dem Profil für diese Rechnung ab.
taxExemptionTextstring | nullEigener Hinweistext; leer = Vorschlagstext des Grundes.. höchstens 500 Zeichen
skontoPercentnumber | nullmindestens 0. höchstens 100
skontoDaysinteger | nullmindestens 0. höchstens 365
items PflichtInvoiceItemInput[]mindestens 1 Eintrag. höchstens 200 Einträge

InvoiceItemInput

FeldTypHinweis
description Pflichtstringhöchstens 2000 Zeichen
quantity PflichtnumberBis 3 Nachkommastellen.. mindestens 0
unitPrice PflichtnumberEinzelpreis in Euro, netto oder brutto je nach priceMode der Rechnung. Bis 4 Nachkommastellen.. mindestens 0
unitstringhöchstens 20 Zeichen. Beispiel Std.
taxRatenumberSteuersatz der Position in Prozent. Fehlt er, gilt der Satz der Rechnung.. mindestens 0. höchstens 100
discountPercentnumbermindestens 0. höchstens 100
productIdstring (uuid)Verknüpfung mit einem Produkt aus /v1/products.
costTypelabor | material | nullKostenart nach § 35a EStG: labor (Arbeits-, Maschinen- und Fahrtkosten) oder material. Fehlt sie, gilt die Kostenart des Produkts.
marginSchemeused_goods | art | collectibles | nullDifferenzbesteuerung (§ 25a UStG): Art des Gegenstands. Das Modul muss im Profil eingeschaltet sein.
marginPurchasePricenumber | nullEinkaufspreis je Einheit für die Differenzbesteuerung.. mindestens 0
marginMethodindividual | total | flat | nullflat: Pauschalmarge 30 % für Kunstgegenstände; sonst gilt das Profil.

Quote

FeldTypHinweis
idstring (uuid)
numberstring | null
statusdraft | sent | accepted | rejected | archived
customerIdstring (uuid) | null
customerNamestring | null
issueDatestring (date) | null
validUntilstring (date) | null
currencystringBeispiel EUR
subtotalnumber | null
taxRatenumber | null
taxAmountnumber | null
totalnumber | null
convertedToInvoiceIdstring (uuid) | null
revisioninteger | null
signedAtstring (date-time) | null
poNumberstring | null
projectReferencestring | null
laborCostobject | nullArbeits-, Maschinen- und Fahrtkosten (§ 35a EStG) der labor-Positionen nach Rabatt, inklusive Umsatzsteuer. null ohne solche Positionen. Auf dem PDF steht der Betrag nur bei Privatkunden.
itemsQuoteItem[]
createdAtstring (date-time) | null
updatedAtstring (date-time) | null

QuoteItem

Wie InvoiceItem, zusätzlich:

FeldTypHinweis
isOptionalboolean
optionGroupstring | null
optionAcceptedboolean

Product

FeldTypHinweis
idstring (uuid)
codestring
typestring
namestring
descriptionstring | null
categorystring | null
unitstring | null
sellingPricenumber | null
taxRatenumber | null
eanstring | null
trackStockboolean
stockQuantitynumber | nullNur bei Produkten mit Lagerführung, sonst null.
costTypelabor | material | nullKostenart nach § 35a EStG; Positionen ohne eigene Kostenart übernehmen sie.
isActiveboolean

Address

FeldTypHinweis
streetstring | null
postalCodestring | null
citystring | null
countrystring | null

Me

FeldTypHinweis
keyIdstring (uuid)
namestring
prefixstringBeispiel kk_live_AbCd1234
scopescustomers:read | customers:write | invoices:read | invoices:write | quotes:read | quotes:write | products:read | products:write | projects:read | projects:write | tasks:read | tasks:write | credit-notes:read | credit-notes:write | contracts:read | delivery-notes:read | dunnings:read | time-entries:read | time-entries:write | calendar:read | calendar:write | payments:read | payments:write | expenses:read | expenses:write[]
scopeLabelsobject
expiresAtstring (date-time) | null

Error

FeldTypHinweis
statusCode PflichtintegerBeispiel 403
code PflichtstringStabiler, maschinenlesbarer Code. Darauf programmieren, nicht auf message.. Beispiel API_SCOPE_MISSING
message PflichtstringLesbare Meldung auf Deutsch. Kann sich ändern.
hintstring | null
detailsobject | null
timestampstring (date-time)
pathstring

QuoteItemInput

FeldTypHinweis
description Pflichtstringhöchstens 2000 Zeichen
quantity PflichtnumberBis 3 Nachkommastellen.. mindestens 0
unitPrice PflichtnumberEinzelpreis in Euro, netto oder brutto je nach priceMode. Bis 4 Nachkommastellen.. mindestens 0
unitstringhöchstens 20 Zeichen. Beispiel Std.
costTypelabor | material | nullKostenart nach § 35a EStG: labor (Arbeits-, Maschinen- und Fahrtkosten) oder material.

QuoteInput

FeldTypHinweis
customerId Pflichtstring (uuid)Der Kunde muss bereits existieren (siehe POST /v1/customers).
issueDatestring (date)Standard: heute (Europe/Berlin).
validUntilstring (date)Standard: issueDate plus 30 Tage. Darf nicht vor issueDate liegen.
taxRatenumberBei Kleinunternehmern (§ 19 UStG) immer 0.. mindestens 0. höchstens 100. Standard 19
priceModenet | grossgross: Einzelpreise sind Bruttopreise und werden in Nettopreise umgerechnet.. Standard net
projectReferencestringhöchstens 100 Zeichen
introTextstringhöchstens 5000 Zeichen
notesstringHinweis an den Kunden, wird auf dem PDF gedruckt.. höchstens 5000 Zeichen
items PflichtQuoteItemInput[]mindestens 1 Eintrag. höchstens 50 Einträge
discountValuenumberRabatt auf das ganze Angebot (Prozent oder Nettobetrag je nach discountType).. mindestens 0
discountTypepercent | absolute
markSentbooleantrue: sofort als versendet markieren, ohne E-Mail.. Standard false

TimeEntry

FeldTypHinweis
idstring (uuid)
datestring (date) | nullTag des Beginns in Europe/Berlin.
startTimestring (date-time) | null
titlestring | null
descriptionstring | null
durationMinutesintegerNetto-Dauer ohne Pause.
breakMinutesinteger
billablebooleanAls abrechenbar markiert.
projectIdstring (uuid) | null
customerIdstring (uuid) | null
taskIdstring (uuid) | null
invoicedbooleanBereits abgerechnet (einer Rechnung zugeordnet).
editablebooleanOffen: lässt sich über die API ändern und löschen (nicht freigegeben, abgerechnet oder gesperrt).

BusySlot

FeldTypHinweis
startstring (date-time)
endstring (date-time)Ausschließlich.
allDayboolean
statusconfirmed | tentative

CalendarEvent

FeldTypHinweis
idstring (uuid)
titlestring
descriptionstring | null
locationstring | null
typeappointment | visit | call | deadline | other
statusconfirmed | tentative | cancelled
startstring (date-time)
endstring (date-time)Ausschließlich. Bei ganztägigen Terminen 00:00 Uhr nach dem letzten Tag (Europe/Berlin).
allDaybooleanÜber die API angelegte Termine sind nie ganztägig.
linkTypecustomer | project | quote | invoice | task | null
linkIdstring | null

CalendarEventInput

FeldTypHinweis
title Pflichtstringhöchstens 200 Zeichen
descriptionstringhöchstens 4000 Zeichen
locationstringhöchstens 300 Zeichen
typeappointment | call | visit | otherStandard appointment
statusconfirmed | tentativeStandard tentative
start Pflichtstring (date-time)Beispiel 2026-10-20T09:00:00+02:00
end Pflichtstring (date-time)Nach start, höchstens 12 Stunden später.
linkTypecustomer | project
linkIdstring (uuid)Nur zusammen mit linkType.

CalendarEventUpdate

FeldTypHinweis
titlestringhöchstens 200 Zeichen
descriptionstring | nullhöchstens 4000 Zeichen
locationstring | nullhöchstens 300 Zeichen
statusconfirmed | tentative | cancelled
startstring (date-time)
endstring (date-time)

Project

FeldTypHinweis
idstring (uuid)
customerIdstring (uuid) | null
namestring
statusactive | completed | archived
startDatestring (date) | null
endDatestring (date) | null
budgetAmountnumber | nullBudget bzw. Auftragswert, netto in Euro.
budgetHoursnumber | nullStundenkontingent.
createdAtstring (date-time) | null
updatedAtstring (date-time) | null

WorkflowTrigger

FeldTypHinweis
entityId PflichtstringKennung des auslösenden Belegs, z. B. Rechnung, Angebot oder Projekt. Bei Mängeln Protokoll und Mangel, durch Doppelpunkt getrennt.
rule PflichtstringName der Regel.
event Pflichtquote_sent | quote_accepted | quote_expiring | invoice_sent | invoice_overdue | invoice_paid | expense_created | receipt_received | project_budget_reached | contract_ending | delivery_note_issued | defect_overdueEreignis der Regel.
number PflichtstringNummer oder Bezeichnung des Belegs.
customerId Pflichtstring (uuid) | null
amount Pflichtnumber | nullBetrag des Belegs (brutto), falls vorhanden.
message PflichtstringText aus der Regel, Platzhalter ersetzt.

DeletedProject

FeldTypHinweis
id Pflichtstring (uuid)
customerId Pflichtstring (uuid) | null
name Pflichtstring
status Pflichtactive | completed | archived

ProjectInput

FeldTypHinweis
customerId Pflichtstring (uuid)Der Kunde muss bereits existieren (siehe POST /v1/customers).
name Pflichtstringhöchstens 150 Zeichen
startDatestring (date) | null
endDatestring (date) | nullDarf nicht vor startDate liegen.
budgetAmountnumber | nullNetto in Euro, bis 2 Nachkommastellen.. mindestens 0
budgetHoursnumber | nullStundenkontingent, bis 2 Nachkommastellen.. mindestens 0

ProjectUpdate

FeldTypHinweis
namestringhöchstens 150 Zeichen
statusactive | completed | archived
startDatestring (date) | null
endDatestring (date) | nullDarf nicht vor startDate liegen.
budgetAmountnumber | nullNetto in Euro, bis 2 Nachkommastellen.. mindestens 0
budgetHoursnumber | nullStundenkontingent, bis 2 Nachkommastellen.. mindestens 0

Milestone

FeldTypHinweis
idstring (uuid)
projectIdstring (uuid)
titlestring
positioninteger | null
statusopen | done | invoiced
dueDatestring (date) | null
amountNetnumber | nullBetrag netto in Euro.
completedAtstring (date) | null
invoiceIdstring (uuid) | nullRechnung, mit der der Meilenstein abgerechnet wurde.
createdAtstring (date-time) | null
updatedAtstring (date-time) | null

ChecklistItem

FeldTypHinweis
idstring (uuid)
titlestring
doneboolean
positioninteger | null

ChecklistItemInput

FeldTypHinweis
title Pflichtstringhöchstens 200 Zeichen

ChecklistItemUpdate

FeldTypHinweis
titlestringhöchstens 200 Zeichen
doneboolean

Task

FeldTypHinweis
idstring (uuid)
titlestring
notesstring | null
statusopen | in_progress | waiting | donedone genau dann, wenn completedAt gesetzt ist.
prioritylow | normal | high | urgent
customerIdstring (uuid) | null
projectIdstring (uuid) | null
assigneeIdstring (uuid) | nullZuständige Person (Kontoinhaber oder Teammitglied).
startDatestring (date) | null
dueDatestring (date) | null
estimatedMinutesnumber | null
recurrencedaily | weekly | monthly | yearly | null
completedAtstring (date-time) | null
checklistChecklistItem[]
createdAtstring (date-time) | null
updatedAtstring (date-time) | null

CreditNote

FeldTypHinweis
idstring (uuid)
numberstring | nullGutschriftsnummer, z. B. GS-2026-0001.
statusdraft | issued | applied | refunded | cancelled
typecustomer_correction | settlement_reverse_charge | nullcustomer_correction (Rechnungskorrektur) oder settlement_reverse_charge (Gutschrift im Gutschriftverfahren).
customerIdstring (uuid) | null
customerNamestring | null
referenceInvoiceIdstring (uuid) | nullKorrigierte Rechnung, falls verknüpft.
referenceInvoiceNumberstring | null
issueDatestring (date) | null
currencystringBeispiel EUR
subtotalnumber | null
taxRatenumber | null
taxAmountnumber | null
totalnumber | null
reasonstring | nullGrund der Gutschrift, steht auch auf dem PDF.
isCancellationbooleantrue: Storno zu einer Gutschrift (eigener Nummernkreis, z. B. SG-2026-0001, Beträge negativ).
cancelledCreditNoteIdstring (uuid) | nullBeim Storno-Beleg: die stornierte Gutschrift.
cancelledByCreditNoteIdstring (uuid) | nullBei einer stornierten Gutschrift: ihr Storno-Beleg.
itemsInvoiceItem[]Gleiche Form wie Rechnungspositionen; taxRate, productId und costType sind bei Gutschriften immer null.
createdAtstring (date-time) | null
updatedAtstring (date-time) | null

ContractItem

FeldTypHinweis
positioninteger | null
descriptionstring
quantitynumber | null
unitstring | null
unitPricenumber | nullVereinbarter Einzelpreis in Euro, brutto.
taxRatenumber | nullnull = Standardsteuersatz des Kontos.
totalnumber | nullMenge mal Einzelpreis, brutto.
isOneTimebooleanEinmalige Position (z. B. Einrichtung), zählt nicht zum wiederkehrenden Betrag.

Contract

FeldTypHinweis
idstring (uuid)
numberstring | null
titlestring | null
subjectstring | null
statusdraft | sent | signed | active | expired | cancelledAlle Status werden geliefert, auch draft.
customerIdstring (uuid) | null
customerNamestring | null
amountnumber | nullVergütung pro Abrechnungsintervall in Euro, brutto (Summe der wiederkehrenden Positionen).
billingIntervalmonthly | quarterly | yearly | one_time | null
startDatestring (date) | null
endDatestring (date) | nullnull bei unbefristeten Verträgen.
nextInvoiceDatestring (date) | null
noticePeriodDaysnumber | nullKündigungsfrist in Tagen.
autoRenewboolean
renewalPeriodMonthsnumber | null
signedAtstring (date-time) | nullZeitpunkt der Unterschrift des Kunden, sonst null.
plannedMinutesPerPeriodinteger | nullInklusivzeit pro Abrechnungszeitraum in Minuten. null, wenn der Vertrag nicht zeitbasiert ist.
itemsContractItem[]
createdAtstring (date-time) | null
updatedAtstring (date-time) | null

ContractUsage

FeldTypHinweis
contractIdstring (uuid)
periodStartstring (date)Beginn des aktuellen Abrechnungszeitraums.
periodEndstring (date)Ende des Zeitraums (Datum der nächsten Rechnung bzw. Vertragsende).
plannedMinutesinteger | nullInklusivzeit im Zeitraum in Minuten, null ohne hinterlegte Inklusivzeit.
usedMinutesintegerIm Zeitraum für den Kunden gebuchte Minuten.
billableMinutesinteger | nullDerzeit immer null: Vertragszeiten werden nicht nach abrechenbar/nicht abrechenbar unterschieden. Reserviert für später.

Acceptance

FeldTypHinweis
idstring (uuid)
projectIdstring (uuid)
milestoneIdstring (uuid) | nullAbgenommener Meilenstein, falls verknüpft.
titlestring
protocolDatestring (date) | null
placestring | null
resultaccepted | accepted_with_defects | rejected
defectsobject[]
clientSignerNamestring | null
clientSignedAtstring (date-time) | null
contractorSignerNamestring | null
contractorSignedAtstring (date-time) | null
createdAtstring (date-time) | null
updatedAtstring (date-time) | null

ProjectSummary

FeldTypHinweis
projectIdstring (uuid)
budgetAmountnumber | nullBudget bzw. Auftragswert, netto in Euro.
budgetHoursnumber | nullStundenkontingent.
trackedMinutesintegerAlle erfassten Minuten des Projekts.
billableMinutesintegerDavon als abrechenbar markiert.
invoicedNetnumber | nullNetto-Summe der ausgestellten Rechnungen zum Projekt (ohne Entwürfe).
milestonesobject
tasksobject

DeliveryNote

FeldTypHinweis
idstring (uuid)
numberstring | null
statusdraft | issued | cancelledissued = ausgeliefert: Bestand gebucht, Inhalt gesperrt.
customerIdstring (uuid) | null
customerNamestring | null
deliveryDatestring (date) | null
poNumberstring | null
shippingCarrierstring | null
trackingNumberstring | null
invoiceIdstring (uuid) | null
invoiceNumberstring | null
itemsobject[]
createdAtstring (date-time) | null
updatedAtstring (date-time) | null

Dunning

FeldTypHinweis
idstring (uuid)
invoiceIdstring (uuid) | null
invoiceNumberstring | null
customerIdstring (uuid) | null
levelfirst | second | final | collections
statuspending | sent | paid | cancelled | escalated
originalAmountnumber | nullOffener Rechnungsbetrag bei Erstellung der Mahnung.
feeAmountnumber | nullMahngebühr.
flatFeenumber | nullVerzugspauschale nach § 288 Abs. 5 BGB (nur Unternehmer).
lateFeesnumber | nullVerzugszinsen.
totalDuenumber | null
dueDatestring (date) | null
sentAtstring (date-time) | null
createdAtstring (date-time) | null
updatedAtstring (date-time) | null

Payment

FeldTypHinweis
idstring (uuid)
directionin | out
paymentDatestring (date) | null
amountnumber | null
counterpartystring | null
referencestring | null
notestring | null
allocationsobject[]Aufteilung auf Rechnungen bzw. Gutschriften und Steuersätze.
createdAtstring (date-time) | null
updatedAtstring (date-time) | null

Expense

FeldTypHinweis
idstring (uuid)
typeexpense | income
titlestring | null
descriptionstring | null
amountnumber | nullBruttobetrag in Euro.
taxRatenumber | null
datestring (date) | nullZahldatum, maßgeblich für die EÜR.
invoiceDatestring (date) | null
categoryoffice | travel | equipment | software | marketing | training | utilities | rent | insurance | taxes | salary | meals | entertainment | other
paymentMethodcash | bank_transfer | credit_card | debit_card | paypal | other
vendorstring | null
receiptNumberstring | null
taxDeductibleboolean
reverseCharge13bboolean
reverseCharge13bTypeforeign | construction | domestic | null
kskRelevantbooleanHonorar an Künstler oder Publizisten (Künstlersozialabgabe).
isPrivateboolean
notesstring | null
receiptobject | nullHochgeladener Beleg (ohne Inhalt).
createdAtstring (date-time) | null
updatedAtstring (date-time) | null

CreditNoteItemInput

FeldTypHinweis
description Pflichtstringhöchstens 2000 Zeichen
quantity Pflichtnumbermindestens 0.001
unitPrice PflichtnumberEinzelpreis netto in Euro, positiv.. mindestens 0
unitstringhöchstens 20 Zeichen
discountPercentnumbermindestens 0. höchstens 100

CreditNoteInput

FeldTypHinweis
referenceInvoiceId Pflichtstring (uuid)Die korrigierte Rechnung (ausgestellt, nicht storniert).
issueDatestring (date)
reason PflichtstringGrund der Gutschrift, steht auf dem PDF.. höchstens 2000 Zeichen
introTextstringhöchstens 5000 Zeichen
taxRatenumberBei Kleinunternehmern immer 0.. mindestens 0. höchstens 100. Standard 19
items PflichtCreditNoteItemInput[]mindestens 1 Eintrag. höchstens 200 Einträge

ProductInput

FeldTypHinweis
code PflichtstringJe Konto eindeutig.. höchstens 100 Zeichen
name Pflichtstringhöchstens 255 Zeichen
typeservice | material | flatStandard service
categorystringhöchstens 100 Zeichen. Standard Allgemein
descriptionstringhöchstens 5000 Zeichen
unitstringStandard: Std. (Leistung), Stk. (Material), pauschal.. höchstens 20 Zeichen
sellingPrice PflichtnumberVerkaufspreis netto in Euro.. mindestens 0
purchasePricenumberEinkaufspreis netto (Material, Standard 0).. mindestens 0
taxRatenumbermindestens 0. höchstens 100. Standard 19
eanstring | nullhöchstens 32 Zeichen
costTypelabor | material | nullKostenart nach § 35a EStG: labor (Arbeits-, Maschinen- und Fahrtkosten) oder material. Fehlt sie, gilt die Kostenart des Produkts.
plannedMinutesPerUnitintegerLeistungen: geplante Minuten je Einheit, Standard 60.. mindestens 1
trackStockbooleanBestand führen.
stockQuantitynumberAnfangsbestand (nur beim Anlegen mit trackStock).. mindestens 0
minStocknumberMeldebestand.. mindestens 0

StockMovementInput

FeldTypHinweis
type Pflichtin | out | correction
quantity PflichtnumberMenge; bei correction der gezählte Bestand.. mindestens 0
datestring (date)Standard: heute.
notestringhöchstens 255 Zeichen

TimeEntryInput

FeldTypHinweis
title Pflichtstringhöchstens 255 Zeichen
descriptionstringhöchstens 5000 Zeichen
startTime Pflichtstring (date-time)Beginn mit Uhrzeit, nicht in der Zukunft.
durationMinutes PflichtintegerNetto-Dauer ohne Pause.. mindestens 1. höchstens 960
breakMinutesintegermindestens 0. höchstens 720
customerIdstring (uuid)Ohne Angabe gilt der Kunde des Projekts.
projectIdstring (uuid)
taskIdstring (uuid)
productIdstring (uuid)Leistung aus /v1/products.
billablebooleanStandard true

PaymentInput

FeldTypHinweis
amount PflichtnumberBetrag in Euro, höchstens der offene Betrag.. mindestens 0.01
datestring (date)Zahldatum, Standard heute.
methodbank_transfer | cash | card | paypal | other
notestringhöchstens 500 Zeichen

ExpenseInput

FeldTypHinweis
typeexpense | incomeStandard expense
title Pflichtstringhöchstens 255 Zeichen
descriptionstringhöchstens 5000 Zeichen
amount PflichtnumberBruttobetrag in Euro.. mindestens 0.01
taxRatenumbermindestens 0. höchstens 100
date Pflichtstring (date)Zahldatum.
invoiceDatestring (date) | null
category Pflichtoffice | travel | equipment | software | marketing | training | utilities | rent | insurance | taxes | salary | meals | entertainment | other
paymentMethod Pflichtcash | bank_transfer | credit_card | debit_card | paypal | other
vendorstringhöchstens 255 Zeichen
receiptNumberstringhöchstens 100 Zeichen
taxDeductibleboolean
reverseCharge13bbooleanSteuerschuldnerschaft nach § 13b UStG (nur mit Steuersatz).
reverseCharge13bTypeforeign | construction | domestic
kskRelevantboolean
isPrivatebooleanPrivat: ohne Steuer, nicht abziehbar.
notesstringhöchstens 5000 Zeichen

TaskInput

FeldTypHinweis
title Pflichtstringhöchstens 200 Zeichen
notesstringhöchstens 5000 Zeichen
customerIdstring (uuid)
projectIdstring (uuid)
assigneeIdstring (uuid)Kontoinhaber oder aktives Teammitglied.
prioritylow | normal | high | urgentStandard normal
statusopen | in_progress | waitingStandard open
startDatestring (date)
dueDatestring (date)
estimatedMinutesintegermindestens 0. höchstens 100000
recurrencedaily | weekly | monthly | yearlyBeim Erledigen entsteht die nächste Aufgabe der Serie.

TaskUpdate

FeldTypHinweis
titlestringhöchstens 200 Zeichen
notesstring | nullhöchstens 5000 Zeichen
customerIdstring (uuid) | null
projectIdstring (uuid) | null
assigneeIdstring (uuid) | null
prioritylow | normal | high | urgent
statusopen | in_progress | waiting | done
startDatestring (date) | null
dueDatestring (date) | null
estimatedMinutesinteger | nullmindestens 0. höchstens 100000
recurrencedaily | weekly | monthly | yearly | null

InvoiceUpdate

FeldTypHinweis
customerIdstring (uuid)Der Kunde muss bereits existieren (siehe POST /v1/customers).
issueDatestring (date)Beispiel 2026-10-06
dueDatestring (date)Beispiel 2026-10-20
serviceDatestring (date)
servicePeriodStartstring (date)
servicePeriodEndstring (date)
taxRatenumbermindestens 0. höchstens 100. Beispiel 19
priceModenet | grossStandard net
introTextstringhöchstens 5000 Zeichen
customerNotesstringHinweis an den Kunden, wird auf dem PDF gedruckt.. höchstens 5000 Zeichen
poNumberstringhöchstens 100 Zeichen
projectReferencestringhöchstens 100 Zeichen
projectIdstring (uuid)Optional: Projekt des Kunden, dem die Rechnung zugeordnet wird.
paymentReferencestringhöchstens 140 Zeichen
propertyServicebooleanLeistung am Grundstück. Fehlt der Wert, gilt der Standard aus dem Profil; bei OSS-Rechnungen immer false.
ossCountryAT | BE | BG | CY | CZ | DK | EE | GR | ES | FI | FR | HR | HU | IE | IT | LT | LU | LV | MT | NL | PL | PT | RO | SE | SI | SK | nullOSS: Bestimmungsland bei Verkauf an EU-Privatkunden. Voraussetzungen: OSS im Profil eingeschaltet, kein Kleinunternehmer, Buchungsautomatik aus, Privatkunde. Ohne taxRate gilt der Normalsatz des Landes; jeder Steuersatz muss im Land gelten.
ossSupplyTypegoods | digital | nullPflicht bei ossCountry: goods (Fernverkauf, § 3c UStG) oder digital (§ 3a Abs. 5 UStG).
discountValuenumberRabatt auf den ganzen Beleg (Prozent oder Betrag je nach discountType).. mindestens 0
discountTypepercent | absolute
taxExemptionReasonnone | ust_4_nr_21 | ust_4_nr_22 | ust_4_nr_14 | ust_4_nr_16 | ust_4_nr_18 | ust_4_nr_20 | ust_4_nr_23 | ust_4_nr_26 | ust_4_nr_12 | ust_4_nr_11 | ust_4_nr_8 | ust_4_nr_1a | ust_4_nr_1b | ust_3a_abs_2 | ust_3a_abs_2_eu | custom | nullGrund der Umsatzsteuerbefreiung. none schaltet den Standard aus dem Profil für diese Rechnung ab.
taxExemptionTextstring | nullEigener Hinweistext; leer = Vorschlagstext des Grundes.. höchstens 500 Zeichen
skontoPercentnumber | nullmindestens 0. höchstens 100
skontoDaysinteger | nullmindestens 0. höchstens 365
itemsInvoiceItemInput[]mindestens 1 Eintrag. höchstens 200 Einträge

QuoteUpdate

FeldTypHinweis
customerIdstring (uuid)Der Kunde muss bereits existieren (siehe POST /v1/customers).
issueDatestring (date)Standard: heute (Europe/Berlin).
validUntilstring (date)Standard: issueDate plus 30 Tage. Darf nicht vor issueDate liegen.
taxRatenumberBei Kleinunternehmern (§ 19 UStG) immer 0.. mindestens 0. höchstens 100. Standard 19
priceModenet | grossgross: Einzelpreise sind Bruttopreise und werden in Nettopreise umgerechnet.. Standard net
projectReferencestringhöchstens 100 Zeichen
introTextstringhöchstens 5000 Zeichen
notesstringHinweis an den Kunden, wird auf dem PDF gedruckt.. höchstens 5000 Zeichen
itemsQuoteItemInput[]mindestens 1 Eintrag. höchstens 50 Einträge
discountValuenumberRabatt auf das ganze Angebot (Prozent oder Nettobetrag je nach discountType).. mindestens 0
discountTypepercent | absolute

ProductUpdate

FeldTypHinweis
codestringJe Konto eindeutig.. höchstens 100 Zeichen
namestringhöchstens 255 Zeichen
typeservice | material | flatStandard service
categorystringhöchstens 100 Zeichen. Standard Allgemein
descriptionstringhöchstens 5000 Zeichen
unitstringStandard: Std. (Leistung), Stk. (Material), pauschal.. höchstens 20 Zeichen
sellingPricenumberVerkaufspreis netto in Euro.. mindestens 0
purchasePricenumberEinkaufspreis netto (Material, Standard 0).. mindestens 0
taxRatenumbermindestens 0. höchstens 100. Standard 19
eanstring | nullhöchstens 32 Zeichen
costTypelabor | material | nullKostenart nach § 35a EStG: labor (Arbeits-, Maschinen- und Fahrtkosten) oder material. Fehlt sie, gilt die Kostenart des Produkts.
plannedMinutesPerUnitintegerLeistungen: geplante Minuten je Einheit, Standard 60.. mindestens 1
trackStockbooleanBestand führen.
minStocknumberMeldebestand.. mindestens 0
isActiveboolean

TimeEntryUpdate

FeldTypHinweis
titlestringhöchstens 255 Zeichen
descriptionstringhöchstens 5000 Zeichen
startTimestring (date-time)Beginn mit Uhrzeit, nicht in der Zukunft.
durationMinutesintegerNetto-Dauer ohne Pause.. mindestens 1. höchstens 960
breakMinutesintegermindestens 0. höchstens 720
customerIdstring (uuid)Ohne Angabe gilt der Kunde des Projekts.
projectIdstring (uuid)
taskIdstring (uuid)
productIdstring (uuid)Leistung aus /v1/products.
billablebooleanStandard true

ExpenseUpdate

FeldTypHinweis
typeexpense | incomeStandard expense
titlestringhöchstens 255 Zeichen
descriptionstringhöchstens 5000 Zeichen
amountnumberBruttobetrag in Euro.. mindestens 0.01
taxRatenumbermindestens 0. höchstens 100
datestring (date)Zahldatum.
invoiceDatestring (date) | null
categoryoffice | travel | equipment | software | marketing | training | utilities | rent | insurance | taxes | salary | meals | entertainment | other
paymentMethodcash | bank_transfer | credit_card | debit_card | paypal | other
vendorstringhöchstens 255 Zeichen
receiptNumberstringhöchstens 100 Zeichen
taxDeductibleboolean
reverseCharge13bbooleanSteuerschuldnerschaft nach § 13b UStG (nur mit Steuersatz).
reverseCharge13bTypeforeign | construction | domestic
kskRelevantboolean
isPrivatebooleanPrivat: ohne Steuer, nicht abziehbar.
notesstringhöchstens 5000 Zeichen

Versionierung

Die Version steht im Pfad: /v1. Innerhalb einer Hauptversion ändern wir nur abwärtskompatibel.

Das ändern wir ohne Vorankündigung

  • Neue Endpunkte, neue optionale Parameter und neue Felder in Antworten.
  • Neue Werte in Aufzählungen wie dem Rechnungsstatus.
  • Neue Webhook-Ereignisse. Sie erhalten sie nur, wenn Sie sie für einen Endpunkt auswählen.
  • Wortlaut von message und hint, Fehlerbehebungen und Leistungsverbesserungen.

Das ändern wir nur in einer neuen Hauptversion

Entfernen oder Umbenennen von Feldern und Endpunkten, geänderte Bedeutung bestehender Felder, neue Pflichtfelder, strengere Prüfungen. Dafür gibt es /v2.

Auslaufen einer Version

Wenn eine neue Hauptversion erscheint, bleibt die alte mindestens 12 Monate nutzbar. Wir kündigen das Ende mindestens 12 Monate vorher per E-Mail an die Kontoinhaber an und weisen in dieser Dokumentation darauf hin. Im Ausnahmefall einer Sicherheitslücke können wir schneller handeln und informieren dann unverzüglich.

Nutzungsregeln

Dies ist eine Zusammenfassung in einfacher Sprache. Verbindlich sind die Allgemeinen Geschäftsbedingungen und der Auftragsverarbeitungsvertrag von KostenKlar.

  • Nur Ihre eigenen Daten. Ein Schlüssel gibt Zugriff auf Ihr Konto. Geben Sie ihn nicht an Dritte weiter, die ihn nicht für Sie einsetzen.
  • Verantwortung für Ihre Integration. Sie sind für die Software verantwortlich, die Sie oder von Ihnen beauftragte Dritte mit Ihrem Schlüssel betreiben, und dafür, dass sie Daten korrekt und rechtmäßig verarbeitet. Das gilt auch für personenbezogene Daten, die Sie über Webhooks an Ihre Systeme übertragen lassen.
  • Fair nutzen. Halten Sie die Limits ein. Umgehen Sie sie nicht durch mehrere Schlüssel. Greifen Sie nicht auf Daten anderer Konten zu und versuchen Sie nicht, die Sicherheitsmaßnahmen zu umgehen.
  • Sperrung bei Missbrauch. Bei Missbrauch, Gefährdung der Plattform oder offensichtlich kompromittierten Schlüsseln können wir Schlüssel oder Endpunkte sperren. Wir informieren Sie, sobald es uns möglich ist.
  • Sicherheitslücken melden Sie bitte vertraulich an info@zirke-digital.de, bevor Sie sie veröffentlichen.
  • Steuerliche Verantwortung. Belege entstehen erst, wenn Sie sie in KostenKlar prüfen und festschreiben. Rechnungen legt die API nur als Entwürfe an.

Änderungen

1.3.0

  • Entwürfe ändern: PATCH /v1/invoices/{id} und PATCH /v1/quotes/{id}, nur gesendete Felder, items ersetzt alle Positionen. Ausgestellte Belege bleiben gesperrt (403).
  • Mehr Felder an Rechnungen: Rabatt auf den Beleg (discountValue, discountType), Skonto, Befreiungsgrund (taxExemptionReason), Differenzbesteuerung nach § 25a je Position (marginScheme, marginPurchasePrice, marginMethod). Angebote haben ebenfalls einen Rabatt.
  • OSS-Verfahren: ossCountry und ossSupplyType für Verkäufe an Privatkunden im EU-Ausland. KostenKlar prüft Voraussetzungen und Steuersätze wie in der App.
  • Grundstück und § 35a: propertyService sowie costType (labor/material) je Position. Rechnungen und Angebote liefern den Arbeitskostenanteil als laborCost.
  • Gutschriften: Entwurf zu einer ausgestellten Rechnung anlegen (POST /v1/credit-notes, Berechtigung credit-notes:write). Die Gutschrift-Endpunkte verlangen jetzt wie die App das Modul Gutschriften.
  • Lieferscheine und Mahnungen lesen, mit PDF (delivery-notes:read, dunnings:read).
  • Produkte: anlegen, ändern und Lagerbewegungen buchen (products:write, Modul Produktkatalog). Produkte liefern ihre costType.
  • Zeitbuchungen: anlegen, ändern und offene Buchungen löschen (time-entries:write). Buchungen liefern zusätzlich startTime, breakMinutes und editable.
  • Zahlungen: Zahlungseingang zu einer Rechnung erfassen (POST /v1/invoices/{id}/payments, payments:write) und Zahlungen der EÜR lesen (payments:read).
  • Ausgaben: lesen, anlegen, ändern und Belege hochladen (expenses:read, expenses:write, Modul Ausgaben).
  • Neue Webhook-Ereignisse: quote.created, credit-note.created, payment.received, expense.created, time-entry.created.
  • Bestehende Schlüssel erhalten die neuen Berechtigungen nicht automatisch; ergänzen Sie sie beim Bearbeiten des Schlüssels. Alle neuen Felder sind optional; bestehende Integrationen funktionieren unverändert.

1.2.0

  • Neues Webhook-Ereignis workflow.triggered: Sie legen in KostenKlar eine Workflow-Regel mit der Aktion „Webhook auslösen“ an und bestimmen selbst, bei welchem Ereignis sie greift (zum Beispiel Angebot läuft ab, Projektbudget erreicht, Vertrag endet). data enthält Regel, Ereignis, Belegnummer, Kunde, Betrag und Ihren Text.

1.1.0

  • Angebote anlegen (POST /v1/quotes, optional direkt als versendet markiert, ohne E-Mail), annehmen und ablehnen. Neue Berechtigung quotes:write.
  • Zeitbuchungen lesen (GET /v1/time-entries), ohne Stundensätze und Kosten. Neue Berechtigung time-entries:read.
  • Kalender: belegte Zeiten ohne Inhalte (GET /v1/calendar/busy) sowie Termine anlegen, ändern, absagen und löschen. Neue Berechtigungen calendar:read und calendar:write.
  • Bestehende Schlüssel erhalten die neuen Berechtigungen nicht automatisch; ergänzen Sie sie beim Bearbeiten des Schlüssels.
  • Neues Webhook-Ereignis project.deleted, wenn ein Projekt gelöscht wird. data enthält die Eckdaten vor dem Löschen (id, customerId, name, status).
  • project.updated kommt jetzt auch, wenn sich die Meilensteine eines Projekts ändern (anlegen, ändern, erledigen, abrechnen, löschen), einmal je Änderung.
  • customer.updated kommt jetzt auch nach dem Zusammenführen zweier Kunden (für den verbleibenden Kunden) und wenn Sie einen Änderungswunsch aus dem Kundenportal übernehmen.

1.0.0, 2. November 2026

  • Erste Veröffentlichung der API.
  • Lesen: Kunden, Rechnungen (mit PDF), Angebote (mit PDF), Produkte, Gutschriften (mit PDF), Verträge (mit PDF, Inklusivzeit und Zeitverbrauch im aktuellen Abrechnungszeitraum), Projekte mit Meilensteinen, Fortschritt und Abnahmeprotokollen (mit PDF), Aufgaben.
  • Schreiben: Kunden anlegen und ändern, Rechnungsentwürfe anlegen, Projekte und Aufgaben anlegen und ändern, mit Idempotency-Key.
  • Webhooks: invoice.created, invoice.sent, invoice.paid, invoice.overdue, quote.accepted, customer.created, customer.updated, project.created, project.updated, task.created, task.completed.

Hilfe

Fragen, Fehler in dieser Dokumentation oder ein Wunsch für die nächste Version? Schreiben Sie an info@zirke-digital.de. Nennen Sie bei einem Problem bitte die Uhrzeit, den Pfad und den Wert von code aus der Fehlerantwort. Senden Sie niemals Ihren vollständigen Schlüssel oder Ihr Webhook-Secret. Der Präfix kk_live_ mit den folgenden acht Zeichen genügt uns zur Zuordnung.