Download
Access the curated collection of n8n workflow templates. Simply download the JSON files, import them into your instance, and activate your automations.
Support Ticket Data Extraction - Analysis & Routing Automation with Clocked.ai
Support Ticket Structured Data Extraction with Clocked.ai
Introduction
Organizations generate enormous amounts of unstructured data every day: IT support tickets, customer emails, invoices, incident reports, meeting notes, insurance documents, and operational logs.
The challenge is not simply extracting text. It is understanding the information, identifying what matters, applying business rules, and deciding what should happen next.
In this article, you will learn on how to build an AI-powered unstructured data analysis pipeline using Clocked by Rezolve.ai and n8n.
Note - This blog post consists of the code snippets in Python. However, the design and development of the workflows are accomplished using n8n. You may refer and incorporate the code snippet or extend the workflow based on your comfort level.
IT support ticket analyzer is an example utilized to show how the same architecture can be adapted to other enterprise workflows.
1. The problem: unstructured data is difficult to operationalize
Consider this support ticket:
Ticket: INC-59301
Requester: Dana Whitfield
Email: dana@example.com
Device: LAPTOP-204
The VPN disconnects every ten minutes. Outlook also crashes on launch.
Error: Authentication token expired.
Restarting the VPN client did not help. Reinstalling Outlook did not help.
Assigned engineer: Priya Raman.
Please call tomorrow afternoon.Update, 14:20: The affected device is LAPTOP-207, not LAPTOP-204. Priya remains assigned. No restoration estimate has been provided.
A conventional workflow might store this as a block of text. A useful AI workflow should identify the ticket ID, requester, email, affected applications, error message, troubleshooting attempts, corrected device ID, and callback preference.
It could then answer additional questions:
- Is this primarily a VPN, identity, or application issue?
- Should the ticket be escalated?
- Which support team should handle it?
- Is the available information sufficient to recommend a next step?
- Which fields require human verification?
These are related but different tasks. Extracting a device ID is not the same as deciding where to route a ticket.
2. The architecture
Our pipeline separates orchestration, extraction, decision-making, and execution.
The components have distinct responsibilities:
- n8n: Orchestrates triggers, API calls, branching, retries, and integrations.
- Clocked: Extracts structured information from supported source content.
- Python or another validation layer: Checks required fields, conflicting values, and business constraints.
- System One: Evaluates explicitly defined questions and returns structured decisions, scores, or probabilities.
- Business systems: Execute approved actions and retain the resulting records.
Clocked capabilities should be selected based on the task. In particular, do not assume every model accepts images or generates arbitrary JSON; check the supported inputs and outputs of the selected model.
3. Step one: ingest and prepare the source
The first stage collects data from a webhook, ticketing system, email, document upload, or scheduled job.
In our example, the workflow starts manually in n8n. A production workflow could start when a new ticket is created.
The initial preparation stage should:
- Assign a stable correlation ID to the processing job.
- Preserve the original ticket content.
- Identify the source and timestamp.
- Check whether the payload is empty or malformed.
- Remove or protect sensitive data when required by organizational policy.
The original source should remain available for traceability. Do not overwrite it with a cleaned-up or AI-generated version.
4. Step two: extract structured information with Clocked
Clocked is the extraction stage: Define the fields the application needs.
For example:
import json
import os
import sys
import uuid
from typing import Any
import requests
API_URL = "https://clocked.rezolve.ai/clocked/extract"
API_KEY = os.getenv("CLOCKED_API_KEY")
if not API_KEY:
raise RuntimeError(
"Missing CLOCKED_API_KEY environment variable."
)
TICKET_TEXT = """
Ticket: INC-59301
Requester: Dana Whitfield
Email: dana@example.com
Device: LAPTOP-204
The VPN disconnects every ten minutes. Outlook also crashes on launch.
The error message is: Authentication token expired.
Restarting the VPN client did not help.
Reinstalling Outlook did not help.
Assigned engineer: Priya Raman
Please call tomorrow afternoon.
Update, 14:20: Dana confirms the affected device is LAPTOP-207,
not LAPTOP-204. Priya remains assigned.
No restoration estimate has been provided.
"""
EXTRACTION_FIELDS = {
"ticket_id": "The support ticket identifier.",
"requester": "The requester's full name.",
"email": "The requester's email address.",
"device": {
"description": (
"The currently affected device identifier. "
"Prefer explicit corrections in later updates."
)
},
"assigned_engineer": "The assigned engineer's full name.",
"error_message": "The exact error message reported.",
"affected_applications": {
"description": (
"All applications or clients reported as malfunctioning, "
"including the VPN client and Outlook when supported "
"by the source."
),
"list": True,
"max_items": 10,
},
"attempted_fixes": {
"description": (
"All troubleshooting actions already attempted, "
"preserving their meaning."
),
"list": True,
"max_items": 10,
},
"callback_time": (
"The requested callback time exactly as written. "
"Do not infer a calendar date."
),
"restoration_estimate": (
"An explicit date or time by which service will be restored. "
"Return null if no estimate is provided."
),
}
def extract_support_ticket(ticket_text: str) -> dict[str, Any]:
"""Send a support ticket to Clocked.ai and return extracted fields."""
if not ticket_text.strip():
raise ValueError("Ticket text cannot be empty.")
payload = {
"text": ticket_text,
"instructions": (
"Extract only facts supported by the ticket. "
"Prefer explicit corrections in later updates over earlier "
"values. Preserve source wording where appropriate. "
"Return null for missing scalar facts and do not guess. "
"Include all explicitly reported affected applications "
"and attempted troubleshooting actions."
),
"fields": EXTRACTION_FIELDS,
}
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
"Idempotency-Key": str(uuid.uuid4()),
}
response = requests.post(
API_URL,
headers=headers,
json=payload,
timeout=60,
)
response.raise_for_status()
result = response.json()
# Clocked Extract returns each extracted field as an object
# containing a "value" property.
raw_fields = result.get("fields")
if not isinstance(raw_fields, dict):
raise ValueError(
"Unexpected API response: missing 'fields' object. "
f"Response: {json.dumps(result, ensure_ascii=False)}"
)
extracted = {
name: raw_fields.get(name, {}).get("value")
if isinstance(raw_fields.get(name), dict)
else None
for name in EXTRACTION_FIELDS
}
# Keep confidence and evidence offsets for auditing.
metadata = {
name: {
key: raw_fields[name].get(key)
for key in ("confidence", "start", "end")
if key in raw_fields[name]
}
for name in EXTRACTION_FIELDS
if isinstance(raw_fields.get(name), dict)
}
return {
"extracted_fields": extracted,
"field_metadata": metadata,
"model": result.get("model"),
"timing": result.get("timing"),
}
def main() -> None:
ticket_text = (
sys.stdin.read()
if not sys.stdin.isatty()
else TICKET_TEXT
)
try:
output = extract_support_ticket(ticket_text)
print(json.dumps(output, indent=2, ensure_ascii=False))
with open(
"extracted_ticket.json",
"w",
encoding="utf-8",
) as file:
json.dump(
output,
file,
indent=2,
ensure_ascii=False,
)
print("\nSaved results to extracted_ticket.json")
except requests.HTTPError as exc:
print(
f"Clocked.ai HTTP error: {exc}\n"
f"Response: {exc.response.text if exc.response else ''}",
file=sys.stderr,
)
sys.exit(1)
except requests.RequestException as exc:
print(f"API request failed: {exc}", file=sys.stderr)
sys.exit(1)
except (ValueError, RuntimeError) as exc:
print(f"Extraction failed: {exc}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
The extraction instructions matter just as much as the schema:
Extract only information supported by the source. Preserve exact identifiers, error messages, and dates where possible. Apply explicit corrections in later updates. Do not invent missing values, infer an unprovided restoration estimate, or turn a vague callback preference into an exact time.
Example output
The target structure for this ticket would look like:
{
"requester": "Dana Whitfield",
"ticket_id": "INC-59301",
"email": "dana@example.com",
"device": "LAPTOP-207",
"assigned_engineer": "Priya Raman",
"error_message": "Authentication token expired.",
"affected_applications": [
"VPN client",
"Outlook"
],
"attempted_fixes": [
"Restarting the VPN client",
"Reinstalling Outlook"
],
"callback_time": "tomorrow afternoon",
"restoration_estimate": null
}
This is an illustrative target output, not a claim that every API invocation will return precisely these values.
Notice the device ID: LAPTOP-207 supersedes LAPTOP-204 because the latest update explicitly corrects the earlier value.
Likewise, restoration_estimate is null because the source provides no estimate. The callback preference remains “tomorrow afternoon” rather than being converted into an invented appointment time.
Important: Configure the actual output schema to match the API's supported field definitions and output format. If the service represents missing values differently, normalize them in your application.
5. Step three: validate before making decisions
Extraction output is not automatically trustworthy business data.
Before sending it to System One, validate it in Python or your preferred application layer.
def validate_ticket(ticket: dict) -> list[str]:
errors = []
required_fields = [
"ticket_id",
"requester",
"device",
"assigned_engineer",
]
for field in required_fields:
if not ticket.get(field):
errors.append(f"Missing required field: {field}")
if ticket.get("restoration_estimate") is None:
# A missing estimate is allowed.
pass
if not ticket.get("affected_applications"):
errors.append("No affected applications identified")
return errors
This is a starting point, not a complete production validator. Add field types, email validation, allowed identifier formats, source-specific requirements, and checks for contradictory updates.
For example, if two updates disagree about the affected device and neither clearly supersedes the other, send the ticket for review rather than choosing arbitrarily.
6. Step four: Use Decide (System One) model for structured decisions
Once the data is structured and validated, System One can help evaluate questions with explicit answer choices, ordered scores, or conditions.
Example A: ticket classification
Define the possible categories:
{
"question": "What is the primary support category?",
"options": [
"Network / VPN",
"Identity / Authentication",
"Email / Outlook",
"Hardware",
"Other"
]
}
The ticket mentions both a VPN disconnection and an expired authentication token, as well as Outlook crashes. Classification should consider the full context and the organization's definition of a primary category.
The workflow might classify the ticket as an identity/authentication issue, but that is a decision to evaluate not a guaranteed result.
Example B: escalation assessment
Define a structured urgency scale:
{
"question": "What escalation level is appropriate?",
"options": [
"Routine",
"Priority",
"Urgent",
"Critical"
]
}
The organization should define the escalation criteria separately. For example, a single-user issue with a workaround may have different priority from a widespread outage affecting business-critical operations.
The model's output should be combined with explicit rules such as business impact, affected-user count, service-level agreements, and incident policies.
Example C: determine whether human review is needed
A condition question could assess whether the ticket contains conflicting or insufficient information.
The application can combine that result with deterministic checks:
def needs_human_review(
validation_errors: list[str],
confidence: float,
threshold: float = 0.85,
) -> bool:
return bool(validation_errors) or confidence < threshold
Here, confidence is an illustrative application input. Connect it to the actual probability or confidence output documented for the selected model; do not assume every API response uses the same field or scale.
A probability is a signal, not a guarantee of correctness. Calibrate thresholds against labeled tickets from your own organization.
7. Step five: orchestrate the entire process in n8n
A simplistic example workflow uses four visible nodes:
- Manual Trigger - Starts the workflow during development.
- Support Ticket Input with Extractor Rules - Defines the ticket text and requested fields in JSON.
- Unique ID Generator - Creates an identifier for the processing request. It's a mandatory one for the Clocked API.
- Rezolve.ai Ticket Analyzer - Sends the extraction request.
For production usages, extend the workflow with validation, decision evaluation, and routing nodes.
A practical sequence is:
- Trigger on a new ticket.
- Normalize the input and preserve the original source.
- Generate a correlation ID.
- Call Clocked API to extract the requested fields.
- Parse and validate the returned data.
- Call Clocked Decide (System One) API for classification or other defined decisions.
- Evaluate business rules and confidence thresholds.
- Route uncertain or conflicting results to a human reviewer.
- Update the ticketing system only when the applicable checks pass.
- Record the outcome for auditing and evaluation.
Connecting to Clocked
Use the Clocked API Documentation to learn about the Clocked API usages. The following request illustrates the extraction pattern; verify the endpoint, authentication, and payload schema against your enabled service before deployment.
import os
import uuid
import requests
CLOCKED_API_KEY = os.environ["CLOCKED_API_KEY"]
def extract_ticket(ticket_text: str, fields: dict) -> dict:
response = requests.post(
"https://clocked.rezolve.ai/clocked/extract",
headers={
"Authorization": f"Bearer {CLOCKED_API_KEY}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"text": ticket_text,
"fields": fields,
},
timeout=120,
)
response.raise_for_status()
return response.json()
This example assumes a text-based payload. If your configured extraction API requires an images array or another source representation, use its documented schema instead. Refer Clocked Extract
In production, reuse the same idempotency key when retrying the same logical request if the API supports that behavior. Add bounded retries, appropriate error handling, request logging without sensitive ticket contents, and monitoring.
Connecting to System One
Use the official Clocked Decide System One to configure the selected model, request format, authentication, and decision primitives.
Keep the integration modular:
import os
import requests
from typing import Any
CLOCKED_DECIDE_URL = "https://clocked.rezolve.ai/decide"
def evaluate_with_system_one(ticket: dict[str, Any]) -> dict[str, Any]:
"""
Evaluate a structured support ticket using Clocked Decide.
NOTE:
Confirm the exact request schema and authentication requirements
against your Clocked Decide API documentation.
"""
api_key = os.environ.get("CLOCKED_API_KEY")
if not api_key:
raise RuntimeError("CLOCKED_API_KEY environment variable is missing")
payload = {
"state": ticket,
"questions": {
"ticket_category": {
"type": "choice",
"instructions": "Choose the primary support category.",
"criteria": {
"network_vpn": "VPN connectivity or network problems",
"identity_authentication": "Authentication or expired-token problems",
"email_application": "Outlook or email application problems",
"hardware": "Physical device or hardware problems",
"other": "Issues not covered by the other categories",
},
},
"urgency": {
"type": "score",
"instructions": (
"Assess ticket urgency using the available evidence "
"and the defined levels."
),
"criteria": [
"Routine",
"Priority",
"Urgent",
"Critical",
],
},
"requires_human_review": {
"type": "noul",
"instructions": (
"Is human review required before taking action? "
"Consider conflicting information, missing critical "
"details, and uncertainty."
),
},
},
}
response = requests.post(
CLOCKED_DECIDE_URL,
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
"Accept": "application/json",
},
json=payload,
timeout=30,
)
response.raise_for_status()
return response.json()
if __name__ == "__main__":
ticket = {
"ticket_id": "INC-59301",
"requester": "Dana Whitfield",
"device": "LAPTOP-207",
"assigned_engineer": "Priya Raman",
"error_message": "Authentication token expired.",
"affected_applications": ["VPN client", "Outlook"],
"attempted_fixes": [
"Restarting the VPN client",
"Reinstalling Outlook",
],
"callback_time": "tomorrow afternoon",
"restoration_estimate": None,
}
result = evaluate_with_system_one(ticket)
print(result)
Keeping this boundary explicit makes it easier to test extraction and decision-making independently.
8. Beyond IT support: Enterprise use cases
The same architecture applies to many business workflows.
| Use case | Extract structured data | Evaluate decisions |
|---|---|---|
| Invoice processing | Supplier, invoice number, total, due date | Duplicate risk, exception routing, approval path |
| Customer support | Customer ID, product, issue, requested resolution | Category, priority, escalation |
| HR operations | Employee request, leave dates, policy references | Request type, missing evidence, routing |
| Compliance review | Entities, dates, obligations, source references | Risk category, review requirement, policy checks |
| Incident management | Affected services, timestamps, error messages | Severity, owning team, escalation |
| Procurement | Supplier, line items, contract references | Policy exceptions, approval requirements |
These are potential applications, not guarantees of model accuracy. Each requires its own schema, decision definitions, access controls, and evaluation dataset.
For high-impact processes, use the model to support decisions rather than silently authorize payments, change access permissions, or make consequential employment decisions.
9. Security, privacy, and governance
Enterprise adoption requires more than a successful API response.
Protect sensitive data
- Store API keys in a secrets manager or protected environment variables.
- Send only the data required for the task.
- Restrict access to tickets, invoices, employee records, and model outputs.
- Establish retention rules for prompts, request logs, and extracted values.
- Confirm the service's data handling, retention, and contractual terms before sending sensitive information.
Preserve provenance
- Keep the original source or a secure reference to it.
- Record which extraction schema and model configuration were used.
- Link extracted fields to the source document or passage where practical.
- Distinguish directly observed values from model-derived classifications.
Keep consequential actions controlled
- Require explicit policy checks before updating critical systems.
- Use human review for ambiguous, conflicting, or low-confidence results.
- Maintain an audit trail for approvals and automated actions.
- Treat external document text as data, not as instructions that can override system controls.
10. Measure the quality of the pipeline
Do not evaluate the workflow solely on whether it returned valid JSON.
Build a labeled test set containing ordinary examples, missing fields, contradictory updates, malformed inputs, and difficult edge cases.
Measure:
- Field-level accuracy: How often extracted values match the labeled source.
- Correction accuracy: Whether later, explicit updates override earlier values correctly.
- Missing-value behavior: Whether absent information stays absent.
- Classification quality: Precision, recall, and confusion matrices for categories.
- Routing quality: Whether the selected team or escalation level matches the expected decision.
- Review rate: How often a human must intervene.
- Operational performance: Latency, failure rate, retry rate, and cost per record.
Track extraction errors separately from decision errors. This helps teams identify whether a problem originates in reading the source, interpreting the information, applying business rules, or executing the downstream action.
11. A practical implementation roadmap
Phase 1 - Extract
Choose one workflow, such as support tickets. Define the schema and test extraction against labeled examples.
Phase 2 - Validate
Add deterministic validation, correction handling, missing-value checks, and duplicate detection.
Phase 3 - Decide
Introduce Clocked Decide (System One) for classification, scoring, and condition checks. Evaluate model outputs against reviewed examples.
Phase 4 - Orchestrate
Connect n8n to the ticketing system and add controlled routing, retry handling, and human-review branches.
Phase 5 - Govern and improve
Monitor quality, review failures, update schemas and policies, and rerun regression tests before deployment changes.
Conclusion
Unstructured data becomes more useful when organizations separate what the source says, what the system infers, and what the business decides to do.
Clocked supports multiple APIs that one could leverage as part of the automation work. However, this blog post focuses primarily on the structured extraction and decision handling via the Clocked.ai extract and decide APIs; One could leverage as part of the n8n automation and can connect the stages into a repeatable workflow.
The result is a modular pipeline that can turn tickets, emails, and documents into structured records, evaluated decisions, and governed business actions.
Start with one narrow use case, define measurable acceptance criteria, and keep humans in control wherever uncertainty or business risk warrants it.



Top comments (0)