HTTP

Nur GET

API-Referenz

JSON über HTTPS. Jede Route ist `GET`. Authentifizierung mit Bearer-Token. Der Mandant kommt aus dem Schlüssel, nie aus dem Query-String.

Basis-URL https://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:read GET /employees, GET /employees/{id}
  • schedules:read GET /schedules
  • days:read GET /days
  • entries:read GET /entries
  • projects:read GET /customers, GET /projects, GET /project-hours
  • leave:read GET /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

  • Feld In Typ Beschreibung
  • limit query integer optional Seitengrösse. Standard 100. Maximum 500.
  • cursor query string optional Opaker Cursor aus dem vorigen next_cursor. Auf der ersten Seite weglassen.
Envelope
{
  "data": [],
  "next_cursor": null
}

Listen-Envelope

  • Feld Typ Beschreibung
  • data array Seite von Ressourcenobjekten.
  • next_cursor string | null Als cursor für die nächste Seite. null auf der letzten Seite.
  • 400 Ungültiger Query: falsches Datum, from nach to, oder ein Fenster über 92 Tage.
  • 401 invalid or expired token. Fehlende, unbekannte, widerrufene und abgelaufene Schlüssel.
  • 403 Scope fehlt, oder der Mandant ist gesperrt. /customers, /projects und /project-hours liefern zusätzlich { "error": "PROJECT_TIME_DISABLED" }, wenn die Projektzeit aus ist.
  • 404 Kein Mitarbeiter mit dieser Id oder E-Mail im Mandanten, unsichtbare Nutzer eingeschlossen.
  • 429 Rate-Limit. Retry-After beachten.

Employees

Sichtbare Profile des Mandanten. personnel_number ist die Personalnummer im Profil (employee_id), nicht id.

GET /employees

Scope employees:read

Antwort 200 application/json

Mitarbeiter listen. Unsichtbare Nutzer fehlen.

Parameter

  • Feld In Typ Beschreibung
  • limit query integer optional Seitengrösse. Standard 100. Maximum 500.
  • cursor query string optional Opaker Cursor aus dem vorigen next_cursor. Auf der ersten Seite weglassen.
Request
curl "https://app.troop.ch/api/external/v1/employees" \
  -H "Authorization: Bearer tf_…"
Response
{
  "data": [
    {
      "id": "8f3c1a2e-4b5d-4e6f-9a70-1c2d3e4f5a6b",
      "personnel_number": "1042",
      "name": "Anna Meier",
      "email": "anna.meier@example.ch",
      "location": "Zürich",
      "start_date": "2022-03-01",
      "leave_date": null
    }
  ],
  "next_cursor": null
}

GET /employees/{id}

Scope employees:read

Antwort 200 application/json

Einen Mitarbeiter per Id oder E-Mail lesen. 404, wenn keines passt oder der Nutzer unsichtbar ist.

Parameter

  • Feld In Typ Beschreibung
  • id path uuid | email pflicht User-Id oder E-Mail-Adresse.
Request
curl "https://app.troop.ch/api/external/v1/employees/8f3c1a2e-4b5d-4e6f-9a70-1c2d3e4f5a6b" \
  -H "Authorization: Bearer tf_…"
Response
{
  "data": {
    "id": "8f3c1a2e-4b5d-4e6f-9a70-1c2d3e4f5a6b",
    "personnel_number": "1042",
    "name": "Anna Meier",
    "email": "anna.meier@example.ch",
    "location": "Zürich",
    "start_date": "2022-03-01",
    "leave_date": null
  }
}

Employee

  • Feld Typ Beschreibung
  • id uuid User-Id.
  • personnel_number string | null Personalnummer. null, wenn leer.
  • name string Anzeigename.
  • email string E-Mail.
  • location string | null Standortname. null, wenn keiner.
  • start_date date | null Eintritt.
  • leave_date date | null Austritt. null, solange angestellt.

Schedules

Anstellungsabschnitte, die [from, to] schneiden. Das ist der aufgelöste Arbeitsplan, nicht der Plan-Katalog. days hat immer sieben Zeilen.

GET /schedules

Scope schedules:read

Antwort 200 application/json

Abschnitte listen, die das Fenster schneiden.

Parameter

  • Feld In Typ Beschreibung
  • from query date pflicht Inklusiver Anfang. YYYY-MM-DD.
  • to query date pflicht Inklusives Ende. YYYY-MM-DD. Nicht vor from. Maximal 92 Tage.
  • user_id query uuid optional Nur dieser User.
  • email query string optional Dieselbe Person wie user_id. Gross- und Kleinschreibung egal.
  • limit query integer optional Seitengrösse. Standard 100. Maximum 500.
  • cursor query string optional Opaker Cursor aus dem vorigen next_cursor. Auf der ersten Seite weglassen.
