Skip to content

Latest commit

 

History

History
1181 lines (933 loc) · 50.9 KB

File metadata and controls

1181 lines (933 loc) · 50.9 KB

IT Service Desk — API Reference

REST API reference for the IT-service ticketing system. This document is generated from the actual Spring controllers and is the authoritative description of every endpoint the system exposes. For an always-live, interactive copy use the Swagger UI:

  • Swagger UI: http://localhost/swagger-ui/index.html
  • OpenAPI spec: http://localhost/v3/api-docs

The system is made of two HTTP services:

Service Default port Documented here
it-service-backend — main API 8081 Tickets, Comments, Attachments, Worklogs, CSAT, Users, Notifications, Notification Preferences, Products, Topics, Known Issues, Canned Responses, Agent-Product Limits, Dashboard Metrics, Admin Mail, Config, Internal/Workflow
llm-service — AI summaries 8082 AI Summaries

Conventions

Base URL

All traffic normally enters through the nginx reverse proxy:

http://localhost/api/v1          # via nginx (recommended)
http://localhost:8081/api/v1     # it-service-backend, direct
http://localhost:8082/api/v1     # llm-service, direct

All backend endpoints are prefixed with /api/v1. Paths in this document are written relative to the host (e.g. /api/v1/tickets).

Authentication

The backend is a stateless OAuth2 resource server. Every non-public endpoint requires a Keycloak-issued JWT access token in the Authorization header:

Authorization: Bearer <JWT>

Tokens are obtained from Keycloak directly (realm TicketSystemRealm); the frontend uses keycloak-js. There is no username/password login endpoint on this API — /api/v1/auth/login and /api/v1/auth/register are reserved/permit-listed paths handled by the Keycloak login flow, not by a backend controller. Password reset is handled entirely by Keycloak's native reset-credentials flow — the backend has no /api/v1/auth/** controller of its own (the former custom forgot/reset-password endpoints were removed). Self-service change password (with the current password) is still a backend endpoint: POST /api/v1/users/me/password.

Roles. The JWT's realm_access.roles are mapped to Spring authorities ROLE_<NAME>. Authorization is additive multi-role: a user holds a set of roles and the effective permission for an endpoint is the union — holding any one of the listed roles is enough. The five application roles, by axis, are:

  • customer — end user: opens tickets and messages on own tickets (product-scoped).
  • agent — operational fulfiller: claims tickets and acts only on claimed tickets (product-scoped).
  • lead_agent — operational team lead; a Keycloak composite of agent (so it inherits all agent abilities) plus: assign tickets to agents, act on tickets without claiming, manage product content (topics / known-issues / shared canned-responses) and a product-scoped team dashboard.
  • admin — system configuration (global): create users, assign roles, create/manage products, grant product access, agent limits, SLA / cache.
  • manager — oversight (global, read-only): all dashboards, reports and full read visibility; no operational actions and no system configuration.

A "super-admin" is simply a user that holds all of admin + lead_agent + manager (e.g. the superadmin seed user) — there is no dedicated super-admin role. Roles are cached in the user_roles table (Flyway V37), synced on /users/sync.

customer is a singleton role: an end user who opens tickets can never also be staff. Combining customer with any other role is rejected with 400 (error.role.customer.exclusive) on role assignment / user creation, keeping the customer and staff identity contexts mutually exclusive.

In this document the Role column lists who may call an endpoint:

  • Authenticated — any valid JWT (role-specific filtering happens in the service layer).
  • A specific role name (or list) — enforced by @PreAuthorize on the controller method; any one of the listed roles satisfies it.
  • Internal token — not JWT; see below.

Internal endpoints. Paths under /api/v1/internal/** bypass JWT entirely. They require a shared secret in the X-Internal-Token header (matching jbpm.kie-server.callback-token). Used only for service-to-service calls (jBPM KIE Server, llm-service).

Public (anonymous) endpoints. /api/v1/auth/login, /api/v1/auth/register (reserved permit-listed paths, no backend controller), /ws/** (WebSocket handshake), Swagger, and /actuator/health|info|metrics.

The llm-service does not run Spring Security. Its /api/v1/ai/** endpoints are reached only over the internal Docker/K8s network and are not exposed through nginx to end users.

Standard error response

All handled errors return a consistent JSON body (ErrorResponse):

{
  "status": 400,
  "error": "TICKET_LIMIT_EXCEEDED",
  "message": "Active ticket limit reached for this product.",
  "fieldErrors": {
    "title": "This field cannot be blank."
  },
  "timestamp": 1700000000000
}
Field Type Notes
status int HTTP status code.
error string Machine-readable error code. Present only for specific errors (USER_ALREADY_EXISTS, WRONG_CURRENT_PASSWORD, INVALID_PASSWORD, TICKET_LIMIT_EXCEEDED). Omitted otherwise.
message string Human-readable, localized to the caller's preferred language (en/tr).
fieldErrors object Field → message map. Present only for validation failures (400) and conflicts.
timestamp long Epoch milliseconds.

Common status codes: 400 validation / business-rule violation, 401 missing or invalid JWT, 403 authenticated but not permitted, 404 resource not found, 409 conflict (duplicate user, ticket limit), 413 upload too large, 429 rate limit exceeded, 500 unexpected error.

Pagination

List endpoints that support paging accept these query parameters:

Param Type Default Notes
page int 0 Zero-based page index.
size int 20 Page size. Constrained to 1..500.
sortBy string createdAt Field to sort by (ticket lists).
sortDir string desc asc or desc (ticket lists).

Most paginated endpoints return a Spring Page envelope:

{
  "content": [ /* array of items */ ],
  "pageable": { "pageNumber": 0, "pageSize": 20 },
  "totalElements": 137,
  "totalPages": 7,
  "number": 0,
  "size": 20,
  "first": true,
  "last": false,
  "numberOfElements": 20,
  "empty": false
}

GET /api/v1/users returns a trimmed envelope instead: { "content": [...], "totalElements", "totalPages", "page", "size" }.


Tickets

TicketController — base path /api/v1/tickets.

