Arbeitszeiten - in der Codenica API

Bevor die erste Anfrage zu Arbeitszeiten gesendet wird, muss unter den Einstellungen der Installation ein API-Schlüssel erstellt werden. Falls noch kein Schlüssel vorhanden ist, öffnen Sie in einem neuen Tab die Seite Codenica API - Einführung. Dort stehen die gemeinsamen Regeln für Schlüsselerstellung, Authentifizierung, Auswahl der API-Adresse und Speicherung des Secrets.

Eine Arbeitszeit erfasst die für ein bestimmtes Ticket, Change, Problem oder Release aufgewendete Zeit. Das Objekt ist bewusst schlank: Es besitzt keine eigenen Dateien, keinen Pin und keinen frei wählbaren Beziehungskatalog. Möglich sind genau ein primärer Parent, eine optionale Work Task und ein optionaler Agent.

Im technischen Vertrag heißt die Sammlung worktimes; ein einzelner Datensatz verwendet in itemType den Wert worktime.


Arbeitszeiten - API-Adresse und Auswahl der Installation

Alle Routen für Arbeitszeiten beginnen mit:

{BASE_URL}/api/v1/worktimes

BASE_URL enthält Protokoll und Anwendungshost, aber nicht das abschließende /api/v1.

Codenica Cloud: Verwenden Sie die echte, dem Unternehmen zugewiesene Domain oder Subdomain.

export BASE_URL="https://{company-domain}"

Codenica On-Premise: Codenica Discovery registriert standardmäßig lokal die Adresse http://codenica.local:5150.

Wenn die Installation über eine Unternehmensdomain, HTTPS, einen Reverse Proxy oder einen anderen Port bereitgestellt wird, verwenden Sie die für diese Installation mitgeteilte exakte Adresse. Gehen Sie nicht davon aus, dass On-Premise-Benutzer localhost verwenden. Dieser Name bezeichnet den Computer des HTTP-Clients und nicht zwingend den Codenica-Server.

export BASE_URL="http://codenica.local:5150"

Wenn ein Reverse Proxy oder der Administrator eine andere Adresse vorgibt, genau diese Adresse verwenden.

Die Adresse http://localhost:5050 ist nur für die lokale Entwicklung vorgesehen, wenn die API auf demselben Computer läuft. Sie ist weder die Standardadresse der Cloud noch die Standardadresse von On-Premise.


Arbeitszeiten - API-Schlüssel und Lizenzlimits

Erstellen Sie den Schlüssel unter Einstellungen -> API -> API Keys. Ein eigener Schlüssel pro Anwendung und Umgebung erleichtert Rotation, Audit und das Abschalten eines einzelnen Zugangs.

Lizenz
Codenica API
Maximale Schlüsselanzahl
Starter
nicht verfügbar
0
Plus
verfügbar
50
Enterprise
verfügbar
100

Der Secret wird nur bei Erstellung oder Rotation angezeigt. Speichern Sie Client ID und Client Secret in einem sicheren Secret-Speicher. Das Löschen des Schlüssels löscht den Datensatz und gibt den Platz im Lizenzlimit frei.


Arbeitszeiten - Authentifizierung der Anfragen

Authentifizieren Sie jede Codenica-API-Anfrage mit zwei Headern:

X-Codenica-Client-Id: {CLIENT_ID}
X-Codenica-Client-Secret: {CLIENT_SECRET}
Accept: application/json

Eine externe Integration benötigt weder eine Sitzung im Panel noch das Bearer-JWT des Benutzers. Bewahren Sie den Secret auf dem Server oder in einem Secret-Manager auf.

export CLIENT_ID="cna_your_client_id"
export CLIENT_SECRET="cns_your_client_secret"

curl --fail-with-body --silent --show-error \
  --header "Accept: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  "$BASE_URL/api/v1/context"

Den Secret niemals in Browsercode, ein Repository, eine URL, die Befehlshistorie oder Logs aufnehmen.


Arbeitszeiten - Verbindungskontext prüfen

Lesen Sie den Kontext, bevor Sie eine Liste abrufen oder Zeit erfassen. So lässt sich prüfen, ob die Adresse zur gewünschten Datenbank führt und der Schlüssel die erforderlichen Scopes und Limits besitzt.

curl --fail-with-body --silent --show-error \
  --header "Accept: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  "$BASE_URL/api/v1/context" | jq

