Introduction
Confluence, Atlassian's collaboration tool, is essential for centralizing enterprise documentation, technical wikis, and DevOps playbooks. In 2026, with the rise of CI/CD pipelines and AI automations, mastering its REST API is crucial for scaling workflows. Imagine automatically generating release notes pages from Jira, importing reports from GitHub, or migrating thousands of pages without manual intervention.
This advanced tutorial guides you step-by-step to build a Node.js bot that interacts with the Confluence Cloud API: token-based authentication, space and page creation, attachment uploads, advanced CQL queries, and robust error handling. Every script is complete and functional, ready to copy-paste. By the end, you'll automate your workflows like a pro, saving hours per sprint. Ideal for DevOps architects or Atlassian admins managing instances with >10k pages.
Prerequisites
- Confluence Cloud Premium or Enterprise account (higher API rate limits)
- Node.js 20+ installed (check with
node -v) - Code editor like VS Code with TypeScript extension
- Advanced knowledge of TypeScript, REST APIs, and async/await
- Admin access or 'Create Space' permission in Confluence
Initialize the Node.js Project
mkdir confluence-bot
cd confluence-bot
npm init -y
npm install axios dotenv typescript ts-node @types/node
npm install -D @types/axios
npx tsc --init
mkdir srcThis bash script sets up a dedicated Node.js project, installs Axios for HTTP calls, Dotenv for environment variables, and configures TypeScript. It creates a zero-friction setup with ts-node to run .ts files directly. Avoid globals: always use a dedicated package.json.
Get Your Confluence API Token
Go to Atlassian Account Settings > Security > API Tokens > Create token. Name it 'confluence-bot-2026'. Copy the token (format ~abcd1234). Use your Atlassian email as the username for Basic Auth. Store them securely in .env, never hardcode. Rate limit: 100 req/min on Premium; monitor X-Seraph-LoginReason headers for debugging.
Configure package.json
{
"name": "confluence-bot",
"version": "1.0.0",
"main": "src/index.ts",
"scripts": {
"start": "ts-node src/index.ts",
"dev": "ts-node --watch src/index.ts"
},
"dependencies": {
"axios": "^1.7.2",
"dotenv": "^16.4.5",
"typescript": "^5.5.3",
"ts-node": "^10.9.2",
"@types/node": "^22.3.0"
},
"devDependencies": {
"@types/axios": "^0.14.0"
}
}This complete package.json adds start and dev scripts for easy bot launching. Axios handles HTTPS requests with auto-retry, Dotenv loads secrets. Strict TypeScript catches compile-time errors. Run with npm run dev for hot-reload in development.
.env Environment File
CONFLUENCE_BASE_URL=https://votre-site.atlassian.net/wiki
CONFLUENCE_EMAIL=votre.email@entreprise.com
CONFLUENCE_API_TOKEN=ATATT3x...VotreTokenComplet
SPACE_KEY=MYSPACEReplace with your actual values: BASE_URL without /api, SPACE_KEY from the target space (found via /spaces). Add .env to .gitignore. Dotenv loads it automatically. Pitfall: URL without 'https://' or trailing slash causes 404; test with curl first.
Implement Authentication and List Spaces
The Confluence API uses Basic Auth (email:token base64-encoded). Test manually first: curl -u email:token $BASE_URL/rest/api/space. Our Axios client wraps this. This first script lists spaces to validate auth, with 401/429 error handling.
Basic API Client with Authentication
import axios, { AxiosInstance, AxiosError } from 'axios';
import dotenv from 'dotenv';
dotenv.config();
const config = {
baseURL: process.env.CONFLUENCE_BASE_URL + '/rest/api',
auth: {
username: process.env.CONFLUENCE_EMAIL!,
password: process.env.CONFLUENCE_API_TOKEN!
},
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }
};
export const client: AxiosInstance = axios.create(config);
client.interceptors.response.use(
(response) => response,
(error: AxiosError) => {
if (error.response?.status === 429) {
console.error('Rate limit atteinte, attendez 1min');
throw error;
}
console.error('Erreur API:', error.response?.data || error.message);
throw error;
}
);
export async function listSpaces(): Promise<any[]> {
const res = await client.get('/space');
return res.data.results;
}
// Test
(async () => {
try {
const spaces = await listSpaces();
console.log('Spaces:', spaces.map((s: any) => ({ key: s.key, name: s.name })));
} catch (err) {
console.error('Échec:', err);
}
})();This reusable Axios client handles Basic auth, baseURL, headers, and an interceptor for rate limits (manual retry required). listSpaces() fetches all spaces with implicit pagination. Run npm run start: outputs valid JSON or debugged errors. Pitfall: expired token → 401, regenerate it.
Create a Space Programmatically
Required permissions: 'Create Space' global. The /space POST endpoint creates 'global' or 'personal' spaces. Use type: 'global' for teams. Response includes id and key for future references.
Create Space Function
import { client } from './confluenceClient';
export async function createSpace(name: string, key: string): Promise<any> {
const payload = {
type: 'global',
status: 'current',
name,
key,
description: { plain: { value: 'Space créé via API en 2026', representation: 'plain' } }
};
const res = await client.post('/space', payload);
console.log('Space créé:', res.data);
return res.data;
}
// Usage
(async () => {
await createSpace('Mon Bot Space', 'BOTSPACE');
})();Minimal but complete payload: key must be unique (uppercase, no spaces). Plain-text description avoids HTML parsing. Returns the full space object. Common error: existing key → 409 Conflict, check first via listSpaces(). Integrate into GitHub Actions pipelines.
Manage Pages and Content
Pages are 'content' of type 'page'. Create them under a parent (root or existing page). Use body.storage for rich HTML, or editor2 for ADF (new 2026 format). Auto-versioning.
Create and Update a Page
import { client } from './confluenceClient';
export async function createPage(spaceKey: string, title: string, bodyHtml: string, parentId?: string): Promise<any> {
const payload = {
type: 'page',
status: 'current',
space: { key: spaceKey },
title,
body: {
storage: {
value: bodyHtml,
representation: 'storage'
}
},
...(parentId && { parent: { id: parentId } })
};
const res = await client.post('/content', payload);
console.log('Page créée ID:', res.data.id);
return res.data;
}
export async function updatePage(pageId: string, version: number, bodyHtml: string): Promise<any> {
const payload = {
id: pageId,
type: 'page',
title: 'Titre mis à jour',
version: { number: version + 1 },
body: {
storage: { value: bodyHtml, representation: 'storage' }
}
};
const res = await client.put(`/content/${pageId}`, payload);
console.log('Page updatée, nouvelle version:', res.data.version.number);
return res.data;
}
// Usage exemple
(async () => {
const page = await createPage('BOTSPACE', 'Page Bot Test', '<h1>Hello 2026!</h1><p>Automatisé.</p>');
await updatePage(page.id, page.version.number, '<h1>Mis à jour!</h1><p>Version 2.</p>');
})();Creation with parentId for hierarchy. Updates must increment version (fetch via GET /content/{id} first). Valid HTML in 'storage' (macro support). Pitfall: body without 'representation' → 400 Bad Request. Scale for 100+ pages in batches.
Upload an Attachment
import { client } from './confluenceClient';
import fs from 'fs';
export async function uploadAttachment(pageId: string, filePath: string): Promise<any> {
if (!fs.existsSync(filePath)) throw new Error('Fichier non trouvé');
const formData = new FormData();
formData.append('file', fs.createReadStream(filePath), { filename: filePath.split('/').pop() });
formData.append('comment', 'Upload auto 2026');
const res = await client.post(`/content/${pageId}/child/attachment`, formData, {
headers: formData.getHeaders()
});
console.log('Attachment uploadé:', res.data);
return res.data;
}
// Add npm install form-data @types/form-data for FormData polyfill if needed
// Usage
// await uploadAttachment('123456', './rapport.pdf');Uses FormData for multipart upload to /child/attachment. fs.createReadStream handles large files (>100MB OK on Enterprise). Auto comment for audit trail. Node pitfall: Native FormData is ES2023; otherwise install 'form-data'. Attachments version like pages.
Advanced Queries with CQL
CQL (Confluence Query Language) filters like SQL: type=page and space=KEY and text~"keyword". Paginate with start/limit. Perfect for automated reports or cleanups.
CQL Query for Pages
import { client } from './confluenceClient';
export async function searchPages(cql: string, start = 0, limit = 25): Promise<any> {
const params = new URLSearchParams({ cql, start: start.toString(), limit: limit.toString() });
const res = await client.get(`/content/search?${params.toString()}`);
console.log(`Résultats (${res.data.size}/${res.data.totalSize}):`, res.data.results.map((p: any) => ({ id: p.id, title: p.title }))) ;
return res.data;
}
// Advanced usage
(async () => {
await searchPages('type=page and space=BOTSPACE and created >= -1d');
await searchPages('type=page and text ~ "erreur"', 0, 100);
})();Full-text CQL search with operators (and, ~ fuzzy). Manual pagination for >500 results. totalSize for loops. More performant than GET /space/{key}/page. Pitfall: Malformed CQL → 400; test in Confluence UI 'Advanced Search' first.
Best Practices
- Rate limiting: Implement exponential backoff (setTimeout) for >50 req/min; use webhooks for push events instead of polling.
- Security: Rotate tokens every 90 days; use App Links OAuth2 for server-side apps (more scalable than Basic).
- Error handling: Always log
error.response.data; retry idempotents (POST/PUT) max 3x. - Scalability: Batch via
/bulkendpoint; migrate to GraphQL beta if Premium+. - Testing: Mock Axios with MSW; CI/CD with Playwright for end-to-end.
Common Errors to Avoid
- 401 Unauthorized: Invalid/expired token or wrong email; verify base64
echo -n 'email:token' | base64. - 409 Conflict: Duplicate space/page key or outdated version; fetch latest before update.
- 413 Payload Too Large: Attachments >2GB fail; chunk or use S3 proxy.
- Empty CQL: Always
limit<100or timeout; no wildcards%(use~).
Next Steps
- Official docs: Confluence REST API
- Advanced plugins: ScriptRunner for in-app Groovy; Forge for custom apps.
- Integrate with Jira:
/rest/api/3/issuesync. - Check our Learni trainings on Atlassian DevOps for full CI/CD.
- Example GitHub repo: provided at the end of the article (fork it!)