Notes in Codenica API

Start working with notes 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 notes, and the type of an individual object is note. A note is an entry saved in Codenica. It can contain a title, description, status, priority, category, link and files. The isPrivate flag controls visibility according to existing permissions, while pin sets the entry's pin level.

The following sections cover the API address, scopes, context, schema, fields, lists, filtering, creation, idempotency, ETag, editing, pinning, relationships, author information, files, batch operations and deletion.

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


Notes - API address and installation choice

All routes for notes start with:

{BASE_URL}/api/v1/notes

BASE_URL is the Codenica server address without the final /api/v1. For Codenica Cloud, use the public domain assigned to the relevant company:

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

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

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

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

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


Notes - 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. At that moment, save the Client ID and Client Secret in the secure storage used by the integration.

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

Licence
API access
Maximum active keys
Starter
Not available
0
Plus
Available
50
Enterprise
Available
100

Deleting a key removes its record and frees a slot within the limit. When its expiry date passes, the key stops authenticating, but it remains on the list until it is deleted. If you do not set an end date when creating a key, the default validity period is 90 days. The maximum validity period for one key is 5 years.


Notes - authentication and secure requests

Authenticate every Codenica API request with the two key headers:

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

curl --request GET --url "$BASE_URL/api/v1/notes?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 put the key in a repository, code delivered to a browser, a URL, shell history or logs. Use HTTPS outside local tests.

Keep meta.requestId from the response. It identifies a particular request for troubleshooting, but it is not the note ID and must not be treated as a secret.


Notes - checking the connection context

Before the first write, read the context. It confirms that the address points to the intended 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 values 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 notes in data.capabilities.resources;
  • the scopes assigned to the key;
  • the page, relationship, file and request limits.

If the context points to a different 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.


Notes - scopes and permissions

Full notes support requires scopes matching the operations your integration will use:

notes:read
notes:write
notes:delete
notes:schema
notes:stats
notes:relationships:read
notes:relationships:write
notes:users:read
notes:files:read
notes:files:write
notes:technical:read
notes:technical:write
notes:pin:write

For ordinary reads, notes:read is enough. Schema and statistics use the separate notes:schema and notes:stats scopes. For creation, editing and deletion, add notes:write and notes:delete as appropriate.

  • notes:relationships:read and notes:relationships:write cover object relationships;
  • notes:users:read covers reading the author;
  • notes:files:read and notes:files:write cover listing, downloading, uploading, attaching and deleting files;
  • notes:stats covers statistics and field values used for filters;
  • notes:pin:write is required for pinning and unpinning;
  • use technical scopes only when the integration needs fields marked as technical in the schema or customValues rules.

If the integration searches for relationship targets itself, also grant the relevant read scopes, such as assets:read, clients:read, vendors:read, documents:read, tickets:read, changes:read, problems:read, releases:read, approvals:read, confirmations:read, worktasks:read and requesteditems:read. A key's scopes do not replace user permissions or access to a location or department.


Notes - schema and fields

The schema shows which fields can be read and written in the selected database. Retrieve it before building a form or field mapping:

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

The response includes, among other things, data.itemType, data.fields and data.relationshipTargets. The fixed itemType for this module is note. For every field, check readable, writable, required, technical, unique and maxLength. Do not build a mapping only from the examples in this article, because field configuration may differ between databases.

The schema also tells you whether relationships with a given dataset are available. Use only targets returned for the current key and user.


Notes - writable and system fields

The public business-field catalogue for Notes includes:

customId
location
department
isPrivate
tag
link
title
status
priority
category
description

The main field limits are:

Field
Type
Maximum length
customId
string
500
location, department
string
300 each
isPrivate
boolean
-
tag, link
string
2000 each
title
string
1000
status, priority, category
string
300 each
description
string
10000

pin is returned in the attributes, but it cannot be changed through attributes. Use its dedicated endpoint instead. Read-only system and technical fields include:

id
itemType
pin
creator
updater
dateCreated
dateUpdated
importId
importSource
dateImported

id and itemType are part of the resource. Dates, author and editor are assigned by the system. Do not try to change them through attributes.


Notes - core endpoints

The main routes for the notes module are:

GET    /api/v1/notes
POST   /api/v1/notes
GET    /api/v1/notes/{NOTE_ID}
PATCH  /api/v1/notes/{NOTE_ID}
DELETE /api/v1/notes/{NOTE_ID}
GET    /api/v1/notes/schema
GET    /api/v1/notes/stats
GET    /api/v1/notes/values
POST   /api/v1/notes:batch
GET    /api/v1/notes/{NOTE_ID}/relationships
POST   /api/v1/notes/{NOTE_ID}/relationships
POST   /api/v1/notes/{NOTE_ID}/relationships:batch
DELETE /api/v1/notes/{NOTE_ID}/relationships/{DATASET}/{TARGET_ID}
GET    /api/v1/notes/{NOTE_ID}/user-relationships
GET    /api/v1/notes/{NOTE_ID}/files
POST   /api/v1/notes/{NOTE_ID}/files
POST   /api/v1/notes/{NOTE_ID}/files/{FILE_ID}
DELETE /api/v1/notes/{NOTE_ID}/files/{FILE_ID}
GET    /api/v1/notes/{NOTE_ID}/files/{FILE_ID}/content
POST   /api/v1/notes/{NOTE_ID}/pin

Every route requires authentication. Mutating operations also require Idempotency-Key, while version-protected operations require the current If-Match. Check the context and schema response for the exact requirements.


Notes - listing and pagination

The list is paginated. This example retrieves the first page and sorts notes from newest to oldest:

curl --request GET --url "$BASE_URL/api/v1/notes?itemType=note&page=1&pageSize=20&sort=dateCreated&direction=desc" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

The response includes, among other values:

data.items
data.page
data.pageSize
data.totalItems
data.totalPages
data.hasNextPage

The maximum pageSize comes from the context and is normally 100. Retrieve subsequent pages while data.hasNextPage is true. Do not assume that the number of records on the first page is the complete list.


Notes - search, filters and sorting

You can use equality filters for fields including customId, location, department, isPrivate, tag, link, title, status, priority and category. This example finds private open notes from the IT department:

curl --request GET --url "$BASE_URL/api/v1/notes?isPrivate=true&department=IT&status=Open&page=1&pageSize=50" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

search searches textual note fields, including customId, tag, link, title, status, priority, category and description:

curl --request GET --url "$BASE_URL/api/v1/notes?search=integration&page=1&pageSize=20" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

The filter parameter may be repeated. Its format is field:operator:value:

filter=status:eq:Open
filter=status:ne:Closed
filter=title:contains:server
filter=title:startswith:Public API
filter=isPrivate:eq:true
filter=description:notempty:

Supported operators include eq, ne, gt, gte, lt, lte, contains, startswith, endswith, empty and notempty. Shortcuts include =, !=, ge, le, sw and ew. You can add createdAfter, createdBefore, updatedAfter and updatedBefore. Sort only by a field allowed by the schema, using direction=asc or direction=desc. URL-encode values containing spaces or special characters.


Notes - field selection and included data

If the integration needs only part of the data, limit the response with fields:

curl --request GET --url "$BASE_URL/api/v1/notes?fields=customId%2Ctitle%2Cstatus%2Cpriority%2CisPrivate&page=1&pageSize=20" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

You can retrieve one note together with its files, relationships and author information:

curl --request GET --url "$BASE_URL/api/v1/notes/{NOTE_ID}?fields=customId%2Ctitle%2Cdescription%2Cstatus&include=files%2Crelationships%2Cusers" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

Allowed include values are files, relationships and users. Each one requires its corresponding read scope. fields=* requests all fields available to the key, but technical fields appear only when the relevant technical scope is granted.


Notes - statistics and field values

Statistics count visible notes and group them by a selected field:

curl --request GET --url "$BASE_URL/api/v1/notes/stats?field=category&limit=20" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

Example response:

{
  "data": {
    "total": 42,
    "field": "category",
    "values": [
      {
        "value": "Integration",
        "count": 12
      },
      {
        "value": "Hardware",
        "count": 8
      }
    ]
  },
  "meta": {
    "requestId": "request-id-from-response"
  }
}

Without field, the endpoint returns the total number of notes. limit accepts values from 1 to 500. Results respect the user's visibility scope.

The values endpoint returns distinct values that can be used to build selection lists:

curl --request GET --url "$BASE_URL/api/v1/notes/values?field=status&search=Open&limit=20" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

Example response:

{
  "data": {
    "field": "status",
    "values": [
      "Open",
      "Open - waiting"
    ]
  },
  "meta": {
    "requestId": "request-id-from-response"
  }
}

Both endpoints are read-only and do not change notes. The values response is not a record list, but a list of distinct values for one field.


Notes - creating a record

Put the technical type note in the body and business fields in attributes. In a practical integration, it is worth saving a title and description even when the schema does not mark them as required:

{
  "itemType": "note",
  "attributes": {
    "customId": "NOTE-ERP-2026-0001",
    "location": "Warsaw",
    "department": "IT",
    "isPrivate": true,
    "tag": "erp,public-api,notes",
    "link": "https://erp.example.com/notes/0001",
    "title": "Server integration check",
    "status": "Open",
    "priority": "Normal",
    "category": "Integration",
    "description": "Note created by an external ERP system."
  }
}

Save the body as note-create.json and send it with a unique idempotency key:

curl --request POST --url "$BASE_URL/api/v1/notes" \
  --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: notes-create-20260905-0001" \
  --data-binary @note-create.json

A successful creation returns 201 Created. The response contains the UUID in data.id, data.itemType=note, saved attributes, system dates and data.meta.etag. Let the system assign id.

isPrivate controls visibility; it is not encryption. Do not save passwords, tokens, Client Secret or other confidential data in a note.


Notes - safely retrying creation

If the client does not know whether the first request arrived, repeat exactly the same request with the same Idempotency-Key:

curl --request POST --url "$BASE_URL/api/v1/notes" \
  --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: notes-create-20260905-0001" \
  --data-binary @note-create.json

Repeating the same logical request must not create a second note. The response should identify the same UUID and operation result. Do not reuse the key for another body, endpoint or operation. Every new mutation needs a new Idempotency-Key.

After a timeout, do not immediately create another record. First retry the previous request with the same body and idempotency key.


Notes - reading a record and ETag

After creation or before an update, retrieve one note and save its UUID and current ETag:

export NOTE_ID="d7a83ba0-41ce-44f6-b2e9-7ddcec716234"

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

The ETag appears in the HTTP ETag header, data.meta.etag and the envelope meta.etag. Example response for one resource:

{
  "data": {
    "id": "d7a83ba0-41ce-44f6-b2e9-7ddcec716234",
    "itemType": "note",
    "attributes": {
      "customId": "NOTE-ERP-0001",
      "title": "Server integration check",
      "isPrivate": true
    },
    "meta": {
      "customId": "NOTE-ERP-0001",
      "etag": "\"etag-value\""
    }
  },
  "meta": {
    "requestId": "request-id-from-response",
    "etag": "\"etag-value\""
  }
}

After every successful mutation, the ETag may change, including after a relationship, file or pin operation. Replace the previous value before making the next change.


Notes - partial update with If-Match

PATCH changes only fields sent in attributes. Use the current ETag and a separate idempotency key:

export NOTE_ETAG='"etag-from-the-latest-response"'

curl --request PATCH --url "$BASE_URL/api/v1/notes/$NOTE_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: $NOTE_ETAG" \
  --header "Idempotency-Key: notes-update-20260905-0001" \
  --data-raw '{
    "attributes": {
      "title": "Updated server integration check",
      "description": "The note was changed by an API workflow.",
      "status": "In progress",
      "priority": "High",
      "isPrivate": false
    }
  }'

You do not need to send every field. You can clear an optional value with null if the schema for the database allows it:

{
  "attributes": {
    "link": null,
    "description": null
  }
}

An empty PATCH without attributes, value rules or relationship changes is rejected. System fields and pin do not belong in an ordinary update.


Notes - stale ETag and missing If-Match

Notes mutations require the If-Match header. Without it, the API returns 428 Precondition Required:

{
  "type": "https://docs.codenica.com/errors/if_match_required",
  "title": "Precondition required.",
  "status": 428,
  "detail": "Send the ETag returned by GET in the If-Match header.",
  "instance": "/api/v1/notes/{id}",
  "code": "if_match_required",
  "requestId": "request-id-from-response"
}

If the supplied ETag is stale, the API returns 412 Precondition Failed:

{
  "type": "https://docs.codenica.com/errors/if_match_failed",
  "title": "Precondition failed.",
  "status": 412,
  "detail": "The supplied ETag is not the current note version.",
  "instance": "/api/v1/notes/{id}",
  "code": "if_match_failed",
  "requestId": "request-id-from-response"
}

After 412, retrieve the note again, compare its current state with the change you want to make, and only then send a new PATCH with the new ETag. Do not endlessly repeat a request with the old value.


Notes - pinning and unpinning

The pin field is read-only in an ordinary update. Use the dedicated route to set the pin level:

curl --request POST --url "$BASE_URL/api/v1/notes/$NOTE_ID/pin" \
  --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: $NOTE_ETAG" \
  --header "Idempotency-Key: notes-pin-20260905-0001" \
  --data-raw '{"pin":3}'

Allowed values are integers from 0 to 3. To unpin the note, send null:

curl --request POST --url "$BASE_URL/api/v1/notes/$NOTE_ID/pin" \
  --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: $NOTE_ETAG" \
  --header "Idempotency-Key: notes-unpin-20260905-0001" \
  --data-raw '{"pin":null}'

The operation requires notes:pin:write, existing access to the note and the current ETag. Retrieve the new ETag after success. Do not set the pin through attributes.pin or send an empty body.

Find pinned notes with a filter:

curl --request GET --url "$BASE_URL/api/v1/notes?pin=3&page=1&pageSize=20" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

Notes - available object relationships

A note can be linked to targets returned by the schema. The current catalogue includes:

assets        - asset
clients       - client
vendors       - vendor
documents     - document
tickets       - ticket
changes       - change
problems      - problem
releases      - release
approvals     - approval
confirmations - confirmation
worktasks     - worktask
requesteditems - requesteditem

A note cannot be related to itself. For most targets, a relationship consists of an ID, dataset and object type, so omit relationshipType. The current relationship model for confirmations stores that parameter. Example confirmation target:

{
  "targetId": "6efaebb6-8650-4674-8478-34fd3e601427",
  "targetDataSet": "confirmations",
  "targetItemType": "confirmation",
  "relationshipType": "client"
}

The API checks the UUID, whether targetDataSet matches targetItemType, existence and visibility of the target, permissions and duplicates. If the schema does not return a target, do not use it in the integration.


Notes - adding, reading and removing relationships

Read relationships through the collection:

curl --request GET --url "$BASE_URL/api/v1/notes/$NOTE_ID/relationships?targetDataSet=assets&targetItemType=asset&page=1&pageSize=50" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

Adding an Asset relationship requires notes:relationships:write, the current ETag and an idempotency key:

curl --request POST --url "$BASE_URL/api/v1/notes/$NOTE_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: $NOTE_ETAG" \
  --header "Idempotency-Key: notes-asset-relationship-20260905-0001" \
  --data-raw '{
    "targetId": "71ce005d-4cb8-4c9d-8239-ffe7e5e9f3e9",
    "targetDataSet": "assets",
    "targetItemType": "asset"
  }'

A collection item may contain targetId, targetDataSet, targetItemType, customId and name. Repeating the same add is safe and should not create a duplicate.

Remove one relationship:

curl --request DELETE --url "$BASE_URL/api/v1/notes/$NOTE_ID/relationships/assets/71ce005d-4cb8-4c9d-8239-ffe7e5e9f3e9" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $NOTE_ETAG" \
  --header "Idempotency-Key: notes-asset-relationship-delete-20260905-0001"

For confirmations, add relationshipType=client to the query. A successful response has status 200 and data=true. Read the note again after every relationship change because its ETag may change.


Notes - batch relationship changes

Use relationships:batch to add or remove several relationships. One request can contain add and remove arrays:

curl --request POST --url "$BASE_URL/api/v1/notes/$NOTE_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: $NOTE_ETAG" \
  --header "Idempotency-Key: notes-relationships-batch-20260905-0001" \
  --data-raw '{
    "add": [
      {
        "targetId": "71ce005d-4cb8-4c9d-8239-ffe7e5e9f3e9",
        "targetDataSet": "assets",
        "targetItemType": "asset"
      }
    ],
    "remove": [
      {
        "targetId": "385b51cc-fb4d-4599-9b82-3c5b66705ccd",
        "targetDataSet": "clients",
        "targetItemType": "client"
      }
    ]
  }'

