Zum Hauptinhalt springen
Hilfecenter

Leitfaden für Coding-Agenten (Vibe Coding)

Was ein KI-Coding-Agent wissen muss, bevor er eine Anwendung gegen die teamspace-API baut: Grundregeln für jede Anwendung (Ändern per PUT, Datum und Uhrzeit, Fehlerantworten) und Hinweise je Aufgabe – Zeiten erfassen, offene Punkte bearbeiten, Projektauswertungen anzeigen, React-Anwendungen ausliefern und testen.

Diese Seite ist die Quelle für Coding-Agenten, die im Auftrag eines Menschen eine Anwendung gegen die teamspace-API bauen. Oben stehen die Regeln, die für jede Anwendung gelten. Darunter folgen Hinweise je Aufgabe: Zeiten erfassen, offene Punkte bearbeiten, Projektauswertungen anzeigen. Die Angaben sind am 2026-09-10 an einem Testmandanten mit Version 2026.3 geprüft oder stammen aus Anwendungen, die gegen einen echten Server gelaufen sind. Die Regeln zum Ändern per PUT, zu Datum und Uhrzeit, zu Fehlerantworten und zu offenen Punkten sind vom 2026-09-10 bis 2026-09-14 beim Bau einer Web-App für offene Punkte gemessen, an einer Installation mit Version 2026.3-preview-10 bis -13. Was noch offen ist, steht dabei.

Bevor du anfängst

Quellen, in dieser Reihenfolge:

  1. Diese Seite. Sie sagt, wie die API sich tatsächlich verhält, auch dort, wo die Beschreibung schweigt.
  2. <server>/api/openapi.json (ab Version 26.2, ohne Anmeldung): Adressen, Filter und Felder. Die Datei ist über 1 MB groß. Werte gezielt die Pfade aus, die du brauchst, statt sie ganz zu lesen. Filter, die nicht im Schema des Objekts stehen, sind eigene Felder eines Mandanten. Verwende sie nicht. Beim Ändern per PUT, bei Pflichtfeldern und beim Datumsformat weicht die Beschreibung vom Verhalten des Servers ab. Dort folgst du dieser Seite. Unter info.version steht die Version des Servers. Manche Angaben hier gelten erst ab einer bestimmten Version.
  3. <server>/api/api/dto.ts: dieselben Objekte kompakt als TypeScript-Klassen, teils mit Kommentaren.
  4. <server>/api/<collection>/meta und …/meta/template (mit Anmeldung): erlaubte Methoden, Filter mit Typ und die Vorlage für neue Objekte samt gültigen Werten.
  5. Die Anleitungen im Thema API, etwa Zeiten über die API anzeigen und buchen und Aufbau der API-Adressen (URL-Struktur).

Was du den Benutzer fragst:

  • Server-Adresse: die Adresse, unter der er teamspace im Browser öffnet. Steht im Auftrag nur ein Platzhalter wie <Server-Adresse>, frag nach. Nimm nie die Angabe servers aus der OpenAPI-Beschreibung, dort kann eine ganz andere Adresse stehen.
  • Geräte-ID und Token gibt der Benutzer erst in der fertigen Anwendung ein. Bitte ihn, sie dir nicht in den Chat zu schreiben und nicht auf Bildschirmfotos zu zeigen.
  • Fachbegriffe ohne eigenes Feld, etwa „Projektleiter“ oder „Budget“. Was damit gemeint ist, legt der Benutzer fest. Siehe Wenn du Projektauswertungen anzeigen willst.
  • Den Rahmen: nur lesen oder auch schreiben, wo die Anwendung laufen soll, welche Bibliotheken erlaubt sind.

Feldnamen, Pflichtfelder und gültige Werte fragst du nicht ab. Sie stehen hier, in meta/template oder in der optionsUrl eines Feldes. Geht eine Angabe aus keiner Quelle hervor, frag nach, statt sie zu erfinden.

Was für jede Anwendung gilt

Anmeldung

  • Angemeldet wird mit einem Gerätepasswort: Persönliche Einstellungen → Geräte → Neues Gerätepasswort. Der Benutzer braucht dafür die Berechtigung „Synchronisation“.
  • Jede Anfrage schickt Authorization: Basic <base64(Geräte-ID:Token)>. Das Login-Passwort verwendest und speicherst du nie.
  • Einen Endpunkt „aktueller Benutzer“ gibt es nicht. Den angemeldeten Mitarbeiter liefert GET <server>/api/device/<Geräte-ID>;depth=1 im Feld user, seine ID steht in user.value. Klappt dieser Aufruf, gilt der Token.
  • externalAccess: true heißt: Das Gerät gehört einem externen Kontakt, nicht einem Mitarbeiter.
  • Über die API hat der Benutzer genau die Rechte aus der Oberfläche. Das gilt auch für eine Anwendung, die nur liest: Ihr Token könnte alles, was der Benutzer darf.

