Services API
Services (Leistungen) ermöglichen es Ihnen, Tätigkeitsarten für die Zeiterfassung zu definieren (z.B. "Entwicklung", "Beratung", "Design"). Jede Service kann optional einen Standard-Stundensatz, einen Verrechenbarkeits-Status und eine Zuordnung zu einer Leistungskategorie haben und wird auf Zeiteinträgen referenziert, um Tätigkeiten projekt- und kundenübergreifend zu kategorisieren.
Service-Objekt
{
"id": 12,
"name": "Entwicklung",
"hourly_rate": "150.00",
"billable": true,
"archived": false,
"service_category_id": 3,
"service_category": {
"id": 3,
"name": "Beratung",
"created_at": "2026-04-09T16:00:00.000000Z",
"updated_at": "2026-04-09T16:00:00.000000Z"
},
"created_at": "2026-04-07T09:30:00.000000Z",
"updated_at": "2026-04-09T16:05:00.000000Z"
}Attribute
| Feld | Typ | Beschreibung |
|---|---|---|
id | integer | Eindeutige Service-ID |
name | string | Name der Leistung (erforderlich, max. 255 Zeichen) |
hourly_rate | decimal|null | Standard-Stundensatz in CHF |
billable | boolean | Ob die Leistung standardmässig verrechenbar ist (Standard: true) |
archived | boolean | Ob die Leistung archiviert ist (Standard: false) |
service_category_id | integer|null | ID der zugeordneten Leistungskategorie |
service_category | object|null | Die zugeordnete Leistungskategorie (immer eingebettet, wenn gesetzt) |
created_at | datetime | Erstellungsdatum |
updated_at | datetime | Datum der letzten Aktualisierung |
Feature-Flag
Services werden nur für Firmen aktiviert, bei denen services_active auf der Firma gesetzt ist. Andernfalls werden zwar Endpoints erreichbar sein, aber die Funktion bleibt im Frontend unsichtbar.
Endpoints
Alle Services auflisten
GET /companies/{company}/servicesRuft eine paginierte Liste aller Services der Firma ab. Die zugeordnete Leistungskategorie wird automatisch mitgeladen.
Parameter
| Parameter | Typ | Standard | Beschreibung |
|---|---|---|---|
page | integer | 1 | Seitennummer |
per_page | integer | 50 | Anzahl Services pro Seite |
filter[archived] | boolean | - | Filtern nach Archivierungsstatus (exakte Übereinstimmung) |
filter[service_category_id] | integer | - | Filtern nach Leistungskategorie (exakte Übereinstimmung) |
sort | string | name | Sortierfeld: name, hourly_rate, created_at. Voranstellen von - für absteigende Sortierung |
Response
{
"data": [
{
"id": 12,
"name": "Entwicklung",
"hourly_rate": "150.00",
"billable": true,
"archived": false,
"service_category_id": 3,
"service_category": {
"id": 3,
"name": "Beratung",
"created_at": "2026-04-09T16:00:00.000000Z",
"updated_at": "2026-04-09T16:00:00.000000Z"
},
"created_at": "2026-04-07T09:30:00.000000Z",
"updated_at": "2026-04-09T16:05:00.000000Z"
}
],
"links": {
"first": "https://app.milkee.ch/api/v2/companies/123/services?page=1",
"last": "https://app.milkee.ch/api/v2/companies/123/services?page=1",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 1,
"per_page": 50,
"to": 8,
"total": 8
}
}Beispiel
curl -X GET \
-H "Authorization: Bearer 1|abcdef123456789..." \
"https://app.milkee.ch/api/v2/companies/123/services?filter[archived]=false&sort=name"Einzelnen Service abrufen
GET /companies/{company}/services/{service}Ruft einen einzelnen Service inklusive zugeordneter Leistungskategorie ab.
Response
{
"data": {
"id": 12,
"name": "Entwicklung",
"hourly_rate": "150.00",
"billable": true,
"archived": false,
"service_category_id": 3,
"service_category": {
"id": 3,
"name": "Beratung",
"created_at": "2026-04-09T16:00:00.000000Z",
"updated_at": "2026-04-09T16:00:00.000000Z"
},
"created_at": "2026-04-07T09:30:00.000000Z",
"updated_at": "2026-04-09T16:05:00.000000Z"
}
}Beispiel
curl -X GET \
-H "Authorization: Bearer 1|abcdef123456789..." \
"https://app.milkee.ch/api/v2/companies/123/services/12"Service erstellen
POST /companies/{company}/servicesErstellt einen neuen Service.
Request Body
| Feld | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
name | string | Ja | Name der Leistung (max. 255 Zeichen) |
hourly_rate | decimal | Nein | Standard-Stundensatz (≥ 0) |
billable | boolean | Nein | Verrechenbar (Standard: true) |
archived | boolean | Nein | Archiviert (Standard: false) |
service_category_id | integer | Nein | ID einer Leistungskategorie der gleichen Firma |
Cross-Company-Schutz
Wird eine service_category_id angegeben, muss die Leistungskategorie zur gleichen Firma gehören. Andernfalls antwortet die API mit 404.
Response
HTTP/1.1 201 Created{
"data": {
"id": 12,
"name": "Entwicklung",
"hourly_rate": "150.00",
"billable": true,
"archived": false,
"service_category_id": 3,
"service_category": {
"id": 3,
"name": "Beratung",
"created_at": "2026-04-09T16:00:00.000000Z",
"updated_at": "2026-04-09T16:00:00.000000Z"
},
"created_at": "2026-04-07T09:30:00.000000Z",
"updated_at": "2026-04-07T09:30:00.000000Z"
}
}Beispiel
curl -X POST \
-H "Authorization: Bearer 1|abcdef123456789..." \
-H "Content-Type: application/json" \
-d '{
"name": "Entwicklung",
"hourly_rate": 150,
"billable": true,
"service_category_id": 3
}' \
"https://app.milkee.ch/api/v2/companies/123/services"Service aktualisieren
PUT /companies/{company}/services/{service}Aktualisiert einen bestehenden Service. Alle Felder sind optional — übermitteln Sie nur die zu ändernden Felder.
Request Body
| Feld | Typ | Beschreibung |
|---|---|---|
name | string | Name der Leistung (max. 255 Zeichen) |
hourly_rate | decimal|null | Standard-Stundensatz (≥ 0); null entfernt den Stundensatz |
billable | boolean | Verrechenbar |
archived | boolean | Archiviert |
service_category_id | integer|null | ID einer Leistungskategorie der gleichen Firma; null entfernt die Zuordnung |
Response
Gibt das aktualisierte Service-Objekt zurück.
Beispiel
# Service archivieren
curl -X PUT \
-H "Authorization: Bearer 1|abcdef123456789..." \
-H "Content-Type: application/json" \
-d '{"archived": true}' \
"https://app.milkee.ch/api/v2/companies/123/services/12"
# Kategorie entfernen
curl -X PUT \
-H "Authorization: Bearer 1|abcdef123456789..." \
-H "Content-Type: application/json" \
-d '{"service_category_id": null}' \
"https://app.milkee.ch/api/v2/companies/123/services/12"Service löschen
DELETE /companies/{company}/services/{service}Löscht einen Service permanent.
Zeiteinträge verhindern die Löschung
Services mit existierenden Zeiteinträgen können nicht gelöscht werden — der Endpoint antwortet mit 422. Archivieren Sie diese Services stattdessen.
Response
HTTP/1.1 204 No ContentBeispiel
curl -X DELETE \
-H "Authorization: Bearer 1|abcdef123456789..." \
"https://app.milkee.ch/api/v2/companies/123/services/12"Mehrere Services aktualisieren
PUT /companies/{company}/services/multipleAktualisiert mehrere Services gleichzeitig. Aktuell werden die Felder hourly_rate und archived unterstützt — nur die im Request enthaltenen Felder werden auf alle gewählten Services angewendet.
Request Body
| Feld | Typ | Beschreibung |
|---|---|---|
ids | array|integer|string | Array von Service-IDs, einzelne ID oder kommagetrennter String |
hourly_rate | decimal | Neuer Stundensatz für alle Services (optional) |
archived | boolean | Neuer Archivierungsstatus für alle Services (optional) |
Response
{
"message": "Leistungen aktualisiert."
}Beispiele
# Stundensatz für mehrere Services setzen
curl -X PUT \
-H "Authorization: Bearer 1|abcdef123456789..." \
-H "Content-Type: application/json" \
-d '{
"ids": [12, 13, 14],
"hourly_rate": 165
}' \
"https://app.milkee.ch/api/v2/companies/123/services/multiple"
# Mehrere Services archivieren
curl -X PUT \
-H "Authorization: Bearer 1|abcdef123456789..." \
-H "Content-Type: application/json" \
-d '{
"ids": [12, 13, 14],
"archived": true
}' \
"https://app.milkee.ch/api/v2/companies/123/services/multiple"Mehrere Services löschen
DELETE /companies/{company}/services/multipleLöscht mehrere Services gleichzeitig. Services mit existierenden Zeiteinträgen werden automatisch übersprungen — der Endpoint gibt eine Zusammenfassung der gelöschten und übersprungenen Services zurück.
Request Body
| Feld | Typ | Beschreibung |
|---|---|---|
ids | array|integer|string | Array von Service-IDs, einzelne ID oder kommagetrennter String |
Response
{
"message": "2 Leistungen gelöscht. 1 Leistungen übersprungen, weil Zeiteinträge darauf referenzieren.",
"deleted": 2,
"skipped": 1
}Beispiel
curl -X DELETE \
-H "Authorization: Bearer 1|abcdef123456789..." \
-H "Content-Type: application/json" \
-d '{"ids": [12, 13, 14]}' \
"https://app.milkee.ch/api/v2/companies/123/services/multiple"Fehlerbehandlung
Häufige Fehler
422 Validation Error — Service hat Zeiteinträge
{
"message": "Diese Leistung kann nicht gelöscht werden, da noch Zeiteinträge darauf referenzieren."
}Ursache: Auf den Service referenzieren noch Zeiteinträge.
Lösung: Archivieren Sie den Service stattdessen:
curl -X PUT \
-H "Authorization: Bearer 1|abcdef123456789..." \
-H "Content-Type: application/json" \
-d '{"archived": true}' \
"https://app.milkee.ch/api/v2/companies/123/services/12"404 Not Found — Leistungskategorie gehört nicht zur Firma
Tritt auf, wenn beim Erstellen oder Aktualisieren eine service_category_id angegeben wird, die nicht zur aktuellen Firma gehört.
422 Validation Error — Ungültige Eingaben
{
"message": "The given data was invalid.",
"errors": {
"name": ["Der Name ist erforderlich."],
"hourly_rate": ["Der Stundensatz muss mindestens 0 sein."]
}
}Best Practices
Archivieren statt Löschen
Services werden auf Zeiteinträgen referenziert. Wenn ein Service nicht mehr benötigt wird, archivieren Sie ihn (archived: true), statt ihn zu löschen — so bleibt die Historie auf bestehenden Zeiteinträgen erhalten.
Verrechenbarkeit als Standard
Das billable-Feld dient als Standardwert für neue Zeiteinträge, die diesen Service referenzieren. Definieren Sie nicht-verrechenbare Tätigkeiten (z.B. "Interne Besprechung") als billable: false, damit Zeiteinträge konsistent erfasst werden.
Kategorien für Reporting
Nutzen Sie Leistungskategorien, um Services für die Auswertung zu gruppieren (z.B. "Beratung" vs. "Entwicklung"). Die Zuordnung ist optional und kann jederzeit geändert werden.