The response contains counters:

{
  "data": {
    "added": 1,
    "removed": 1,
    "skipped": 0
  },
  "meta": {
    "requestId": "request-id-from-response"
  }
}

Every target must be visible and match the relationship catalogue. Repeating an existing relationship may be counted as skipped. Empty add and remove arrays are rejected when they contain no operation. A relationship batch also changes the source note's ETag.


Notes - author relationship

The author is assigned by the existing note-creation flow. Read it through the user relationship:

curl --request GET --url "$BASE_URL/api/v1/notes/$NOTE_ID/user-relationships?relationshipType=author&page=1&pageSize=20" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

Example response item:

{
  "targetId": "8e9cbff3-340f-41f6-97ec-6997bb915829",
  "targetDataSet": "users",
  "relationshipType": "author",
  "displayName": "Fred Savage",
  "email": "[email protected]",
  "role": "Administrator"
}

Reading it requires notes:users:read and the appropriate permission to list notes. The current contract exposes only the author relationship. There is no public POST or DELETE for changing or removing the author. Do not send the author in relationships or attributes.


Notes - files

Notes can have files, but they do not support setting a main file. In every file resource, isMain is false. Available routes are:

GET    /api/v1/notes/{NOTE_ID}/files
POST   /api/v1/notes/{NOTE_ID}/files
POST   /api/v1/notes/{NOTE_ID}/files/{FILE_ID}
DELETE /api/v1/notes/{NOTE_ID}/files/{FILE_ID}
GET    /api/v1/notes/{NOTE_ID}/files/{FILE_ID}/content

Start by checking the file list:

curl --request GET --url "$BASE_URL/api/v1/notes/$NOTE_ID/files?page=1&pageSize=50" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

A list item includes, among other values, id, name, fileName, contentType, size, relationshipType, isMain and downloadUrl. Listing and downloading require notes:files:read. Uploading, attaching and deleting require notes:files:write, system permissions, the current ETag and an idempotency key.

Upload a file as multipart/form-data:

curl --request POST --url "$BASE_URL/api/v1/notes/$NOTE_ID/files?relationshipType=documentation" \
  --header "Accept: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $NOTE_ETAG" \
  --header "Idempotency-Key: notes-file-upload-20260905-0001" \
  --form "file=@./note-evidence.txt;type=text/plain"

A successful upload returns 201 Created and the file ID. The relationshipType can describe the purpose, for example documentation, manual or evidence. Read the size limit from data.capabilities.limits.maxUploadBytes.

Download the content through the authenticated path:

curl --request GET --url "$BASE_URL/api/v1/notes/$NOTE_ID/files/$FILE_ID/content" \
  --header "Accept: application/octet-stream" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --output ./note-evidence.downloaded.txt

Treat downloadUrl as an API path, not a public anonymous link. The content endpoint returns file bytes, not a JSON envelope.

If the file already exists in Codenica storage, attach its existing ID:

curl --request POST --url "$BASE_URL/api/v1/notes/$NOTE_ID/files/$FILE_ID?relationshipType=manual" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $NOTE_ETAG" \
  --header "Idempotency-Key: notes-file-attach-20260905-0001"

Remove a file relationship:

curl --request DELETE --url "$BASE_URL/api/v1/notes/$NOTE_ID/files/$FILE_ID" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $NOTE_ETAG" \
  --header "Idempotency-Key: notes-file-delete-20260905-0001"

After every file operation, read the note again and save the new ETag. Do not call files/{FILE_ID}/main for Notes because it is not part of this object's contract.


Notes - batch operations

Batch combines note creation, update and deletion in one request. Each item is processed separately:

curl --request POST --url "$BASE_URL/api/v1/notes: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: notes-batch-create-20260905-0001" \
  --data-raw '{
    "items": [
      {
        "operation": "create",
        "create": {
          "itemType": "note",
          "attributes": {
            "customId": "NOTE-BATCH-A",
            "title": "Batch note A",
            "description": "First note from a batch operation.",
            "category": "Integration",
            "status": "Open",
            "priority": "Normal",
            "isPrivate": false
          }
        }
      },
      {
        "operation": "create",
        "create": {
          "itemType": "note",
          "attributes": {
            "customId": "NOTE-BATCH-B",
            "title": "Batch note B",
            "description": "Second note from a batch operation.",
            "category": "Integration",
            "status": "Open",
            "priority": "Low",
            "isPrivate": true
          }
        }
      }
    ]
  }'