Method Endpoint Role Description
POST /api/v1/tickets customer Create a new ticket.
GET /api/v1/tickets customer, agent, lead_agent, manager, admin List tickets (role-scoped, paged, filtered).
GET /api/v1/tickets/pool agent, lead_agent Unclaimed NEW tickets in the agent's products.
GET /api/v1/tickets/my-assigned Authenticated Tickets the calling agent has claimed.
GET /api/v1/tickets/team agent, lead_agent Active tickets across the agent's authorized products.
GET /api/v1/tickets/all agent, lead_agent, manager, admin All tickets (all statuses) in authorized products.
GET /api/v1/tickets/by-product/{productId} Authenticated Tickets for one product (role-scoped).
GET /api/v1/tickets/{id} Authenticated Get one ticket with full detail.
GET /api/v1/tickets/{id}/sla-timer customer, agent, lead_agent, manager, admin Live SLA timer info for a ticket.
PUT /api/v1/tickets/{id}/claim agent, lead_agent Claim a ticket in any status except CLOSED.
DELETE /api/v1/tickets/{id}/claim agent, lead_agent Release the caller's own claim.
PUT /api/v1/tickets/{id}/assign lead_agent, admin Manually assign a ticket to a target agent.
PUT /api/v1/tickets/{id}/wait agent, lead_agent Action: put on hold (IN_PROGRESSWAITING_FOR_CUSTOMER).
PUT /api/v1/tickets/{id}/resume customer, agent, lead_agent Action: resume after a hold (WAITING_FOR_CUSTOMERIN_PROGRESS).
PUT /api/v1/tickets/{id}/resolve agent, lead_agent Action: resolve (IN_PROGRESSRESOLVED; reason code required).
PUT /api/v1/tickets/{id}/reopen customer, agent, lead_agent Action: reopen (RESOLVEDIN_PROGRESS).
PUT /api/v1/tickets/{id}/priority agent, lead_agent Change ticket priority.
PUT /api/v1/tickets/{id}/topic agent, lead_agent Change ticket topic.
PUT /api/v1/tickets/{id}/close customer, agent, lead_agent Close a ticket (note + reason code).
DELETE /api/v1/tickets/{id} admin Permanently delete a ticket.

Filtering query parameters

All list endpoints (GET /api/v1/tickets, /pool, /my-assigned, /team, /all, /by-product/{productId}) accept the pagination params plus these optional filters (all repeatable list parameters):

Param Type Description
status string[] Filter by status (NEW, IN_PROGRESS, WAITING_FOR_CUSTOMER, RESOLVED, CLOSED). Not accepted by /pool.
priority string[] Filter by priority (LOW, MEDIUM, HIGH, CRITICAL).
search string Case-insensitive free-text search over the ticket title (max 100 chars).
productId long[] Filter by product ID. Not accepted by /by-product/{productId}.
agentId string[] Filter by claiming agent's Keycloak ID.
topicId long[] Filter by topic ID.
slaStatus string[] Filter by SLA state.
csatRating string[] Filter by CSAT rating: 15 or NONE (no survey). Accepted only on /my-assigned and /all, and honoured only for admin/manager callers (silently ignored for others).
dateFrom ISO date-time Created-at lower bound.
dateTo ISO date-time Created-at upper bound.

Sorting by csatRating (sortBy=csatRating) is likewise honoured only for admin/manager on /my-assigned and /all; other callers fall back to createdAt.

POST /api/v1/tickets — Create ticket

Body TicketRequestDTO:

Field Type Required Notes
title string yes Not blank, max 100 chars.
description string yes Not blank, max 500 chars. Also stored as the first comment.
priority string yes LOW, MEDIUM, HIGH, CRITICAL.
productId long yes Product/category the ticket belongs to.
topicId long conditional Topic ID; must be an active topic of productId. May be omitted/null only when the product has no active topics (a topicless ticket); otherwise required.

Request:

POST /api/v1/tickets
Authorization: Bearer <JWT>
{
  "title": "Cannot connect to VPN",
  "description": "VPN times out since this morning. Error: ERR_TIMEOUT",
  "priority": "HIGH",
  "productId": 1,
  "topicId": 12
}

Response 200 OK (TicketResponseDTO):

{
  "id": 42,
  "title": "Cannot connect to VPN",
  "description": "VPN times out since this morning. Error: ERR_TIMEOUT",
  "status": "NEW",
  "priority": "HIGH",
  "productId": 1,
  "productNameTr": "Müşteri Yönetimi",
  "productNameEn": "CRM",
  "topicId": 12,
  "topicNameTr": "Şifre sıfırlama",
  "topicNameEn": "Password reset",
  "customerId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "customerName": "Ali Yilmaz",
  "claimers": [],
  "slaDeadline": "2026-05-22T17:00:00+03:00",
  "slaBreached": false,
  "slaElapsedMs": 0,
  "slaPausedAt": null,
  "createdAt": "2026-05-21T09:30:00+03:00",
  "resolvedAt": null,
  "closedAt": null,
  "hasCsat": false,
  "csatRating": null,
  "slaInfo": { "slaState": "active", "remainingMs": 27000000 },
  "auditLogs": []
}

GET /api/v1/tickets — List tickets

GET /api/v1/tickets?page=0&size=20&status=NEW&status=IN_PROGRESS&priority=HIGH&sortBy=createdAt&sortDir=desc

Response is a Page envelope whose content is an array of TicketResponseDTO.

GET /api/v1/tickets/{id} — Ticket detail

Returns a single TicketResponseDTO (same shape as the create response, including claimers, slaInfo and auditLogs). Path param id (long).

The csatRating field (1–5) is populated only for admin/manager callers; all other roles receive null. Likewise the CSAT_SUBMITTED audit-log entry (which carries the rating/comment) is included only for admin/manager and the ticket's own customer — agents/leads do not see it.

auditLogs[] items (TicketAuditLogDTO):

