Jira ChatGPT Integration: Complete Agile Automation Guide
Jira is the industry standard for agile project management, used by over 100,000 organizations worldwide. Integrating Jira with ChatGPT enables teams to manage issues, track sprints, query backlogs, and automate workflows through natural language conversations. Whether your team needs to create bug reports, update sprint boards, search with JQL (Jira Query Language), or receive real-time webhook notifications, this comprehensive guide provides production-ready code examples for building robust Jira integrations.
In this guide, you'll learn how to authenticate with Jira Cloud API using OAuth 2.0, manage issues and workflows, perform agile operations with sprints and boards, execute advanced JQL queries, and set up webhook automation for real-time synchronization. Each section includes TypeScript code examples that you can adapt for your ChatGPT app, whether you're building internal DevOps tools, customer support systems, or project management assistants.
By the end of this article, you'll have a complete understanding of Jira's REST API capabilities and practical implementations for issue management, sprint planning, backlog grooming, velocity tracking, and workflow automation—all accessible through conversational AI.
Jira Cloud API Setup
Atlassian Connect and OAuth 2.0
Jira Cloud uses OAuth 2.0 for secure authentication. You'll need to create an OAuth 2.0 integration in the Atlassian Developer Console to obtain client credentials and configure redirect URIs for your ChatGPT app.
Authentication Flow:
- Redirect users to Atlassian's authorization URL
- User grants permissions to your app
- Atlassian redirects back with authorization code
- Exchange code for access token and refresh token
- Use access token for API requests (expires after 1 hour)
- Use refresh token to obtain new access tokens (expires after 90 days)
Required Scopes:
read:jira-work- Read issues, projects, boardswrite:jira-work- Create and update issuesread:jira-user- Access user informationoffline_access- Obtain refresh tokens
OAuth Client Implementation
import axios, { AxiosInstance } from 'axios';
import { URL } from 'url';
interface JiraOAuthConfig {
clientId: string;
clientSecret: string;
redirectUri: string;
scopes: string[];
}
interface JiraTokens {
accessToken: string;
refreshToken: string;
expiresAt: number;
}
interface JiraCloudResource {
id: string;
name: string;
url: string;
scopes: string[];
avatarUrl: string;
}
export class JiraOAuthClient {
private config: JiraOAuthConfig;
private tokens: JiraTokens | null = null;
private apiClient: AxiosInstance | null = null;
private cloudId: string | null = null;
constructor(config: JiraOAuthConfig) {
this.config = config;
}
/**
* Generate OAuth authorization URL
*/
getAuthorizationUrl(state: string): string {
const params = new URLSearchParams({
audience: 'api.atlassian.com',
client_id: this.config.clientId,
scope: this.config.scopes.join(' '),
redirect_uri: this.config.redirectUri,
state,
response_type: 'code',
prompt: 'consent'
});
return `https://auth.atlassian.com/authorize?${params.toString()}`;
}
/**
* Exchange authorization code for tokens
*/
async exchangeCodeForTokens(code: string): Promise<JiraTokens> {
try {
const response = await axios.post(
'https://auth.atlassian.com/oauth/token',
{
grant_type: 'authorization_code',
client_id: this.config.clientId,
client_secret: this.config.clientSecret,
code,
redirect_uri: this.config.redirectUri
},
{
headers: { 'Content-Type': 'application/json' }
}
);
this.tokens = {
accessToken: response.data.access_token,
refreshToken: response.data.refresh_token,
expiresAt: Date.now() + response.data.expires_in * 1000
};
await this.initializeApiClient();
return this.tokens;
} catch (error: any) {
throw new Error(`Token exchange failed: ${error.message}`);
}
}
/**
* Refresh access token
*/
async refreshAccessToken(): Promise<JiraTokens> {
if (!this.tokens?.refreshToken) {
throw new Error('No refresh token available');
}
try {
const response = await axios.post(
'https://auth.atlassian.com/oauth/token',
{
grant_type: 'refresh_token',
client_id: this.config.clientId,
client_secret: this.config.clientSecret,
refresh_token: this.tokens.refreshToken
},
{
headers: { 'Content-Type': 'application/json' }
}
);
this.tokens = {
accessToken: response.data.access_token,
refreshToken: response.data.refresh_token,
expiresAt: Date.now() + response.data.expires_in * 1000
};
await this.initializeApiClient();
return this.tokens;
} catch (error: any) {
throw new Error(`Token refresh failed: ${error.message}`);
}
}
/**
* Initialize API client with access token
*/
private async initializeApiClient(): Promise<void> {
if (!this.tokens) {
throw new Error('No tokens available');
}
// Get cloud ID
const resources = await this.getAccessibleResources();
if (resources.length === 0) {
throw new Error('No accessible Jira sites found');
}
this.cloudId = resources[0].id;
// Create authenticated API client
this.apiClient = axios.create({
baseURL: `https://api.atlassian.com/ex/jira/${this.cloudId}/rest/api/3`,
headers: {
'Authorization': `Bearer ${this.tokens.accessToken}`,
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
// Add response interceptor for token refresh
this.apiClient.interceptors.response.use(
response => response,
async error => {
if (error.response?.status === 401 && this.tokens) {
// Token expired, refresh and retry
await this.refreshAccessToken();
error.config.headers['Authorization'] = `Bearer ${this.tokens.accessToken}`;
return axios.request(error.config);
}
return Promise.reject(error);
}
);
}
/**
* Get accessible Jira Cloud resources
*/
private async getAccessibleResources(): Promise<JiraCloudResource[]> {
if (!this.tokens) {
throw new Error('Not authenticated');
}
const response = await axios.get(
'https://api.atlassian.com/oauth/token/accessible-resources',
{
headers: {
'Authorization': `Bearer ${this.tokens.accessToken}`,
'Accept': 'application/json'
}
}
);
return response.data;
}
/**
* Get authenticated API client
*/
getApiClient(): AxiosInstance {
if (!this.apiClient) {
throw new Error('API client not initialized');
}
return this.apiClient;
}
/**
* Get cloud ID
*/
getCloudId(): string {
if (!this.cloudId) {
throw new Error('Cloud ID not available');
}
return this.cloudId;
}
}
This OAuth client handles the complete authentication flow, automatic token refresh, and provides an authenticated Axios instance for API requests. Learn more about building authentication systems for ChatGPT apps.
Issue Management
Creating and Updating Issues
Jira's issue management capabilities include creating issues, updating fields, transitioning workflows, managing watchers, and linking related issues. The REST API provides comprehensive access to all issue operations.
import { AxiosInstance } from 'axios';
interface JiraIssue {
id: string;
key: string;
self: string;
fields: {
summary: string;
description?: any;
issuetype: { id: string; name: string };
project: { id: string; key: string };
status: { id: string; name: string };
priority?: { id: string; name: string };
assignee?: { accountId: string; displayName: string };
reporter: { accountId: string; displayName: string };
created: string;
updated: string;
[key: string]: any;
};
}
interface CreateIssueRequest {
projectKey: string;
issueType: string;
summary: string;
description?: string;
priority?: string;
assigneeAccountId?: string;
labels?: string[];
components?: string[];
customFields?: Record<string, any>;
}
interface UpdateIssueRequest {
summary?: string;
description?: string;
priority?: string;
assigneeAccountId?: string;
labels?: string[];
customFields?: Record<string, any>;
}
export class JiraIssueManager {
constructor(private apiClient: AxiosInstance) {}
/**
* Create new issue
*/
async createIssue(request: CreateIssueRequest): Promise<JiraIssue> {
const fields: any = {
project: { key: request.projectKey },
issuetype: { name: request.issueType },
summary: request.summary
};
if (request.description) {
fields.description = {
type: 'doc',
version: 1,
content: [
{
type: 'paragraph',
content: [{ type: 'text', text: request.description }]
}
]
};
}
if (request.priority) {
fields.priority = { name: request.priority };
}
if (request.assigneeAccountId) {
fields.assignee = { accountId: request.assigneeAccountId };
}
if (request.labels && request.labels.length > 0) {
fields.labels = request.labels;
}
if (request.components && request.components.length > 0) {
fields.components = request.components.map(name => ({ name }));
}
// Add custom fields
if (request.customFields) {
Object.assign(fields, request.customFields);
}
const response = await this.apiClient.post('/issue', { fields });
return this.getIssue(response.data.key);
}
/**
* Get issue by key
*/
async getIssue(issueKey: string): Promise<JiraIssue> {
const response = await this.apiClient.get(`/issue/${issueKey}`);
return response.data;
}
/**
* Update issue
*/
async updateIssue(
issueKey: string,
request: UpdateIssueRequest
): Promise<void> {
const fields: any = {};
if (request.summary) {
fields.summary = request.summary;
}
if (request.description) {
fields.description = {
type: 'doc',
version: 1,
content: [
{
type: 'paragraph',
content: [{ type: 'text', text: request.description }]
}
]
};
}
if (request.priority) {
fields.priority = { name: request.priority };
}
if (request.assigneeAccountId) {
fields.assignee = { accountId: request.assigneeAccountId };
}
if (request.labels) {
fields.labels = request.labels;
}
if (request.customFields) {
Object.assign(fields, request.customFields);
}
await this.apiClient.put(`/issue/${issueKey}`, { fields });
}
/**
* Add comment to issue
*/
async addComment(issueKey: string, comment: string): Promise<void> {
await this.apiClient.post(`/issue/${issueKey}/comment`, {
body: {
type: 'doc',
version: 1,
content: [
{
type: 'paragraph',
content: [{ type: 'text', text: comment }]
}
]
}
});
}
/**
* Add watcher to issue
*/
async addWatcher(issueKey: string, accountId: string): Promise<void> {
await this.apiClient.post(`/issue/${issueKey}/watchers`, `"${accountId}"`, {
headers: { 'Content-Type': 'application/json' }
});
}
/**
* Link issues
*/
async linkIssues(
inwardIssue: string,
outwardIssue: string,
linkType: string
): Promise<void> {
await this.apiClient.post('/issueLink', {
type: { name: linkType },
inwardIssue: { key: inwardIssue },
outwardIssue: { key: outwardIssue }
});
}
/**
* Delete issue
*/
async deleteIssue(issueKey: string): Promise<void> {
await this.apiClient.delete(`/issue/${issueKey}`);
}
}
This issue manager provides complete CRUD operations for Jira issues, including advanced features like watchers and issue linking. For webhook-driven workflows, see real-time event processing for ChatGPT apps.
Workflow Transitions
interface JiraTransition {
id: string;
name: string;
to: {
id: string;
name: string;
};
hasScreen: boolean;
isGlobal: boolean;
isInitial: boolean;
isConditional: boolean;
}
interface TransitionRequest {
transitionId: string;
comment?: string;
fields?: Record<string, any>;
}
export class JiraWorkflowManager {
constructor(private apiClient: AxiosInstance) {}
/**
* Get available transitions for issue
*/
async getTransitions(issueKey: string): Promise<JiraTransition[]> {
const response = await this.apiClient.get(
`/issue/${issueKey}/transitions`
);
return response.data.transitions;
}
/**
* Transition issue to new status
*/
async transitionIssue(
issueKey: string,
request: TransitionRequest
): Promise<void> {
const payload: any = {
transition: { id: request.transitionId }
};
if (request.comment) {
payload.update = {
comment: [
{
add: {
body: {
type: 'doc',
version: 1,
content: [
{
type: 'paragraph',
content: [{ type: 'text', text: request.comment }]
}
]
}
}
}
]
};
}
if (request.fields) {
payload.fields = request.fields;
}
await this.apiClient.post(`/issue/${issueKey}/transitions`, payload);
}
/**
* Find transition by name
*/
async findTransitionByName(
issueKey: string,
transitionName: string
): Promise<JiraTransition | null> {
const transitions = await this.getTransitions(issueKey);
return transitions.find(t => t.name === transitionName) || null;
}
/**
* Transition issue by name (convenience method)
*/
async transitionIssueByName(
issueKey: string,
transitionName: string,
comment?: string
): Promise<void> {
const transition = await this.findTransitionByName(issueKey, transitionName);
if (!transition) {
throw new Error(`Transition "${transitionName}" not found for ${issueKey}`);
}
await this.transitionIssue(issueKey, {
transitionId: transition.id,
comment
});
}
}
Workflow transitions enable automated status updates based on ChatGPT conversations, making it easy to move issues from "To Do" to "In Progress" to "Done" through natural language commands.
Agile Operations
Sprint Management
Jira's agile capabilities include sprint management, board operations, backlog grooming, and velocity tracking. These features are essential for teams practicing Scrum or Kanban methodologies.
interface JiraSprint {
id: number;
self: string;
state: 'future' | 'active' | 'closed';
name: string;
startDate?: string;
endDate?: string;
completeDate?: string;
originBoardId: number;
goal?: string;
}
interface JiraBoard {
id: number;
self: string;
name: string;
type: 'scrum' | 'kanban';
location: {
projectId: number;
projectKey: string;
projectName: string;
};
}
interface CreateSprintRequest {
boardId: number;
name: string;
startDate?: string;
endDate?: string;
goal?: string;
}
export class JiraSprintManager {
constructor(private apiClient: AxiosInstance) {}
/**
* Get all boards
*/
async getBoards(): Promise<JiraBoard[]> {
const response = await this.apiClient.get('/board', {
params: { maxResults: 50 }
});
return response.data.values;
}
/**
* Get board by ID
*/
async getBoard(boardId: number): Promise<JiraBoard> {
const response = await this.apiClient.get(`/board/${boardId}`);
return response.data;
}
/**
* Get all sprints for board
*/
async getSprints(boardId: number): Promise<JiraSprint[]> {
const response = await this.apiClient.get(`/board/${boardId}/sprint`, {
params: { maxResults: 50 }
});
return response.data.values;
}
/**
* Get active sprint
*/
async getActiveSprint(boardId: number): Promise<JiraSprint | null> {
const sprints = await this.getSprints(boardId);
return sprints.find(s => s.state === 'active') || null;
}
/**
* Create sprint
*/
async createSprint(request: CreateSprintRequest): Promise<JiraSprint> {
const response = await this.apiClient.post('/sprint', {
name: request.name,
originBoardId: request.boardId,
startDate: request.startDate,
endDate: request.endDate,
goal: request.goal
});
return response.data;
}
/**
* Start sprint
*/
async startSprint(
sprintId: number,
startDate: string,
endDate: string
): Promise<JiraSprint> {
const response = await this.apiClient.post(`/sprint/${sprintId}`, {
state: 'active',
startDate,
endDate
});
return response.data;
}
/**
* Complete sprint
*/
async completeSprint(sprintId: number): Promise<JiraSprint> {
const response = await this.apiClient.post(`/sprint/${sprintId}`, {
state: 'closed',
completeDate: new Date().toISOString()
});
return response.data;
}
/**
* Get issues in sprint
*/
async getSprintIssues(sprintId: number): Promise<JiraIssue[]> {
const response = await this.apiClient.get(`/sprint/${sprintId}/issue`, {
params: { maxResults: 100 }
});
return response.data.issues;
}
/**
* Move issues to sprint
*/
async moveIssuesToSprint(
sprintId: number,
issueKeys: string[]
): Promise<void> {
await this.apiClient.post(`/sprint/${sprintId}/issue`, {
issues: issueKeys
});
}
}
Sprint management enables ChatGPT to automate agile ceremonies like sprint planning, daily standups, and retrospectives. See scheduling automation for ChatGPT apps for time-based workflows.
Agile Metrics
interface SprintMetrics {
sprintId: number;
sprintName: string;
totalIssues: number;
completedIssues: number;
incompletedIssues: number;
totalStoryPoints: number;
completedStoryPoints: number;
velocity: number;
completionRate: number;
}
export class JiraMetricsCalculator {
constructor(private apiClient: AxiosInstance) {}
/**
* Calculate sprint metrics
*/
async calculateSprintMetrics(sprintId: number): Promise<SprintMetrics> {
const sprint = await this.apiClient.get(`/sprint/${sprintId}`);
const issues = await this.apiClient.get(`/sprint/${sprintId}/issue`, {
params: { maxResults: 200 }
});
const sprintIssues: JiraIssue[] = issues.data.issues;
const completedIssues = sprintIssues.filter(
issue => issue.fields.status.name === 'Done'
);
const totalStoryPoints = sprintIssues.reduce(
(sum, issue) => sum + (issue.fields.customfield_10016 || 0),
0
);
const completedStoryPoints = completedIssues.reduce(
(sum, issue) => sum + (issue.fields.customfield_10016 || 0),
0
);
return {
sprintId: sprint.data.id,
sprintName: sprint.data.name,
totalIssues: sprintIssues.length,
completedIssues: completedIssues.length,
incompletedIssues: sprintIssues.length - completedIssues.length,
totalStoryPoints,
completedStoryPoints,
velocity: completedStoryPoints,
completionRate: (completedIssues.length / sprintIssues.length) * 100
};
}
}
JQL Queries & Search
Advanced JQL Query Builder
JQL (Jira Query Language) is a powerful search language that enables complex filtering and reporting. ChatGPT can translate natural language queries into JQL syntax.
interface JQLQuery {
jql: string;
maxResults?: number;
startAt?: number;
fields?: string[];
}
interface JQLSearchResult {
total: number;
startAt: number;
maxResults: number;
issues: JiraIssue[];
}
export class JiraJQLQueryBuilder {
constructor(private apiClient: AxiosInstance) {}
/**
* Execute JQL search
*/
async search(query: JQLQuery): Promise<JQLSearchResult> {
const response = await this.apiClient.post('/search', {
jql: query.jql,
maxResults: query.maxResults || 50,
startAt: query.startAt || 0,
fields: query.fields || ['summary', 'status', 'assignee', 'priority']
});
return {
total: response.data.total,
startAt: response.data.startAt,
maxResults: response.data.maxResults,
issues: response.data.issues
};
}
/**
* Find issues assigned to user
*/
async findMyIssues(accountId: string): Promise<JiraIssue[]> {
const result = await this.search({
jql: `assignee = "${accountId}" AND resolution = Unresolved ORDER BY priority DESC`
});
return result.issues;
}
/**
* Find overdue issues
*/
async findOverdueIssues(projectKey: string): Promise<JiraIssue[]> {
const result = await this.search({
jql: `project = "${projectKey}" AND duedate < now() AND resolution = Unresolved ORDER BY duedate ASC`
});
return result.issues;
}
/**
* Find recently updated issues
*/
async findRecentlyUpdated(
projectKey: string,
days: number
): Promise<JiraIssue[]> {
const result = await this.search({
jql: `project = "${projectKey}" AND updated >= -${days}d ORDER BY updated DESC`
});
return result.issues;
}
/**
* Find issues by sprint
*/
async findIssuesBySprint(sprintId: number): Promise<JiraIssue[]> {
const result = await this.search({
jql: `sprint = ${sprintId} ORDER BY rank ASC`
});
return result.issues;
}
/**
* Find high priority bugs
*/
async findHighPriorityBugs(projectKey: string): Promise<JiraIssue[]> {
const result = await this.search({
jql: `project = "${projectKey}" AND issuetype = Bug AND priority in (Highest, High) AND resolution = Unresolved ORDER BY priority DESC, created ASC`
});
return result.issues;
}
/**
* Build custom JQL query
*/
buildCustomQuery(filters: {
project?: string;
issueType?: string;
status?: string;
assignee?: string;
priority?: string;
labels?: string[];
sprint?: number;
orderBy?: string;
}): string {
const clauses: string[] = [];
if (filters.project) {
clauses.push(`project = "${filters.project}"`);
}
if (filters.issueType) {
clauses.push(`issuetype = "${filters.issueType}"`);
}
if (filters.status) {
clauses.push(`status = "${filters.status}"`);
}
if (filters.assignee) {
clauses.push(`assignee = "${filters.assignee}"`);
}
if (filters.priority) {
clauses.push(`priority = "${filters.priority}"`);
}
if (filters.labels && filters.labels.length > 0) {
const labelClause = filters.labels.map(l => `"${l}"`).join(', ');
clauses.push(`labels in (${labelClause})`);
}
if (filters.sprint) {
clauses.push(`sprint = ${filters.sprint}`);
}
let jql = clauses.join(' AND ');
if (filters.orderBy) {
jql += ` ORDER BY ${filters.orderBy}`;
}
return jql;
}
}
JQL enables sophisticated filtering that would be difficult to implement manually. Learn more about advanced filtering techniques for ChatGPT apps.
Webhook Automation
Webhook Event Processor
Jira webhooks enable real-time notifications when issues are created, updated, transitioned, or commented on. This enables ChatGPT apps to react instantly to project changes.
interface JiraWebhookEvent {
webhookEvent: string;
issue?: JiraIssue;
user?: {
accountId: string;
displayName: string;
emailAddress: string;
};
changelog?: {
items: Array<{
field: string;
fieldtype: string;
from: string;
fromString: string;
to: string;
toString: string;
}>;
};
comment?: {
id: string;
body: any;
author: {
accountId: string;
displayName: string;
};
created: string;
};
}
export class JiraWebhookProcessor {
/**
* Process webhook event
*/
async processWebhook(event: JiraWebhookEvent): Promise<void> {
switch (event.webhookEvent) {
case 'jira:issue_created':
await this.handleIssueCreated(event);
break;
case 'jira:issue_updated':
await this.handleIssueUpdated(event);
break;
case 'jira:issue_deleted':
await this.handleIssueDeleted(event);
break;
case 'comment_created':
await this.handleCommentCreated(event);
break;
default:
console.log(`Unhandled webhook event: ${event.webhookEvent}`);
}
}
/**
* Handle issue created event
*/
private async handleIssueCreated(event: JiraWebhookEvent): Promise<void> {
if (!event.issue) return;
console.log(`New issue created: ${event.issue.key}`);
console.log(`Summary: ${event.issue.fields.summary}`);
console.log(`Type: ${event.issue.fields.issuetype.name}`);
console.log(`Reporter: ${event.issue.fields.reporter.displayName}`);
// Send notification, trigger automation, etc.
}
/**
* Handle issue updated event
*/
private async handleIssueUpdated(event: JiraWebhookEvent): Promise<void> {
if (!event.issue || !event.changelog) return;
console.log(`Issue updated: ${event.issue.key}`);
event.changelog.items.forEach(change => {
console.log(
`${change.field}: ${change.fromString} → ${change.toString}`
);
// Check for status changes
if (change.field === 'status') {
this.handleStatusChange(event.issue!, change.fromString, change.toString);
}
// Check for assignee changes
if (change.field === 'assignee') {
this.handleAssigneeChange(event.issue!, change.toString);
}
});
}
/**
* Handle issue deleted event
*/
private async handleIssueDeleted(event: JiraWebhookEvent): Promise<void> {
if (!event.issue) return;
console.log(`Issue deleted: ${event.issue.key}`);
}
/**
* Handle comment created event
*/
private async handleCommentCreated(event: JiraWebhookEvent): Promise<void> {
if (!event.comment || !event.issue) return;
console.log(`New comment on ${event.issue.key}`);
console.log(`Author: ${event.comment.author.displayName}`);
// Extract plain text from ADF
const commentText = this.extractTextFromADF(event.comment.body);
console.log(`Comment: ${commentText}`);
}
/**
* Handle status change
*/
private handleStatusChange(
issue: JiraIssue,
fromStatus: string,
toStatus: string
): void {
console.log(`Status changed: ${fromStatus} → ${toStatus}`);
// Trigger automation based on status
if (toStatus === 'Done') {
console.log(`Issue ${issue.key} completed!`);
}
}
/**
* Handle assignee change
*/
private handleAssigneeChange(issue: JiraIssue, newAssignee: string): void {
console.log(`New assignee: ${newAssignee}`);
// Send notification to new assignee
}
/**
* Extract plain text from Atlassian Document Format (ADF)
*/
private extractTextFromADF(adf: any): string {
if (!adf || !adf.content) return '';
let text = '';
for (const node of adf.content) {
if (node.type === 'paragraph' && node.content) {
for (const child of node.content) {
if (child.type === 'text') {
text += child.text;
}
}
text += '\n';
}
}
return text.trim();
}
}
Webhooks enable event-driven architectures where ChatGPT apps react to Jira changes in real-time. See event-driven architecture for ChatGPT apps for design patterns.
Comment Manager
export class JiraCommentManager {
constructor(private apiClient: AxiosInstance) {}
/**
* Add comment to issue
*/
async addComment(
issueKey: string,
comment: string,
visibility?: { type: 'role' | 'group'; value: string }
): Promise<void> {
const payload: any = {
body: {
type: 'doc',
version: 1,
content: [
{
type: 'paragraph',
content: [{ type: 'text', text: comment }]
}
]
}
};
if (visibility) {
payload.visibility = visibility;
}
await this.apiClient.post(`/issue/${issueKey}/comment`, payload);
}
/**
* Get comments for issue
*/
async getComments(issueKey: string): Promise<any[]> {
const response = await this.apiClient.get(`/issue/${issueKey}/comment`);
return response.data.comments;
}
}
Attachment Handler
import FormData from 'form-data';
import fs from 'fs';
export class JiraAttachmentHandler {
constructor(private apiClient: AxiosInstance) {}
/**
* Upload attachment to issue
*/
async uploadAttachment(
issueKey: string,
filePath: string,
filename: string
): Promise<void> {
const form = new FormData();
form.append('file', fs.createReadStream(filePath), filename);
await this.apiClient.post(`/issue/${issueKey}/attachments`, form, {
headers: {
...form.getHeaders(),
'X-Atlassian-Token': 'no-check'
}
});
}
/**
* Get attachments for issue
*/
async getAttachments(issueKey: string): Promise<any[]> {
const issue = await this.apiClient.get(`/issue/${issueKey}`, {
params: { fields: 'attachment' }
});
return issue.data.fields.attachment || [];
}
}
Custom Field Manager
export class JiraCustomFieldManager {
constructor(private apiClient: AxiosInstance) {}
/**
* Get all custom fields
*/
async getCustomFields(): Promise<any[]> {
const response = await this.apiClient.get('/field');
return response.data.filter((field: any) => field.custom);
}
/**
* Set custom field value
*/
async setCustomFieldValue(
issueKey: string,
customFieldId: string,
value: any
): Promise<void> {
await this.apiClient.put(`/issue/${issueKey}`, {
fields: {
[customFieldId]: value
}
});
}
/**
* Get story points field ID
*/
async getStoryPointsFieldId(): Promise<string | null> {
const fields = await this.getCustomFields();
const storyPointsField = fields.find(
f => f.name === 'Story Points' || f.name === 'Story point estimate'
);
return storyPointsField?.id || null;
}
}
Conclusion
Integrating Jira with ChatGPT enables powerful agile automation through natural language conversations. This guide covered OAuth 2.0 authentication, comprehensive issue management, workflow transitions, sprint operations, JQL query building, and webhook-driven real-time synchronization. With these production-ready TypeScript examples, you can build ChatGPT apps that streamline project management, automate DevOps workflows, and enhance team collaboration.
The Jira Cloud API provides extensive capabilities beyond what we've covered—including project management, user administration, permission schemes, and advanced reporting. For additional integration patterns, explore the official Jira Cloud REST API documentation, JQL reference guide, and OAuth 2.0 implementation guide.
Ready to build your Jira ChatGPT integration? Start your free trial with MakeAIHQ and deploy agile automation tools to the ChatGPT App Store in 48 hours—no coding required.
Related Resources
- ChatGPT App Builder: Complete Guide
- Authentication Systems for ChatGPT Apps
- Webhook Processing for ChatGPT Apps
- Event-Driven Architecture for ChatGPT Apps
- Scheduling Automation for ChatGPT Apps
- Advanced Filtering Techniques
- Project Management ChatGPT Apps
- DevOps Automation with ChatGPT
- API Rate Limiting Best Practices
- Error Handling in ChatGPT Integrations
Meta Information:
- Word Count: 2,100+ words
- Code Examples: 10 production-ready TypeScript implementations
- Internal Links: 10 strategically placed links to related content
- External Links: 3 authoritative Atlassian documentation links
- Schema Type: HowTo (structured guide format)
- Primary Keyword Density: "jira chatgpt integration" optimized throughout
- Secondary Keywords: jira api, jql, agile automation, sprint management, webhook integration