Changes in Codenica API

You start working with changes through Codenica API by creating a key in Codenica settings. If you have not created one yet, open Codenica API - introduction in a new tab. It explains key creation, secret storage and the shared authentication rules.

The technical module name is changes and the type of one record is change. A change is used to plan and control a scheduled modification to a service, infrastructure or configuration. Alongside its basic data, it contains planning fields such as dates, risk, impact, rollout plan, backout plan and reason for change.

The following sections cover the complete workflow: checking the schema and dictionaries, lists, filtering, creation, ETag-based updates, batch operations, relationships, users, files, workflow actions, approvals and deletion.

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


Changes - API address and installation selection

All change routes start with:

{BASE_URL}/api/v1/changes

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

export BASE_URL="https://your-company.codenica.com"

In the default On-Premise installation, Codenica Discovery registers the service locally at:

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

If the administrator published the On-Premise 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.your-company.example"

Do not use localhost when the integrating program runs on another computer than the API. Do not send tenantId in the body or query string. The correct database is selected from the host address used by the integration.


Changes - API key and licence 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. Save the Client ID and Client Secret in the secure secret store used by the integration.

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

Select only the permissions required for the work with changes. A read-only integration may use changes:read. Creating approvals and processing approval decisions also requires scopes from the approvals module.


Changes - authentication and secure requests

Authenticate every public API request with the two key headers:

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

curl --request GET "$BASE_URL/api/v1/changes?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 an administrator JWT or Codenica panel cookies. Do not put the key in a repository, browser-delivered code, URL, command history or logs. Outside local testing, use HTTPS.

Keep meta.requestId from every response. It helps diagnose one particular request, but it is not a change identifier and must not be treated as a secret.


Changes - checking the connection context

Read the context before the first write. This confirms that the address points to the correct database and that the selected key has the required scopes:

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

Check:

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

If the context identifies another database or does not include a required scope, stop the integration and correct the address or key. Scopes cannot be granted in an individual request.


Changes - permission scopes

Full change handling requires scopes matching the operations you use:

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

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

If the integration creates, reads, changes or deletes approval records, add:

approvals:read
approvals:write
approvals:delete
approvals:relationships:read
approvals:relationships:write
approvals:technical:read
approvals:technical:write

Relationships with other modules also require read access to the target module, for example assets:read, documents:read, tickets:read, problems:read or releases:read. Grant scopes according to the principle of least privilege.


Changes - schema and planning fields

The schema shows which fields can be read and written in your database. Fetch it before preparing the request body:

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

For every field, check readable, writable, required, technical, unique, maxLength and the automatic-generation rules. The schema also returns the available relationship targets.

The current minimum for creating a change is subject and requesterEmail. Other fields depend on the configuration and workflow:

Group
Example fields
Use
Basic
subject, requesterEmail, description, comments
description and requester
Classification
type, status, priority, impact, urgency, severity
handling and impact assessment
Planning
datePlannedStart, datePlannedEnd, risk, impactInfo, rolloutPlan, backoutPlan, reasonForChange
dates, risk and implementation method
Integration
source, externalNumber, referenceNumber, services, tags
link to another system
Costs
currency, estimatedCost, totalValue
financial values

Read dictionary values such as status, priority, type and risk from the schema or values endpoint. Send dates in ISO 8601 format and numbers as JSON numbers. Do not assume that dictionaries are identical in two databases.

{
  "datePlannedStart": "2030-01-15T09:00:00Z",
  "datePlannedEnd": "2030-01-15T17:00:00Z",
  "risk": "Medium",
  "impactInfo": "Planned impact assessment",
  "rolloutPlan": "Deploy and verify health checks.",
  "backoutPlan": "Restore the previous version if verification fails.",
  "reasonForChange": "The current platform version requires a controlled update."
}

Changes - main endpoints

The most frequently used change routes are:

  • GET /api/v1/changes - list;
  • GET /api/v1/changes/{id} - one change;
  • POST /api/v1/changes - create;
  • PATCH /api/v1/changes/{id} - partial update;
  • DELETE /api/v1/changes/{id} - delete;
  • GET /api/v1/changes/schema - fields and relationship schema;
  • GET /api/v1/changes/stats - statistics;
  • GET /api/v1/changes/values - filter values;
  • POST /api/v1/changes: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.


