ProductsDocsGuidesPricing
Sign inGet Started

Intelligence APIs for developers.

Product

  • All APIs
  • Support Triage
  • Pricing

Developers

  • Getting started
  • Guides
  • API reference
  • Errors
  • Dashboard

Company

  • About
  • Security
  • Contact

Legal

  • Privacy Policy
  • Terms of Service
© Kovca. kovca.io
GuidesOperations

Automating Support Ticket Triage in Your Helpdesk Pipeline

Build a support ticket triage pipeline: webhook intake, category and priority classification, human-review routing, and safe fallbacks. Code in curl, TypeScript and Python.

Support Triage7 min readcURL · TypeScript · Python
billingtechnicalneeds_review

Triage is the least glamorous part of support, and also the place where most response-time problems start. A ticket that says "can't log in since this morning, whole team is blocked" sits in the general queue behind fourteen feature requests because nobody tagged it. Meanwhile a senior engineer spends the first hour of every day reading tickets just to decide where they go.

This guide covers how to automate that first routing decision without making things worse. The design applies to Zendesk, Freshdesk, Help Scout, Intercom, Front, or a homegrown inbox. For the classification step we use the Kovca Support Triage API, but the parts most teams get wrong are the parts around the classifier.

What "triage" should actually decide

Keep the automated decision small. A triage step should answer three questions:

  1. Which queue? Billing, technical, account, product, or other.
  2. How urgent? Is the customer blocked right now, or can they keep working?
  3. Does a human need to look before automation acts? Legal threats, security reports, and ambiguous messages should never be auto-handled.

Everything else (auto-replies, SLA timers, assignment to a person) is your business logic, and it should key off those three answers. Mixing "classify" and "act" in one opaque step is how teams end up auto-closing a GDPR request.

Rules first, then a classifier

Before adding any ML, write down the deterministic rules you already know:

  • Tickets from your status-page integration go straight to technical/high.
  • Anything from a known enterprise domain gets a priority floor.
  • Messages matching your own internal keywords (a product codename, say) go to a specific team.

Run these first. They're cheap, explainable, and they handle your most predictable traffic. The classifier handles the long tail of free-text messages that rules can't.

The pipeline

  1. 1
    Helpdesk webhook
    verify the signature, enqueue the ticket ID, return 200
  2. 2
    Intake worker
    strip quoted replies, signatures, auto-responder text
  3. 3
    Your rules
    status-page alerts, enterprise domains, codenames
  4. 4
    Kovca Support Triage
    category, priority, requires_human_review
    Review queuerequires_human_review: true
  5. 5
    Routing
    your queues, your priority floors
  6. 6
    Helpdesk API
    tags, custom fields, request_id

1Intake from a webhook

Most helpdesks can fire a webhook on ticket creation. Do almost nothing in the webhook handler: verify the signature, put the ticket ID on a queue, and return 200. Classification happens in a worker so slow calls or retries never make the helpdesk time out and re-send.

2Classify the message

The Support Triage endpoint takes a message (1 to 8,000 characters) and an optional subject (up to 200). It returns a category, a priority, and a review flag:

JSON
{
  "request_id": "req_8sJq2LwzVd0cYb1E",
  "category": "billing",
  "priority": "medium",
  "requires_human_review": false
}

The categories are fixed: billing, technical, account, product, other. Priority is low (a question or feedback), medium (a problem, but the customer can keep working), or high (blocked right now). The rules behind it are published, which matters when you have to explain routing to your support lead:

  • A likely security problem is always high.
  • requires_human_review is true when a legal/regulatory threat or a security problem is reasonably possible, or when the category or urgency judgment itself is unclear.

The underlying likelihoods aren't returned and aren't calibrated confidence scores, so don't build thresholds on top of them. Use the three output fields as routing hints, and let your own rules make the final call.

curl

cURL
curl https://kovca.io/api/v1/support/triage \
  -H "Authorization: Bearer $KOVCA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "subject": "Refund not received",
    "message": "I returned my order two weeks ago and still haven'\''t received my refund."
  }'

TypeScript (Node 18+)

TypeScript
type Triage = {
  request_id: string;
  category: "billing" | "technical" | "account" | "product" | "other";
  priority: "low" | "medium" | "high";
  requires_human_review: boolean;
};

export async function triage(subject: string | undefined, message: string): Promise<Triage | null> {
  const body: Record<string, string> = { message: message.trim().slice(0, 8000) };
  if (subject) body.subject = subject.slice(0, 200);

  for (let attempt = 0; attempt < 4; attempt++) {
    const res = await fetch("https://kovca.io/api/v1/support/triage", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.KOVCA_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(body),
    });
    if (res.ok) return res.json();

    const err = await res.json().catch(() => ({}));
    const code = err?.error?.code;
    const transient = [500, 503, 504].includes(res.status) || code === "RATE_LIMITED";
    if (!transient) break; // INVALID_REQUEST, UNAUTHORIZED, USAGE_LIMIT_EXCEEDED: don't retry
    const wait = Number(res.headers.get("Retry-After")) || 2 ** attempt;
    await new Promise((r) => setTimeout(r, wait * 1000));
  }
  return null; // caller falls back to the default queue
}