Prüfen Sie data.tenant.id, data.tenant.name, data.tenant.resolvedDomain, data.caller.clientId und data.caller.scopes. data.caller.authentication muss api_key sein. Prüfen Sie außerdem data.capabilities.supportsETag, supportsIdempotency und supportsRelationships.

{
  "data": {
    "apiVersion": "v1",
    "caller": {
      "authentication": "api_key",
      "clientId": "{CLIENT_ID}",
      "scopes": [
        "worktimes:read",
        "worktimes:write"
      ]
    },
    "capabilities": {
      "supportsETag": true,
      "supportsIdempotency": true,
      "supportsRelationships": true
    }
  },
  "meta": {
    "requestId": "{REQUEST_ID}"
  }
}

Wenn der Kontext ein anderes Unternehmen zeigt oder ein erforderlicher Scope fehlt, korrigieren Sie die Adresse oder erstellen Sie einen Schlüssel mit passenden Berechtigungen. Versuchen Sie nicht, die Datenbank durch eine zusätzliche ID im Body auszuwählen.


Arbeitszeiten - Schema und unterstützte Felder

Das Schema ist die maßgebliche Beschreibung des aktuellen Vertrags für Arbeitszeiten. Es liefert Feldtypen, Schreibbarkeit, Einschränkungen, technische Felder und erlaubte Beziehungsziele.

curl --fail-with-body --silent --show-error \
  --header "Accept: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  "$BASE_URL/api/v1/worktimes/schema" | jq
{
  "data": {
    "itemType": "worktime",
    "fields": [
      { "name": "date", "type": "dateTime", "writable": true },
      { "name": "time", "type": "integer", "writable": true },
      { "name": "isBillable", "type": "boolean", "writable": true },
      { "name": "dateCreated", "type": "dateTime", "writable": false, "system": true }
    ],
    "relationshipTargets": [
      { "targetDataSet": "tickets", "targetItemType": "ticket" },
      { "targetDataSet": "changes", "targetItemType": "change" },
      { "targetDataSet": "problems", "targetItemType": "problem" },
      { "targetDataSet": "releases", "targetItemType": "release" },
      { "targetDataSet": "worktasks", "targetItemType": "worktask" }
    ],
    "userRelationshipTypes": ["agent"]
  }
}

Prüfen Sie vor dem Mapping die Werte readable, writable, required, technical und maxLength. Bauen Sie die Integration nicht ausschließlich anhand einer Beispielantwort.


Arbeitszeiten - primärer übergeordneter Datensatz und Sichtbarkeit

Jede Arbeitszeit muss genau einen primären operativen Parent besitzen. Als Parent sind Ticket, Change, Problem oder Release zulässig.

targetDataSet
targetItemType
relationshipType
tickets
ticket
parent
changes
change
parent
problems
problem
parent
releases
release
parent

Ohne diese Beziehung kann kein Datensatz erstellt werden; der letzte Parent eines vorhandenen Datensatzes darf nicht entfernt werden. Die Sichtbarkeit richtet sich nach dem Zugriff auf den primären Parent. Der Zugriff auf eine Work Task allein macht die Arbeitszeit nicht sichtbar.

Senden Sie die Parent-Beziehung im Array relationships. Schreiben Sie ticketId, changeId, problemId und releaseId nicht in attributes.


Arbeitszeiten - schreibbare und systembezogene Felder

Die folgenden Felder übertragen Geschäftsdaten in attributes. Wenn das Schema der aktuellen Datenbank andere Grenzen vorgibt, hat das Schema Vorrang.

Feld
Typ
Verwendung
customId
string
Externe Integrationskennung, maximal 500 Zeichen.
date
date-time
Datum oder Zeitpunkt der Arbeit im ISO-8601-Format.
location
string
Ort der Arbeit, maximal 300 Zeichen.
department
string
Zuständige Abteilung oder Einheit, maximal 300 Zeichen.
time
integer
Dauer in Sekunden, Wert null oder größer.
title
string
Beschreibung der Sitzung oder Tätigkeit, maximal 1000 Zeichen.
category
string
Kategorie für Abrechnung oder Auswertung, maximal 300 Zeichen.
isBillable
boolean
Gibt an, ob die Zeit abgerechnet werden kann.
{
  "date": "2026-09-06T09:00:00Z",
  "time": 5400,
  "title": "Bearbeitung eines Tickets durch den Service Desk",
  "category": "Service Desk",
  "isBillable": true
}