Changes - listing and pagination

Read lists page by page. Even for a small collection, provide the page number and size explicitly:

curl --request GET "$BASE_URL/api/v1/changes?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. Continue while hasNextPage is true:

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

For synchronisation, sorting by dateUpdated and remembering the last processed records is useful. Do not set pageSize higher than the limit returned in the context.


Changes - search, filters and sorting

You can combine list parameters. This example finds a particular record, limits it to type change and risk Medium, then sorts the result by update date:

curl --get "$BASE_URL/api/v1/changes" \
  --data-urlencode "itemType=change" \
  --data-urlencode "customId=PUBLIC-API-CHANGE-20260905130127-SOURCE" \
  --data-urlencode "risk=Medium" \
  --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 regular synchronisation, the following parameters may also be useful: status, priority, subject, requesterEmail, source, externalNumber, referenceNumber, datePlannedStart, datePlannedEnd, createdAfter, createdBefore, updatedAfter and updatedBefore, when available in the current contract.

URL-encode text values and dates. Use search for a general search and the field-specific parameter supported by the schema for an exact filter. Do not assume that every dictionary value has an English name.


Changes - selecting fields and including data

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

curl --get "$BASE_URL/api/v1/changes/PUBLIC_CHANGE_UUID" \
  --data-urlencode "fields=subject,requesterEmail,status,priority,risk,datePlannedStart,datePlannedEnd" \
  --data-urlencode "include=files,relationships,users" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

For the complete 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.


Changes - statistics and dictionary values

Statistics can, for example, count changes by risk. This is a read-only operation and does not modify records:

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

Fetch field values separately when you need to build filter controls:

curl --get "$BASE_URL/api/v1/changes/values" \
  --data-urlencode "field=risk" \
  --data-urlencode "search=Medium" \
  --data-urlencode "limit=20" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

Read the dictionary first and send the returned value in the request body afterwards. This is particularly important for risk, status, priority and type, because their values may depend on the language configuration and settings of a particular database.


Changes - creating a record

Create a change with POST /api/v1/changes. Put the technical type change and writable fields inside attributes. The following example includes basic data, classification, integration information and the complete planning group:

export IDEMPOTENCY_KEY="public-api-change-create-20260905130127"

curl --request POST "$BASE_URL/api/v1/changes" \
  --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": "change",
    "attributes": {
      "customId": "PUBLIC-API-CHANGE-20260905130127-SOURCE",
      "subject": "PUBLIC-API-CHANGE-20260905130127 integration request",
      "requesterEmail": "[email protected]",
      "description": "Created by the Public API Changes flow",
      "comments": "ITSM integration change",
      "source": "Public API",
      "type": "Standard",
      "status": "Closed",
      "priority": "High",
      "impact": "Medium",
      "urgency": "High",
      "severity": "High",
      "services": "Codenica Public API",
      "tags": "public-api,change",
      "externalNumber": "EXT-PUBLIC-API-CHANGE-20260905130127",
      "referenceNumber": "REF-PUBLIC-API-CHANGE-20260905130127",
      "currency": "PLN",
      "estimatedCost": 12.5,
      "totalValue": 12.5,
      "datePlannedStart": "2030-01-15T09:00:00Z",
      "datePlannedEnd": "2030-01-15T17:00:00Z",
      "risk": "Medium",
      "impactInfo": "Planned impact assessment for the integration request",
      "rolloutPlan": "Deploy the approved change and verify health checks.",
      "backoutPlan": "Restore the previous release if verification fails.",
      "reasonForChange": "The current platform version requires a controlled update."
    }
  }'

A successful request returns 201 Created. Save data.id, the ETag from the HTTP header and data.meta.etag. The optional customValues property is intended for custom fields when the integration knows their configuration.

If the database requires different dictionary values, do not copy the names above without checking the schema and the values endpoint.


Changes - safe retries with Idempotency-Key

Send a unique Idempotency-Key with every operation that changes data. If the response is lost because of a network interruption, repeat exactly the same request with the same key:

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

