> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Take a customer issue from open to resolution across the Inbox tickets surface

> Walk one real customer issue end-to-end across the Inbox ticket surfaces — open via an inbound channel, link it to the contact and conversation, AI-draft the reply, escalate when the queue can't clear it, then measure resolution time and SLA attainment and hand the thread off.

# Take a customer issue from open to resolution

The Inbox tickets surface is where a customer issue becomes tracked work: an inbound channel (a phone call disposition, a chat, a help-center form, an operator's own note) opens a ticket, the operator links it to the contact and thread it came from, drafts a reply with AI assistance, resolves it, and the timestamps land in SLA and resolution reporting. This guide walks one ticket through every state it can hold — `open → pending → resolved / wont_fix`, plus the reopen path — over the same routes the dashboard buttons post to.

Everything shown here is tenant-owned configuration: your API key, your SLA policies, your knowledge bases, your escalation rules. Reading the queue works with a `inbox:read` scope; mutating tickets requires an owner, admin, or developer role on the workspace, matching the dashboard's role gate.

## 1. The open-to-resolution lifecycle

A ticket is a row with a subject, a status, a priority (`low` / `normal` / `high` / `urgent`), a type (`question` / `incident` / `problem` / `task`), an optional contact and conversation linkage, SLA clocks, and an audit-trailed history. Statuses are deliberately small — four values that mirror the helpdesk state machine the API enforces:

* **`open`** — work has started or is queued. A new ticket arrives `open`.
* **`pending`** — waiting on the customer or a third party; the operator answered and the ball is with the requester.
* **`resolved`** — the issue is answered; the resolution timestamps stamp the SLA clocks. Resolving the ticket closes the work item.
* **`wont_fix`** — deliberately closed without resolution (spam, duplicate, out of scope). Distinct from `resolved` so reports don't count dropped work as answered.

```text theme={null}
          +-------------+      +----------+      +-----------+
 create ->| open        |<---> | pending  | ---> | resolved  |
          +-------------+      +----------+      +-----------+
                |                                      ^
                +---------------------> +-----------+  |
                                        | wont_fix  |--+
                                        +-----------+
                              (reopen: POST /tickets/:id/reopen)
```

Two paths open a ticket: **automatic** (an inbound phone call ends with a `callback`, `escalate`, `unresolved`, or `customer_request` disposition, which fires ticket creation; an operator escalates a chat conversation into a ticket; or an unauthenticated visitor submits the help-center form) and **manual** (the operator clicks **Create ticket** on the Inbox → Tickets page, or a partner system POSTs to the internal endpoint). Both land on the same queue with the same shape. The [queue-operations guide](/guides/inbox-tickets-workflow) covers the filters, assignment modes, and SLA attachment mechanics in depth — this page covers the lifecycle path one ticket travels.

The audit trail is part of the lifecycle, not a sidecar. Every status transition, assignment change, and reopen writes a history row that `GET /api/v1/inbox/tickets/:id/history` returns timeline-ordered, and the same events drive the reopen-rate metric supervisors report against. The dedicated reopen action (`POST /tickets/:id/reopen`) exists so "reopened from resolved" is measurable separately from a bare status flip back to `open` — counting reopen rate against first resolution is how you know whether the answer actually held.

## 2. Worked example A: customer complaint to AI-drafted resolution

A retail customer, Priya, calls to complain that order #58-2214 never arrived. The agent who takes the call can't resolve it in one pass — it needs a warehouse lookup. This is the canonical lifecycle.

**Open.** The agent dispositions the call `escalate` on hangup, and the auto-create path opens a ticket with the call's `call_id` and the resolved contact already attached. The equivalent explicit create, when the agent files it by hand from the call screen:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/inbox/tickets/internal" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "subject": "Order #58-2214 never arrived",
    "summary": "Customer reports tracking stopped updating Friday.",
    "priority": "high",
    "type": "incident",
    "contact_id": "cnt_priya83",
    "call_id": "call_58f2214",
    "requester_email": "priya@example.com"
  }'
```

The response carries the new ticket id (`tkt_...`) in `open` status. If the inbound channel was a chat instead of a call, the same create accepts `conversation_id` instead of `call_id` — one ticket, whichever channel started it.

**Link.** Two tickets and four conversations about the same order shouldn't live as separate cases. If a duplicate ticket exists — say the customer also submitted the help-center form — merge it so one thread owns the resolution:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/inbox/tickets/tkt_dup93/merge" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "target_id": "tkt_main14" }'
```

