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-URL | https://api.timecapt.de |
| Format | JSON (Standard). XML über das Suffix .xml (z. B. /customers.xml) oder den Header Accept: application/xml. |
| Zeichensatz | UTF-8 |
| Zeitstempel | created_at, updated_at usw. als Unix-Timestamps (Sekunden). Datumsfelder wie date_at als YYYY-MM-DD. |
| Geldbeträge | Stundensätze, Budgets und Umsätze immer in Cent (Integer) — keine Rundungsfehler. |
| Löschverhalten | Kunden, 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/login | Anmelden; subdomain optional als Mandanten-Hinweis |
| POST | /v1/auth/refresh | Neues Token-Paar gegen gültiges Refresh-Token |
| POST | /v1/auth/logout | Refresh-Token widerrufen |
| GET | /v1/auth/me | Aktueller 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_key | Eigenen Schlüssel lesen (null, wenn keiner existiert) |
| POST | /v1/myself/api_key | Schlüssel (neu) erzeugen — ein bestehender wird ersetzt |
| DELETE | /v1/myself/api_key | Schlü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/customers | Liste (Parameter: archived=true, limit, offset) |
| POST | /v1/customers | Anlegen |
| GET | /v1/customers/{id} | Einzeln lesen |
| PUT/PATCH | /v1/customers/{id} | Ändern (Teil-Updates erlaubt) |
| DELETE | /v1/customers/{id} | Archivieren |
Felder
| Ressource | Wichtige Felder |
customers | name, note, hourly_rate (Cent) |
projects | name, customer_id, note, hourly_rate, budget, budget_type (minutes | minutes_per_month | cents | cents_per_month) |
services | name, 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_entries | Liste mit Filtern (siehe unten) |
| POST | /v1/time_entries | Anlegen |
| GET | /v1/time_entries/{id} | Einzeln lesen |
| PUT/PATCH | /v1/time_entries/{id} | Ändern |
| DELETE | /v1/time_entries/{id} | Löschen |
Filter (GET-Parameter)
| Parameter | Bedeutung |
from / to | Zeitraum (YYYY-MM-DD, einschließlich) |
user_id | Nur Einträge dieses Benutzers (nur Inhaber/Admin) |
project_id, customer_id, service_id | Nach Projekt / Kunde / Leistung filtern |
billable | true / false |
limit, offset | Paginierung (Standard 100, max. 1000) |
Felder beim Anlegen/Ändern
date_at | YYYY-MM-DD (Standard: heute) |
minutes | Dauer in Minuten (0–1440) |
project_id, service_id | optional — Einträge ohne Projekt/Leistung sind erlaubt |
note | Freitext (max. 2000 Zeichen) |
billable | bool, 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/tracker | Laufender 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/users | Teamliste (Parameter archived=true für Archivierte) |
| POST | /v1/users | Anlegen — 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
| Parameter | Bedeutung |
from / to | Zeitraum (Standard: aktueller Monat) |
group_by | project (Standard) | customer | service | user |
user_id | optional, nur Inhaber/Admin |
billable | true / 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/dashboard | KPIs (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/profitability | Effektiver Stundensatz je customer/project (Umsatz ÷ alle Stunden); nur Inhaber/Admin |
| GET | /v1/reports/utilization | Soll/Ist je Benutzer (Wochensoll weekly_minutes anteilig auf Werktage); nur Inhaber/Admin |
| GET | /v1/reports/anomalies | Auffälligkeiten: Einträge ≥ 12 h, 0-Minuten-Einträge, Werktage ohne Eintrag, Timer > 12 h |
| GET | /v1/reports/unbilled | Offene abrechenbare Leistungen je Kunde (billable, nicht gesperrt); nur Inhaber/Admin |
| POST | /v1/reports/unbilled/lock | Einträge eines Kunden als abgerechnet sperren; Body: {"customer_id": 1, "to": "2026-06-30"} |
| GET | /v1/reports/budgets | Budget-Verbrauch je Projekt inkl. pct und forecast_date (lineare Hochrechnung) |
| GET | /v1/reports/team-live | Aktuell laufende Timer aller Mitarbeitenden; nur Inhaber/Admin |
| GET | /v1/reports/timesheet.pdf | Tätigkeitsnachweis als PDF; Parameter from, to, optional customer_id, project_id, user_id |
Passwort-Reset
| POST | /v1/auth/password/forgot | Body: {"email": "...", "subdomain": "..."} — antwortet immer {"ok": true}; existiert der Account, kommt eine Mail mit Reset-Link (60 Min. gültig) |
| POST | /v1/auth/password/reset | Body: {"token": "...", "password": "..."} — setzt das Passwort und widerruft alle Sessions |
Account & Profil
| GET | /v1/account | Firmendaten (Name, Subdomain, Währung, Zeitzone, Custom Domain, Wochenreport-Flag, …) |
| PATCH | /v1/account | Firmendaten ändern (Inhaber/Admin); custom_domain nur Inhaber, weekly_report_email (bool) aktiviert den Montags-Report |
| GET | /v1/myself | Eigenes Profil |
| PATCH | /v1/myself | Profil ändern; Passwortwechsel mit current_password + password (widerruft alle Sessions) |
Auftragsverarbeitung (AV-Vertrag)
| GET | /v1/account/dpa | Status: { concluded, template_version, current_version, concluded_at, pdf_url, … } |
| GET | /v1/account/dpa/preview | Vertrags-HTML mit eingesetzten (noch unverbindlichen) Daten; Parameter company_name, represented_by, address |
| POST | /v1/account/dpa | Verbindlich abschließen (Inhaber/Admin); Body: company_name, represented_by, address; 409 wenn aktuelle Version schon abgeschlossen |
| GET | /v1/account/dpa/{id}.pdf | PDF-Download (nur eigener Tenant) |
Fehler
Fehler kommen als JSON mit HTTP-Statuscode und Meldung, z. B. {"error": "Field 'name' is required."}:
400 | Fehlerhafte Anfrage |
401 | Nicht authentifiziert / Token abgelaufen |
403 | Keine Berechtigung (z. B. fremder Zeiteintrag, falsche Rolle) |
404 | Nicht gefunden |
409 | Konflikt (z. B. E-Mail bereits vergeben) |
422 | Validierungsfehler (Details im Feld details) |
429 | Zu viele Fehlversuche — später erneut versuchen |
500 | Serverfehler |
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"