Zum Hauptinhalt springen
Hilfecenter
Lernpfad: Schritt von

Zeiten über die API anzeigen und buchen

Geführte Übung: den Mitarbeiter zum Gerät ermitteln, buchbare Projekte und die gültigen Werte für Rubrik und Bereich (area, field) laden, die Zeiten eines Tages mit Tagessumme lesen und eine Zeit mit allen Pflichtfeldern buchen.

Voraussetzungen

Eine Projektzeit ist in der API ein Eintrag der Collection time. Er gehört zu einem Mitarbeiter (worker) und einem Projekt (project), trägt die Dauer in Minuten (amount) und, wenn erfasst, Beginn und Ende. Rubrik und Bereich hängen als area und field daran.

Eine einfache Zeiterfassungs-App braucht dafür fünf Anfragen: Wer ist angemeldet? Auf welche Projekte darf gebucht werden? Welche Werte sind für Rubrik und Bereich gültig? Was ist an diesem Tag schon gebucht? Und zuletzt die neue Buchung.

Die beteiligten Endpunkte

SchrittMethodeAdresse
1. Mitarbeiter zum Gerät ermittelnGET/api/device/{Geräte-ID};depth=1
2. Buchbare Projekte ladenGET/api/project;sort=name?isBookable=true
3. Gültige Werte für area und field ladenGET/api/time/meta/template, danach die beiden optionsUrl
4. Zeiten eines Tages lesenGET/api/time;depth=1?worker={id}&date={Datum}
5. Zeit buchenPOST/api/time

Jede Anfrage meldet sich per Basic Auth mit Geräte-ID und Token an. <server> steht in den Beispielen für deine Server-Adresse, also die Adresse, unter der du teamspace im Browser öffnest.

1. Den Mitarbeiter zum Gerät ermitteln

GET  <server>/api/device/10001234;depth=1

Die Geräte-Ressource zeigt, wem das Gerätepasswort gehört. Klappt der Aufruf, weißt du zugleich, dass Geräte-ID und Token gültig sind.

Antwort (Auszug, Namen und IDs exemplarisch):

{
  "_id": 10001234,
  "deviceName": "Zeiterfassung Handy",
  "deviceType": "de.fivepoint.generic.app_password",
  "apiHost": "<server>/api",
  "externalAccess": false,
  "user": {
    "caption": "Anna Müller",
    "href": "<server>/api/user/54967",
    "value": 54967,
    "data": { "_id": 54967, "active": true }
  }
}
  • user: der Besitzer des Geräts als Link-Objekt. Sein value ist die Mitarbeiter-ID. Du brauchst sie in Schritt 4 als Filter worker und in Schritt 5 als Feld worker. Unter data steht mit ;depth=1 der vollständige Mitarbeiter.
  • externalAccess: steht auf true, wenn das Gerätepasswort einem externen Kontakt gehört und nicht einem Mitarbeiter. Für eine Zeiterfassung brauchst du einen Mitarbeiter.
  • apiHost: die Server-Adresse mit angehängtem /api.

2. Buchbare Projekte laden

GET  <server>/api/project;sort=name?isBookable=true&isActive=true&isTemplate=false&isDraft=false
  • isBookable=true: nur Projekte, auf die gebucht werden kann.
  • isActive=true und isTemplate=false: aktive Projekte, keine Vorlagen. Beides ist laut OpenAPI-Beschreibung ohnehin voreingestellt. Ausdrücklich gesetzt bleibt die Abfrage trotzdem eindeutig.
  • isDraft=false: keine Projektentwürfe.

Die Antwort ist eine Liste von Link-Objekten: caption ist der Projektname, value die Projekt-ID für die Buchung. Welche Projekte darin auftauchen, entscheiden wie in der Oberfläche die Rechte des angemeldeten Mitarbeiters.

Info: Eine Liste liefert standardmäßig höchstens 100 Einträge. size nennt die Gesamtzahl. Ist sie größer, lädst du den Rest seitenweise mit ;offset=100, ;offset=200 und so weiter.

3. Die gültigen Werte für Rubrik und Bereich laden