Beispiel für einen Eintrag von 90 Minuten:

Die Felder isAuto, agentId, workTaskId, ticketId, changeId, problemId, releaseId, creator, updater, dateCreated, dateUpdated, importId, importSource und dateImported sind technisch oder systembezogen. Schreiben Sie sie nicht in attributes; verwalten Sie Agent und Work Task über die vorgesehenen Beziehungs-Endpoints.


Arbeitszeiten - wichtigste Endpoints

Die Sammlung der Arbeitszeiten stellt folgende Routen bereit:

GET    /api/v1/worktimes
POST   /api/v1/worktimes
POST   /api/v1/worktimes:batch
GET    /api/v1/worktimes/schema
GET    /api/v1/worktimes/stats
GET    /api/v1/worktimes/values
GET    /api/v1/worktimes/{WORKTIME_ID}
PATCH  /api/v1/worktimes/{WORKTIME_ID}
DELETE /api/v1/worktimes/{WORKTIME_ID}
GET    /api/v1/worktimes/{WORKTIME_ID}/relationships
POST   /api/v1/worktimes/{WORKTIME_ID}/relationships
POST   /api/v1/worktimes/{WORKTIME_ID}/relationships:batch
DELETE /api/v1/worktimes/{WORKTIME_ID}/relationships/{DATASET}/{TARGET_ID}
GET    /api/v1/worktimes/{WORKTIME_ID}/user-relationships
POST   /api/v1/worktimes/{WORKTIME_ID}/user-relationships
POST   /api/v1/worktimes/{WORKTIME_ID}/user-relationships:batch
DELETE /api/v1/worktimes/{WORKTIME_ID}/user-relationships/{TARGET_ID}

Jeder Lesevorgang benötigt den passenden read-Scope. Jede Mutation benötigt zusätzlich einen eindeutigen Idempotency-Key. Der aktuelle ETag ist für Änderungen, Beziehungsänderungen und Löschungen erforderlich.


Arbeitszeiten - Liste und Paginierung

Lesen Sie die Liste seitenweise. Legen Sie die Sortierung ausdrücklich fest, damit spätere Lesevorgänge eine stabile Reihenfolge verwenden:

curl --fail-with-body --silent --show-error -G \
  --data-urlencode "itemType=worktime" \
  --data-urlencode "page=1" \
  --data-urlencode "pageSize=25" \
  --data-urlencode "sort=date" \
  --data-urlencode "direction=desc" \
  --header "Accept: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  "$BASE_URL/api/v1/worktimes"

Die Antwort enthält data.items und Informationen zur Seite.

{
  "data": {
    "items": [
      {
        "id": "{WORKTIME_ID}",
        "itemType": "worktime",
        "attributes": {
          "customId": "ERP-WORKTIME-2026-0042",
          "date": "2026-09-06T09:00:00Z",
          "time": 5400,
          "title": "Bearbeitung eines Tickets durch den Service Desk",
          "isBillable": true
        },
        "meta": { "etag": "\"{ETAG}\"" }
      }
    ],
    "page": 1,
    "pageSize": 25,
    "totalItems": 1,
    "totalPages": 1,
    "hasNextPage": false
  },
  "meta": { "requestId": "{REQUEST_ID}" }
}

Fordern Sie die nächste Seite nur an, wenn hasNextPage auf true steht. Das maximale pageSize lesen Sie aus den im Kontext gemeldeten Limits.


Arbeitszeiten - Suche und Filter

Verwenden Sie search für eine einfache Textsuche. Für die Synchronisierung sind eine stabile customId, eine UUID oder ein Filter nach dem primären Parent geeigneter:

curl --fail-with-body --silent --show-error -G \
  --data-urlencode "search=Service Desk" \
  --data-urlencode "category=Service Desk" \
  --data-urlencode "isBillable=true" \
  --data-urlencode "parentDataSet=tickets" \
  --data-urlencode "parentId=$TICKET_ID" \
  --data-urlencode "page=1" \
  --data-urlencode "pageSize=50" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  "$BASE_URL/api/v1/worktimes"

Zu den Parametern gehören customId, date, dateAfter, dateBefore, location, department, time, title, category, isBillable, agentId, createdAfter, createdBefore, updatedAfter, updatedBefore, sort und direction. Ein Filter mit parentDataSet benötigt zusätzlich parentId.

