confluence-api-doc

Original🇺🇸 English
Not Translated

Sync API documentation from a docs/api/ directory (multi-file structure generated by api-doc-gen) to Confluence pages using acli + REST API. Each endpoint file becomes one Confluence page, grouped under domain parent pages. Prompts for directory path, parent page URL, and credentials at runtime — no pre-configured environment variables required. Use this skill whenever: uploading or syncing API docs to Confluence, updating Confluence pages from docs/api/, publishing documentation, "sync api doc", "push doc to confluence", "sync docs/api/ to confluence", "อัปเดต api doc ไป confluence", "sync confluence pages", "confluence-api-doc", or when the user wants to publish or update any API documentation to Confluence.

1installs
Added on

NPX Install

npx skill4agent add jojious/ada-skills confluence-api-doc

SKILL.md Content

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):
  1. API doc directory path — e.g.,
    docs/api/
    (relative to project root, or absolute). Default:
    docs/api/
  2. 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+Reference
    or a folder URL like
    https://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.md
    must exist in the provided path
  • At least one subdirectory with
    .md
    files 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

bash
acli auth status
If not authenticated or acli not found:
From the output, extract:
  • CONFLUENCE_URL
    Site:
    field prefixed with
    https://
    , e.g.,
    company.atlassian.net
    https://company.atlassian.net
  • EMAIL
    Email:
    field, e.g.,
    user@company.com
Why REST API for writes?
acli confluence page
currently only supports
view
. For page create/update we use Confluence REST API via
curl
. URL and email come from
acli 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

  1. Read
    index.md
    — extract service name (from H1), overview paragraph, and Common Error Responses section
  2. List subdirectories — each subdirectory = one domain group (e.g.,
    consent/
    → "Consent"). Skip
    health/
    — health check endpoints are infrastructure-only and not synced to Confluence.
  3. List
    .md
    files per subdirectory
    — each file = one API endpoint page
  4. 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.
  5. 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)

Page Types

SourcePage TypeTitleContent
Group directoryDomain groupDirectory 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 contentService name from H1Overview + 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:
bash
# 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 as
    SPACE_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:
bash
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:
SourcePage TitleTypeMatched Page IDAction
consent/ConsentDomain group456789Update
consent/accept-consent.mdPOST: /api/v1/consentsAPI page567890Update
consent/revoke-consent.mdDELETE: /api/v1/consents/:id/revokeAPI pageCreate (under 456789)
purpose/PurposeDomain groupCreate (under parent)
purpose/create-purpose.mdPOST: /api/v1/purposesAPI pageCreate (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/consents
    matches 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):
bash
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:
  1. Breadcrumb line — first line starting with
    >
    (e.g.,
    > [API Documentation](../index.md) > ...
    )
  2. H1 heading — first
    # 
    line (used as page title, not body content)

Conversion Rules

CRITICAL — Processing order matters. Follow these phases strictly:

Phase 1: Extract code blocks FIRST (before any other conversion)

Scan the entire Markdown content for fenced code blocks (
```
pairs). For each block:
  1. Capture the language identifier (e.g.,
    json
    ,
    bash
    ,
    yaml
    )
  2. Capture ALL lines between the opening and closing
    ```
    verbatim — do NOT apply any HTML conversion to these lines
  3. Replace the entire block (including fences) with the Confluence code macro:
xml
<ac:structured-macro ac:name="code">
  <ac:parameter ac:name="language">LANG</ac:parameter>
  <ac:plain-text-body><![CDATA[...entire code content verbatim...]]></ac:plain-text-body>
</ac:structured-macro>
Rules for code blocks:
  • Content inside
    <![CDATA[...]]>
    must be the raw text — never wrap lines in
    <p>
    ,
    <br/>
    , or any HTML tags
  • Preserve original indentation and newlines exactly as-is
  • Language mapping:
    sh
    bash
    ,
    js
    javascript
    ; all others use their name directly
  • Code blocks without a language identifier: omit the
    <ac:parameter>
    line

Phase 2: Convert remaining Markdown (non-code-block content only)

MarkdownConfluence Storage
**bold**
<strong>bold</strong>
*italic*
<em>italic</em>
[[text](url)]
<a href="url">text</a>
process before normal links (used in type columns like
array[[Type](#anchor)]
)
[text](url)
<a href="url">text</a>
## Heading
<h2>Heading</h2>
| col | col |
table
<table><tbody><tr><td>...</td></tr></tbody></table>
Plain text paragraphs (separated by blank lines)
<p>text</p>
- item
/
* item
unordered list
<ul><li>item</li></ul>
1. item
ordered list
<ol><li>item</li></ol>
`inline code`
<code>inline code</code>
Nested list handling (critical):
When an ordered list item contains indented sub-items (e.g.,
- 
bullets indented under a
1. 
item), the sub-items become a
<ul>
inside that
<li>
, and the
<li>
is then closed. Subsequent numbered items (
5. 
,
6. 
, etc.) must continue as siblings in the same parent
<ol>
— do NOT nest them inside the previous item's sub-list.
Example input:
4. Validate collection point
   - Check active status
   - Check purpose mapping
5. Create consent record
6. Return response
Correct output:
html
<ol>
  <li>Validate collection point
    <ul><li>Check active status</li><li>Check purpose mapping</li></ul>
  </li>
  <li>Create consent record</li>
  <li>Return response</li>
</ol>
Wrong output (items 5-6 nested inside item 4's sub-list):
html
<ol>
  <li>Validate collection point
    <ul><li>Check active status</li><li>Check purpose mapping</li>
      <li>Create consent record</li><li>Return response</li>
    </ul>
  </li>
</ol>

Step 7: Sync Pages (Create + Update) via REST API

Before writing, resolve the API token:
bash
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)

  1. 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.
  2. Second pass — Individual API pages: Create or update all endpoint pages as children of their respective domain group pages.

Creating a New Page

bash
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

bash
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

ScenarioAction
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 callCheck API token — re-check
$CONFLUENCE_API_TOKEN
or ask user
HTTP 404 on pageVerify page ID is correct; page may have been deleted
HTTP 409 version conflictRe-fetch version with acli and retry
Section title has no matchAsk user to manually provide page ID