Support routing

Route support messages to the right department, rate their urgency and escalate to a human when needed, with the Support Routing template.

Goal: each incoming support message lands in the right queue with an urgency level, and messages that need a person — an angry customer, a legal threat, data loss, a cancellation — skip the automated answer.

Create the decision

Templates → Support Routing → Create decision:

PartContent
Statemessage (string, required), plan (string)
departmentchoice — billing (invoices, charges, refunds, payment methods), technical (bugs, errors, integrations, outages), account (login, access, users, settings), sales (upgrades, new plans, quotes), other — minimum confidence 0.6
urgencyscore — low, normal, high, urgent
human_requiredprobability — does this need a human instead of an automated answer? Yes: angry customer, legal threat, data loss, cancellation. No: simple question answered by docs.
Policies1. human_required ≥ 0.7 → escalate
Fallbackescalate — for other and for a department below 60% confidence

Call it

curl -X POST https://api.dcision.io/v1/decisions/support-routing \
  -H "Authorization: Bearer $DCISION_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: ticket-55120" \
  -d '{ "state": { "message": "I was charged twice this month and need a refund today.", "plan": "pro" } }'
200 OK
{
  "decision_id": "dec_Rb6Tn2Wq8Lx4Mk9Pz1Cv",
  "execution_id": "exec_Ys3Fp7Kd1Qw9Bn5Lt2Gh",
  "schema": "support-routing",
  "version": 1,
  "result": { "department": "billing", "urgency": "high", "human_required": 0.82 },
  "confidence": { "department": 0.9375, "urgency": 0.61, "human_required": 0.64 },
  "scores": { "urgency": 3.11 },
  "action": "escalate",
  "action_reason": { "type": "policy", "rule": 0 },
  "metrics": { "latency_ms": 356, "engine": "jev", "model": "jev-1.13.0", "estimated_cost_usd": 0.00001386, "input_tokens": 330, "output_tokens": 33 }
}

human_required is 0.82, above the 0.7 threshold of rule 0, so the action is escalate. A how-to question ("How do I export invoices?") would typically come back with a low human_required and continue. A message the decision can't place — department below 60% confidence — takes the fallback action, escalate, with action_reason.type = "low_confidence".

Act on it

import os

import requests


def route_ticket(ticket):
    response = requests.post(
        "https://api.dcision.io/v1/decisions/support-routing",
        headers={
            "Authorization": f"Bearer {os.environ['DCISION_API_KEY']}",
            "Idempotency-Key": f"ticket-{ticket['id']}",
        },
        json={"state": {"message": ticket["message"], "plan": ticket["plan"]}},
        timeout=10,
    )
    if not response.ok:
        return helpdesk.assign(ticket, queue="triage")  # a person decides on errors

    decision = response.json()
    queue = decision["result"]["department"]
    if queue == "other":
        queue = "triage"
    priority = decision["result"]["urgency"]

    if decision["action"] == "escalate":
        return helpdesk.assign(ticket, queue=queue, priority=priority, agent="human")
    return helpdesk.auto_reply(ticket, queue=queue, priority=priority)

Tune it

  • Mirror your queues: rename or add departments as options and describe the boundaries ("refunds go to billing, even for technical failures").

  • Use the plan: add context such as "Enterprise customers have a 1-hour SLA" — the plan field is already in the state.

  • Escalate urgent tickets regardless of human_required — on the answer, or on the weighted level to also catch tickets that lean urgent:

    { "field": "urgency", "on": "score", "operator": "gte", "value": 3.5, "action": "escalate" }
  • Tune the routing floor: department ships with "minConfidence": 0.6. Raise it to send more unclear messages to the fallback action, lower it to automate more.

  • Answer more in the same call: add speculative questions such as refund_requested or has_reproducible_steps — see Speculative fan-out and its ticket-triage template.

  • Keep personal data out of logs if messages contain it: turn off storeInput in the decision's settings.

On this page