Jira REST API Client
This skill documents the REST API fallback tier for the Jira integration. When the
Atlassian MCP plugin is unavailable, commands use the jira_client.py script to
interact with Jira directly via REST API v2 — except for issue search, which uses
the v3 /search/jql endpoint after Atlassian removed the v2/v3 /search routes
(HTTP 410, see CHANGE-2046).
Architecture
Phase 0 Cascade:
1. Try MCP → JIRA_MODE = "MCP"
2. Try REST → JIRA_MODE = "REST"
3. Fallback → JIRA_MODE = "OFFLINE"
Agents are completely insulated — they produce identical output regardless of JIRA_MODE. Only commands and this script interact with the transport layer.
Script Location
Locate the script before invoking:
1. Glob: **/agent-team-creator/scripts/jira_client.py
2. Fallback: ~/.claude/plugins/agent-team-creator/scripts/jira_client.py
3. Not found → REST mode unavailable, fall to OFFLINE
Invocation Syntax
python3 {SCRIPT_PATH} \
--action {action-name} \
--config .claude/jira-rest-config.json \
[--issue-key PROJ-123] \
[--project PROJ] \
[--query "search term"] \
[--payload-file .claude/tmp/jira-payload.json] \
[--file-path /abs/path/to/file] \
[--no-cascade]
Credentials are ALWAYS read from the config file. Never pass tokens as CLI arguments.
Action Reference
| Action | Required Flags | Optional Flags | Returns |
|---|---|---|---|
verify-auth |
--config |
— | {ok, email, displayName, accountId} |
get-current-user |
--config |
— | {ok, email, displayName, accountId} (clearer-named alias for verify-auth) |
get-projects |
--config |
--query |
{ok, projects: [{key,name,id}]} |
search-issues |
--config --payload-file |
— | {ok, issues: [{key,summary,status,created}], total, nextPageToken?} |
get-issue-types |
--config --project |
— | {ok, issueTypes: [{name,id,subtask}]} |
create-issue |
--config --payload-file |
— | {ok, key, url} |
update-issue |
--config --issue-key --payload-file |
— | {ok, key, url, updated: [field-names]} |
delete-issue |
--config --issue-key |
--no-cascade |
{ok, key, deleted, cascade} |
attach-file |
--config --issue-key --file-path |
— | {ok, key, attachmentId, filename, size} |
get-issue |
--config --issue-key |
— | {ok, key, summary, description, status, comments} |
add-comment |
--config --issue-key --payload-file |
— | {ok, commentId} |
get-accessible-resources |
--config |
— | {ok, baseUrl} (alias for verify-auth) |
Notes on search-issues
- Endpoint:
POST /rest/api/3/search/jql(the v2/v3/searchroutes were removed by Atlassian). totalis an approximate count fetched from/rest/api/3/search/approximate-countin a separate non-fatal call. If that secondary call fails,totalfalls back tolen(issues)so the search still returns useful data.- Pagination is token-based, not offset-based. When more results exist beyond
maxResults, the response includesnextPageToken; pass it back in the next payload'snextPageTokenfield to fetch the next page. There is nostartAt. - Default
maxResultsis5; payload may override (Jira caps at 100).
Payload pattern: create-issue
{
"project_key": "GCI",
"issue_type": "Task",
"summary": "Short title",
"description": "Markdown body. Converted to wiki markup automatically.",
"labels": ["claude-code", "automation"],
"priority": "Medium",
"assignee_account_id": "712020:abc-...",
"parent_key": "GCI-40"
}
project_key,issue_type,summaryare required; everything else is optional.priorityaccepts the name as it appears in Jira (e.g.Highest,High,Medium,Low,Lowest, or custom names likeP0–P3if defined).assignee_account_idmust be an Atlassian accountId (get yours viaget-current-user).parent_keymakes the new issue a child. Pair withissue_type: "Subtask"when the project's subtask type is namedSubtask(callget-issue-types --project KEYto confirm the exact name).
Payload pattern: update-issue
{
"summary": "New title (optional)",
"description": "New markdown body (optional)",
"labels": ["replaces", "existing", "labels"],
"priority": "Low",
"assignee_account_id": "712020:abc-...",
"fields": { "duedate": "2026-12-31" }
}
- All top-level keys are optional but at least one must be set.
labelsreplaces the existing label list (Jira's PUT semantics).- Set
assignee_account_idtonullto unassign. fieldsis a raw escape hatch: any key/value here is merged into the Jirafieldsobject verbatim. Use for fields the script doesn't model (e.g.,duedate,customfield_10001).
Invocation pattern: attach-file
python3 {SCRIPT_PATH} \
--action attach-file \
--config .claude/jira-rest-config.json \
--issue-key GCI-40 \
--file-path /abs/path/to/report.md
- No payload file. The file is uploaded as
multipart/form-data. - Endpoint sends header
X-Atlassian-Token: no-check(required by Atlassian for attachments). - Markdown attachments display inline in the Jira UI; logs/screenshots/PDFs are linked.
- Multiple files require multiple invocations.
Invocation pattern: delete-issue
python3 {SCRIPT_PATH} \
--action delete-issue \
--config .claude/jira-rest-config.json \
--issue-key GCI-40 \
[--no-cascade]
- Default cascades subtask deletion (
?deleteSubtasks=true). --no-cascademakes Jira refuse to delete an issue that has subtasks.- This is irreversible — commands should confirm with the user first.
Notes on search-issues
- Endpoint:
POST /rest/api/3/search/jql(the v2/v3/searchroutes were removed by Atlassian). totalis an approximate count fetched from/rest/api/3/search/approximate-countin a separate non-fatal call. If that secondary call fails,totalfalls back tolen(issues)so the search still returns useful data.- Pagination is token-based, not offset-based. When more results exist beyond
maxResults, the response includesnextPageToken; pass it back in the next payload'snextPageTokenfield to fetch the next page. There is nostartAt. - Default
maxResultsis5; payload may override (Jira caps at 100).
Exit Codes
| Code | Meaning | Command Recovery |
|---|---|---|
| 0 | Success | Parse stdout JSON |
| 1 | Auth failure | Fall to OFFLINE, suggest re-running credential setup |
| 2 | Validation error | Fix payload/config, retry |
| 3 | Network error | Fall to OFFLINE |
| 4 | Jira API error | Show error message, fall to OFFLINE |
Credential Configuration
Config file: .claude/jira-rest-config.json (MUST be in .gitignore)
{
"baseUrl": "https://your-site.atlassian.net",
"email": "your-email@company.com",
"apiToken": "your-api-token",
"configuredAt": "2026-02-24T10:00:00Z"
}
Generate API tokens at: https://id.atlassian.com/manage-profile/security/api-tokens
Security: API tokens have full account scope. The config file must NEVER be committed.
MCP-to-REST Parameter Mapping
| MCP Parameter | REST Equivalent | Notes |
|---|---|---|
cloudId |
(not needed) | REST uses baseUrl from config |
issueIdOrKey |
--issue-key |
Same value |
commentBody |
body field in payload file |
Written as JSON file |
jql |
jql field in payload file |
Written as JSON file |
maxResults |
maxResults in payload |
Part of payload JSON |
projectKey |
--project or project_key in payload |
Depends on action |
searchString |
--query |
URL-encoded by script |
Response Normalization
MCP and REST return different structures. Commands must normalize:
| Data | MCP Path | REST Script Path |
|---|---|---|
| Summary | fields.summary |
summary |
| Description | fields.description |
description |
| Status | fields.status.name |
status |
| Comments | fields.comment.comments[] |
comments[] |
| Comment author | comments[].author.displayName |
comments[].author |
| Comment body | comments[].body |
comments[].body |
URL Construction
When JIRA_MODE = REST, construct issue URLs from the config:
{baseUrl}/browse/{issueKey}
Do NOT use MCP site resolution for URL construction in REST mode.
Temp File Pattern
For actions requiring --payload-file:
- Create dir:
.claude/tmp/(if needed) - Write JSON:
.claude/tmp/jira-payload.json - Invoke script
- Parse stdout
- Delete temp file (command responsibility)
Wiki Markup
REST API v2 uses Jira wiki markup (not markdown, not ADF) for description and
comment body fields. The script converts markdown to wiki markup automatically.
See references/wiki-markup-guide.md for the full translation table.
Rate Limiting
Jira Cloud: ~100 requests/minute per user. The script handles 429 responses automatically
(parses Retry-After, sleeps, retries up to 2x). Commands typically make <10 requests
per invocation.
Source: Cpicon/claude-code-plugins — distributed by TomeVault.