Listen und Antworten

  • Aufbau: <server>/api/<collection>;<matrix>?<query>. Steuerung mit Semikolon am Pfad (;limit=100;depth=1), Filter hinter dem Fragezeichen (?date=2026-06-01..2026-06-30&project=123,456).
  • Seiten: höchstens 100 Einträge je Abruf, size ist die Gesamtzahl. Blättere mit ;offset=, oder folge in _links dem Eintrag > nextPage, bis es keinen mehr gibt. Ohne Blättern fehlen Daten, ohne dass es auffällt.
  • Leere Listen kommen als "items": null, nicht als []. Behandle null als leere Liste.
  • ;depth=1: Einträge einer Liste bleiben Link-Objekte, die Felder stehen unter data. Ohne depth gibt es nur caption, href und value.
  • Fehlende Felder: Felder ohne Wert können ganz fehlen statt null zu sein, am Projekt zum Beispiel plannedMinutes. Prüf auf beides.
  • Voreinstellungen: Manche Listen filtern ohne Angabe. /api/project liefert nur aktive Projekte ohne Vorlagen, /api/time Projektzeiten und Entwürfe. Die tatsächlich angewandten Filter zeigt _url in der Antwort.
  • Filter: = gleich, *= enthält, von..bis Bereich einschließlich beider Grenzen, A,B mehrere Werte. Ob ein Filter einen Bereich erlaubt, zeigt searchableRange in meta.
  • Ob ein Filter vorhanden ist, steht in der Parameterliste der Collection in <server>/api/openapi.json des Zielservers. Nach einem Update vergleichst du die Parameterlisten der alten und der neuen Beschreibung. Prüf das zur Laufzeit nicht mit erfundenen Werten: userRoleResponsible=0 lieferte keine leere Liste, und eine Anwendung hielt den Filter deshalb für nicht vorhanden. Vergleich mit echten Werten, etwa die Liste gefiltert mit der eigenen Benutzer-ID gegen die ungefilterte. Wie ältere Server auf unbekannte Filter reagieren, ist nicht geprüft.
  • Aufzählungen wie Belegstatus oder Zahlungsstatus kommen mit technischem value und lesbarer caption, etwa "value": "OPEN_AUTO", "caption": "Offen (auto)". Zeig die caption an.
  • Gültige Werte: Auswahlfelder tragen eine optionsUrl. Für neue Objekte findest du sie in <server>/api/<collection>/meta/template. Übernimm die Adresse, bau sie nicht selbst.
  • ;evaluate=isBookable filtert nicht, sondern ergänzt jeden Eintrag um evaluation: true/false. Zum Filtern nimmst du ?isBookable=true.

Schreiben

  • Anlegen mit POST an die Liste, etwa <server>/api/time. Fehlt ein Pflichtfeld, antwortet der Server mit 403 und nennt es in message.
  • Ändern mit PUT an die Adresse des einzelnen Elements, etwa <server>/api/issue/<id>. Dabei gelten die Regeln unten.
  • Frag den Benutzer, bevor die Anwendung etwas mit Folgen schreibt.

PUT ersetzt den ganzen Datensatz. Felder, die du nicht mitschickst, leert der Server. Ein PUT nur mit subject löscht an einem offenen Punkt zum Beispiel Projekt, Priorität und Beschreibung. Die OpenAPI-Beschreibung sagt davon nichts. So änderst du sicher:

  1. Laden: unmittelbar vor dem Speichern GET auf das Element.
  2. Konflikt prüfen: changedDate mit dem Wert vergleichen, den du beim Öffnen gelesen hast. Ein _lastModifiedDate liefert die Einzelressource nicht. Weicht changedDate ab, hat inzwischen jemand anderes geändert. Zeig das an, statt seine Änderung zu überschreiben.
  3. Übernehmen: alle schreibbaren Felder aus der Antwort, in den Formaten aus der Tabelle unten. Nur-Lese-Felder lässt du weg, am offenen Punkt initiator, createDate, createdUser, changedDate und changeUser.
  4. Ändern und senden: nur die Felder ersetzen, die sich ändern sollen, dann PUT.
  5. Prüfen: erneut GET und vergleichen, ob die Werte gespeichert sind.

Diese Formate bleiben beim Zurückschreiben erhalten, geprüft am offenen Punkt:

FeldSo schickst du es
Verknüpfungen wie project, assignee{ "value": <id> } oder die reine ID
type{ "value": "ISSUETYPE_TODO" } oder der reine Wert "ISSUETYPE_TODO"
colorlabelID als Zahl
issueList[ { "value": <id> } ]
customfields[ { "key": "…", "value": "…" } ]
categoriesListe von Texten

