Dokumentation der Schnittstelle
Über die Schnittstelle liest die IT eines Ausbildungsbetriebs Azubis, bestätigte Wochen, Abwesenheiten und Nachweise und legt Azubis an. Diese Seite beschreibt Anmeldung, Wege, Fehlercodes und Grenzen.
Stand: 17.10.2026
In Entwicklung: Die Schnittstelle wird freigeschaltet, sobald sie fertig geprüft ist. Bis dahin lassen sich keine Zugangsschlüssel anlegen, und ein vorhandener Schlüssel erhält 403 noch_nicht_freigeschaltet.
Basis-URL und Anmeldung
Alle Wege beginnen mit https://heftwerk.app/api/v1/extern. Anfragen und Antworten sind JSON in UTF-8, nur der Nachweis kommt als PDF.
Jede Anfrage trägt den Zugangsschlüssel in der Kopfzeile Authorization:
Authorization: Bearer hw_live_…Zugangsschlüssel legt in heftwerk an, wer den Betrieb verwaltet: die Inhaber und die Personen mit Verwaltungsrecht, unter Schnittstellen. Der Schlüssel erscheint genau einmal, direkt nach dem Anlegen; gespeichert wird danach nur seine Prüfsumme. Ein Schlüssel gehört zu genau einem Betrieb und sieht nur dessen Daten.
Umfänge
Beim Anlegen wird festgelegt, was der Schlüssel darf. Fehlt ihm der Umfang eines Wegs, antwortet der Weg mit 403 umfang_fehlt.
| Umfang | Erlaubt |
|---|---|
azubis:lesen | Azubis des Betriebs lesen. |
azubis:schreiben | Azubis anlegen und ihre Stammdaten ändern. |
wochen:lesen | Bestätigte Wochen und die Abwesenheiten daraus lesen. |
nachweise:lesen | Den Nachweis einer bestätigten Woche als PDF abrufen. |
Gültigkeit und Widerruf der Schlüssel
Wie lange ein Schlüssel gilt, wird beim Anlegen gewählt: 1 Stunde, 1 Woche, 1 Monat, 1 Jahr, 10 Jahre oder unbegrenzt. Vorausgewählt ist 1 Jahr. Nach Ablauf antwortet jeder Weg mit 401 schluessel_abgelaufen; dann ist ein neuer Schlüssel nötig.
Je Betrieb gelten höchstens 20 Schlüssel zugleich. Gezählt werden die, die weder widerrufen noch abgelaufen sind.
Die Verwaltung kann einen Schlüssel jederzeit widerrufen; er gilt dann ab der nächsten Anfrage nicht mehr. Wird die Person, die ihn angelegt hat, aus dem Team entfernt oder verliert sie das Verwaltungsrecht, werden ihre Schlüssel widerrufen.
Jeder Zugriff mit einem Schlüssel wird protokolliert: Zeitpunkt, Methode, Weg ohne Abfrageparameter, Status, Dauer und Absenderadresse. Die Verwaltung sieht die Zugriffe der letzten 90 Tage beim jeweiligen Schlüssel; danach werden sie gelöscht.
Wege
GET /api/v1/extern/azubis
Azubis auflisten. Die Azubis des Betriebs ohne archivierte, sortiert nach Kennung, in Seiten.
Umfang: azubis:lesen
| Parameter | Ort | Pflicht | Art und Bedeutung |
|---|---|---|---|
limit | Abfrage | nein | ganze Zahl, 1 bis 200, ohne Angabe 50. Einträge je Seite. |
cursor | Abfrage | nein | Text, 0 bis 200 Zeichen. naechsterCursor der vorigen Seite. |
Beispielantwort mit Status 200:
{
"daten": [
{
"id": "cmgw79z2k0003lukas",
"vorname": "Lukas",
"nachname": "Brenner",
"email": "[email protected]",
"beruf": "Kaufmann für Büromanagement",
"ausbildungsbeginn": "2025-08-01",
"ausbildungsende": "2028-07-31",
"standort": "Werk Nord",
"externeId": "P-10427"
}
],
"naechsterCursor": null
}Fehlercodes: unbekannter_parameter (400), ungueltige_eingabe (400), unauthorized (401), schluessel_abgelaufen (401), noch_nicht_freigeschaltet (403), betrieb_angehalten (403), umfang_fehlt (403), rate_limit (429), interner_fehler (500)
POST /api/v1/extern/azubis
Azubi anlegen. Legt einen Azubi an, mit denselben Regeln wie in heftwerk: angenommener Auftragsverarbeitungsvertrag, freie E-Mail-Adresse, bei Minderjährigen eine Adresse der Erziehungsberechtigten. Der Azubi bekommt eine Einladung per E-Mail und meldet sich ohne Passwort an. Überschreitet er die gebuchte Preisstufe, wird nicht höhergestuft: Die Antwort ist 409 stufe_voll. Hat der Betrieb das Anlegen auf die Verwaltung und den Bereich „Azubis anlegen“ beschränkt, gilt das auch für den Schlüssel, gemessen an der Person, die ihn angelegt hat. Mit externeId lässt sich der Aufruf gefahrlos wiederholen: Ist die Kennung schon vergeben, kommt 409 externe_id_vergeben mit der Kennung des Azubis.
Umfang: azubis:schreiben
| Feld im Körper | Pflicht | Art und Bedeutung |
|---|---|---|
vorname | ja | Text, 1 bis 80 Zeichen |
nachname | ja | Text, 1 bis 80 Zeichen |
email | ja | E-Mail-Adresse. Die Anmeldung des Azubis. Später nur in heftwerk änderbar. |
geburtsdatum | nein | Tag JJJJ-MM-TT. Kalendertag als JJJJ-MM-TT. |
beruf | nein | Text, 1 bis 120 Zeichen |
ausbildungsjahr | nein | ganze Zahl, 1 bis 5 |
ausbildungsbeginn | nein | Tag JJJJ-MM-TT. Kalendertag als JJJJ-MM-TT. |
nachweisExternBis | nein | Tag JJJJ-MM-TT. Ein Tag aus der letzten Woche, die noch anderswo geführt wurde, etwa im Berichtsheft der IHK. |
angerechneteMonate | nein | ganze Zahl, 0 bis 36 |
benachrichtigungsadressen | nein | Liste, höchstens 10 Einträge |
externeId | nein | Text, 1 bis 100 Zeichen. Kennung im System des Betriebs, je Betrieb eindeutig. |
Beispiel für den Körper:
{
"vorname": "Lukas",
"nachname": "Brenner",
"email": "[email protected]",
"beruf": "Kaufmann für Büromanagement",
"ausbildungsbeginn": "2025-08-01",
"externeId": "P-10427"
}Beispielantwort mit Status 201:
{
"daten": {
"id": "cmgw79z2k0003lukas",
"vorname": "Lukas",
"nachname": "Brenner",
"email": "[email protected]",
"beruf": "Kaufmann für Büromanagement",
"ausbildungsbeginn": "2025-08-01",
"ausbildungsende": "2028-07-31",
"standort": "Werk Nord",
"externeId": "P-10427"
},
"einladungVersendet": true
}Fehlercodes: unbekannter_parameter (400), ungueltige_eingabe (400), ungueltiges_json (400), erziehungsberechtigter_pflicht (400), erziehungsberechtigter_volljaehrig (400), unauthorized (401), schluessel_abgelaufen (401), noch_nicht_freigeschaltet (403), betrieb_angehalten (403), umfang_fehlt (403), vertrag_ruht (403), anlegen_nur_mit_bereich (403), externe_id_vergeben (409), email_bereits_verwendet (409), stufe_voll (409), staffel_auf_anfrage (409), avv_nicht_angenommen (409), zu_gross (413), falscher_inhaltstyp (415), rate_limit (429), interner_fehler (500)
PATCH /api/v1/extern/azubis/{id}
Stammdaten eines Azubis ändern. Ändert nur, was mitkommt; null nimmt eine Angabe zurück. Dieselben Regeln wie in heftwerk. Die E-Mail-Adresse lässt sich hier nicht ändern, weil sie die Anmeldung des Azubis umstellt. Namen ändert der Betrieb in heftwerk.
Umfang: azubis:schreiben
| Parameter | Ort | Pflicht | Art und Bedeutung |
|---|---|---|---|
id | Pfad | ja | Text. Kennung des Azubis aus GET /azubis. |
| Feld im Körper | Pflicht | Art und Bedeutung |
|---|---|---|
beruf | nein | Text, 1 bis 120 Zeichen oder null |
ausbildungsbeginn | nein | Tag JJJJ-MM-TT oder null. Kalendertag als JJJJ-MM-TT, null nimmt die Angabe zurück. |
ausbildungsende | nein | Tag JJJJ-MM-TT oder null. Kalendertag als JJJJ-MM-TT, null nimmt die Angabe zurück. |
nachweisExternBis | nein | Tag JJJJ-MM-TT oder null. Kalendertag als JJJJ-MM-TT, null nimmt die Angabe zurück. |
probezeitMonate | nein | ganze Zahl, 1 bis 4 oder null |
geburtsdatum | nein | Tag JJJJ-MM-TT oder null. Kalendertag als JJJJ-MM-TT, null nimmt die Angabe zurück. |
externeId | nein | Text, 1 bis 100 Zeichen oder null. Kennung im System des Betriebs, je Betrieb eindeutig. |
Beispiel für den Körper:
{
"ausbildungsende": "2028-01-31",
"externeId": "P-10427"
}Beispielantwort mit Status 200:
{
"daten": {
"id": "cmgw79z2k0003lukas",
"vorname": "Lukas",
"nachname": "Brenner",
"email": "[email protected]",
"beruf": "Kaufmann für Büromanagement",
"ausbildungsbeginn": "2025-08-01",
"ausbildungsende": "2028-01-31",
"standort": "Werk Nord",
"externeId": "P-10427"
}
}Fehlercodes: unbekannter_parameter (400), ungueltige_eingabe (400), ungueltiges_json (400), email_nicht_aenderbar (400), ende_vor_beginn (400), extern_vor_beginn (400), extern_nach_ende (400), ungueltiges_geburtsdatum (400), erziehungsberechtigter_pflicht (400), unauthorized (401), schluessel_abgelaufen (401), noch_nicht_freigeschaltet (403), betrieb_angehalten (403), umfang_fehlt (403), vertrag_ruht (403), nicht_gefunden (404), externe_id_vergeben (409), azubi_archiviert (409), zu_gross (413), falscher_inhaltstyp (415), rate_limit (429), interner_fehler (500)
GET /api/v1/extern/wochen
Bestätigte Wochen auflisten. Nur bestätigte Wochen, also Nachweise, von Azubis ohne Archiv, sortiert nach Kennung, in Seiten. Der Parameter status bleibt aus Gründen der Verträglichkeit erlaubt; jeder andere Wert als der Status bestätigter Wochen ergibt eine leere Liste.
Umfang: wochen:lesen
| Parameter | Ort | Pflicht | Art und Bedeutung |
|---|---|---|---|
limit | Abfrage | nein | ganze Zahl, 1 bis 200, ohne Angabe 50. Einträge je Seite. |
cursor | Abfrage | nein | Text, 0 bis 200 Zeichen. naechsterCursor der vorigen Seite. |
azubiId | Abfrage | nein | Text, 0 bis 60 Zeichen. Nur die Wochen dieses Azubis. |
jahr | Abfrage | nein | ganze Zahl, 2000 bis 2100. Nur Wochen dieses Jahres. |
status | Abfrage | nein | einer von: entwurf, eingereicht, vorgeprueft, bestaetigt, zurueckgegeben. Nur der Status bestätigter Wochen liefert Einträge. |
Beispielantwort mit Status 200:
{
"daten": [
{
"id": "cmgw7a1x80001lukas39",
"azubiId": "cmgw79z2k0003lukas",
"jahr": 2026,
"kalenderwoche": 39,
"von": "2026-09-21",
"bis": "2026-09-27",
"art": "betrieb",
"status": "bestaetigt",
"abteilung": "Auftragsbearbeitung",
"anzahlEintraege": 5,
"eingereichtAm": "2026-09-25T15:00:00.000Z",
"genehmigtVon": "[email protected]",
"genehmigtAm": "2026-09-28T08:00:00.000Z"
}
],
"naechsterCursor": null
}Fehlercodes: unbekannter_parameter (400), ungueltige_eingabe (400), unauthorized (401), schluessel_abgelaufen (401), noch_nicht_freigeschaltet (403), betrieb_angehalten (403), umfang_fehlt (403), rate_limit (429), interner_fehler (500)
GET /api/v1/extern/abwesenheiten
Abwesenheiten eines Jahres. Urlaub, Krankheit, Freistellung und Feiertage je Tag, nur aus bestätigten Wochen. Ohne Blättern.
Umfang: wochen:lesen
| Parameter | Ort | Pflicht | Art und Bedeutung |
|---|---|---|---|
jahr | Abfrage | ja | ganze Zahl, 2000 bis 2100. Das Jahr. |
azubiId | Abfrage | nein | Text, 0 bis 64 Zeichen. Nur dieser Azubi. |
Beispielantwort mit Status 200:
{
"daten": [
{
"azubiId": "cmgw79z2k0003lukas",
"datum": "2026-10-05",
"art": "urlaub",
"stunden": 0,
"wocheId": "cmgw7a1x80001lukas41"
}
]
}Fehlercodes: unbekannter_parameter (400), ungueltige_eingabe (400), unauthorized (401), schluessel_abgelaufen (401), noch_nicht_freigeschaltet (403), betrieb_angehalten (403), umfang_fehlt (403), rate_limit (429), interner_fehler (500)
GET /api/v1/extern/nachweise/{wocheId}.pdf
Nachweis einer Woche als PDF. Der Nachweis einer bestätigten Woche. Was die Liste der Wochen nicht zeigt, gibt es auch hier nicht.
Umfang: nachweise:lesen
| Parameter | Ort | Pflicht | Art und Bedeutung |
|---|---|---|---|
wocheId | Pfad | ja | Text. Kennung der Woche aus GET /wochen. |
Antwort mit Status 200: Das PDF, als Anhang nachweis-JJJJ-KWnn.pdf.
Fehlercodes: unbekannter_parameter (400), unauthorized (401), schluessel_abgelaufen (401), noch_nicht_freigeschaltet (403), betrieb_angehalten (403), umfang_fehlt (403), nicht_gefunden (404), rate_limit (429), interner_fehler (500)
Fehler
Jede Fehlerantwort hat diese Form:
{ "error": "umfang_fehlt", "meldung": "Diesem Schlüssel fehlt der Umfang für diesen Weg." }error ist ein fester Code, an dem ein Programm entscheiden kann. meldung erklärt ihn in Worten und kann sich ändern. Manche Antworten tragen weitere Felder, etwa azubiId bei externe_id_vergeben.
| Status | Code | Bedeutung |
|---|---|---|
| 400 | unbekannter_parameter | Abfrage oder Körper enthält ein Feld, das der Weg nicht kennt. Die Meldung nennt es. |
| 400 | ungueltige_eingabe | Ein Pflichtfeld fehlt, oder ein Feld hat die falsche Form. Die Meldung nennt die Felder. |
| 400 | ungueltiges_json | Der Körper ist kein gültiges JSON. |
| 400 | email_nicht_aenderbar | Die E-Mail-Adresse lässt sich über die Schnittstelle nicht ändern. |
| 400 | erziehungsberechtigter_pflicht | Für Minderjährige fehlt eine Benachrichtigungsadresse vom Typ erziehungsberechtigter. |
| 400 | erziehungsberechtigter_volljaehrig | Der Azubi ist nach dem Geburtsdatum volljährig; eine Adresse vom Typ erziehungsberechtigter lässt sich nicht eintragen. |
| 400 | ende_vor_beginn | Das Ende der Ausbildung liegt vor ihrem Beginn. |
| 400 | extern_vor_beginn | nachweisExternBis liegt vor dem Beginn der Ausbildung. |
| 400 | extern_nach_ende | nachweisExternBis liegt nach dem Ende der Ausbildung. |
| 400 | ungueltiges_geburtsdatum | Das Geburtsdatum liegt in der Zukunft oder vor 1950. |
| 400 | ungueltige_anfrage | Die Anfrage lässt sich nicht verarbeiten. |
| 401 | unauthorized | Der Schlüssel fehlt, ist unbekannt, widerrufen oder falsch geschrieben. Die Antwort unterscheidet das bewusst nicht. |
| 401 | schluessel_abgelaufen | Die Gültigkeit des Schlüssels ist abgelaufen. Ein neuer Schlüssel ist nötig. |
| 403 | noch_nicht_freigeschaltet | Die Schnittstelle ist noch nicht freigeschaltet. |
| 403 | umfang_fehlt | Dem Schlüssel fehlt der Umfang, den der Weg verlangt. |
| 403 | betrieb_angehalten | Der Zugang des Betriebs ist eingeschränkt. Bei einer Schreibsperre bleibt Lesen möglich. |
| 403 | vertrag_ruht | Für den Betrieb läuft kein Vertrag. Lesen bleibt möglich, Anlegen und Ändern nicht. |
| 403 | anlegen_nur_mit_bereich | Der Betrieb lässt Azubis nur durch die Verwaltung und den Bereich „Azubis anlegen“ anlegen, und wer den Schlüssel angelegt hat, hat dieses Recht nicht. |
| 404 | nicht_gefunden | Die Kennung gibt es nicht oder nicht in diesem Betrieb. Beides antwortet gleich. |
| 409 | externe_id_vergeben | Die externeId trägt schon ein Azubi des Betriebs. azubiId nennt ihn. |
| 409 | email_bereits_verwendet | Die E-Mail-Adresse ist in heftwerk schon vergeben. |
| 409 | stufe_voll | Der Azubi überschreitet die gebuchte Preisstufe. Die Höherstufung lässt sich nur in heftwerk bestätigen. |
| 409 | staffel_auf_anfrage | Ab 81 Azubis gilt ein individuelles Angebot. |
| 409 | avv_nicht_angenommen | Der Betrieb hat den Auftragsverarbeitungsvertrag noch nicht angenommen. |
| 409 | azubi_archiviert | Der Azubi ist archiviert und lässt sich nicht ändern. |
| 413 | zu_gross | Die Anfrage ist zu groß. |
| 415 | falscher_inhaltstyp | Der Körper kommt nicht als application/json. |
| 429 | rate_limit | Zu viele Aufrufe. Retry-After nennt die Wartezeit in Sekunden. |
| 500 | interner_fehler | Ein Fehler auf unserer Seite. Die Antwort enthält keine Einzelheiten. |
Grenzen, Blättern und Zeitformat
Je Schlüssel gelten höchstens 120 Aufrufe in der Minute, je Absenderadresse höchstens 600. Darüber antwortet die Schnittstelle mit 429 rate_limit und der Kopfzeile Retry-After, die die Wartezeit in Sekunden nennt.
Listen kommen in Seiten. limit legt die Größe fest, 1 bis 200, ohne Angabe 50. Jede Seite nennt naechsterCursor; diesen Wert als cursor mitschicken, um die nächste Seite zu holen. Ist naechsterCursor gleich null, ist die Liste zu Ende. Sortiert wird nach Kennung, so verschiebt ein neu angelegter Datensatz keine Seite.
Zeitpunkte stehen in ISO 8601 und UTC, etwa 2026-09-28T08:00:00.000Z. Kalendertage stehen als JJJJ-MM-TT, etwa 2026-09-21.
Abfrage und Körper sind streng: Ein Parameter oder Feld, das der Weg nicht kennt, ergibt 400 unbekannter_parameter. So fällt ein Tippfehler auf, statt still übergangen zu werden. Jede Antwort trägt Cache-Control: no-store.
Versionen und Abkündigung
Die Version steht im Pfad, heute v1. Innerhalb von v1 kommen Felder und Wege hinzu; es fällt nichts weg, und kein Feld ändert seine Bedeutung. Programme sollten unbekannte Felder in Antworten überlesen.
Soll ein Weg oder Feld wegfallen, kündigen wir das mindestens sechs Monate vorher an, auf dieser Seite und in den Antworten mit den Kopfzeilen Deprecation und Sunset. Sunset nennt den Tag, ab dem der Weg nicht mehr antwortet. Heute ist nichts abgekündigt.
Sicherheitshinweise
- Den Schlüssel wie ein Passwort behandeln: nicht in Quelltext, Tickets oder Protokolle schreiben, sondern in einem Speicher für Geheimnisse ablegen.
- Jedem System einen eigenen Schlüssel geben, mit nur den Umfängen, die es braucht, und kurzer Gültigkeit.
- Nur über HTTPS aufrufen. Der Schlüssel beginnt mit
hw_live_; daran lässt er sich in Scannern für Geheimnisse erkennen. - Bei Verdacht auf Missbrauch den Schlüssel widerrufen und einen neuen anlegen. Das Zugriffsprotokoll beim Schlüssel zeigt, wann und von welcher Adresse er genutzt wurde.
- Ein Schlüssel gilt nur für die Wege unter
/api/v1/extern. Mit ihm meldet sich niemand in heftwerk an, und er kann keine Schlüssel anlegen.
Beispiele mit curl
Die Beispiele erwarten den Schlüssel in der Umgebungsvariablen HEFTWERK_SCHLUESSEL.
Azubis auflisten, 50 je Seite:
curl -H "Authorization: Bearer $HEFTWERK_SCHLUESSEL" \
"https://heftwerk.app/api/v1/extern/azubis?limit=50"Die nächste Seite, mit naechsterCursor der vorigen:
curl -H "Authorization: Bearer $HEFTWERK_SCHLUESSEL" \
"https://heftwerk.app/api/v1/extern/azubis?limit=50&cursor=cmgw79z2k0003lukas"Bestätigte Wochen eines Jahres:
curl -H "Authorization: Bearer $HEFTWERK_SCHLUESSEL" \
"https://heftwerk.app/api/v1/extern/wochen?jahr=2026"Einen Azubi anlegen, mit externer Kennung:
curl -X POST -H "Authorization: Bearer $HEFTWERK_SCHLUESSEL" \
-H "Content-Type: application/json" \
-d '{"vorname":"Lukas","nachname":"Brenner","email":"[email protected]","externeId":"P-10427"}' \
"https://heftwerk.app/api/v1/extern/azubis"Das Ende der Ausbildung ändern:
curl -X PATCH -H "Authorization: Bearer $HEFTWERK_SCHLUESSEL" \
-H "Content-Type: application/json" \
-d '{"ausbildungsende":"2028-01-31"}' \
"https://heftwerk.app/api/v1/extern/azubis/cmgw79z2k0003lukas"Den Nachweis einer Woche als PDF speichern:
curl -H "Authorization: Bearer $HEFTWERK_SCHLUESSEL" -o nachweis.pdf \
"https://heftwerk.app/api/v1/extern/nachweise/cmgw7a1x80001lukas39.pdf"Maschinenlesbare Beschreibung
Dieselben Wege als OpenAPI 3.1, ohne Anmeldung abrufbar: /api/v1/openapi.json.