- Startseite
- Handwerker-Blog
- REST API und JSON: der Einstieg, wenn du aus der Dateiwelt kommst
REST API und JSON: der Einstieg, wenn du aus der Dateiwelt kommst

Du programmierst, aber HTTP und JSON sind dir bisher aus dem Weg gegangen? Du betreust ein konventionelles ERP, eine gewachsene Access-Anwendung oder ein paar VBA-Makros, die seit Jahren ihren Dienst tun? Importe, Exporte und Berichte hast du oft genug gebaut, CSV ist für dich Routine? Und jetzt soll dieses System Daten mit einer Cloud-Anwendung austauschen?
Dann bist du hier richtig. Es geht um die Grundlagen einer REST API: wie ein Aufruf aufgebaut ist, wie JSON gelesen wird, wo der Schlüssel hingehört und wie der erste Request aus VBA, C# oder Java aussieht.
Der Umstieg ist überschaubar. Es gibt vier Bausteine, ein Datenformat und eine Handvoll Regeln, und die gelten für jede weitere Schnittstelle genauso, nicht nur für die einer Handwerkersoftware.
Das Wichtigste in Kürze
- Ein Request besteht aus Methode, URL, Header und Body. Mehr ist es nicht.
- Die URL benennt die Ressource, die Methode sagt, was mit ihr passieren soll:
GETlesen,POSTanlegen,PATCHändern,DELETElöschen. - JSON ist verschachtelt und typisiert. Genau darin unterscheidet es sich von einer Zeile CSV.
- Der Schlüssel gehört in den
Authorization-Header, nie in die URL und nie in den Quellcode. - Listen kommen seitenweise, bei Artesa mit maximal 20 Datensätzen pro Aufruf. Wer mehr braucht, blättert.
- Den ersten Aufruf machst du in Postman und nicht im eigenen Code, und einen abgelehnten Body prüfst du mit JSONLint.
REST API statt CSV-Schnittstelle: was die Cloud geändert hat
Im eigenen Haus war eine Schnittstelle eine Frage des Netzwerks. Zwei Programme standen im selben Gebäude, hinter derselben Firewall, und damit gab es zwei naheliegende Wege: eine CSV auf ein Netzlaufwerk legen und vom anderen Programm einlesen, oder gleich per ODBC auf die Datenbank des ERP zugreifen und die Tabellen in Access verknüpfen. Beides funktionierte, weil beide Seiten auf denselben Server sehen konnten.
Sobald ein System in der Cloud läuft, und jede moderne Handwerkersoftware läuft dort, fällt genau diese Voraussetzung weg. Es gibt kein gemeinsames Laufwerk, und die Datenbank steht in einem Rechenzentrum, auf das dein Programm keinen Netzzugang hat und auch nicht bekommen soll. Was bleibt, ist der einzige Weg, der über das Internet zuverlässig offen ist: HTTP. Und was dort an Daten durchgeht, ist JSON.
- CSV auf ein Netzlaufwerk, der andere liest sie ein
- ODBC direkt auf die ERP-Datenbank
- Verknüpfte Tabellen in Access
- Funktioniert, weil beide Seiten im selben Netz stehen
- Kein gemeinsames Laufwerk, keine erreichbare Datenbank
- Ein Request pro Vorgang, Antwort sofort
- Rechte hängen am Schlüssel, nicht am Windows-Benutzer
- Derselbe Weg für jedes weitere System
Und der Teil, der den Aufwand rechtfertigt: Das ist keine Artesa-Eigenheit. Buchhaltung, Shop, Auswertung, Verbindungsdienste wie n8n, alles spricht dieselben zwei Dinge. Wer HTTP und JSON einmal gebaut hat, baut die nächste Anbindung in einem Bruchteil der Zeit, egal gegen welchen Anbieter. Was der Wechsel in die Cloud sonst noch am Betrieb ändert, steht im Blog „Cloud oder On-Premise", und welche Arten von Schnittstellen es daneben gibt, in „Software-Schnittstellen".
Die URL benennt das Ding, die Methode sagt, was damit passieren soll. /v1/customers sind die Kunden, und ob du sie lesen, anlegen oder ändern willst, entscheidet nicht die URL, sondern das Verb davor. Genau diese Aufteilung ist alles, was das Wort REST an dieser Stelle bedeutet.
Aufbau eines HTTP-Requests: Methode, URL, Header und Body
Ein Aufruf gegen die Artesa-API sieht ausgeschrieben so aus:
GET /v1/customers?size=20 HTTP/1.1
Host: api.artesa.de
Authorization: Bearer dein-api-schluessel
Accept: application/json
Vier Bausteine, und jeder hat genau eine Aufgabe:
- Methode, hier
GET: was passieren soll. - URL
/v1/customers?size=20: womit. Der Pfad benennt die Ressource, alles hinter dem Fragezeichen sind Parameter, die die Anfrage einschränken. - Header: die Angaben zum Request selbst. Wer fragt, steht in
Authorization, welches Format zurückkommen soll inAccept, in welchem Format ein Body geschickt wird inContent-Type. - Body: die Daten. Bei
GETundDELETEbleibt er leer, beiPOSTundPATCHsteht dort das JSON.
Zurück kommen zwei Dinge: ein Statuscode und ein Body. Der Statuscode ist die Kurzfassung („hat geklappt", „darfst du nicht", „gibt es nicht"), der Body enthält die Daten oder die Fehlermeldung. Nachschlagen kannst du beides bei MDN, die HTTP-Methoden und die Statuscodes.
JSON verstehen: Aufbau, Datentypen und typische Fehler
JSON kennt genau zwei Behälter und vier einfache Werte. Objekte stehen in geschweiften Klammern und bestehen aus Schlüssel-Wert-Paaren, Listen stehen in eckigen Klammern. Als Wert erlaubt sind Text in doppelten Anführungszeichen, Zahl, true/false, null, und wieder ein Objekt oder eine Liste. Das ist der ganze Sprachumfang.
So antwortet die Kundenliste:
{
"page": 0,
"pages": 12,
"size": 20,
"items": [
{
"id": 4711,
"number": "K-1042",
"companyName": "Metallbau Sander GmbH",
"externalId": "ACC-90231",
"firstName": "Jens",
"surname": "Sander",
"telephones": [
{ "label": "main", "isDefault": true, "telephoneNumber": "+49 381 1234560" }
],
"emails": [
{ "label": "main", "isDefault": true, "emailAddress": "[email protected]" }
],
"billingAddress": {
"street": "Werftstraße 12",
"zip": "18057",
"city": "Rostock",
"country": "DE"
},
"createdAt": "2026-03-14T09:12:04.000Z",
"updatedAt": "2026-08-27T16:40:11.000Z"
}
]
}
Daran hängt der Unterschied zu CSV, und er ist größer als die Schreibweise. Eine CSV-Zeile ist flach: ein Kunde, eine Zeile, feste Spalten. Der Kunde oben hat eine Liste von Telefonnummern, eine Liste von E-Mail-Adressen und eine Adresse als eigenes Objekt. In CSV wären das Telefon1, Telefon2, Telefon3 und die Hoffnung, dass niemand eine vierte einträgt, oder eine zweite Datei mit Fremdschlüssel. In JSON ist es einfach eine Liste, und sie darf leer sein.
Wahrscheinlich kennst du XML besser als JSON, aus GAEB, DATANORM, UGL oder der XRechnung. Dann ist der Umstieg klein, denn die Aufgabe ist dieselbe: strukturierte Daten als Text. JSON lässt nur das weg, was beim Lesen und Schreiben Arbeit macht. Keine schließenden Tags, keine Entscheidung zwischen Attribut und Element, keine Namespaces, kein Schema, das du brauchst, um überhaupt an einen Wert zu kommen. Was in XML ein XPath-Ausdruck war, ist hier ein Zugriff auf einen Schlüssel im Wörterbuch.
Zwei Dinge, die daraus folgen: Ein JSON-Dokument ist bei gleichem Inhalt deutlich kürzer, und es gibt genau eine naheliegende Abbildung auf die Datenstrukturen deiner Sprache, nämlich Wörterbuch und Liste. Die Artesa-API antwortet ausschließlich in JSON, ein Accept: application/xml gibt es nicht.
Was aus der CSV-Welt kommend am häufigsten schiefgeht:
- Zahl gegen Text
"id": 4711ist eine Zahl,"number": "K-1042"ein Text. Anführungszeichen um eine ID herum sind ein anderer Wert, nicht dieselbe ID in anderer Schreibweise. - null gegen leerer Text
nullheißt „nicht gesetzt",""heißt „gesetzt, und zwar leer". Bei Feldern wieapprovedAtentscheidet das darüber, ob ein Stundenzettel genehmigt ist oder nicht. - Datum immer ISO 8601
2026-08-27T16:40:11.000Zstatt27.08.2026. DasZam Ende heißt UTC, im Sommer also zwei Stunden vor der Uhr im Büro. - Kein Komma hinter dem letzten Eintrag
Ein Komma nach dem letzten Feld ist in JSON ungültig, auch wenn jeder Editor es durchlässt. Häufigster Fehler beim Zusammenbauen eines Bodys per String-Verkettung.
- Keine Kommentare
JSON hat kein
//und kein#. Was aussieht wie ein Kommentar in einer Doku, ist eine Auslassung und kein gültiges JSON. - Groß- und Kleinschreibung zählt
companyNameist nichtCompanyNameund nichtcompanyname. Anders als bei Spaltenüberschriften in Excel gibt es hier keine Nachsicht.
Diese Fehler entstehen fast alle an derselben Stelle: wenn JSON von Hand zusammengesetzt wird. Eine Verkettung wie "{" & Chr(34) & "companyName" & Chr(34) & ": " & … läuft genau so lange, bis ein Kunde ein Anführungszeichen im Firmennamen hat, ein Feld leer bleibt oder in einer Adresse ein Zeilenumbruch steckt. Wir haben Bodys bekommen, in denen eine Liste in geschweiften statt in eckigen Klammern stand, und solche mit echten Zeilenumbrüchen mitten im Text, wo ein \n hingehört.
JSON nicht als Text zusammenbauen
Zum Schreiben ein Objekt füllen und serialisieren lassen, zum Lesen einmal parsen und auf Felder zugreifen. Nie mit Instr und Mid im Antworttext nach Werten suchen und nie einen Body per String-Verkettung erzeugen.
Jede Sprache hat dafür etwas Fertiges: VBA-JSON in VBA, System.Text.Json in .NET, Jackson in Java. Die Bibliothek kennt die Sonderfälle, die beim Verketten durchrutschen: Anführungszeichen im Wert, Umlaute, Zeilenumbrüche, das Komma hinter dem letzten Feld.
Und wenn ein Body trotzdem abgelehnt wird, ist der erste Griff ein Validator statt der Fehlermeldung: JSONLint zeigt die Zeile, in der die Klammer fehlt.
Für den Umstieg im Kopf hilft derselbe Dienst mit einem CSV-to-JSON-Konverter: Wirf eine deiner Exportdateien hinein und schau, wie dieselben Daten in JSON aussehen.
Die Spezifikation dahinter ist kurz genug, um sie einmal zu lesen: json.org auf einer Seite, formal in RFC 8259. Für den Alltag reicht die JSON-Seite bei MDN.
Die HTTP-Methoden: GET, POST, PATCH und DELETE
Vier Verben decken alles ab, was eine Anbindung tut:
| Was du willst | Request | Antwort |
|---|---|---|
| Kundenliste lesen | GET /v1/customers | 200 und eine Seite mit items |
| Einen Kunden lesen | GET /v1/customers/4711 | 200 und das Objekt |
| Kunden anlegen | POST /v1/customers mit Body | 201 und das neue Objekt samt id |
| Kunden ändern | PATCH /v1/customers/4711 mit Body | 200 und das geänderte Objekt |
| Kunden löschen | DELETE /v1/customers/4711 | 204, kein Body |
Wichtig an PATCH: Du schickst nur die Felder, die sich ändern sollen. Ein Body mit einem einzigen Feld ist der Normalfall, alles andere am Datensatz bleibt, wie es war. Der Reflex aus der Datenbankwelt, erst den ganzen Satz zu lesen, ein Feld zu ändern und alles zurückzuschreiben, ist hier nicht nur unnötig, er überschreibt dir womöglich, was in der Zwischenzeit jemand im Büro eingetippt hat.
Authentifizierung: API-Schlüssel als Bearer Token im Header
Der Header ist die Stelle für alles, was über den Request etwas sagt, statt Teil der Daten zu sein. Die Authentifizierung sitzt genau deshalb dort und nicht als Parameter in der URL: URLs landen überall. Im Browserverlauf, im Proxy-Log, im Webserver-Log, in der Fehlermeldung, die dir jemand als Screenshot ins Ticket hängt.
Bei Artesa sieht die Zeile so aus:
Authorization: Bearer dein-api-schluessel
Bearer heißt Inhaber, und das ist wörtlich gemeint: Es gibt keinen Benutzernamen, kein Passwort und keine Anmeldung davor. Wer den Schlüssel hat, darf alles, was der Schlüssel darf. Er gehört dem Unternehmen, nicht einem einzelnen Benutzer, und er läuft nicht von sich aus ab. Behandle ihn wie ein Passwort, nicht wie eine Kundennummer. Den Schlüssel selbst bekommst du bei uns auf Anfrage.
Fehlt der Header oder ist der Schlüssel falsch, antwortet die API mit 401 und rührt nichts an.
Wo der Schlüssel nicht hingehört
- Nicht in die URL. Kein
?apikey=..., aus den Gründen oben. - Nicht in den Quellcode. Eine Konstante im VBA-Modul wandert mit der Datei durch den halben Betrieb.
- Nicht ins Repository. Auch nicht „nur kurz zum Testen", denn die Historie behält ihn.
- Nicht in die Excel-Datei, die per Mail rumgeht.
Stattdessen: eine Konfigurationsdatei neben der Anwendung, eine Umgebungsvariable oder die Registry, in jedem Fall außerhalb dessen, was verteilt oder eingecheckt wird. Und immer über https, nie über http.
Daten abrufen: Filter und Pagination
Listen kommen seitenweise, und die Seite ist klein. page zählt ab 0, size liegt standardmäßig bei 10 und maximal bei 20. Wer 800 Kunden abholen will, blättert also durch 40 Seiten. Wie viele es sind, sagt die Antwort selbst:
{ "page": 0, "pages": 12, "size": 20, "items": [] }
Du liest also pages aus der ersten Antwort und zählst page hoch, bis du durch bist. Zum Filtern hängst du Bedingungen an die URL:
| Operator | Bedeutung | Beispiel |
|---|---|---|
| ohne | genau dieser Wert | ?assignmentId=123 |
[$gt] / [$gte] | größer / größer oder gleich | ?updatedAt[$gt]=2026-09-01T00:00:00Z |
[$lt] / [$lte] | kleiner / kleiner oder gleich | ?updatedAt[$lt]=2026-09-02T00:00:00Z |
[$ne] | ungleich | ?submittedAt[$ne]=null |
[$in] / [$nin] | in / nicht in einer Liste, bis 100 Werte | ?assignmentId[$in]=123,456,789 |
Mehrere Bedingungen im selben Aufruf werden mit UND verknüpft, und null schreibst du als Wort, wo es wirklich „leer" heißen soll. Damit wird das Filtern zum eigentlichen Werkzeug, und hier liegt der größte Unterschied zum Datei-Export: Du holst nicht alles ab und sortierst hinterher in Access, sondern stellst die Frage direkt an die API. Welche Felder ein Endpunkt filtern kann, listet seine Doku unter Query Parameters, und jedes davon nimmt die Operatoren aus der Tabelle:
GET /v1/customers?externalId=ACC-90231
GET /v1/customers?updatedAt[$gt]=2026-09-01T22:00:00Z&size=20
GET /v1/time-sheet-days?submittedAt[$ne]=null&approvedAt=null
GET /v1/assignment-events?assignmentId[$in]=4711,4712,4713
GET /v1/time-entries?eventId[$in]=null,123
Der Reihe nach: einen Kunden über die eigene Nummer aus dem Altsystem finden, alles Geänderte seit dem letzten Lauf, die Stundenzettel-Tage, die abgegeben aber noch nicht genehmigt sind, die Arbeitsschritte zu drei Aufträgen auf einmal, und Zeiteinträge mit oder ohne Arbeitsschritt. Nichts davon braucht Nachbearbeitung auf deiner Seite, und jede Bedingung, die in der URL steht, spart Seiten, die du sonst durchblättern müsstest.
Daraus ergibt sich der wichtigste Handgriff der ganzen Anbindung. Du holst nicht jede Nacht alles, sondern nur, was sich seit dem letzten Lauf geändert hat:
GET /v1/time-entries?updatedAt[$gt]=2026-09-01T22:00:00Z&size=20&page=0 HTTP/1.1
Host: api.artesa.de
Authorization: Bearer dein-api-schluessel
Zwei Dinge, die dabei zu beachten sind: Der Zeitstempel ist UTC, die 22:00:00Z sind im Sommer also Mitternacht deutscher Zeit. Und setze die Grenze ein paar Minuten vor dem letzten Lauf statt exakt darauf, sonst fällt dir irgendwann ein Datensatz durch, der genau in der Sekunde geschrieben wurde, in der du gefragt hast.
Was du auf diesem Weg abholen kannst, geht über Kunden und Aufträge hinaus: Zeiteinträge, Abwesenheiten und die Stundenzettel als fertig gerechnete Summen pro Tag, Woche und Monat, jeweils mit dem Genehmigungsstatus. Für eine Nachkalkulation oder die Übergabe an die Lohnabrechnung ist das die Quelle, die du willst, statt einzelne Buchungen selbst zu summieren. Erfasst werden sie vorher in der Projektzeiterfassung, auf dem Handy am Auftrag.
Daten schreiben: anlegen, ändern und Doubletten vermeiden
Ein POST schickt das Objekt als JSON im Body, ohne id, denn die vergibt die API:
{
"companyName": "Metallbau Sander GmbH",
"externalId": "ACC-90231",
"salutation": "Herr",
"firstName": "Jens",
"surname": "Sander",
"emails": [
{ "label": "main", "isDefault": true, "emailAddress": "[email protected]" }
],
"billingAddress": {
"street": "Werftstraße 12",
"zip": "18057",
"city": "Rostock",
"country": "DE"
}
}
Zurück kommt 201 und derselbe Datensatz mit id. Anders als GET, PATCH und DELETE lässt sich ein POST aber nicht gefahrlos wiederholen: Läuft dein Job zweimal, oder bricht er nach dem Anlegen und vor dem Merken der id ab, steht der Kunde zweimal drin. Eine Schnittstelle, die das nicht abfängt, produziert Doubletten, und zwar genau dann, wenn niemand hinschaut.
Das Feld dafür ist externalId. Dort schreibst du den Schlüssel aus deinem System hinein, die Kundennummer aus dem ERP oder den Primärschlüssel aus der Access-Tabelle. Damit lässt sich jeder Datensatz wiederfinden, ohne dass du dir die Artesa-id merken musst:
GET /v1/customers?externalId=ACC-90231
Kommt eine leere items-Liste zurück, legst du mit POST an. Kommt ein Datensatz, änderst du ihn mit PATCH auf dessen id. Dieselben zwei Schritte für jeden Lauf, egal ob es der erste ist.
Praxistipp
Schreib die externalId von der ersten Zeile an mit, auch wenn du sie heute nicht brauchst. Nachträglich zuzuordnen heißt, 800 Kunden über Firmenname und Postleitzahl zu vergleichen, und das geht nie ganz auf. Adressen tragen ebenfalls eine externalId, sodass auch die Zuordnung von Lieferadressen erhalten bleibt.
Auf derselben Logik laufen Aufträge und Arbeitsschritte. Im Betrieb sammelt die Auftragsakte danach alles, was am Auftrag hängt: Termine, Status, Zeiten und Dokumente. Das ist auch der Grund, warum die Anbindung meist in dieser Richtung gebaut wird: Ein Metallbaubetrieb kalkuliert weiter in seinem Programm und schiebt den fertigen Auftrag samt Kunde nach Artesa, wo daraus Planung, Zeiten und Doku entstehen.
Fehler behandeln: die Statuscodes 400, 401 und 404
Fehler kommen mit dem Statuscode und einem Body in immer derselben Form:
{
"name": "BadRequest",
"message": "Invalid query: \"createdAt\": Expected a valid ISO 8601 date-time string",
"code": 400
}
Die message sagt in der Regel genau, welches Feld nicht passt. Bei Validierungsfehlern stehen die betroffenen Felder zusätzlich einzeln unter data.validationErrors.
| Code | Heißt | Was zu tun ist |
|---|---|---|
400 | Anfrage ist falsch gebaut | Nicht wiederholen, die message lesen. Wiederholen ändert nichts. |
401 | Schlüssel fehlt oder ist falsch | Header prüfen, Bearer mit Leerzeichen davor. |
404 | Gibt es nicht, oder nicht für dich | Falsche id, oder der Datensatz gehört zu einem anderen Unternehmen. |
5xx | Problem auf unserer Seite | Wiederholen, aber mit steigendem Abstand statt in der Schleife. |
Der eine Fehler, der jede Anbindung irgendwann kostet: Statuscode nicht prüfen und einfach den Body parsen. Dann steht in deiner Zieltabelle eine Fehlermeldung, wo ein Kunde stehen sollte, und niemand merkt es. Prüf den Code vor dem Parsen, und schreib code und message ins Log.
OpenAPI 3.1: Spezifikation abrufen und Client generieren
Die Schnittstelle ist nicht nur dokumentiert, sie ist beschrieben. Jede Ressource liefert ihre eigene Spezifikation nach OpenAPI 3.1 als JSON, und dafür brauchst du nicht einmal einen Schlüssel:
GET /v1/customers/spec
GET /v1/time-entries/spec
Drin steht, welche Felder es gibt, welchen Typ sie haben, welche Pflicht sind und welche Werte erlaubt sind, dazu getrennte Schemas fürs Anlegen, fürs Ändern und fürs Lesen. Das ist der Unterschied zwischen „beim Anlegen darf das mitkommen" und „das darfst du nachträglich ändern", und du musst ihn nicht raten.
Zwei Dinge machst du damit. Erstens: in Postman oder Insomnia importieren, dann stehen alle Aufrufe fertig da, statt dass du sie abtippst. Zweitens: einen typisierten Client generieren, in .NET mit NSwag oder Kiota, in Java mit dem OpenAPI Generator. Das ersetzt die Records, die du sonst von Hand pflegst, und wenn ein Feld dazukommt, generierst du neu, statt einen Changelog zu vergleichen.
Und der Punkt, der aus der Datei-Welt kommend am meisten wert ist: Die API validiert jede Anfrage gegen genau dieses Schema. Ein Text, wo eine Zahl hingehört, ein fehlendes Pflichtfeld, ein Datum, das nicht ISO 8601 ist, ein Wert außerhalb der erlaubten Liste, all das wird mit 400 abgelehnt, und die betroffenen Felder stehen einzeln unter data.validationErrors. Ein CSV-Import nimmt in derselben Lage, was er bekommt, und die Sache fällt in der Lohnabrechnung auf. Hier fällt sie in der Sekunde des Aufrufs auf, in deinem Log, mit Feldnamen.
Die zwei Richtungen einer Schnittstelle zur Handwerkersoftware
Bei aller Freiheit, die eine offene Schnittstelle lässt, sehen die Anbindungen in der Praxis überraschend gleich aus. Fast jede besteht aus denselben zwei Richtungen:
Kunden, Aufträge und Arbeitsschritte entstehen im Bestandssystem, weil dort kalkuliert und fakturiert wird. Nach Artesa wandern sie, damit daraus Planung, Zeiten und Dokumentation werden.
Technisch sind das POST und PATCH auf /v1/customers, /v1/assignments und /v1/assignment-events.
Die erfassten Stunden gehen ins Bestandssystem zurück, für Nachkalkulation und Lohnabrechnung.
Das ist reines Lesen über /v1/time-entries und die Stundenzettel nach Tag, Woche und Monat, jeweils mit dem Genehmigungsstatus.
Baust du die Anbindung für mehrere Betriebe?
Dann sag uns das, denn dann lohnt sich Arbeit auf unserer Seite. Für die Hin-Richtung bauen wir einen eigenen Endpunkt, der Stamm- und Auftragsdaten in einem Aufruf annimmt, statt dass du drei Ressourcen einzeln bedienst und die Reihenfolge der Abhängigkeiten selbst sortierst.
Für die Rück-Richtung sprechen wir das Format ab und legen die Datei dort ab, wo dein System sie ohnehin abholt, etwa in OneDrive oder SharePoint. Dann musst du auf deiner Seite gar nichts gegen HTTP bauen.
Erzähl uns in einer Viertelstunde am Telefon, welches System auf der anderen Seite steht und welche der zwei Richtungen du brauchst. Dann sagen wir dir, was v1 heute schon abdeckt und ob sich für deinen Fall ein eigener Endpunkt lohnt.
API-Client: die Schnittstelle testen, bevor du Code schreibst
Der erste Aufruf gehört nicht in dein Programm, sondern in einen API-Client. Das ist ein Werkzeug, in dem du einen Request von Hand zusammensetzt, also Methode, URL, Header und Body einträgst, ihn abschickst und die Antwort im Rohzustand siehst. Postman ist der bekannteste, Bruno und Insomnia machen dasselbe, und für VS Code gibt es Erweiterungen, die reichen.
Der Grund, warum das am Anfang wichtiger ist als später: Es trennt zwei Fehlerquellen, die sonst gemeinsam auftreten. Läuft der Aufruf im Client und in deinem Programm nicht, liegt es an deinem Code. Läuft er im Client auch nicht, liegt es an URL, Schlüssel, Rechten oder Body, und du suchst an der richtigen Stelle. Ohne diesen Zwischenschritt debuggst du beides gleichzeitig, und genau das ist der Grund, warum eine erste Anbindung gern zwei Tage kostet statt zwei Stunden.
Drei Dinge, die sich dabei lohnen: Den Schlüssel als Umgebungsvariable anlegen und nicht in jeden Request einzeln, dann kannst du zwischen Test und Produktion umschalten, ohne 20 Aufrufe zu ändern. Die Requests als Sammlung speichern, weil du sie beim nächsten Kunden wieder brauchst. Und wenn ein Aufruf steht, den Code-Schnipsel exportieren, den die meisten Clients für C#, Java und andere Sprachen erzeugen. Der ist ein besserer Startpunkt als ein Beispiel aus einem Forum, weil er genau die Header trägt, mit denen es gerade funktioniert hat.
Und bevor der erste Schreibzugriff läuft: Unter api-staging.artesa.dev liegt unsere Spielwiese. Dort darf schiefgehen, was schiefgehen will, und niemand im Büro muss danach 200 doppelte Kunden von Hand aufräumen. Der Datenbestand dort ist nicht aktuell, und es können Funktionen darin stecken, die noch nicht veröffentlicht sind.
REST API aus VBA aufrufen: Excel und Access
VBA kann HTTP von Hause aus, über WinHttp.WinHttpRequest.5.1. Was fehlt, ist JSON: dafür holst du dir VBA-JSON, ein einzelnes Modul unter MIT-Lizenz, das du als JsonConverter.bas importierst.
Public Function ArtesaGet(ByVal Pfad As String) As Object
Dim Http As Object
Set Http = CreateObject("WinHttp.WinHttpRequest.5.1")
Http.Open "GET", "https://api.artesa.de" & Pfad, False
Http.setRequestHeader "Authorization", "Bearer " & ApiSchluessel()
Http.setRequestHeader "Accept", "application/json"
Http.send
If Http.Status <> 200 Then
Err.Raise vbObjectError + 1000, , _
"Artesa API " & Http.Status & ": " & Http.responseText
End If
Set ArtesaGet = JsonConverter.ParseJson(Http.responseText)
End Function
ApiSchluessel() ist dabei eine eigene Funktion, die den Schlüssel aus einer Konfigurationsdatei oder der Registry liest, nicht eine Konstante zwei Zeilen weiter oben. Benutzt wird das Ganze so:
Dim Antwort As Object, Kunde As Variant
Set Antwort = ArtesaGet("/v1/customers?size=20&page=0")
For Each Kunde In Antwort("items")
Debug.Print Kunde("id"), Kunde("companyName")
Next Kunde
Debug.Print "Seite"; Antwort("page"); "von"; Antwort("pages")
Drei Stellen, an denen es in VBA typischerweise klemmt:
- Fehlende Felder werfen keinen Fehler
ParseJsonliefert Objekte alsDictionary. Ein Zugriff auf einen Schlüssel, den die Antwort nicht enthält, gibt dirEmptystatt eines Fehlers. Bei optionalen Feldern wieexternalIderstKunde.Exists("externalId")fragen. - Listen sind Collections, keine Arrays
Antwort("items")ist eineCollection.For Eachfunktioniert, ein Index ab0nicht: gezählt wird ab1. - Body per String zusammenbauen
Für Schreibzugriffe nicht die Anführungszeichen selbst zusammensetzen. Ein
Dictionaryfüllen und mitJsonConverter.ConvertToJsonausgeben, dann stimmen Kommas und Sonderzeichen von allein.
Umlaute kommen übrigens richtig an: Die API schickt charset=utf-8 mit, und responseText wertet das aus.
REST API aus C# aufrufen: HttpClient und System.Text.Json
In .NET ist beides eingebaut, der HTTP-Client und der JSON-Serialisierer:
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
// Eine Instanz für die Lebensdauer der Anwendung, nicht eine pro Aufruf.
static readonly HttpClient Http = new()
{
BaseAddress = new Uri("https://api.artesa.de/"),
};
Http.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", ApiSchluessel());
// JsonSerializerDefaults.Web bringt camelCase mit, sonst findet der
// Serialisierer companyName nicht.
var optionen = new JsonSerializerOptions(JsonSerializerDefaults.Web);
record Kunde(int Id, string? CompanyName, string? ExternalId, DateTimeOffset UpdatedAt);
record Seite<T>(int Page, int Pages, int Size, List<T> Items);
var seite = await Http.GetFromJsonAsync<Seite<Kunde>>(
"v1/customers?size=20&page=0", optionen);
foreach (var kunde in seite!.Items)
Console.WriteLine($"{kunde.Id} {kunde.CompanyName}");
Ein PATCH ist dann eine Zeile, und der anonyme Typ im Body ist genau das, was PATCH will: nur das eine Feld.
var antwort = await Http.PatchAsJsonAsync(
$"v1/customers/{kunde.Id}",
new { companyName = "Metallbau Sander GmbH & Co. KG" },
optionen);
antwort.EnsureSuccessStatusCode();
Der Fallstrick in .NET ist ein anderer als in VBA: new HttpClient() pro Aufruf sieht harmlos aus, lässt aber Verbindungen offen und läuft unter Last in die Portknappheit. Eine Instanz für die ganze Anwendung, oder IHttpClientFactory, wenn du sowieso mit Dependency Injection arbeitest.
REST API aus Java aufrufen: HttpClient und Jackson
Seit Java 11 steckt der HTTP-Client in der Standardbibliothek, fürs JSON kommt eine Bibliothek dazu, meist Jackson.
record Kunde(int id, String companyName, String externalId) {}
record Seite<T>(int page, int pages, int size, List<T> items) {}
var client = HttpClient.newHttpClient();
var mapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
var request = HttpRequest.newBuilder()
.uri(URI.create("https://api.artesa.de/v1/customers?size=20&page=0"))
.header("Authorization", "Bearer " + apiSchluessel())
.header("Accept", "application/json")
.GET()
.build();
var antwort = client.send(request, HttpResponse.BodyHandlers.ofString());
if (antwort.statusCode() != 200) {
throw new IOException("Artesa API " + antwort.statusCode() + ": " + antwort.body());
}
var seite = mapper.readValue(antwort.body(), new TypeReference<Seite<Kunde>>() {});
for (var kunde : seite.items()) {
System.out.println(kunde.id() + " " + kunde.companyName());
}
Die eine Zeile, die hier über die Haltbarkeit deiner Anbindung entscheidet, ist FAIL_ON_UNKNOWN_PROPERTIES. Jackson wirft standardmäßig eine Exception, sobald die Antwort ein Feld enthält, das dein Record nicht kennt. Kommt in v1 ein Feld hinzu, und das ist eine abwärtskompatible Änderung, für die es kein v2 gibt, steht deine Anbindung. System.Text.Json in .NET ignoriert unbekannte Felder von sich aus, Jackson muss man es sagen.
Checkliste vor dem Produktivbetrieb
- Gegen Staging getestet
Schreibzugriffe erst gegen
api-staging.artesa.dev, nicht gegen die Daten, mit denen morgen geplant wird. - Delta statt Vollsync
updatedAt[$gt]mit gemerktem Zeitstempel, ein paar Minuten Überlappung. Alles andere skaliert nicht und produziert nur Last. - Schreibzugriffe wiederholbar
externalIdgesetzt, Suchen-dann-Anlegen-oder-Ändern statt blindesPOST. - Statuscode vor dem Parsen geprüft
Und
codeplusmessageim Log, mit Zeitstempel und Endpunkt. - Wiederholung mit steigendem Abstand
Bei
5xxwarten und erneut versuchen, bei400nicht. Eine Schleife ohne Wartezeit hilft niemandem. - Alles in UTC gerechnet
Umrechnung erst bei der Anzeige. Sommerzeit ist der Grund, warum eine Anbindung im März plötzlich eine Stunde daneben liegt.
- Version im Pfad, Changelog im Blick
v1bleibtv1. Kommt eine Änderung, die bricht, heißt siev2und du bekommst Bescheid.
Damit ist der Umstieg beschrieben. Es bleibt bei vier Bausteinen pro Aufruf, einem Datenformat mit zwei Klammerarten und einer Handvoll Regeln, die dir bei der nächsten Schnittstelle genauso begegnen.
Auf Anfrage bei uns. Nötig ist ein aktiver Artesa-Zugang des Betriebs, dann stellen wir den Schlüssel bereit, dazu die technische Dokumentation unter api.artesa.de/docs. Womit die REST API sonst noch verbunden wird, zeigen die bestehenden Anbindungen.
Ja, nach OpenAPI 3.1 und je Ressource abrufbar unter /v1/<ressource>/spec, zum Beispiel https://api.artesa.de/v1/customers/spec. Dafür ist kein Schlüssel nötig. Damit lässt sich die Sammlung in Postman importieren oder ein typisierter Client generieren, etwa mit NSwag für .NET oder dem OpenAPI Generator für Java.
JSON leistet dasselbe wie XML, mit weniger Aufwand beim Lesen und Schreiben: keine schließenden Tags, keine Unterscheidung zwischen Attribut und Element, keine Namespaces und kein Schema, das man zum Auslesen eines Wertes braucht. Dazu ist es bei gleichem Inhalt kürzer und lässt sich direkt auf Wörterbuch und Liste abbilden. Die Artesa-API antwortet ausschließlich in JSON.
CSV ist flach, JSON ist verschachtelt und typisiert. Eine CSV-Zeile hat feste Spalten und kennt nur Text, in JSON kann ein Kunde mehrere Telefonnummern als Liste und die Adresse als eigenes Objekt tragen, und eine Zahl ist als Zahl von einem Text unterscheidbar. Deshalb braucht JSON kein Trennzeichen, keine Spaltenreihenfolge und keine Absprache über die Kodierung.
Ja. VBA bringt mit WinHttp.WinHttpRequest.5.1 alles mit, was für den Request nötig ist, für das Parsen der Antwort kommt VBA-JSON dazu. Rein lesend geht es auch ohne eine Zeile Code über Power Query. Wo Excel als Datenquelle sinnvoll bleibt und wo nicht, zeigen wir im Blog „Warum Excel im Handwerk heute nicht mehr funktioniert"; über die Excel-Anbindung wandern Listen auch ohne eigenen Code hin und zurück.
Eigener Code lohnt sich, sobald die Daten aus einem System kommen, an das nur du herankommst, oder die Zuordnung Logik braucht: Abgleich über externalId, Sonderfälle pro Kunde, Umrechnungen. Sollen dagegen zwei Cloud-Systeme schlicht Daten austauschen, ist n8n schneller fertig und lässt sich leichter übergeben. Für die Buchhaltung gibt es fertige Wege wie Lexware Office, da lohnt kein Eigenbau.
Über die offene Schnittstelle von Artesa: Kunden, Aufträge, Arbeitsschritte und Projekte, dazu Benutzer, Zeiteinträge, Abwesenheiten und die Stundenzettel als Summen pro Tag, Woche und Monat samt Genehmigungsstatus. Lesen und schreiben geht in beide Richtungen, jeweils über dieselben vier Methoden. Wo diese Daten im Betrieb entstehen, siehst du an der Zeiterfassung, und welche Systeme heute schon angebunden sind, an den Schnittstellen.
Die Typen, auf die du beim Schreiben verweist: Auftragstypen, Arbeitsschritttypen, Auftragsstatus, Benutzerrollen und Zeiterfassungsarten. Die werden im Betrieb in den Einstellungen angelegt, du liest ihre id über die Settings-Endpunkte und schreibst sie nicht fest in deinen Code. Wie der Betrieb damit arbeitet, steuert er in der Auftragsverwaltung und der Mitarbeiterplanung.
Ja, je nach Bundesland und Vorhaben gibt es Zuschüsse, die auch Software und Einführung abdecken. Welche Programme aktuell laufen und was sie jeweils verlangen, steht in der Übersicht „Förderprogramme für die Digitalisierung im Handwerk".
Sag uns, welche Richtung du brauchst.
Stamm- und Auftragsdaten hin, Arbeitszeiten zurück: das sind die zwei Richtungen fast jeder Anbindung. Erzähl uns in einer Viertelstunde am Telefon, welches System auf der anderen Seite steht, dann sagen wir dir, was v1 heute abdeckt und ob sich für deinen Fall ein eigener Endpunkt lohnt.
Weitere spannende Blog-Artikel:
Schnittstellen und ITCloud oder eigener Server: Wo die Daten deines Betriebs liegen sollten
Schnittstellen und ITSoftware-Schnittstellen: Was sie sind und wozu du sie brauchst
BehelfslösungenTrello, Asana und MS Teams im Handwerk: gut für Aufgaben, blind für Kapazität
Behelfslösungen