An example response contains succeeded, failed and the result of each item:

{
  "data": {
    "succeeded": 2,
    "failed": 0,
    "items": [
      {
        "index": 0,
        "operation": "create",
        "status": 201,
        "id": "note-id-a"
      },
      {
        "index": 1,
        "operation": "create",
        "status": 201,
        "id": "note-id-b"
      }
    ]
  },
  "meta": {
    "requestId": "request-id-from-response"
  }
}

Before an update or delete, retrieve the current ETag for each note. In a batch item, send id, ifMatch and the relevant update block. Use operation=delete for deletion. A batch is not an all-or-nothing transaction. On partial success, the API may return 207 Multi-Status, so inspect every item.


Notes - deleting a record

Before deletion, retrieve the note again and use its current ETag:

curl --request DELETE --url "$BASE_URL/api/v1/notes/$NOTE_ID" \
  --header "Accept: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $NOTE_ETAG" \
  --header "Idempotency-Key: notes-delete-20260905-0001"

Successful deletion requires notes:delete and returns 200 OK with data=true. The existing deletion flow also cleans up relationships according to the system configuration.

Verify the individual record after the operation:

curl --request GET --url "$BASE_URL/api/v1/notes/$NOTE_ID" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

The expected status is 404 with code note_not_found. Also check your custom identifier:

curl --request GET --url "$BASE_URL/api/v1/notes?customId=NOTE-ERP-2026-0001&page=1&pageSize=10" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

After successful deletion, totalItems should be 0. Remove the identifier from the integration's local index or mark it inactive.


Notes - errors, limits and security

API errors use the Problem Details format with additional Codenica fields:

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

In application logic, rely mainly on status and code. The detail text is guidance for a person and may change.

  • 400 - invalid body, parameter, UUID or field value;
  • 401 - missing or invalid authentication;
  • 403 - missing scope or user permission;
  • 404 - note, file, relationship or target is unavailable;
  • 409 - identifier conflict, duplicate or concurrent change;
  • 412 - stale ETag;
  • 413 - file or body exceeds the limit;
  • 422 - an existing domain flow rejected the operation;
  • 428 - If-Match or Idempotency-Key is missing;
  • 429 - request limit exceeded;
  • 500 or 503 - server error or temporary unavailability.

Read the X-RateLimit-Limit, X-RateLimit-Remaining and, for 429, Retry-After headers. Use controlled retries with increasing delays. Never store Client Secret in a repository, URL, browser code, shell history or logs. isPrivate does not replace encryption.


Notes - integration sequence

  1. Determine the actual Cloud or On-Premise address and set BASE_URL.
  2. Create a separate key for the application and environment under Settings - API - API Keys.
  3. Grant only the scopes needed for reading, writing, relationships, files, statistics or pinning.
  4. Send GET /api/v1/context and check the database, caller, scopes and limits.
  5. Retrieve GET /api/v1/notes/schema and build the field and relationship-target mapping.
  6. Retrieve a list of notes with pagination, search or filters.
  7. Create a record with POST and a unique Idempotency-Key.
  8. Save the UUID and ETag from the response.
  9. After a timeout, repeat the identical request with the same idempotency key.
  10. Retrieve a fresh ETag before every mutation.
  11. Use PATCH for ordinary fields and the /pin endpoint for pinning.
  12. Use only relationship targets returned by the schema and the correct targetItemType.
  13. For confirmations, send relationshipType=client; omit it for the other datasets.
  14. Only read the author relationship because there is no public write endpoint.
  15. Remember that Notes do not have a main file.
  16. Inspect every batch item because a partial failure does not roll back successful items.
  17. After 412, retrieve the record again and resolve the conflict.
  18. After 429, respect Retry-After.
  19. Log requestId, status and error code, but never the secret.
  20. When the integration ends, delete the unused API key.

This sequence lets you synchronize notes with another system without relying on assumptions about fields, visibility, relationships or system data.