Request
curl "https://app.troop.ch/api/external/v1/schedules?from=2026-03-01&to=2026-03-31" \
  -H "Authorization: Bearer tf_…"
Response
{
  "data": [
    {
      "user_id": "8f3c1a2e-4b5d-4e6f-9a70-1c2d3e4f5a6b",
      "email": "anna.meier@example.ch",
      "personnel_number": "1042",
      "name": "Anna Meier",
      "start": "2026-01-01",
      "end": null,
      "plan_name": "Vollzeit",
      "weekly_hours": 42,
      "work_percentage": 100,
      "days": [
        { "day_of_week": 1, "target_hours": 8.4, "is_working_day": true },
        { "day_of_week": 2, "target_hours": 8.4, "is_working_day": true },
        { "day_of_week": 3, "target_hours": 8.4, "is_working_day": true },
        { "day_of_week": 4, "target_hours": 8.4, "is_working_day": true },
        { "day_of_week": 5, "target_hours": 8.4, "is_working_day": true },
        { "day_of_week": 6, "target_hours": 0, "is_working_day": false },
        { "day_of_week": 7, "target_hours": 0, "is_working_day": false }
      ]
    }
  ],
  "next_cursor": null
}

Schedule

  • Feld Typ Beschreibung
  • user_id uuid Gleicher Wert wie employees.id.
  • email string E-Mail-Adresse.
  • personnel_number string | null Personalnummer. null, wenn leer.
  • name string Anzeigename.
  • start date Abschnittsanfang, inklusiv.
  • end date | null Abschnittsende, inklusiv. null, wenn der Abschnitt offen ist.
  • plan_name string | null Name des Quellplans. null, wenn nicht aus einem Plan erzeugt.
  • weekly_hours number Sollstunden pro Woche.
  • work_percentage number Pensum in Prozent. 100 ist Vollzeit.
  • days object[] Wochentagszeilen. Länge immer 7.
  • days[].day_of_week integer ISO-Wochentag. 1 Montag bis 7 Sonntag.
  • days[].target_hours number Sollstunden dieses Wochentags.
  • days[].is_working_day boolean true, 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.

GET /days

Scope days:read

Antwort 200 application/json

Gespeicherte Tage listen.

Parameter

  • Feld In Typ Beschreibung
  • from query date pflicht Inklusiver Anfang. YYYY-MM-DD.
  • to query date pflicht Inklusives Ende. YYYY-MM-DD. Nicht vor from. Maximal 92 Tage.
  • user_id query uuid optional Nur dieser User.
  • email query string optional Dieselbe Person wie user_id. Gross- und Kleinschreibung egal.
  • personnel_number query string optional Exakte Personalnummer.
  • status query string optional approved oder all. Siehe Konventionen.
  • limit query integer optional Seitengrösse. Standard 100. Maximum 500.
  • cursor query string optional Opaker Cursor aus dem vorigen next_cursor. Auf der ersten Seite weglassen.
Request
curl "https://app.troop.ch/api/external/v1/days?from=2026-03-01&to=2026-03-31" \
  -H "Authorization: Bearer tf_…"
Response
{
  "data": [
    {
      "date": "2026-03-02",
      "employee": {
        "id": "8f3c1a2e-4b5d-4e6f-9a70-1c2d3e4f5a6b",
        "personnel_number": "1042",
        "name": "Anna Meier"
      },
      "email": "anna.meier@example.ch",
      "target_hours": 8.4,
      "actual_hours": 9.1,
      "daily_overtime": 0.7,
      "is_working_day": true,
      "holiday_name": null,
      "leave_type_name": null,
      "approved": true
    }
  ],
  "next_cursor": null
}

Day

  • Feld Typ Beschreibung
  • date date Kalenderdatum.
  • employee.id uuid User-Id.
  • email string E-Mail-Adresse.
  • employee.personnel_number string | null Personalnummer. null, wenn leer.
  • employee.name string Anzeigename.
  • target_hours number Sollstunden.
  • actual_hours number Erfasste Stunden.
  • daily_overtime number actual_hours - target_hours.
  • is_working_day boolean Aus dem aufgelösten Plan.
  • holiday_name string | null Feiertagsname. null, wenn keiner.
  • leave_type_name string | null Name der Abwesenheitsart. null, wenn keine.
  • approved boolean Weggelassen, 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_name null. Freigabe wie unter Konventionen.

GET /entries

Scope entries:read

Antwort 200 application/json

Zeiteinträge listen. updated_since für inkrementellen Sync.