{
  "id": 7,
  "actorId": "f9e8d7c6-b5a4-3210-fedc-ba0987654321",
  "actorName": "Mehmet Demir",
  "actionType": "CLAIM",
  "reasonCode": null,
  "note": null,
  "previousState": "NEW",
  "newState": "IN_PROGRESS",
  "createdAt": "2026-05-21T10:05:00+03:00"
}

Mutation endpoints — request bodies

PUT /api/v1/tickets/{id}/claim — no body. Allowed in any status except CLOSED; claiming a CLOSED ticket returns 400. The first claim auto-promotes a NEW ticket to IN_PROGRESS, advancing the BPMN state machine via a transition signal.

DELETE /api/v1/tickets/{id}/claim — Body UnclaimRequestDTO: reasonCode (string, required), note (string, required when reasonCode is OTHER). Releasing the last claim moves the ticket IN_PROGRESSNEW, also driven through the BPMN.

PUT /api/v1/tickets/{id}/assign — Body AssignTicketRequestDTO: targetAgentId (string, required — Keycloak ID), note (string, optional). Capacity of the target agent is checked; returns 400/409 if the agent's limit is full. Like claim, assigning a NEW ticket advances it to IN_PROGRESS through the BPMN state machine.

Status action endpointsPUT /api/v1/tickets/{id}/wait, /resume, /resolve, /reopen. There is no generic status-change endpoint anymore (the former PUT /api/v1/tickets/{id}/status was removed). The caller runs an action, not a free-form target status — each action drives the status within its own guard: it is idempotent if already at the target, and returns 400 if the source state is incompatible. The NEWIN_PROGRESS transition is driven by claim/unclaim only, keeping the claim/status invariant intact. Each action takes an optional TicketActionRequestDTO body (resolve requires it because the reason code is mandatory):

Field Type Required Notes
reasonCode string conditional Required for resolve; optional for wait/resume/reopen.
note string conditional Required when reasonCode is OTHER.
Action Transition Roles
PUT .../wait IN_PROGRESSWAITING_FOR_CUSTOMER agent, lead_agent
PUT .../resume WAITING_FOR_CUSTOMERIN_PROGRESS customer, agent, lead_agent
PUT .../resolve IN_PROGRESSRESOLVED agent, lead_agent
PUT .../reopen RESOLVEDIN_PROGRESS customer, agent, lead_agent

The BPMN process is the authoritative state machine: a transition the BPMN does not accept is rejected with 400.

PUT /api/v1/tickets/42/resolve
{ "reasonCode": "SOLUTION_PROVIDED", "note": "Fix sent by email." }

PUT /api/v1/tickets/{id}/priority — Body PriorityChangeRequestDTO: priority (string, required), reasonCode (string, required), note (string — required when reasonCode is OTHER).

PUT /api/v1/tickets/{id}/topic — Body TopicChangeRequestDTO: topicId (long, required — an active topic of the same product), reasonCode (string, required), note (string — required when reasonCode is OTHER).

PUT /api/v1/tickets/{id}/closecustomer, agent, lead_agent. Body CloseTicketRequestDTO: reasonCode (string, required), note (string — required when reasonCode is OTHER).

DELETE /api/v1/tickets/{id}admin only; no body; returns 204 No Content.

GET /api/v1/tickets/{id}/sla-timercustomer, agent, lead_agent, manager, admin. Returns a JSON object describing the live SLA timer, e.g. { "slaState": "active", "remainingMs": 27000000, "deadlineTimestamp": 1780346400000 } (deadlineTimestamp is epoch-ms, and is -1 while paused/closed). slaState is one of active, paused, expired, completed. The SLA counts only active time: while paused (WAITING_FOR_CUSTOMER/RESOLVED) remainingMs is frozen (SLA budget minus accumulated active elapsed) and does not decrease; on resume (back to IN_PROGRESS) the countdown continues from that frozen value — time spent paused is not lost.


Ticket Comments

CommentController — base path /api/v1/tickets/{ticketId}/comments. All endpoints require an authenticated user; role-based filtering is applied in the service layer.

Method Endpoint Role Description
POST /api/v1/tickets/{ticketId}/comments Authenticated Add a comment to a ticket.
GET /api/v1/tickets/{ticketId}/comments Authenticated List a ticket's comments (filtered by role).

