timeCapt

API-Dokumentation

timeCapt bietet eine REST-API für eigene Skripte, Tools und Integrationen. Alle Funktionen der Oberfläche stehen auch über die API zur Verfügung.

Grundlagen

Basis-URLhttps://api.timecapt.de
FormatJSON (Standard). XML über das Suffix .xml (z. B. /customers.xml) oder den Header Accept: application/xml.
ZeichensatzUTF-8
Zeitstempelcreated_at, updated_at usw. als Unix-Timestamps (Sekunden). Datumsfelder wie date_at als YYYY-MM-DD.
GeldbeträgeStundensätze, Budgets und Umsätze immer in Cent (Integer) — keine Rundungsfehler.
LöschverhaltenKunden, Projekte, Leistungen und Benutzer werden archiviert (Soft-Delete, Feld archived). Zeiteinträge werden hart gelöscht.

Antworten sind in benannte Objekte gewrappt: Listen als {"customers": [...]}, Einzelobjekte als {"customer": {...}}. Schreib-Bodies werden flach ({"name": "..."}) oder gewrappt ({"customer": {"name": "..."}}) akzeptiert.

Authentifizierung

1. JWT (Login mit E-Mail/Passwort)

Login liefert ein kurzlebiges Access-Token (Bearer) und ein Refresh-Token. Das Access-Token wird bei jedem Request im Authorization-Header mitgeschickt.

POST https://api.timecapt.de/v1/auth/login
Content-Type: application/json

{ "email": "name@firma.de", "password": "geheim", "subdomain": "deinefirma" }

→ { "access_token": "…", "refresh_token": "…", "token_type": "Bearer",
    "expires_in": 900, "user": { … } }

Danach: Authorization: Bearer <access_token>. Abgelaufene Access-Tokens werden über POST /v1/auth/refresh mit {"refresh_token": "…"} erneuert (Token-Rotation: das alte Refresh-Token wird ungültig).

POST/v1/auth/loginAnmelden; subdomain optional als Mandanten-Hinweis
POST/v1/auth/refreshNeues Token-Paar gegen gültiges Refresh-Token
POST/v1/auth/logoutRefresh-Token widerrufen
GET/v1/auth/meAktueller Benutzer + Mandant

2. API-Schlüssel

Alternativ authentifizieren Skripte sich mit einem persönlichen API-Schlüssel im Header X-TimeCaptApiKey — ohne Login-Flow und Token-Verwaltung.

GET https://api.timecapt.de/v1/time_entries?from=2026-06-01&to=2026-06-30
X-TimeCaptApiKey: <dein-api-schluessel>

Deinen persönlichen Schlüssel verwaltest du in der App unter Einstellungen → API-Schlüssel (erzeugen, anzeigen, neu erzeugen, widerrufen) — oder direkt per API:

GET/v1/myself/api_keyEigenen Schlüssel lesen (null, wenn keiner existiert)
POST/v1/myself/api_keySchlüssel (neu) erzeugen — ein bestehender wird ersetzt
DELETE/v1/myself/api_keySchlüssel widerrufen
Aus Sicherheitsgründen erfordert die Schlüssel-Verwaltung eine Anmeldung per Bearer-Token — ein API-Schlüssel kann sich nicht selbst rotieren oder widerrufen.

Stammdaten

Alle Stammdaten-Ressourcen folgen demselben CRUD-Muster (hier am Beispiel customers; identisch für projects und services):

GET/v1/customersListe (Parameter: archived=true, limit, offset)
POST/v1/customersAnlegen
GET/v1/customers/{id}Einzeln lesen
PUT/PATCH/v1/customers/{id}Ändern (Teil-Updates erlaubt)
DELETE/v1/customers/{id}Archivieren

Felder

