Skip to content

Confluence: API Automation with Python

Install the Atlassian Python API library:

Terminal window
pip install atlassian-python-api

Or using uv:

Terminal window
uv pip install atlassian-python-api

Set up environment variables for authentication:

Terminal window
export CONFLUENCE_URL="https://your-domain.atlassian.net"
export CONFLUENCE_EMAIL="your-email@example.com"
export CONFLUENCE_API_TOKEN="your-api-token"

You can generate an API token from: https://id.atlassian.com/manage-profile/security/api-tokens

import os
from atlassian import Confluence
# Initialize Confluence client
confluence = Confluence(
url=os.environ["CONFLUENCE_URL"],
username=os.environ["CONFLUENCE_EMAIL"],
password=os.environ["CONFLUENCE_API_TOKEN"],
cloud=True # Set to False for Confluence Server
)
# Test connection
try:
user = confluence.get_current_user()
print(f"Connected as: {user['displayName']}")
except Exception as e:
print(f"Authentication failed: {e}")
import os
from atlassian import Confluence
confluence = Confluence(
url=os.environ["CONFLUENCE_URL"],
username=os.environ["CONFLUENCE_EMAIL"],
password=os.environ["CONFLUENCE_API_TOKEN"],
cloud=True
)
def create_page(space_key, title, body, parent_id=None):
"""
Create a new Confluence page
Args:
space_key: The space key (e.g., "TEAM", "DOCS")
title: Page title
body: Page content in Confluence storage format (HTML-like)
parent_id: Optional parent page ID to nest under
Returns:
Created page ID
"""
try:
page = confluence.create_page(
space=space_key,
title=title,
body=body,
parent_id=parent_id,
type='page',
representation='storage'
)
print(f"Created page: {page['_links']['webui']}")
return page['id']
except Exception as e:
print(f"Failed to create page: {e}")
return None
# Example usage
space = "TEAM"
title = "Getting Started with Python"
body = """
<h1>Introduction</h1>
<p>This page covers Python basics for our team.</p>
<h2>Key Concepts</h2>
<ul>
<li>Variables and data types</li>
<li>Functions and modules</li>
<li>Error handling</li>
</ul>
"""
page_id = create_page(space, title, body)
def update_page(page_id, new_body, new_title=None):
"""
Update an existing Confluence page
Args:
page_id: The page ID to update
new_body: New content in storage format
new_title: Optional new title (keeps existing if None)
"""
try:
# Get current page to retrieve version number
page = confluence.get_page_by_id(page_id, expand='version')
current_version = page['version']['number']
current_title = page['title']
# Update the page
updated_page = confluence.update_page(
page_id=page_id,
title=new_title or current_title,
body=new_body,
version_comment="Updated via Python script",
minor_edit=False # Set to True for minor edits
)
print(f"Updated page to version {current_version + 1}")
return updated_page
except Exception as e:
print(f"Failed to update page: {e}")
return None
# Example: Append content to existing page
page_id = "123456789"
existing_page = confluence.get_page_by_id(page_id, expand='body.storage')
existing_body = existing_page['body']['storage']['value']
new_content = "<h2>New Section</h2><p>Additional information added.</p>"
updated_body = existing_body + new_content
update_page(page_id, updated_body)

Confluence uses a storage format similar to HTML with some special macros. Here are common patterns:

# Headers
h1 = "<h1>Main Title</h1>"
h2 = "<h2>Subtitle</h2>"
# Paragraphs and formatting
paragraph = "<p>This is a paragraph with <strong>bold</strong> and <em>italic</em> text.</p>"
# Lists
unordered_list = """
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
"""
ordered_list = """
<ol>
<li>First step</li>
<li>Second step</li>
<li>Third step</li>
</ol>
"""
# Links
internal_link = '<a href="/wiki/spaces/TEAM/pages/123456">Link to another page</a>'
external_link = '<a href="https://example.com">External link</a>'
# Code blocks
code_block = """
<ac:structured-macro ac:name="code" ac:schema-version="1">
<ac:parameter ac:name="language">python</ac:parameter>
<ac:plain-text-body><![CDATA[
def hello_world():
print("Hello, World!")
]]></ac:plain-text-body>
</ac:structured-macro>
"""
# Tables
table = """
<table>
<thead>
<tr>
<th>Name</th>
<th>Role</th>
<th>Department</th>
</tr>
</thead>
<tbody>
<tr>
<td>Alice</td>
<td>Engineer</td>
<td>Development</td>
</tr>
<tr>
<td>Bob</td>
<td>Manager</td>
<td>Operations</td>
</tr>
</tbody>
</table>
"""
# Info panel (macro)
info_panel = """
<ac:structured-macro ac:name="info">
<ac:rich-text-body>
<p>This is an informational message.</p>
</ac:rich-text-body>
</ac:structured-macro>
"""
# Warning panel
warning_panel = """
<ac:structured-macro ac:name="warning">
<ac:rich-text-body>
<p>This is a warning message.</p>
</ac:rich-text-body>
</ac:structured-macro>
"""
def create_documentation_page(space_key, title, sections):
"""
Create a well-formatted documentation page
Args:
space_key: Confluence space key
title: Page title
sections: List of dicts with 'title', 'content', 'code' keys
"""
body_parts = [f"<h1>{title}</h1>"]
for section in sections:
# Section title
body_parts.append(f"<h2>{section['title']}</h2>")
# Section content
body_parts.append(f"<p>{section['content']}</p>")
# Optional code block
if 'code' in section:
code_macro = f"""
<ac:structured-macro ac:name="code" ac:schema-version="1">
<ac:parameter ac:name="language">{section.get('language', 'python')}</ac:parameter>
<ac:plain-text-body><![CDATA[{section['code']}]]></ac:plain-text-body>
</ac:structured-macro>
"""
body_parts.append(code_macro)
body = "\n".join(body_parts)
return create_page(space_key, title, body)
# Example usage
sections = [
{
"title": "Installation",
"content": "Install the required dependencies using pip:",
"code": "pip install requests\npip install pandas",
"language": "bash"
},
{
"title": "Usage",
"content": "Here's a basic example:",
"code": "import requests\n\nresponse = requests.get('https://api.example.com')\nprint(response.json())",
"language": "python"
}
]
create_documentation_page("DOCS", "API Client Setup", sections)
def create_page_tree(space_key, page_structure, parent_id=None):
"""
Recursively create a hierarchy of pages
Args:
space_key: Confluence space key
page_structure: Dict with 'title', 'body', and optional 'children' keys
parent_id: Parent page ID (None for root level)
Returns:
Dict mapping page titles to their IDs
"""
created_pages = {}
# Create current page
page_id = create_page(
space_key=space_key,
title=page_structure['title'],
body=page_structure['body'],
parent_id=parent_id
)
if page_id:
created_pages[page_structure['title']] = page_id
# Create child pages
for child in page_structure.get('children', []):
child_pages = create_page_tree(space_key, child, page_id)
created_pages.update(child_pages)
return created_pages
# Example: Create documentation structure
docs_structure = {
"title": "Python Development Guide",
"body": "<h1>Python Development Guide</h1><p>Welcome to our Python documentation.</p>",
"children": [
{
"title": "Getting Started",
"body": "<h1>Getting Started</h1><p>Setup instructions for new developers.</p>",
"children": [
{
"title": "Environment Setup",
"body": "<h1>Environment Setup</h1><p>Install Python and configure your IDE.</p>"
},
{
"title": "Project Structure",
"body": "<h1>Project Structure</h1><p>Understanding our codebase layout.</p>"
}
]
},
{
"title": "Best Practices",
"body": "<h1>Best Practices</h1><p>Coding standards and guidelines.</p>"
}
]
}
created = create_page_tree("DOCS", docs_structure)
print(f"Created {len(created)} pages")
def safe_create_page(space_key, title, body, parent_id=None, max_retries=3):
"""Create page with retry logic and validation"""
import time
# Validate inputs
if not space_key or not title or not body:
raise ValueError("space_key, title, and body are required")
# Check if page already exists
existing = confluence.get_page_by_title(space_key, title)
if existing:
print(f"Page '{title}' already exists with ID: {existing['id']}")
return existing['id']
# Retry logic
for attempt in range(max_retries):
try:
page = confluence.create_page(
space=space_key,
title=title,
body=body,
parent_id=parent_id
)
return page['id']
except Exception as e:
if attempt < max_retries - 1:
wait_time = 2 ** attempt # Exponential backoff
print(f"Attempt {attempt + 1} failed, retrying in {wait_time}s...")
time.sleep(wait_time)
else:
raise Exception(f"Failed to create page after {max_retries} attempts: {e}")
def bulk_create_pages(space_key, pages, delay=0.5):
"""
Create multiple pages with rate limiting
Args:
space_key: Confluence space key
pages: List of dicts with 'title', 'body', 'parent_id' keys
delay: Delay between requests in seconds
"""
import time
results = []
for i, page_data in enumerate(pages):
print(f"Creating page {i+1}/{len(pages)}: {page_data['title']}")
try:
page_id = create_page(
space_key=space_key,
title=page_data['title'],
body=page_data['body'],
parent_id=page_data.get('parent_id')
)
results.append({'title': page_data['title'], 'id': page_id, 'status': 'success'})
except Exception as e:
results.append({'title': page_data['title'], 'error': str(e), 'status': 'failed'})
# Rate limiting
if i < len(pages) - 1:
time.sleep(delay)
return results
from string import Template
def create_page_from_template(space_key, title, template_path, variables):
"""
Create a page using an HTML template
Args:
space_key: Confluence space key
title: Page title
template_path: Path to HTML template file
variables: Dict of variables to substitute in template
"""
with open(template_path, 'r') as f:
template_content = f.read()
template = Template(template_content)
body = template.safe_substitute(variables)
return create_page(space_key, title, body)
# Example template file (meeting_notes_template.html):
# <h1>$meeting_title</h1>
# <p><strong>Date:</strong> $date</p>
# <p><strong>Attendees:</strong> $attendees</p>
# <h2>Agenda</h2>
# <p>$agenda</p>
# <h2>Action Items</h2>
# <p>$action_items</p>
# Usage:
variables = {
'meeting_title': 'Sprint Planning Meeting',
'date': '2026-01-28',
'attendees': 'Alice, Bob, Carol',
'agenda': 'Review backlog and assign tasks',
'action_items': 'TBD during meeting'
}
create_page_from_template(
'TEAM',
'Sprint Planning - Jan 28',
'meeting_notes_template.html',
variables
)
def find_and_update_pages(space_key, search_term, update_function):
"""
Find pages matching criteria and apply updates
Args:
space_key: Confluence space key
search_term: CQL search term
update_function: Function that takes page_id and returns new body
"""
# Search for pages
cql = f'space = "{space_key}" AND text ~ "{search_term}"'
results = confluence.cql(cql)
updated_pages = []
for result in results.get('results', []):
page_id = result['content']['id']
page_title = result['content']['title']
try:
# Get current content
page = confluence.get_page_by_id(page_id, expand='body.storage')
current_body = page['body']['storage']['value']
# Apply update function
new_body = update_function(current_body)
# Update page
update_page(page_id, new_body)
updated_pages.append(page_title)
print(f"Updated: {page_title}")
except Exception as e:
print(f"Failed to update {page_title}: {e}")
return updated_pages
# Example: Add a banner to all pages mentioning "deprecated"
def add_deprecation_banner(body):
banner = """
<ac:structured-macro ac:name="warning">
<ac:rich-text-body>
<p><strong>DEPRECATED:</strong> This content is outdated. See the new documentation.</p>
</ac:rich-text-body>
</ac:structured-macro>
"""
return banner + body
updated = find_and_update_pages("DOCS", "deprecated", add_deprecation_banner)
print(f"Updated {len(updated)} pages")