Route refund requests from Gorgias using an agent skill
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 recent unrouted tickets from Gorgias, passes each one to Claude (claude-haiku-4-5) to determine whether it's a refund request, and if so assigns it to your refund team and applies a tag via the Gorgias API. Because Claude reads the full message body, it catches refund requests phrased conversationally — "this isn't what I ordered and I want my money" — that keyword Rules would miss.
Step 1: Create the skill directory
mkdir -p .claude/skills/refund-routing/scriptsStep 2: Write the SKILL.md
Create .claude/skills/refund-routing/SKILL.md:
---
name: refund-routing
description: Identifies refund and return requests in unrouted Gorgias tickets using Claude, then assigns them to the refund team and applies a tag via the Gorgias API.
disable-model-invocation: true
allowed-tools: Bash(python *)
---
Route refund requests to authorized agents:
1. Run: `python $SKILL_DIR/scripts/route_refunds.py`
2. Review the output — it lists each ticket classified and whether it was routed
3. Spot-check a few in Gorgias to verify accuracyStep 3: Write the routing script
Create .claude/skills/refund-routing/scripts/route_refunds.py:
#!/usr/bin/env python3
"""
Gorgias Refund Request Routing
Fetches unrouted open tickets → classifies refund intent with Claude → routes to refund team.
"""
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)
REFUND_TEAM = "Refund Team" # Change to match your team name
REFUND_TAG = "refund-request"
client = Anthropic()
def get_unrouted_tickets(limit: int = 50) -> list:
"""Fetch open tickets that haven't been assigned to a team yet."""
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("assignee_team")
and not any(tag["name"] == REFUND_TAG for tag in (t.get("tags") or []))
]
def classify_refund_intent(subject: str, body: str) -> dict:
"""Use Claude to determine if a ticket is a refund/return request."""
prompt = (
"You are a support ticket classifier. Determine if this ticket is a "
"refund or return request.\n\n"
f"Subject: {subject}\n"
f"Body (first 500 chars): {body[:500]}\n\n"
"Reply with exactly one of:\n"
"- REFUND — customer wants money back, a refund, store credit, or reimbursement\n"
"- RETURN — customer wants to return, exchange, or replace an item\n"
"- NOT_REFUND — ticket is not about refunds or returns\n\n"
"Reply with only the label, nothing else."
)
message = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=20,
messages=[{"role": "user", "content": prompt}],
)
label = message.content[0].text.strip().upper()
is_refund = label in ("REFUND", "RETURN")
return {"is_refund": is_refund, "label": label}
def route_ticket(ticket_id: int, existing_tags: list) -> None:
"""Assign the ticket to the refund team and apply the refund tag."""
merged_tags = list({t["name"] for t in existing_tags} | {REFUND_TAG})
resp = requests.put(
f"{BASE_URL}/tickets/{ticket_id}",
auth=AUTH,
json={
"tags": [{"name": t} for t in merged_tags],
"assignee_team": {"name": REFUND_TEAM},
},
)
resp.raise_for_status()
def main() -> None:
print("Fetching unrouted open tickets...")
tickets = get_unrouted_tickets()
print(f"Found {len(tickets)} unrouted tickets to check\n")
if not tickets:
print("No unrouted tickets to process.")
return
routed = 0
for ticket in tickets:
subject = ticket.get("subject", "")
messages = ticket.get("messages", [])
body = messages[0].get("body_text", "") if messages else ""
result = classify_refund_intent(subject, body)
if result["is_refund"]:
route_ticket(ticket["id"], ticket.get("tags", []))
routed += 1
print(f" #{ticket['id']} {subject[:60]!r} → routed ({result['label']})")
else:
print(f" #{ticket['id']} {subject[:60]!r} → skipped (not a refund)")
print(f"\nRouted {routed} of {len(tickets)} tickets to {REFUND_TEAM}.")
if __name__ == "__main__":
main()Update the REFUND_TEAM variable at the top of the script to match the exact team name in your Gorgias account. You can find your team names under Settings → Teams.
Step 4: Run the skill
# Via Claude Code
/refund-routing
# Or run the script directly
python .claude/skills/refund-routing/scripts/route_refunds.pyThe script processes up to 50 unrouted open tickets per run. A typical run looks like:
Fetching unrouted open tickets...
Found 12 unrouted tickets to check
#14301 'I want a refund for order #7821' → routed (REFUND)
#14305 'Where is my package?' → skipped (not a refund)
#14308 'This arrived broken, I want my money' → routed (REFUND)
#14312 'Can I exchange this for a different size' → routed (RETURN)
#14315 'How do I update my address?' → skipped (not a refund)
Routed 3 of 5 tickets to Refund Team.Step 5: Schedule it
Option A: Cron
# crontab -e — run every 30 minutes during business hours
*/30 8-18 * * 1-5 cd /path/to/project && python .claude/skills/refund-routing/scripts/route_refunds.pyOption B: GitHub Actions
name: Refund Request Routing
on:
schedule:
- cron: '*/30 13-22 * * 1-5' # 8 AM–5 PM ET, weekdays
workflow_dispatch: {}
jobs:
route:
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/refund-routing/scripts/route_refunds.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 }}Refund tickets have higher urgency than general triage. If you're using a scheduled approach, run every 15–30 minutes during business hours rather than hourly. For real-time routing, combine this with Gorgias native Rules for common keywords and use the skill to catch the edge cases.
Cost
- Claude Haiku: ~$0.001 per ticket classified
- 500 tickets checked/month (not all will be refunds) ≈ $0.50 in API costs
- Only unrouted tickets are checked, so costs stay low even at scale
Need help implementing this?
We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.