Problems in Codenica API

You start working with problems through Codenica API by creating a key in Codenica settings. If you have not created a key yet, open Codenica API - introduction in a new tab. It explains the shared rules for issuing keys, storing the secret and authenticating requests.

The technical module name is problems, and the type of an individual object is problem. A problem records the cause or source of recurring incidents. In addition to descriptive data, it has diagnostic fields isKnown, symptoms, rootCause and impactInfo.

The following sections cover the address, scopes, schema, lists, filtering, creation, editing, ETag, batch operations, relationships, users, files, workflow actions, escalation, approval and deletion of problems.

The examples use the prefix PUBLIC-API-PROBLEM-20260905131727. Replace it with your own identifier and adapt email addresses, IDs and field values to the data in your database.


Problems - API address and installation choice

All problem routes start with:

{BASE_URL}/api/v1/problems

In Codenica Cloud, use the public domain assigned to the relevant installation:

export BASE_URL="https://twoja-firma.codenica.com"

In the default On-Premise installation, the address registered locally by Codenica Discovery is:

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

If an administrator has exposed the installation under a company domain, through a reverse proxy, with HTTPS or on another port, use the exact address provided for that installation:

export BASE_URL="https://api.twoja-firma.example"

Do not use localhost if the integration runs on a different computer from the API. Do not send tenantId in the body or query string. The correct database is selected from the host address to which the integration connects.


Problems - API key and license limits

Create an API key in Codenica under Settings - API - API Keys. The secret is shown only once, immediately after the key is created or rotated. At that moment, save the Client ID and Client Secret in the secure store used by the integration.

Codenica API is available with Plus and Enterprise licenses. Plus allows up to 50 active keys, and Enterprise up to 100. Starter does not provide Codenica API. Create a separate key for each application and environment so that you can limit its scopes, rotate its secret or remove its access independently.

License
Codenica API
Active keys
Starter
unavailable
0
Plus
available
up to 50
Enterprise
available
up to 100

The default key lifetime is 90 days if you do not set another date in the panel. The maximum lifetime is 5 years. Expired or inactive keys do not occupy an active slot, but remain visible until you use Delete. Deleting the record is permanent.


Problems - authentication and secure requests

Authenticate every Codenica API request with the two key headers:

export CLIENT_ID="cna_twoj_client_id"
export CLIENT_SECRET="cns_twoj_client_secret"

curl --request GET --url "$BASE_URL/api/v1/problems?page=1&pageSize=25" \
  --header "Accept: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

An external integration does not need the administrator's JWT or cookies from the Codenica panel. Do not place the key in a repository, code delivered to a browser, a URL, command history or logs. Use HTTPS outside local testing.

Keep meta.requestId from the response. It helps diagnose a specific request, but does not replace the problem identifier and must not be treated as a secret.


Problems - checking the connection context

Fetch the context before the first write. This lets you confirm that the address leads to the correct database and that the selected key has the required scopes:

curl --request GET --url "$BASE_URL/api/v1/context" \
  --header "Accept: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

Verify the following in the response:

  • data.apiVersion and data.contractVersion;
  • data.tenant.id, data.tenant.name and data.tenant.resolvedDomain;
  • data.caller.authentication equal to api_key;
  • the presence of problems in data.capabilities.resources;
  • the scopes assigned to the key;
  • page, batch, file and request limits.

If the context points to another database or does not contain a required scope, stop the integration and correct the address or key. Scopes cannot be added to an individual request.


Problems - permission scopes

Complete problem support requires scopes matching the operations you intend to use:

problems:read
problems:write
problems:delete
problems:schema
problems:stats
problems:relationships:read
problems:relationships:write
problems:users:read
problems:users:write
problems:files:read
problems:files:write
problems:technical:read
problems:technical:write
problems:pin:write
problems:spam:write
problems:reopen:write
problems:rating:write
problems:escalation:write
problems:approval:write

For ordinary reads, problems:read is enough. Schema and statistics require the separate scopes problems:schema and problems:stats. Reading relationships, users and files requires the corresponding read scopes. Write operations use the matching :write scopes.