RessourceWichtige Felder
customersname, note, hourly_rate (Cent)
projectsname, customer_id, note, hourly_rate, budget, budget_type (minutes | minutes_per_month | cents | cents_per_month)
servicesname, note, hourly_rate, billable (bool)
Stundensatz-Hierarchie: Beim Berechnen des Umsatzes eines Zeiteintrags gilt: Projektsatz → sonst Leistungssatz → sonst Kundensatz. Der verwendete Satz wird je Eintrag als Snapshot gespeichert.

Zeiteinträge

GET/v1/time_entriesListe mit Filtern (siehe unten)
POST/v1/time_entriesAnlegen
GET/v1/time_entries/{id}Einzeln lesen
PUT/PATCH/v1/time_entries/{id}Ändern
DELETE/v1/time_entries/{id}Löschen

Filter (GET-Parameter)

ParameterBedeutung
from / toZeitraum (YYYY-MM-DD, einschließlich)
user_idNur Einträge dieses Benutzers (nur Inhaber/Admin)
project_id, customer_id, service_idNach Projekt / Kunde / Leistung filtern
billabletrue / false
limit, offsetPaginierung (Standard 100, max. 1000)

Felder beim Anlegen/Ändern

date_atYYYY-MM-DD (Standard: heute)
minutesDauer in Minuten (0–1440)
project_id, service_idoptional — Einträge ohne Projekt/Leistung sind erlaubt
noteFreitext (max. 2000 Zeichen)
billablebool, Standard true

Die Antwort enthält zusätzlich berechnete Felder: revenue (Cent), hourly_rate (Satz-Snapshot) sowie die Namen project_name, customer_name, service_name, user_name in Listen.

POST /v1/time_entries
Authorization: Bearer <token>

{ "date_at": "2026-06-07", "minutes": 90, "project_id": 3,
  "service_id": 1, "note": "Konzept", "billable": true }

Live-Timer (Tracker)

GET/v1/trackerLaufender Timer des Benutzers (oder null)
PUT/v1/tracker/{time_entry_id}Timer auf diesem Zeiteintrag starten
DELETE/v1/tracker/{time_entry_id}Timer stoppen — verstrichene Minuten werden dem Eintrag gutgeschrieben

Je Benutzer läuft maximal ein Timer; ein neuer Start ersetzt den laufenden.

Arbeitszeiten (daily_times)

Getrennt von den Projekt-Zeiteinträgen: die tägliche Gesamtarbeitszeit je Benutzer (Anwesenheit, gesetzeskonforme Arbeitszeiterfassung). Ein Datensatz pro Benutzer und Tag mit date_at, minutes, break_minutes, note. CRUD-Muster wie bei den Stammdaten unter /v1/daily_times.

Benutzer (Team)

GET/v1/usersTeamliste (Parameter archived=true für Archivierte)
POST/v1/usersAnlegen — name, email, password (min. 8), role
PUT/PATCH/v1/users/{id}Ändern inkl. Rolle und Passwort-Reset
DELETE/v1/users/{id}Archivieren (widerruft alle Sessions)

Rollen: owner (Inhaber, genau einer), admin, time_tracker (Zeiterfasser), coworker (lesend). Schreiboperationen erfordern Inhaber/Admin. Eigene Rolle nicht änderbar; der Inhaber kann nur sich selbst bearbeiten und ist nicht archivierbar.

Berichte

GET /v1/reports?from=2026-06-01&to=2026-06-30&group_by=project
ParameterBedeutung
from / toZeitraum (Standard: aktueller Monat)
group_byproject (Standard) | customer | service | user
user_idoptional, nur Inhaber/Admin
billabletrue / false

Antwort: report.groups[] mit minutes, billable_minutes, revenue (Cent) und entry_count je Gruppe plus report.totals. Mit compare=true kommen zusätzlich totals_prev der gleich langen Vorperiode. Zeiterfasser erhalten ausschließlich ihre eigenen Daten.

Weitere Auswertungen