Parameter

  • Feld In Typ Beschreibung
  • from query date pflicht Inklusiver Anfang. YYYY-MM-DD.
  • to query date pflicht Inklusives Ende. YYYY-MM-DD. Nicht vor from. Maximal 92 Tage.
  • user_id query uuid optional Nur dieser User.
  • email query string optional Dieselbe Person wie user_id. Gross- und Kleinschreibung egal.
  • personnel_number query string optional Exakte Personalnummer.
  • project_id query uuid optional Nur dieses Projekt.
  • type query string optional work, break, sick oder vacation.
  • status query string optional approved oder all. Siehe Konventionen.
  • updated_since query timestamp optional Zeilen mit updated_at grösser oder gleich diesem Zeitpunkt.
  • limit query integer optional Seitengrösse. Standard 100. Maximum 500.
  • cursor query string optional Opaker Cursor aus dem vorigen next_cursor. Auf der ersten Seite weglassen.
Request
curl "https://app.troop.ch/api/external/v1/entries?from=2026-03-01&to=2026-03-31&updated_since=2026-03-01T00:00:00Z" \
  -H "Authorization: Bearer tf_…"
Response
{
  "data": [
    {
      "id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890",
      "employee": {
        "id": "8f3c1a2e-4b5d-4e6f-9a70-1c2d3e4f5a6b",
        "personnel_number": "1042",
        "name": "Anna Meier"
      },
      "email": "anna.meier@example.ch",
      "date": "2026-03-02",
      "start": "08:00:00",
      "end": "12:15:00",
      "hours": 4.25,
      "type": "work",
      "description": "Baustelle",
      "tracking_label": null,
      "project_id": "c0ffee00-0000-4000-8000-000000000001",
      "project_name": "Umbau",
      "customer_name": "Muster AG",
      "updated_at": "2026-03-02T16:40:12Z"
    }
  ],
  "next_cursor": null
}

Entry

  • Feld Typ Beschreibung
  • id uuid Eintrags-Id.
  • employee.id uuid User-Id.
  • email string E-Mail-Adresse.
  • employee.personnel_number string | null Personalnummer. null, wenn leer.
  • employee.name string Anzeigename.
  • date date Kalenderdatum des Intervalls.
  • start time Lokaler Start an date.
  • end time Lokales Ende an date.
  • hours number Dauer in Dezimalstunden.
  • type string work, break, sick oder vacation.
  • description string | null Freitext. null, wenn leer.
  • tracking_label string | null Timer-Label. null, wenn leer. Nicht in den Projektstunden.
  • project_id uuid | null Projekt-Id. null, wenn nicht zugeordnet.
  • project_name string | null Projektname. null, wenn nicht zugeordnet.
  • customer_name string | null Kundenname. null, wenn nicht zugeordnet oder das Projekt keinen Kunden hat.
  • updated_at timestamp Letzter 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

Scope leave:read

Antwort 200 application/json

Abwesenheitsanträge listen, die das Fenster überlappen.

Parameter

  • Feld In Typ Beschreibung
  • from query date pflicht Inklusiver Anfang. YYYY-MM-DD.
  • to query date pflicht Inklusives Ende. YYYY-MM-DD. Nicht vor from. Maximal 92 Tage.
  • user_id query uuid optional Nur dieser User.
  • email query string optional Dieselbe Person wie user_id. Gross- und Kleinschreibung egal.
  • personnel_number query string optional Exakte Personalnummer.
  • status query string optional approved oder all. approved schliesst auto_approved ein.
  • updated_since query timestamp optional Zeilen mit updated_at grösser oder gleich diesem Zeitpunkt.
  • limit query integer optional Seitengrösse. Standard 100. Maximum 500.
  • cursor query string optional Opaker Cursor aus dem vorigen next_cursor. Auf der ersten Seite weglassen.
Request
curl "https://app.troop.ch/api/external/v1/leave-requests?from=2026-03-01&to=2026-03-31" \
  -H "Authorization: Bearer tf_…"
Response
{
  "data": [
    {
      "id": "a1b2c3d4-e5f6-4789-a012-3456789abcde",
      "user_id": "8f3c1a2e-4b5d-4e6f-9a70-1c2d3e4f5a6b",
      "email": "anna.meier@example.ch",
      "personnel_number": "1042",
      "name": "Anna Meier",
      "leave_type_id": "d4e5f6a7-b8c9-4012-8345-6789abcdef01",
      "leave_type_name": "Urlaub",
      "start_date": "2026-03-10",
      "end_date": "2026-03-12",
      "days_count": 3,
      "status": "approved",
      "is_half_day": false,
      "reason": "Family",
      "updated_at": "2026-03-01T08:15:00Z"
    }
  ],
  "next_cursor": null
}

