Tracking Confluence
Discovery
What discovery provides:
resources: List of accessible Atlassian sites withcloudId,url,namecurrentUser: YouraccountId,name, andemail(for CQL queries likecreator = currentUser())spaces: Available spaces withid,key,name,type(global/personal); includesbyTypecountsrecentPages: Last 10 modified pages withtitle,spaceKey,type,modifiedhints: Pagination model info (cursor-based via_links.next)
Why run discovery:
- Get
cloudIdrequired for all Confluence API calls - Know available spaces before creating pages
- Get your
accountIdfor CQL queries - Understand pagination model (cursor-based, not offset-based)
Tools
Pages: getConfluencePage(cloudId, pageId, expand?), createConfluencePage(cloudId, spaceId, title, body, contentFormat?), updateConfluencePage(cloudId, pageId, title, body, contentFormat?, versionMessage?), getConfluencePageDescendants(cloudId, pageId)
Spaces: getConfluenceSpaces(cloudId, limit?), getPagesInConfluenceSpace(cloudId, spaceId)
Comments: getConfluencePageFooterComments(cloudId, pageId), getConfluencePageInlineComments(cloudId, pageId), createConfluenceFooterComment(cloudId, pageId, body)
Search: searchConfluenceUsingCql(cloudId, cql, maxResults?)
Quick Patterns
Get cloudId (always first):
const resources = await getAccessibleAtlassianResources();
const cloudId = resources[0].id;
const siteUrl = resources[0].url;
Create page:
const spaces = await getConfluenceSpaces({ cloudId, limit: 10 });
const spaceId = spaces.results[0].id;
const result = await createConfluencePage({ cloudId, spaceId: spaceId.toString(), title: 'Page Title', body: '# Markdown', contentFormat: 'markdown' });
const url = `${siteUrl}/wiki/spaces/${spaceId}/pages/${result.id}`;
Search pages:
const results = await searchConfluenceUsingCql({ cloudId, cql: 'space = "DOCS" AND type = page AND title ~ "API"', maxResults: 10 });
results.results.forEach(page => console.log(page.title, page.url));
Update page:
const page = await getConfluencePage({ cloudId, pageId: '12345' });
await updateConfluencePage({ cloudId, pageId: '12345', title: 'Updated', body: '# New content', contentFormat: 'markdown' });
Pagination
Pattern:
let allResults = [];
let response = await getConfluenceSpaces({ cloudId, limit: 25 });
allResults.push(...response.results);
while (response._links?.next) {
// _links.next contains the full path for the next page
// Re-call with updated parameters from the next link
response = await getConfluenceSpaces({ cloudId, limit: 25 /* next cursor handled internally */ });
allResults.push(...response.results);
}
Workflows
Create page: getAccessibleAtlassianResources() → getConfluenceSpaces({cloudId}) → createConfluencePage({cloudId, spaceId, title, body}) → return ${siteUrl}/wiki/spaces/${spaceId}/pages/${result.id}
Find pages: getAccessibleAtlassianResources() → searchConfluenceUsingCql({cloudId, cql: 'type = page AND title ~ "topic"'})
Update page: getConfluencePage({cloudId, pageId}) → updateConfluencePage({cloudId, pageId, title, body, contentFormat: 'markdown'})
CQL Reference
Operators: =, !=, ~ (contains), IN, >=, <=
Fields: space, type, title, text, created, modified, creator, contributor, label
Examples: space = "DOCS" AND type = page, title ~ "API", created >= -7d ORDER BY created DESC, label = "important", creator = currentUser()
CQL Best Practices
Content Formats
| Format | contentFormat value |
When to use | Notes |
|---|---|---|---|
| Markdown | markdown |
Default for new pages | Simplest; auto-converted by Confluence |
| Storage | storage |
Precise HTML control | Uses <ac:*> macros for rich content |
| Wiki | wiki |
Legacy content | Rarely needed for new pages |
Recommendation: Use markdown as the default contentFormat. Only use storage when you need Confluence-specific macros (code blocks, panels, etc.).
SDK Utilities
Import from @connections/_utils (NOT from format module):
import { format, normalize, countBy, groupBy, parseArgs, parallel } from "@connections/_utils";
| Utility | Purpose |
|---|---|
format(data) |
Pretty-print JSON output for discovery scripts |
normalize(arr, key) |
Convert array to Record<string, T> keyed by field |
countBy(arr, fn) |
Count items by category (e.g., spaces by type) |
groupBy(arr, fn) |
Group items by category |
parseArgs() |
Parse CLI flags (--max-spaces 25) |
parallel(items, fn, opts) |
Execute async operations in parallel with fallback |
Defensive Coding
// ❌ WRONG — crashes if space.links is undefined
const webUrl = space.links.webui;
// ✅ CORRECT — safe access with fallback
const webUrl = space.links?.webui ?? "N/A";
Fields commonly missing at runtime: space.links, space.homepage, page.version, result._links, start, limit, size.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
returned unexpected response: null |
OAuth token expired | Re-authenticate |
CQL parse error |
Unquoted reserved word | Double-quote string values |
start/limit/size undefined |
Normal — pagination fields are optional | Use _links.next for pagination |
TypeError: undefined is not an object |
Accessing nested property on missing field (e.g., space.links.webui) |
Use optional chaining: space.links?.webui |
UNAUTHENTICATED |
Token expired mid-session | Re-run discovery |
Page does not exist |
Wrong cloudId or pageId | Verify cloudId from discovery |
| Discovery returns 0 spaces | Missing OAuth scopes | Ensure read:confluence-space.summary granted |
Connection Resilience
- Always use
cloudIdfrom discovery output, never hardcode - If discovery fails with auth errors, re-authenticate the Atlassian connection and re-run discovery
- For multi-site workspaces, discovery returns all accessible sites in
resources[]— pick the correctcloudIdfor the target site
Output Format
Present results as a structured report:
Tracking Confluence Report
══════════════════════════
Resources discovered: [count]
Resource Status Key Metric Issues
──────────────────────────────────────────────
[name] [ok/warn] [value] [findings]
Summary: [total] resources | [ok] healthy | [warn] warnings | [crit] critical
Action Items: [list of prioritized findings]
Target ≤50 lines of output. Use tables for multi-resource comparisons.
Anti-Hallucination Rules
- NEVER assume resource names — always discover via CLI/API in Phase 1 before referencing in Phase 2.
- NEVER fabricate metric names or dimensions — verify against the service documentation or
--helpoutput. - NEVER mix CLI commands between service versions — confirm which version/API you are targeting.
- ALWAYS use the discovery → verify → analyze chain — every resource referenced must have been discovered first.
- ALWAYS handle empty results gracefully — an empty response is valid data, not an error to retry.
Counter-Rationalizations
| Shortcut | Counter | Why |
|---|---|---|
| "I'll skip discovery and check known resources" | Always run Phase 1 discovery first | Resource names change, new resources appear — assumed names cause errors |
| "The user only asked for a quick check" | Follow the full discovery → analysis flow | Quick checks miss critical issues; structured analysis catches silent failures |
| "Default configuration is probably fine" | Audit configuration explicitly | Defaults often leave logging, security, and optimization features disabled |
| "Metrics aren't needed for this" | Always check relevant metrics when available | API/CLI responses show current state; metrics reveal trends and intermittent issues |
| "I don't have access to that" | Try the command and report the actual error | Assumed permission failures prevent useful investigation; actual errors are informative |