Jira Cloud REST API via curl. Use this skill to create, update, search, and manage issues, projects, and workflows in Jira.
/plugin marketplace add vm0-ai/api0/plugin install api0@api0This skill inherits all available tools. When active, it can use any tool Claude has access to.
Use the Jira Cloud REST API via direct curl calls to manage issues, projects, and workflows.
Official docs:
https://developer.atlassian.com/cloud/jira/platform/rest/v3/
Use this skill when you need to:
export JIRA_DOMAIN="mycompany" # e.g., "mycompany" or "mycompany.atlassian.net"
export JIRA_EMAIL="you@example.com" # Your Atlassian account email
export JIRA_API_TOKEN="your-api-token" # API token from step 2
Jira Cloud has rate limits that vary by endpoint. For most REST API calls, expect limits around 100-500 requests per minute.
Important: When using
$VARin a command that pipes to another command, wrap the command containing$VARinbash -c '...'. Due to a Claude Code bug, environment variables are silently cleared when pipes are used directly.bash -c 'curl -s "https://api.example.com" -H "Authorization: Bearer $API_KEY"' | jq .
All examples below assume JIRA_DOMAIN, JIRA_EMAIL, and JIRA_API_TOKEN are set.
Base URL: https://${JIRA_DOMAIN%.atlassian.net}.atlassian.net/rest/api/3
Note:
${JIRA_DOMAIN%.atlassian.net}strips the suffix if present, so bothmycompanyandmycompany.atlassian.network.
Verify your authentication:
bash -c 'curl -s -X GET "https://${JIRA_DOMAIN%.atlassian.net}.atlassian.net/rest/api/3/myself" -u "${JIRA_EMAIL}:${JIRA_API_TOKEN}" --header "Accept: application/json"' | jq '{accountId, emailAddress, displayName, active}
Get all projects you have access to:
bash -c 'curl -s -X GET "https://${JIRA_DOMAIN%.atlassian.net}.atlassian.net/rest/api/3/project" -u "${JIRA_EMAIL}:${JIRA_API_TOKEN}" --header "Accept: application/json"' | jq '.[] | {id, key, name}
Get details for a specific project:
PROJECT_KEY="PROJ"
bash -c 'curl -s -X GET "https://${JIRA_DOMAIN%.atlassian.net}.atlassian.net/rest/api/3/project/${PROJECT_KEY}" -u "${JIRA_EMAIL}:${JIRA_API_TOKEN}" --header "Accept: application/json"' | jq '{id, key, name, projectTypeKey, lead: .lead.displayName}
List available issue types (Task, Bug, Story, etc.):
PROJECT_KEY="PROJ"
bash -c 'curl -s -X GET "https://${JIRA_DOMAIN%.atlassian.net}.atlassian.net/rest/api/3/project/${PROJECT_KEY}" -u "${JIRA_EMAIL}:${JIRA_API_TOKEN}" --header "Accept: application/json"' | jq '.issueTypes[] | {id, name, subtask}
Search issues using Jira Query Language:
bash -c 'curl -s -G "https://${JIRA_DOMAIN%.atlassian.net}.atlassian.net/rest/api/3/search/jql" -u "${JIRA_EMAIL}:${JIRA_API_TOKEN}" --header "Accept: application/json" --data-urlencode "jql=project = PROJ AND status != Done ORDER BY created DESC" --data-urlencode "maxResults=10" --data-urlencode "fields=key,summary,status,assignee,priority"' | jq '.issues[] | {key, summary: .fields.summary, status: .fields.status.name, assignee: .fields.assignee.displayName, priority: .fields.priority.name}
Common JQL examples:
project = PROJ - Issues in projectassignee = currentUser() - Your issuesstatus = "In Progress" - By statuscreated >= -7d - Created in last 7 dayslabels = bug - By labelpriority = High - By priorityGet full details of an issue:
ISSUE_KEY="PROJ-123"
bash -c 'curl -s -X GET "https://${JIRA_DOMAIN%.atlassian.net}.atlassian.net/rest/api/3/issue/${ISSUE_KEY}" -u "${JIRA_EMAIL}:${JIRA_API_TOKEN}" --header "Accept: application/json"' | jq '{key, summary: .fields.summary, status: .fields.status.name, assignee: .fields.assignee.displayName, priority: .fields.priority.name, created: .fields.created, updated: .fields.updated}
Create a new issue (API v3 uses Atlassian Document Format for description):
bash -c 'curl -s -X POST "https://${JIRA_DOMAIN%.atlassian.net}.atlassian.net/rest/api/3/issue" -u "${JIRA_EMAIL}:${JIRA_API_TOKEN}" --header "Accept: application/json" --header "Content-Type: application/json" -d '"'"'{
"fields": {
"project": {"key": "PROJ"},
"summary": "Bug: Login button not responding",
"description": {
"type": "doc",
"version": 1,
"content": [
{
"type": "paragraph",
"content": [
{"type": "text", "text": "The login button on the mobile app is not responding when tapped."}
]
}
]
},
"issuetype": {"name": "Bug"}
}
}'"'"' | jq '"'"'{id, key, self}'"'"''
Create issue with additional fields:
bash -c 'curl -s -X POST "https://${JIRA_DOMAIN%.atlassian.net}.atlassian.net/rest/api/3/issue" -u "${JIRA_EMAIL}:${JIRA_API_TOKEN}" --header "Accept: application/json" --header "Content-Type: application/json" -d '"'"'{
"fields": {
"project": {"key": "PROJ"},
"summary": "Implement user authentication",
"description": {
"type": "doc",
"version": 1,
"content": [
{
"type": "paragraph",
"content": [
{"type": "text", "text": "Add OAuth2 authentication flow for the mobile app."}
]
}
]
},
"issuetype": {"name": "Story"},
"priority": {"name": "High"},
"labels": ["backend", "security"]
}
}'"'"' | jq '"'"'{id, key, self}'"'"''
Update an existing issue:
ISSUE_KEY="PROJ-123"
curl -s -X PUT "https://${JIRA_DOMAIN%.atlassian.net}.atlassian.net/rest/api/3/issue/${ISSUE_KEY}" -u "${JIRA_EMAIL}:${JIRA_API_TOKEN}" --header "Accept: application/json" --header "Content-Type: application/json" -d '{
"fields": {
"summary": "Updated: Login button not responding on iOS",
"priority": {"name": "Highest"},
"labels": ["bug", "ios", "urgent"]
}
}'
Returns 204 No Content on success.
Get possible status transitions for an issue:
ISSUE_KEY="PROJ-123"
bash -c 'curl -s -X GET "https://${JIRA_DOMAIN%.atlassian.net}.atlassian.net/rest/api/3/issue/${ISSUE_KEY}/transitions" -u "${JIRA_EMAIL}:${JIRA_API_TOKEN}" --header "Accept: application/json"' | jq '.transitions[] | {id, name, to: .to.name}
Move issue to a different status:
ISSUE_KEY="PROJ-123"
TRANSITION_ID="31" # Get from transitions endpoint
curl -s -X POST "https://${JIRA_DOMAIN%.atlassian.net}.atlassian.net/rest/api/3/issue/${ISSUE_KEY}/transitions" -u "${JIRA_EMAIL}:${JIRA_API_TOKEN}" --header "Accept: application/json" --header "Content-Type: application/json" -d "{\"transition\": {\"id\": \"${TRANSITION_ID}\"}}"
Returns 204 No Content on success.
Add a comment to an issue:
ISSUE_KEY="PROJ-123"
bash -c 'curl -s -X POST "https://${JIRA_DOMAIN%.atlassian.net}.atlassian.net/rest/api/3/issue/${ISSUE_KEY}/comment" -u "${JIRA_EMAIL}:${JIRA_API_TOKEN}" --header "Accept: application/json" --header "Content-Type: application/json" -d '"'"'{
"body": {
"type": "doc",
"version": 1,
"content": [
{
"type": "paragraph",
"content": [
{"type": "text", "text": "Investigated and found the root cause. Working on a fix."}
]
}
]
}
}'"'"' | jq '"'"'{id, created, author: .author.displayName}'"'"''
List all comments on an issue:
ISSUE_KEY="PROJ-123"
bash -c 'curl -s -X GET "https://${JIRA_DOMAIN%.atlassian.net}.atlassian.net/rest/api/3/issue/${ISSUE_KEY}/comment" -u "${JIRA_EMAIL}:${JIRA_API_TOKEN}" --header "Accept: application/json"' | jq '.comments[] | {id, author: .author.displayName, created, body: .body.content[0].content[0].text}
Assign an issue to a user:
ISSUE_KEY="PROJ-123"
ACCOUNT_ID="5b10ac8d82e05b22cc7d4ef5" # Get from /rest/api/3/user/search
curl -s -X PUT "https://${JIRA_DOMAIN%.atlassian.net}.atlassian.net/rest/api/3/issue/${ISSUE_KEY}/assignee" -u "${JIRA_EMAIL}:${JIRA_API_TOKEN}" --header "Accept: application/json" --header "Content-Type: application/json" -d "{\"accountId\": \"${ACCOUNT_ID}\"}"
Find users by email or name:
bash -c 'curl -s -G "https://${JIRA_DOMAIN%.atlassian.net}.atlassian.net/rest/api/3/user/search" -u "${JIRA_EMAIL}:${JIRA_API_TOKEN}" --header "Accept: application/json" --data-urlencode "query=john"' | jq '.[] | {accountId, displayName, emailAddress}
Delete an issue (use with caution):
ISSUE_KEY="PROJ-123"
curl -s -X DELETE "https://${JIRA_DOMAIN%.atlassian.net}.atlassian.net/rest/api/3/issue/${ISSUE_KEY}" -u "${JIRA_EMAIL}:${JIRA_API_TOKEN}"
Returns 204 No Content on success.
Jira API v3 uses ADF for rich text fields like description and comment.body. Basic structure:
{
"type": "doc",
"version": 1,
"content": [
{
"type": "paragraph",
"content": [
{"type": "text", "text": "Plain text"},
{"type": "text", "text": "Bold text", "marks": [{"type": "strong"}]},
{"type": "text", "text": "Code", "marks": [{"type": "code"}]}
]
},
{
"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"}]}]}
]
},
{
"type": "codeBlock",
"attrs": {"language": "python"},
"content": [{"type": "text", "text": "print('hello')"}]
}
]
}
startAt and maxResults for large result sets