Relationships with other modules also require read access to the target module, for example assets:read, documents:read, tickets:read or solutions:read. To create an approval and record its decision, add the scopes required by the approvals module itself. Grant scopes according to the principle of least privilege.


Problems - schema and diagnostic fields

The schema shows which fields can be read and written in a particular database. Fetch it before preparing a form or mapping:

curl --request GET --url "$BASE_URL/api/v1/problems/schema" \
  --header "Accept: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

For each field, check among other things readable, writable, required, technical, unique and maxLength. The schema also returns dictionaries and available relationship targets.

A minimal problem write currently requires subject and requesterEmail. Problems have their own diagnostic group:

Group
Fields
Use
Basic
subject, requesterEmail, description, comments
problem description and requester
Classification
type, status, priority, impact, urgency, severity
handling method and importance
Diagnostics
isKnown, symptoms, rootCause, impactInfo
known problem, symptoms, cause and impact
Integration
source, services, tags, externalNumber, referenceNumber
link to another system

The pin and isSpam fields are technical and are changed through dedicated actions. System fields and read-only fields, including rating and escalation data, should not be sent in an ordinary PATCH. Problems do not support the cost fields currency, estimatedCost and totalValue known from other modules.


Problems - basic endpoints

The most frequently used problem routes are:

  • GET /api/v1/problems - problem list;
  • GET /api/v1/problems/{id} - individual problem;
  • POST /api/v1/problems - creation;
  • PATCH /api/v1/problems/{id} - partial edit;
  • DELETE /api/v1/problems/{id} - deletion;
  • GET /api/v1/problems/schema - field and relationship schema;
  • GET /api/v1/problems/stats - statistics;
  • GET /api/v1/problems/values - values used in filters;
  • POST /api/v1/problems:batch - create, update and delete operations.

Relationships, users, files, workflow actions and approvals have separate routes. This allows an integration to receive only the permissions it actually needs.


Problems - listing and pagination

Fetch the list page by page. Even with a small number of records, provide the page number and size explicitly:

curl --request GET --url "$BASE_URL/api/v1/problems?page=1&pageSize=25" \
  --header "Accept: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

The response contains data.items and the page, pageSize, totalItems, totalPages and hasNextPage values. Fetch subsequent pages while hasNextPage is true:

curl --request GET --url "$BASE_URL/api/v1/problems?page=2&pageSize=25&sort=dateUpdated&direction=desc" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

For synchronization, sorting by dateUpdated and remembering the last processed records is usually the most convenient approach. Do not set pageSize above the limit returned in the context.


Problems - search, filters and sorting

You can combine list parameters. The following example searches by identifier, limits the result to the problem type and known problems, then sorts by update date:

curl --get --url "$BASE_URL/api/v1/problems" \
  --data-urlencode "itemType=problem" \
  --data-urlencode "customId=PUBLIC-API-PROBLEM-20260905131727-SOURCE" \
  --data-urlencode "isKnown=true" \
  --data-urlencode "sort=dateUpdated" \
  --data-urlencode "direction=desc" \
  --data-urlencode "page=1" \
  --data-urlencode "pageSize=25" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

For daily synchronization, the parameters search, status, priority, subject, requesterEmail, source, externalNumber, referenceNumber, symptoms, rootCause, impactInfo, createdAfter, createdBefore, updatedAfter and updatedBefore are also useful when available in the current schema.

A structural filter has the format field:operator:value. Available operators are eq, ne, in, contains, startsWith, endsWith, empty, notEmpty, gt, gte, lt and lte:

curl --get --url "$BASE_URL/api/v1/problems" \
  --data-urlencode "filter=status:eq:Closed" \
  --data-urlencode "filter=rootCause:contains:connection" \
  --data-urlencode "page=1" \
  --data-urlencode "pageSize=25" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

Encode text values and dates according to URL rules. Do not assume that a dictionary is identical in two databases.


Problems - field selection and included data

The fields parameter limits the response to the fields needed by the integration. The include parameter adds related data:

