Skip to content

API

  • Replying to a ticket that carries no requester e-mail address no longer reports the mail server as unreachable. Sending was attempted with an empty recipient list, and the resulting rejection was reported as “The configured mail server could not be reached” — a misleading message that sent operators checking a healthy SMTP host, while a test send from the mail settings page succeeded every time. Such a send now fails immediately with its own message, “The e-mail could not be sent, it has no recipient address”, and the log line names the ticket’s subject instead of the transport. The check covers every outbound path (ticket reply, processing a ticket with “send e-mail” ticked, workflow e-mail actions, welcome mail), and blank address entries are now skipped rather than rejected by the mail library. Tickets created by hand or through a form with no e-mail field are the ones affected; a ticket opened from an incoming e-mail always has a requester address.
  • The knowledge base integration takes a second address: Knowledge base site address (siteUrl), alongside the existing API address. They are different hosts on a real deployment — the API answers JSON and is not meant to be opened in a browser — so the dashboard can now link operators to the help centre people actually read. The field is optional: an instance connected before this version keeps working untouched, and one that publishes no help centre can leave it empty. A value that IS supplied is validated like any other address on this form.
  • Attachment records on the dashboard conversation endpoint (GET v1/dashboard/tickets/{id}/messages) now carry an objectKey field as well. Files are downloaded with GET v1/attachments/{objectKey}; because the response returned only id, originalFileName, contentType and fileSize until now, a frontend could list an attachment by name but could not download it. The new field is required — frontends must regenerate their clients.

  • The integration conversation endpoints (GET v1/integration/tickets/{id}/messages and its tracking-key equivalent) now check the pool of the parent tickets against the token’s pool scope, not just the pool of the ticket addressed. Child tickets are opened in the parent’s pool, so the chain started out in a single pool; but once a ticket is moved to another pool from the dashboard the chain can span two pools, and a token scoped to pool A could read the message contents of a parent ticket in pool B. Parent tickets outside the scope are now dropped from the chain and their messages are never queried. The same walk on the dashboard is deliberately unchanged — authority there comes from a permission, not from a pool set.

  • When the same token uses the same attachment id in two concurrent message requests, the attachment is now attached to only one of them. Consuming the attachment claim was made atomic; previously both requests were told “this attachment is yours” and the same file could be attached to two different tickets, even though the endpoint states an attachment id may be used once. The losing request now gets the same response (403) as it would for an attachment id that was never issued.

  • An Idempotency-Key first written against an open ticket and then repeated against a different ticket id no longer produces a self-contradictory response. It used to return continuedInNewTicket: true together with parentTicketId: null — a claim of continuing into a parent that does not exist. continuedInNewTicket is now true only when there really is a parent; in this scenario it is false with parentTicketId null, and ticketId points at the ticket the message actually landed on.

  • On upload, originalFileName now stores only the file name rather than the path the client sent (fixtures/sample.txt -> sample.txt, C:\dir\report.pdf -> report.pdf). Since this value is written into the Content-Disposition header on download, storing a client-supplied path verbatim was wrong. Applies to both POST v1/attachments and POST v1/integration/attachments; existing records are unchanged.

  • Breaking (database): the TicketEmailLogs table was renamed to TicketMessages, and the TicketEmailLogId column on TicketAttachments to TicketMessageId. The migration renames in place without moving data — no record is lost or copied — but this upgrade is not zero downtime: during a rolling update a pod still running the old version queries the old table name and returns 500. Upgrade this version with a short maintenance window, or by setting the deployment strategy to Recreate for this version. The same change renamed two localization keys (in both en-US and tr-TR) — TicketAttachment:Create:TicketEmailLogIsRequired -> TicketAttachment:Create:TicketMessageIsRequired, TicketEmailLog:RecordInbound:SourceMessageIdIsRequired -> TicketMessage:RecordInbound:SourceMessageIdIsRequired — and the controller action behind the dashboard ticket-history endpoint (and therefore its OpenAPI operation id) became GetEmailLogsAsync -> GetMessagesAsync; the route ({id}/email-logs) did not change in this step, see the route change below. Two DTO/OpenAPI schema names were also renamed: TicketEmailLogForDashboardResponseDto -> TicketMessageForDashboardResponseDto and TicketEmailLogAttachmentForDashboardResponseDto -> TicketMessageAttachmentForDashboardResponseDto — regenerate your client types.
  • The dashboard ticket conversation endpoint was renamed: GET v1/dashboard/tickets/{id}/email-logs -> GET v1/dashboard/tickets/{id}/messages. The old address keeps returning the same body for one version, announcing that it is time to move with the Deprecation: true and Link: <.../messages>; rel="successor-version" headers.
  • Ticket conversation entries now have a channel field (Email / Api) showing how the message arrived. Every existing record, and every message that arrives by e-mail today, is marked Email; added to the GET v1/dashboard/tickets/{id}/messages response.
  • The opening text of a ticket created through the integration API (the description, or the subject when there is none) is now recorded as the ticket’s first conversation message too — as it already was for tickets opened by e-mail. The conversation now starts from the first message and appears in the GET v1/dashboard/tickets/{id}/messages response.
  • New endpoints: POST v1/integration/tickets/{id}/messages and POST v1/integration/tickets/by-tracking-key/{trackingKey}/messages — they add a reply coming from the customer’s own system to an existing ticket’s conversation. When an Idempotency-Key header is sent and the same key is repeated with the same content, nothing is written and the original message is returned with 200 (isReplay: true); repeated with different content it returns 409. There are two distinct 409s, and because the client’s correct reaction is the opposite in each case, tell them apart by the error.code field — the message text varies with Accept-Language: FD:3001 means this key was already used with different content (do not retry, fix the key or the body), while FD:3002 means a request with the same key is still being processed (retry shortly and you will get the result with 200). When a new message is written the response is 201. attachmentIds accepts only attachment ids uploaded by the same token through POST v1/integration/attachments and not yet used; someone else’s attachment, or an expired one, returns 403. A ticket outside the token’s pool scope returns 404 even if it exists.
  • When a message is added through the integration to a closed ticket (completed or cancelled), the message is not written to that ticket; following the same rule as the e-mail flow, a new child ticket is opened and the message is written there — so that the closed ticket’s CompletedAt, its SL measurement and its already-reported periods do not change retroactively. In this case ticketId and trackingKey in the response point at the child ticket, continuedInNewTicket is true, and parentTicketId gives the ticket you addressed: update the id mapping in your integration from this response. Opening the child ticket depends on the pool being fully configured for intake; when it is not, the response is not a 500 but a 422 that says what needs to be done. If two requests sent with the same Idempotency-Key are processed at the same time, only one opens the child ticket; the other returns the winner’s result with 200, or, if the winner has not finished writing yet, a 409 with code FD:3002 saying “retry” — two child tickets are never opened.
  • New endpoint: POST v1/integration/attachments — for integration tokens to upload their own attachments. The id of an uploaded file is reserved (claimed) so that it can be used only by the token that uploaded it, only once, and only within 24 hours; the existing POST v1/attachments endpoint is unchanged and was not opened to integration tokens.
  • New endpoints: GET v1/integration/tickets/{id}/messages and GET v1/integration/tickets/by-tracking-key/{trackingKey}/messages — they read a ticket’s conversation (every message, yours and ours), merged across the parent chain even when the ticket was closed and continued in a child, ordered oldest to newest. Each record carries which ticket it belongs to via ticketId and trackingKey, and how it arrived via direction (Inbound/Outbound) and channel (Email/Api); who wrote an outgoing message (the agent’s identity) is not exposed — direction is already enough to say “this came from us”. Send the last seen id as afterId to poll incrementally. For older tickets opened through the integration API before Task 4, whose opening text was not yet recorded as a separate message row, the opening text is synthesised from the ticket’s own fields and returned with id: 0 (this synthetic record is not listed when afterId is sent, so it does not reappear). A ticket outside the token’s pool scope returns 404 even if it exists.
  • The ticket status create and update endpoints now accept an order field (POST / PUT v1/dashboard/ticket-statuses). The order can therefore be set from the individual record form as well; the bulk PUT v1/dashboard/ticket-statuses/order endpoint is unchanged. On update the field is optional: leave it out and the existing order is kept, not reset.

See GitHub Releases for older versions.