Topolo Docs Topolo Platform
Open workspace

List Support tickets

Read a scoped ticket page with exact requester visibility, response fields, pagination and SLA limits.

Outcome

Read a scoped Support ticket page and understand requester visibility, pagination limits and computed SLA displays.

GET /api/support/tickets

Before you start

Use the original authorized identity/organization and discover an accessible canonical Support workspace, bound separately. The published input exposes only optional workspaceId; an input id does not grant access. Keep it consistent with the verified binding.

The route requires actor.canReadTickets, derived from tickets:read or tickets:write. Ordinary organization operators additionally require tickets:write, a non-guest role and an original organization id; non-operators have requester-specific visibility. The global operator branch has a different broad scope: do not omit the binding and treat that as an ordinary organization-only query.

This is a ticket-read operation, but shared workspace resolution can initialize a local mirror, kind settings and default inbox before the ticket permission check. No actual Support query, initialization, subject read, send, provider, key operation or application test was performed for this source-derived guide.

Every response below is synthetic and unrun. Real subject/description, requester details, assignment fields and source references are private application data even though this technical guide is public.

Request and response

Action: app_topolo_support.tickets.list. Input is {} or a schema-valid optional workspaceId, with the canonical workspace bound separately. Validate the current input; do not add undeclared pagination fields.

The concrete success is HTTP200 {success:true,message:"Support tickets fetched successfully",data:{tickets:[...],nextCursor:<timestamp or null>}}. Use data.tickets for identifiers; this is not a bare array or the generic $.items resource locator.

The shared path authenticates the original actor, checks the organization erasure marker, resolves the platform workspace and can self-heal local scaffolding before checking tickets:read. An organization marked erased can return410; an auth, protection or database error is not a successful empty list. Initialization and ticket querying are separate operations.

Actor scoping is explicit: global operator adds no actor predicate; an ordinary organization operator adds requester_organization_id=actor.orgId; otherwise an actor with userId adds requester_user_id=actor.userId, falling back to organization only if there is no user id. A verified workspace adds workspace_id in every branch. Keep a canonical workspace bound rather than assuming all branches are confined to the active organization without it.

The HTTP handler supports limit/cursor, defaults to100 rows, caps at500 and asks for one lookahead row below500. However, current CLI/MCP input declares neither limit nor cursor. Do not send undeclared fields or claim the automation action can traverse every page. A returned nextCursor is not itself permission or a supported input field; its publication gap is with the owner.

For direct HTTP source behavior, ordering is updated_at DESC and a cursor adds updated_at<cursor. There is no id tie-breaker or snapshot isolation, so equal timestamps can be skipped between pages and concurrent edits can move rows. At limit500 the helper cap prevents a lookahead row, so nextCursor can be null despite additional history. Null is not a complete-export certificate.

The mapper returns the full ticket summary: subject and description; workspace/inbox ids and labels; source/reference; requester ids/name/email; app/service; status/priority/category; assignment ids/name/email; timestamps; publicReplyCount/internalNoteCount; and SLA objects. It does not include message bodies, event arrays or full notification rows. In this list path workspaceName remains the mapper default empty string, even though a verified workspace name may be known elsewhere.

Seven protected ticket fields are unprotected before mapping: subject,description,requester_name,requester_email,assigned_to_name,assigned_to_email,source_reference. Do not interpret a protection failure as absent tickets, bypass encryption, or invoke a canary/key change as read recovery. This source review does not certify actual key health or complete privacy redaction.

Public/internal message counts and lastMessageAt come from correlated message queries. For non-operators internalNoteCount is forced to0 and notifications:[] is added. lastMessageAt still reflects the SQL maximum across all message visibility, so masking the count is not a guarantee that every internal-activity signal is removed.

SLA firstResponse is immediately marked met when any public reply exists or status is resolved/closed; resolution is marked met when status is resolved/closed. Otherwise current time and priority-derived target hours determine healthy/warning/breached. This is a current display calculation, not evidence that historical response or resolution met its deadline; a late resolved ticket can still show met.

This handler does not change ticket status, assign an agent, execute routing/triage, emit a reply or dispatch notifications. It can be expensive: a bounded ticket page still joins workspace/inbox data and performs per-ticket correlated message counts and protected-field reads. No constant-time or cross-read snapshot guarantee is made.

Worked responses

One resolved ticket for an organization operator

Complete source-derived synthetic UNRUN HTTP200. Exactly one matching readable fictional ticket under the verified workspace. Resolved status causes both SLA displays to show met even with0public replies; no actual historical SLA performance is claimed. SQL returns fewer than the default lookahead threshold.

Input:

{}

Response:

{
  "success": true,
  "message": "Support tickets fetched successfully",
  "data": {
    "tickets": [
      {
        "id": "ticket_docs_fixture",
        "subject": "Fixture access question",
        "description": "Fictional request used only in documentation.",
        "workspaceId": "ws_support_docs_fixture",
        "workspaceName": "",
        "workspaceKind": "customer",
        "inboxId": "inbox_docs_fixture",
        "inboxName": "General",
        "source": "portal",
        "sourceReference": null,
        "requesterUserId": "usr_docs_fixture",
        "requesterOrgId": "org_docs_fixture",
        "requesterName": "Fixture requester",
        "requesterEmail": "requester@example.test",
        "appId": null,
        "serviceName": "Forms",
        "priority": "medium",
        "status": "resolved",
        "category": "general",
        "assignedToUserId": null,
        "assignedToName": null,
        "assignedToEmail": null,
        "createdAt": "2026-10-20T08:00:00.000Z",
        "updatedAt": "2026-10-21T08:00:00.000Z",
        "lastMessageAt": "2026-10-21T07:00:00.000Z",
        "publicReplyCount": 0,
        "internalNoteCount": 2,
        "sla": {
          "firstResponse": {
            "state": "met",
            "dueAt": null,
            "label": "First response met"
          },
          "resolution": {
            "state": "met",
            "dueAt": null,
            "label": "Resolution met"
          }
        }
      }
    ],
    "nextCursor": null
  }
}

Confirm actual original scope and source semantics. Fictional subject/identity data authorizes no real ticket operation.

The same requester’s non-operator projection

Complete source-derived synthetic UNRUN HTTP200. Fictional actor has tickets:read and matches requesterUserId but is not an organization operator. The count is masked to0 and notifications[] added; lastMessageAt is retained from the all-visibility SQL maximum. No actual private record was read.

Input:

{}

Response:

{
  "success": true,
  "message": "Support tickets fetched successfully",
  "data": {
    "tickets": [
      {
        "id": "ticket_docs_fixture",
        "subject": "Fixture access question",
        "description": "Fictional request used only in documentation.",
        "workspaceId": "ws_support_docs_fixture",
        "workspaceName": "",
        "workspaceKind": "customer",
        "inboxId": "inbox_docs_fixture",
        "inboxName": "General",
        "source": "portal",
        "sourceReference": null,
        "requesterUserId": "usr_docs_fixture",
        "requesterOrgId": "org_docs_fixture",
        "requesterName": "Fixture requester",
        "requesterEmail": "requester@example.test",
        "appId": null,
        "serviceName": "Forms",
        "priority": "medium",
        "status": "resolved",
        "category": "general",
        "assignedToUserId": null,
        "assignedToName": null,
        "assignedToEmail": null,
        "createdAt": "2026-10-20T08:00:00.000Z",
        "updatedAt": "2026-10-21T08:00:00.000Z",
        "lastMessageAt": "2026-10-21T07:00:00.000Z",
        "publicReplyCount": 0,
        "internalNoteCount": 0,
        "sla": {
          "firstResponse": {
            "state": "met",
            "dueAt": null,
            "label": "First response met"
          },
          "resolution": {
            "state": "met",
            "dueAt": null,
            "label": "Resolution met"
          }
        },
        "notifications": []
      }
    ],
    "nextCursor": null
  }
}

Confirm actual original scope and source semantics. Fictional subject/identity data authorizes no real ticket operation.

Successful empty ticket page

Complete source-derived synthetic UNRUN HTTP200. The original actor, non-erased organization, verified workspace and read grant pass; successful scoped query returns no rows. Not an auth, database or protection failure.

Input:

{}

Response:

{
  "success": true,
  "message": "Support tickets fetched successfully",
  "data": {
    "tickets": [],
    "nextCursor": null
  }
}

Confirm actual original scope and source semantics. Fictional subject/identity data authorizes no real ticket operation.

Ticket permission refused

Complete source-derived synthetic UNRUN HTTP403. Concrete handler permission check refuses after the shared path. Local workspace scaffolding may already have been initialized; no actual denial or initialization was induced.

Input:

{}

Response:

{
  "success": false,
  "error": {
    "message": "Insufficient permissions"
  }
}

Confirm actual original scope and source semantics. Fictional subject/identity data authorizes no real ticket operation.

Verify and recover

  1. Compare every returned workspaceId and intended requester/organization scope with the approved target. Keep original request context; do not infer scope only from a human label or empty workspaceName.

  2. Treat the default page, nextCursor and timestamp ordering as partial/current reads. The automation input cannot yet pass pagination fields; do not claim complete ticket coverage or export all data through guessed HTTP parameters.

  3. Interpret status, masked counts and computed SLA displays separately from external business outcomes and historical deadline performance. Keep real returned text and identities private.

  4. Retain actual sanitized status/request evidence. No reply, routing, assignment, key change, workspace deletion or new ticket is authorized as recovery or a documentation test.

No ticket-row mutation is performed by the list query. Shared initialization effects are separate, so no blind scaffold cleanup or business rollback is appropriate. Raw concrete refusals carry error.message; a CLI/SDK may classify HTTP403 as permission_denied, which is not a raw route code.

Change log

  • 2026-10-21: Added exact wrapped ticket projection, actor/workspace and initialization boundaries, automation pagination gap, timestamp lookahead/tie limits, protection and SLA interpretation, plus complete synthetic responses. Source review only; examples remain unrun.