Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

JIRA AS

PyPI version Python 3.10+ License: MIT

A Python library and CLI for JIRA REST API automation, providing HTTP client, configuration management, error handling, and utilities for the JIRA Assistant Skills Claude Code plugin.

Installation

pip install jira-as

With optional keyring support for secure credential storage:

pip install jira-as[keyring]

Features

  • CLI (jira-as): Command-line interface for JIRA operations
  • JiraClient: HTTP client with automatic retry logic and exponential backoff
  • ConfigManager: Multi-source configuration (env vars > keychain > settings.local.json > settings.json > defaults)
  • Error Handling: Exception hierarchy mapping HTTP status codes to domain exceptions
  • Validators: Input validation for issue keys, project keys, JQL queries, URLs, and more
  • Formatters: Output formatting for tables, JSON, CSV export
  • ADF Helper: Atlassian Document Format conversion (markdown/text to ADF and back)
  • Time Utils: JIRA time format parsing and formatting (e.g., '2h', '1d 4h 30m')
  • Cache: SQLite-based caching with TTL support for API responses
  • Credential Manager: Secure credential storage via system keychain or JSON fallback
  • Mock Client: Full mock implementation for testing without JIRA access

Quick Start

Configuration

Set environment variables:

export JIRA_API_TOKEN="your-api-token"  # Get from https://id.atlassian.com/manage-profile/security/api-tokens
export JIRA_EMAIL="your-email@company.com"
export JIRA_SITE_URL="https://your-company.atlassian.net"

CLI Usage

# Get an issue
jira-as issue get PROJ-123

# Search issues
jira-as search query "project = PROJ AND status = Open"

# Create an issue
jira-as issue create PROJ --summary "New task" --type Task

# Transition an issue
jira-as lifecycle transition PROJ-123 "In Progress"

# See all commands
jira-as --help

Library Usage

from jira_as import get_jira_client, handle_errors

@handle_errors
def main():
    # Get a configured JIRA client (use as context manager)
    with get_jira_client() as client:
        # Fetch an issue
        issue = client.get_issue('PROJ-123')
        print(f"Summary: {issue['fields']['summary']}")

        # Search issues with JQL
        results = client.search_issues('project = PROJ AND status = Open')
        for issue in results['issues']:
            print(f"{issue['key']}: {issue['fields']['summary']}")

if __name__ == '__main__':
    main()

Core Components

JiraClient

from jira_as import JiraClient

# Direct instantiation (prefer get_jira_client() for config management)
client = JiraClient(
    base_url="https://your-company.atlassian.net",
    email="your-email@company.com",
    api_token="your-api-token"
)

# Use as context manager
with client:
    issue = client.get_issue('PROJ-123')
    client.create_issue(project_key='PROJ', summary='New issue', issue_type='Task')
    client.transition_issue('PROJ-123', 'Done')

Error Handling

from jira_as import (
    JiraError,
    AuthenticationError,
    PermissionError,
    NotFoundError,
    handle_errors
)

@handle_errors
def main():
    # Exceptions are caught and formatted nicely
    pass

# Or handle manually
try:
    with get_jira_client() as client:
        client.get_issue('INVALID-999')
except NotFoundError as e:
    print(f"Issue not found: {e}")
except AuthenticationError as e:
    print(f"Auth failed: {e}")
except JiraError as e:
    print(f"JIRA error: {e}")

Validators

from jira_as import (
    validate_issue_key,
    validate_project_key,
    validate_jql,
    validate_url,
    ValidationError
)

try:
    key = validate_issue_key('PROJ-123')  # Returns 'PROJ-123'
    key = validate_issue_key('invalid')   # Raises ValidationError
except ValidationError as e:
    print(f"Invalid input: {e}")

ADF Helper

from jira_as import (
    markdown_to_adf,
    text_to_adf,
    adf_to_text
)

# Convert markdown to ADF for JIRA
adf = markdown_to_adf("**Bold** and *italic* text")

# Convert plain text to ADF
adf = text_to_adf("Simple text content")

# Extract text from ADF
text = adf_to_text(adf_document)

Time Utils

from jira_as import (
    parse_time_string,
    format_seconds,
    parse_relative_date
)

# Parse JIRA time format to seconds
seconds = parse_time_string('2h 30m')  # 9000

# Format seconds to JIRA time format
time_str = format_seconds(9000)  # '2h 30m'

# Parse relative dates
dt = parse_relative_date('yesterday')
dt = parse_relative_date('2025-01-15')

Mock Mode

For testing without JIRA access:

export JIRA_MOCK_MODE=true
jira-as issue get DEMO-85  # Returns mock data
import os
os.environ['JIRA_MOCK_MODE'] = 'true'

from jira_as import get_jira_client

with get_jira_client() as client:  # Returns MockJiraClient
    issue = client.get_issue('DEMO-85')  # Mock data

Development

# Clone the repository
git clone https://github.com/grandcamel/jira-as.git
cd jira-as

# Install development dependencies
pip install -e ".[dev]"

# Run tests
pytest