Abwesenheitsantrag

  • Feld Typ Beschreibung
  • id uuid Antrags-Id.
  • user_id uuid User-Id. Dieselbe wie employees.id.
  • email string E-Mail-Adresse.
  • personnel_number string | null Personalnummer. null, wenn leer.
  • name string Anzeigename.
  • leave_type_id uuid Abwesenheitsart-Id.
  • leave_type_name string Name der Abwesenheitsart.
  • start_date date Erster Tag des Antrags.
  • end_date date Letzter Tag des Antrags.
  • days_count number Dauer in Tagen.
  • status string pending, submitted, approved, auto_approved, rejected oder cancelled.
  • is_half_day boolean True bei einem halben Tag.
  • half_day_period string | null morning oder afternoon. Fehlt sonst.
  • hours_per_day number | null Stunden pro Tag. Fehlt, wenn der Antrag in Tagen zählt.
  • reason string | null Notiz am Antrag. Fehlt, wenn leer.
  • updated_at timestamp Letzter Schreibzeitpunkt. Filterbar mit updated_since.

Customers und Projects

Beide Routen verlangen projects:read und liefern 403 { "error": "PROJECT_TIME_DISABLED" }, wenn die Projektzeiterfassung aus ist.

GET /customers

Scope projects:read

Antwort 200 application/json

Kunden listen.

Parameter

  • Feld In Typ Beschreibung
  • limit query integer optional Seitengrösse. Standard 100. Maximum 500.
  • cursor query string optional Opaker Cursor aus dem vorigen next_cursor. Auf der ersten Seite weglassen.
Request
curl "https://app.troop.ch/api/external/v1/customers" \
  -H "Authorization: Bearer tf_…"
Response
{
  "data": [
    {
      "id": "11111111-2222-4333-8444-555555555555",
      "name": "Muster AG",
      "active": true
    }
  ],
  "next_cursor": null
}

GET /projects

Scope projects:read

Antwort 200 application/json

Projekte listen.

Parameter

  • Feld In Typ Beschreibung
  • limit query integer optional Seitengrösse. Standard 100. Maximum 500.
  • cursor query string optional Opaker Cursor aus dem vorigen next_cursor. Auf der ersten Seite weglassen.
Request
curl "https://app.troop.ch/api/external/v1/projects" \
  -H "Authorization: Bearer tf_…"
Response
{
  "data": [
    {
      "id": "c0ffee00-0000-4000-8000-000000000001",
      "name": "Umbau",
      "active": true,
      "customer_id": "11111111-2222-4333-8444-555555555555",
      "customer_name": "Muster AG"
    }
  ],
  "next_cursor": null
}

Customer

  • Feld Typ Beschreibung
  • id uuid Kunden-Id.
  • name string Name.
  • active boolean false, wenn deaktiviert.

Project

  • Feld Typ Beschreibung
  • id uuid Projekt-Id.
  • name string Name.
  • active boolean false, wenn deaktiviert.
  • customer_id uuid | null Kunden-Id. null, wenn keiner.
  • customer_name string | null Kundenname. 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.

GET /project-hours

Scope projects:read

Antwort 200 application/json

Arbeitsstunden aggregieren.

Parameter

  • Feld In Typ Beschreibung
  • from query date pflicht Inklusiver Anfang. YYYY-MM-DD.
  • to query date pflicht Inklusives Ende. YYYY-MM-DD. Nicht vor from. Maximal 92 Tage.
  • project_id query uuid optional Nur dieses Projekt.
  • user_id query uuid optional Nur dieser User.
  • email query string optional Dieselbe Person wie user_id. Gross- und Kleinschreibung egal.
  • status query string optional approved oder all. Siehe Konventionen.
  • limit query integer optional Seitengrösse. Standard 100. Maximum 500.
  • cursor query string optional Opaker Cursor aus dem vorigen next_cursor. Auf der ersten Seite weglassen.
Request
curl "https://app.troop.ch/api/external/v1/project-hours?from=2026-03-01&to=2026-03-31" \
  -H "Authorization: Bearer tf_…"
Response
{
  "data": [
    {
      "project_id": "c0ffee00-0000-4000-8000-000000000001",
      "project_name": "Umbau",
      "customer_name": "Muster AG",
      "hours": 12.5,
      "employees": [
        {
          "id": "8f3c1a2e-4b5d-4e6f-9a70-1c2d3e4f5a6b",
          "email": "anna.meier@example.ch",
          "personnel_number": "1042",
          "name": "Anna Meier",
          "hours": 8.5
        }
      ]
    }
  ],
  "next_cursor": null
}

Project hours

  • Feld Typ Beschreibung
  • project_id uuid | null null für Zeit ohne Projekt.
  • project_name string | null null, wenn project_id null ist.
  • customer_name string | null null, wenn das Projekt keinen Kunden hat.
  • hours number Summe der Arbeitsstunden im Fenster.
  • employees object[] Mitarbeiter mit Zeit auf diesem Projekt.
  • employees[].id uuid User-Id.
  • employees[].email string E-Mail-Adresse.
  • employees[].personnel_number string | null Personalnummer. null, wenn leer.
  • employees[].name string Anzeigename.
  • employees[].hours number Stunden dieses Mitarbeiters.