time:gte:3600
time:lt:28800
category:eq:Service Desk
title:contains:Ticket
customId:startswith:ERP-WORKTIME-
location:notempty:

Strukturierte Filter verwenden das Format field:operator:value:

Unterstützt werden eq, ne, gt, gte, lt, lte, contains, startswith, endswith und notempty. Werte mit Leerzeichen, Doppelpunkten oder Sonderzeichen müssen URL-kodiert werden.


Arbeitszeiten - Feldauswahl und eingebundene Daten

Verwenden Sie fields, wenn die Antwort nur die für die Synchronisierung benötigten Daten enthalten soll. Objekt- und Benutzerbeziehungen werden mit include eingebunden:

curl --fail-with-body --silent --show-error -G \
  --data-urlencode "fields=id,itemType,customId,date,time,title,category,isBillable" \
  --data-urlencode "include=relationships,users" \
  --data-urlencode "ids=$WORKTIME_ID" \
  --header "Accept: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  "$BASE_URL/api/v1/worktimes"

Für Arbeitszeiten sind relationships und users als include-Werte zulässig. include=files wird nicht unterstützt. Technische Felder benötigen den passenden technischen Scope; fields=* umgeht weder Berechtigungen noch Systemfelder der Antwort-Hülle.


Arbeitszeiten - Statistiken und Feldwerte

Statistiken zeigen die Datenverteilung, ohne die gesamte Sammlung laden zu müssen. Das folgende Beispiel gruppiert nach Kategorie:

curl --fail-with-body --silent --show-error -G \
  --data-urlencode "field=category" \
  --data-urlencode "limit=20" \
  --header "Accept: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  "$BASE_URL/api/v1/worktimes/stats"
{
  "data": {
    "total": 11,
    "field": "category",
    "values": [
      { "value": "Service Desk", "count": 7 },
      { "value": "Public API", "count": 4 }
    ]
  },
  "meta": { "requestId": "{REQUEST_ID}" }
}

Für Werte, die zu einer Suche passen, verwenden Sie values:

curl --fail-with-body --silent --show-error -G \
  --data-urlencode "field=category" \
  --data-urlencode "search=Public" \
  --data-urlencode "limit=20" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  "$BASE_URL/api/v1/worktimes/values"

Statistiken und Werte sind reine Leseoperationen. Sie verändern keine Arbeitszeitdatensätze.


Arbeitszeiten - minimale Erstellung

Eine minimale Erstellung benötigt itemType, Felder in attributes und genau eine parent-Beziehung. Jede POST-Anfrage muss einen neuen Idempotency-Key enthalten:

curl --fail-with-body --silent --show-error \
  --request POST \
  --header "Accept: application/json" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "Idempotency-Key: erp-worktime-create-0001" \
  --data '{
    "itemType": "worktime",
    "attributes": {
      "customId": "ERP-WORKTIME-2026-0042",
      "date": "2026-09-06T09:00:00Z",
      "time": 5400,
      "title": "Bearbeitung eines Tickets durch den Service Desk",
      "category": "Service Desk",
      "isBillable": true
    },
    "relationships": [
      {
        "targetId": "{TICKET_ID}",
        "targetDataSet": "tickets",
        "targetItemType": "ticket",
        "relationshipType": "parent"
      }
    ]
  }' \
  "$BASE_URL/api/v1/worktimes"

Die erwartete Antwort ist normalerweise HTTP 201 Created. Speichern Sie data.id sowie den ETag aus dem HTTP-Header und aus data.meta.etag.


Arbeitszeiten - Erstellung mit Ort und Agent

Zusätzliche Geschäftsfelder wie Ort und Abteilung können in derselben Anfrage übergeben werden. Ein Agent ist eine Benutzerbeziehung und gehört in das separate Array userRelationships:

{
  "itemType": "worktime",
  "attributes": {
    "customId": "ERP-WORKTIME-2026-0043",
    "date": "2026-09-06T10:30:00Z",
    "location": "Krakau",
    "department": "IT",
    "time": 1800,
    "title": "Problemanalyse und Kontakt mit dem Benutzer",
    "category": "Betrieb",
    "isBillable": false
  },
  "relationships": [
    {
      "targetId": "{PROBLEM_ID}",
      "targetDataSet": "problems",
      "targetItemType": "problem",
      "relationshipType": "parent"
    }
  ],
  "userRelationships": [
    {
      "targetId": "{APP_USER_ID}",
      "targetDataSet": "users",
      "relationshipType": "agent"
    }
  ]
}