curl --get --url "$BASE_URL/api/v1/problems/{PROBLEM_ID}" \
  --data-urlencode "fields=subject,requesterEmail,status,priority,isKnown,symptoms,rootCause,impactInfo" \
  --data-urlencode "include=files,relationships,users" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

When reading the full model, you can use fields=*. Including files, relationships and users requires the corresponding read scopes. fields does not bypass access control or reveal technical fields for which the key has no permission.

In the response, pay attention to data.id, data.itemType, data.attributes and data.meta. Read technical fields such as pin or isSpam, but change them through the dedicated actions described below.


Problems - statistics and dictionary values

Statistics can, for example, count problems by the isKnown field. This is a read operation and does not modify records:

curl --get --url "$BASE_URL/api/v1/problems/stats" \
  --data-urlencode "field=isKnown" \
  --data-urlencode "limit=20" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

Fetch values of rootCause separately when you need them for suggestions or filters:

curl --get --url "$BASE_URL/api/v1/problems/values" \
  --data-urlencode "field=rootCause" \
  --data-urlencode "search=connection" \
  --data-urlencode "limit=20" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

Fetch the dictionary first and only then send a value in the body. This is particularly important for status, priority, type and diagnostic fields configured in a given database.


Problems - creating a record

Create a new problem with POST /api/v1/problems. Put the technical type problem in the body and writable fields in attributes. The example includes descriptive data, classification, integration details and the complete diagnostic group:

export IDEMPOTENCY_KEY="public-api-problem-create-20260905131727"

curl --request POST --url "$BASE_URL/api/v1/problems" \
  --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: $IDEMPOTENCY_KEY" \
  --data-raw '{
    "itemType": "problem",
    "attributes": {
      "customId": "PUBLIC-API-PROBLEM-20260905131727-SOURCE",
      "subject": "Codenica API integration problem",
      "requesterEmail": "[email protected]",
      "description": "Problem created through the Codenica API integration.",
      "comments": "Diagnostic example for the Problems module.",
      "source": "Codenica API",
      "type": "Standard",
      "status": "Closed",
      "priority": "High",
      "impact": "Medium",
      "urgency": "High",
      "severity": "High",
      "services": "Codenica API",
      "tags": "codenica-api,problem",
      "externalNumber": "EXT-CODENICA-API-PROBLEM-20260905131727",
      "referenceNumber": "REF-CODENICA-API-PROBLEM-20260905131727",
      "isKnown": true,
      "symptoms": "Users cannot complete the synchronization.",
      "rootCause": "Connection error with the external service.",
      "impactInfo": "Synchronization of the affected data group is delayed."
    },
    "customValues": [
      {
        "name": "description",
        "valuePattern": "[problem-test] PUBLIC-API-PROBLEM-20260905131727"
      }
    ]
  }'

The minimum is subject and requesterEmail, unless the schema imposes additional requirements. A successful creation returns HTTP 201, the data.id identifier, and an ETag in the header and in data.meta.etag. The key secret is not part of the object response.


Problems - idempotent creation

Repeating the same request with the same Idempotency-Key should return the same logical result rather than create a second problem:

curl --request POST --url "$BASE_URL/api/v1/problems" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "Idempotency-Key: public-api-problem-create-20260905131727" \
  --data-binary @problem.json

You can safely repeat the same key after an uncertain network result only for that same request. Do not use one key for two different operations. Generate a new key for a new body.

Idempotency-Key is required for every request that changes data, including editing, relationships, files, workflow actions and deletion. Reusing the same key with another route or body ends with an idempotency conflict.


Problems - reading and ETag

Read an individual problem with its included data as follows:

curl --get --url "$BASE_URL/api/v1/problems/{PROBLEM_ID}" \
  --data-urlencode "fields=*" \
  --data-urlencode "include=files,relationships,users" \
  --header "Accept: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

Keep the ETag from the response header. It should match data.meta.etag and meta.etag in the response envelope. After every successful write, action, relationship change or file operation, fetch or otherwise read the new ETag.

An ETag represents the version of one specific problem. Do not use an ETag fetched for one problem to modify another.


