Godziny pracy w Codenica API
Zanim zostanie wysłane pierwsze żądanie dotyczące godzin pracy, należy utworzyć klucz w ustawieniach swojej instalacji. Jeżeli klucz nie został jeszcze utworzony, otwórz w nowej karcie stronę Codenica API - wprowadzenie. Znajdują się tam wspólne zasady tworzenia kluczy, uwierzytelniania, wyboru adresu API oraz przechowywania sekretu.
Godzina pracy zapisuje czas poświęcony na obsługę konkretnego zgłoszenia, zmiany, problemu albo wydania. To celowo wąski obiekt: nie ma własnych plików, przypięcia ani dowolnego katalogu powiązań. Może mieć dokładnie jednego głównego rodzica, opcjonalny WorkTask oraz opcjonalnego Agenta.
W technicznym kontrakcie kolekcja ma nazwę worktimes, a pojedynczy rekord ma itemType równy worktime. W dalszych przykładach identyfikatory i adresy są wartościami demonstracyjnymi. Zastąp je danymi własnej instalacji.
Godziny pracy - adres API i wybór instalacji
Wszystkie trasy dotyczące godzin pracy zaczynają się od adresu:
{BASE_URL}/api/v1/worktimesBASE_URL obejmuje protokół i host aplikacji, ale nie zawiera końcowego /api/v1.
Codenica Cloud: użyj rzeczywistej domeny lub subdomeny przypisanej do firmy:
export BASE_URL="https://{rzeczywista-domena-firmy}"Codenica On-Premise: domyślny adres rejestrowany lokalnie przez Codenica Discovery to:
export BASE_URL="http://codenica.local:5150"Jeżeli administrator udostępnił instalację przez firmową domenę, HTTPS, reverse proxy albo na innym porcie, użyj dokładnego adresu przekazanego dla tej instalacji. Nie zakładaj, że użytkownik On-Premise ma używać localhost. Ten adres oznacza komputer, na którym działa klient HTTP, a niekoniecznie serwer Codenica.
Adres localhost:5050 dotyczy wyłącznie lokalnego środowiska deweloperskiego, w którym API zostało uruchomione na tym samym komputerze. Nie jest to standardowy adres Cloud ani domyślny adres On-Premise.
Właściwa baza danych jest wybierana na podstawie adresu i hosta żądania. Nie przesyłaj tenantId w body, query stringu ani dodatkowym nagłówku.
Godziny pracy - klucz API i limity licencyjne
Klucz utwórz w panelu Ustawienia -> API -> API Keys. Dla każdej aplikacji i środowiska warto utworzyć osobny klucz, nadać mu czytelną nazwę i wybrać tylko zakresy potrzebne do rejestrowania czasu.
Sekret jest pokazywany tylko podczas utworzenia albo rotacji klucza. Zapisz wtedy Client ID i Client Secret w bezpiecznym magazynie. Usunięcie klucza usuwa jego rekord i zwalnia miejsce w limicie. Osobny klucz dla każdej integracji ułatwia rotację, audyt i odcięcie jednego systemu bez zmiany pozostałych.
Godziny pracy - uwierzytelnianie żądań
Każde żądanie Codenica API uwierzytelnij dwoma nagłówkami:
X-Codenica-Client-Id: {CLIENT_ID}
X-Codenica-Client-Secret: {CLIENT_SECRET}
Accept: application/jsonPrzykład pierwszego odczytu:
export CLIENT_ID="cna_twoj_client_id"
export CLIENT_SECRET="cns_twoj_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"Zewnętrzna integracja nie potrzebuje sesji panelu ani Bearer JWT użytkownika. Sekret przechowuj po stronie serwera albo w menedżerze sekretów. Nie umieszczaj go w kodzie przeglądarkowym, repozytorium, adresie URL, historii poleceń ani logach.
Godziny pracy - sprawdzenie kontekstu połączenia
Przed pobraniem listy albo zapisaniem czasu odczytaj kontekst. Potwierdzisz, że adres prowadzi do właściwej bazy danych, a klucz ma potrzebne zakresy i limity.
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" | jqW odpowiedzi sprawdź data.tenant.id, data.tenant.name, data.tenant.resolvedDomain, data.caller.clientId oraz data.caller.scopes. Wartość data.caller.authentication powinna wynosić api_key. Sprawdź także data.capabilities.supportsETag, supportsIdempotency i 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}"
}
}Jeżeli kontekst wskazuje inną firmę albo nie zawiera wymaganego zakresu, popraw adres lub utwórz klucz z właściwymi uprawnieniami. Nie próbuj zmieniać bazy danych przez dodanie obcego identyfikatora do body.
Godziny pracy - schema i obsługiwane pola
Schema jest źródłem informacji o aktualnym kontrakcie godzin pracy. Zwraca typy pól, ich zapisywalność, ograniczenia, pola techniczne oraz dozwolone targety relacji.
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"]
}
}Przed mapowaniem pól sprawdź wartości readable, writable, required, technical i maxLength. Nie buduj integracji wyłącznie na podstawie przykładowej odpowiedzi.
Godziny pracy - główny rodzic i widoczność
Każda godzina pracy musi mieć dokładnie jednego głównego rodzica operacyjnego. Rodzicem może być zgłoszenie, zmiana, problem albo wydanie:
ticketsticketparentchangeschangeparentproblemsproblemparentreleasesreleaseparentNie można utworzyć rekordu bez tego powiązania ani usunąć ostatniego rodzica istniejącego rekordu. Widoczność godziny pracy wynika z dostępu do jej głównego rodzica. Sam dostęp do WorkTaska nie wystarcza, aby rekord pojawił się na liście.
Relację rodzica przekazuj w tablicy relationships. Nie zapisuj identyfikatorów ticketId, changeId, problemId ani releaseId w attributes.
Godziny pracy - pola zapisywalne i systemowe
Poniższe pola służą do przekazywania danych biznesowych w attributes. Jeżeli schema bieżącej bazy podaje inne ograniczenia, pierwszeństwo ma właśnie schema.
customIddatelocationdepartmenttimetitlecategoryisBillablePrzykład czasu trwającego 90 minut:
{
"date": "2026-09-06T09:00:00Z",
"time": 5400,
"title": "Obsługa zgłoszenia przez Service Desk",
"category": "Service Desk",
"isBillable": true
}Pola isAuto, agentId, workTaskId, ticketId, changeId, problemId, releaseId, creator, updater, dateCreated, dateUpdated, importId, importSource i dateImported są techniczne albo systemowe. Nie zapisuj ich w attributes; relacje Agenta i WorkTaska obsługuj przez właściwe endpointy.
Godziny pracy - najważniejsze endpointy
Kolekcja godzin pracy udostępnia następujące trasy:
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}Każdy odczyt wymaga scope'u read odpowiedniej grupy, a każda zmiana danych wymaga dodatkowo Idempotency-Key. Aktualny ETag jest wymagany przy edycji, zmianie relacji i usuwaniu.
Godziny pracy - listowanie i paginacja
Pobieraj listę stronami. Parametry page i pageSize pozwalają kontrolować wielkość odpowiedzi:
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"Odpowiedź zawiera data.items oraz informacje o stronie:
{
"data": {
"items": [
{
"id": "{WORKTIME_ID}",
"itemType": "worktime",
"attributes": {
"customId": "ERP-WORKTIME-2026-0042",
"date": "2026-09-06T09:00:00Z",
"time": 5400,
"title": "Obsługa zgłoszenia",
"isBillable": true
},
"meta": { "etag": "\"{ETAG}\"" }
}
],
"page": 1,
"pageSize": 25,
"totalItems": 1,
"totalPages": 1,
"hasNextPage": false
},
"meta": { "requestId": "{REQUEST_ID}" }
}Następną stronę pobieraj tylko wtedy, gdy hasNextPage ma wartość true. Maksymalny pageSize odczytaj z limitów zwróconych w kontekście.
Godziny pracy - wyszukiwanie i filtrowanie
Do prostego wyszukiwania tekstowego użyj search. Do synchronizacji lepiej wykorzystać stabilny customId, UUID albo filtr po głównym rodzicu:
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"Dostępne są między innymi parametry customId, date, dateAfter, dateBefore, location, department, time, title, category, isBillable, agentId, createdAfter, createdBefore, updatedAfter, updatedBefore, sort i direction. Filtr po parentDataSet wymaga również parentId.
Filtry strukturalne mają format field:operator:value:
time:gte:3600
time:lt:28800
category:eq:Service Desk
title:contains:zgłoszenia
customId:startswith:ERP-WORKTIME-
location:notempty:Obsługiwane operatory to eq, ne, gt, gte, lt, lte, contains, startswith, endswith i notempty. Wartości zawierające spacje, dwukropki lub znaki specjalne koduj w URL.
Godziny pracy - wybór pól i dołączanie relacji
Jeżeli odpowiedź ma zawierać tylko dane potrzebne synchronizacji, użyj fields. Relacje obiektowe i relacje użytkowników dołącz przez include:
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"Dla godzin pracy obsługiwane wartości include to relationships i users. Każda z nich wymaga odpowiedniego scope'u odczytu. WorkTimes nie obsługuje include=files. Techniczne pola wymagają odpowiedniego scope'u technicznego, a fields=* nie omija kontroli uprawnień ani nie zwraca pól systemowych z koperty odpowiedzi.
Godziny pracy - statystyki i wartości pól
Statystyki pomagają szybko sprawdzić rozkład danych bez pobierania całej kolekcji. Przykład grupowania po kategorii:
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}" }
}Do pobrania wartości pasujących do wyszukiwania użyj 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"Statystyki i wartości są operacjami tylko do odczytu. Nie modyfikują rekordów godzin pracy.
Godziny pracy - minimalne utworzenie
Do utworzenia potrzebujesz itemType, pól w attributes oraz dokładnie jednej relacji parent. Każde żądanie POST musi mieć nowy Idempotency-Key:
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": "Obsługa zgłoszenia przez Service Desk",
"category": "Service Desk",
"isBillable": true
},
"relationships": [
{
"targetId": "{TICKET_ID}",
"targetDataSet": "tickets",
"targetItemType": "ticket",
"relationshipType": "parent"
}
]
}' \
"$BASE_URL/api/v1/worktimes"Prawidłowa odpowiedź to zwykle HTTP 201 Created. Zapisz data.id oraz ETag z nagłówka HTTP i z data.meta.etag.
Godziny pracy - utworzenie z lokalizacją i Agentem
Dodatkowe pola biznesowe, takie jak lokalizacja i dział, możesz przekazać w tym samym żądaniu. Agent jest relacją użytkownika i korzysta z osobnej tablicy userRelationships:
{
"itemType": "worktime",
"attributes": {
"customId": "ERP-WORKTIME-2026-0043",
"date": "2026-09-06T10:30:00Z",
"location": "Krakow",
"department": "IT",
"time": 1800,
"title": "Analiza problemu i kontakt z użytkownikiem",
"category": "Obsługa",
"isBillable": false
},
"relationships": [
{
"targetId": "{PROBLEM_ID}",
"targetDataSet": "problems",
"targetItemType": "problem",
"relationshipType": "parent"
}
],
"userRelationships": [
{
"targetId": "{APP_USER_ID}",
"targetDataSet": "users",
"relationshipType": "agent"
}
]
}targetId Agenta oznacza identyfikator aktywnego użytkownika aplikacji. Nie używaj w tym miejscu identyfikatora klienta z kolekcji clients. Jedna godzina pracy może mieć najwyżej jednego Agenta.
Godziny pracy - opcjonalny WorkTask
WorkTask może zostać dodany jako dodatkowa relacja obiektowa. Nie zastępuje głównego rodzica:
"relationships": [
{
"targetId": "{TICKET_ID}",
"targetDataSet": "tickets",
"targetItemType": "ticket",
"relationshipType": "parent"
},
{
"targetId": "{WORKTASK_ID}",
"targetDataSet": "worktasks",
"targetItemType": "worktask",
"relationshipType": "worktask"
}
]Jeden WorkTask może być przypisany tylko do jednej godziny pracy. Jeżeli jest już zajęty, API zwróci HTTP 400 z kodem validation_failed i informacją The WorkTask is already assigned to another WorkTime.. Wybierz wtedy wolny WorkTask albo pomiń tę relację. Nie zmieniaj technicznego pola workTaskId w attributes.
Godziny pracy - Idempotency-Key i bezpieczne powtórzenie
Idempotencja chroni przed podwójnym zapisem, gdy klient nie otrzyma odpowiedzi na czas. Przy ponowieniu dokładnie tej samej operacji wyślij identyczne body i ten sam klucz:
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"Powtórzenie z tym samym kluczem i tym samym body powinno zwrócić ten sam zasób, a nie utworzyć drugi rekord. Ten sam klucz użyty z innym body zakończy się konfliktem 409 idempotency_conflict. Dla nowej operacji wygeneruj nowy klucz. Nie twórz kolejnego rekordu tylko dlatego, że pierwsza odpowiedź nie dotarła do klienta.
Godziny pracy - odczyt pojedynczego rekordu
Po utworzeniu pobierz rekord po UUID. Możesz dołączyć relację rodzica, WorkTaska i Agenta:
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"Prawidłowa odpowiedź to HTTP 200 OK. Zwróć uwagę na data.attributes.time, date, title, category i isBillable, a także na data.relationships, data.userRelationships i data.meta.etag. ETag może być również zwrócony w nagłówku HTTP ETag.
Godziny pracy - edycja z ETag i If-Match
Przed zmianą pobierz świeży ETag. Aktualizuj wyłącznie pola, które mają się zmienić, i dołącz ten ETag w nagłówku 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": "Obsługa zgłoszenia - druga sesja",
"isBillable": false
}
}'Po HTTP 200 OK zapisz nowy ETag. Nie używaj ponownie poprzedniej wartości przy kolejnej zmianie.
Godziny pracy - nieaktualny lub brakujący ETag
API wymaga kontroli wersji, aby jedna integracja nie nadpisała zmiany zapisanej wcześniej przez inną osobę lub system. Pominięcie If-Match zwraca HTTP 428 Precondition Required z kodem if_match_required. Stary ETag zwraca HTTP 412 Precondition Failed z kodem if_match_failed:
HTTP/1.1 412 Precondition Failed
code: if_match_failed
HTTP/1.1 428 Precondition Required
code: if_match_requiredPo odpowiedzi 412 pobierz rekord ponownie, porównaj aktualne dane z planowaną zmianą i dopiero potem zdecyduj o ponownym PATCH. Nie uruchamiaj ślepej pętli retry. Odrzucona zmiana nie powinna zmienić czasu, rodzica ani relacji.
Godziny pracy - relacje obiektowe WorkTask
Dla relacji obiektowych używaj tras /relationships. Godziny pracy dopuszczają tylko targety tickets, changes, problems, releases i worktasks.
Dodanie WorkTaska do istniejącego rekordu:
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"
}'Listę odczytasz przez GET /api/v1/worktimes/{WORKTIME_ID}/relationships. Usunięcie relacji wymaga aktualnego ETagu:
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"Po każdej zmianie relacji odśwież ETag. Usunięcie WorkTaska nie usuwa godziny pracy.
Godziny pracy - zmiana głównego rodzica
Jeżeli czas ma zostać przeniesiony ze zgłoszenia do zmiany, użyj jednego PATCH z usunięciem starego rodzica i dodaniem nowego. Po operacji musi pozostać dokładnie jeden rodzic:
{
"relationshipsToRemove": [
{
"targetId": "{OLD_TICKET_ID}",
"targetDataSet": "tickets",
"targetItemType": "ticket",
"relationshipType": "parent"
}
],
"relationshipsToAdd": [
{
"targetId": "{NEW_CHANGE_ID}",
"targetDataSet": "changes",
"targetItemType": "change",
"relationshipType": "parent"
}
]
}Operację wyślij na /api/v1/worktimes/{WORKTIME_ID} z bieżącym If-Match i nowym Idempotency-Key. Nie wykonuj najpierw osobnego usunięcia, ponieważ przez chwilę rekord nie miałby wymaganego rodzica.
Godziny pracy - relacja Agenta
Agent jest użytkownikiem przypisanym do czasu pracy. Do jego obsługi służą trasy /user-relationships, a nie zwykłe /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"
}'Listę pobierz przez GET /api/v1/worktimes/{WORKTIME_ID}/user-relationships?relationshipType=agent. Aby wymienić Agenta, najpierw usuń bieżące powiązanie, a następnie dodaj nowe, za każdym razem używając świeżego ETagu. Jedna godzina pracy może mieć maksymalnie jednego Agenta.
targetId musi wskazywać aktywnego i widocznego użytkownika aplikacji. Nie jest to Clients.Id ani identyfikator firmy.
Godziny pracy - batch relacji
Jeżeli jedna operacja ma dodać lub usunąć kilka relacji, użyj relationships:batch. Dla relacji Agenta istnieje analogiczna trasa 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": []
}'Odpowiedź zawiera liczniki added, removed i skipped. Przy rodzicu zachowaj zasadę dokładnie jednej relacji parent; nie wysyłaj batcha, który usuwa jedynego rodzica.
{
"data": {
"added": 1,
"removed": 0,
"skipped": 0
},
"meta": { "requestId": "{REQUEST_ID}" }
}Godziny pracy - operacje batch dla rekordów
Dla wielu godzin pracy użyj POST /api/v1/worktimes:batch. Każdy element określa operację create, update albo delete.
{
"items": [
{
"operation": "create",
"create": {
"itemType": "worktime",
"attributes": {
"customId": "ERP-WORKTIME-BATCH-0001",
"date": "2026-09-06T10:00:00Z",
"time": 1800,
"title": "Czas z importu ERP",
"category": "ERP",
"isBillable": true
},
"relationships": [
{
"targetId": "{TICKET_ID}",
"targetDataSet": "tickets",
"targetItemType": "ticket",
"relationshipType": "parent"
}
]
}
}
]
}Element update i delete musi mieć własne id oraz ifMatch. Wartość ifMatch przekazuj jako string zawierający aktualny ETag:
{
"items": [
{
"operation": "update",
"id": "{WORKTIME_ID}",
"ifMatch": "\"{CURRENT_ETAG}\"",
"update": {
"attributes": {
"time": 2700
}
}
},
{
"operation": "delete",
"id": "{OTHER_WORKTIME_ID}",
"ifMatch": "\"{OTHER_CURRENT_ETAG}\""
}
]
}Sprawdzaj wynik każdego elementu po index, operation i status. Przy częściowym wyniku API może zwrócić HTTP 207 Multi-Status. Jeden błąd nie jest potwierdzeniem powodzenia pozostałych elementów.
Godziny pracy - usunięcie rekordu
Usunięcie jest nieodwracalne z punktu widzenia Public API. Przed operacją pobierz świeży ETag i upewnij się, że UUID oraz customId wskazują właściwy rekord:
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"Prawidłowy wynik to HTTP 200 OK z data=true. Następnie sprawdź odczyt po 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"Po usunięciu powinien pojawić się HTTP 404 Not Found z kodem workTime_not_found. Warto również sprawdzić listę filtrowaną po customId, aby potwierdzić, że rekord nie został zwrócony ponownie.
Godziny pracy - brak plików i pina
Godziny pracy nie posiadają własnego modułu plików ani przypięcia. Nie używaj tych tras:
/api/v1/worktimes/{WORKTIME_ID}/files
/api/v1/worktimes/{WORKTIME_ID}/pinPróba traktowania godziny pracy jak zasobu z plikami albo obiektu z pinem nie jest obsługiwaną operacją kontraktu. Jeżeli czas wymaga dokumentu lub załącznika, zapisz plik na obiekcie, który obsługuje pliki, na przykład na zgłoszeniu albo dokumencie, i zachowaj własne powiązanie w systemie integrującym.
Godziny pracy - błędy i limity
validation_failedauthentication_requiredpublic_api_scope_deniedworkTime_parent_access_deniedworkTime_not_foundidempotency_conflictif_match_failedif_match_requiredidempotency_key_requiredrate_limit_exceededtenant_context_unavailablePo błędzie zapisuj HTTP status, code i meta.requestId, ale nigdy sekret klucza. Dla HTTP 400 odczytaj również pole errors, ponieważ wskazuje konkretne pole albo element tablicy.
Limit zapytań i limit batcha odczytaj z data.capabilities.limits. Nagłówki X-RateLimit-Limit i X-RateLimit-Remaining pozwalają dostosować tempo synchronizacji.
Godziny pracy - synchronizacja i n8n
Do synchronizacji z ERP, helpdeskiem lub n8n używaj customId nadawanego przez system zewnętrzny, na przykład ERP-WORKTIME-{external-id}. Nie traktuj tytułu jako klucza deduplikacji, ponieważ dwie sesje mogą mieć taki sam opis.
W n8n wystarczy node HTTP Request. W credentialu nagłówków zapisz X-Codenica-Client-Id, X-Codenica-Client-Secret oraz Accept: application/json. Dla POST dodaj unikalny Idempotency-Key.
{
"itemType": "worktime",
"attributes": {
"customId": "N8N-WORKTIME-{{$execution.id}}",
"date": "2026-09-06T09:00:00Z",
"time": 1800,
"title": "Czas zsynchronizowany przez n8n",
"category": "Automatyzacja",
"isBillable": true
},
"relationships": [
{
"targetId": "{{$json.ticketId}}",
"targetDataSet": "tickets",
"targetItemType": "ticket",
"relationshipType": "parent"
}
]
}Przy aktualizacji workflow powinien najpierw pobrać rekord, zachować data.meta.etag, a potem wysłać PATCH z tym ETagiem. Przy HTTP 412 pobierz rekord ponownie i rozstrzygnij konflikt. Przy HTTP 429 zastosuj ograniczony backoff. Sekret nie powinien trafiać do node Code, danych wejściowych workflow ani historii wykonania.
Godziny pracy - zalecana kolejność operacji
Bezpieczny przebieg integracji wygląda następująco:
1. Ustaw BASE_URL zgodny z Cloud albo On-Premise.
2. Utwórz klucz w Ustawienia -> API i zapisz sekret.
3. Pobierz GET /api/v1/context i sprawdź bazę, scope'y oraz limity.
4. Pobierz GET /api/v1/worktimes/schema.
5. Wybierz jeden dostępny Ticket, Change, Problem albo Release.
6. Opcjonalnie wybierz wolny WorkTask i aktywnego Agenta.
7. Utwórz rekord z jednym rodzicem i nowym Idempotency-Key.
8. Zachowaj UUID oraz ETag z odpowiedzi.
9. Odczytuj rekord z include=relationships,users.
10. Przy zmianie używaj świeżego If-Match i nowego Idempotency-Key.
11. Relacje zmieniaj przez właściwy endpoint, a nie przez pola techniczne.
12. Przy imporcie masowym sprawdzaj wynik każdego elementu batcha.
13. Przed DELETE pobierz świeży ETag.
14. Po DELETE potwierdź HTTP 404 i brak customId na liście.Najważniejsze zasady są proste: jedna godzina pracy ma jednego głównego rodzica, czas zapisuje się w sekundach, WorkTask jest opcjonalny i zajmuje jedno miejsce, Agent jest relacją użytkownika, a pliki i pin nie są obsługiwane. Idempotencja chroni zapis, a ETag chroni konkretną wersję rekordu.