targetId des Agents ist die ID eines aktiven Anwendungsbenutzers. Verwenden Sie hier keine Kunden-ID aus der Sammlung clients. Eine Arbeitszeit kann höchstens einen Agent besitzen.


Arbeitszeiten - optionale Work-Task-Beziehung

Eine Work Task kann als zusätzliche Objektbeziehung hinzugefügt werden. Sie ersetzt den primären Parent nicht:

"relationships": [
  {
    "targetId": "{TICKET_ID}",
    "targetDataSet": "tickets",
    "targetItemType": "ticket",
    "relationshipType": "parent"
  },
  {
    "targetId": "{WORKTASK_ID}",
    "targetDataSet": "worktasks",
    "targetItemType": "worktask",
    "relationshipType": "worktask"
  }
]

Eine Work Task kann nur einer Arbeitszeit zugeordnet werden. Ist sie bereits belegt, antwortet die API mit HTTP 400, validation_failed und der Meldung The WorkTask is already assigned to another WorkTime.. Wählen Sie eine freie Work Task oder lassen Sie die Beziehung weg. Ändern Sie das technische Feld workTaskId nicht in attributes.


Arbeitszeiten - Idempotency-Key und sichere Wiederholungen

Idempotenz schützt vor einem doppelten Eintrag, wenn der Client die Antwort nicht rechtzeitig erhält. Bei einer Wiederholung müssen Body und Schlüssel identisch bleiben:

curl --fail-with-body --silent --show-error \
  --request POST \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "Idempotency-Key: erp-worktime-create-0001" \
  --data-binary @worktime-create.json \
  "$BASE_URL/api/v1/worktimes"

Derselbe Schlüssel mit demselben Body muss dieselbe Ressource zurückgeben, statt einen zweiten Datensatz zu erzeugen. Derselbe Schlüssel mit einem anderen Body führt zu 409 idempotency_conflict. Für eine neue Operation muss ein neuer Schlüssel erzeugt werden.


Arbeitszeiten - einen Datensatz lesen

Lesen Sie den Datensatz nach der Erstellung über die zurückgegebene UUID. Parent, Work Task und Agent können mit den include-Parametern angefordert werden:

curl --fail-with-body --silent --show-error \
  --request GET \
  --url "$BASE_URL/api/v1/worktimes/$WORKTIME_ID?itemType=worktime&include=relationships,users" \
  --header "Accept: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

Das normale Ergebnis ist HTTP 200 OK. Prüfen Sie data.attributes.time, date, title, category und isBillable sowie data.relationships, data.userRelationships und data.meta.etag.


Arbeitszeiten - mit ETag und If-Match aktualisieren

Lesen Sie vor einer Änderung einen aktuellen ETag. Senden Sie nur die zu ändernden Felder und den ETag im Header If-Match:

curl --fail-with-body --silent --show-error \
  --request PATCH \
  --url "$BASE_URL/api/v1/worktimes/$WORKTIME_ID" \
  --header "Accept: application/json" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $WORKTIME_ETAG" \
  --header "Idempotency-Key: erp-worktime-update-0001" \
  --data '{
    "attributes": {
      "time": 7200,
      "title": "Bearbeitung eines Tickets - zweite Sitzung",
      "isBillable": false
    }
  }'

Nach HTTP 200 OK speichern Sie den neuen ETag. Verwenden Sie den vorherigen Wert nicht für die nächste Änderung.


Arbeitszeiten - veralteter oder fehlender ETag

Die Versionskontrolle verhindert, dass eine Integration eine zwischenzeitliche Änderung überschreibt. Ohne If-Match antwortet die API mit HTTP 428 Precondition Required und if_match_required. Ein alter ETag führt zu HTTP 412 Precondition Failed und if_match_failed:

HTTP/1.1 412 Precondition Failed
code: if_match_failed

HTTP/1.1 428 Precondition Required
code: if_match_required

Nach 412 lesen Sie den Datensatz erneut, vergleichen die aktuellen Daten mit der geplanten Änderung und entscheiden dann über einen neuen PATCH. Keine blinde Retry-Schleife einsetzen. Eine abgelehnte Änderung darf Zeit, Parent oder Beziehungen nicht verändern.