Problems - editing with If-Match

Editing is partial. Send only the fields that should change:

curl --request PATCH --url "$BASE_URL/api/v1/problems/{PROBLEM_ID}" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $PROBLEM_ETAG" \
  --header "Idempotency-Key: public-api-problem-update-20260905131727" \
  --data-raw '{
    "attributes": {
      "description": "Description updated through the integration.",
      "status": "Closed",
      "priority": "High",
      "isKnown": false,
      "symptoms": "Symptoms after renewed observation.",
      "rootCause": "Updated root-cause analysis.",
      "impactInfo": "Impact after applying the workaround."
    }
  }'

Do not change read-only fields in an ordinary PATCH, including rating, dateRating, dateFeedback, dateReopened and dateEscalated. Pin, spam, reopen, rating, escalation and approval have dedicated endpoints.

After a successful edit, you receive HTTP 200 and a new ETag. Save it before the next operation.


Problems - checking a stale If-Match

Every mutation except creation requires the current ETag. A missing header and an outdated value are rejected:

curl --request PATCH --url "$BASE_URL/api/v1/problems/{PROBLEM_ID}" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "Idempotency-Key: public-api-problem-missing-if-match-20260905131727" \
  --data-raw '{"attributes":{"isKnown":false}}'

Missing If-Match returns HTTP 428 with the if_match_required code. If you send an older ETag, you receive HTTP 412 with the if_match_failed code. A rejected request should not change the problem.

After HTTP 412, fetch the record again, read the new ETag and only then decide whether to retry the edit. Do not blindly overwrite changes made by another user or process.


Problems - batch operations

Batch is intended for multiple independent items. One request can contain create, update and delete operations:

curl --request POST --url "$BASE_URL/api/v1/problems:batch" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "Idempotency-Key: public-api-problem-batch-20260905131727" \
  --data-raw '{
    "items": [
      {
        "operation": "create",
        "create": {
          "itemType": "problem",
          "attributes": {
            "customId": "PUBLIC-API-PROBLEM-20260905131727-BATCH-A",
            "subject": "Batch problem A",
            "requesterEmail": "[email protected]",
            "source": "Codenica API",
            "type": "Standard",
            "status": "Open",
            "priority": "Medium",
            "isKnown": true,
            "symptoms": "Symptoms of problem A",
            "rootCause": "Root cause of problem A",
            "impactInfo": "Impact of problem A"
          }
        }
      },
      {
        "operation": "update",
        "id": "PROBLEM_UUID",
        "ifMatch": "\"CURRENT_ETAG\"",
        "update": {
          "attributes": {
            "isKnown": false,
            "rootCause": "New root-cause analysis"
          }
        }
      },
      {
        "operation": "delete",
        "id": "OTHER_PROBLEM_UUID",
        "ifMatch": "\"OTHER_CURRENT_ETAG\""
      }
    ]
  }'

Use the ETag of the specific record for batch update and delete. The idempotency key identifies the whole batch request, not an individual item. Check the response item by item using its index, status, identifier and error. Full success usually returns HTTP 200, while a partial result returns HTTP 207 Multi-Status. Batch is not an all-or-nothing transaction.


Problems - relationships with objects

Available relationship targets are returned by /api/v1/problems/schema. The current contract may include:

assets
documents
changes
tickets
problems
solutions
releases
notes
approvals
worktasks
requesteditems

The presence of a target in the schema does not mean that an accessible record exists in the particular database. Before adding a relationship, check the identifier, targetDataSet, targetItemType and read permission for the target.

For assets, documents, problems, changes, tickets, solutions and releases, use a relationshipType allowed by the schema, for example related. For notes, approvals, worktasks and requesteditems, leave relationshipType equal to null. Do not force related where it is not supported.

Adding several relationships:

