Work Times - in Codenica API

Before sending the first request for Work Times, create an API key in your installation settings. If the key does not exist yet, open the page Codenica API - Introduction in a new tab. It explains the shared rules for key creation, authentication, API address selection and secret storage.

A Work Time records the time spent handling a particular ticket, change, problem or release. This is deliberately a narrow object: it has no own files, pin or unrestricted relationship catalogue. It can have exactly one primary parent, an optional Work Task and an optional Agent.

In the technical contract, the collection is named worktimes and a single record has itemType set to worktime.


Work Times - API address and installation choice

All Work Time routes start with:

{BASE_URL}/api/v1/worktimes

BASE_URL contains the protocol and application host but not the final /api/v1.

Codenica Cloud: use the real domain or subdomain assigned to the company.

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

Codenica On-Premise: Codenica Discovery registers the default local address http://codenica.local:5150.

If the administrator exposes the installation through a company domain, HTTPS, reverse proxy or another port, use the exact address supplied for that installation. Do not assume that an On-Premise user should use localhost. That name identifies the computer running the HTTP client, not necessarily the Codenica server.

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

If a reverse proxy or administrator gives another address, use that exact address.

The address http://localhost:5050 is only for local development where the API runs on the same computer. It is not the standard Cloud address or the default On-Premise address.


Work Times - API key and licence limits

Create the key in Settings -> API -> API Keys. A separate key for each application and environment makes rotation, auditing and access removal easier.

Licence
Codenica API
Maximum number of keys
Starter
not available
0
Plus
available
50
Enterprise
available
100

The secret is displayed only while the key is created or rotated. Store the Client ID and Client Secret in a secure secret store. Deleting the key removes its record and frees its place in the licence limit.


Work Times - request authentication

Authenticate every Codenica API request with two headers:

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

An external integration does not need a panel session or the user's Bearer JWT. Keep the secret on the server side or in a secret manager.

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"

The secret must not be placed in browser code, a repository, a URL, command history or logs.


Work Times - check the connection context

Read the context before downloading a list or recording time. This confirms that the address points to the intended database and that the key has the required scopes and limits.

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

Check data.tenant.id, data.tenant.name, data.tenant.resolvedDomain, data.caller.clientId and data.caller.scopes. data.caller.authentication should be api_key. Also check data.capabilities.supportsETag, supportsIdempotency and 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}"
  }
}

If the context identifies another company or lacks a required scope, correct the address or issue a key with the right permissions. Do not try to select a database by placing another identifier in the request body.


Work Times - schema and supported fields

The schema is the source of truth for the current Work Times contract. It returns field types, writability, restrictions, technical fields and permitted relationship targets.

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"]
  }
}

Before mapping fields, inspect readable, writable, required, technical and maxLength. Do not build an integration solely from an example response.


Work Times - primary parent and visibility

Every Work Time must have exactly one primary operational parent. The parent can be a ticket, change, problem or release.

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

A record cannot be created without this relationship, and the last parent of an existing record cannot be removed. Visibility follows access to the primary parent. Access to a Work Task alone is not enough for the Work Time to appear in the list.

Send the parent relationship in the relationships array. Do not write ticketId, changeId, problemId or releaseId inside attributes.


Work Times - writable and system fields

The following fields carry business data in attributes. If the schema of the current database specifies different limits, the schema takes precedence.

Field
Type
Use
customId
string
External integration identifier, maximum 500 characters.
date
date-time
Date or time when the work was performed in ISO 8601 format.
location
string
Place where the work was performed, maximum 300 characters.
department
string
Responsible department or unit, maximum 300 characters.
time
integer
Duration in seconds, zero or greater.
title
string
Session or activity description, maximum 1000 characters.
category
string
Billing or reporting category, maximum 300 characters.
isBillable
boolean
Whether the time can be billed.
{
  "date": "2026-09-06T09:00:00Z",
  "time": 5400,
  "title": "Service Desk ticket handling",
  "category": "Service Desk",
  "isBillable": true
}

For a 90-minute entry:

isAuto, agentId, workTaskId, ticketId, changeId, problemId, releaseId, creator, updater, dateCreated, dateUpdated, importId, importSource and dateImported are technical or system fields. Do not write them in attributes; manage Agent and Work Task through their dedicated relationship endpoints.


Work Times - main endpoints

The Work Times collection provides the following routes:

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}

Every read requires the relevant group's read scope. Every mutation also requires a unique Idempotency-Key. The current ETag is required for editing, relationship changes and deletion.


Work Times - listing and pagination

Read the list page by page. Set sorting explicitly so that subsequent reads use a predictable order:

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"

The response contains data.items and page information.

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

Read the next page only when hasNextPage is true. Read the maximum pageSize from the limits returned in context.


Work Times - search and filters

Use search for simple text search. For synchronization, a stable customId, UUID or primary-parent filter is more reliable:

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"