Eine Buchung verweist in area und field auf je einen Eintrag der Collection timecategory. Beide Felder sind beim Buchen Pflicht. Coding-Agenten übersetzen sie oft mit „Tätigkeitsbereich“ und „Tätigkeitsfeld“. Welche Einträge gültig sind, sagt dir die API selbst: Die Vorlage für eine neue Zeitbuchung enthält für beide Felder eine optionsUrl.

GET  <server>/api/time/meta/template

Antwort (Auszug, geprüft am 2026-09-10):

{
  "project": null,
  "area": { "href": null, "value": null, "optionsUrl": "<server>/api/timecategory?categoryType=2" },
  "field": { "href": null, "value": null, "optionsUrl": "<server>/api/timecategory?categoryType=1" },
  "worker": null,
  "date": null
}

Ruf die beiden Adressen aus optionsUrl auf. Jede liefert die Einträge, die in das jeweilige Feld gehören, als Link-Objekte: caption ist der Name, value die ID für die Buchung. Dieselbe optionsUrl steht auch an area und field jeder vorhandenen Buchung.

Am Testmandanten enthielt die Liste für area die Rubriken Verrechenbar, Nicht verrechenbar und Zu klären, die Liste für field den Bereich Standard. Das sind die Namen, die du in der Oberfläche beim Buchen von Projektzeiten als Rubrik und Bereich siehst.

Achtung: Im Eintrag steht categoryType als Text ("area" oder "field"), der Filter erwartet aber die Zahl aus der optionsUrl. ?categoryType=area liefert eine leere Liste. Übernimm die Adresse deshalb aus optionsUrl, statt sie selbst zu bauen.

Jeder Eintrag hat außerdem description und die Schalter billable, performanceTime und eligibleForOvertime. Welche Werte in den Listen stehen, legt jede Installation selbst fest. Zeig in deiner App deshalb die Namen aus der Liste zur Auswahl an, statt Werte fest einzubauen.

4. Die Zeiten eines Tages lesen

GET  <server>/api/time;depth=1?worker=54967&date=2026-09-10

worker grenzt auf den Mitarbeiter aus Schritt 1 ein, date auf einen Tag im Format YYYY-MM-DD. Ohne depth enthält items nur Link-Objekte ohne die Felder der Buchung. Mit ;depth=1 bleibt jeder Eintrag ein Link-Objekt, die vollständige Buchung steht dann unter data.

Nach Beginn sortieren kann die Liste nicht, begin fehlt unter den Sortierfeldern. Ordne die Einträge eines Tages deshalb in der App.

Antwort (Auszug, Namen und IDs exemplarisch):

{
  "size": 1,
  "offset": 0,
  "limit": 100,
  "items": [
    {
      "href": "<server>/api/time/187654321",
      "value": 187654321,
      "rel": "link",
      "data": {
        "_id": 187654321,
        "date": "2026-09-10",
        "begin": "2026-09-10T06:30:00.000+00:00",
        "end": "2026-09-10T08:00:00.000+00:00",
        "amount": 90,
        "draftStatus": { "value": "NORMAL", "optionsUrl": "<server>/api/enum/draftStatus" },
        "project": { "caption": "Website-Relaunch", "href": "<server>/api/project/4711", "value": 4711 },
        "area": { "caption": "Verrechenbar", "value": 1201, "optionsUrl": "<server>/api/timecategory?categoryType=2" },
        "field": { "caption": "Standard", "value": 1305, "optionsUrl": "<server>/api/timecategory?categoryType=1" },
        "worker": { "caption": "Anna Müller", "href": "<server>/api/user/54967", "value": 54967 },
        "description": "Startseite abgestimmt"
      }
    }
  ]
}

Für die Anzeige brauchst du diese Felder aus data:

  • project: Link-Objekt, caption ist der Projektname.
  • begin und end: Zeitstempel in UTC (+00:00). Rechne sie für die Anzeige in die Ortszeit um, in JavaScript etwa mit new Date(…). Bei Buchungen ohne Zeitspanne sind beide null.
  • amount: die Dauer in Minuten. Sie kann null sein, wenn eine Buchung ohne Dauer angelegt wurde.
  • draftStatus: NORMAL ist eine Projektzeit, DRAFT ein Entwurf. Nur NORMAL wertet teamspace als Projektzeit.
  • description: die Beschreibung. Sie kann in Berichten für den Kunden erscheinen. Für interne Notizen gibt es comment.