A merge copies the duplicate's comments onto the target, stamps a merge edge, and closes the source. For non-duplicate relationships — this incident is one of several under the same root-cause `problem` ticket — attach a parent edge instead:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/inbox/tickets/tkt_main14/link" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "target_id": "tkt_rootcause08", "kind": "parent" }'
```

**Assign and acknowledge.** Point the ticket at the agent who owns the warehouse follow-up:

```bash theme={null}
curl -X PATCH "https://api.orbit.devotel.io/api/v1/inbox/tickets/tkt_main14" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "agent_user_id": "user_dana", "status": "open" }'
```

**Work it with AI assistance.** When the ticket ties back to a live conversation, the composer uses the AI draft endpoint over that conversation to propose the reply. The operator reviews the draft — accept, edit, or discard; the endpoint never sends on its own — and posts the accepted reply as a public comment on the ticket:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/inbox/conversations/cnv_priya_thread/ai-draft" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
```

The draft response carries the suggested body, a suggested action (`send` / `escalate` / `end`), and source citations when the knowledge base was consulted. Post the operator-approved reply onto the ticket:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/inbox/tickets/tkt_main14/comments" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "body": "Warehouse confirmed the parcel was missorted; replacement ships today, tracking attached. Sorry for the wait.",
    "visibility": "public",
    "author_kind": "operator"
  }'
```

**Resolve.** When the replacement ships, close the ticket. Resolving stamps the resolution timestamps the SLA clocks read:

```bash theme={null}
curl -X PATCH "https://api.orbit.devotel.io/api/v1/inbox/tickets/tkt_main14" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "status": "resolved" }'
```

If Priya writes back the next day — "the replacement arrived, thank you, but I was charged twice" — that is a reopen, not a new ticket: the original resolution didn't hold for billing. Reopen it explicitly so the reopen metric counts it:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/inbox/tickets/tkt_main14/reopen" \
  -H "X-API-Key: $ORBIT_API_KEY"
```

The ticket moves back to `open` with a reopen timestamp, and the billing question works the same lifecycle a second time.

## 3. The auto-answer loop: deflection with kb-auto-draft integration

Before a ticket ever needs an operator, high-confidence FAQs can answer themselves. Two autonomous stages run ahead of the operator queue, and both are opt-in tenant settings.

**Inbound deflection.** When an inbound chat or form submission matches a knowledge-base answer above the tenant's confidence threshold, the auto-deflect loop posts the answer immediately. The negative-sentiment guard keeps the conversation open when the caller is angry — an automatable FAQ with an unhappy customer still gets a human — and a monthly resolution cap and budget pre-check bound how much the loop can spend. The tenant flips the loop on and sets its thresholds through the ticket-deflection configuration; the published stats (`GET /api/v1/inbox/deflection/stats`) report how many inbound questions the loop resolved without queue time.

**Interactive deflect card.** On an existing ticket, the operator asks the deflection endpoints (`POST /api/v1/inbox/tickets/:id/deflect`) for a candidate answer based on the ticket's question, then accepts the draft as the public reply or escalates it when the candidate is wrong. Accepting stamps the ticket resolved via the same endpoints above; escalating keeps it in the queue with a note on why the automatable answer missed.

**Closing the loop with kb-auto-draft.** Deflection is only as good as the knowledge base behind it. The kb-auto-draft miner reads unresolved, repetitive question clusters — the ones where deflection attempted and lost — and drafts a new help article from the real conversation excerpts. The article lands in a dedicated pending-approval knowledge base for human review. Operators enable it and bound it per run:

```bash theme={null}
curl -X PUT "https://api.orbit.devotel.io/api/v1/insights/kb-auto-draft/config" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": true, "max_drafts_per_run": 5 }'
```

Then trigger the miner and review what it filed:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/insights/kb-auto-draft/run" \
  -H "X-API-Key: $ORBIT_API_KEY"