Available parameters include customId, date, dateAfter, dateBefore, location, department, time, title, category, isBillable, agentId, createdAfter, createdBefore, updatedAfter, updatedBefore, sort and direction. A parentDataSet filter also requires parentId.

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

Structural filters use the format field:operator:value:

Supported operators are eq, ne, gt, gte, lt, lte, contains, startswith, endswith and notempty. URL-encode values containing spaces, colons or special characters.


Work Times - field selection and included data

Use fields when the response should contain only data needed for synchronization. Include object and user relationships through 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"

For Work Times, supported include values are relationships and users. Work Times does not support include=files. Technical fields require the relevant technical scope, and fields=* does not bypass permissions or expose system fields from the response envelope.


Work Times - statistics and field values

Statistics provide a quick view of the data distribution without downloading the whole collection. The following example groups entries by category:

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}" }
}

To obtain values matching a search term, use 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"

Statistics and values are read-only operations. They do not modify Work Time records.


Work Times - minimal creation

A minimal create request needs itemType, fields in attributes and exactly one parent relationship. Every POST request must have a new 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": "Service Desk ticket handling",
      "category": "Service Desk",
      "isBillable": true
    },
    "relationships": [
      {
        "targetId": "{TICKET_ID}",
        "targetDataSet": "tickets",
        "targetItemType": "ticket",
        "relationshipType": "parent"
      }
    ]
  }' \
  "$BASE_URL/api/v1/worktimes"

The expected response is normally HTTP 201 Created. Store data.id and the ETag from both the HTTP header and data.meta.etag.


Work Times - creation with location and Agent

Additional business fields such as location and department can be sent in the same request. An Agent is a user relationship and belongs in the separate userRelationships array:

{
  "itemType": "worktime",
  "attributes": {
    "customId": "ERP-WORKTIME-2026-0043",
    "date": "2026-09-06T10:30:00Z",
    "location": "Krakow",
    "department": "IT",
    "time": 1800,
    "title": "Problem analysis and user contact",
    "category": "Operations",
    "isBillable": false
  },
  "relationships": [
    {
      "targetId": "{PROBLEM_ID}",
      "targetDataSet": "problems",
      "targetItemType": "problem",
      "relationshipType": "parent"
    }
  ],
  "userRelationships": [
    {
      "targetId": "{APP_USER_ID}",
      "targetDataSet": "users",
      "relationshipType": "agent"
    }
  ]
}

targetId for an Agent is the identifier of an active application user. Do not use a client identifier from the clients collection. One Work Time can have at most one Agent.


Work Times - optional Work Task

A Work Task may be added as an additional object relationship. It does not replace the primary parent:

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

One Work Task can be assigned to only one Work Time. If it is already occupied, the API returns HTTP 400 with validation_failed and the message The WorkTask is already assigned to another WorkTime.. Select a free Work Task or omit this relationship. Do not change the technical workTaskId field in attributes.


Work Times - Idempotency-Key and safe retries

Idempotency prevents a duplicate write when the client does not receive a response in time. When retrying, send the identical body with the same key:

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"

The same key and body should return the same resource instead of creating another record. Reusing the key with a different body produces 409 idempotency_conflict. Generate a new key for a new operation.


Work Times - read one record

After creation, read the record by UUID. The primary parent, Work Task and Agent can be requested with the include parameters:

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"

The normal result is HTTP 200 OK. Check data.attributes.time, date, title, category and isBillable, together with data.relationships, data.userRelationships and data.meta.etag.


Work Times - update with ETag and If-Match

Read a fresh ETag before changing a record. Update only the fields that should change and send that ETag in 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": "Service Desk ticket handling - second session",
      "isBillable": false
    }
  }'

After HTTP 200 OK, store the new ETag. Do not reuse the previous value for the next change.


Work Times - stale or missing ETag

Version control prevents one integration from overwriting a change made earlier by another person or system. Omitting If-Match returns HTTP 428 Precondition Required with if_match_required. An old ETag returns HTTP 412 Precondition Failed with if_match_failed:

HTTP/1.1 412 Precondition Failed
code: if_match_failed

HTTP/1.1 428 Precondition Required
code: if_match_required

After a 412, read the record again, compare the current data with the planned change and then decide whether to send another PATCH. Do not run a blind retry loop. A rejected change must not alter the time, parent or relationships.


Work Times - object relationships with Work Task

Use /relationships routes for object relationships. Work Times accept only tickets, changes, problems, releases and worktasks targets.

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"
  }'

Read the collection through GET /api/v1/worktimes/{WORKTIME_ID}/relationships. Removing a relationship requires the current ETag:

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"

Refresh the ETag after every relationship change. Removing a Work Task does not remove the Work Time.


Work Times - change the primary parent