Idempotency makes an identical retry return the result of the original operation instead of creating a second change. The same key must not be used with another body. Generate a new key for a new change, update, relationship, file or action.

Idempotency does not replace ETag. For operations that require version control, send the current If-Match at the same time.


Changes - reading a record and its ETag

After creating or finding an ID, read one change:

export CHANGE_ID="PUBLIC_CHANGE_UUID"

curl --get "$BASE_URL/api/v1/changes/$CHANGE_ID" \
  --data-urlencode "fields=*" \
  --data-urlencode "include=files,relationships,users" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

The ETag is returned in the HTTP ETag header and in data.meta.etag. Treat it as the version of this particular change and save it before every following mutation.

The ETag can change after editing fields, changing relationships, assigning a user, uploading or deleting a file, or executing a workflow action. After every successful mutation, read the new state or take the new ETag from the response.


Changes - updating with If-Match

Use PATCH for a partial update. Send only the fields that should change, the current ETag and a new idempotency key:

curl --request PATCH "$BASE_URL/api/v1/changes/$CHANGE_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: "CURRENT_ETAG"' \
  --header "Idempotency-Key: public-api-change-update-20260905130127" \
  --data-raw '{
    "attributes": {
      "description": "Updated change PUBLIC-API-CHANGE-20260905130127",
      "status": "Closed",
      "priority": "High",
      "risk": "Low",
      "impactInfo": "Updated impact assessment",
      "rolloutPlan": "Run the revised deployment plan and verify the service.",
      "backoutPlan": "Restore the previous release if the revised change fails.",
      "reasonForChange": "Updated implementation rationale."
    }
  }'

A current ETag returns 200 OK and a new record version. Change technical fields such as pin and isSpam through their dedicated endpoints. Do not update them with an ordinary PATCH when the schema marks them as read-only.

Planning dates remain ordinary change fields, so update them inside attributes. Before saving, check that the schema marks them as writable.


Changes - stale or missing ETag

If another process changed the record after you read it, an old ETag cannot overwrite the newer version. For a stale value, the API returns 412 Precondition Failed and if_match_failed:

{
  "status": 412,
  "code": "if_match_failed"
}

Missing If-Match on a mutation that requires it returns 428 Precondition Required with if_match_required:

{
  "status": 428,
  "code": "if_match_required"
}

After either response, read the change again, inspect its current state and decide whether your update is still needed. Then send it with the new ETag and a new idempotency key. Do not disable concurrency control in the integration.


Changes - batch operations

A batch combines several independent operations in one request. This example creates two changes:

curl --request POST "$BASE_URL/api/v1/changes:batch" \
  --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: public-api-change-batch-create-20260905130127" \
  --data-raw '{
    "items": [
      {
        "operation": "create",
        "create": {
          "itemType": "change",
          "attributes": {
            "customId": "PUBLIC-API-CHANGE-20260905130127-BATCH-A",
            "subject": "Batch change A",
            "requesterEmail": "[email protected]",
            "description": "Batch change A",
            "source": "Public API",
            "type": "Standard",
            "status": "Open",
            "priority": "Medium",
            "risk": "Low",
            "datePlannedStart": "2030-02-10T09:00:00Z",
            "datePlannedEnd": "2030-02-10T12:00:00Z"
          }
        }
      },
      {
        "operation": "create",
        "create": {
          "itemType": "change",
          "attributes": {
            "customId": "PUBLIC-API-CHANGE-20260905130127-BATCH-B",
            "subject": "Batch change B",
            "requesterEmail": "[email protected]",
            "description": "Batch change B",
            "source": "Public API",
            "type": "Standard",
            "status": "Open",
            "priority": "Low",
            "risk": "High",
            "datePlannedStart": "2030-02-11T09:00:00Z",
            "datePlannedEnd": "2030-02-11T12:00:00Z"
          }
        }
      }
    ]
  }'

The response contains items, the status of each operation and the succeeded and failed counters. Process each item separately. A batch is not an all-or-nothing transaction, so one failed item does not necessarily roll back the others.

Updates and deletion require the ETag of each record. A batch containing an update and a deletion may use this body:

{
  "items": [
    {
      "operation": "update",
      "id": "CHANGE_A_UUID",
      "ifMatch": "\"CHANGE_A_ETAG\"",
      "update": {
        "attributes": {
          "description": "Batch update A"
        }
      }
    },
    {
      "operation": "delete",
      "id": "CHANGE_B_UUID",
      "ifMatch": "\"CHANGE_B_ETAG\""
    }
  ]
}

One idempotency key identifies the entire batch request, not individual items. After the response, save the IDs and ETags only for records created or changed successfully.


Changes - object relationships

The possible relationship targets are returned by changes/schema. Depending on access and data, a change can be linked to assets, documents, other changes, tickets, problems and releases. Each target must be visible to the key, and targetItemType must match the actual object type.

To add one relationship:

curl --request POST "$BASE_URL/api/v1/changes/$CHANGE_ID/relationships" \
  --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: "CURRENT_ETAG"' \
  --header "Idempotency-Key: public-api-change-relationship-asset-20260905130127" \
  --data-raw '{
    "targetId": "ASSET_UUID",
    "targetDataSet": "assets",
    "targetItemType": "computer",
    "relationshipType": "related"
  }'

A single add returns 201 Created. Add several relationships in one request:

curl --request POST "$BASE_URL/api/v1/changes/$CHANGE_ID/relationships:batch" \
  --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: "CURRENT_ETAG"' \
  --header "Idempotency-Key: public-api-change-relationship-batch-20260905130127" \
  --data-raw '{
    "add": [
      {
        "targetId": "ASSET_UUID",
        "targetDataSet": "assets",
        "targetItemType": "computer",
        "relationshipType": "related"
      },
      {
        "targetId": "DOCUMENT_UUID",
        "targetDataSet": "documents",
        "targetItemType": "document",
        "relationshipType": "related"
      }
    ],
    "remove": []
  }'

Read the relationships with:

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

The batch response returns added, removed and skipped counters. Every relationship change changes the source ETag, so read a new value before the next mutation.


Changes - removing relationships

Remove a relationship with the current ETag of the source change. Put the target collection and ID in the path:

curl --request DELETE "$BASE_URL/api/v1/changes/$CHANGE_ID/relationships/changes/$TARGET_CHANGE_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: "CURRENT_ETAG"' \
  --header "Idempotency-Key: public-api-change-relationship-delete-20260905130127"

For an asset, use relationships/assets/{TARGET_ID}; for a document, use relationships/documents/{TARGET_ID}. The relationshipType parameter must match the type that was saved.

You can also remove relationships as part of a partial update:

curl --request PATCH "$BASE_URL/api/v1/changes/$CHANGE_ID" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header 'If-Match: "CURRENT_ETAG"' \
  --header "Idempotency-Key: public-api-change-relationship-patch-20260905130127" \
  --data-raw '{
    "relationshipsToRemove": [
      {
        "targetId": "TARGET_UUID",
        "targetDataSet": "changes",
        "targetItemType": "change",
        "relationshipType": "related"
      }
    ]
  }'

After removing a relationship, read the list again and confirm that the correct target was removed. Removing a relationship does not delete the record that was its target.


Changes - user relationships

User relationships are a separate mechanism. A change supports three roles: agent for the person doing the work, watcher for an observer and appUserRequester for the user who submitted the request. This object does not support clientRequester.

Assign an agent:

curl --request POST "$BASE_URL/api/v1/changes/$CHANGE_ID/user-relationships" \
  --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: "CURRENT_ETAG"' \
  --header "Idempotency-Key: public-api-change-agent-20260905130127" \
  --data-raw '{
    "targetId": "USER_UUID",
    "targetDataSet": "users",
    "relationshipType": "agent"
  }'

Add a watcher through batch:

curl --request POST "$BASE_URL/api/v1/changes/$CHANGE_ID/user-relationships:batch" \
  --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: "CURRENT_ETAG"' \
  --header "Idempotency-Key: public-api-change-watcher-20260905130127" \
  --data-raw '{
    "add": [
      {
        "targetId": "WATCHER_USER_UUID",
        "targetDataSet": "users",
        "relationshipType": "watcher"
      }
    ],
    "remove": []
  }'