GET/v1/reports/dashboardKPIs (heute/Woche/Monat/Jahr inkl. Vorperioden), Aktivität der letzten 365 Tage, Top-Projekte; Admins zusätzlich laufende Timer und Budget-Warnungen
GET/v1/reports/profitabilityEffektiver Stundensatz je customer/project (Umsatz ÷ alle Stunden); nur Inhaber/Admin
GET/v1/reports/utilizationSoll/Ist je Benutzer (Wochensoll weekly_minutes anteilig auf Werktage); nur Inhaber/Admin
GET/v1/reports/anomaliesAuffälligkeiten: Einträge ≥ 12 h, 0-Minuten-Einträge, Werktage ohne Eintrag, Timer > 12 h
GET/v1/reports/unbilledOffene abrechenbare Leistungen je Kunde (billable, nicht gesperrt); nur Inhaber/Admin
POST/v1/reports/unbilled/lockEinträge eines Kunden als abgerechnet sperren; Body: {"customer_id": 1, "to": "2026-06-30"}
GET/v1/reports/budgetsBudget-Verbrauch je Projekt inkl. pct und forecast_date (lineare Hochrechnung)
GET/v1/reports/team-liveAktuell laufende Timer aller Mitarbeitenden; nur Inhaber/Admin
GET/v1/reports/timesheet.pdfTätigkeitsnachweis als PDF; Parameter from, to, optional customer_id, project_id, user_id

Passwort-Reset

POST/v1/auth/password/forgotBody: {"email": "...", "subdomain": "..."} — antwortet immer {"ok": true}; existiert der Account, kommt eine Mail mit Reset-Link (60 Min. gültig)
POST/v1/auth/password/resetBody: {"token": "...", "password": "..."} — setzt das Passwort und widerruft alle Sessions

Account & Profil

GET/v1/accountFirmendaten (Name, Subdomain, Währung, Zeitzone, Custom Domain, Wochenreport-Flag, …)
PATCH/v1/accountFirmendaten ändern (Inhaber/Admin); custom_domain nur Inhaber, weekly_report_email (bool) aktiviert den Montags-Report
GET/v1/myselfEigenes Profil
PATCH/v1/myselfProfil ändern; Passwortwechsel mit current_password + password (widerruft alle Sessions)

Auftragsverarbeitung (AV-Vertrag)

GET/v1/account/dpaStatus: { concluded, template_version, current_version, concluded_at, pdf_url, … }
GET/v1/account/dpa/previewVertrags-HTML mit eingesetzten (noch unverbindlichen) Daten; Parameter company_name, represented_by, address
POST/v1/account/dpaVerbindlich abschließen (Inhaber/Admin); Body: company_name, represented_by, address; 409 wenn aktuelle Version schon abgeschlossen
GET/v1/account/dpa/{id}.pdfPDF-Download (nur eigener Tenant)

Fehler

Fehler kommen als JSON mit HTTP-Statuscode und Meldung, z. B. {"error": "Field 'name' is required."}:

400Fehlerhafte Anfrage
401Nicht authentifiziert / Token abgelaufen
403Keine Berechtigung (z. B. fremder Zeiteintrag, falsche Rolle)
404Nicht gefunden
409Konflikt (z. B. E-Mail bereits vergeben)
422Validierungsfehler (Details im Feld details)
429Zu viele Fehlversuche — später erneut versuchen
500Serverfehler

Komplettes Beispiel (curl)

# 1) Anmelden
curl -s https://api.timecapt.de/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"name@firma.de","password":"geheim","subdomain":"deinefirma"}'

# 2) Zeiteinträge des Monats abrufen
curl -s "https://api.timecapt.de/v1/time_entries?from=2026-06-01&to=2026-06-30" \
  -H "Authorization: Bearer $TOKEN"

# 3) Eintrag anlegen
curl -s -X POST https://api.timecapt.de/v1/time_entries \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"date_at":"2026-06-07","minutes":90,"note":"Konzept"}'

# 4) Bericht nach Kunde
curl -s "https://api.timecapt.de/v1/reports?group_by=customer" \
  -H "Authorization: Bearer $TOKEN"