curl --request POST --url "$BASE_URL/api/v1/problems/{PROBLEM_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: $PROBLEM_ETAG" \
  --header "Idempotency-Key: public-api-problem-relationships-20260905131727" \
  --data-raw '{
    "add": [
      {
        "targetId": "ASSET_UUID",
        "targetDataSet": "assets",
        "targetItemType": "computer",
        "relationshipType": "related"
      },
      {
        "targetId": "DOCUMENT_UUID",
        "targetDataSet": "documents",
        "targetItemType": "invoice",
        "relationshipType": "related"
      },
      {
        "targetId": "NOTE_UUID",
        "targetDataSet": "notes",
        "targetItemType": "note",
        "relationshipType": null
      }
    ],
    "remove": []
  }'

The HTTP 200 response contains the added, removed and skipped counters. skipped is not a transport error, so fetch the relationship collection after the operation and check its contents.


Problems - reading and removing relationships

Fetch the relationship list as follows:

curl --request GET --url "$BASE_URL/api/v1/problems/{PROBLEM_ID}/relationships?page=1&pageSize=100" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

Remove an individual relationship with the current ETag of the source problem. For a target that stores relationshipType, provide it in the query string:

curl --request DELETE --url "$BASE_URL/api/v1/problems/{PROBLEM_ID}/relationships/tickets/{TICKET_ID}?relationshipType=related" \
  --header "Accept: application/json, application/problem+json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $PROBLEM_ETAG" \
  --header "Idempotency-Key: public-api-problem-relationship-delete-20260905131727"

For a target such as notes, where the schema indicates that no relationship type is used, omit the relationshipType parameter. You can also remove a relationship through a partial edit:

curl --request PATCH --url "$BASE_URL/api/v1/problems/{PROBLEM_ID}" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $PROBLEM_ETAG" \
  --header "Idempotency-Key: public-api-problem-relationship-patch-20260905131727" \
  --data-raw '{
    "relationshipsToRemove": [
      {
        "targetId": "TICKET_UUID",
        "targetDataSet": "tickets",
        "targetItemType": "ticket",
        "relationshipType": "related"
      }
    ]
  }'

Removing a relationship does not delete the record that was its target. After every change, fetch the collection again and save the new problem ETag.


Problems - relationships with users

A problem can have the following user relationships:

  • agent - person responsible for handling the problem;
  • watcher - observer;
  • appUserRequester - application user who reported the problem.

For Problems, do not assume a clientRequester relationship. Targets are active users and are subject to location and department access controls.

Assigning an agent:

curl --request POST --url "$BASE_URL/api/v1/problems/{PROBLEM_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: $PROBLEM_ETAG" \
  --header "Idempotency-Key: public-api-problem-agent-20260905131727" \
  --data-raw '{
    "targetId": "USER_UUID",
    "targetDataSet": "users",
    "relationshipType": "agent"
  }'

Adding a watcher through batch:

curl --request POST --url "$BASE_URL/api/v1/problems/{PROBLEM_ID}/user-relationships:batch" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $PROBLEM_ETAG" \
  --header "Idempotency-Key: public-api-problem-watcher-20260905131727" \
  --data-raw '{
    "add": [
      {
        "targetId": "WATCHER_USER_UUID",
        "targetDataSet": "users",
        "relationshipType": "watcher"
      }
    ],
    "remove": []
  }'

Reading and removing relationships:

curl --request GET --url "$BASE_URL/api/v1/problems/{PROBLEM_ID}/user-relationships?page=1&pageSize=100" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

curl --request DELETE --url "$BASE_URL/api/v1/problems/{PROBLEM_ID}/user-relationships/users/{USER_ID}?relationshipType=agent" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $PROBLEM_ETAG" \
  --header "Idempotency-Key: public-api-problem-agent-delete-20260905131727"

You can also remove a watcher through batch by leaving add empty and placing the entry in remove. Read the new ETag after every change.


Problems - files

Before a file operation, read the current problem and its ETag. Uploading a file requires a multipart/form-data request:

curl --request POST --url "$BASE_URL/api/v1/problems/{PROBLEM_ID}/files?relationshipType=documentation" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $PROBLEM_ETAG" \
  --header "Idempotency-Key: public-api-problem-file-one-20260905131727" \
  --form "[email protected];type=text/plain"

File list:

curl --request GET --url "$BASE_URL/api/v1/problems/{PROBLEM_ID}/files?page=1&pageSize=100" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

A list item includes, among other things, id, name, fileName, contentType, size, relationshipType, isMain and downloadUrl. Treat downloadUrl as an API path, not as a public anonymous link. In Problems, isMain is always false.

Download the content as binary data:

curl --request GET --url "$BASE_URL/api/v1/problems/{PROBLEM_ID}/files/{FILE_ID}/content" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --output problem-evidence.txt

You can attach an existing file to another problem. The ETag then belongs to the target problem:

curl --request POST --url "$BASE_URL/api/v1/problems/{OTHER_PROBLEM_ID}/files/{FILE_ID}?relationshipType=manual" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $OTHER_PROBLEM_ETAG" \
  --header "Idempotency-Key: public-api-problem-file-attach-20260905131727"

Deleting a file:

curl --request DELETE --url "$BASE_URL/api/v1/problems/{PROBLEM_ID}/files/{FILE_ID}" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $PROBLEM_ETAG" \
  --header "Idempotency-Key: public-api-problem-file-delete-20260905131727"

Read the file-size limit from context before uploading. Do not load a large file into memory before checking the limit.


Problems - pinning, spam and reopening

Pinning, marking spam and reopening are separate actions. Each action requires the current ETag and a new idempotency key.

Pinning a problem:

curl --request POST --url "$BASE_URL/api/v1/problems/{PROBLEM_ID}/pin" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $PROBLEM_ETAG" \
  --header "Idempotency-Key: public-api-problem-pin-20260905131727" \
  --data-raw '{"pin":2}'

The pin value may be a number from 0 to 3 or null, according to the schema. Marking and unmarking spam:

curl --request POST --url "$BASE_URL/api/v1/problems/{PROBLEM_ID}/spam" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $PROBLEM_ETAG" \
  --header "Idempotency-Key: public-api-problem-spam-on-20260905131727" \
  --data-raw '{"isSpam":true}'

curl --request POST --url "$BASE_URL/api/v1/problems/{PROBLEM_ID}/spam" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $PROBLEM_ETAG" \
  --header "Idempotency-Key: public-api-problem-spam-off-20260905131727" \
  --data-raw '{"isSpam":false}'

Reopening a closed problem:

curl --request POST --url "$BASE_URL/api/v1/problems/{PROBLEM_ID}/reopen" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $PROBLEM_ETAG" \
  --header "Idempotency-Key: public-api-problem-reopen-20260905131727"

The required scopes are respectively problems:pin:write, problems:spam:write and problems:reopen:write. Fetch the problem again after every action and save the new ETag.


Problems - rating and escalation

Save a rating through a separate endpoint. You can include an escalation request with it:

curl --request POST --url "$BASE_URL/api/v1/problems/{PROBLEM_ID}/rating" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $PROBLEM_ETAG" \
  --header "Idempotency-Key: public-api-problem-rating-20260905131727" \
  --data-raw '{
    "rating": 4,
    "feedback": "Rating from the Codenica API integration.",
    "isEscalationRequested": true,
    "escalationRequestReason": "The problem requires second-line team analysis."
  }'

A rating ranges from 0 to 5 and requires problems:rating:write. Adding an escalation request also requires problems:escalation:write and the appropriate user permission. If you are saving only a rating, omit the escalation fields. After saving, read at least rating, feedback, the rating dates and escalationRequestReason.


Problems - approval and decision

You can create an approval as a separate approval object and connect it to the problem with a relationship. The person specified in approverId must be allowed to make the decision:

curl --request POST --url "$BASE_URL/api/v1/approvals" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "Idempotency-Key: public-api-problem-approval-create-20260905131727" \
  --data-raw '{
    "itemType": "approval",
    "approverId": "APPROVER_USER_UUID",
    "attributes": {
      "customId": "PUBLIC-API-PROBLEM-20260905131727-APPROVAL",
      "category": "Codenica API",
      "description": "Approval of the problem analysis."
    },
    "relationships": [
      {
        "targetId": "PROBLEM_UUID",
        "targetDataSet": "problems",
        "targetItemType": "problem"
      }
    ]
  }'