Comment types: EXTERNAL (visible to the customer), INTERNAL (operational staff — agent / lead_agent — only, hidden from customers). Customers may only add EXTERNAL comments to their own tickets. Posting is restricted to operational roles plus the owning customer: only an agent/lead_agent (or the ticket's own customer) may add a comment. A pure admin or manager is rejected server-side (error.comment.role.forbidden) — to comment they must also hold lead_agent, aligning comments with the worklog/attachment model. (Reading still follows the role-based visibility filter.)

A per-user posting cooldown (default 3s, COMMENT_COOLDOWN_SECONDS) and the max message length (COMMENT_MAX_LENGTH, default 500) are enforced server-side; clients read the current values from GET /api/v1/config/comments (see Config) rather than hardcoding them.

POST /api/v1/tickets/{ticketId}/comments

Path param ticketId (long). Body CommentRequestDTO:

Field Type Required Notes
message string yes Not blank, max 500 chars.
type string yes EXTERNAL or INTERNAL.

Request:

POST /api/v1/tickets/42/comments
{ "message": "I checked your VPN config, try again.", "type": "EXTERNAL" }

Response 200 OK (CommentDTO):

{
  "id": 128,
  "authorId": "f9e8d7c6-b5a4-3210-fedc-ba0987654321",
  "authorName": "Mehmet Demir",
  "authorRole": "AGENT",
  "message": "I checked your VPN config, try again.",
  "type": "EXTERNAL",
  "createdAt": "2026-05-21T11:30:00+03:00"
}

GET /api/v1/tickets/{ticketId}/comments

Returns a JSON array of CommentDTO ordered chronologically. Customers receive only EXTERNAL comments; operational staff (agent / lead_agent) receive both types.


Config

ConfigController — base path /api/v1/config. Read-only client configuration so the frontend mirrors backend settings instead of hardcoding them.

Method Endpoint Role Description
GET /api/v1/config/comments Authenticated Comment posting cooldown (seconds) and max length.

Response 200 OK:

{ "cooldownSeconds": 3, "maxLength": 500 }

These mirror the COMMENT_COOLDOWN_SECONDS and COMMENT_MAX_LENGTH (default 500) settings.


Admin Mail

AdminMailController — base path /api/v1/admin/mail. Admin-only SMTP diagnostics, so the configured mail server can be validated end-to-end without triggering a full ticket flow.

Method Endpoint Role Description
POST /api/v1/admin/mail/test admin Send a test email to the calling admin's own address (synchronously) and report the outcome.

POST /api/v1/admin/mail/test — no body. The mail is sent only to the calling admin's own address (not an arbitrary recipient), so the endpoint cannot be abused as an open relay. Returns 200 OK:

{ "success": true, "recipient": "admin@example.com", "error": "" }

On failure success is false and error carries the reason.


Attachments

AttachmentController — base path /api/v1. File content is stored in the database as BYTEA. Max file size 10 MB; text-based files are scanned for secret-like patterns.

Method Endpoint Role Description
POST /api/v1/tickets/{ticketId}/attachments customer, agent, lead_agent Upload a file to a ticket.
GET /api/v1/tickets/{ticketId}/attachments customer, agent, lead_agent, manager, admin List a ticket's attachment metadata.
GET /api/v1/attachments/{id} customer, agent, lead_agent, manager, admin Download a file's content.
DELETE /api/v1/attachments/{id} customer, agent, lead_agent Delete a file.

POST /api/v1/tickets/{ticketId}/attachmentsmultipart/form-data with a single part file. Path param ticketId (long). Returns 200 OK with AttachmentDTO metadata; returns 413 when the file exceeds the size limit.

{
  "id": 55,
  "fileName": "error_screenshot.png",
  "fileType": "image/png",
  "uploaderId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "createdAt": "2026-05-21T12:00:00+03:00"
}

GET /api/v1/tickets/{ticketId}/attachments — returns a JSON array of AttachmentDTO.

GET /api/v1/attachments/{id} — returns the raw bytes with the original Content-Type and a Content-Disposition: attachment; filename="..." header. Path param id (long).

DELETE /api/v1/attachments/{id} — returns 204 No Content. Customers may delete only their own uploads; agents only on their claimed tickets; lead_agent any (within authorized products).


Worklogs

TicketWorklogController — base path /api/v1/tickets. Tracks agent time spent on tickets.

Method Endpoint Role Description
POST /api/v1/tickets/{id}/worklogs agent, lead_agent Add a worklog entry.
GET /api/v1/tickets/{id}/worklogs agent, lead_agent, manager, admin List a ticket's worklogs (manager/admin read-only).
PUT /api/v1/tickets/{id}/worklogs/{worklogId} agent, lead_agent Update a worklog entry.
DELETE /api/v1/tickets/{id}/worklogs/{worklogId} agent, lead_agent Delete a worklog entry.
GET /api/v1/tickets/all-worklogs manager, admin List every worklog in the system.

POST/PUT body WorklogRequestDTO: minutes (int, required, ≥ 1), description (string, optional, max 500 chars).

Request:

POST /api/v1/tickets/42/worklogs
{ "minutes": 45, "description": "Reviewed firewall logs, updated port rules." }

Response 201 Created (WorklogResponseDTO):

{
  "id": 15,
  "ticketId": 42,
  "agentId": "f9e8d7c6-b5a4-3210-fedc-ba0987654321",
  "agentName": "Mehmet Demir",
  "minutes": 45,
  "description": "Reviewed firewall logs, updated port rules.",
  "createdAt": "2026-05-21T14:00:00+03:00",
  "updatedAt": "2026-05-21T14:00:00+03:00"
}

GET endpoints return JSON arrays of WorklogResponseDTO; each entry carries agentName (the agent's display name) alongside agentId. Agents may update/delete only their own worklogs; lead_agent may delete any. DELETE returns 204 No Content.


CSAT (Customer Satisfaction)

TicketCsatController — base path /api/v1/tickets. Surveys filled in at ticket closing.

Method Endpoint Role Description
POST /api/v1/tickets/{id}/csat customer Submit a CSAT survey for a resolved ticket.
GET /api/v1/tickets/{id}/csat agent, lead_agent, manager, admin Get the CSAT result of a ticket.
GET /api/v1/tickets/all-csats manager, admin List every CSAT result.

POST /api/v1/tickets/{id}/csat — Body CsatDTO: rating (int, required, 1–5), comment (string, optional). The ticket must be in RESOLVED status and owned by the caller; submitting the survey transitions the ticket to CLOSED. One survey per ticket.

Request:

POST /api/v1/tickets/42/csat
{ "rating": 5, "comment": "Resolved quickly, thanks!" }

Response 200 OK (Csat entity):

{
  "id": 9,
  "ticketId": 42,
  "rating": 5,
  "comment": "Resolved quickly, thanks!",
  "createdAt": "2026-05-21T16:00:00+03:00"
}

Users

UserController — base path /api/v1/users.

Method Endpoint Role Description
POST /api/v1/users/sync Authenticated Sync the logged-in user from the JWT into the local DB (also refreshes the cached user_roles set).
GET /api/v1/users admin, manager List users (paged, searchable, role-filterable).
GET /api/v1/users/{id} self, admin, manager Get a user by Keycloak ID (callers may always read their own record).
GET /api/v1/users/agents agent, lead_agent, admin, manager List all agent users with their authorized products.
GET /api/v1/users/agents/capacity lead_agent, admin List agents with current load/limit for a product.
PUT /api/v1/users/me Authenticated Update the caller's profile (name, email).
POST /api/v1/users/me/password Authenticated Change the caller's password.
PUT /api/v1/users/me/language Authenticated Update the caller's preferred language.
PUT /api/v1/users/me/theme Authenticated Update the caller's preferred theme.
PUT /api/v1/users/me/date-format Authenticated Update the caller's preferred date display format.
GET /api/v1/users/me/2fa Authenticated List the caller's registered TOTP devices.
DELETE /api/v1/users/me/2fa/{credentialId} Authenticated Delete one of the caller's TOTP devices.
POST /api/v1/users/me/2fa/notify-added Authenticated Trigger the "2FA device added" notification email.
GET /api/v1/users/me/pdf-preferences Authenticated Get the caller's last-used PDF export modal selections.
PUT /api/v1/users/me/pdf-preferences Authenticated Persist the caller's PDF export modal selections.
PUT /api/v1/users/me/panel-preferences Authenticated Persist the caller's sidebar ticket-panel visibility selections.
PUT /api/v1/users/me/onboarding-complete Authenticated Mark the caller's onboarding as completed (idempotent).
POST /api/v1/users/{userId}/products/{productId} admin Grant an agent access to a product.
DELETE /api/v1/users/{userId}/products/{productId} admin Revoke an agent's product access.
PUT /api/v1/users/{userId}/status admin Activate / deactivate a user.
PUT /api/v1/users/{userId}/roles admin Replace a user's realm roles.
POST /api/v1/users/admin/create admin Create a new Keycloak user.
GET /api/v1/users/admin/roles admin List assignable realm roles.

POST /api/v1/users/sync

No body — the user is derived from the JWT. Returns a UserDTO:

{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "email": "user@example.com",
  "fullName": "Ali Yilmaz",
  "role": "AGENT",
  "isActive": true,
  "preferredLanguage": "tr",
  "preferredTheme": "dark",
  "preferredDateFormat": "DMY_SLASH",
  "createdAt": "2026-01-15T09:00:00+03:00",
  "authorizedProducts": [
    { "id": 1, "name": "CRM", "isActive": true, "maxActiveTickets": 5 }
  ]
}

The role field carries the user's primary role for display/routing. Because roles are additive, the authoritative set of all roles the user holds is cached server-side in the user_roles table (Flyway V37, synced on this call) and drives @PreAuthorize checks — a user may, for example, hold both agent and manager.

GET /api/v1/users

Query params: search (string, optional), role (string[], optional), excludeGlobalRoles (boolean, default false), productId (long[], optional), sortBy (string, default name), sortDir (string, default asc), page (int, ≥ 0), size (int, 1–500). Returns the trimmed envelope: { "content": [UserDTO...], "totalElements", "totalPages", "page", "size" }.

Other request bodies / parameters

  • GET /api/v1/users/agents/capacity — query productId (long, required). Returns AgentCapacityDTO[].
  • PUT /api/v1/users/me — Body UpdateProfileRequest: firstName, lastName (≤ 50 chars each), email (valid email). All required. Returns UserDTO. 409 if email is taken.
  • POST /api/v1/users/me/password — Body ChangePasswordRequest: currentPassword (required), newPassword (required, ≥ 8 chars, must satisfy realm policy). Returns 204 No Content; 400 if the current password is wrong or the new one violates policy.
  • PUT /api/v1/users/me/language — query lang (string: en or tr). Returns UserDTO.
  • PUT /api/v1/users/me/theme — query theme (string: light or dark). Returns UserDTO.
  • PUT /api/v1/users/me/date-format — query format (string: one of DMY_SLASH, MDY_SLASH, YMD_DASH, DMY_DOT, MED). Drives every date shown in the UI. Returns UserDTO.
  • GET /api/v1/users/me/pdf-preferences — returns PdfPreferencesDTO containing the caller's last-used PDF export modal selections (selected sections, language, orientation, etc.) as an opaque JSON string. Returns null if no preference has been saved yet.
  • PUT /api/v1/users/me/pdf-preferences — Body PdfPreferencesDTO; persists the export modal selections verbatim. The frontend reads this back to pre-fill the modal on next open. Returns 204 No Content.
  • PUT /api/v1/users/me/panel-preferences — Body PanelPreferencesDTO; persists the agent/lead sidebar ticket-panel visibility selections (workspace, pool, history, team, all-tickets) verbatim as an opaque JSON string (max 500 chars). Hydrated back to the client via /users/sync. Returns 204 No Content.
  • PUT /api/v1/users/me/onboarding-complete — no body. Marks the caller's onboarding flow as completed. Idempotent. Returns 204 No Content.
  • PUT /api/v1/users/{userId}/status — query active (boolean). An admin cannot deactivate themselves (400). Returns UserDTO.
  • PUT /api/v1/users/{userId}/roles — Body: JSON array of role strings (non-empty); roles are additive, so a user may hold several, e.g. ["agent","lead_agent"] or ["manager","admin"]. Returns UserDTO. Assignable roles are customer, agent, lead_agent, admin, manager. customer is a singleton — combining it with any other role is rejected with 400.
  • POST /api/v1/users/admin/create — Body CreateUserRequest (see below). Returns 201 Created with UserCreationResponseDTO. 409 if email/username already exists.

CreateUserRequest:

Field Type Required Notes
username string yes Unique, 3–50 chars.
email string yes Unique, valid email.
firstName string yes ≤ 50 chars.
lastName string yes ≤ 50 chars.
password string yes ≥ 8 chars (temporary password by default).
roles string[] yes At least one realm role.
temporaryPassword boolean no Default true.
POST /api/v1/users/admin/create
{
  "username": "john.doe",
  "email": "john.doe@example.com",
  "firstName": "John",
  "lastName": "Doe",
  "password": "Temp1234!",
  "roles": ["agent"],
  "temporaryPassword": true
}

Response 201 Created:

{
  "keycloakId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "username": "john.doe",
  "email": "john.doe@example.com",
  "fullName": "John Doe",
  "assignedRoles": ["agent"]
}

Notifications

NotificationController — base path /api/v1/notifications. All endpoints require an authenticated user and operate only on the caller's own notifications.

Method Endpoint Role Description
GET /api/v1/notifications Authenticated List the caller's notifications (paged).
GET /api/v1/notifications/unread-count Authenticated Count of unread notifications.
PATCH /api/v1/notifications/{id}/read Authenticated Mark one notification as read.
POST /api/v1/notifications/read-all Authenticated Mark all notifications as read.
DELETE /api/v1/notifications/{id} Authenticated Delete one notification.
DELETE /api/v1/notifications Authenticated Delete all of the caller's notifications.

GET /api/v1/notifications — query page (int, ≥ 0), size (int, 1–500). Returns a Page envelope whose content is an array of NotificationResponse:

{
  "id": 42,
  "userId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "message": "Ticket #12 has been assigned.",
  "isRead": false,
  "createdAt": "2026-05-21T13:00:00+03:00",
  "type": "TICKET_ASSIGNED",
  "referenceId": 12,
  "referenceType": "TICKET"
}

GET /api/v1/notifications/unread-count — returns {"count": 3}.

PATCH, POST /read-all, and the DELETE endpoints return 204 No Content.


Notification Preferences

NotificationPreferenceController — base path /api/v1/notification-preferences. Each user reads and writes their own preferences.

Method Endpoint Role Description
GET /api/v1/notification-preferences Authenticated Get the caller's notification preferences.
PUT /api/v1/notification-preferences Authenticated Update the caller's notification preferences.

Both return NotificationPreferenceResponse. If no preference row exists, all flags default to true. On PUT, fields sent as null keep their current value (UpdateNotificationPreferenceRequest).

{
  "emailOnTicketCreated": true,
  "emailOnTicketAssigned": true,
  "emailOnStatusChanged": true,
  "emailOnCommentAdded": true,
  "emailOnSlaWarning": true,
  "emailOnSlaBreached": true,
  "emailOnTicketResolved": true,
  "notifyOnTicketCreated": true,
  "notifyOnTicketAssigned": true,
  "notifyOnStatusChanged": true,
  "notifyOnCommentAdded": true,
  "notifyOnSlaWarning": true,
  "notifyOnSlaBreached": true,
  "notifyOnTicketResolved": true
}

Products

ProductController — base path /api/v1/products. Products are the support categories.

Method Endpoint Role Description
GET /api/v1/products Authenticated List products visible to the caller's role.
GET /api/v1/products/{id} Authenticated Get a single product.
POST /api/v1/products admin Create a product.
PUT /api/v1/products/{id} admin Update a product's name / active flag.
PATCH /api/v1/products/{id}/limit admin Update the product's default concurrent-ticket limit.
DELETE /api/v1/products/{id} admin Delete a product.

GET /api/v1/productscustomer / agent see only their authorized products; admin and manager see all. Product/category names are bilingual: both nameTr and nameEn variants are returned (at least one is non-null) and the client picks the variant matching its UI language, falling back to the other. Returns a JSON array of ProductDTO:

[
  { "id": 1, "nameTr": "Müşteri Yönetimi", "nameEn": "CRM", "isActive": true, "maxActiveTickets": 5 },
  { "id": 2, "nameTr": null, "nameEn": "ERP", "isActive": true, "maxActiveTickets": null }
]

POST / PUT /api/v1/products — Body is a Product entity with the bilingual name fields, e.g. { "nameTr": "Kurumsal Kaynak", "nameEn": "ERP", "isActive": true } (at least one of nameTr / nameEn required). Returns ProductDTO.

PATCH /api/v1/products/{id}/limit — Body ProductLimitUpdateRequestDTO: maxActiveTickets (int, nullable — null removes the limit). Returns ProductDTO.

DELETE /api/v1/products/{id} — returns 204 No Content.


Ticket Topics

TicketTopicController — topics are sub-categories belonging to a product. (Controller has no class-level base path; full paths are shown below.)

Method Endpoint Role Description
GET /api/v1/products/{productId}/topics Authenticated List a product's topics.
POST /api/v1/products/{productId}/topics lead_agent Create a topic under a product.
PUT /api/v1/topics/{id} lead_agent Update a topic's name / active flag.
DELETE /api/v1/topics/{id} lead_agent Delete a topic.

GET /api/v1/products/{productId}/topics — query includeInactive (boolean, default false). Topic names are bilingual (nameTr / nameEn, client picks by UI language with fallback). Returns a JSON array of TicketTopicDTO:

[
  { "id": 12, "productId": 3, "nameTr": "Şifre sıfırlama", "nameEn": "Password reset", "isActive": true }
]

POST / PUT body TicketTopicDTO: nameTr / nameEn (each ≤ 255 chars; at least one non-blank required on create — isActive-only updates may omit both), isActive (boolean). Returns TicketTopicDTO. DELETE returns 204 No Content.


Known Issues

KnownIssueController — knowledge-base entries tied to a product (and optionally a topic). (No class-level base path; full paths shown below.) List/detail require product authorization; write operations require lead_agent (product content management).

Method Endpoint Role Description
GET /api/v1/products/{productId}/known-issues Authenticated List a product's known issues.
GET /api/v1/known-issues/{id} Authenticated Get one known-issue entry.
POST /api/v1/products/{productId}/known-issues lead_agent Create a known-issue entry.
PUT /api/v1/known-issues/{id} lead_agent Update a known-issue entry.
DELETE /api/v1/known-issues/{id} lead_agent Delete a known-issue entry.

GET /api/v1/products/{productId}/known-issues — query topicId (long, optional), includeInactive (boolean, default false). Returns a JSON array of KnownIssueDTO.

Title and content are bilingual (*Tr / *En variants); the client picks the variant matching its UI language with fallback.

POST / PUT body KnownIssueDTO:

Field Type Required Notes
topicId long no Optional topic association.
titleTr string conditional Turkish title (≤ 255 chars). At least one of titleTr / titleEn required.
titleEn string conditional English title (≤ 255 chars).
contentTr string conditional Turkish content (≤ 10000 chars). At least one of contentTr / contentEn required.
contentEn string conditional English content (≤ 10000 chars).
isActive boolean no Whether shown to users.

Response KnownIssueDTO:

{
  "id": 42,
  "productId": 3,
  "topicId": 12,
  "titleTr": "VPN bağlantısı kopuyor",
  "titleEn": "VPN connection drops",
  "contentTr": "Ağ ayarlarınızı kontrol edip ...",
  "contentEn": "Check your network settings and ...",
  "isActive": true,
  "createdBy": "f9e8d7c6-b5a4-3210-fedc-ba0987654321",
  "createdAt": "2026-05-10T09:00:00+03:00",
  "updatedAt": "2026-05-12T11:00:00+03:00"
}

DELETE returns 204 No Content.


Canned Responses

CannedResponseController — base path /api/v1/canned-responses. Reusable reply templates for agents. Every endpoint requires one of agent, lead_agent, or admin — customers cannot access this feature.

Two scopes: PERSONAL (owned by the creating agent, only they can see/edit/delete it) and SHARED (product-wide or global; only lead_agent / admin may create or mutate shared templates). Three visibility values control which comment type the template targets: EXTERNAL, INTERNAL, or BOTH.

Method Endpoint Role Description
GET /api/v1/canned-responses agent, lead_agent, admin List canned responses visible to the caller (composer picker; non-paged).
GET /api/v1/canned-responses/paged agent, lead_agent, admin Paged + filtered list for the management screen.
POST /api/v1/canned-responses agent, lead_agent, admin Create a canned response.
PUT /api/v1/canned-responses/{id} agent, lead_agent, admin Update a canned response.
DELETE /api/v1/canned-responses/{id} agent, lead_agent, admin Delete a canned response.
POST /api/v1/canned-responses/{id}/favorite agent, lead_agent, admin Mark a response as a favorite (idempotent).
DELETE /api/v1/canned-responses/{id}/favorite agent, lead_agent, admin Remove a favorite mark (idempotent).

GET /api/v1/canned-responses

Returns the caller's own personal templates plus all shared templates (global shared + those tied to the caller's authorized products). Optional query params:

Param Type Description
productId long Scope shared templates to this product (global + product-specific).
scope string PERSONAL or SHARED.
visibility string EXTERNAL, INTERNAL, or BOTH.
q string Free-text search over title and shortcut.

Returns a JSON array of CannedResponseDTO.

GET /api/v1/canned-responses/paged

Server-side filtered + paginated variant for the management list. Accepts productId (long, specific product) or global (boolean, productless templates only), scope, visibility, lang (tr/en), q (search), plus page/size. Results are ordered favorites-first then by updatedAt descending. Returns a Page envelope of CannedResponseDTO.

POST / PUT body CannedResponseDTO

Field Type Required Notes
title string yes Max 150 chars. Language-neutral management label.
shortcut string no Max 50 chars. Slash-command shortcut (without the /).
contentTr string conditional Turkish content variant (max 2000 chars). At least one of contentTr / contentEn required.
contentEn string conditional English content variant (max 2000 chars).
scope string yes PERSONAL or SHARED. Creating SHARED requires lead_agent or admin.
productId long no Optional product association (meaningful only for SHARED; null = global).
visibility string yes EXTERNAL, INTERNAL, or BOTH.

Response CannedResponseDTO:

{
  "id": 7,
  "title": "VPN bağlantı adımları",
  "shortcut": "vpn",
  "contentTr": "VPN sorununuzu çözmek için şu adımları uygulayın: ...",
  "contentEn": "To resolve your VPN issue, follow these steps: ...",
  "scope": "PERSONAL",
  "ownerAgentId": "f9e8d7c6-b5a4-3210-fedc-ba0987654321",
  "productId": null,
  "visibility": "EXTERNAL",
  "favorite": false,
  "createdAt": "2026-05-10T09:00:00+03:00",
  "updatedAt": "2026-05-10T09:00:00+03:00"
}

DELETE returns 204 No Content. POST /{id}/favorite and DELETE /{id}/favorite return 204 No Content (both idempotent).


Agent-Product Limits

AgentProductLimitController — base path /api/v1/agents/{agentId}/limits. Per-agent overrides of the product-level concurrent-ticket limit.

Method Endpoint Role Description
GET /api/v1/agents/{agentId}/limits admin List all product-limit overrides for an agent.
PUT /api/v1/agents/{agentId}/limits/{productId} admin Create / update an agent's limit for a product.
DELETE /api/v1/agents/{agentId}/limits/{productId} admin Remove an agent/product override.

PUT body AgentProductLimitRequestDTO: useCustomLimit (boolean), maxActiveTickets (int, nullable).

Response AgentProductLimitResponseDTO:

{
  "agentId": "f9e8d7c6-b5a4-3210-fedc-ba0987654321",
  "productId": 10,
  "productName": "CRM",
  "useCustomLimit": true,
  "maxActiveTickets": 3,
  "effectiveLimit": 3
}

GET returns a JSON array of the above; DELETE returns 204 No Content.


Dashboard Metrics

MetricsController — base path /api/v1/metrics. Aggregated KPIs and analytics. The management dashboards are open to manager, lead_agent and admin; results are Caffeine-cached (5-minute TTL). Scope is resolved from the JWT: admin/manager see everything (global), while a pure lead_agent is restricted to the products they are authorized on (agent-performance returns that product-scoped team dashboard).

Management dashboards

Method Endpoint Role Description
GET /api/v1/metrics/dashboard-summary manager, lead_agent, admin Headline KPIs (open tickets, SLA breach rate, response time, CSAT).
GET /api/v1/metrics/status-distribution manager, lead_agent, admin Ticket counts per status.
GET /api/v1/metrics/agent-performance manager, lead_agent, admin Agent leaderboard (load, resolution speed, CSAT, SLA). lead_agent sees its product-scoped team dashboard; admin/manager see all.
GET /api/v1/metrics/ticket-timeline manager, lead_agent, admin Daily created/resolved/closed/breach trend.
GET /api/v1/metrics/priority-sla-metrics manager, lead_agent, admin SLA metrics broken down by priority.
GET /api/v1/metrics/product-metrics manager, lead_agent, admin Per-product ticket metrics.
GET /api/v1/metrics/csat-metrics manager, lead_agent, admin Detailed CSAT analytics.
GET /api/v1/metrics/alerts-backlog manager, lead_agent, admin SLA-breach alerts and backlog summary.
GET /api/v1/metrics/worklog-completion manager, lead_agent, admin Worklog totals and ticket-completion stats.

Personal & oversight dashboards

Method Endpoint Role Description
GET /api/v1/metrics/me/customer Authenticated The caller's own customer dashboard (tickets they opened). Non-customers simply see zeros.
GET /api/v1/metrics/me/agent agent, lead_agent The caller's own agent-performance dashboard (tickets they claimed + their worklogs).
GET /api/v1/metrics/users/{userId}/agent manager, lead_agent, admin A specific user's agent dashboard. admin/manager global; a pure lead_agent sees only the slice within its authorized products and gets 403 for agents sharing no product.
GET /api/v1/metrics/users/{userId}/customer manager, admin A specific user's customer dashboard. Leads have no access.
GET /api/v1/metrics/products/{productId}/dashboard manager, lead_agent, admin Dedicated single-product dashboard. admin/manager any product; a pure lead_agent only products it is authorized on (else 403).

Query parameters:

Endpoint Param Type Default
/ticket-timeline days int 30
/priority-sla-metrics days int (optional)
/product-metrics days int (optional)
/csat-metrics months int 3
/worklog-completion days int 30
/me/customer, /me/agent, /users/{userId}/agent, /users/{userId}/customer, /products/{productId}/dashboard days int (optional) 30 (clamped 1–365)

Each endpoint returns its dedicated DTO (DashboardMetricsDTO, StatusDistributionDTO, AgentPerformanceDTO, TicketTimelineDTO, PrioritySLAMetricsDTO, ProductMetricsDTO, CSATMetricsDTO, AlertsBacklogDTO, WorklogCompletionDTO, CustomerDashboardDTO, AgentDashboardDTO, ProductDashboardDTO). Example GET /api/v1/metrics/dashboard-summary:

{
  "openTickets": 137,
  "slaBreachRate": 4.2,
  "averageResponseTimeHours": 3.6,
  "averageCsat": 4.4,
  "priorityDistribution": { "LOW": 40, "MEDIUM": 60, "HIGH": 30, "CRITICAL": 7 }
}

Field names of metric DTOs are illustrative — consult the Swagger UI / OpenAPI spec for the exact schema of each metrics response.


AI Summaries

AiSummaryControllerserved by llm-service at base path /api/v1/ai/summaries (port 8082). These endpoints have no Spring Security; they are called service-to-service (by it-service-backend / internal callers) and are not exposed to end users via nginx.

Method Endpoint Role Description
POST /api/v1/ai/summaries Internal Summarize a ticket from a supplied raw payload.
POST /api/v1/ai/summaries/tickets/{ticketId}/generate Internal Fetch ticket data and generate a summary.
GET /api/v1/ai/summaries/tickets/{ticketId}/latest Internal Get the most recent summary for a ticket.
GET /api/v1/ai/summaries/tickets/{ticketId} Internal List all summaries for a ticket (newest first).

POST /api/v1/ai/summaries — Body SummarizeRequestDTO: ticketId (long), ticket (object), comments (array), worklogs (array), resolutionNote (object, optional), knownIssues (array), language (string, tr or en, default tr).

POST /api/v1/ai/summaries/tickets/{ticketId}/generate — path param ticketId (long), query language (string, default tr). llm-service pulls the ticket data from it-service-backend (GET /api/v1/internal/tickets/{ticketId}/full), sends it to the Groq LLM, and persists the result.

Response AiSummaryResponseDTO:

{
  "id": 7,
  "ticketId": 42,
  "model": "llama-3.1-8b-instant",
  "promptTokens": 850,
  "completionTokens": 120,
  "summary": "The customer reported a VPN timeout. The agent identified a blocked port and ...",
  "createdAt": "2026-05-21T15:30:00+03:00"
}

GET .../tickets/{ticketId} returns a JSON array of AiSummaryResponseDTO.


Internal / Workflow

These endpoints are authenticated by the X-Internal-Token header (not JWT). They live under /api/v1/internal/** and are used only for service-to-service communication.

Internal Tickets

InternalTicketController — base path /api/v1/internal/tickets.

Method Endpoint Auth Description
GET /api/v1/internal/tickets/{ticketId}/full X-Internal-Token Full ticket bundle (ticket, comments, worklogs, known issues) — consumed by llm-service.

Returns a JSON object: { "ticket": TicketResponseDTO, "comments": [CommentDTO], "worklogs": [WorklogResponseDTO], "knownIssues": [KnownIssueDTO] }.

Workflow Callback

WorkflowCallbackController — base path /api/v1/internal/workflow.

Method Endpoint Auth Description
POST /api/v1/internal/workflow/callback X-Internal-Token jBPM KIE Server posts process events (SLA breach, process completion).

POST /api/v1/internal/workflow/callback — header X-Internal-Token (required). Body WorkflowCallbackDTO:

Field Type Required Notes
ticketId long yes The ticket the event relates to.
eventType string yes SLA_BREACHED or PROCESS_COMPLETED.
processInstanceId long no jBPM process instance ID.
additionalData string no Free-text payload.
POST /api/v1/internal/workflow/callback
X-Internal-Token: <shared-secret>
{
  "ticketId": 42,
  "eventType": "SLA_BREACHED",
  "processInstanceId": 1001,
  "additionalData": "SLA deadline was 2026-05-21T17:00:00Z"
}

Returns 200 with a plain-text body on success; 400 for an unknown eventType, 401 for a missing/invalid token, 404 if the ticket does not exist.


Endpoint summary

Resource Endpoints
Tickets 20
Ticket Comments 2
Config 1
Admin Mail 1
Attachments 4
Worklogs 5
CSAT 3
Users 23
Notifications 6
Notification Preferences 2
Products 6
Ticket Topics 4
Known Issues 5
Canned Responses 7
Agent-Product Limits 3
Dashboard Metrics 14
AI Summaries (llm-service) 4
Internal / Workflow 2
Total 112