Worauf du achten musst:

  • Unbekannte Feldnamen ignoriert der Server stillschweigend und antwortet trotzdem mit 200. Ein Tippfehler fällt erst beim Prüfen in Schritt 5 auf.
  • Pflichtfelder beim Ändern nennt die OpenAPI-Beschreibung nicht. Am offenen Punkt sind es subject und type. Fehlt eins, antwortet der Server mit 500 statt 403: „Subject is required“ bzw. „Invalid type“.

Datum und Uhrzeit

  • Zeitstempel schickst du mit Zeitzone: 2026-09-20T09:30:00.000+02:00. Das gilt für begin und end der Zeitbuchung genauso wie für duedate und donedate des offenen Punkts.
  • Zurück kommen sie in UTC: 2026-09-20T07:30:00.000+00:00. Rechne für die Anzeige in die Ortszeit um.
  • Ein Datum ohne Uhrzeit schickst du als Mitternacht in Ortszeit: 2026-09-21T00:00:00.000+02:00, im Winter mit +01:00. Schickst du an duedate nur 2026-09-21, speichert der Server Mitternacht UTC, in Deutschland also 02:00 Uhr.
  • Nicht mit Leerzeichen: Das Format yyyy-MM-dd HH:mm:ss, das die OpenAPI-Beschreibung für donedate nennt, lehnt der Server mit 400 ab („Cannot deserialize value of type java.util.Calendar“). Folge hier nicht der Beschreibung. Diese Antwort kommt ohne CORS-Header, siehe Fehlerbilder.
  • Nur-Lese-Felder wie createDate und changedDate kommen dagegen mit Leerzeichen und ohne Zeitzone: 2026-09-10 17:35:42.0. Zum Vergleichen nimmst du den Text, wie er kommt.
  • Oberfläche und API: In der Oberfläche sind Datum und Uhrzeit zwei Felder, in der API ist es eines.
  • Bis Version 2026.3-preview-12 ließen sich duedate und donedate offener Punkte gar nicht schreiben. Der Server antwortete mit 500 („null for Key: xDUEDATEx“ bzw. „xDONEDATEx“ oder „GregorianCalendar cannot be cast to java.util.Date“). Ab preview-13 geht es.

So erzeugst du den Zeitstempel in JavaScript:

// Ortszeit mit Zeitzone, z. B. 2026-09-20T09:30:00.000+02:00
function zeitstempel(d) {
  const zz = (n) => String(n).padStart(2, '0');
  const versatz = -d.getTimezoneOffset();
  const v = Math.abs(versatz);
  return d.getFullYear() + '-' + zz(d.getMonth() + 1) + '-' + zz(d.getDate()) +
    'T' + zz(d.getHours()) + ':' + zz(d.getMinutes()) + ':' + zz(d.getSeconds()) + '.000' +
    (versatz < 0 ? '-' : '+') + zz(Math.floor(v / 60)) + ':' + zz(v % 60);
}

zeitstempel(new Date());             // jetzt, etwa für donedate
zeitstempel(new Date(2026, 8, 21));  // 21.09.2026, Mitternacht Ortszeit

Sicherheit

  • Nur https://-Adressen zulassen.
  • Geräte-ID und Token nie in den Quelltext, in Dateien oder in Protokolle, auch nicht beim Testen. Dauerhaft speichern nur, wenn der Benutzer „Angemeldet bleiben“ wählt.
  • Serverdaten immer als Text ausgeben (textContent), nie als HTML. Beschreibungen von Tickets und offenen Punkten enthalten zum Beispiel HTML.
  • Verweisen aus Server-Antworten (href, optionsUrl, _links) nur folgen, wenn sie auf die eigene Server-Adresse zeigen. Sonst gehen die Zugangsdaten an eine fremde Adresse.
  • Keine Skripte von fremden Servern laden.
  • Bei Fehlern nur die Meldung anzeigen, nie den Stacktrace. Wie du sie herausziehst, steht unter Fehlerbilder.
  • Dem Benutzer empfehlen, für die Anwendung ein eigenes Gerätepasswort anzulegen und für Tests ein weiteres. Das für Tests löscht er danach, das der Anwendung widerruft er bei Verdacht.

Mehr dazu in Eigene Web-Anwendungen sicher bauen.

Browserzugriff

Eine Anwendung im Browser darf die API direkt aufrufen, ein eigener Proxy ist nicht nötig. Der Server erlaubt jede Herkunft und den Header Authorization. Die erlaubten Methoden hängen an der Adresse: an Listen wie /api/time GET und POST, an einzelnen Elementen wie /api/issue/{id} GET, PUT und DELETE. Zeigt die Vorabprüfung an einer Liste nur Access-Control-Allow-Methods: POST, GET, scheitert PUT deshalb nicht: Es steht in der Vorabprüfung des einzelnen Elements. Schick die Anmeldung im Header, nicht über Cookies. Die Anwendung kann deshalb auf jedem Webspace mit https liegen. Manche Fehlerantworten tragen allerdings keine CORS-Header, siehe Fehlerbilder.