# Format code (black formats; ruff sorts imports)
black src tests
ruff check --fix src tests

# Type checking
mypy src

License

MIT License - see LICENSE for details.

Generic Surface

The api group exposes the pinned Jira Cloud platform v3, Jira Software and Jira Service Management documents through as-engine. All three indexes are primary. Start with jira-as help, jira-as api search sprint, or jira-as api describe getIssue. Use help paging, help search, and help agile for tagged operations; --tier platform|software|servicedesk selects one document's help. --full expands descriptions, --examples shows enrichment examples, and long help lists continue with --offset.

jira-as api --transport responder call getIssue --issueIdOrKey SBX-1
jira-as api call getIssue --issue-id-or-key PROJ-1
jira-as api call searchAndReconsileIssuesUsingJql --jql 'project = PROJ' --all --limit 100
jira-as api call searchAndReconsileIssuesUsingJqlPost --body @search.json --all

Parameters accept their exact published names and kebab aliases. Bodies come from --body @file, --body - (stdin), or repeated --field path=value. One page is returned by default. --all merges the tagged collection; its --limit caps the total, while --maxResults or the body maxResults field sets the request page size. For APIs with a limit parameter use --parameter-limit alongside --all. JSON is the default call output; --format table|markdown renders it. Errors are JSON on stderr with status, messages, operation and note; exits distinguish usage (2), authentication (3), permission (4), not found (5), server/transport (6), and conflict (7).

Automatic paging is unsupported for findBulkAssignableUsers, findAssignableUsers, findUsersWithAllPermissions, and findUsersWithBrowsePermission: they filter after slicing, so an empty page does not prove exhaustion. Use getAllUsers plus caller-side filtering. For getAllUsers/getAllUsersDefault, maxResults above 1000 refuses before sending. The supported bare-array endpoints advance by the sent page size and probe until an empty page; a short nonempty page continues.

Discovery and responder mode need no credentials. HTTP calls use the existing JIRA_SITE_URL, JIRA_EMAIL, JIRA_API_TOKEN and configuration chain only when a call is sent. JIRA_AS_TRANSPORT selects http, responder, cassette (with JIRA_AS_CASSETTE), or simulation (optional JIRA_AS_SIMULATION_SEED). JIRA_AS_RECORD records HTTP responses through the shared scrubber. Simulation is stateful for the supported wrapper workflows. Risk-tagged operations preview without sending until --confirm is supplied.

Published operation IDs that collide are corrected in each document's identity.overlay.json; descriptions/notes preserve their original names and routes for search. Platform IDs are unchanged. Other examples include getSoftwareIssue, getBoardConfiguration, getServiceDeskArticles, and getRequestAttachmentContent. These explicit corrections preserve every route.

Project scope

The Generic Surface checks generated scope tags against JIRA_ALLOWED_PROJECTS before sending. An absent allowlist is unrestricted; an empty value denies scoped calls. Body-only identity requires matching --project KEY, including bodies read from files. Keyed updates also check any project change hidden in the body. JQL requires a complete project restriction and supports literal AND predicates. Site-level calls (including numeric board, sprint and service-desk routes) require JIRA_ALLOW_SITE_OPERATIONS=true; the default is false. Discovery and help stay settings-free. See project scope details.

Wrapper migration

JAS-49 retains 35 wrappers that need a workflow, local transform, cache, or autocomplete affordance; 14 compatibility verbs retain their 1.x names. The remaining 143 wrappers are migration hints: invoking one performs no transport, prints the indexed replacement, and exits 2; --help exits 0. Use help migration or the wrapper table for the complete map.

The survivor groups are bulk, lifecycle, fields, ops, relationships, search, time, dev, agile, and JSM. fields list, fields get, and fields cache warm use the v2 cached instance metadata. api call --adf-field customfield_ID explicitly converts Markdown to ADF; a warm textarea-field cache enables the same conversion automatically. Stateful simulation exercises supported survivor workflows without HTTP.

Sixteen commands remain on the legacy client pending JAS-64: admin automation and automation-template, dev get-commits, and JSM asset commands. Jira attachment multipart/binary transport and generic risk enrichment remain pending JAS-65; their migration hints do not claim those capabilities are available.

Destructive operations

Every indexed DELETE and the documented bulk delete, move, archive and removal operations carry a risk level. api call defaults destructive and irreversible operations to a JSON preview with zero requests (exit 0); add --confirm to send the request through the normal scope guard. Inspect the level with api describe OPERATION, and browse irreversible operations with help risk.

For the 2.0.0rc1 upgrade, see the release migration notes and the complete 1.2.0 client method mapping.

Instance fields cache

fields cache warm fetches instance metadata into ~/.cache/jira-as/v2/instance-fields.json, with a 24-hour TTL. Set JIRA_FIELDS_CACHE_DIR (or jira.fields_cache_dir in configuration) to select the directory; use separate directories for different Jira instances. Reads never fetch metadata or migrate a 1.x cache. Missing, expired, or malformed metadata is cold: fields list reports it, and api describe createIssue explains that automatic textarea conversion is inactive. Explicit api call createIssue --adf-field customfield_ID … still works with a cold cache. For an offline warm-up, use fields cache warm --transport responder.