Tagessumme: Addiere amount über alle Einträge des Tages mit draftStatus NORMAL, 510 Minuten sind 8:30 Stunden. Fehlt amount, zählt die Buchung mit 0. Rechne nicht mit der Spanne von begin bis end: Beginn und Ende können fehlen, und der Server prüft nicht, ob sie zur Dauer passen.

5. Eine Zeit buchen

POST  <server>/api/time
{
  "date": "2026-09-10",
  "begin": "2026-09-10T08:15:00.000+00:00",
  "end": "2026-09-10T09:45:00.000+00:00",
  "amount": 90,
  "project": 4711,
  "worker": 54967,
  "area": 1201,
  "field": 1305,
  "description": "Entwurf der Unterseiten besprochen"
}
  • Verknüpfte Felder (project, worker, area, field) gibst du als reine ID an oder als Link-Objekt { "value": 4711 }. Beides nimmt der Server an.
  • begin und end schickst du mit Zeitzone. Der Server speichert sie in UTC: Aus 2026-09-10T10:00:00+02:00 wird 2026-09-10T08:00:00.000+00:00.
  • amount schickst du immer mit. Der Server berechnet die Dauer nicht aus Beginn und Ende. Ohne amount entsteht eine Buchung ohne Dauer. Passt amount nicht zur Spanne, speichert er beide Angaben unverändert.
  • worker kannst du weglassen. Dann bucht der Server auf den Besitzer des Geräts.

Hat die Buchung geklappt, antwortet der Server mit 201. Die Antwort enthält die angelegte Buchung mit allen Feldern, der Header Location ihre Adresse. Zeig die Buchung danach in der Tagesliste an, entweder aus der Antwort oder indem du die Liste neu lädst.

Pflichtfelder

Bei einer Prüfung am Testmandanten am 2026-09-10 lehnte der Server Buchungen ohne diese Felder ab:

  • date: „Das Datum liegt außerhalb Ihrer Schreibberechtigungen“ mit dem gültigen Zeitraum. Dieselbe Meldung kommt, wenn der Tag außerhalb dieses Zeitraums liegt.
  • project: „Project is missing!“
  • area: „ActivityArea is missing!“
  • field: „ActivityField is missing!“

Fehlt etwas, antwortet der Server mit 403 und nennt alle fehlenden Felder zusammen in message:

{
  "message": "Aktion kann nicht ausgeführt werden. \nActivityField is missing!\nActivityArea is missing!",
  "stacktrace": "de.fivepoint.exception.PermissionDeniedException: …"
}

Ob eine Installation weitere Felder verlangt, etwa die Beschreibung, hängt von der Konfiguration der Zeiterfassung ab. Mach Projekt, Rubrik, Bereich und Beschreibung in deiner App deshalb sichtbar und wählbar, statt sie unbemerkt vorzubelegen.

Stoppuhr: Eine Stoppuhr wie in „Mein Tag“ gibt es in der OpenAPI-Beschreibung nicht als eigene Collection. Eine App misst die Zeit deshalb selbst: Beim Start merkt sie sich den Beginn, beim Stopp bucht sie Beginn, Ende und Dauer mit POST /api/time. Legt sie den laufenden Beginn im Browser ab, dann je Mitarbeiter, damit ein anderer Benutzer auf demselben Gerät ihn nicht übernimmt.

Felder einer Zeitbuchung

Die OpenAPI-Beschreibung und die Vorlage unter /api/time/meta/template nennen keine Pflichtfelder. Mit * markiert sind die Felder, ohne die der Server am Testmandanten nicht buchte.

FeldDatentypBeschreibung
date*stringTag der Buchung (YYYY-MM-DD)
project*VerknüpfungProjekt
area*VerknüpfungEintrag aus timecategory; gültige Werte über optionsUrl, am Testmandanten die Rubrik
field*VerknüpfungEintrag aus timecategory; gültige Werte über optionsUrl, am Testmandanten der Bereich
amountnumberDauer in Minuten; wird nicht aus Beginn und Ende berechnet, deshalb immer mitschicken
beginstringBeginn als Zeitstempel mit Zeitzone, gespeichert in UTC
endstringEnde als Zeitstempel mit Zeitzone, gespeichert in UTC
workerVerknüpfungMitarbeiter; ohne Angabe der Besitzer des Geräts
amountBillablenumberabrechenbare Dauer in Minuten; ohne Angabe gleich amount
draftStatusAufzählungNORMAL (Projektzeit) oder DRAFT (Entwurf); alle Werte unter <server>/api/enum/draftStatus
descriptionstringBeschreibung, kann in Berichten für den Kunden erscheinen
commentstringinterner Kommentar, nicht für den Kunden
ticketVerknüpfungTicket, dem die Zeit zugeordnet ist

