Tickets in Codenica API
Start working with tickets through the API by creating an API key in Codenica settings. If you have not created one yet, open the Codenica API - introduction article in a new tab. It explains key creation, licence limits and the authentication rules shared by the API.
A ticket is a Service Desk object. Alongside basic information such as the subject, description and requester, it can contain priority, impact, urgency, severity, status, SLA data, resolution details, relationships with other objects and files. The API also provides ticket-specific actions for pinning, marking spam, reopening, rating, requesting escalation and making an approval decision.
The examples use technical field and route names because those are the exact values to send in requests. Replace the sample text with data from your own application.
Tickets - API address
Perform all ticket operations at this address:
{BASE_URL}/api/v1/ticketsFor Codenica Cloud, use the public address assigned to your installation. The example below uses a placeholder company address:
https://your-company.codenica.com/api/v1/ticketsIn an On-Premise installation, the default address registered by Codenica Discovery is:
http://codenica.local:5150/api/v1/ticketsIf the administrator published the installation at another address, use that exact address, for example:
https://api.your-company.example/api/v1/ticketsUse localhost only when the integrating application runs on the same computer as the API. Do not add tenantId to requests. The correct database is selected from the address you connect to.
Tickets - API key and licence limits
Create the key in Codenica under Settings - API - API Keys. The secret is shown only once, immediately after the key is created or rotated. Save both values in the secure secret store used by the integration at that moment.
The number of keys depends on the licence assigned to the installation:
It is best to create a separate key for each integration and environment, for example one for production, one for testing and one for automation. When creating a key, select only the permission scopes required by that connection. The key used in this article should have at least tickets:read, tickets:write and the other scopes required by the planned operations.
Tickets - authentication and secure requests
The integration authenticates with two headers. It does not need an administrator JWT or cookies from the Codenica panel.
export BASE_URL="https://your-company.codenica.com"
export CLIENT_ID="cna_example"
export CLIENT_SECRET="cns_example"
curl --request GET "$BASE_URL/api/v1/tickets?page=1&pageSize=25" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET"Do not place the key in application code, a repository, logs or error messages. The CLIENT_ID and CLIENT_SECRET values in the examples are symbolic. In production, read them from environment variables or a dedicated secret store.
The API response includes a request identifier in meta.requestId. Keep it in technical logs because it helps locate a specific request during diagnosis. Do not log the key secret alongside it.
Tickets - checking the connection context
Before the first ticket operation, check that the address, key and scopes are configured correctly. The context endpoint returns, among other things, the API version, database identifier, calling identity, scopes and capabilities available to the key.
curl --request GET "$BASE_URL/api/v1/context" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET"In a valid response, check:
data.apiVersion- it should indicate versionv1;data.contractVersion- the contract version used by the integration;data.tenant.idanddata.tenant.resolvedDomain- the database and resolved address;data.caller.authentication- the valueapi_key;data.caller.scopes- the scopes assigned to the key;data.capabilities.resources- the presence of theticketsresource;- page, batch and requests-per-minute limits.
If a scope is missing at this stage, change the key permissions in settings or create a new key. Do not try to pass scopes in the request itself.
Tickets - permission scopes
The following scopes are required for full ticket support:
tickets:read
tickets:write
tickets:delete
tickets:schema
tickets:stats
tickets:relationships:read
tickets:relationships:write
tickets:users:read
tickets:users:write
tickets:files:read
tickets:files:write
tickets:technical:read
tickets:technical:write
tickets:pin:write
tickets:spam:write
tickets:reopen:write
tickets:rating:write
tickets:escalation:write
tickets:approval:writeNot every integration needs the complete set. A read-only integration can use tickets:read; to read the schema, statistics and dictionary values, add tickets:schema and tickets:stats as needed. Reading relationships, users and files requires the corresponding :relationships:read, :users:read and :files:read scopes.
If you use ticket approval decisions, you need tickets:approval:write. If the integration also creates and manages approval objects itself, it additionally needs the scopes for approvals. Do not grant write permissions merely because they are convenient during an initial test.
Tickets - schema and fields
The schema returns the current field configuration for your database. This is especially important for dictionary values such as status, priority, impact, urgency, severity, type and category.
curl --request GET "$BASE_URL/api/v1/tickets/schema" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET"In the response, look for fields marked required, writable, technical and hasAutoGeneration. A new ticket requires at least subject and requesterEmail. Save other fields only when they are available and needed in your workflow.
subject, requesterEmail, description, commentstype, category, priority, impact, urgency, severity, statuslocation, department, teams, servicesexternalNumber, referenceNumber, link, tagsFields such as sla, rating, feedback, pin, isSpam and action-related dates are managed by the system or by dedicated endpoints. Do not assume that a normal PATCH can change them.
Tickets - basic routes
The most frequently used routes are:
GET /api/v1/tickets- ticket list;GET /api/v1/tickets/{id}- one ticket;POST /api/v1/tickets- create a ticket;PATCH /api/v1/tickets/{id}- partial update;DELETE /api/v1/tickets/{id}- delete;GET /api/v1/tickets/schema- field schema;GET /api/v1/tickets/stats- statistics;GET /api/v1/tickets/values- values used in filters;POST /api/v1/tickets:batch- multiple operations in one request.
Relationships, users, files and actions have separate routes described below. Separating these operations makes it possible to give an integration exactly the permissions it needs.
Tickets - lists and pagination
Read the list with GET. It is good practice to provide the page number and page size even when you initially expect only a few records.
curl --request GET "$BASE_URL/api/v1/tickets?page=1&pageSize=25" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET"The response contains a data object with items, page, pageSize, totalItems, totalPages and hasNextPage. When hasNextPage is true, request the next page.
curl --request GET "$BASE_URL/api/v1/tickets?page=2&pageSize=25&sort=dateUpdated&direction=desc" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET"Sorting depends on the field supported by the API. For synchronisation, sort by dateUpdated in ascending or descending order and remember the last processed record.
Tickets - search and filters
The API lets you combine text search with field filters. Use search for general searching and filter to specify an operator and a value.
curl --get "$BASE_URL/api/v1/tickets" \
--data-urlencode "page=1" \
--data-urlencode "pageSize=25" \
--data-urlencode "search=VPN" \
--data-urlencode "filter=status:eq:Open" \
--data-urlencode "filter=priority:eq:High" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET"Examples of useful ticket filters:
filter=status:eq:Closed- closed tickets;filter=priority:eq:High- high priority;filter=subject:contains:VPN- a subject containing the specified text;filter=description:notEmpty:- tickets with a description;filter=isSpam:eq:false- tickets not marked as spam.
Read status, priority and other dictionary values from your database configuration through the values endpoint. Do not assume that the same names exist in every installation.
Tickets - response fields and expansions
For a basic list, keep the default field set. Request additional fields with fields and related data with include.
curl --get "$BASE_URL/api/v1/tickets" \
--data-urlencode "fields=id,itemType,subject,status,priority,requesterEmail,dateUpdated" \
--data-urlencode "include=relationships,users,files" \
--data-urlencode "page=1" \
--data-urlencode "pageSize=10" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET"Use fields=* when you need the complete model. Expansions may require additional scopes. If the integration cannot access users, relationships or files, remove the corresponding item from include or grant the appropriate permission.
For synchronisation, pay attention to id, itemType, attributes and meta. The object identifier is stable, while meta.etag is used for safe updates.
Tickets - statistics and field values
Statistics are useful, for example, for counting tickets by priority. They do not change data.
curl --get "$BASE_URL/api/v1/tickets/stats" \
--data-urlencode "field=priority" \
--data-urlencode "limit=20" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET"Request field values with an optional search:
curl --get "$BASE_URL/api/v1/tickets/values" \
--data-urlencode "field=priority" \
--data-urlencode "search=High" \
--data-urlencode "limit=20" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET"Before sending a new ticket, request the available values from the same database. This prevents the integration from sending a value that the local configuration does not recognise.
Tickets - creating a ticket
Create a new ticket with POST. The smallest useful set contains itemType, a subject and the requester's email address. Choose the remaining data to match your support process.
curl --request POST "$BASE_URL/api/v1/tickets" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: ticket-create-ERP-2026-001" \
--data '{
"itemType": "ticket",
"attributes": {
"customId": "ERP-TICKET-2026-001",
"subject": "VPN unavailable for the finance department",
"requesterEmail": "[email protected]",
"description": "The VPN connection is interrupted after a few minutes of use.",
"comments": "Ticket created from the ERP system.",
"source": "ERP",
"type": "Incident",
"category": "Network",
"status": "Open",
"priority": "High",
"impact": "Department",
"urgency": "High",
"severity": "Major",
"services": "VPN",
"tags": "vpn;finance;integration",
"externalNumber": "ERP-4581",
"referenceNumber": "INC-2026-001",
"currency": "PLN",
"estimatedCost": 150.00
}
}'The dictionary values in this example are illustrative. Replace them with values returned by the schema and the values endpoint for your database. A successful creation returns 201 Created, data.id and the current ETag in the header and in data.meta.etag. Save these values because they are needed for later operations.
Tickets - idempotency for write operations
Every request that creates, changes or deletes data should include a unique Idempotency-Key header. This prevents a ticket from being created twice when the application retries a request after a connection interruption.
curl --request POST "$BASE_URL/api/v1/tickets" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: ticket-create-ERP-2026-001" \
--data '{
"itemType": "ticket",
"attributes": {
"subject": "VPN unavailable for the finance department",
"requesterEmail": "[email protected]"
}
}'Repeating the same request with the same method, route, content and idempotency key should return the same record. A new operation must use a new key. Do not use one permanent key for every ticket.
Store the idempotency key in the integration together with its processing state. If you change the request body, use a new key even when it concerns the same ticket.
Tickets - reading one record
After creating or finding the ticket identifier, read the ticket by its UUID:
export TICKET_ID="TICKET_UUID"
curl --get "$BASE_URL/api/v1/tickets/$TICKET_ID" \
--data-urlencode "fields=*" \
--data-urlencode "include=relationships,users,files" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET"Read data.attributes and data.meta.etag from the response. The ETag can change after data, relationship, user or file changes, and after an action. Before a write operation, use the current ETag rather than a value remembered from an earlier read.
Tickets - editing and ETag protection
Use PATCH for an update. Send only the fields you want to change and the ETag read from the current ticket version.
curl --request PATCH "$BASE_URL/api/v1/tickets/$TICKET_ID" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
--header "Content-Type: application/json" \
--header "If-Match: $TICKET_ETAG" \
--header "Idempotency-Key: ticket-update-ERP-2026-001" \
--data '{
"attributes": {
"status": "In Progress",
"priority": "High",
"comments": "The network team is investigating the interrupted VPN session.",
"resolutionSummary": ""
}
}'A successful update returns 200 OK and a new ETag. Change action-managed fields such as isSpam, pin and rating through their dedicated endpoints. Do not try to bypass this separation with a normal PATCH.
When updating a deadline, cost or integration data, keep the type encoding returned by the schema. Send dates in ISO 8601 format and decimal values as JSON numbers.
Tickets - stale or missing ETag
The API blocks a write based on an outdated record version. If two processes work at the same time, the second one cannot overwrite the first one's changes without an explicit retry.
A stale ETag returns 412 Precondition Failed with code if_match_failed. A missing If-Match header returns 428 Precondition Required with code if_match_required.
curl --request PATCH "$BASE_URL/api/v1/tickets/$TICKET_ID" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
--header "Content-Type: application/json" \
--header "If-Match: $TICKET_ETAG" \
--header "Idempotency-Key: ticket-update-retry-ERP-2026-001" \
--data '{
"attributes": {
"comments": "Retrying the update after reading the current version."
}
}'After receiving either error, read the ticket again, check whether the change is still needed, and send it with a new ETag and a new idempotency key. Do not disable ETag protection in the integration.
Tickets - relationships with objects
A ticket can be linked with the objects visible in the relationship schema, including assets, documents, other tickets, changes, problems, releases, notes, approvals, work tasks and requested items. The available catalogue can depend on configuration and key scopes, so check relationshipTargets in the schema before writing a relationship.
Read relationships:
curl --request GET "$BASE_URL/api/v1/tickets/$TICKET_ID/relationships?page=1&pageSize=50" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET"Adding an asset relationship can look like this:
curl --request POST "$BASE_URL/api/v1/tickets/$TICKET_ID/relationships" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
--header "Content-Type: application/json" \
--header "If-Match: $TICKET_ETAG" \
--header "Idempotency-Key: ticket-relation-asset-ERP-2026-001" \
--data '{
"targetId": "ASSET_UUID",
"targetDataSet": "assets",
"targetItemType": "computer",
"relationshipType": "related"
}'To add several relationships in one request, use the batch operation:
curl --request POST "$BASE_URL/api/v1/tickets/$TICKET_ID/relationships:batch" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
--header "Content-Type: application/json" \
--header "If-Match: $TICKET_ETAG" \
--header "Idempotency-Key: ticket-relationship-batch-ERP-2026-001" \
--data '{
"add": [
{
"targetId": "OTHER_TICKET_UUID",
"targetDataSet": "tickets",
"targetItemType": "ticket",
"relationshipType": "related"
},
{
"targetId": "DOCUMENT_UUID",
"targetDataSet": "documents",
"targetItemType": "document",
"relationshipType": "related"
}
],
"remove": []
}'The targetItemType value must match the real type of the referenced object. The ticket ETag changes after a relationship is added or removed. Remove a relationship using the collection name and object identifier, usually with a relationshipType query parameter:
curl --request DELETE "$BASE_URL/api/v1/tickets/assets/$ASSET_ID?relationshipType=related" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
--header "If-Match: $TICKET_ETAG" \
--header "Idempotency-Key: ticket-relation-remove-ERP-2026-001"Tickets - relationships with users and clients
User relationships are separate from object relationships. You can assign an employee as an agent, add a watcher with watcher, identify the requesting application user with appUserRequester or indicate a client with clientRequester.
User relationship list:
curl --get "$BASE_URL/api/v1/tickets/$TICKET_ID/user-relationships" \
--data-urlencode "relationshipType=agent" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET"Assign an employee:
curl --request POST "$BASE_URL/api/v1/tickets/$TICKET_ID/user-relationships" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
--header "Content-Type: application/json" \
--header "If-Match: $TICKET_ETAG" \
--header "Idempotency-Key: ticket-agent-ERP-2026-001" \
--data '{
"targetId": "USER_UUID",
"targetDataSet": "users",
"relationshipType": "agent"
}'Change several user relationships in one request:
curl --request POST "$BASE_URL/api/v1/tickets/$TICKET_ID/user-relationships:batch" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
--header "Content-Type: application/json" \
--header "If-Match: $TICKET_ETAG" \
--header "Idempotency-Key: ticket-user-relationship-batch-ERP-2026-001" \
--data '{
"add": [
{
"targetId": "WATCHER_USER_UUID",
"targetDataSet": "users",
"relationshipType": "watcher"
}
],
"remove": []
}'Adding a watcher uses the same format but the watcher type. Save a client relationship with targetDataSet set to clients and type clientRequester. The tickets:users:write scope does not grant access to every user or bypass that user's permissions.
Removing an assignment requires the relationship type in the query parameter:
curl --request DELETE "$BASE_URL/api/v1/tickets/$TICKET_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: $TICKET_ETAG" \
--header "Idempotency-Key: ticket-agent-remove-ERP-2026-001"Tickets - files
Files use their own routes. To upload a file, you need the current ticket ETag, an idempotency header and a multipart request.
curl --request POST "$BASE_URL/api/v1/tickets/$TICKET_ID/files?relationshipType=documentation" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
--header "If-Match: $TICKET_ETAG" \
--header "Idempotency-Key: ticket-file-ERP-2026-001" \
--form "[email protected];type=text/plain"A successful upload returns 201 Created and file data, including its identifier and downloadUrl path. Read the file list with:
curl --request GET "$BASE_URL/api/v1/tickets/$TICKET_ID/files?page=1&pageSize=50" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET"File content is binary, so save the response to a file:
curl --request GET "$BASE_URL/api/v1/tickets/$TICKET_ID/files/$FILE_ID/content" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
--output downloaded-file.txtYou can attach an existing file to another ticket with POST /api/v1/tickets/{id}/files/{fileId}. Before deleting, check the file identifier and use the ticket ETag. Delete a file with DELETE /api/v1/tickets/{id}/files/{fileId}. Tickets do not have a separate action for selecting a main file.
Attach an existing file to another ticket:
curl --request POST "$BASE_URL/api/v1/tickets/$OTHER_TICKET_ID/files/$FILE_ID?relationshipType=manual" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
--header "If-Match: $OTHER_TICKET_ETAG" \
--header "Idempotency-Key: ticket-file-attach-ERP-2026-001"Delete a file from the current ticket:
curl --request DELETE "$BASE_URL/api/v1/tickets/$TICKET_ID/files/$FILE_ID" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
--header "If-Match: $TICKET_ETAG" \
--header "Idempotency-Key: ticket-file-delete-ERP-2026-001"Tickets - pinning, spam and reopening
Some ticket properties are changed through dedicated actions. Each action requires the current ETag and its own idempotency key.
Pin a ticket:
curl --request POST "$BASE_URL/api/v1/tickets/$TICKET_ID/pin" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
--header "Content-Type: application/json" \
--header "If-Match: $TICKET_ETAG" \
--header "Idempotency-Key: ticket-pin-ERP-2026-001" \
--data '{"pin":2}'Remove the pin by sending null:
curl --request POST "$BASE_URL/api/v1/tickets/$TICKET_ID/pin" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
--header "Content-Type: application/json" \
--header "If-Match: $TICKET_ETAG" \
--header "Idempotency-Key: ticket-unpin-ERP-2026-001" \
--data '{"pin":null}'Mark a ticket as spam:
curl --request POST "$BASE_URL/api/v1/tickets/$TICKET_ID/spam" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
--header "Content-Type: application/json" \
--header "If-Match: $TICKET_ETAG" \
--header "Idempotency-Key: ticket-spam-ERP-2026-001" \
--data '{"isSpam":true}'Undo the mark through the same route with {"isSpam":false}. Reopen a closed ticket with:
curl --request POST "$BASE_URL/api/v1/tickets/$TICKET_ID/reopen" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
--header "If-Match: $TICKET_ETAG" \
--header "Idempotency-Key: ticket-reopen-ERP-2026-001"These operations require tickets:pin:write, tickets:spam:write or tickets:reopen:write as appropriate. After each successful action, save the new ETag returned by the API.
Tickets - rating and escalation requests
After a ticket has been handled, you can save a rating and the reviewer's comment. The rating is a value from 0 to 5.
curl --request POST "$BASE_URL/api/v1/tickets/$TICKET_ID/rating" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
--header "Content-Type: application/json" \
--header "If-Match: $TICKET_ETAG" \
--header "Idempotency-Key: ticket-rating-ERP-2026-001" \
--data '{
"rating": 4,
"feedback": "The issue was resolved and communication with the team was smooth.",
"isEscalationRequested": true,
"escalationRequestReason": "Please perform an additional review of the VPN connection stability."
}'If you are saving only a rating, omit the escalation fields. If you include an escalation request, you also need tickets:escalation:write. A rating alone requires tickets:rating:write. After saving, the values appear as read-only fields, including rating, feedback, dateRating, dateFeedback, dateEscalationRequest and escalationRequestReason.
Tickets - approval decision
If an approval is assigned to a ticket, the designated approver can make the decision directly through the ticket route. The approver needs tickets:approval:write and must be assigned to that approval.
export APPROVAL_ID="APPROVAL_UUID"
curl --request POST "$BASE_URL/api/v1/tickets/$TICKET_ID/approvals/$APPROVAL_ID" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
--header "Content-Type: application/json" \
--header "If-Match: $TICKET_ETAG" \
--header "Idempotency-Key: ticket-approval-ERP-2026-001" \
--data '{
"approve": true,
"remark": "The change has been reviewed and can be deployed."
}'Reject the request with approve set to false and your own comment. APPROVAL_ID is the approval identifier, not a user identifier. If the integration creates approvals itself, it uses the separate approvals resource, identifies the approver and links the approval to the ticket with a relationship. After the decision, read the ticket again and save the new ETag.
Tickets - batch operations, deletion and errors
Send multiple operations through POST /api/v1/tickets:batch. Each item describes a create, update or delete operation. An update and a deletion require their own ifMatch because each record may have a different version.
curl --request POST "$BASE_URL/api/v1/tickets:batch" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: tickets-batch-ERP-2026-001" \
--data '{
"items": [
{
"operation": "create",
"create": {
"itemType": "ticket",
"attributes": {
"subject": "No access to the printer",
"requesterEmail": "[email protected]",
"description": "The printer does not respond to print requests.",
"source": "ERP"
}
}
},
{
"operation": "update",
"id": "TICKET_UUID",
"ifMatch": "\"CURRENT_ETAG\"",
"update": {
"attributes": {
"priority": "Normal"
}
}
}
]
}'The response can have status 200 or 207 Multi-Status when some items fail. Process every response item by its index, operation, status and error field. Do not assume that one failed item rolls back all the others.
Deleting one ticket first requires reading its current ETag:
curl --request DELETE "$BASE_URL/api/v1/tickets/$TICKET_ID" \
--header "X-Codenica-Client-Id: $CLIENT_ID" \
--header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
--header "If-Match: $TICKET_ETAG" \
--header "Idempotency-Key: ticket-delete-ERP-2026-001"After 200 OK, verify with another read that the ticket returns 404 with code ticket_not_found. Common problem responses include: 400 for invalid data, 401 for missing authentication, 403 for a missing scope, 404 for a missing record, 409 for a conflict, 412 for a stale ETag, 428 for a missing ETag or idempotency key and 429 after the limit is exceeded. A problem response includes, among other fields, title, detail, code and requestId. Keep these details in logs and retry only operations that can be repeated safely.
A practical order of work is: check the context, read the schema and values, read or create a ticket, save its ETag, make changes with idempotency and the current ETag, then read the new state after each action. At the end, verify synchronisation with a list filtered by customId or externalNumber.