```

A reviewer approves the drafted documents through the knowledge-base approval endpoints (`POST /api/v1/knowledge-bases/:id/documents/:docId/approve`), and once published the same answer is available to both the deflection loop and the AI draft assistant on the next similar question. That is the loop: unanswered questions become articles, articles become automatic answers, and the operator queue only holds the work that genuinely needs a person.

## 4. Escalation: when a ticket moves into an escalation runbook

Stay in the tickets queue when the issue is a customer question a support answer resolves — even a long one. Move into an escalation runbook when the issue has crossed into incident territory: the inbox ticket is the *how a customer reported it*, but the fix belongs to an on-call responder with an SLA of its own. The triggers operators use:

* **An `incident`-type ticket with `urgent` priority that is breaching its response SLA**, and the breach notify action already reassigned it once — the queue is telling you the queue can't clear it.
* **A cluster of tickets linked to one `problem` parent** (the parent-edge shape from worked example A) — the pattern is a service degradation, not individual complaints. Promote the parent problem ticket to an incident and run the on-call policy on it; the linked incidents stay in the queue as customer-facing status reports.
* **A ticket that names a platform outage** (delivery failures, webhook backlog, provider degradation) — that is never an inbox-resolution shape.

The escalation mechanics are tenant-controlled: SLA breach actions (reassign, notify, Slack/Teams, webhook when a ticket's clocks breach) are configured per policy (`GET`/`POST /api/v1/inbox/sla/policies`), and the on-call runbook that receives the promoted incident is drafted by the [on-call roster and escalation policy runbook](/guides/oncall-escalation-runbook). Tickets do not migrate themselves — the escalation decision is the operator's or the breach action's, and the ticket keeps its own audit trail through it. The [SLA escalation policies guide](/inbox/sla-escalation-policies) covers the policy-side knobs; the [escalation-stack guide](/guides/oncall-escalation-stack) covers the responder side.

## 5. Worked example B: escalation for a breach-prone incident

A workspace reports that WhatsApp deliveries are stalling. Within an hour, six tickets arrive with the same symptom across different channels — help-center form, chat, two phone dispositions.

**Promote the pattern, link the cases.** The first ticket becomes the incident-of-record; the rest merge in as duplicates, then one `problem` ticket parents the incident for the root-cause investigation:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/inbox/tickets/tkt_dup59/merge" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "target_id": "tkt_whatsapp_stall" }'

curl -X POST "https://api.orbit.devotel.io/api/v1/inbox/tickets/tkt_whatsapp_stall/link" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "target_id": "tkt_delivery_rc_problem", "kind": "parent" }'

curl -X PATCH "https://api.orbit.devotel.io/api/v1/inbox/tickets/tkt_delivery_rc_problem" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "type": "problem", "priority": "urgent" }'
```

**Handoff to on-call.** The breach action on the workspace's incident SLA policy pings the delivery team's escalation policy, and the operator posts the context the responder needs as an internal note on the problem ticket:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/inbox/tickets/tkt_delivery_rc_problem/comments" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "body": "6 linked customer incidents; first failure at 09:14Z; delivery receipts stop arriving at the softswitch. On-call paged via breach action at 09:31Z.",
    "visibility": "internal",
    "author_kind": "operator"
  }'
```

Public, customer-facing updates continue to post to the linked incident tickets as the responder's updates come in, so every complainant gets the same status without six separate hand-ups:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/inbox/tickets/tkt_whatsapp_stall/comments" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "body": "Deliveries are flowing again as of 09:58Z; queues are draining. We will confirm full recovery on this thread.",
    "visibility": "public",
    "author_kind": "operator"
  }'

curl -X PATCH "https://api.orbit.devotel.io/api/v1/inbox/tickets/tkt_whatsapp_stall" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "status": "resolved" }'
```

The `problem` ticket stays open until the root cause lands; the customer-facing incidents close as the fix rolls out.

## 6. Measurement: SLA attainment and resolution-time reporting

Two overlapping measurements cover "did we resolve it in time": the conversation-side breach report the supervisors run on the Inbox SLA report page, and per-ticket resolution evidence you pull over the tickets API.

**Per-ticket attainment.** Query resolved tickets in a window and compare each ticket's opened and resolved timestamps against your policy targets. The list endpoint's filters take the burden:

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/inbox/tickets?status=resolved&opened_after=2026-09-01T00:00:00Z&opened_before=2026-10-01T00:00:00Z" \
  -H "X-API-Key: $ORBIT_API_KEY"
```

Each item carries the opened and resolved timestamps; the delta is the wall-clock resolution time, and the tenant's SLA policy (`GET /api/v1/inbox/sla/policies`) supplies the target it gets judged against. The bulk-export variant delivers the same rows as CSV or JSON with full comments when the review runs offline:

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/inbox/tickets/export?format=csv&status=resolved&opened_after=2026-09-01T00:00:00Z" \
  -H "X-API-Key: $ORBIT_API_KEY"
```