Python

Python
import os, time, requests

def triage(subject, message):
    body = {"message": message.strip()[:8000]}
    if subject:
        body["subject"] = subject[:200]
    for attempt in range(4):
        r = requests.post(
            "https://kovca.io/api/v1/support/triage",
            headers={"Authorization": f"Bearer {os.environ['KOVCA_API_KEY']}"},
            json=body, timeout=30,
        )
        if r.ok:
            return r.json()
        code = r.json().get("error", {}).get("code")
        if r.status_code in (500, 503, 504) or code == "RATE_LIMITED":
            time.sleep(int(r.headers.get("Retry-After", 2 ** attempt)))
            continue
        break
    return None

Two details worth copying. First, send only the customer's words. Strip quoted reply chains, signatures, and your own auto-responder text before calling. A ten-message thread where the customer's latest line is "thanks, all sorted!" shouldn't be classified as high because of a message from last week. Second, never put the key in a browser widget. Call from your backend (authentication docs).

3Route

Python
QUEUES = {"billing": "finance", "technical": "engineering-support",
          "account": "account-help", "product": "product-support", "other": "general"}

def route(ticket, result):
    if result is None:
        return {"queue": "general", "tags": ["triage_unavailable"]}       # fail open
    tags = [f"cat_{result['category']}", f"pri_{result['priority']}"]
    if result["requires_human_review"]:
        return {"queue": "triage-review", "tags": tags + ["needs_review"],
                "automation": False}                                     # no auto-replies
    priority = result["priority"]
    if ticket.get("enterprise") and priority == "low":
        priority = "medium"                                              # your floor, your rule
    return {"queue": QUEUES[result["category"]], "priority": priority,
            "tags": tags, "automation": True}

The important properties:

  • Fail open. If classification is unavailable, the ticket lands in the general queue exactly as it would have without automation. Triage should never be a reason a ticket goes missing.
  • Review means hands off. When requires_human_review is true, disable auto-replies and macros for that ticket. That flag exists so a legal threat or a vulnerability report doesn't get "Thanks! Have you tried clearing your cache?"
  • Your rules override. Customer tier, contract SLAs, and known incidents are things only you know.

4Write back and log

Write the category and priority back as helpdesk tags or custom fields, and store the request_id on the ticket. When an agent re-routes a ticket, log the original and corrected values. After a few weeks that log is your evaluation set. You'll see exactly which kinds of messages get mis-routed, and you can add rules for them.

Make corrections one click. Put a "wrong queue" button in the agent sidebar that records the corrected category next to the request_id. Agents will log a correction when it takes one click. They won't fill out a form.

Rolling it out safely

  1. Shadow mode, one to two weeks. Classify every ticket and write tags, but don't change routing. Compare against what agents actually did.
  2. Route, don't respond. Turn on queue assignment only. Keep auto-replies off.
  3. Expand slowly. Enable automation per category, starting with the one where shadow-mode agreement looked best for your traffic.

Measure first-response time on high tickets and the re-route rate. Those two numbers tell you whether triage is helping.

Volume and cost

One request per new ticket (not per reply) keeps usage predictable. Kovca's Free plan covers 1,000 successful requests a month at 10 per minute, which is plenty for a shadow-mode pilot at many small teams. Pay As You Go is $0.005 per successful request at 60 per minute, with a monthly spending cap (pricing). Invalid or failed requests aren't billed. Message text isn't stored by Kovca; only request metadata is recorded.

Try it on last week's tickets

Pull twenty real tickets, including the ones that got mis-routed, and run them through. Sign up free with no card, create an API key, and paste a ticket into the playground to see the result. Then read the full contract in the Support Triage docs.

On this page

  • What "triage" should actually decide
  • Rules first, then a classifier
  • The pipeline
  • Rolling it out safely
  • Volume and cost
  • Try it on last week's tickets

Support Triage

Classify inbound support tickets by category and priority, and flag the ones a person should review.

Try it in the playground

Keep going

Support TriageAPI reference
monitorcontact_carrierinvestigate
Shipment Intelligence6 min read

Catching Delivery Exceptions Before Your Customers Do

Turn raw carrier tracking events into routed delivery exceptions: late and stalled parcels, address problems, losses and disputes. A scheduled pipeline with curl, TypeScript and Python.

Read guide
hotwarmnurture
Lead Intelligence7 min read

Qualifying and Routing Inbound Leads in Minutes

Qualify inbound demo requests against your own criteria, route them to the right territory, and keep research and support requests out of sales queues. Code in curl, TypeScript and Python.

Read guide
same_variantdifferent_variantuncertain
Catalog Matching9 min read

How to Match Supplier Product Catalogs to Your Own SKUs

A practical guide to matching supplier feeds to your SKUs: blocking candidates, using GTINs safely, handling variants, and routing uncertain matches. Code in curl, TypeScript and Python.

Read guide