To move time from a ticket to a change, use one PATCH that removes the old parent and adds the new one. Exactly one parent must remain after the operation:

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

Send the operation to /api/v1/worktimes/{WORKTIME_ID} with the current If-Match and a new Idempotency-Key. Do not remove the old parent in a separate request, because the record would temporarily have no required parent.


Work Times - Agent relationship

An Agent is a user assigned to the Work Time. Use /user-relationships, not ordinary /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"
  }'

Read the list with GET /api/v1/worktimes/{WORKTIME_ID}/user-relationships?relationshipType=agent. To replace an Agent, remove the current relationship and add the new one, using a fresh ETag each time. One Work Time can have at most one Agent.

targetId must identify an active and visible application user. It is not Clients.Id and it is not a company identifier.


Work Times - relationship batch

Use relationships:batch when one operation should add or remove several relationships. Agent relationships have the corresponding user-relationships:batch route:

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": []
  }'

The response contains added, removed and skipped counters. Keep exactly one parent relationship; never send a batch that removes the only parent.

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

Check the response counters before treating the operation as complete.


Work Times - batch operations for records

For several Work Times, use POST /api/v1/worktimes:batch. Each item specifies create, update or delete:

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

An update or delete item needs its own id and ifMatch. Send ifMatch as a string containing the current ETag:

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

Inspect every item by index, operation and status. A partial result may use HTTP 207 Multi-Status; one failed item does not confirm that the remaining items succeeded.


Work Times - delete a record

Deletion is irreversible from the point of view of Public API. Before deleting, read a fresh ETag and confirm that the UUID and customId identify the intended record:

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"

A successful result is HTTP 200 OK with data=true. Then read the UUID again:

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"

The follow-up read should return HTTP 404 Not Found with workTime_not_found. Also check a list filtered by customId so the record is not returned again.


Work Times - no files and no pin

Work Times do not have their own file module or pin action. Do not use these routes:

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

Treating a Work Time as an object with files or a pin is outside the supported contract. If a time entry needs a document or attachment, store the file on an object that supports files, such as a ticket or document, and keep the connection in the integrating system.


Work Times - errors and limits

HTTP
Code
Meaning and response
400
validation_failed
Invalid field, parent or occupied Work Task. Correct the data.
401
authentication_required
Check both key headers and the installation address.
403
public_api_scope_denied
Add the required scope to the key in Settings -> API.
403
workTime_parent_access_denied
Use a parent that is available to the caller.
404
workTime_not_found
Check the UUID, address and access to the record.
409
idempotency_conflict
This idempotency key was used with a different body.
412
if_match_failed
Read the record and use a new ETag.
428
if_match_required
Add the current If-Match value.
428
idempotency_key_required
Add a unique Idempotency-Key to the mutation.
429
rate_limit_exceeded
Use backoff and respect the rate-limit headers.
503
tenant_context_unavailable
Retry with backoff without changing the body.

For an error, record the HTTP status, code and meta.requestId, but never the key secret. For HTTP 400, also inspect errors because it identifies the exact field or array item.

Read request and batch limits from data.capabilities.limits. The X-RateLimit-Limit and X-RateLimit-Remaining headers help adjust synchronization speed.


Work Times - synchronization and n8n

For synchronization with an ERP, helpdesk or n8n, use a customId assigned by the external system, such as ERP-WORKTIME-{external-id}. Do not use the title as the deduplication key because two sessions may have the same description.

In n8n, an HTTP Request node is sufficient. Store X-Codenica-Client-Id, X-Codenica-Client-Secret and Accept: application/json in the header credential. Add a unique Idempotency-Key to POST requests.

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

For an update, the workflow should read the record first, keep data.meta.etag and then send PATCH with that ETag. On HTTP 412, read again and resolve the conflict. On HTTP 429, use a bounded backoff. Never put the secret in a Code node, workflow input or execution history.


Work Times - recommended operation order

A safe integration sequence looks like this:

1. Set BASE_URL for Cloud or On-Premise.
2. Create a key in Settings -> API and save the secret.
3. Read GET /api/v1/context and check the database, scopes and limits.
4. Read GET /api/v1/worktimes/schema.
5. Select one available Ticket, Change, Problem or Release.
6. Optionally select a free Work Task and active Agent.
7. Create the record with one parent and a new Idempotency-Key.
8. Keep the UUID and ETag from the response.
9. Read the record with include=relationships,users.
10. Use a fresh If-Match and new Idempotency-Key for changes.
11. Change relationships through the correct endpoint, not technical fields.
12. For bulk imports, inspect every batch item.
13. Read a fresh ETag before DELETE.
14. Confirm HTTP 404 and absence of the customId after DELETE.

The essential rules are simple: one Work Time has one primary parent, time is stored in seconds, Work Task is optional and occupies one place, Agent is a user relationship, and files and pin are not supported. Idempotency protects the write and ETag protects the specific record version.