Fehlerbilder

StatusFormBedeutungWas die Anwendung tut
400ohne CORS-HeaderJSON nicht lesbar, etwa ein Datum im falschen FormatFormate prüfen, siehe Datum und Uhrzeit
401reiner TextGeräte-ID oder Token falsch, Gerät gelöschtAnmeldung neu abfragen
403JSON mit messagePflichtfeld fehlt beim Anlegen, Datum außerhalb der Schreibberechtigung, fehlende Rechte; auch beim Abruf eines gelöschten Eintragsmessage anzeigen, Eingaben behalten
405JSON mit messageMethode an dieser Adresse nicht erlaubt oder ungültige ID in der AdresseMethode und Adresse prüfen
500Text oder JSONFehler im Server, etwa meta ohne Anmeldung, der Filter ?type= an issue oder ein fehlendes Pflichtfeld beim PUT an issueMeldung anzeigen, nicht wiederholen, Anfrage vereinfachen. Bei unerklärlichen Fehlern zuerst die Version prüfen
  • Zwei Formen von Fehlertexten: JSON mit message und stacktrace, oder Klartext in einem Rahmen aus Sternchen mit Java-Stacktrace, etwa :( Invalid type und darunter Zeilen wie at de.fivepoint…. Zeig dem Benutzer nur die eigentliche Meldung, ohne Sternchen und Stacktrace.
  • Nicht jede Fehlerantwort trägt CORS-Header, zum Beispiel 400 bei nicht lesbarem JSON. Im Browser scheitert fetch dann wie bei einem Netzwerkfehler, Status und Meldung bleiben unsichtbar. Ein „Netzwerkfehler“ beim Speichern kann also eine Ablehnung durch den Server sein. Melde dann nicht nur „keine Verbindung“, sondern bitte den Benutzer, in teamspace nachzusehen, ob gespeichert wurde.

Module der Oberfläche und Collections

Die API spricht von Collections, der Benutzer von Modulen. Verwechsle sie nicht: „Offene Punkte“ sind issue, nicht ticket.

OberflächeCollectionWichtigIm Hilfecenter
Zeiterfassung, ProjektzeitentimePflicht beim Buchen: date, project, area, fieldProjektzeiten buchen
Rubrik und Bereichtimecategoryan der Buchung als area und field, Werte über optionsUrlProjektzeiten buchen
Offene Punkteissueerledigt ist, was ein Erledigt-Datum hatEinführung in die Offenen Punkte
TicketsticketStatus aus ticketstate, Beschreibung enthält HTMLEinführung ins Ticketsystem
ProjekteprojectPlan- und Ist-Werte, Unterprojekte über mainProjectEinführung ins Projektmanagement
Projektrollen, Projektberechtigungenprojectrole, project/{id}/projectpermissionProjektleitung ab 2026.3 über den Filter userRoleResponsible an project, davor über die Rolle mit responsibleEinführung ins Projektmanagement
RechnungeninvoiceFeld project, aber kein Filter danachRechnungen stellen und Zahlungen buchen
MitarbeiteruserID für worker und assigneeEinführung in Personal & HR
GerätepasswortdeviceAnmeldung, Besitzer in userAuthentifizieren

Wenn du Zeiten erfassen willst

  1. Mitarbeiter: GET <server>/api/device/<Geräte-ID>;depth=1, dann user.value.
  2. Projekte: GET <server>/api/project;sort=name?isBookable=true&isActive=true&isTemplate=false&isDraft=false.
  3. Rubrik und Bereich: GET <server>/api/time/meta/template, dann die optionsUrl von area und field aufrufen. Zeig die caption zur Auswahl, gebucht wird der value.
  4. Zeiten eines Tages: GET <server>/api/time;depth=1?worker=<id>&date=YYYY-MM-DD. Die Tagessumme ist die Summe von data.amount über alle Einträge mit draftStatus NORMAL. amount, begin und end können null sein. Nach begin sortiert die App selbst.
  5. Buchen: POST <server>/api/time mit date, project, area, field, amount (Minuten) und, wenn gemessen, begin und end. worker darfst du weglassen, dann gilt der Besitzer des Geräts.

Worauf du achten musst:

  • amount immer mitschicken. Der Server berechnet die Dauer nicht aus Beginn und Ende. Ohne amount entsteht eine Buchung ohne Dauer.
  • Fehlende Pflichtfelder meldet der Server mit 403 und „Project is missing!“, „ActivityArea is missing!“ oder „ActivityField is missing!“. Fehlt date oder liegt es außerhalb des erlaubten Zeitraums: „Das Datum liegt außerhalb Ihrer Schreibberechtigungen“.
  • Weitere Pflichtfelder kann eine Installation verlangen, etwa die Beschreibung. Nennt der Auftrag solche Felder, mach sie in der Anwendung zur Pflicht. Zeig die message des Servers trotzdem in jedem Fall an.
  • Erfolg ist 201. Die Antwort enthält die angelegte Buchung, der Header Location ihre Adresse.
  • Minuten anzeigen: Rechne erst bei der Anzeige in Stunden um. Eine Buchung von 2 Minuten darf nicht als „0 Std.“ erscheinen.
  • Eine Stoppuhr gibt es in der API nicht. Die App merkt sich den Beginn selbst, je Mitarbeiter, und bucht beim Stopp.

Beispiele für jede Anfrage und Antwort stehen in Zeiten über die API anzeigen und buchen.

Wenn du offene Punkte liest oder bearbeitest

Lesen

  1. Eigene offene Punkte nach Fälligkeit: GET <server>/api/issue;sort=duedate;limit=100;offset=0?assignee=<Benutzer-ID>&isDone=false. Die Benutzer-ID liefert die Geräte-Ressource, siehe Anmeldung.
  2. Details: Ohne ;depth=1 liefert die Liste nur Verweise. Lade die Details dann je Punkt per GET <server>/api/issue/<id>, mehrere gleichzeitig, aber nicht alle auf einmal. Zum Ändern brauchst du diese Einzelressource ohnehin, siehe Schreiben.
  3. Felder: subject (Betreff), description (HTML), type, duedate (Fälligkeit), donedate (Erledigt-Datum), progress (Prozent), priority, state (Status als Freitext), project, assignee, colorlabel (Farbmarkierung), issueList (Listen), categories, customfields. Viele davon können null sein.
  4. Typ: type.value ist einer von ISSUETYPE_TODO, ISSUETYPE_GOAL, ISSUETYPE_PROBLEM, ISSUETYPE_IDEA, ISSUETYPE_NOTE, ISSUETYPE_UNKNOWN. Anzeigen solltest du type.caption. Die Liste liefert <server>/api/enum/issueType, dort zählt die Groß- und Kleinschreibung. Die OpenAPI-Beschreibung nennt die Werte ohne das Präfix ISSUETYPE_.
  5. Überfällig ist ein Punkt mit duedate in der Vergangenheit und ohne donedate. Punkte ohne Fälligkeit sind nie überfällig. Soll ihre Position in der Liste feststehen, sortierst du in der App nach.
  6. Nach Typ filtern machst du in der App. Der Filter ?type= endete am Testmandanten mit 500, egal mit welchem Wert.

Erledigen, wieder öffnen, löschen

Geprüft ab Version 2026.3-preview-13. Davor ließ sich donedate nicht schreiben, siehe Datum und Uhrzeit.

  • Erledigen: vollständiges PUT wie unter Schreiben, mit donedate auf jetzt und progress: 100. donedate schickst du als Zeitstempel mit Zeitzone, nicht im Format der OpenAPI-Beschreibung. So macht es auch der Haken in der Oberfläche, siehe Meine offenen Punkte: anlegen & abarbeiten. Danach findet ?isDone=true den Punkt.
  • progress: 100 allein erledigt nichts. Erledigt ist, was ein donedate hat.
  • Wieder öffnen: vollständiges PUT mit donedate: null.
  • Löschen: DELETE <server>/api/issue/<id>, Antwort 204. Praktisch, um Test-Datensätze wieder zu entfernen.

Ein Punkt wird so erledigt, gekürzt. Alle übrigen schreibbaren Felder übernimmst du unverändert aus dem GET:

PUT <server>/api/issue/<id>
Content-Type: application/json

{
  "subject": "Angebot nachfassen",
  "type": { "value": "ISSUETYPE_TODO" },
  "project": { "value": <id> },
  "assignee": { "value": <Benutzer-ID> },
  "priority": 5,
  "description": "<p>Kunden anrufen</p>",
  "duedate": "2026-09-20T07:30:00.000+00:00",
  "donedate": "2026-09-14T16:05:00.000+02:00",
  "progress": 100,
  …
}

Beschreibung

  • Die Beschreibung ist Rich-Text (HTML). Zur Anzeige ziehst du den Text heraus, etwa mit DOMParser und textContent.
  • Überschreib keine formatierte Beschreibung mit dem Inhalt eines einfachen Textfelds. Fettdruck, Listen und Bilder gingen verloren.
  • Eigenen Text schickst du als ein <p>…</p> je Zeile, mit maskierten Sonderzeichen.
const alsText = (html) => new DOMParser().parseFromString(html || '', 'text/html').body.textContent;
const maskieren = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
const alsHtml = (text) => text.split('\n').map((zeile) => '<p>' + maskieren(zeile) + '</p>').join('');

Priorität, Farbmarkierung, Listen und Projekt

  • Priorität: intern immer 1 (niedrig) bis 9 (sehr hoch), ohne Priorität null. Wie die Oberfläche sie zeigt, als Zahl, Sterne oder Buchstaben, stellt jede Installation ein. Welcher Wert welchem Stern oder Buchstaben entspricht, ist nicht dokumentiert. Klär das mit dem Benutzer.
  • Farbmarkierungen laden: GET <server>/api/colorlabel;limit=100;depth=1 liefert jede Farbe mit allen Feldern unter items[].data. Eine Anfrage genügt.
  • Farbwert: color ist ein Hexwert ohne #, in gemischter Schreibweise, etwa 00CC00 oder 2b81c5.
  • Namen von Farbmarkierungen sind nicht eindeutig, „Standard“ kann zweimal vorkommen. Unterscheide sie über die ID.
  • Für offene Punkte gedacht sind nur Farben mit useInIssues: true. Der Server nimmt trotzdem jede an. Biete deshalb nur diese an.
  • Am offenen Punkt enthält colorlabel keinen Farbwert. Die Farbe holst du über die ID aus der Liste der Farbmarkierungen.
  • Listen: issueList: [] entfernt die Zuordnung zu einer Liste nicht.
  • Projekt suchen: GET <server>/api/project;sort=name;limit=20?isActive=true&search*="<Text>". Den Suchtext setzt du in Anführungszeichen.

Wenn du Projektauswertungen anzeigen willst

Plan- und Ist-Werte

Ein Projekt trägt mehrere Plan/Ist-Paare. „Budget“ ist keines davon allein, der Benutzer muss festlegen, welches gemeint ist:

PaarPlanIst
Stunden (in Minuten)plannedMinutessumMinutes
abrechenbare Stunden (in Minuten)plannedMinutesBillablesumMinutesBillable
Leistungsstunden (in Minuten)plannedMinutesPerformancesumMinutesPerformance
ArbeitskostenplannedWorkingCostssumWorkingCosts
KostenplannedExpensessumExpenses
ErlöseplannedIncomesumIncome

Zu jedem Paar gibt es …Enabled und …Calculated, etwa plannedMinutesEnabled und plannedMinutesCalculated. Ist kein Planwert gepflegt, fehlt das Feld planned… in der Antwort ganz. Am Testmandanten war plannedMinutesCalculated an einem Hauptprojekt trotzdem gefüllt. Was genau darin steckt, ist noch nicht bestätigt, siehe die offenen Fragen unten.

Unterprojekte

  • sumMinutes enthält die Unterprojekte. Am Testmandanten ergab ein Hauptprojekt mit 210 eigenen Minuten ein sumMinutes von 2102, genau die Summe über alle Unterprojekte.
  • Der Filter project enthält sie nicht. /api/time?project=<Hauptprojekt> liefert nur die Zeiten, die direkt auf dem Hauptprojekt gebucht sind, /api/issue?project= ebenso.
  • Alle Projekte eines Baums liefert GET <server>/api/project?mainProject=<id>, das Hauptprojekt selbst eingeschlossen. Nur die direkten Kinder liefert ?parent=<id>. Der Matrix-Parameter ;parent= wirkt an Projekten nicht.
  • Für Auswertungen über den ganzen Baum gibst du alle IDs als Mengenfilter mit: ?project=<id1>,<id2>,<id3>. Das gilt für Zeiten, offene Punkte und Tickets.

Stunden je Mitarbeiter und Woche

GET <server>/api/time;depth=1?project=<IDs des Baums>&date=<von>..<bis>, mit Blättern bis zur letzten Seite. Gruppiere in der App nach data.worker.value und der Kalenderwoche von data.date, summiere data.amount der Einträge mit draftStatus NORMAL. Stimmt die Gesamtsumme mit sumMinutes des Hauptprojekts überein, sind Filter und Unterprojekte richtig.

Projektleitung

Ein Feld „Projektleiter“ gibt es nicht.

Ab Version 2026.3 (geprüft mit 2026.3-preview-13) hat die Projektliste die Filter userRoleResponsible („Verantwortlich“) und userRoleArranger („Bearbeiter“). Als Wert dient die Benutzer-ID aus der Anmeldung, nicht die ID einer Benutzergruppe. Eine Anfrage genügt, hier für Hauptprojekte ohne Vorlagen:

GET <server>/api/project;depth=1?isMainproject=true&isTemplate=false&userRoleResponsible=<Benutzer-ID>

In Version 2026.2 fehlen beide Filter. Ob der Zielserver sie kennt, steht in seiner openapi.json, siehe Listen und Antworten.

Auf älteren Servern bleibt nur der Weg über Rollen und Berechtigungen. „Bearbeiter“ lässt sich dort nicht ermitteln. Am Testmandanten ging das so:

  1. Rollen: GET <server>/api/projectrole;depth=1. Rollen mit data.responsible: true sind verantwortlich, am Testmandanten die Rolle „Verantwortlich“.
  2. Gruppen des Benutzers: GET <server>/api/user/<id>/usergroups.
  3. Berechtigungen je Projekt: GET <server>/api/project/<id>/projectpermission;depth=1. Jeder Eintrag nennt projectRole, userGroup und, wenn geerbt, inheritationFromProject. Leitet der Benutzer das Projekt, gibt es einen Eintrag mit einer verantwortlichen Rolle und einer seiner Gruppen.

Das ist eine Anfrage je Projekt. Stell sie parallel und lade nur die Projekte, die du wirklich anzeigen willst.

Offene Punkte und Tickets je Projekt

  • Offene Punkte: GET <server>/api/issue;depth=1?project=<IDs>&isDone=false.
  • Tickets: GET <server>/api/ticket;depth=1?project=<IDs>. Der Status in data.state ist ein Verweis auf /api/ticketstate mit caption, zum Beispiel „In Arbeit“. Die Status legt jede Installation selbst fest. Jeder trägt eine responsibility: INTERN, EXTERN oder NONE. Am Testmandanten hatten nur die geschlossenen Status („Erfolgreich geschlossen“, „Erfolglos geschlossen“) NONE. Welche Status als offen gelten, frag den Benutzer, bis das bestätigt ist.
  • Ticket-Beschreibungen enthalten HTML. Gib sie als Text aus oder lass sie weg.

Rechnungen

  • Rechnungen haben ein Feld project, lassen sich aber nicht nach Projekt filtern. Lade GET <server>/api/invoice;depth=1 seitenweise und filtere in der App auf data.project.value.
  • data.status ist der Belegstatus (zum Beispiel VALIDATED, „Validiert“), data.payStatus der Zahlungsstatus (zum Beispiel OPEN_AUTO, „Offen (auto)“). Zeig jeweils die caption.
  • data.payStatusOutstanding ist der offene Betrag, data.payStatusPayed der bezahlte, data.dueDate die Fälligkeit.

Wenn du eine React-Anwendung auslieferst

Diese Hinweise stammen aus einer Übersicht für Projektleiter, gebaut mit React und Vite.

  • Ein Doppelklick auf index.html funktioniert nicht. Vite erzeugt JavaScript-Module, und die laden Browser nicht über file://. Die Seite bleibt leer. Sag das dem Benutzer.
  • Lokal ansehen: npm run build, danach npm run preview im Projektordner, üblich unter http://localhost:4173. Das Terminal muss offen bleiben. „Verbindung abgelehnt“ im Browser heißt: npm run preview läuft nicht (mehr).
  • Vite lauscht teils nur über IPv6 ([::1]) oder nur über IPv4. Antwortet localhost oder 127.0.0.1 nicht, probier die andere Adresse, oder starte den Server mit --host 127.0.0.1.
  • Veröffentlichen: den Inhalt von dist/ auf einen beliebigen Webserver kopieren. Mit relativem Basispfad (base: './' in vite.config) läuft die Anwendung auch in einem Unterverzeichnis.
  • Nach jeder Änderung wieder npm run build und dist/ neu hochladen.

Wenn du testest

  • Miss das Verhalten am Server, bevor du dich auf eine Annahme verlässt, auch auf eine aus der OpenAPI-Beschreibung.

  • Ohne Zugangsdaten testest du mit nachgebauten Daten und sagst dem Benutzer, dass der erste Test gegen den echten Server bei ihm liegt. Dabei zeigen sich meist noch Abweichungen.

  • Mit einem Zugang für Tests (eigenes Gerätepasswort, siehe Sicherheit) liest du den Token aus einer Umgebungsvariable, die der Benutzer setzt, nie aus dem Chat. Experimentier nie an echten Daten:

    1. Leg einen eigenen Test-Datensatz an, etwa einen offenen Punkt mit dem Betreff API-Test, bitte ignorieren.
    2. Mach alle Schreibversuche an diesem Datensatz.
    3. Vergleich nach jedem Schreiben alle Felder per GET.
    4. Lösch ihn am Ende per DELETE.
  • Umlaute und Sonderzeichen unter Windows: Mit curl aus Git Bash kommen sie leicht falsch kodiert an. Ein Gedankenstrich im Body endete zum Beispiel mit „Invalid UTF-8 start byte 0x96“. Übergib den Body als UTF-8-Datei oder nimm für Tests nur ASCII:

    curl -X PUT "<server>/api/issue/<id>" \
      -u "$GERAETE_ID:$TOKEN" \
      -H "Content-Type: application/json" \
      --data-binary @body.json
  • Bau aussagekräftige Fehlermeldungen ein: die angefragte Adresse ohne Zugangsdaten, den Statuscode und den Aufbau der Antwort mit Feldnamen, Typen, size, offset und limit, aber ohne Inhalte. Dann genügt dem Benutzer ein Bildschirmfoto, und du kannst den Fehler gezielt beheben, statt zu raten. So fiel zum Beispiel "items": null auf.

  • Bau die nachgebauten Daten wie echte Antworten: Einträge als Link-Objekte mit data, leere Listen mit "items": null, fehlende Felder statt null.

  • Eine lokale Attrappe des Servers lohnt sich. Bilde darin das gemessene Verhalten nach: PUT ersetzt den ganzen Datensatz, Datumsfelder kommen in UTC zurück, Fehler kommen als JSON oder Klartext und teils ohne CORS-Header. So testest du Fehler- und Randfälle automatisiert und ohne Risiko.

  • Bildschirmfotos in Handybreite: Headless-Chrome hält das Fenster auf mindestens 500 px Breite, auch mit --window-size=375,800. Prüf Breiten von 360 bis 400 px in einem iframe mit fester Breite, sonst wirken Inhalte fälschlich abgeschnitten.

  • Teste zwei Fälle: ein Projekt ohne Buchungen, Tickets und offene Punkte und eines mit Daten.

  • Vergleich Summen mit teamspace, etwa die gebuchten Stunden gegen sumMinutes oder die Angabe im Projekt. Stimmen sie, sind Filter und Unterprojekte richtig.

Noch offen: hier fragst du nach

Zu diesen Punkten gibt es noch keine bestätigte Antwort. Frag den Benutzer, statt eine Annahme einzubauen:

  • ob „Verantwortlich“ (userRoleResponsible, auf älteren Servern die Rolle mit responsible) in jeder Installation die Projektleitung meint
  • welche Ticket-Status als offen gelten
  • welches Plan/Ist-Paar „Budget“ heißt, und was …Calculated enthält
  • wie die Installation die Priorität offener Punkte anzeigt (Zahl, Sterne oder Buchstaben) und welcher Wert zu welcher Stufe gehört

Für Menschen: der Auftrag an die KI

Wer einen Coding-Agenten beauftragt, achtet auf fünf Dinge:

  • Die echte Server-Adresse eintragen. Ein stehengebliebener Platzhalter wie <Server-Adresse> wird leicht übersehen, dann kann die KI die Beschreibung der Installation nicht lesen.
  • Die Quellen nennen: diese Seite, <Server-Adresse>/api/openapi.json und <Server-Adresse>/api/api/dto.ts.
  • Zusätzliche Pflichtfelder nennen. Ohne date, project, area und field bucht der Server keine Zeit, das steht auf dieser Seite. Verlangt eure Installation mehr, etwa eine Beschreibung, gehört das in den Auftrag.
  • Fachbegriffe festlegen oder Nachfragen ausdrücklich erlauben.
  • Den Rahmen nennen: nur lesen oder auch schreiben, wo die Anwendung laufen soll, welche Bibliotheken erlaubt sind.

Vorlage für eine kleine Anwendung

Diese Vorlage stammt aus einer Zeiterfassungs-App, die als einzelne HTML-Datei entstanden ist. Ersetze alle Angaben in spitzen Klammern, bevor du den Auftrag abschickst:

Baue eine Web-Anwendung als einzelne HTML-Datei ohne externe Abhängigkeiten, die sich über die REST-API mit teamspace verbindet. <Was die Anwendung tun soll, in zwei bis drei Sätzen.>
Server-Adresse: <https://eure-installation> (die Adresse, unter der wir teamspace im Browser öffnen)
Lies zuerst den Leitfaden für Coding-Agenten unter https://help.teamspace.de/thema/api/coding-agenten/, dann <Server-Adresse>/api/openapi.json (nur die benötigten Pfade) und <Server-Adresse>/api/api/dto.ts. Verwende immer die eingegebene Server-Adresse, nicht die Angabe unter „servers“.
Anmeldung per Gerätepasswort (Geräte-ID und Token, Basic Auth).
Zusätzliche Pflichtfelder in unserer Installation: <z. B. Beschreibung bei Zeitbuchungen, sonst „keine“>.
Die Anwendung darf: <nur lesen | Zeiten buchen | offene Punkte erledigen>.
Anforderungen: nur https; Zugangsdaten nie im Code und nur nach Zustimmung speichern; Serverdaten immer als Text ausgeben; Fehlermeldungen des Servers anzeigen.
Frage nach, bevor du etwas annimmst, das nicht in den Quellen steht.

Für einen bestehenden Auftrag

Hast du schon einen Auftrag, reicht dieser Satz:

Lies vor dem Programmieren den Leitfaden für Coding-Agenten unter https://help.teamspace.de/thema/api/coding-agenten/ und die OpenAPI-Beschreibung unter <Server-Adresse>/api/openapi.json. Halte dich an die dort beschriebenen Adressen, Pflichtfelder und Regeln. Wenn eine Angabe nicht daraus hervorgeht, frage nach, statt sie zu erfinden.

Zugangsdaten gehören nicht in den Chat mit der KI und nicht auf Bildschirmfotos. Ist das doch passiert, widerruf das Gerätepasswort in den Persönlichen Einstellungen.

Verwandte Themen