Arbeitszeiten - Objektbeziehungen mit Work Task

Für Objektbeziehungen verwenden Sie die Routen /relationships. Arbeitszeiten akzeptieren nur die Ziele tickets, changes, problems, releases und worktasks.

curl --fail-with-body --silent --show-error \
  --request POST \
  --url "$BASE_URL/api/v1/worktimes/$WORKTIME_ID/relationships" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $WORKTIME_ETAG" \
  --header "Idempotency-Key: erp-worktime-worktask-add-0001" \
  --data '{
    "targetId": "{WORKTASK_ID}",
    "targetDataSet": "worktasks",
    "targetItemType": "worktask",
    "relationshipType": "worktask"
  }'

Die Sammlung wird mit GET /api/v1/worktimes/{WORKTIME_ID}/relationships gelesen. Zum Entfernen ist der aktuelle ETag erforderlich:

curl --fail-with-body --silent --show-error \
  --request DELETE \
  --url "$BASE_URL/api/v1/worktimes/$WORKTIME_ID/relationships/worktasks/$WORKTASK_ID?relationshipType=worktask" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $WORKTIME_ETAG" \
  --header "Idempotency-Key: erp-worktime-worktask-remove-0001"

Nach jeder Beziehungsänderung den ETag aktualisieren. Das Entfernen einer Work Task löscht die Arbeitszeit nicht.


Arbeitszeiten - primären übergeordneten Datensatz ändern

Um Zeit von einem Ticket auf einen Change zu übertragen, verwenden Sie einen PATCH, der den alten Parent entfernt und den neuen hinzufügt. Danach muss genau ein Parent vorhanden sein:

{
  "relationshipsToRemove": [
    {
      "targetId": "{OLD_TICKET_ID}",
      "targetDataSet": "tickets",
      "targetItemType": "ticket",
      "relationshipType": "parent"
    }
  ],
  "relationshipsToAdd": [
    {
      "targetId": "{NEW_CHANGE_ID}",
      "targetDataSet": "changes",
      "targetItemType": "change",
      "relationshipType": "parent"
    }
  ]
}

Senden Sie die Operation an /api/v1/worktimes/{WORKTIME_ID} mit dem aktuellen If-Match und einem neuen Idempotency-Key. Entfernen Sie den alten Parent nicht zuerst separat, da der Datensatz dann kurzzeitig keinen erforderlichen Parent hätte.


Arbeitszeiten - Agent-Beziehung

Der Agent ist der Benutzer, dem die Arbeitszeit zugewiesen ist. Verwenden Sie /user-relationships, nicht /relationships:

curl --fail-with-body --silent --show-error \
  --request POST \
  --url "$BASE_URL/api/v1/worktimes/$WORKTIME_ID/user-relationships" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $WORKTIME_ETAG" \
  --header "Idempotency-Key: erp-worktime-agent-add-0001" \
  --data '{
    "targetId": "{APP_USER_ID}",
    "targetDataSet": "users",
    "relationshipType": "agent"
  }'

Lesen Sie die Liste mit GET /api/v1/worktimes/{WORKTIME_ID}/user-relationships?relationshipType=agent. Zum Wechseln entfernen Sie zuerst die aktuelle Beziehung und fügen danach die neue hinzu. Verwenden Sie jeweils einen frischen ETag. Eine Arbeitszeit kann höchstens einen Agent besitzen.

targetId muss auf einen aktiven und sichtbaren Anwendungsbenutzer zeigen. Es ist weder Clients.Id noch eine Unternehmens-ID.


Arbeitszeiten - Beziehungs-Batch

Mit relationships:batch lassen sich mehrere Objektbeziehungen in einer Operation hinzufügen oder entfernen. Für Agent-Beziehungen gibt es den entsprechenden Endpoint user-relationships:batch:

curl --fail-with-body --silent --show-error \
  --request POST \
  --url "$BASE_URL/api/v1/worktimes/$WORKTIME_ID/relationships:batch" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $WORKTIME_ETAG" \
  --header "Idempotency-Key: erp-worktime-relationships-batch-0001" \
  --data '{
    "add": [
      {
        "targetId": "{WORKTASK_ID}",
        "targetDataSet": "worktasks",
        "targetItemType": "worktask",
        "relationshipType": "worktask"
      }
    ],
    "remove": []
  }'

