confluence-api-doc
Confluence API Doc Sync
Sync API documentation from a multi-file docs/api/ directory to Confluence — one endpoint file = one Confluence page. The directory structure maps directly to a Confluence page tree, so no heading-based splitting is needed.
Uses acli for authentication verification and page reading (to get current version), and Confluence REST API via curl for page updates.
Core Principle: Directory Structure = Confluence Page Tree
The docs/api/ directory (generated by api-doc-gen) already organizes endpoints as individual files grouped by domain. This skill maps that structure directly to Confluence:
docs/api/ Confluence Page Tree
├── index.md → Parent Page (overview + common errors)
├── consent/ → ├── Consent (domain group page)
│ ├── accept-consent.md → │ ├── Accept Consent
│ ├── get-consent.md → │ ├── Get Consent
│ └── revoke-consent.md → │ └── Revoke Consent
├── channel/ → ├── Channel (domain group page)
│ ├── create-channel.md → │ ├── Create Channel
│ └── get-all-channels.md → │ └── Get All Channels
└── purpose/ → └── Purpose (domain group page)
├── create-purpose.md → ├── Create Purpose
└── get-purposes.md → └── Get Purposes
Each .md endpoint file becomes exactly one Confluence page. Group directories become parent pages.
Step 1: Gather Required Information
Ask the user for the following (if not already provided):
- API doc directory path — e.g.,
docs/api/(relative to project root, or absolute). Default:docs/api/ - Parent page URL — the Confluence page/folder under which API doc pages live (or will be created), e.g.,
https://company.atlassian.net/wiki/spaces/PROJ/pages/123456789/API+Referenceor a folder URL likehttps://company.atlassian.net/wiki/spaces/PROJ/folder/123456789
Extract the page ID directly from the URL (the numeric segment, e.g., 123456789).
Validate the directory:
index.mdmust exist in the provided path- At least one subdirectory with
.mdfiles must exist
Do NOT ask for Confluence URL, email, or API token — those are resolved automatically in the next step.
Step 2: Verify Authentication and Resolve Credentials
acli auth status
If not authenticated or acli not found:
- Not installed → tell user:
brew install atlassian/tap/aclior visit https://developer.atlassian.com/cloud/acli/install/ - Not authenticated → guide user to run:
acli auth login
From the output, extract:
- CONFLUENCE_URL —
Site:field prefixed withhttps://, e.g.,company.atlassian.net→https://company.atlassian.net - EMAIL —
Email:field, e.g.,user@company.com
Why REST API for writes?
acli confluence pagecurrently only supportsview. For page create/update we use Confluence REST API viacurl. URL and email come fromacli auth status; only the API token needs to be resolved (at write time).
Step 3: Read Directory Structure
Scan the docs/api/ directory to build the page list. No heading-based parsing needed — the file/directory structure is the source of truth.
Scanning Steps
- Read
index.md— extract service name (from H1), overview paragraph, and Common Error Responses section - List subdirectories — each subdirectory = one domain group (e.g.,
consent/→ "Consent"). Skiphealth/— health check endpoints are infrastructure-only and not synced to Confluence. - List
.mdfiles per subdirectory — each file = one API endpoint page - Extract page title per endpoint — read the Method and Path fields from the file, then format as
METHOD: /path(e.g.,POST: /api/v1/consents). This is the Confluence page title — not the H1 heading. - Extract page content per endpoint — use the full file content, but:
- Strip the breadcrumb line (first line starting with
>) - Strip the H1 heading (used as in-page heading, not page title)
- Strip the breadcrumb line (first line starting with
Page Types
| Source | Page Type | Title | Content |
|---|---|---|---|
| Group directory | Domain group | Directory name → Title Case (e.g., consent → "Consent") |
Brief intro or empty |
Endpoint .md file |
API page | METHOD: /path from Method + Path fields (e.g., POST: /api/v1/consents) |
File content (minus breadcrumb) |
index.md overview |
Parent page content | Service name from H1 | Overview + Common Errors (appended to parent page) |
Summary Output
After scanning, show the user a structured summary:
Found N domain groups, M individual API endpoints:
Consent (5 APIs)
POST: /api/v1/consents
GET: /api/v1/consents/:citizen_id
GET: /api/v1/consents/:id
GET: /api/v1/consents/:id/history
DELETE: /api/v1/consents/:id/revoke
Channel (5 APIs)
POST: /api/v1/channels
GET: /api/v1/channels
...
Purpose (11 APIs)
POST: /api/v1/purposes
GET: /api/v1/purposes
...
Total pages to create/update: N (domain groups) + M (APIs) = T pages
Ask the user to confirm or specify which sections to sync (all, specific domains, or specific APIs).
Step 4: Map Sections to Confluence Page Hierarchy
The page structure in Confluence mirrors the directory structure:
Parent page (provided by user)
├── Domain Group pages (directories) ← first-level children
│ └── Individual API pages (files) ← second-level children
Discover Existing Pages
First, fetch all children (and grandchildren) under the parent page to find existing pages:
# Get direct children of parent page
curl -s "${CONFLUENCE_URL}/wiki/rest/api/content/${PARENT_PAGE_ID}?expand=space,children.page" \
-u "${EMAIL}:${API_TOKEN}"
From the response extract:
space.key→ save asSPACE_KEY(needed for creating new pages)children.page.results[]→ list of{id, title}for direct children
For each direct child that looks like a domain group, also fetch its children:
curl -s "${CONFLUENCE_URL}/wiki/rest/api/content/${DOMAIN_GROUP_PAGE_ID}/child/page" \
-u "${EMAIL}:${API_TOKEN}"
Build the Mapping
Match existing page titles against scanned sections to build the mapping:
| Source | Page Title | Type | Matched Page ID | Action |
|---|---|---|---|---|
| consent/ | Consent | Domain group | 456789 | Update |
| consent/accept-consent.md | POST: /api/v1/consents | API page | 567890 | Update |
| consent/revoke-consent.md | DELETE: /api/v1/consents/:id/revoke | API page | — | Create (under 456789) |
| purpose/ | Purpose | Domain group | — | Create (under parent) |
| purpose/create-purpose.md | POST: /api/v1/purposes | API page | — | Create (under new domain group page) |
Important ordering: When creating new pages, domain group pages must be created before their child API pages (because child pages need the parent's page ID as ancestor).
Matching Strategy
- Match by exact page title (e.g.,
POST: /api/v1/consentsmatches existing page with same title) - If ambiguous, show the user and ask them to confirm the mapping
- For unmatched sections → mark as "Create new"
Step 5: Get Current Page Versions
For each page in the mapping, fetch its current version number (required for updates):
acli confluence page view --id <PAGE_ID> --include-version --json
Extract version.number from the JSON output. Store as CURRENT_VERSION per page.
Step 6: Convert Markdown to Confluence Storage Format
For each endpoint file, convert the Markdown content to Confluence storage format (XHTML-based).
Pre-processing
Before conversion, strip from each endpoint file:
- Breadcrumb line — first line starting with
>(e.g.,> [API Documentation](../index.md) > ...) - H1 heading — first
#line (used as page title, not body content)
Conversion Rules
| Markdown | Confluence Storage |
|---|---|
```lang\ncode\n``` |
<ac:structured-macro ac:name="code"><ac:parameter ac:name="language">lang</ac:parameter><ac:plain-text-body><![CDATA[code]]></ac:plain-text-body></ac:structured-macro> |
**bold** |
<strong>bold</strong> |
*italic* |
<em>italic</em> |
[text](url) |
<a href="url">text</a> |
## Heading |
<h2>Heading</h2> |
| col | col | table |
<table><tbody><tr><td>...</td></tr></tbody></table> |
For js, javascript, sh, bash, json, yaml code blocks, map to the Confluence language name accordingly (bash for sh, javascript for js).
Step 7: Sync Pages (Create + Update) via REST API
Before writing, resolve the API token:
echo $CONFLUENCE_API_TOKEN
- If set → use it silently, no need to ask
- If empty → ask the user once:
"ต้องการ API token สำหรับ write ผ่าน Confluence REST API (acli ยังไม่ support page write) — generate ได้ที่ https://id.atlassian.com/manage-profile/security/api-tokens"
Use EMAIL extracted from acli auth status in Step 2.
Execution Order (critical for parent-child hierarchy)
- First pass — Domain group pages: Create or update all domain group pages as children of the parent page. This ensures parent page IDs exist before creating child API pages.
- Second pass — Individual API pages: Create or update all endpoint pages as children of their respective domain group pages.
Creating a New Page
curl -s -X POST \
"${CONFLUENCE_URL}/wiki/rest/api/content" \
-u "${EMAIL}:${API_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"type\": \"page\",
\"title\": \"${PAGE_TITLE}\",
\"ancestors\": [{\"id\": \"${ANCESTOR_PAGE_ID}\"}],
\"space\": {\"key\": \"${SPACE_KEY}\"},
\"body\": {
\"storage\": {
\"value\": \"${ESCAPED_HTML}\",
\"representation\": \"storage\"
}
}
}"
- For domain group pages:
ANCESTOR_PAGE_ID= user-provided parent page ID - For individual API pages:
ANCESTOR_PAGE_ID= the domain group page ID (created in first pass)
Extract id from the response to use as ancestor for child pages.
Updating an Existing Page
curl -s -X PUT \
"${CONFLUENCE_URL}/wiki/rest/api/content/${PAGE_ID}" \
-u "${EMAIL}:${API_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"version\": {\"number\": $((CURRENT_VERSION + 1))},
\"title\": \"${PAGE_TITLE}\",
\"type\": \"page\",
\"body\": {
\"storage\": {
\"value\": \"${ESCAPED_HTML}\",
\"representation\": \"storage\"
}
}
}"
Check HTTP status — 200 means success.
Content comparison tip: Before updating, compare normalized content (collapse whitespace) to skip pages with no real changes. This avoids unnecessary version bumps.
Step 8: Report Results
Print a summary table after all operations:
| Page Title | Type | Page ID | Status |
|-----------------------------------------|----------------|------------|-------------------------|
| Consent | Domain group | 456789 | Updated (v3 → v4) |
| POST: /api/v1/consents | API page | 567890 | Updated (v2 → v3) |
| DELETE: /api/v1/consents/:id/revoke | API page | 678901 | Created |
| GET: /api/v1/consents/:citizen_id | API page | 567890 | Skipped (no changes) |
| Purpose | Domain group | 789012 | Created |
| POST: /api/v1/purposes | API page | 890123 | Created |
Total: N domain groups, M API pages updated, K created, J skipped, F failed.
Error Reference
| Scenario | Action |
|---|---|
acli not found |
Install via brew install atlassian/tap/acli |
acli auth status fails |
Run acli auth login |
API doc directory not found or missing index.md |
Re-ask for correct directory path |
| HTTP 401 on REST call | Check API token — re-check $CONFLUENCE_API_TOKEN or ask user |
| HTTP 404 on page | Verify page ID is correct; page may have been deleted |
| HTTP 409 version conflict | Re-fetch version with acli and retry |
| Section title has no match | Ask user to manually provide page ID |
More from witooh/skills
brainstorm
>-
45improve
Iteratively improve any output until measurable criteria are met. Use when the user wants to refine existing work against specific standards — whether it's code, prose, data, config, or any other artifact. Triggers on phrases like "improve this", "make it better", "iterate", "refine", "keep improving", "not good enough yet", "optimize this", "polish this", "tighten this up", "ปรับปรุง", "ทำให้ดีขึ้น", "ยังไม่ดี", "แก้ให้ดีกว่านี้", "iterate ต่อ", or when the user provides criteria and wants repeated improvement until they're satisfied. Also use when the user gives feedback on output and expects you to keep refining, even if they don't say "improve" explicitly.
41neo-team-claude
>
33atlassian
>
23neo-team-copilot
>
15gitlab-copilot
>
14