**Aggregate attainment and breaches.** The blended SLA view and the breach-events feed report workspace-level attainment — totals, breach counts, and the per-channel breakdown the supervisor triages on the dashboard:

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/inbox/sla/blended" \
  -H "X-API-Key: $ORBIT_API_KEY"

curl "https://api.orbit.devotel.io/api/v1/inbox/sla/breach-events" \
  -H "X-API-Key: $ORBIT_API_KEY"
```

For the conversation-side clocks (first response, next response, resolution), evaluate a page of conversations in one call rather than N polls:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/inbox/sla/timers" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "conversations": [
      { "conversation_id": "cnv_priya_thread", "channel_filter": "whatsapp" },
      { "conversation_id": "cnv_other", "channel_filter": "webchat" }
    ]
  }'
```

**Agent handle time.** For the productivity side of measurement, the dashboard posts focus-time deltas as the operator works a ticket, and the rollup reads them back:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/inbox/tickets/tkt_main14/handle-time" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "seconds": 540 }'
```

Handle time (active seconds an agent spent) is the coaching KPI; resolution time (wall clock) is the customer-experience KPI — the pair tells you whether resolution was fast because it was efficient or because it was skipped. The [SLA report reading guide](/guides/inbox-sla-report-reading) and the [first-reply-time reading guide](/guides/first-reply-time-reading) cover the dashboard-side report surfaces these endpoints back.

## 7. Export and handoff: the full thread leaves together

**Export.** The export endpoint above — `GET /api/v1/inbox/tickets/export` with `format=csv|json` and optional `include_comments=true` — downloads the same filtered queue the operator was looking at, with the full comment thread when requested. That is the handoff payload for a partner helpdesk migration, an audit request, or an offline review. The operation is recorded in the workspace audit log, and the role gates (admin, developer, owner) apply to the mutation-adjacent surfaces — everything on this surface is tenant-owned configuration, not a platform-side lock.

**Handoff to Devotel Orbit's own support.** When the ticket is about your Orbit workspace itself — an API 5xx, a deliverability anomaly, a billing question — the receiving side is the workspace's own support-history surface: **Support → Timeline** in the dashboard. Opening it from your own organization creates the same kind of tracked work this guide operates on, from the other side of the relationship. The [support-timeline guide](/guides/support-timeline) covers the rows, the search, and the export; the [support-timeline-vs-archive guide](/guides/support-timeline-surface) disambiguates it from your customers' archived conversation history. The handoff discipline that matters either way: quote the ticket reference from your queue when you open the support request so the threads stay linked — a support response is much faster when the receiving agent can see the exact case rather than a re-described one.

## Troubleshooting

* **422 on create or patch.** The schemas reject an enum miss (`status: "solved"`, `priority: "critical"`) or missing required fields. Match the four statuses, four priorities, and four types exactly.
* **Ticket not linked to the conversation.** Create it with `conversation_id` (or `call_id`) in the body; the auto-create path and the dashboard's create-from-thread button fill it automatically, the bare API call only if you pass it.
* **SLA clocks not counting.** Attach a policy (`POST /api/v1/inbox/sla/policies`) at open time — the platform defaults (4h first response, 24h resolution, business hours) apply only when no policy matches, and `business_hours_only` pauses clocks outside your posted hours. See the [SLA timers guide](/guides/inbox-sla-timers).
* **Re-open metric looks inflated.** Switch bare `PATCH status:"open"` back-openers to the explicit `POST /tickets/:id/reopen` — only the dedicated action stamps the reopen marker that analytics count.

## See also

* [Operate the Inbox Tickets queue](/guides/inbox-tickets-workflow) — queue operations, filters, assignment, SLA attachment mechanics.
* [Inbox setup](/guides/inbox-setup) — the queue configuration this lifecycle runs on.
* [Inbox SLA timers](/guides/inbox-sla-timers) and [SLA escalation policies](/inbox/sla-escalation-policies) — the clocks behind the measurement.
* [Inbox ticket automation](/guides/inbox-ticket-automation) — rule-based auto-actions on ticket events.
* [On-call roster and escalation runbook](/guides/oncall-escalation-runbook) — the runbook a breaching incident moves into.
* [Support timeline](/guides/support-timeline) — the receiving surface for handoff to Devotel Orbit support.
* [Ticket deflection configuration](/inbox/ai-deflection-budget) — the auto-answer loop's budget and thresholds.
