opbox

The helpdesk.* family (4 verbs). Every verb enters through the one front door: it is capability-checked, permission-checked, scope-checked and audited before it runs (see Security & permissions). Each entry below lists its capability, risk class (which fixes the minimum autonomy level), the minimum caller permission, its availability tier, whether it is idempotent, and any outbound egress.

Createhelpdesk.cannedResponse.create

PropertyValue
Capabilityhelpdesk.write
Risk classWrite - autonomy L1
Min. permissionAdmin
AvailabilityOn demand (MCP tier 2)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)

A plain insert; created_by_id is the acting user’s FRONTEND users.id (mapped via the actor link) - REQUIRED (NOT NULL), so a SYSTEM/service actor with no linked user is refused (BadInput). No unique constraint on shortcut today, so no 23505 handling.

Input { title, content, category?, shortcut? } (fields ending in ? are optional)

Call POST /v/helpdesk.cannedResponse.create with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool helpdesk.cannedResponse.create.

Createhelpdesk.ticket.create

PropertyValue
Capabilityhelpdesk.write
Risk classWrite - autonomy L1
Min. permissionMember
AvailabilityOn demand (MCP tier 2)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)

Opens a ticket in the caller’s workspace. The kernel OWNS the per-workspace ticket_number counter: a tx-scoped advisory lock (salt 42, distinct from the audit-chain lock) serialises the COALESCE(MAX(ticket_number),0)+1 mint so two concurrent opens cannot collide (replacing the frontend withTicketNumberRetry). matterId/teamId/assigneeId are each fenced within the workspace (BadInput on miss).

Input { subject, description, priority?, category?, clientEmail, clientName, matterId?, teamId?, assigneeId? } (fields ending in ? are optional)

Call POST /v/helpdesk.ticket.create with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool helpdesk.ticket.create.

Replyhelpdesk.ticket.reply

PropertyValue
Capabilityhelpdesk.write
Risk classWrite - autonomy L1
Min. permissionMember
AvailabilityOn demand (MCP tier 2)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)

The single-writer collapse of the old prisma.$transaction: ticket_messages.create + support_tickets.update on ONE ctx.tx. (1) resolve

  • FOR UPDATE the ticket in the workspace (BadInput on miss); (2) REFUSE a CLOSED ticket (Conflict, the route’s old 422); (3) INSERT the AGENT message (author_id mapped from the acting user, nullable); (4) transition: status -> AWAITING_CLIENT unless internal, and first_reply_at stamped once (COALESCE).

Input { ticketId, content, isInternal?, authorName?, authorEmail? } (fields ending in ? are optional)

Call POST /v/helpdesk.ticket.reply with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool helpdesk.ticket.reply.

Updatehelpdesk.ticket.update

PropertyValue
Capabilityhelpdesk.write
Risk classWrite - autonomy L1
Min. permissionAdmin
AvailabilityOn demand (MCP tier 2)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)

A partial PATCH with present-vs-null discrimination on the four nullable FKs (an omitted field is left unchanged; an explicit JSON null un-sets it). resolved_at/closed_at are derived off the FOR UPDATE current-status read (transition INTO RESOLVED/CLOSED stamps the timestamp once). Non-null cross-refs (assignee/team/matter) are fenced within the workspace.

Input { ticketId, status?, priority?, category?|null, assigneeId?|null, teamId?|null, matterId?|null } (fields ending in ? are optional)

Call POST /v/helpdesk.ticket.update with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool helpdesk.ticket.update.