Ticket Schema
Felder
Ticket Schema
Schema for validating ticket entities
title
The ticket title
description
Detailed ticket description
descriptionHtml
Rich text HTML version of the ticket description
aiSummary
Best-effort AI summary of the ticket content. Two writers: (a) the worker queue for imported external-master mirrors (e.g. TANSS — their description holds the raw source mail), rendered on the detail page; (b) create_ticket_from_email, which mirrors the summary it just wrote into `description` so it stays recognisable as machine-written text — not rendered for source EMAIL (the description already IS the summary). Null when AI is disabled for the tenant. Distinct from dispatch.aiSummary, which is an internal metadata field of the auto-dispatch pipeline and is not rendered.
aiSummaryAttemptedAt
Set by the worker whenever an AI summary was attempted — even when the LLM returned nothing (AI off, content too short). Prevents the lazy on-open trigger from re-enqueuing the same ticket on every load.
aiSummaryFailReason
Machine-readable reason why the last AI summary attempt produced nothing: disabled_by_admin | no_account_no_fallback | account_unusable | too_short | truncated | model_empty | no_output | llm_error. Null on success. Exists so "why does this ticket show the raw mail?" is answerable from the record instead of only from the worker journal.
aiFeedSummary
AI summary of the ticket CONVERSATION (description + messages), as opposed to `aiSummary` which only condenses the initial description. Written on demand by POST /tickets/:id/summary and cached until new messages arrive. Additive, no backfill — absent means "never requested".
text
The summary itself. Null when the last attempt produced nothing.
computedAt
When the summary was produced — rendered as a freshness stamp.
messageCount
How many messages the summary covers. The cache is stale as soon as the ticket holds more than this, which is what triggers a recompute.
includedInternalNotes
Whether internal notes went into this summary. A cached summary built WITH internal notes must never be served to a caller lacking message_view_internal — it is recomputed for them instead.
failReason
Machine-readable reason the last attempt produced nothing: no_feed | too_short | model_empty | truncated | llm_error. Null on success.
status
Current ticket status
priority
Ticket priority level
LOWNORMALHIGHCRITICALpartyId
Associated party UUID
partyAddressId
Specific site/address of the associated party
contactPartyId
Associated contact person UUID
assignedToId
Assigned user UUID
dispatcherId
Human dispatcher responsible for this ticket (ADR 0260): the person who manually created/dispatched it or confirmed/overrode the AI dispatch. Null for pure AI auto-dispatch (no human dispatcher). Server-managed — drives the per-dispatcher Rückfragen-KPI (Dispo-Zielvereinbarung).
orgUnitId
Associated org unit UUID
handlingTier
Current ownership tier: CUSTOMER (customer IT) or MSP. null/absent means MSP (legacy).
CUSTOMERMSPnullhandedToCustomerAt
When the ticket was last handed to CUSTOMER (SLA pause start). Server-managed.
slaPausedMs
Accumulated milliseconds the SLA clock was paused while the ticket sat at CUSTOMER. Server-managed.
handoverPreviousAssigneeId
Assignee parked while the ticket sits at CUSTOMER; restored on hand-back. Server-managed.
contractId
Associated contract UUID
projectId
Associated project UUID
salesOrderId
Linked sales_order UUID (the order this ticket originated from)
salesOrderItemId
ID of the orderItem inside the linked sales_order (matches salesOrder.orderItems[].id)
salesOrderItemSource
Origin of the linked sales-order item — internal or weclapp
internalweclappnulldueAt
Due date in ISO format
resolvedAt
Resolution date in ISO format
closedAt
Closure date in ISO format
statusChangedAt
Timestamp of the last status change (denormalized for time limit queries)
reopenedAt
Timestamp of the last reopen event (system or manual). Cleared on next non-reopen status change.
reopenedBy
User UUID of the actor who reopened the ticket, or one of the sentinel strings "system:auto_close_undo", "system:inbound_mail_ai", "system:inbound_mail_closed" for system-triggered reopens.
reopenReason
Why the ticket was reopened — drives UI banners and audit.
MANUALINBOUND_MAIL_AIINBOUND_MAIL_CLOSEDAUTO_CLOSE_UNDOnullreopenedFromStatus
The status the ticket was in immediately before being reopened.
aiReopenDecision
Einstufung des Klassifikators, die zur Wiedereröffnung geführt hat.
thanksreopenunclearnullaiReopenConfidence
Konfidenz (0…1) der Einstufung zum Zeitpunkt der Wiedereröffnung.
aiReopenBasis
Woraus sich die Wiedereröffnung ergab: Schwellwert erreicht ("threshold"), Tenant-Fallback bei Unsicherheit ("fallback") oder von einem Menschen bestätigt ("confirmed").
thresholdfallbackconfirmednullaiReopenConfirmedBy
User-UUID der Person, die die unsichere KI-Einstufung bestätigt hat (nur bei aiReopenBasis "confirmed").
aiReopenSuggestion
Zustand der manuellen Bestätigung einer unsicheren KI-Einstufung.
status
PENDING blockiert Auto-Close; alle anderen Werte sind Endzustände. Genau ein PENDING pro Ticket (neuere Mail aktualisiert das bestehende).
PENDINGCONFIRMEDDISMISSEDEXPIREDdecision
thanksreopenunclearconfidence
reasoning
modelName
emailMessageId
messageId
suggestedAt
expiresAt
taskId
decidedAt
decidedBy
closeEmail
Marker: die Abschluss-Mail dieses Schließ-Zyklus wurde bereits versendet (server-managed).
unlinkedTicketRef
Im Betreff genannte, aber mangels Absenderbezug verworfene Ticketnummer (ADR 0604). Grundlage für den Zuordnungs-Vorschlag auf der Detailseite.
ticketId
`tickets._id` des genannten Vorgangs.
ticketNumber
Die Nummer, wie sie im Betreff stand (z. B. `ITF-2026-80295`).
reason
Warum der Vermerk entstand: `sender_unrelated` = Betreff nannte die Nummer, Absender ohne Bezug (ADR 0604); `same_sender_subject` = gleicher Absender und gleicher Betreff wie ein offenes Ticket, nur ein Duplikat-Hinweis (ADR 0827).
sender_unrelatedsame_sender_subjectnullsenderEmail
detectedAt
followupOrigin
Folge-Ticket wurde wie ein neues Mail-Ticket angelegt, weil das Projekt des Vorgängers nicht mehr lief (ADR 0966). Grundlage für den Hinweis auf der Detailseite.
closedTicketId
`tickets._id` des geschlossenen Vorgängers.
closedTicketNumber
reason
`project_not_running` = das Projekt des Vorgängers stand in einem Endstatus oder einem gewählten Status wie „In Abrechnung“.
project_not_runningprojectId
projectNumber
projectTitle
projectStatus
Roher Statusschlüssel des Projekts zum Zeitpunkt der Anlage.
decidedAt
tags
Ticket tags
billingOverride
ADR 0146 — explicit permission to invoice time bookings for this ticket while it is still open (e.g. customer ghosted). When `allowedWhileOpen` is true the billing-guard treats the ticket as billable. The reason is internal and propagates into the Leistungsnachweis snapshot, not onto the customer invoice.
standaloneBilling
ADR 0274 — mark this ticket for STANDALONE billing: its billable time is carved out of the customer collective Leistungsnachweis (and, if the ticket belongs to a project, out of the project LN too) and billed on its own via POST /tickets/:id/generate-performance-record. `enabledBy`/`enabledAt` and the `performanceRecord*`/`invoicedAt` back-references are set server-side only. Set via PUT /tickets/:id/standalone-billing (permission `ticket_billing_override`).
enabled
reason
enabledBy
enabledAt
performanceRecordId
performanceRecordNumber
invoicedAt
difficulty
Ticket difficulty (0–100). Feeds the XP award formula on close. Defaults to 50 at award time if null.
requiredSkills
Structured skill requirements for dispatch matching
skillId
UUID of the skill_definition
minimumLevel
Minimum proficiency level required. Null = any level.
BEGINNERINTERMEDIATEADVANCEDEXPERTnullsource
How this skill requirement was added. ASSET_DERIVED = auto-derived from the customer's assets (CMDB) via skill assetMatchRules.
MANUALAI_DETECTEDASSET_DERIVEDcommunicationLanguage
Explicitly chosen language for customer-facing ticket communication (ADR 0875). null = derived from contact/site/customer/tenant.
deennulldetectedLanguage
Automatically detected language of the ticket content (ISO 639-1, via fastText lid.176)
aaabaeafakamanarasavayazbabebgbhbibmbnbobrbscacechcocrcscucvcydadedvdzeeeleneoeseteufafffifjfofrfygagdglgngugvhahehihohrhthuhyhziaidieigiiikioisitiujajvkakgkikjkkklkmknkokrkskukvkwkylalblglilnloltlulvmgmhmimkmlmnmrmsmtmynanbndnengnlnnnonrnvnyocojomorospapiplpsptqurmrnrorurwsascsdsesgsiskslsmsnsosqsrssstsusvswtatetgthtitktltntotrtstttwtyugukuruzvevivowawoxhyiyozazhzunulldetectedLanguageConfidence
Detection confidence (0..1). Used with LANGUAGE_DETECTION_CONFIDENCE_THRESHOLD
detectedLanguageAt
Timestamp of the last language detection run
attachments
linkedEntities
convertedTo
Set when this ticket was converted to another entity (opportunity/complaint). Seit ADR 0548 wird das Quell-Ticket dabei soft-geloescht, WENN nichts daran haengt, das am Ziel nicht abbildbar ist. Haengt etwas dran, bleibt es erhalten, wird auf den Endstatus gesetzt und `retainedBecause` nennt den Grund. Written only by the conversion routes.
entityType
ticketopportunitycomplaintconvertedAt
convertedBy
retainedBecause
convertedFrom
Set when this ticket was created by converting another entity (opportunity/complaint). Written only by the conversion routes.
contractPositionRef
Reference to a contract position this ticket relates to
contactPartyName
Contact person name (used when contactPartyId is not available)
contactChannel
Channel through which the ticket was reported
PHONEEMAILPORTALON_SITEREMOTETEAMSOTHERticketType
Ticket type — references the `key` of a ticket_type entity (e.g. INCIDENT, REPAIR). Configurable per tenant; no longer a fixed enum.
extTicketId
External ticket reference ID
estimatedMinutes
Estimated work time in minutes
respondedAt
Timestamp of the first agent response (drives time_to_first_response targets, ADR 0117a)
inboundReplyAckedAt
Timestamp at which the latest inbound customer email was acknowledged as "handled, no reply needed". Clears the awaiting-reply badge when >= the last inbound email time; a newer inbound email re-arms the badge.
inboundReplyAckedBy
User who acknowledged the latest inbound customer email (see inboundReplyAckedAt).
commentsAckedAt
Timestamp at which new comments on this ticket were acknowledged as seen. Clears the comment badge (speech bubble) on the board card and in the ticket list; a newer comment re-arms it. Deliberately SEPARATE from inboundReplyAckedAt: the card shows two pills (envelope = unanswered customer mails, bubble = new comments) and each acknowledges only its own — a shared stamp would mean clicking one pill silently clears the other. Team-wide, not per user (same shared-inbox philosophy as the mail badge). Nullable, no backfill: a missing stamp counts every comment inside the 90-day window.
commentsAckedBy
User who acknowledged the new comments (see commentsAckedAt).
awaitingReplyPinnedAt
Timestamps of unanswered inbound customer emails that were carried over by a merge and that the standard computation can no longer see. The awaiting-reply badge derives from "inbound emails newer than the last OUTBOUND email of the record" — after a merge that comparison spans two formerly independent conversations, so a reply in one thread silently marks an open question in the other as answered. The merge therefore pins the affected timestamps here; the badge counts them in addition to the computed ones. Cleared by ack-inbound-reply. Server-managed (see merge.service.ts).
targets
Denormalized active service targets (ADR 0117a)
stageKind
time_to_first_responsetime_in_statustime_to_statustime_to_closestageStatus
stageLabel
bindingness
contractualinternalbreachedAt
completedAt
mergedIntoTicketId
If set, this ticket was merged into the referenced ticket
mergedIntoTicketNumber
Ticket number of the ticket this was merged into (denormalized for display)
linkedRiskIds
Linked risk IDs
incidentData
incidentType
DATA_BREACHUNAUTHORIZED_ACCESSMALWAREPHISHINGDOSINSIDER_THREATSUPPLY_CHAINOTHERseverity
CRITICALHIGHMEDIUMLOWdetectedAt
containedAt
resolvedAt
affectedDataCategories
affectedPersonCount
rootCause
lessonsLearned
timeline
linkedAssetIds
linkedRiskIds
breachNotifications
notificationType
DSGVO_ART33_AUTHORITYDSGVO_ART34_DATA_SUBJECTSNIS2_EARLY_WARNINGNIS2_NOTIFICATIONNIS2_FINAL_REPORTBSI_NOTIFICATIONstatus
DRAFTPENDING_REVIEWSUBMITTEDACKNOWLEDGEDsubmittedAt
dmsDocumentId
budget
Budget control: billing type, caps, consumed time, and lock state
billingType
Ticket billing model
TIME_AND_MATERIALFIXED_PRICENOT_BILLABLEfixedPriceAmount
Fixed price amount in EUR (when billingType is FIXED_PRICE)
capMinutes
Hard budget cap in minutes. When reached, ticket is locked.
capDisplayUnit
How the cap was originally entered (MINUTES, HOURS or EUR). capMinutes stays the canonical value; this field lets the UI round-trip the input without precision loss.
MINUTESHOURSEURcapDisplayValue
Original cap value as entered, matched to capDisplayUnit. Stored as a decimal string to avoid float rounding (e.g. "2.5" hours or "199.99" EUR).
capHourlyRateEur
Hourly rate (EUR/h) used to convert capDisplayValue to capMinutes when capDisplayUnit=EUR. Frozen at the moment the cap was set so later rate changes do not silently re-interpret the cap.
consumedMinutes
Denormalized sum of all time record durationMinutes for this ticket
locked
Whether the ticket is budget-locked (cap reached)
lockedAt
When the budget lock was engaged
lockedReason
Reason the budget was locked (auto or manual)
unlockRequestId
Pending approval request ID for budget unlock
lockStatusKey
ADR 0223 — resolved at create from the ticket type / tenant default: the status the ticket is auto-transitioned into when the cap is reached. Null = no auto status change (only budget.locked is set).
warnPercent
ADR 0549 — percentage of capMinutes at which the budget WARNING notification fires. Resolved at create from the ticket type / tenant default and stamped here, so changing the default later does not reinterpret running tickets. Null ⇒ 80 % (the pre-0544 hard-coded value).
lockPercent
ADR 0549 — percentage of capMinutes at which the ticket LOCKS. Null ⇒ 100 % (the pre-0544 behaviour). Below 100 keeps a buffer for customer consultation; above 100 tolerates a defined overrun.
capHistory
History of budget cap changes
dispatch
Auto-dispatch metadata set by the AI dispatch pipeline
dispatchDecisionId
dispatchedAt
dispatchStatus
PENDINGDISPATCHEDSUGGESTEDMANUAL_OVERRIDEFAILEDaiSummary
aiCategory
aiEstimatedMinutes
aiConfidence
source
Herkunftsmarker (EMAIL, M365_OUTLOOK, TANSS, …). Zwei Bedeutungen, die zusammenfallen: WOHER der Datensatz kam und — falls der Wert einem `externalMaster`-Provider entspricht — WER ihn führt. Ein Ticket mit `source: "TANSS"` ist ein read-only Spiegel (ADR 0377): Bearbeitungsversuche scheitern mit 423, gepflegt wird es ausschließlich vom Sync. Der Guard wirkt PRO DOKUMENT — in Codemeta angelegte Tickets tragen kein `source` und bleiben uneingeschränkt bearbeitbar, obwohl sie in derselben Collection liegen. Die Sperre lässt sich pro Ticket aufheben, ohne die Herkunft zu verfälschen: `externalOwnership: "LOCAL"` (ADR 0432) übernimmt den Vorgang nach Codemeta — `source` bleibt dann als Herkunftsangabe stehen, führt aber nicht mehr. Vorher wurde das Feld bereits ad hoc geschrieben (EMAIL/M365_OUTLOOK), war aber nie deklariert — bei `additionalProperties: false` konnte es über validierte Pfade nicht gesetzt werden.
suppressCreateConfirmation
Unterdrückt die Ticket-Eingangsbestätigung an den Kunden (`ticket_created`-Mail) für genau dieses Ticket, auch wenn eine Benachrichtigungsregel (approval_policy, ADR 0458) greifen würde. Wird von der Erstell-Maske gesetzt („Eingangsbestätigung senden" abgewählt), z. B. wenn der Kunde bereits am Telefon informiert wurde oder das Ticket rein intern angelegt wird. Nur beim Anlegen ausgewertet (`afterCreate`), danach reine Dokumentation. Fehlt das Feld (Altbestand), gilt `false` — kein Backfill nötig.
externalOwnership
Übernahme eines Fremdsystem-Spiegels in die lokale Verantwortung (ADR 0432). `"LOCAL"` hebt die read-only-Sperre aus ADR 0377 auf: das Ticket bleibt formal ein TANSS-Ticket (`source`/`sourceRef` bleiben erhalten, der Deeplink funktioniert weiter), wird aber ab jetzt in Codemeta zu Ende bearbeitet und vom TANSS-Sync nicht mehr angefasst. In TANSS trägt der Vorgang dann den Tag "CodemetaOS". Fehlt das Feld (Altbestand), gilt der Datensatz weiter als Spiegel — kein Backfill nötig. Nur über POST /tickets/:id/takeover setzbar (in `protectedFields`), niemals per generischem PATCH.
LOCALnullexternalOwnershipTakenAt
Zeitpunkt der Übernahme (ADR 0432).
externalOwnershipTakenBy
Interne User-Id, die die Übernahme ausgelöst hat (ADR 0432).
externalOwnershipTag
Name des Tags, der bei der Übernahme im Fremdsystem gesetzt wurde (ADR 0432). Belegt, dass die Gegenseite markiert ist — die Übernahme wird nur bei Erfolg vollzogen. `null` BEI GESETZTEM `externalOwnership: "LOCAL"` ist die eine dokumentierte Ausnahme (ADR 0457): der Vorgang existierte im Fremdsystem nicht mehr, konnte deshalb nicht markiert werden, und die Übernahme wurde nach ausdrücklicher Bestätigung trotzdem vollzogen. Bei fehlendem `externalOwnership` heißt `null` schlicht „nie übernommen".
sourceUrl
Direktverweis auf den Datensatz in der Oberfläche des führenden Fremdsystems (ADR 0362/0377). Wird beim Spiegeln einmal mitgeschrieben, weil der Host nur in `integration_configs` steht — Daten, die die Ticketseite nicht lädt und ein Techniker nicht zwingend lesen darf. Fehlt das Feld (ältere Spiegel), zeigt die Oberfläche schlicht keinen Link — kein Backfill nötig.
satisfaction
Customer satisfaction rating for this ticket, populated from survey responses
score
Raw score value (1-5 for CSAT, 0-10 for NPS)
scoreType
Type of satisfaction score
CSATNPSnormalizedScore
Score normalized to 0-100 scale for aggregation
comment
Optional free-text comment from the respondent
responseId
UUID of the linked survey_response
invitationId
UUID of the survey_invitation that produced this response
surveyId
UUID of the survey used
submittedAt
When the response was submitted
respondentContactId
Contact person UUID who submitted the response
skillImpact
Audit of skill level deltas applied to the assignee from this response
delta
-101source
Which mapping produced this impact
required_skillssurvey_questionappliedToUserId
suitableForIntern
Whether this ticket is appropriate for an intern/junior to work on (auto-set when difficulty < 30; manual override possible)
parentIncidentId
Parent major incident this ticket is grouped under
snoozedUntil
Ticket is snoozed until this timestamp; hidden from active queues
snoozedBy
User who snoozed the ticket
snoozedAt
When the ticket was snoozed
snoozeReason
Reason provided when snoozing
suggestedTemplateIds
Top-N task_template IDs suggested by the dispatcher (ADR 0138 Hook A). Server-managed via /suggest-templates endpoint.
appliedTemplateIds
task_template IDs that have been applied to this ticket (used by skill-aware dispatch in Hook B).
suggestionsComputedAt
When the template suggestion pipeline last ran for this ticket.
Keine Felder passen zum Filter.
Standard-Endpoints
Diese Resource folgt dem generischen CRUD-Vertrag der Plattform. Lesen Sie die Konventionen für Pagination, Idempotenz, Optimistic Locking und Audit. Die wichtigsten Endpoints:
GET /api/v1/tickets— Liste, paginiert + filterbarGET /api/v1/tickets/<id>— Einzelne EntityPOST /api/v1/tickets— AnlegenPATCH /api/v1/tickets/<id>— Teil-UpdateDELETE /api/v1/tickets/<id>— Soft-DeleteGET /api/v1/tickets/<id>/timeline— Audit + Aktivitäten