Confluence AS
2.0.0: Confluence exposes a spec-driven
apisurface through as-engine. The reviewed 108-verb inventory retains 38 workflows, replaces 70 verbs with migration hints, and has no deferred implementations.jira create-from-pagenow creates Jira issues through the shared engine transport; its Confluence reads and page update use indexed operations, while the Jira transport remains the bounded JAS-51 exception. Start withconfluence-as help, then read the migration notes and complete rename table before updating scripts.
Python library for Confluence Cloud REST API - shared utilities for the Confluence Assistant Skills project.
CLI discovery (api describe, api search, help and --version) defers HTTP,
legacy client and converter imports. Discovery no longer loads or validates scope
configuration because it sends no requests; the same scope configuration is loaded
once immediately before the first call, before any guard or transport runs.
Command-group help such as help api still loads wrapper modules to enumerate
their commands. Keep optional runtime imports at their point of use when extending
these entry paths.
Features
- ConfluenceClient - HTTP client with retry logic, pagination, and file uploads
- ConfigManager - Multi-source configuration (env vars, JSON files)
- Error Handling - Exception hierarchy and decorators for clean error handling
- Validators - Input validation for page IDs, space keys, CQL queries, etc.
- Formatters - Output formatting for pages, spaces, tables, JSON, CSV
- ADF Helper - Atlassian Document Format conversion (Markdown ↔ ADF)
- XHTML Helper - Legacy storage format conversion
- Cache - File-based response caching with TTL
Installation
pip install confluence-as
Note: This is the library package. For the CLI tool, install
confluence-assistant-skillsinstead:pip install confluence-assistant-skills # CLI with 'confluence' command
Quick Start
from confluence_as import (
get_confluence_client,
validate_page_id,
format_page,
markdown_to_adf,
)
# Set environment variables:
# CONFLUENCE_SITE_URL=https://your-site.atlassian.net
# CONFLUENCE_EMAIL=your-email@example.com
# CONFLUENCE_API_TOKEN=your-api-token
# Get a configured client
client = get_confluence_client()
# Get a page
page_id = validate_page_id("12345")
page = client.get(f"/api/v2/pages/{page_id}")
print(format_page(page))
# Create content from Markdown
content = markdown_to_adf("# Hello\n\nThis is **bold** text.")
Direct Client Usage
from confluence_as import ConfluenceClient
client = ConfluenceClient(
base_url="https://your-site.atlassian.net",
email="your-email@example.com",
api_token="your-api-token",
timeout=30,
max_retries=3,
)
# GET request
page = client.get("/api/v2/pages/12345")
# POST request
new_page = client.post("/api/v2/pages", json_data={
"spaceId": "123456",
"title": "New Page",
"body": {"representation": "atlas_doc_format", "value": "..."}
})
# Pagination
for page in client.paginate("/api/v2/pages", params={"space-id": "123456"}):
print(page["title"])
# File upload
result = client.upload_file(
"/api/v2/attachments",
file_path="/path/to/file.pdf",
additional_data={"pageId": "12345"}
)
Configuration
Environment Variables
export CONFLUENCE_SITE_URL="https://your-site.atlassian.net"
export CONFLUENCE_EMAIL="your-email@example.com"
export CONFLUENCE_API_TOKEN="your-api-token"
Error Handling
from confluence_as import (
handle_errors,
ConfluenceError,
AuthenticationError,
NotFoundError,
ValidationError,
)
@handle_errors
def main():
# Validation errors
page_id = validate_page_id(user_input) # Raises ValidationError if invalid
# API errors are caught and formatted
page = client.get(f"/api/v2/pages/{page_id}")
if __name__ == "__main__":
main() # Errors are caught, formatted, and exit with appropriate code
Validators
from confluence_as import (
validate_page_id, # Numeric string validation
validate_space_key, # Space key format
validate_cql, # CQL query syntax
validate_content_type, # page, blogpost, etc.
validate_url, # URL format
validate_email, # Email format
validate_title, # Page title constraints
validate_label, # Label format
validate_limit, # Pagination limit
)
page_id = validate_page_id("12345") # Returns "12345"
space_key = validate_space_key("docs") # Returns "DOCS" (normalized)
cql = validate_cql('space = "DOCS"') # Validates syntax
ADF Conversion
from confluence_as import (
markdown_to_adf,
adf_to_markdown,
text_to_adf,
adf_to_text,
create_heading,
create_paragraph,
create_bullet_list,
)
# Convert Markdown to ADF
adf = markdown_to_adf("""
# My Document
This is a **bold** paragraph.
- Item 1
- Item 2
```python
print("Hello")
""")
Convert ADF back to Markdown
markdown = adf_to_markdown(adf)
Build ADF programmatically
from confluence_as import create_adf_doc doc = create_adf_doc([ create_heading("Title", level=1), create_paragraph(text="Hello, World!"), create_bullet_list(["Item 1", "Item 2", "Item 3"]), ])
## Caching
```python
from confluence_as import Cache, cached
# Direct cache usage
cache = Cache(default_ttl=300) # 5 minutes
cache.set("key", {"data": "value"})
value = cache.get("key")
# Decorator usage
@cached(ttl=300)
def get_page(page_id):
return client.get(f"/api/v2/pages/{page_id}")
# First call hits API, subsequent calls use cache
page = get_page("12345")
API Reference
Client
ConfluenceClient- HTTP client classcreate_client()- Factory functionget_confluence_client()- Get configured client from environment variables
Config
ConfigManager- Configuration management class
Errors
ConfluenceError- Base exceptionAuthenticationError- 401 errorsPermissionError- 403 errorsValidationError- 400 errorsNotFoundError- 404 errorsRateLimitError- 429 errorsConflictError- 409 errorsServerError- 5xx errors
Formatters
format_page(),format_space(),format_comment()format_table()- ASCII table formattingformat_json()- JSON formattingexport_csv()- CSV exportprint_success(),print_warning(),print_info()
ADF Helper
markdown_to_adf(),adf_to_markdown()text_to_adf(),adf_to_text()create_*()- Node creation functionsvalidate_adf()- Validate ADF structure
XHTML Helper
xhtml_to_markdown(),markdown_to_xhtml()xhtml_to_adf(),adf_to_xhtml()validate_xhtml()- Validate XHTML structure
Cache
Cache- File-based cache classcached()- Caching decoratorget_cache()- Get global cache instance
Contributing
Development prerequisites: pip install -e '.[dev]' includes hatchling and
build; check local wheel and source builds with python -m build --no-isolation.
For Base Document refreshes, install oasdiff 1.31.0: on macOS run
curl --fail --location --retry 3 --output oasdiff.tar.gz https://github.com/oasdiff/oasdiff/releases/download/1.31.0/oasdiff_1.31.0_darwin_all.tar.gz;
on Linux run
curl --fail --location --retry 3 --output oasdiff.tar.gz https://github.com/oasdiff/oasdiff/releases/download/1.31.0/oasdiff_1.31.0_linux_amd64.tar.gz.
Then run tar -xzf oasdiff.tar.gz oasdiff and chmod +x oasdiff, and place the
binary on PATH (required by the refresh acceptance test), or set
OASDIFF=/path/to/oasdiff for the refresh script.
- Fork the repository
- Create a feature branch
- Make changes with tests
- Submit a pull request
# Install development dependencies
pip install -e ".[dev]"
# Install pre-commit hooks (recommended)
pre-commit install
# Run tests
pytest
# Run with coverage
pytest --cov=src/confluence_as
# Code quality checks (run automatically on commit with pre-commit)
ruff check src/ tests/ # Linting
ruff format src/ tests/ # Formatting
mypy src/ # Type checking
bandit -r src/ -q # Security scanning
License
MIT License - see LICENSE for details.
Related Projects
- Confluence Assistant Skills - Claude Code skills for Confluence automation
Build
Confluence v2 (primary) and v1 (lower tier, loaded on demand) Base Documents
are pinned in src/confluence_as/specs/manifest.json by source URL, declared
version, SHA256 and fetch time. The JSON overlays beside them are the source
of enrichment; vendored documents remain pristine. as-engine verifies the
pins, strips the narrative extension, applies overlays and defect hooks, and
compiles one deterministic operation index per document.
Wheel and editable builds both generate src/confluence_as/_generated/.
It is ignored by git; wheels include its indexes and catalog, while source
archives include the inputs and build hook. Nothing is fetched at build or
runtime. Consumers use as_engine.index.load_index(path) for one document,
or ProductIndexes(generated_directory) to load primary indexes eagerly and
retrieve v1 on demand with .get("v1").
The runtime dependency is as-engine>=0.1.2,<0.2. Release and isolated builds
use the verified as-engine==0.1.2 wheel
(SHA256 ac4cb0b07effbecff812652a33aea54b036971e883d3fe349428c6d723d90d83)
and hatchling==1.32.0; do not use a moving source branch as the release
baseline. Install the development extra with
python -m pip install --no-build-isolation -e '.[dev]', or build an sdist and
wheel with python -m build --no-isolation in the prepared environment.
Refresh deliberately with python scripts/refresh_base_documents.py, with
oasdiff on PATH (or OASDIFF=/path/to/oasdiff). For an offline refresh use
--from-file v2=/path/to/new-v2.json; only named documents are refreshed in
that mode. Each refresh appends <document>.changelog.md, then replaces the
source and updates the manifest pin. Review and commit those sources and
changelogs together. A missing oasdiff binary records an explicit skip in the
changelog and stderr; install it and obtain the real diff before accepting a
refresh. Invalid JSON, changed existing pins or a failed oasdiff command leave
the sources unchanged.
Indexed API operations
Tagged API calls now require CONFLUENCE_ALLOWED_SPACES=DOCS,ENG (or settings-file
confluence.allowed_spaces); absent means default deny. Body-scope calls require
--space KEY, for example api call createPage --space DOCS --field 'spaceId="55"':
one bounded space lookup must verify that id before the mutation. Space-filtered
lists require --space-id; page-id calls resolve metadata before checking the
space. Local refusals exit 4. Site-level calls such as getSpaces require explicit
CONFLUENCE_ALLOW_SITE_OPERATIONS=1 (or confluence.allow_site_operations). This
policy applies to tagged API calls and surviving wrapper workflows; untagged
operations keep their documented existing behavior. The Jira issue creation
leg in jira create-from-page uses a bounded JAS-51 createIssue operation
through engine HTTPTransport; it does not establish a Jira project guard or
universal Surface.call coverage. See as-engine's docs/guard.md for the exact 82-operation
v2 coverage, untagged list, metadata-read exception, and request counts; the examples
below require the corresponding scope flags and settings for tagged operations.
The api group calls operations from the packaged OpenAPI index through as-engine:
confluence-as api search page
confluence-as api describe createPage
confluence-as api call getPages --limit 5
confluence-as api call get-pages --space-id '[123,456]' --limit 5
confluence-as api call createPage --body @page.json
confluence-as api call createPage --body - < page.json
confluence-as api call createPage --field 'spaceId="123"' --field title=Example
confluence-as api topics
Operation IDs accept canonical camelCase or kebab-case. Spec parameters become
kebab-case flags; api call OPERATION --help lists them. Booleans take explicit
true/false; arrays accept repeated flags, comma-separated values or JSON arrays.
--field path=value builds dotted object fields, parsing JSON values where possible;
quote a numeric ID as JSON when the body schema requires a string. Invalid
parameters are refused before a transport is created. --limit is the operation's
spec parameter; one response page is returned.
For operations tagged for paging, --all aggregates response pages. With --all,
--limit caps the total items returned and --parameter-limit sets each server page
size; without it, --limit keeps its spec-defined page-size meaning. Tagged
prerequisites expose their key aliases (for example --space-key DOCS) and resolve
them before the call; tagged updates accept --version to override version enrichment.
Tagged page/blog body fields accept literal Markdown or UTF-8 files, for example
api call updatePage --id 123 --field 'id="123"' --field title=Notes --field status=current --field body=@notes.md --confirm (on one line, with the configured space allowlist).
The default write representation is storage; --representation atlas_doc_format
sends stringified ADF. Existing JSON envelopes remain encoded input. Reads render
tagged bodies as Markdown with lossless placeholders; --raw retains the stored
body. Use the separate spec flag --body-format storage|atlas_doc_format to request
read content. Copy a mention placeholder into a Markdown file and select its same
representation on the next write to preserve its node. Missing files and conflicting
representation options fail before lookups; --raw still converts write input.
An unseeded schema-generated responder body is not a valid rich-text document;
offline rich-text tests seed explicit storage/ADF responses. Surviving wrapper writes use the same tagged conversion and version pipeline.
Calls emit JSON; --format table|markdown renders the top-level result.
Search defaults to a table and supports --format json; describe defaults to
Markdown and supports --format json. Search excludes deprecated operations unless
--include-deprecated is set; calls warn and show a replacement when enrichment
provides one. Topics come from x-as-topic, with a clean empty state until tagged.
Only v2 is loaded for primary discovery; a named v1 operation loads v1 on demand.
Use --validate-body to check a body before sending. Otherwise body checks run only
after a 400 to enrich the error. The checker reflects the indexed schema and reports
unsupported validators explicitly. Upstream createPage body alternatives overlap:
its body oneOf may reject a valid service payload until a correcting overlay lands.
Ordinary calls do not run that optional body check.
HTTP uses the existing configuration chain (CONFLUENCE_SITE_URL,
CONFLUENCE_EMAIL, CONFLUENCE_API_TOKEN), timeouts, TLS settings and retry settings.
The engine supplies pooled HTTP with 429/5xx backoff and Retry-After support.
Confluence's existing domain error mapper is reused. JSON bodies are supported;
non-JSON media types are explicitly refused pending JAS-61. Legacy single-operation
commands now return migration hints; workflow survivors remain available.
Offline responder mode uses the same call path without reading credentials:
CONFLUENCE_AS_TRANSPORT=responder confluence-as api call getPages --limit 5
CONFLUENCE_AS_TRANSPORT=responder confluence-as api --respond-with 400 call getPages
The responder returns indexed examples or bounded schema-generated bodies, never a
live request. It is stateless and returns null where no 200 schema/example survives
in the index. --respond-with is a test hook accepted only in responder mode.
Errors are JSON on stderr: {status, messages, operation, note}. Exit codes are
usage/400 2, auth 3, permission/scope 4, not found 5, server/exhausted
rate limit 6, other failure (including 409) 1, success 0. No Confluence
project guard is introduced. Live-site acceptance is held separately from offline
responder tests pending the Confluence sandbox ruling.
Progressive help
Run confluence-as or confluence-as help for the surface map, help api
for discovery commands, and help adf or help paging for tagged gotchas.
api describe deletePage shows parameters, body and risk; --full includes the
complete description, and --examples shows enrichment invocations and bodies.
Wrapper <group> <verb> --help uses the same renderer, with --full and
--examples. Add --format json to help/describe for the same content in a
structured document. Large topic/group lists show --offset N to continue.
help search loads the v1 CQL topic on demand; help risk --tier v1 explicitly
loads lower-tier help. api describe resolves lower-tier operation IDs on demand.
Operations tagged destructive or irreversible preview by default: for example,
api call deletePage --id 123 prints JSON and sends no request. Add --confirm
to send through the normal guard and transform pipeline. Previews validate local
inputs but do not perform prerequisite or version lookups; unresolved requirements
are identified in the output. Direct Python Surface calls retain their existing
behavior. Discovery needs no credentials. API responder/cassette modes are distinct from the stateful simulation mode.
CONFLUENCE_MOCK_MODE applies to Python integrations that still use the
legacy client. The Confluence calls in jira create-from-page use indexed
operations and the selected engine transport; indexed API calls support the
responder, cassette, simulation and HTTP transport modes.
Help snapshots live in tests/golden/help/. Regenerate with
UPDATE_HELP_GOLDEN=1 .venv/bin/python -m pytest -q -p no:cacheprovider tests/test_help.py
and review the diff. Tests enforce ceil(characters/4) Markdown caps of
400/800/1200/600 tokens for Levels 0/1/2/3 and 800 for the topic list;
--full is intentionally uncapped. JSON serialization metadata is not counted
again. Regeneration never disables cap checks.
Wrapper workflows and migration
The reviewed 108-verb inventory retains 38 workflows, replaces 70 verbs with
index-backed migration hints, and has no deferred implementations. See
the full decision table for every old command, reason,
replacement invocation and disposition. For example, executing page create --space DOCS --title T now emits JSON on stderr, exits 2, and points to api call createPage;
page get --help still succeeds. help migration discovers the rename topic.
Survivors cover bulk selection/checkpoint/resume, hierarchy traversal/copy,
CSV/history/cache/report transforms, permission decisions and Jira macro workflows.
Confluence requests from surviving workflows use Surface and the same scope/tag
pipeline as api, except for Jira issue creation in jira create-from-page.
That bounded JAS-51 Jira leg uses engine HTTPTransport without a Jira project
guard; see its documented seam above. Bulk dry-run resolves targets using reads,
prints intended operations, and sends no writes. Both bulk label add --labels reviewed --cql 'space=DOCS' --dry-run and
bulk label --add reviewed --cql 'space=DOCS' --dry-run are supported.
For offline stateful workflows set CONFLUENCE_AS_TRANSPORT=simulation and an
explicit space allowlist (plus site-operation permission where required). The
simulation starts a fresh store per process and never reads credentials or sends
HTTP; tests may inject a shared store for sequential runs. The schema responder
remains stateless. See the engine's docs/simulation.md for supported operations,
CQL and explicit failures. attachment download ATTACHMENT_ID resolves safe
metadata through the Surface and writes the binary response; use --all with a
page ID to download every attachment into --output-dir (also accepted as
--output or -o). jira create-from-page creates the Jira issue with the
JAS-51 hand-built createIssue operation through engine HTTPTransport, with
the existing 30-second timeout, zero retries, flags and output, and no
jira-as dependency. Its Confluence page reads and marker update use indexed
engine operations and the Confluence scope guard. The Jira request is a bounded
transport exception: it has no Jira project guard and is not universal
Surface.call coverage. A cross-product operation is not transactional; if
Jira creation succeeds and the Confluence update fails, inspect the reported
issue before retrying.
1.x support
The 1.x branch receives security and critical fixes only until one quarter
after the 2.0.0 release date, 2026-10-06; support ends on 2027-01-06.
Metadata
Release files for confluence-as 2.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| confluence_as-2.0.0.tar.gz | 242.2 kB | With provenance | Details for confluence_as-2.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| confluence_as-2.0.0-py3-none-any.whl | Python 3 | none | any | Details for confluence_as-2.0.0-py3-none-any.whl |
Total release size: 595.3 kB
Release files / confluence_as-2.0.0.tar.gz
| Download URL | confluence_as-2.0.0.tar.gz |
|---|---|
| Size | 242.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
85785e4fa1e23eee752ca5ec3c2144a13e41fc69e7dcd69548fb21ae14e82df1
|
|
BLAKE2b-256 checksum How to use checksums |
56f6b3b5b63707966dfeffeb21077fab24ae4ce5d1f1d9cb232d23a14492ed86
|
| 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 Oct 7, 2026.
Transparency logRelease files / confluence_as-2.0.0-py3-none-any.whl
| Download URL | confluence_as-2.0.0-py3-none-any.whl |
|---|---|
| Size | 353.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
07c22a4f99dd4187f477e1fdd897991ebe41544610bd7f46ec6163fc46b9bd9d
|
|
BLAKE2b-256 checksum How to use checksums |
1f6b16044a15bec8c73ff3eb859689a9d7f5f2e6a9df787a5469336c8419b271
|
| 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 Oct 7, 2026.
Transparency logSecurity for confluence-as 2.0.0
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.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for confluence_as-2.0.0.tar.gz
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 Oct 7, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.