Die Antwort enthält die Zähler added, removed und skipped. Bewahren Sie genau eine parent-Beziehung und senden Sie niemals einen Batch, der den einzigen Parent entfernt.

{
  "data": {
    "added": 1,
    "removed": 0,
    "skipped": 0
  },
  "meta": { "requestId": "{REQUEST_ID}" }
}

Prüfen Sie die Zähler der Antwort, bevor die Operation als erfolgreich gilt.


Arbeitszeiten - Batch-Operationen für Datensätze

Für mehrere Arbeitszeiten verwenden Sie POST /api/v1/worktimes:batch. Jedes Element gibt create, update oder delete an:

{
  "items": [
    {
      "operation": "create",
      "create": {
        "itemType": "worktime",
        "attributes": {
          "customId": "ERP-WORKTIME-BATCH-0001",
          "date": "2026-09-06T10:00:00Z",
          "time": 1800,
          "title": "Aus dem ERP importierte Zeit",
          "category": "Public API",
          "isBillable": true
        },
        "relationships": [
          {
            "targetId": "{TICKET_ID}",
            "targetDataSet": "tickets",
            "targetItemType": "ticket",
            "relationshipType": "parent"
          }
        ]
      }
    }
  ]
}

Ein Element für update oder delete benötigt eine eigene id und ein eigenes ifMatch. Übergeben Sie ifMatch als String mit dem aktuellen ETag:

{
  "items": [
    {
      "operation": "update",
      "id": "{WORKTIME_ID}",
      "ifMatch": "\"{CURRENT_ETAG}\"",
      "update": {
        "attributes": {
          "time": 2700
        }
      }
    },
    {
      "operation": "delete",
      "id": "{OTHER_WORKTIME_ID}",
      "ifMatch": "\"{OTHER_CURRENT_ETAG}\""
    }
  ]
}

Prüfen Sie jedes Element anhand von index, operation und status. Ein Teilergebnis kann HTTP 207 Multi-Status verwenden; ein Fehler bestätigt nicht den Erfolg der übrigen Elemente.


Arbeitszeiten - Datensatz löschen

Das Löschen ist aus Sicht der Public API nicht rückgängig zu machen. Lesen Sie vor dem Vorgang einen frischen ETag und prüfen Sie, ob UUID und customId den richtigen Datensatz bezeichnen:

curl --fail-with-body --silent --show-error \
  --request DELETE \
  --url "$BASE_URL/api/v1/worktimes/$WORKTIME_ID" \
  --header "Accept: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $WORKTIME_ETAG" \
  --header "Idempotency-Key: erp-worktime-delete-0001"

Erfolgreich ist HTTP 200 OK mit data=true. Lesen Sie danach erneut über die UUID:

curl --fail-with-body --silent --show-error \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  "$BASE_URL/api/v1/worktimes/$WORKTIME_ID"

Der Folgelesevorgang sollte HTTP 404 Not Found mit workTime_not_found liefern. Prüfen Sie zusätzlich eine nach customId gefilterte Liste, damit der Datensatz nicht wieder erscheint.


Arbeitszeiten - keine Dateien und kein Pin

Arbeitszeiten besitzen kein eigenes Dateimodul und keine Pin-Aktion. Verwenden Sie diese Routen nicht:

/api/v1/worktimes/{WORKTIME_ID}/files
/api/v1/worktimes/{WORKTIME_ID}/pin

Eine Arbeitszeit wie ein Objekt mit Dateien oder Pin zu behandeln, ist nicht Bestandteil des Vertrags. Wenn ein Zeiteintrag ein Dokument benötigt, speichern Sie die Datei auf einem Objekt mit Dateifunktion, etwa einem Ticket oder Dokument, und verwalten Sie die Verbindung im Integrationssystem.


Arbeitszeiten - Fehler und Limits

