Atlassian API Integration
Integrates with the Atlassian ecosystem — Jira (issue tracking), Confluence (documentation), Bitbucket (Git hosting), Rovo (AI search), and Forge (serverless apps) — using the atlassian-python-api library to automate development workflows across planning, coding, and documentation.
TL;DR for Code Generation
- Use
Jira, Confluence, and Bitbucket classes from atlassian-python-api v4.0+
- Authenticate with personal access tokens (cloud) or basic auth (server) — prefer PATs in production
- Jira uses JQL for issue queries; Confluence uses CQL for content search
- Paginate with
start/limit parameters (Jira) or cursor-based paging (Confluence cloud)
- Use Jira's
update method with field dicts for partial updates — avoid full object replacement
- Handle
requests.exceptions.HTTPError for API errors, differentiate 401 (auth), 403 (permissions), 404 (not found)
When to Use
Use this skill when:
- Creating, querying, updating, or transitioning Jira issues
- Fetching and updating Confluence pages, attachments, and spaces
- Automating Bitbucket pull request reviews and repository management
- Building Forge apps that extend Atlassian products
- Synchronizing Jira issues with external databases or spreadsheets
- Generating release notes from Jira issues and Confluence pages
- Cross-referencing code commits (Bitbucket) with tickets (Jira) in automated pipelines
When NOT to Use
- Real-time issue updates (use Jira webhooks or Forge event handlers instead of polling)
- Bulk-importing thousands of issues in a single request — batch in chunks of 50
- Replacing Bitbucket Pipelines for CI/CD (use the pipeline API only for triggers/status)
- Accessing Jira Server with expired certificates or unsupported TLS versions
Core Workflow
Choose Authentication Method — For Jira Cloud, generate an API token from https://id.atlassian.com/manage/api-tokens. For Confluence Cloud, use the same token with the appropriate subdomain. Checkpoint: Verify the token works with jira.get_user().
Initialize the Client — Instantiate Jira(url=..., token=...) or Confluence(url=..., token=...). Always specify the full instance URL (e.g., https://your-domain.atlassian.net). Checkpoint: Call jira.get_project(project_key) to validate connectivity.
Construct JQL or CQL Query — For Jira, build a JQL string with project, status, assignee, and date filters. For Confluence, use CQL for space/page filtering. Checkpoint: Run the query with limit=1 first to confirm syntax.
Execute and Paginate — Use jira.jql(query, start=0, limit=50) and check the total field. Increment start by limit until all results are consumed. Checkpoint: Verify the issues list is non-empty and contains expected fields.
Process Results — Iterate over issues or pages. Map fields to your data model. For updates, use jira.update_issue_field(issue_key, fields=...) with a dict of only changed fields. Checkpoint: Confirm the update by re-fetching the issue.
Handle Errors — Wrap API calls in try/except requests.exceptions.HTTPError. For 429 (rate limit), parse Retry-After header and wait. For 401, refresh credentials. Checkpoint: Log structured error output with request ID.
Implementation Patterns
Pattern 1: Query Jira Issues with JQL
import os
from atlassian import Jira
jira = Jira(
url="https://your-domain.atlassian.net",
token=os.environ["ATLASSIAN_API_TOKEN"],
)
def find_open_bugs(project_key: str, max_results: int = 100) -> list[dict]:
"""Find open bug tickets in a project, ordered by priority."""
jql = (
f'project = "{project_key}" '
f'AND issuetype = Bug '
f'AND status NOT IN (Done, Closed, Resolved) '
f'ORDER BY priority DESC, created ASC'
)
issues = []
start = 0
while True:
results = jira.jql(jql, start=start, limit=50)
issues.extend(results.get("issues", []))
if start + 50 >= results.get("total", 0):
break
start += 50
return issues
bugs = find_open_bugs("PROJ")
for issue in bugs:
key = issue["key"]
summary = issue["fields"]["summary"]
priority = issue["fields"]["priority"]["name"]
print(f"[{priority}] {key}: {summary}")
Pattern 2: Create a Confluence Page
from atlassian import Confluence
confluence = Confluence(
url="https://your-domain.atlassian.net/wiki",
token=os.environ["ATLASSIAN_API_TOKEN"],
)
def publish_release_notes(space_key: str, parent_id: int | None, title: str, body_html: str) -> dict:
"""Create or update a Confluence page with release notes."""
status = confluence.create_page(
space=space_key,
title=title,
body=body_html,
parent_id=parent_id,
representation="storage",
)
return status
# HTML body in Confluence Storage Format
html_content = (
"<h1>Release v2.5.0</h1>"
"<ul><li>PROJ-123: Fixed login timeout</li>"
"<li>PROJ-456: Added export feature</li></ul>"
)
publish_release_notes("ENG", 987654, "Release v2.5.0 Notes", html_content)
Pattern 3: Transition a Jira Issue
def transition_issue(issue_key: str, target_status: str) -> bool:
"""Transition a Jira issue to the target status by resolution name."""
transitions = jira.get_transitions(issue_key)
target_id = None
for t in transitions.get("transitions", []):
if t["name"].lower() == target_status.lower():
target_id = t["id"]
break
if not target_id:
print(f"Transition '{target_status}' not available for {issue_key}")
return False
jira.transition_issue(issue_key, target_id)
return True
transition_issue("PROJ-789", "In Review")
Pattern 4: BAD vs GOOD — Updating Issues
# ❌ BAD — fetches entire issue only to update one field
issue = jira.get_issue("PROJ-123")
issue["fields"]["description"] = "Updated description"
jira.update_issue("PROJ-123", issue)
# ✅ GOOD — targeted field update, minimal payload
jira.update_issue_field("PROJ-123", fields={"description": "Updated description"})
Pattern 5: BAD vs GOOD — Pagination
# ❌ BAD — no pagination, assumes all results fit in one page
results = jira.jql("project = PROJ ORDER BY key")
all_issues = results["issues"]
# ✅ GOOD — paginated loop respecting total count
def get_all_issues(jql_query: str, batch_size: int = 50) -> list[dict]:
"""Paginate through all JQL results."""
collected = []
start = 0
while True:
page = jira.jql(jql_query, start=start, limit=batch_size)
collected.extend(page.get("issues", []))
total = page.get("total", 0)
if start + batch_size >= total:
break
start += batch_size
return collected
Constraints
MUST DO
- Use API tokens over passwords for all Atlassian Cloud instances
- Always paginate JQL and CQL results with
start/limit or cursor-based paging
- Use
jira.update_issue_field() for partial updates — never send full issue objects
- Handle 429 rate limits with exponential backoff and
Retry-After header parsing
MUST NOT DO
- Commit API tokens to version control — use environment variables or secrets manager
- Assume Jira issue IDs are sequential or predictable
- Use Confluence Storage Format for simple content (use
representation="wiki" instead)
- Run JQL queries without a
project filter — can scan entire instance
Output Template
Every integration function should expose:
- Authentication —
Jira(url, token) or Confluence(url, token) instantiation
- Query/Command — JQL/CQL string or targeted method call
- Pagination — Loop with
start/limit for Jira or cursor for Confluence cloud
- Data Mapping — Extract fields from Atlassian response dicts to your domain model
- Error Handling —
try/except requests.exceptions.HTTPError with status-specific recovery
Related Skills
| Skill | Purpose |
|
1---2name: atlassian-api3description: Integrates with Atlassian suite (Jira, Confluence, Bitbucket, Rovo, Forge) using atlassian-python-api to automate issue tracking, documentation, and code management.4license: MIT5---67891011# Atlassian API Integration1213Integrates with the Atlassian ecosystem — Jira (issue tracking), Confluence (documentation), Bitbucket (Git hosting), Rovo (AI search), and Forge (serverless apps) — using the `atlassian-python-api` library to automate development workflows across planning, coding, and documentation.1415## TL;DR for Code Generation1617- Use `Jira`, `Confluence`, and `Bitbucket` classes from `atlassian-python-api` v4.0+18- Authenticate with personal access tokens (cloud) or basic auth (server) — prefer PATs in production19- Jira uses JQL for issue queries; Confluence uses CQL for content search20- Paginate with `start`/`limit` parameters (Jira) or `cursor`-based paging (Confluence cloud)21- Use Jira's `update` method with field dicts for partial updates — avoid full object replacement22- Handle `requests.exceptions.HTTPError` for API errors, differentiate 401 (auth), 403 (permissions), 404 (not found)2324## When to Use2526Use this skill when:2728- Creating, querying, updating, or transitioning Jira issues29- Fetching and updating Confluence pages, attachments, and spaces30- Automating Bitbucket pull request reviews and repository management31- Building Forge apps that extend Atlassian products32- Synchronizing Jira issues with external databases or spreadsheets33- Generating release notes from Jira issues and Confluence pages34- Cross-referencing code commits (Bitbucket) with tickets (Jira) in automated pipelines3536## When NOT to Use3738- Real-time issue updates (use Jira webhooks or Forge event handlers instead of polling)39- Bulk-importing thousands of issues in a single request — batch in chunks of 5040- Replacing Bitbucket Pipelines for CI/CD (use the pipeline API only for triggers/status)41- Accessing Jira Server with expired certificates or unsupported TLS versions4243## Core Workflow44451. **Choose Authentication Method** — For Jira Cloud, generate an API token from `https://id.atlassian.com/manage/api-tokens`. For Confluence Cloud, use the same token with the appropriate subdomain. **Checkpoint:** Verify the token works with `jira.get_user()`.46472. **Initialize the Client** — Instantiate `Jira(url=..., token=...)` or `Confluence(url=..., token=...)`. Always specify the full instance URL (e.g., `https://your-domain.atlassian.net`). **Checkpoint:** Call `jira.get_project(project_key)` to validate connectivity.48493. **Construct JQL or CQL Query** — For Jira, build a JQL string with project, status, assignee, and date filters. For Confluence, use CQL for space/page filtering. **Checkpoint:** Run the query with `limit=1` first to confirm syntax.50514. **Execute and Paginate** — Use `jira.jql(query, start=0, limit=50)` and check the `total` field. Increment `start` by `limit` until all results are consumed. **Checkpoint:** Verify the `issues` list is non-empty and contains expected fields.52535. **Process Results** — Iterate over issues or pages. Map `fields` to your data model. For updates, use `jira.update_issue_field(issue_key, fields=...)` with a dict of only changed fields. **Checkpoint:** Confirm the update by re-fetching the issue.54556. **Handle Errors** — Wrap API calls in `try/except requests.exceptions.HTTPError`. For 429 (rate limit), parse `Retry-After` header and wait. For 401, refresh credentials. **Checkpoint:** Log structured error output with request ID.5657## Implementation Patterns5859### Pattern 1: Query Jira Issues with JQL6061```python62import os63from atlassian import Jira6465jira = Jira(66 url="https://your-domain.atlassian.net",67 token=os.environ["ATLASSIAN_API_TOKEN"],68)6970def find_open_bugs(project_key: str, max_results: int = 100) -> list[dict]:71 """Find open bug tickets in a project, ordered by priority."""72 jql = (73 f'project = "{project_key}" '74 f'AND issuetype = Bug '75 f'AND status NOT IN (Done, Closed, Resolved) '76 f'ORDER BY priority DESC, created ASC'77 )78 issues = []79 start = 080 while True:81 results = jira.jql(jql, start=start, limit=50)82 issues.extend(results.get("issues", []))83 if start + 50 >= results.get("total", 0):84 break85 start += 5086 return issues8788bugs = find_open_bugs("PROJ")89for issue in bugs:90 key = issue["key"]91 summary = issue["fields"]["summary"]92 priority = issue["fields"]["priority"]["name"]93 print(f"[{priority}] {key}: {summary}")94```9596### Pattern 2: Create a Confluence Page9798```python99from atlassian import Confluence100101confluence = Confluence(102 url="https://your-domain.atlassian.net/wiki",103 token=os.environ["ATLASSIAN_API_TOKEN"],104)105106def publish_release_notes(space_key: str, parent_id: int | None, title: str, body_html: str) -> dict:107 """Create or update a Confluence page with release notes."""108 status = confluence.create_page(109 space=space_key,110 title=title,111 body=body_html,112 parent_id=parent_id,113 representation="storage",114 )115 return status116117# HTML body in Confluence Storage Format118html_content = (119 "<h1>Release v2.5.0</h1>"120 "<ul><li>PROJ-123: Fixed login timeout</li>"121 "<li>PROJ-456: Added export feature</li></ul>"122)123publish_release_notes("ENG", 987654, "Release v2.5.0 Notes", html_content)124```125126### Pattern 3: Transition a Jira Issue127128```python129def transition_issue(issue_key: str, target_status: str) -> bool:130 """Transition a Jira issue to the target status by resolution name."""131 transitions = jira.get_transitions(issue_key)132 target_id = None133 for t in transitions.get("transitions", []):134 if t["name"].lower() == target_status.lower():135 target_id = t["id"]136 break137 if not target_id:138 print(f"Transition '{target_status}' not available for {issue_key}")139 return False140 jira.transition_issue(issue_key, target_id)141 return True142143transition_issue("PROJ-789", "In Review")144```145146### Pattern 4: BAD vs GOOD — Updating Issues147148```python149# ❌ BAD — fetches entire issue only to update one field150issue = jira.get_issue("PROJ-123")151issue["fields"]["description"] = "Updated description"152jira.update_issue("PROJ-123", issue)153154# ✅ GOOD — targeted field update, minimal payload155jira.update_issue_field("PROJ-123", fields={"description": "Updated description"})156```157158### Pattern 5: BAD vs GOOD — Pagination159160```python161# ❌ BAD — no pagination, assumes all results fit in one page162results = jira.jql("project = PROJ ORDER BY key")163all_issues = results["issues"]164165# ✅ GOOD — paginated loop respecting total count166def get_all_issues(jql_query: str, batch_size: int = 50) -> list[dict]:167 """Paginate through all JQL results."""168 collected = []169 start = 0170 while True:171 page = jira.jql(jql_query, start=start, limit=batch_size)172 collected.extend(page.get("issues", []))173 total = page.get("total", 0)174 if start + batch_size >= total:175 break176 start += batch_size177 return collected178```179180## Constraints181182### MUST DO183- Use API tokens over passwords for all Atlassian Cloud instances184- Always paginate JQL and CQL results with `start`/`limit` or cursor-based paging185- Use `jira.update_issue_field()` for partial updates — never send full issue objects186- Handle 429 rate limits with exponential backoff and `Retry-After` header parsing187188### MUST NOT DO189- Commit API tokens to version control — use environment variables or secrets manager190- Assume Jira issue IDs are sequential or predictable191- Use Confluence Storage Format for simple content (use `representation="wiki"` instead)192- Run JQL queries without a `project` filter — can scan entire instance193194## Output Template195196Every integration function should expose:1971981. **Authentication** — `Jira(url, token)` or `Confluence(url, token)` instantiation1992. **Query/Command** — JQL/CQL string or targeted method call2003. **Pagination** — Loop with `start`/`limit` for Jira or cursor for Confluence cloud2014. **Data Mapping** — Extract fields from Atlassian response dicts to your domain model2025. **Error Handling** — `try/except requests.exceptions.HTTPError` with status-specific recovery203204## Related Skills205206| Skill | Purpose |207|