Auto-triage Gorgias tickets using an agent skill
low complexityCost: Usage-based
Prerequisites
Prerequisites
- Claude Code, Cursor, or another AI coding agent that supports skills
GORGIAS_EMAIL— your Gorgias account emailGORGIAS_API_KEY— your Gorgias REST API key (Settings → API → REST API)GORGIAS_DOMAIN— your store subdomain (e.g.,your-store)ANTHROPIC_API_KEY— your Anthropic API key
Overview
This approach uses an agent skill that fetches untagged open tickets from Gorgias, passes each one to Claude (claude-haiku-4-5) for topic classification, and writes the tag back via the Gorgias API. Because Claude reads the full ticket body and understands intent rather than matching keywords, it handles vague, multilingual, and conversational tickets that keyword Rules would miss.
Step 1: Create the skill directory
mkdir -p .claude/skills/ticket-triage/scriptsStep 2: Write the SKILL.md
Create .claude/skills/ticket-triage/SKILL.md:
---
name: ticket-triage
description: Classifies untagged open Gorgias tickets by reading subject and body, then applies a topic tag (billing, shipping, returns, technical, account, or feedback) via the Gorgias API.
disable-model-invocation: true
allowed-tools: Bash(python *)
---
Classify untagged support tickets:
1. Run: `python $SKILL_DIR/scripts/triage.py`
2. Review the output — it lists each ticket classified and the tag applied
3. Spot-check a few in Gorgias to verify accuracyStep 3: Write the triage script
Create .claude/skills/ticket-triage/scripts/triage.py:
#!/usr/bin/env python3
"""
Gorgias Ticket Triage
Fetches untagged open tickets → classifies with Claude → applies tags via API.
"""
import os
try:
import requests
from anthropic import Anthropic
except ImportError:
os.system("pip install requests anthropic -q")
import requests
from anthropic import Anthropic
GORGIAS_EMAIL = os.environ["GORGIAS_EMAIL"]
GORGIAS_KEY = os.environ["GORGIAS_API_KEY"]
GORGIAS_DOMAIN = os.environ["GORGIAS_DOMAIN"]
BASE_URL = f"https://{GORGIAS_DOMAIN}.gorgias.com/api"
AUTH = (GORGIAS_EMAIL, GORGIAS_KEY)
CATEGORIES = ["billing", "shipping", "returns", "technical", "account", "feedback", "other"]
client = Anthropic()
def get_untagged_tickets(limit: int = 50) -> list:
resp = requests.get(
f"{BASE_URL}/tickets",
auth=AUTH,
params={"status": "open", "limit": limit},
)
resp.raise_for_status()
tickets = resp.json().get("data", [])
return [t for t in tickets if not t.get("tags")]
def classify(subject: str, body: str) -> str:
prompt = (
f"Classify this customer support ticket into exactly one category.\n\n"
f"Categories: {', '.join(CATEGORIES)}\n\n"
f"Subject: {subject}\n"
f"Body (first 500 chars): {body[:500]}\n\n"
f"Reply with only the category name, nothing else."
)
message = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=20,
messages=[{"role": "user", "content": prompt}],
)
tag = message.content[0].text.strip().lower()
return tag if tag in CATEGORIES else "other"
def apply_tag(ticket_id: int, new_tag: str, existing: list) -> None:
# Merge to avoid replacing existing tags
merged = list({t["name"] for t in existing} | {new_tag})
resp = requests.put(
f"{BASE_URL}/tickets/{ticket_id}",
auth=AUTH,
json={"tags": [{"name": t} for t in merged]},
)
resp.raise_for_status()
def main() -> None:
print("Fetching untagged open tickets...")
tickets = get_untagged_tickets()
print(f"Found {len(tickets)} untagged tickets\n")
if not tickets:
print("Nothing to triage.")
return
for ticket in tickets:
subject = ticket.get("subject", "")
messages = ticket.get("messages", [])
body = messages[0].get("body_text", "") if messages else ""
tag = classify(subject, body)
apply_tag(ticket["id"], tag, ticket.get("tags", []))
print(f" #{ticket['id']} {subject[:60]!r} → {tag}")
print(f"\nTagged {len(tickets)} ticket(s).")
if __name__ == "__main__":
main()Step 4: Run the skill
# Via Claude Code
/ticket-triage
# Or run the script directly
python .claude/skills/ticket-triage/scripts/triage.pyThe script processes up to 50 untagged open tickets per run. At claude-haiku-4-5 pricing, classifying 50 tickets costs roughly $0.05 in API calls.
Step 5: Schedule it
Option A: Cron
# crontab -e — run every hour on weekdays
0 8-18 * * 1-5 cd /path/to/project && python .claude/skills/ticket-triage/scripts/triage.pyOption B: GitHub Actions
name: Ticket Triage
on:
schedule:
- cron: '0 13-22 * * 1-5' # 8 AM–5 PM ET, weekdays
workflow_dispatch: {}
jobs:
triage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install requests anthropic
- run: python .claude/skills/ticket-triage/scripts/triage.py
env:
GORGIAS_EMAIL: ${{ secrets.GORGIAS_EMAIL }}
GORGIAS_API_KEY: ${{ secrets.GORGIAS_API_KEY }}
GORGIAS_DOMAIN: ${{ secrets.GORGIAS_DOMAIN }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}When to use this approach
- You need semantic classification — Claude understands "my parcel hasn't arrived" as a shipping issue even without the word "shipping"
- You have multilingual customers and keyword Rules fail on non-English tickets
- You want to test category definitions before hardcoding them into Gorgias Rules
Cost
- Claude Haiku: ~$0.001 per ticket classified
- 1,000 tickets/month ≈ $1 in API costs
Need help implementing this?
We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.