
Jira
- 25 installs
- 4 repo stars
- Updated April 11, 2026
- 89jobrien/steve
jira is a Claude Code skill that manages and searches Jira Cloud issues through the REST API v3 and JQL.
About
jira is a Claude Code skill that integrates with Jira Cloud for issue management and search. A developer uses it to search issues with JQL, create or update issues, add comments, transition status, and assign work via the REST API v3. It ships a Python helper script and references for JQL and Atlassian Document Format.
- Integrates with Jira Cloud via REST API v3 and JQL
- Creates, updates, comments on, and transitions issues
- Includes a Python helper script and JQL/ADF references
Jira by the numbers
- 25 all-time installs (skills.sh)
- Ranked #1,961 of 3,282 Productivity & Planning skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
jira capabilities & compatibility
Free skill; requires a Jira Cloud account and API token.
- Capabilities
- jira integration · issue management · jql search
- Works with
- jira · atlassian
- Use cases
- project management
- Pricing
- Bring your own API key
What jira says it does
Jira Cloud integration for issue management and search.
This skill enables direct interaction with Jira Cloud via REST API v3 and JQL queries.
npx skills add https://github.com/89jobrien/steve --skill jiraAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 25 |
|---|---|
| repo stars | ★ 4 |
| Last updated | April 11, 2026 |
| Repository | 89jobrien/steve ↗ |
What it does
Manage and search Jira Cloud issues via the REST API and JQL.
Who is it for?
Developers who manage Jira issues, run JQL searches, or automate ticket updates from the terminal.
Skip if: Jira Server/Data Center instances or teams without a Jira Cloud API token.
When should I use this skill?
You are working with Jira tickets, searching with JQL, or creating and transitioning issues.
What you get
Runs JQL searches and creates, updates, comments on, transitions, and assigns Jira Cloud issues via the API.
- Jira issue operations
- JQL search results
By the numbers
- 7 core workflows from get issue to assign issue
- 3 bundled reference files (API endpoints, JQL, ADF)
Files
Jira Integration Skill
This skill enables direct interaction with Jira Cloud via REST API v3 and JQL queries.
Prerequisites
Set these environment variables (or in .env file):
JIRA_DOMAIN=company.atlassian.net
JIRA_EMAIL=user@company.com
JIRA_API_TOKEN=your-api-tokenGenerate API tokens at: <https://id.atlassian.com/manage-profile/security/api-tokens>
Core Workflows
1. Get Issue Details
To retrieve issue information:
python scripts/jira_api.py GET /issue/PROJ-123With specific fields:
python scripts/jira_api.py GET "/issue/PROJ-123?fields=summary,status,assignee"2. Search with JQL
To search issues using JQL:
python scripts/jira_api.py GET /search --query "jql=project=AOP AND status='In Progress'&maxResults=20"Common JQL patterns - see references/jql-reference.md:
- My open issues:
assignee = currentUser() AND resolution = Unresolved - Recent updates:
updated >= -1d ORDER BY updated DESC - Sprint work:
sprint in openSprints() AND assignee = currentUser()
3. Create Issue
To create a new issue, use ADF format for description (see references/adf-format.md):
python scripts/jira_api.py POST /issue --data '{
"fields": {
"project": { "key": "PROJ" },
"issuetype": { "name": "Task" },
"summary": "Issue title",
"description": {
"type": "doc",
"version": 1,
"content": [
{
"type": "paragraph",
"content": [{ "type": "text", "text": "Description here" }]
}
]
}
}
}'4. Update Issue
To update fields on an existing issue:
python scripts/jira_api.py PUT /issue/PROJ-123 --data '{
"fields": {
"summary": "Updated title",
"labels": ["label1", "label2"]
}
}'5. Add Comment
To add a comment (requires ADF format):
python scripts/jira_api.py POST /issue/PROJ-123/comment --data '{
"body": {
"type": "doc",
"version": 1,
"content": [
{
"type": "paragraph",
"content": [{ "type": "text", "text": "Comment text here" }]
}
]
}
}'6. Transition Issue Status
First, get available transitions:
python scripts/jira_api.py GET /issue/PROJ-123/transitionsThen transition to new status:
python scripts/jira_api.py POST /issue/PROJ-123/transitions --data '{
"transition": { "id": "21" }
}'7. Assign Issue
To assign an issue:
# Get user account ID first
python scripts/jira_api.py GET "/user/search?query=username"
# Then assign
python scripts/jira_api.py PUT /issue/PROJ-123/assignee --data '{
"accountId": "user-account-id"
}'To unassign:
python scripts/jira_api.py PUT /issue/PROJ-123/assignee --data '{"accountId": null}'Direct curl Usage
For quick operations without the helper script:
JIRA_DOMAIN="company.atlassian.net"
AUTH=$(echo -n "$JIRA_EMAIL:$JIRA_API_TOKEN" | base64)
curl -s "https://$JIRA_DOMAIN/rest/api/3/issue/PROJ-123" \
-H "Authorization: Basic $AUTH" \
-H "Content-Type: application/json"Reference Files
- `references/api-endpoints.md` - Complete REST API v3 endpoint reference
- `references/jql-reference.md` - JQL operators, functions, fields, and patterns
- `references/adf-format.md` - Atlassian Document Format for rich text fields
Common Patterns
Bulk Operations
To get multiple issues efficiently:
python scripts/jira_api.py GET /search --query "jql=key in (PROJ-1,PROJ-2,PROJ-3)"Get Project Info
To list projects or get project details:
python scripts/jira_api.py GET /project
python scripts/jira_api.py GET /project/PROJGet Available Issue Types
python scripts/jira_api.py GET "/project/PROJ?expand=issueTypes"Error Handling
Common error codes:
- 400: Bad request - check JSON syntax and field names
- 401: Unauthorized - verify credentials
- 403: Forbidden - check user permissions
- 404: Not found - verify issue key exists
- 429: Rate limited - wait and retry
For field validation errors, Jira returns detailed error messages indicating which fields are invalid.
Atlassian Document Format (ADF) Reference
Jira Cloud API v3 uses ADF for rich text fields like description and comment.body.
Basic Structure
Every ADF document has this structure:
{
"type": "doc",
"version": 1,
"content": [
// Array of block nodes
]
}Block Nodes
Paragraph
{
"type": "paragraph",
"content": [
{ "type": "text", "text": "Plain text here" }
]
}Heading
{
"type": "heading",
"attrs": { "level": 2 },
"content": [
{ "type": "text", "text": "Heading Text" }
]
}Levels: 1-6
Bullet List
{
"type": "bulletList",
"content": [
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [{ "type": "text", "text": "Item 1" }]
}
]
},
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [{ "type": "text", "text": "Item 2" }]
}
]
}
]
}Ordered List
{
"type": "orderedList",
"content": [
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [{ "type": "text", "text": "Step 1" }]
}
]
}
]
}Code Block
{
"type": "codeBlock",
"attrs": { "language": "python" },
"content": [
{ "type": "text", "text": "def hello():\n print('Hello')" }
]
}Blockquote
{
"type": "blockquote",
"content": [
{
"type": "paragraph",
"content": [{ "type": "text", "text": "Quoted text" }]
}
]
}Rule (Horizontal Line)
{
"type": "rule"
}Inline Formatting (Marks)
Add marks array to text nodes for formatting:
Bold
{
"type": "text",
"text": "Bold text",
"marks": [{ "type": "strong" }]
}Italic
{
"type": "text",
"text": "Italic text",
"marks": [{ "type": "em" }]
}Code (Inline)
{
"type": "text",
"text": "code snippet",
"marks": [{ "type": "code" }]
}Link
{
"type": "text",
"text": "Click here",
"marks": [
{
"type": "link",
"attrs": { "href": "https://example.com" }
}
]
}Combined Marks
{
"type": "text",
"text": "Bold and italic",
"marks": [
{ "type": "strong" },
{ "type": "em" }
]
}Special Nodes
Mention User
{
"type": "mention",
"attrs": {
"id": "account-id-here",
"text": "@username"
}
}Emoji
{
"type": "emoji",
"attrs": {
"shortName": ":thumbsup:",
"text": "thumbs up"
}
}Hard Break (Line Break)
{
"type": "hardBreak"
}Complete Examples
Simple Comment
{
"body": {
"type": "doc",
"version": 1,
"content": [
{
"type": "paragraph",
"content": [
{ "type": "text", "text": "This is a comment." }
]
}
]
}
}Issue Description with Formatting
{
"type": "doc",
"version": 1,
"content": [
{
"type": "heading",
"attrs": { "level": 2 },
"content": [{ "type": "text", "text": "Summary" }]
},
{
"type": "paragraph",
"content": [
{ "type": "text", "text": "This issue tracks " },
{ "type": "text", "text": "important", "marks": [{ "type": "strong" }] },
{ "type": "text", "text": " changes." }
]
},
{
"type": "heading",
"attrs": { "level": 2 },
"content": [{ "type": "text", "text": "Steps" }]
},
{
"type": "orderedList",
"content": [
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [{ "type": "text", "text": "First step" }]
}
]
},
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [{ "type": "text", "text": "Second step" }]
}
]
}
]
}
]
}Tips
1. Always wrap text in paragraph nodes for block content 2. The version must be 1 3. Empty content arrays are invalid - omit the field or include content 4. Test complex ADF in Jira's editor first, then inspect the API response
Jira REST API v3 Endpoints Reference
Base URL: https://{domain}.atlassian.net/rest/api/3
Authentication
All requests require Basic Auth with email and API token:
AUTH=$(echo -n "email@example.com:API_TOKEN" | base64)
curl -H "Authorization: Basic $AUTH" -H "Content-Type: application/json" ...Issue Operations
Get Issue
GET /issue/{issueIdOrKey}Query params: fields, expand (changelog, transitions, renderedFields)
Create Issue
POST /issue{
"fields": {
"project": { "key": "PROJ" },
"issuetype": { "name": "Task" },
"summary": "Issue title",
"description": {
"type": "doc",
"version": 1,
"content": [
{
"type": "paragraph",
"content": [{ "type": "text", "text": "Description text" }]
}
]
},
"assignee": { "accountId": "user-account-id" },
"labels": ["label1", "label2"],
"priority": { "name": "High" }
}
}Update Issue
PUT /issue/{issueIdOrKey}Same body format as create, only include fields to update.
Delete Issue
DELETE /issue/{issueIdOrKey}Add Comment
POST /issue/{issueIdOrKey}/comment{
"body": {
"type": "doc",
"version": 1,
"content": [
{
"type": "paragraph",
"content": [{ "type": "text", "text": "Comment text" }]
}
]
}
}Get Transitions
GET /issue/{issueIdOrKey}/transitionsReturns available status transitions for the issue.
Transition Issue
POST /issue/{issueIdOrKey}/transitions{
"transition": { "id": "21" }
}Assign Issue
PUT /issue/{issueIdOrKey}/assignee{ "accountId": "user-account-id" }To unassign: { "accountId": null }
Add Labels
PUT /issue/{issueIdOrKey}{
"update": {
"labels": [
{ "add": "new-label" }
]
}
}Link Issues
POST /issueLink{
"type": { "name": "Blocks" },
"inwardIssue": { "key": "PROJ-123" },
"outwardIssue": { "key": "PROJ-456" }
}Search
Search with JQL
GET /search?jql={jql}&maxResults=50&startAt=0Or POST for complex queries:
POST /search{
"jql": "project = PROJ AND status = 'In Progress'",
"startAt": 0,
"maxResults": 50,
"fields": ["summary", "status", "assignee"]
}Project Operations
List Projects
GET /projectGet Project
GET /project/{projectIdOrKey}Query params: expand (issueTypes, lead, description)
Get Project Issue Types
GET /project/{projectIdOrKey}/statusesUser Operations
Get Current User
GET /myselfSearch Users
GET /user/search?query={query}Get User by Account ID
GET /user?accountId={accountId}Common Response Codes
| Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Created |
| 204 | No content (success) |
| 400 | Bad request - check parameters |
| 401 | Unauthorized - check credentials |
| 403 | Forbidden - check permissions |
| 404 | Not found |
| 429 | Rate limited |
JQL (Jira Query Language) Reference
JQL is used to search for issues in Jira. Queries follow the pattern: field operator value [AND/OR field operator value]
Operators
| Operator | Description | Example |
|---|---|---|
= | Equals | status = "In Progress" |
!= | Not equals | assignee != currentUser() |
> | Greater than | created > -7d |
>= | Greater than or equal | priority >= High |
< | Less than | duedate < endOfWeek() |
<= | Less than or equal | updated <= -1w |
~ | Contains (text search) | summary ~ "bug" |
!~ | Does not contain | description !~ "test" |
IN | In list | status IN ("Open", "In Progress") |
NOT IN | Not in list | priority NOT IN (Low, Lowest) |
IS | Is (for empty/null) | assignee IS EMPTY |
IS NOT | Is not empty | fixVersion IS NOT EMPTY |
WAS | Previous value | status WAS "Open" |
WAS IN | Was in list | status WAS IN ("Open", "Reopened") |
WAS NOT | Was not value | assignee WAS NOT jsmith |
CHANGED | Field changed | status CHANGED |
Common Fields
Issue Fields
project- Project key (e.g., "AOP")issuetype- Issue type (Task, Bug, Story, Epic)status- Current statussummary- Issue title (text search with ~)description- Issue description (text search with ~)priority- Priority levelresolution- Resolution statuslabels- Issue labels
People Fields
assignee- Assigned userreporter- Issue creatorcreator- Same as reporterwatcher- Users watching
Date Fields
created- Creation dateupdated- Last update dateduedate- Due dateresolved- Resolution datelastViewed- Last viewed date
Agile Fields
sprint- Sprint name or IDfixVersion- Fix versionaffectedVersion- Affected versioncomponent- Component nameepic- Epic link (for stories)parent- Parent issue (for subtasks)
Functions
User Functions
currentUser()- Logged-in usermembersOf("group")- Members of a group
Date Functions
now()- Current timestampstartOfDay()- Start of todayendOfDay()- End of todaystartOfWeek()- Start of current weekendOfWeek()- End of current weekstartOfMonth()- Start of current monthendOfMonth()- End of current monthstartOfYear()- Start of current yearendOfYear()- End of current year
Sprint Functions
openSprints()- Active sprintsclosedSprints()- Completed sprintsfutureSprints()- Planned sprints
Other Functions
issueHistory()- Issues in historylinkedIssues(KEY)- Issues linked to KEYvotedIssues()- Issues you voted forwatchedIssues()- Issues you're watching
Relative Dates
Use relative time with +/- and units:
1d- 1 day1w- 1 week1m- 1 month (note: not minutes)1y- 1 year1h- 1 hour
Examples:
-7d- 7 days ago-2w- 2 weeks ago+1d- 1 day from now
Common JQL Patterns
My Open Issues
assignee = currentUser() AND resolution = Unresolved ORDER BY priority DESCIssues Updated Recently
updated >= -1d ORDER BY updated DESCBugs in Current Sprint
issuetype = Bug AND sprint in openSprints()Unassigned Issues in Project
project = AOP AND assignee IS EMPTY AND status != DoneIssues Due This Week
duedate >= startOfWeek() AND duedate <= endOfWeek()High Priority Blockers
priority = Highest AND status != Done AND issuetype = BugMy Team's Work
assignee IN membersOf("my-team") AND sprint in openSprints()Recently Created by Me
reporter = currentUser() AND created >= -7dIssues Changed Status Today
status CHANGED AFTER startOfDay()Text Search
summary ~ "login" OR description ~ "authentication"Complex Query
project = AOP
AND issuetype IN (Bug, Task)
AND status NOT IN (Done, Closed)
AND (assignee = currentUser() OR assignee IS EMPTY)
ORDER BY priority DESC, created ASCORDER BY
Sort results with ORDER BY:
ORDER BY created DESC- Newest firstORDER BY priority DESC, updated DESC- By priority, then update dateORDER BY rank ASC- Board ranking order
Tips
1. Quote values with spaces: status = "In Progress" 2. Field names are case-insensitive 3. Use parentheses for complex logic: (A OR B) AND C 4. Escape special chars with backslash: summary ~ "test\-case" 5. Empty check: field IS EMPTY not field = ""
#!/usr/bin/env python3
"""Jira API Helper Script
Makes authenticated requests to Jira Cloud REST API v3.
Usage:
python jira_api.py <method> <endpoint> [--data JSON] [--query PARAMS]
Examples:
# Get an issue
python jira_api.py GET /issue/PROJ-123
# Search with JQL
python jira_api.py GET /search --query "jql=project=AOP&maxResults=10"
# Create an issue
python jira_api.py POST /issue --data '{"fields":{"project":{"key":"AOP"},...}}'
# Add a comment
python jira_api.py POST /issue/PROJ-123/comment --data '{"body":{"type":"doc",...}}'
# Transition an issue
python jira_api.py POST /issue/PROJ-123/transitions --data '{"transition":{"id":"21"}}'
Environment variables (or .env file):
JIRA_DOMAIN - Your Jira domain (e.g., company.atlassian.net)
JIRA_EMAIL - Your Jira account email
JIRA_API_TOKEN - Your Jira API token
"""
import argparse
import base64
import json
import os
import sys
import urllib.error
import urllib.parse
import urllib.request
from pathlib import Path
def load_env():
"""Load environment variables from .env file if present."""
env_file = Path.cwd() / ".env"
if env_file.exists():
with open(env_file) as f:
for line in f:
line = line.strip()
if line and not line.startswith("#") and "=" in line:
key, value = line.split("=", 1)
value = value.strip().strip('"').strip("'")
if key not in os.environ:
os.environ[key] = value
def get_auth_header():
"""Get Basic Auth header from environment."""
email = os.environ.get("JIRA_EMAIL")
token = os.environ.get("JIRA_API_TOKEN")
if not email or not token:
print(
"Error: JIRA_EMAIL and JIRA_API_TOKEN environment variables required", file=sys.stderr
)
sys.exit(1)
credentials = f"{email}:{token}"
encoded = base64.b64encode(credentials.encode()).decode()
return f"Basic {encoded}"
def get_base_url():
"""Get Jira base URL from environment."""
domain = os.environ.get("JIRA_DOMAIN")
if not domain:
print("Error: JIRA_DOMAIN environment variable required", file=sys.stderr)
sys.exit(1)
if not domain.startswith("http"):
domain = f"https://{domain}"
return f"{domain}/rest/api/3"
def make_request(method, endpoint, data=None, query=None):
"""Make an authenticated request to Jira API."""
base_url = get_base_url()
url = f"{base_url}{endpoint}"
if query:
url = f"{url}?{query}"
headers = {
"Authorization": get_auth_header(),
"Content-Type": "application/json",
"Accept": "application/json",
}
body = None
if data:
if isinstance(data, str):
body = data.encode()
else:
body = json.dumps(data).encode()
req = urllib.request.Request(url, data=body, headers=headers, method=method)
try:
with urllib.request.urlopen(req) as response:
if response.status == 204:
return {"status": "success", "code": 204}
content = response.read().decode()
if content:
return json.loads(content)
return {"status": "success", "code": response.status}
except urllib.error.HTTPError as e:
error_body = e.read().decode()
try:
error_json = json.loads(error_body)
print(json.dumps(error_json, indent=2), file=sys.stderr)
except json.JSONDecodeError:
print(f"Error {e.code}: {error_body}", file=sys.stderr)
sys.exit(1)
except urllib.error.URLError as e:
print(f"Connection error: {e.reason}", file=sys.stderr)
sys.exit(1)
def main():
parser = argparse.ArgumentParser(description="Jira API Helper")
parser.add_argument("method", choices=["GET", "POST", "PUT", "DELETE"], help="HTTP method")
parser.add_argument("endpoint", help="API endpoint (e.g., /issue/PROJ-123)")
parser.add_argument("--data", "-d", help="JSON data for POST/PUT requests")
parser.add_argument("--query", "-q", help="Query parameters (e.g., 'jql=project=AOP')")
args = parser.parse_args()
load_env()
data = None
if args.data:
try:
data = json.loads(args.data)
except json.JSONDecodeError:
print("Error: Invalid JSON data", file=sys.stderr)
sys.exit(1)
result = make_request(args.method, args.endpoint, data, args.query)
print(json.dumps(result, indent=2))
if __name__ == "__main__":
main()
Related skills
FAQ
What Jira version does it support?
Jira Cloud via REST API v3 and JQL.
What credentials does it need?
JIRA_DOMAIN, JIRA_EMAIL, and a JIRA_API_TOKEN set as environment variables.