JSON über HTTPS. Jede Route ist `GET`. Authentifizierung mit Bearer-Token. Der Mandant kommt aus dem Schlüssel, nie aus dem Query-String.
Basis-URLhttps://app.troop.ch/api/external/v1
Produktions-Host. Dev und Staging liefern denselben Pfad auf ihrem App-Host. Schlüssel gelten nicht über Umgebungen hinweg.
Authentifizierung
Jeder Request sendet Authorization: Bearer <secret>. Das Geheimnis entspricht ^tf_[A-Za-z0-9_-]+$. Session-Tokens haben ein anderes Präfix und werden abgelehnt.
Keine Cookies. Kein CSRF-Token. tenant_id nicht mitsenden.
Fehlender, unbekannter, widerrufener oder abgelaufener Schlüssel: 401 und { "error": "invalid or expired token" }. Der Body ist in allen vier Fällen identisch.
Ein gesperrter Mandant liefert auf jeder Route 403.
Header
Authorization: Bearer tf_…
API-Schlüssel
Ein Admin legt sie unter Einstellungen → Organisation an. Felder: name (string), scopes (nichtleere Teilmenge der Liste unten), expires_at (never, oder ein Datum in der Zukunft, höchstens 366 Tage nach dem Anlegen). never gilt bis zum Widerruf.
Das Geheimnis ist tf_ plus 32 CSPRNG-Bytes, base64url, einmal sichtbar. Gespeichert wird SHA-256 des gesamten Geheimnisses. Die Liste zeigt key_prefix, die ersten 11 Zeichen.
name ist per PATCH änderbar. scopes und expires_at sind unveränderlich. Rotation heisst anlegen, dann widerrufen. Widerruf setzt revoked_at und behält die Zeile. Limit: 20 nicht widerrufene Schlüssel pro Mandant.
employees:readGET /employees, GET /employees/{id}
schedules:readGET /schedules
days:readGET /days
entries:readGET /entries
projects:readGET /customers, GET /projects, GET /project-hours
leave:readGET /leave-requests
Konventionen
Erfolgs-Bodies sind JSON. Listen nutzen den Envelope unten. Eine einzelne Ressource ist { "data": { } } ohne next_cursor. Fehler sind { "error": string } mit Content-Type: application/json.
date ist YYYY-MM-DD. time ist eine lokale Uhrzeit HH:MM:SS an diesem date. timestamp ist RFC3339 UTC. uuid ist die Troop-Id. Nullable Felder sind JSON null.
Zeilen mit is_invisible = true fehlen. GET /employees/{id} für eine unsichtbare oder unbekannte Id oder E-Mail ist 404.
Rate-Limit: 120 Requests pro Minute und Schlüssel. Darüber 429 mit Retry-After in Sekunden. Fehlgeschlagene Authentifizierung: 30 Requests pro Minute und IP.
Ist die Zeitfreigabe aktiv, liefern /days, /entries und /project-hours standardmässig nur freigegebene Zeilen (status=approved). status=all nimmt nicht freigegebene Zeilen dazu und setzt approved auf Tag- und Eintragsobjekten. Ist die Freigabe aus, wird status ignoriert und approved weggelassen.
Parameter
FeldInTypBeschreibung
limitqueryintegeroptionalSeitengrösse. Standard 100. Maximum 500.
cursorquerystringoptionalOpaker Cursor aus dem vorigen next_cursor. Auf der ersten Seite weglassen.
Envelope
{
"data": [],
"next_cursor": null
}
Listen-Envelope
FeldTypBeschreibung
dataarraySeite von Ressourcenobjekten.
next_cursorstring | nullAls cursor für die nächste Seite. null auf der letzten Seite.
400Ungültiger Query: falsches Datum, from nach to, oder ein Fenster über 92 Tage.
401invalid or expired token. Fehlende, unbekannte, widerrufene und abgelaufene Schlüssel.
403Scope fehlt, oder der Mandant ist gesperrt. /customers, /projects und /project-hours liefern zusätzlich { "error": "PROJECT_TIME_DISABLED" }, wenn die Projektzeit aus ist.
404Kein Mitarbeiter mit dieser Id oder E-Mail im Mandanten, unsichtbare Nutzer eingeschlossen.
429Rate-Limit. Retry-After beachten.
Employees
Sichtbare Profile des Mandanten. personnel_number ist die Personalnummer im Profil (employee_id), nicht id.
GET/employees
Scopeemployees:read
Antwort 200application/json
Mitarbeiter listen. Unsichtbare Nutzer fehlen.
Parameter
FeldInTypBeschreibung
limitqueryintegeroptionalSeitengrösse. Standard 100. Maximum 500.
cursorquerystringoptionalOpaker Cursor aus dem vorigen next_cursor. Auf der ersten Seite weglassen.
personnel_numberstring | nullPersonalnummer. null, wenn leer.
namestringAnzeigename.
startdateAbschnittsanfang, inklusiv.
enddate | nullAbschnittsende, inklusiv. null, wenn der Abschnitt offen ist.
plan_namestring | nullName des Quellplans. null, wenn nicht aus einem Plan erzeugt.
weekly_hoursnumberSollstunden pro Woche.
work_percentagenumberPensum in Prozent. 100 ist Vollzeit.
daysobject[]Wochentagszeilen. Länge immer 7.
days[].day_of_weekintegerISO-Wochentag. 1 Montag bis 7 Sonntag.
days[].target_hoursnumberSollstunden dieses Wochentags.
days[].is_working_daybooleantrue, wenn der Wochentag ein Arbeitstag ist.
Days
Ein Objekt je gespeicherter day_status-Zeile im Fenster. daily_overtime ist actual_hours - target_hours dieses Datums. Freigabe wie unter Konventionen.
employee.personnel_numberstring | nullPersonalnummer. null, wenn leer.
employee.namestringAnzeigename.
target_hoursnumberSollstunden.
actual_hoursnumberErfasste Stunden.
daily_overtimenumberactual_hours - target_hours.
is_working_daybooleanAus dem aufgelösten Plan.
holiday_namestring | nullFeiertagsname. null, wenn keiner.
leave_type_namestring | nullName der Abwesenheitsart. null, wenn keine.
approvedbooleanWeggelassen, wenn die Zeitfreigabe aus ist. Sonst true, wenn das Datum in einer freigegebenen Periode liegt.
Entries
Intervalle aus time_entries. type ist work, break, sick oder vacation. Bei break sind project_id, project_name und customer_namenull. Freigabe wie unter Konventionen.
GET/entries
Scopeentries:read
Antwort 200application/json
Zeiteinträge listen. updated_since für inkrementellen Sync.
employee.personnel_numberstring | nullPersonalnummer. null, wenn leer.
employee.namestringAnzeigename.
datedateKalenderdatum des Intervalls.
starttimeLokaler Start an date.
endtimeLokales Ende an date.
hoursnumberDauer in Dezimalstunden.
typestringwork, break, sick oder vacation.
descriptionstring | nullFreitext. null, wenn leer.
tracking_labelstring | nullTimer-Label. null, wenn leer. Nicht in den Projektstunden.
project_iduuid | nullProjekt-Id. null, wenn nicht zugeordnet.
project_namestring | nullProjektname. null, wenn nicht zugeordnet.
customer_namestring | nullKundenname. null, wenn nicht zugeordnet oder das Projekt keinen Kunden hat.
updated_attimestampLetzter Schreibzeitpunkt. Filterbar mit updated_since.
Abwesenheitsanträge
Zeilen aus leave_requests, deren Daten das Fenster überlappen, auch wenn der Antrag vor from begonnen hat. Standard status=approved liefert approved und auto_approved. status=all liefert jeden Status. Dieser Filter folgt nicht der Zeitfreigabe.
status ist pending, submitted, approved, auto_approved, rejected oder cancelled. half_day_period ist morning oder afternoon und fehlt sonst. hours_per_day und reason fehlen, wenn sie leer sind.
GET/leave-requests
Scopeleave:read
Antwort 200application/json
Abwesenheitsanträge listen, die das Fenster überlappen.
customer_iduuid | nullKunden-Id. null, wenn keiner.
customer_namestring | nullKundenname. null, wenn keiner.
Project hours
Arbeitsstunden im Fenster, gruppiert nach Projekt, dann nach Mitarbeiter. Nicht zugeordnete Arbeit ist eine Zeile mit project_id: null. Dieselbe Freigabe und derselbe Fehler PROJECT_TIME_DISABLED wie bei Kunden und Projekten.