HTTP
Code
Bedeutung und Reaktion
400
validation_failed
Ungültiges Feld, ungültiger Parent oder bereits belegte Work Task. Daten korrigieren.
401
authentication_required
Beide Schlüssel-Header und die Installationsadresse prüfen.
403
public_api_scope_denied
Den erforderlichen Scope unter Einstellungen -> API zum Schlüssel hinzufügen.
403
workTime_parent_access_denied
Einen für den Caller verfügbaren Parent verwenden.
404
workTime_not_found
UUID, Adresse und Zugriff auf den Datensatz prüfen.
409
idempotency_conflict
Dieser Idempotency-Key wurde mit einem anderen Body verwendet.
412
if_match_failed
Datensatz erneut lesen und einen neuen ETag verwenden.
428
if_match_required
Den aktuellen If-Match-Wert hinzufügen.
428
idempotency_key_required
Der Mutation einen eindeutigen Idempotency-Key hinzufügen.
429
rate_limit_exceeded
Backoff verwenden und die Rate-Limit-Header beachten.
503
tenant_context_unavailable
Mit Backoff wiederholen, ohne den Body zu ändern.

Bei einem Fehler sollten HTTP-Status, code und meta.requestId gespeichert werden, niemals der Schlüssel-Secret. Bei HTTP 400 zusätzlich errors prüfen, weil dort das konkrete Feld oder Array-Element steht.

Anfrage- und Batch-Limits stehen in data.capabilities.limits. Die Header X-RateLimit-Limit und X-RateLimit-Remaining helfen bei der Anpassung der Synchronisierungsgeschwindigkeit.


Arbeitszeiten - Synchronisierung und n8n

Für eine Synchronisierung mit ERP, Helpdesk oder n8n verwenden Sie eine vom externen System vergebene customId, zum Beispiel ERP-WORKTIME-{external-id}. Der Titel ist kein geeigneter Deduplizierungsschlüssel, weil zwei Sitzungen dieselbe Beschreibung haben können.

In n8n genügt ein HTTP Request-Node. Hinterlegen Sie X-Codenica-Client-Id, X-Codenica-Client-Secret und Accept: application/json in den Header-Zugangsdaten. Für POST muss ein eindeutiger Idempotency-Key ergänzt werden.

{
  "itemType": "worktime",
  "attributes": {
    "customId": "N8N-WORKTIME-{{$execution.id}}",
    "date": "2026-09-06T09:00:00Z",
    "time": 1800,
    "title": "Mit n8n synchronisierte Zeit",
    "category": "Public API",
    "isBillable": true
  },
  "relationships": [
    {
      "targetId": "{{$json.ticketId}}",
      "targetDataSet": "tickets",
      "targetItemType": "ticket",
      "relationshipType": "parent"
    }
  ]
}

Bei einer Aktualisierung soll der Workflow den Datensatz zuerst lesen, data.meta.etag behalten und danach PATCH mit diesem ETag senden. Bei HTTP 412 erneut lesen und den Konflikt entscheiden. Bei HTTP 429 einen begrenzten Backoff verwenden. Der Secret darf nicht in einem Code-Node, Workflow-Input oder Ausführungsverlauf landen.


Arbeitszeiten - empfohlene Reihenfolge

Eine sichere Integrationsreihenfolge sieht so aus:

1. BASE_URL für Cloud oder On-Premise festlegen.
2. Einen Schlüssel unter Einstellungen -> API erstellen und den Secret speichern.
3. GET /api/v1/context lesen und Datenbank, Scopes und Limits prüfen.
4. GET /api/v1/worktimes/schema lesen.
5. Einen verfügbaren Ticket-, Change-, Problem- oder Release-Datensatz auswählen.
6. Optional eine freie Work Task und einen aktiven Agent auswählen.
7. Den Datensatz mit einem Parent und einem neuen Idempotency-Key erstellen.
8. UUID und ETag aus der Antwort speichern.
9. Den Datensatz mit include=relationships,users lesen.
10. Für Änderungen einen aktuellen If-Match-Wert und einen neuen Idempotency-Key verwenden.
11. Beziehungen über den passenden Endpoint ändern, nicht über technische Felder.
12. Bei Massenimporten jedes Batch-Element prüfen.
13. Vor DELETE einen aktuellen ETag lesen.
14. HTTP 404 und das Fehlen der customId nach DELETE bestätigen.

Die wichtigsten Regeln sind einfach: Eine Arbeitszeit hat einen primären Parent, die Dauer wird in Sekunden gespeichert, eine Work Task ist optional und nur einmal belegbar, der Agent ist eine Benutzerbeziehung und Dateien und Pin werden nicht unterstützt. Idempotenz schützt den Schreibvorgang, ETag die konkrete Datensatzversion.