Rich text and notes

Platform v3 description/environment, comment body and worklog comment paths accept Markdown. Use real newlines in UTF-8 files:

jira-as api --transport responder call createIssue \
  --field fields.project.key=SBX --field fields.summary=x \
  --field fields.issuetype.name=Task --field fields.description=@notes.md
jira-as api --transport responder call addComment \
  --issueIdOrKey SBX-1 --field body=@notes.md

Tagged reads render Markdown with lossless placeholders for unsupported ADF nodes; --raw preserves the stored ADF. Use a JSON --body @request.json or stdin for already encoded ADF or an explicit null. Bulk create converts the static paths in each issueUpdates item supplied as JSON. Use --adf-field customfield_ID to convert a selected custom field; a warm instance-field cache also converts textarea custom fields automatically. A cold cache leaves unselected custom fields literal, and pre-encoded ADF passes through unchanged. JSM request fields also pass through unchanged; its explicit isAdfRequest=true mode requires caller-supplied ADF. JSM request comments remain strings.

help adf, help fields, help project-types, help rate-limits and the other topics render source-backed entries. api describe and errors carry relevant notes. Removed issue-search operations name their /search/jql replacements; use api describe searchForIssuesUsingJql or help search. The operationId search continues to mean status discovery. Search hides deprecated operations unless --include-deprecated is supplied; calling one warns on stderr.

Build

The product vendors pristine Base Documents and manifest pins in src/jira_as/specs. The wheel hook keeps the source stamp and compiles all three documents through as-engine into _generated/catalog.json and three indexes. Editable builds persist the same indexes; sdists contain source inputs and the hook, excluding compiled indexes. Nothing is fetched during compilation or runtime. The dependency range is as-engine>=0.1.0a0,<0.2.

To rebuild local indexes after changing an overlay:

python -c "from as_engine.build import compile_product; compile_product('src/jira_as/specs', 'src/jira_as/_generated')"
python scripts/generate_paging_tags.py

Run the generator before compilation when paging changes. Generated paging precedes hand paging overrides in the manifest. Refresh deliberately with python scripts/refresh_base_documents.py --from-file platform=/path/to/document.json (and similarly software or servicedesk). The script records an oasdiff changelog beside each refreshed source and updates manifest pins last. Set OASDIFF to choose the executable. Without --from-file it explicitly fetches the manifest URLs; offline workflows must supply local files.

Compatibility Contract

The fourteen jira-host operations keep their recorded 1.x invocation and output shapes on the generic engine path. The machine-readable contract, capture provenance, scope rules and responder suite are described in Compatibility Contract. Duration input accepts both 2h30m and 2h 30m; issue update --format markdown|text|adf supplements existing description auto-detection.

Cassettes and the live suite

Offline cassette tests replay every Compatibility Contract variant and the Generic Surface with networking disabled. The SBX live suite is gated by --live, supports an offline simulation rehearsal, and creates and cleans up its own disposable issues. See recording, replay and drift checks for the supervisor's host commands and fixture review process.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jira_as-2.0.0rc1.tar.gz (786.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

jira_as-2.0.0rc1-py3-none-any.whl (1.2 MB view details)

Uploaded Python 3

File details

Details for the file jira_as-2.0.0rc1.tar.gz.

File metadata

  • Download URL: jira_as-2.0.0rc1.tar.gz
  • Upload date:
  • Size: 786.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for jira_as-2.0.0rc1.tar.gz
Algorithm Hash digest
SHA256 5cc6118eadd314374d76a5e0013cb5d01de8f10483b5f0253b479b8b5c9157d5
MD5 39a8b514401290b4bceefee8146eb30c
BLAKE2b-256 e88105c57d04c4f24c4a400b3b5056e4dc6eb55a9c9b63bb8984d4f81dc42760

See more details on using hashes here.

Provenance

The following attestation bundles were made for jira_as-2.0.0rc1.tar.gz:

Publisher: publish.yml on grandcamel/jira-as

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file jira_as-2.0.0rc1-py3-none-any.whl.

File metadata

  • Download URL: jira_as-2.0.0rc1-py3-none-any.whl
  • Upload date:
  • Size: 1.2 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for jira_as-2.0.0rc1-py3-none-any.whl
Algorithm Hash digest
SHA256 0b639914dbaf3e3b2ee9f927d690b659cab30370fbf8f719f95f342883910bdc
MD5 2b4a09739e8bb89613e67f572c5e1edd
BLAKE2b-256 964db4dd754ff9a5e2c0427c57e147ce7334ea7117234674a3b8fa9246af2d41

See more details on using hashes here.

Provenance

The following attestation bundles were made for jira_as-2.0.0rc1-py3-none-any.whl:

Publisher: publish.yml on grandcamel/jira-as

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

2.0.0rc1 This release

2 files

1.1.3

2 files

1.1.1

2 files

1.1.0

2 files

1.0.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page