Building a Jira AI Agent: Ticket Creation, Updates, and Sprint Management
Build an AI agent that integrates with Jira for automated ticket creation, intelligent updates, JQL-powered queries, and sprint management using the Jira REST API with practical Python examples.
Why Build AI Agents for Jira
Jira is the backbone of project tracking for software teams. An AI agent connected to Jira can automate ticket creation from Slack messages or emails, enrich tickets with context from codebases, estimate story points based on historical data, manage sprint planning, and generate sprint retrospective summaries — turning Jira from a manual data entry system into an intelligent project assistant.
Setting Up the Jira Client
Use API tokens for Jira Cloud authentication. The REST API provides comprehensive access to issues, boards, sprints, and workflows.
import httpx
from base64 import b64encode
class JiraClient:
def __init__(self, domain: str, email: str, api_token: str):
credentials = b64encode(
f"{email}:{api_token}".encode()
).decode()
self.http = httpx.AsyncClient(
base_url=f"https://{domain}.atlassian.net/rest/api/3",
headers={
"Authorization": f"Basic {credentials}",
"Content-Type": "application/json",
},
timeout=30.0,
)
async def create_issue(self, project_key: str, summary: str,
description: str, issue_type: str = "Task",
priority: str = "Medium",
labels: list[str] = None) -> dict:
payload = {
"fields": {
"project": {"key": project_key},
"summary": summary,
"description": {
"type": "doc",
"version": 1,
"content": [
{
"type": "paragraph",
"content": [
{"type": "text", "text": description}
],
}
],
},
"issuetype": {"name": issue_type},
"priority": {"name": priority},
}
}
if labels:
payload["fields"]["labels"] = labels
response = await self.http.post("/issue", json=payload)
response.raise_for_status()
return response.json()
async def search_issues(self, jql: str, max_results: int = 50) -> list:
response = await self.http.post(
"/search",
json={
"jql": jql,
"maxResults": max_results,
"fields": [
"summary", "status", "assignee",
"priority", "created", "updated",
],
},
)
response.raise_for_status()
return response.json()["issues"]
AI-Powered Ticket Creation
Let the agent parse unstructured requests — from Slack messages, emails, or voice transcripts — and create well-formatted Jira tickets.
async def create_ticket_from_request(
jira: JiraClient,
agent,
raw_request: str,
project_key: str,
):
# Agent structures the raw input into Jira fields
structured = await agent.run(
prompt=(
f"Parse this request into a Jira ticket.\n"
f"Determine: summary (one line), description (detailed), "
f"issue_type (Bug/Task/Story), priority (Highest/High/Medium/Low/Lowest), "
f"and relevant labels.\n\n"
f"Request: {raw_request}"
)
)
ticket = await jira.create_issue(
project_key=project_key,
summary=structured.summary,
description=structured.description,
issue_type=structured.issue_type,
priority=structured.priority,
labels=structured.labels,
)
return ticket["key"]
JQL Queries for Intelligent Context
JQL (Jira Query Language) gives your agent powerful search capabilities. Use it to gather context before making decisions.
See AI Voice Agents Handle Real Calls
Book a free demo or calculate how much you can save with AI voice automation.
async def get_sprint_health(jira: JiraClient, project_key: str) -> dict:
# Find current sprint issues
in_progress = await jira.search_issues(
f'project = {project_key} AND sprint in openSprints() '
f'AND status = "In Progress"'
)
done = await jira.search_issues(
f'project = {project_key} AND sprint in openSprints() '
f'AND status = "Done"'
)
todo = await jira.search_issues(
f'project = {project_key} AND sprint in openSprints() '
f'AND status = "To Do"'
)
blocked = await jira.search_issues(
f'project = {project_key} AND sprint in openSprints() '
f'AND status = "Blocked"'
)
return {
"total": len(in_progress) + len(done) + len(todo) + len(blocked),
"done": len(done),
"in_progress": len(in_progress),
"todo": len(todo),
"blocked": len(blocked),
"completion_pct": round(
len(done) / max(len(in_progress) + len(done) + len(todo) + len(blocked), 1) * 100
),
}
Workflow Transitions
Moving tickets through workflow states requires knowing the available transitions for the current status.
async def transition_issue(
jira: JiraClient, issue_key: str, target_status: str
):
# Get available transitions
response = await jira.http.get(
f"/issue/{issue_key}/transitions"
)
transitions = response.json()["transitions"]
# Find the transition that leads to our target status
transition = next(
(t for t in transitions if t["to"]["name"] == target_status),
None,
)
if not transition:
available = [t["to"]["name"] for t in transitions]
raise ValueError(
f"Cannot transition to '{target_status}'. "
f"Available: {available}"
)
await jira.http.post(
f"/issue/{issue_key}/transitions",
json={"transition": {"id": transition["id"]}},
)
# Agent-driven bulk status update
async def close_stale_tickets(jira: JiraClient, project_key: str, agent):
stale = await jira.search_issues(
f'project = {project_key} AND status = "In Progress" '
f'AND updated <= -14d'
)
for issue in stale:
key = issue["key"]
summary = issue["fields"]["summary"]
decision = await agent.run(
prompt=f"Ticket {key} ('{summary}') has not been updated in "
f"14 days. Should we move it to Blocked, close it, "
f"or leave it? Explain briefly."
)
if decision.action != "leave":
await transition_issue(jira, key, decision.target_status)
await jira.http.post(
f"/issue/{key}/comment",
json={"body": {
"type": "doc", "version": 1,
"content": [{"type": "paragraph", "content": [
{"type": "text", "text": f"AI Agent: {decision.reason}"}
]}]
}},
)
FAQ
How do I handle Jira's Atlassian Document Format for descriptions?
Jira Cloud V3 API uses Atlassian Document Format (ADF), a JSON-based rich text format. Simple text wraps in paragraph nodes as shown above. For complex formatting (tables, code blocks, bullet lists), build nested ADF node structures. Consider writing a helper function that converts markdown to ADF to simplify agent output formatting.
What are the Jira API rate limits?
Jira Cloud allows roughly 100 requests per minute for basic plans and higher limits for premium. Implement rate limiting on your client side with a token bucket or semaphore. The API returns Retry-After headers on 429 responses — respect those values before retrying.
Can the AI agent assign tickets to specific team members?
Yes. Use the assignee field in the create or update payload with the user's Atlassian account ID. To find account IDs, query /rest/api/3/user/search?query=username. Your agent can learn team members' areas of expertise and intelligently assign based on ticket content and past assignments.
#Jira #ProjectManagement #RESTAPI #AIAgents #SprintManagement #AgenticAI #LearnAI #AIEngineering
CallSphere Team
Expert insights on AI voice agents and customer communication automation.
Try CallSphere AI Voice Agents
See how AI voice agents work for your industry. Live demo available -- no signup required.