Skip to content
Learni
View all tutorials
Outils Collaboration

How to Automate Confluence with Its REST API in 2026

Lire en français

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

terminal
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 src

This 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

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

.env
CONFLUENCE_BASE_URL=https://votre-site.atlassian.net/wiki
CONFLUENCE_EMAIL=votre.email@entreprise.com
CONFLUENCE_API_TOKEN=ATATT3x...VotreTokenComplet
SPACE_KEY=MYSPACE

Replace 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

src/confluenceClient.ts
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

src/createSpace.ts
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

src/managePages.ts
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

src/uploadAttachment.ts
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

src/cqlQuery.ts
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 /bulk endpoint; 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<100 or 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/issue sync.
  • Check our Learni trainings on Atlassian DevOps for full CI/CD.
  • Example GitHub repo: provided at the end of the article (fork it!)