Read the user relationships:

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

Remove an assignment by specifying the relationship type:

curl --request DELETE "$BASE_URL/api/v1/changes/$CHANGE_ID/user-relationships/users/$USER_ID?relationshipType=appUserRequester" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header 'If-Match: "CURRENT_ETAG"' \
  --header "Idempotency-Key: public-api-change-app-user-requester-delete-20260905130127"

Use the same route to remove an agent, watcher or requester and change relationshipType. A watcher can also be removed through batch with an empty add array and an item in remove.


Changes - files

A file uploaded to a change has its own ID and metadata. Upload requires the current ETag, a new idempotency key and a multipart/form-data request:

curl --request POST "$BASE_URL/api/v1/changes/$CHANGE_ID/files?relationshipType=documentation" \
  --header "Accept: application/json, application/problem+json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header 'If-Match: "CURRENT_ETAG"' \
  --header "Idempotency-Key: public-api-change-file-one-20260905130127" \
  --form "[email protected];type=text/plain"

A successful upload returns 201 Created with the file ID and metadata. Read the file list with:

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

Download the content as binary data and save it to a file:

export FILE_ID="FILE_UUID"

curl --request GET "$BASE_URL/api/v1/changes/$CHANGE_ID/files/$FILE_ID/content" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --output downloaded-change-file.bin

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

curl --request POST "$BASE_URL/api/v1/changes/OTHER_CHANGE_UUID/files/$FILE_ID?relationshipType=manual" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header 'If-Match: "OTHER_CHANGE_ETAG"' \
  --header "Idempotency-Key: public-api-change-file-attach-20260905130127"

Delete a file with DELETE /api/v1/changes/{id}/files/{fileId}. The operation returns 200 OK when successful. After uploading, attaching or deleting, refresh the change ETag and file list.

curl --request DELETE "$BASE_URL/api/v1/changes/$CHANGE_ID/files/$FILE_ID" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header 'If-Match: "CURRENT_ETAG"' \
  --header "Idempotency-Key: public-api-change-file-delete-20260905130127"

Changes - pinning, spam and reopening

Pinning, marking as spam and reopening are separate actions. Each requires the current If-Match and a new Idempotency-Key.

Pin a change:

curl --request POST "$BASE_URL/api/v1/changes/$CHANGE_ID/pin" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header 'If-Match: "CURRENT_ETAG"' \
  --header "Idempotency-Key: public-api-change-pin-20260905130127" \
  --data-raw '{"pin":2}'

To remove the pin, use the same route with null, if the schema and permissions allow it:

curl --request POST "$BASE_URL/api/v1/changes/$CHANGE_ID/pin" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header 'If-Match: "CURRENT_ETAG"' \
  --header "Idempotency-Key: public-api-change-unpin-20260905130127" \
  --data-raw '{"pin":null}'

Mark as spam and undo the mark:

curl --request POST "$BASE_URL/api/v1/changes/$CHANGE_ID/spam" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header 'If-Match: "CURRENT_ETAG"' \
  --header "Idempotency-Key: public-api-change-spam-20260905130127" \
  --data-raw '{"isSpam":true}'

curl --request POST "$BASE_URL/api/v1/changes/$CHANGE_ID/spam" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header 'If-Match: "CURRENT_ETAG"' \
  --header "Idempotency-Key: public-api-change-spam-undo-20260905130127" \
  --data-raw '{"isSpam":false}'

Reopen a closed change:

curl --request POST "$BASE_URL/api/v1/changes/$CHANGE_ID/reopen" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header 'If-Match: "CURRENT_ETAG"' \
  --header "Idempotency-Key: public-api-change-reopen-20260905130127"

Read the change and save its new ETag after every action. The required scopes are changes:pin:write, changes:spam:write and changes:reopen:write respectively.


Changes - rating and escalation

Save a rating through a separate endpoint. You may include an escalation request:

curl --request POST "$BASE_URL/api/v1/changes/$CHANGE_ID/rating" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header 'If-Match: "CURRENT_ETAG"' \
  --header "Idempotency-Key: public-api-change-rating-20260905130127" \
  --data-raw '{
    "rating": 4,
    "feedback": "Rating from the integration",
    "isEscalationRequested": true,
    "escalationRequestReason": "Public API escalation request"
  }'

changes:rating:write is required to save the rating and changes:escalation:write to include an escalation request. Use a rating value supported by the API. After saving, read technical fields such as rating, feedback, rating dates and escalationRequestReason.

If no escalation is needed, omit isEscalationRequested and escalationRequestReason. Do not send an escalation request without a reason.


Changes - approval and decision

Create an approval as a separate approval object and link it to the change. You need the approval-module scopes and the ID of the person who should make the decision:

export APPROVER_ID="USER_UUID"

curl --request POST "$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-change-approval-create-20260905130127" \
  --data-raw '{
    "itemType": "approval",
    "approverId": "USER_UUID",
    "attributes": {
      "customId": "PUBLIC-API-CHANGE-20260905130127-APPROVAL",
      "category": "Public API",
      "description": "Changes approval"
    },
    "relationships": [
      {
        "targetId": "CHANGE_UUID",
        "targetDataSet": "changes",
        "targetItemType": "change"
      }
    ]
  }'

The person named in approverId can make the decision through the change route. APPROVAL_ID is the approval ID, not a user ID:

export APPROVAL_ID="APPROVAL_UUID"

curl --request POST "$BASE_URL/api/v1/changes/$CHANGE_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: "CURRENT_ETAG"' \
  --header "Idempotency-Key: public-api-change-approval-decision-20260905130127" \
  --data-raw '{
    "approve": true,
    "remark": "Approved through Changes Public API."
  }'

Reject the request by sending approve as false and your own remark. After the decision, read the approval and check its status and decision date. Then refresh the change because the decision may change its ETag and workflow state.


Changes - deleting a record

Read the change again before deletion and use its current ETag:

curl --request DELETE "$BASE_URL/api/v1/changes/$CHANGE_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: "CURRENT_ETAG"' \
  --header "Idempotency-Key: public-api-change-delete-20260905130127"

After 200 OK, send GET for the same UUID. Expect 404 with change_not_found or the appropriate record code. To check synchronisation, list records with a customId filter and confirm that totalItems is zero.

Do not use deletion as a way to archive history. Before a production operation, check your retention policy, relationships and audit requirements. If the record must remain in history, change its status instead of deleting it.


Changes - errors, limits and security

Errors use the application/problem+json Problem Details format. Example:

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

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

HTTP
Meaning
401
missing or invalid authentication
403
required scope or user permission is missing
404
record, file or relationship target is missing or not visible
409
data or idempotency conflict
412
stale ETag
422
invalid body or field values
428
If-Match or Idempotency-Key is missing
429
request limit exceeded

Read X-RateLimit-Limit and X-RateLimit-Remaining. After 429, use backoff and respect Retry-After when present. Do not bypass limits by creating extra keys or increasing request parallelism. Log the method, endpoint, status and requestId, but never Client Secret or full authentication headers.


Changes - recommended integration sequence

  1. Set BASE_URL for the correct Cloud or On-Premise installation.
  2. Create a separate key under Settings - API - API Keys and select minimum scopes.
  3. Put Client ID and Client Secret in a secure store.
  4. Send GET /api/v1/context and check the database, scopes and limits.
  5. Fetch GET /api/v1/changes/schema and the field values used by the integration.
  6. Read the change list or create a new record with POST and a unique Idempotency-Key.
  7. Save the change UUID and ETag.
  8. Refresh the ETag before every mutation and use a new idempotency key.
  9. Add relationships, users and files only after checking the target catalogue in the schema.
  10. Perform workflow actions and approval decisions separately, then read the new state after each one.
  11. For 412, read the record, resolve the conflict and retry consciously.
  12. For batches, inspect every item because a partial error does not have to roll back successful items.
  13. Handle 429, save requestId without secrets and delete the key when the integration is no longer used.

This flow lets you synchronise planned changes with another system without depending on the internal database structure. If field configuration, installation address or key scopes change, read the context and schema again.