Alle Felder stehen im Schema TimeInput der OpenAPI-Beschreibung.

Typische Fragen & Anforderungen

Du möchtest …So geht’s
herausfinden, wer angemeldet istGET <server>/api/device/<Geräte-ID>;depth=1, im Feld user den value lesen.
nur buchbare Projekte anbietenGET <server>/api/project;sort=name?isBookable=true&isActive=true&isTemplate=false&isDraft=false
die Auswahl für Rubrik und Bereich füllenGET <server>/api/time/meta/template und die optionsUrl von area und field aufrufen.
die Zeiten einer Woche lesenDen Filter date als Bereich setzen: ?worker=54967&date=2026-09-07..2026-09-13. Beide Grenztage gehören dazu, siehe URL-Struktur.
die Tagessumme anzeigenamount aller Einträge des Tages mit draftStatus NORMAL addieren.
eine gestoppte Zeit buchenPOST <server>/api/time mit date, project, area, field, amount sowie begin und end.
eine Buchung wieder löschenDELETE <server>/api/time/<id>, Antwort 204.

Häufige Probleme

Woher kommen die gültigen Werte für area und field (Tätigkeitsbereich und Tätigkeitsfeld)? Aus der optionsUrl der beiden Felder. Du findest sie in GET <server>/api/time/meta/template und an jeder vorhandenen Buchung. Die Adresse liefert die erlaubten timecategory-Einträge, deren value schickst du beim Buchen mit.

Warum antwortet der Server beim Buchen mit 403 und „ActivityArea is missing!“? Der Buchung fehlen area oder field. Beide verlangt der Server, obwohl die OpenAPI-Beschreibung sie nicht als Pflicht markiert. Die gültigen Werte holst du wie in Schritt 3.

Warum meldet der Server „Das Datum liegt außerhalb Ihrer Schreibberechtigungen“? date fehlt, oder der Tag liegt außerhalb des Zeitraums, in dem der Mitarbeiter buchen darf. Den gültigen Zeitraum nennt die Meldung.

Warum hat meine Buchung keine Dauer? amount fehlte. Der Server berechnet die Dauer nicht aus Beginn und Ende. Schick amount in Minuten immer mit.

Warum liefert ?categoryType=area eine leere Liste? Der Filter erwartet eine Zahl, auch wenn im Eintrag "area" steht. Nimm die fertige Adresse aus der optionsUrl.

Warum fehlen in der Tagesliste Projekt, Beginn und Dauer? Ohne ;depth=1 enthält items nur Link-Objekte. Mit ;depth=1 stehen die Felder jeder Buchung unter data.

Warum antwortet der Server mit 401, und mein JSON-Parser bricht ab? Die Anmeldung wurde abgelehnt: Geräte-ID oder Token stimmen nicht, oder das Gerät wurde gelöscht. Die Antwort auf 401 ist reiner Text, kein JSON. Prüf deshalb zuerst den Statuscode und lies erst danach den Inhalt als JSON.

Wie zeige ich eine abgelehnte Buchung verständlich an? Zeig den Text aus message an, nicht stacktrace. Behalte die erfasste Zeit, bis die Buchung geklappt hat, damit nichts verloren geht.

Warum fehlen Projekte in der Auswahl? Eine Liste liefert standardmäßig 100 Einträge, den Rest holst du mit ;offset=. Außerdem enthält die Liste nur Projekte, die der angemeldete Mitarbeiter sehen darf.

Hinweise

  • Rufst du die API direkt aus einer Web-Anwendung im Browser auf, gelten eigene Regeln für Zugangsdaten und Browserzugriff. Sie stehen in Eigene Web-Anwendungen sicher bauen.
  • Welche Filter und Felder dein Server sonst noch kennt, schlägst du in der OpenAPI-Beschreibung nach.

Verwandte Themen