After creation, read the approval and record the decision through the problem route. APPROVAL_ID is the approval identifier, not a user identifier:

curl --request POST --url "$BASE_URL/api/v1/problems/{PROBLEM_ID}/approvals/{APPROVAL_ID}" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $PROBLEM_ETAG" \
  --header "Idempotency-Key: public-api-problem-approval-decision-20260905131727" \
  --data-raw '{
    "approve": true,
    "remark": "Approved through the Codenica API integration."
  }'

Reject by sending approve equal to false together with your own comment. After the decision, read the approval again and check its status or decision date. Then refresh the problem because the decision may change its ETag and process state.


Problems - deleting a record

Before deletion, fetch the problem again and use its current ETag:

curl --request DELETE --url "$BASE_URL/api/v1/problems/{PROBLEM_ID}" \
  --header "Accept: application/json, application/problem+json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $PROBLEM_ETAG" \
  --header "Idempotency-Key: public-api-problem-delete-20260905131727"

After HTTP 200, send a control GET for the same UUID. Expect HTTP 404 with the problem_not_found code or another code specified by the contract. If the problem has relationships, files or an approval, check the consequences in the schema and the requirements of your database before proceeding.

Deleting a problem should not replace history archiving. If the record must remain in the documentation, change its status or move the data to a system intended for retaining history.


Problems - errors, limits and security

Errors are returned as application/problem+json. Example response:

{
  "type": "https://docs.codenica.com/errors/problem_not_found",
  "title": "Problem not found.",
  "status": 404,
  "detail": "The problem does not exist or is outside the caller's access scope.",
  "instance": "/api/v1/problems/PROBLEM_UUID",
  "code": "problem_not_found",
  "requestId": "request-id-from-response"
}

In integration logic, rely primarily on status and code. The detail field is intended for people and its wording may change.

HTTP
Meaning
Response
400
invalid data or field outside the schema
read field errors and correct the mapping
401
missing or invalid authentication
check the host and key
403
missing scope or database access
change the key scope or user permissions
404
problem, file or relationship target does not exist or is not visible
verify the UUID and installation address
409
data, version or idempotency conflict
do not create a second record without analysis
412
stale ETag
fetch the problem and its new ETag
413
body or file is too large
check the limit in context
422
body or field values are invalid
correct the payload according to the schema
428
If-Match or Idempotency-Key is missing
add the correct header
429
request limit exceeded
use backoff and Retry-After

Read the X-RateLimit-Limit and X-RateLimit-Remaining headers. After 429, use increasing backoff and respect any Retry-After. Log the method, endpoint, status and requestId, but never the Client Secret or complete authentication headers.

Problem data may contain operational, personal and diagnostic information. Minimize the field set, use HTTPS and limit the integration's access to the specific database.


Problems - integration workflow

  1. Set BASE_URL for the correct Cloud or On-Premise installation.
  2. Create a separate key under Settings - API - API Keys and select the minimum scopes.
  3. Place the Client ID and Client Secret in a secure store.
  4. Send GET /api/v1/context and verify the database, caller and limits.
  5. Fetch GET /api/v1/problems/schema and map the diagnostic fields.
  6. Fetch the problem list or create a new record with POST and a unique Idempotency-Key.
  7. Save the problem UUID and its ETag.
  8. Refresh the ETag before every mutation and use a new idempotency key.
  9. Add relationships, users and files only after checking their targets in the schema.
  10. Run pin, spam, rating, escalation, reopen and approval decisions as separate operations.
  11. After 412, fetch the record, resolve the conflict and consciously retry the operation.
  12. For batch, check the result of every item because a partial error does not necessarily roll back successful items.
  13. Handle 429, log requestId without secrets and delete the key when the integration is no longer used.
  14. Confirm the current ETag before deletion and verify HTTP 404 afterwards.

This workflow lets you synchronize problems and their analysis with another system without relying on the internal database structure. If field configuration, the installation address or key scopes change, read the context and schema again.