jira2py
A type-safe Python client for the Jira Cloud REST API v3. Use it to read and search issues, create and edit issues, transition workflows, retrieve changelogs, discover canonical field IDs, and work with comments, attachments, links, worklogs, projects, metadata, users, and saved filters.
Scope
jira2py supports Jira Cloud and Python 3.11+. It does not support Jira Server or Data Center, board/sprint/epic workflows, issue deletion or archiving, or a dedicated issue-assignment API.
Install
pip install jira2py
Authenticate safely
Create an Atlassian API token, then provide your Cloud URL, Atlassian account email, and token. Without credentials_file, each credential uses a non-empty explicit url, username, or api_token argument, then its JIRA_URL, JIRA_USER, or JIRA_API_TOKEN environment variable.
When you supply credentials_file, jira2py first loads and validates it as a complete set: the JSON must contain non-empty url, username, and api_token values. A partial file cannot be completed from explicit arguments or environment variables; after validation, non-empty explicit arguments override their matching file values.
There is no default credentials-file path. Keep tokens out of source control, logs, and error reports; use environment variables or a protected local JSON file instead.
export JIRA_URL="https://your-domain.atlassian.net"
export JIRA_USER="your-email@example.com"
export JIRA_API_TOKEN="your-api-token"
{
"url": "https://your-domain.atlassian.net",
"username": "your-email@example.com",
"api_token": "your-api-token"
}
Pass the JSON file only when needed:
from jira2py import JiraAPI
jira = JiraAPI(credentials_file="./jira-credentials.json")
Choose an API layer
JiraAPIis the low-level, endpoint-oriented interface. Operations return parsed Jira JSON-like data when available; attachment downloads stream to a caller-owned binary destination and return observed bytes.JiraHelpersprovides grouped workflows and readableHelperResultvalues, with optional structured data, for common tasks.format_issueis an optional pure presentation function for an issue response you already retrieved.
Use JiraAPI when you want direct REST payloads and endpoint control. Full issue retrieval is performed only by jira.issues.get_issue():
from jira2py import JiraAPI
jira = JiraAPI()
issue = jira.issues.get_issue("PROJECT-123", fields=["summary", "status"])
results = jira.search.enhanced_search("project = PROJECT AND status = 'In Progress'")
Use format_issue only when you want readable text in addition to that structured response. It does not fetch data or change the response:
from jira2py import JiraAPI
from jira2py.helpers import JiraHelpers, format_issue
api = JiraAPI()
issue = api.issues.get_issue(
"PROJECT-123",
fields=["summary", "status", "description"],
)
print(format_issue(issue, browse_url=f"{api.credentials.url}/browse/{issue['key']}"))
helpers = JiraHelpers(api)
print(helpers.metadata.transitions("PROJECT-123").text)
field_page = helpers.metadata.list_fields("PROJECT", field_types=["custom"])
print(field_page.text) # names plus canonical IDs; one Jira page
print(helpers.attachments.list("PROJECT-123").text)
print(helpers.changelogs.list("PROJECT-123").text)
Structured issue-read migration
Issue reads no longer use a helper or a comma-delimited fields string. Pass one exact selector per sequence item, then optionally format the returned data:
# Before (removed)
from jira2py.helpers import JiraHelpers
helpers = JiraHelpers(api)
api.issues.get_issue("PROJECT-123", fields="summary,status")
helpers.issues.read("PROJECT-123", extra_fields=["customfield_10001"])
# After
issue = api.issues.get_issue(
"PROJECT-123",
fields=["summary", "status", "customfield_10001"],
)
text = format_issue(
issue,
browse_url=f"{api.credentials.url}/browse/{issue['key']}",
)
The supplied selector sequence is forwarded unchanged: jira2py does not add fields, deduplicate selectors, or request an expansion. None omits fields and lets Jira choose its default unless raw extra_params["fields"] overrides it. Wildcards and negative selectors such as "*all" and "-description" can still return broad responses; choose projections deliberately.
Jira account mentions in high-level Markdown
High-level Markdown write helpers recognize Jira account mentions in the canonical form
[~accountId:<account-id>]. Use a real opaque account ID from Jira user discovery,
not a display name:
helpers.comments.add("PROJECT-123", "Please review: [~accountId:557057:User:AbC]")
This applies to issue create/edit descriptions, environment, compatible custom
textarea fields, comment add/update bodies, and worklog add/update comments. The
accountId label is case-insensitive on input, and IDs can contain :. A single
leading backslash escapes a token; malformed tokens and tokens inside Markdown code,
links, or images remain ordinary text rather than creating mentions. Bold, italic, and
strikethrough wrappers around a valid mention create an unmarked Jira mention, so that
formatting is discarded. Jira may notify the mentioned account where it supports
notifications; jira2py does not guarantee delivery.
Formatted ADF reads from format_issue and formatted comment/worklog helper output
present mentions with adf-bridge's identity-preserving [~accountId:<id>] tokens; a
mention's display text is not retained. jira2py does not traverse mentions or rewrite
rendered Markdown, so literal matching tokens, including code spans, remain literal.
Raw ADF in low-level responses and HelperResult.data remains unchanged. Native transition
fields / update mappings continue to accept Jira ADF directly and do not convert
Markdown mentions.
Jira-managed Markdown images
High-level writes can turn an ordinary Markdown image into Jira-managed media when its
destination is the exact HTTPS attachment content URL returned by an upload on the
same issue:
uploaded = helpers.attachments.upload("PROJECT-123", "screen.png")
content_url = uploaded.data[0]["content"]
helpers.issues.edit(
"PROJECT-123",
description=f"Full replacement description\n\n",
)
This applies only to issue edit description, environment, and compatible custom
textarea fields, plus comment add/update bodies and worklog add/update comments. The
attachment must be associated with that issue and have an image/* MIME type. The URL
must not contain a query or fragment delimiter, including bare ? or #. External and
relative image destinations retain ordinary external-media behavior. These methods
replace their complete rich-text value; they do not append Markdown fragments.
Issue creation has no existing issue context, so an attachment-content image URL is
rejected before the create POST. Create the issue first, upload the image to its returned
key, then edit the complete rich-text field using the upload result's content URL.
Markdown image titles are not preserved. Before a managed write, jira2py privately
acquires intrinsic dimensions for PNG/APNG, JPEG, GIF, WebP, BMP, and TIFF; JPEG and
TIFF EXIF orientation is applied. It makes an authenticated, no-follow redirect=false
64-byte Jira-host range probe and performs one complete fallback only when needed. The
fallback is bounded to 100 MiB; a larger attachment whose supported dimensions are found
in the prefix is accepted without a full download. Dimensions must be positive, no more
than 65,535 on either axis, and no more than 89,478,485 total pixels. This is metadata
inspection, not pixel decoding. Unsupported or malformed images and failed acquisition
are pre-write helper errors, so no mutation is sent.
Dimensioned managed media is centered with a 100% parent width and exact intrinsic child width/height. Acquisition occurs once per unique attachment for a single helper mutation; attachment bytes are never persisted. External and relative images remain widthless external media and are never fetched. On formatted ADF-to-Markdown reads, managed media has no recoverable attachment-content URL and is shown only as readable attachment text; use raw ADF when Jira enrichments or exact media structure must be retained. After a successful managed-media write, jira2py verifies the raw persisted ADF structure. Jira-rendered HTML is not a runtime verification contract; corroborate tenant rendering with authorized live end-to-end testing.
jira2py validates the observed Jira attachment 303 redirect shape privately after
acquiring dimensions and before writing. That Media Services mapping is
compatibility-sensitive observed behavior, not a public Jira mapping API. Public attachment
downloads separately stream from the configured Jira endpoint to a caller-owned binary
destination with a required cumulative bound. If a direct issue, comment, or worklog mutation fails with a connection error or Jira 5xx,
it may already have been applied. The helper error sets
details["mutation_may_have_succeeded"]; reread the affected resource before retrying.
jira2py does not retry or roll back after that uncertain failure.
Workflow transitions
Discover the current issue's transitions and transition-screen metadata before choosing the Jira transition ID:
metadata = helpers.metadata.transitions("PROJ-123")
print(metadata.text)
print(metadata.data) # complete Jira transitions envelope, including fields
accepted = helpers.issues.transition(
"PROJ-123",
"31",
fields={"resolution": {"name": "Done"}},
update={"labels": [{"add": "released"}]},
)
assert accepted.data["verified"] is False
fields and update are Jira-native mappings and cannot share an exact field key. The helper's successful result means Jira accepted the request; it does not read the issue afterward. Verify the expected destination status and changed fields with api.issues.get_issue(). Transition names remain supported for compatibility, but IDs from fresh discovery avoid ambiguity.
Documentation
- Installation
- Configuration and credential details
- High-level helpers
- API reference
- Full documentation
- Machine-readable documentation and complete reference
License
Metadata
Release files for jira2py 0.16.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| jira2py-0.16.1.tar.gz | 116.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| jira2py-0.16.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 198.8 kB
Release files / jira2py-0.16.1.tar.gz
| Download URL | jira2py-0.16.1.tar.gz |
|---|---|
| Size | 116.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5d30cc65aa93a7f2f76b6dd3e710bed2edb2b2af8b6bf69401ca87bd147964bf
|
|
BLAKE2b-256 checksum How to use checksums |
c0a69aee24bb30d5cb3a2137b404ca311d6a6162d19f1218daf3bb16b35703ed
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.
Transparency logRelease files / jira2py-0.16.1-py3-none-any.whl
| Download URL | jira2py-0.16.1-py3-none-any.whl |
|---|---|
| Size | 82.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
307fae4f93a777bbcad6bb55470a14669dda23529002e6bfd19ef2d185033e28
|
|
BLAKE2b-256 checksum How to use checksums |
d1d1af503234fbbeb7fff995b0b28f2738c1715e1f58854099697e768562870c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.
Transparency log