- name
- confluence-operations
- description
- This skill should be used when the user wants to do Confluence work — "create a Confluence page", "update a page", "edit a wiki page", "create a child page", "create a space", "list spaces", "comment on a page", "add a label", "attach a file in Confluence", or any everyday Confluence operation. Provides plain-language playbooks that drive the Confluence Cloud REST API v2.
# Confluence Common Operations
Plain-language playbooks for everyday Confluence work, each driving the REST API v2. For exact method signatures and every field, open the `api-reference` skill's matching `references/confluence/*.md` file.
## Before anything: ensure access
If `ATLASSIAN_*` aren't confirmed this session, run the `setup` skill first (one `GET /wiki/api/v2/spaces?limit=1` call). Define these once:
```bash
# Classic token → site URL; scoped token (ATLASSIAN_CLOUD_ID set) → API gateway — see `setup`
CONF_ROOT="${ATLASSIAN_CLOUD_ID:+https://api.atlassian.com/ex/confluence/${ATLASSIAN_CLOUD_ID}}"
CONF_ROOT="${CONF_ROOT:-${ATLASSIAN_SITE_URL%/}}"
CONF="${CONF_ROOT}/wiki/api/v2"
AUTH=(-u "${ATLASSIAN_EMAIL}:${ATLASSIAN_API_TOKEN}" -H "Accept: application/json")
JSON=(-H "Content-Type: application/json")
```
## The golden rules (why workflows look the way they do)
1. **`/wiki/api/v2` base** — the `/wiki` prefix is mandatory; omitting it is the usual cause of a `404`.
2. **Bodies carry a `representation`** — `{"representation":"storage","value":"<p>…</p>"}` (XHTML) or `atlas_doc_format` (ADF JSON string).
3. **Update = read-then-write.** Fetch the current `version.number`, then `PUT` with `number + 1`. A stale number returns `409`. **In a space that requires approval before publishing, a direct `PUT` on a published page will return `409` whatever the version** (CHANGE-3432, announced 2026-09-28, rollout pending) — see "update a page safely" below.
4. **`spaceId` is numeric**, not the space key — resolve the key first.
5. **Cursor pagination** — follow `_links.next`, don't compute offsets.
6. **Confirm destructive actions** — `DELETE /pages/{id}` (especially `?purge=true`).
## Workflow: resolve a space key → id, list its pages
```bash
SPACE_ID=$(curl -s "${AUTH[@]}" "${CONF}/spaces?keys=PROJ" | jq -r '.results[0].id')
curl -s "${AUTH[@]}" "${CONF}/pages?space-id=${SPACE_ID}&limit=25&sort=-modified-date" \
| jq '.results[] | {id, title, status}'
```
(→ `spaces.md`, `pages-blogposts.md`)
## Workflow: create a page
```bash
curl -s "${AUTH[@]}" "${JSON[@]}" -X POST "${CONF}/pages" -d "{
\"spaceId\": \"${SPACE_ID}\",
\"status\": \"current\",
\"title\": \"Onboarding Guide\",
\"body\": {\"representation\": \"storage\", \"value\": \"<h1>Welcome</h1><p>Start here.</p>\"}
}" | jq '{id, title, _links}'
```
Add `"parentId":"<pageId>"` to nest it under another page. (→ `pages-blogposts.md`)
## Workflow: update a page safely (the #1 Confluence pitfall)
```bash
PAGE_ID=12345
# 1. Read the current version number (and title)
read VER TITLE < <(curl -s "${AUTH[@]}" "${CONF}/pages/${PAGE_ID}?body-format=storage" \
| jq -r '"\(.version.number) \(.title)"')
# 2. Write with version.number + 1
curl -s "${AUTH[@]}" "${JSON[@]}" -X PUT "${CONF}/pages/${PAGE_ID}" -d "{
\"id\": \"${PAGE_ID}\",
\"status\": \"current\",
\"title\": \"${TITLE}\",
\"version\": {\"number\": $((VER+1)), \"message\": \"Updated via API\"},
\"body\": {\"representation\": \"storage\", \"value\": \"<h1>Welcome</h1><p>Updated content.</p>\"}
}" | jq '{id, version: .version.number}'
```
Skipping the version bump → `409 Conflict`.
**Approval spaces (announced 2026-09-28, rollout pending).** Once enabled, in a space that needs approval before publishing this `PUT` (and `PUT /pages/{id}/title`) returns `409` even with the right `version.number`. Do not loop on retries. Atlassian's guidance is to save the change as a draft, complete the approval, and publish the approved draft — but no REST draft→approval→publish flow is documented yet, so use the Confluence UI or ask a Confluence admin. (→ `pages-blogposts.md`)
## Workflow: list child pages
```bash
curl -s "${AUTH[@]}" "${CONF}/pages/${PAGE_ID}/direct-children?limit=50" | jq '.results[] | {id, type, title}'
```
`GET /pages/{id}/children` is deprecated — use `direct-children` (it also lists folders, databases, whiteboards and Smart Links). (→ `pages-blogposts.md`)
## Workflow: create a space
```bash
curl -s "${AUTH[@]}" "${JSON[@]}" -X POST "${CONF}/spaces" \
-d '{"key":"PROJ","name":"Project PROJ"}' | jq '{id, key, name}'
```
(→ `spaces.md`)
## Workflow: comment on a page
```bash
curl -s "${AUTH[@]}" "${JSON[@]}" -X POST "${CONF}/footer-comments" -d "{
\"pageId\": \"${PAGE_ID}\",
\"body\": {\"representation\": \"storage\", \"value\": \"<p>Looks good — shipping.</p>\"}
}" | jq '{id}'
```
Reply by adding `"parentCommentId":"<id>"`. (→ `comments-attachments.md`)
## Workflow: add a label (v1 endpoint)
Label writes aren't in v2 — use the v1 REST API:
<!-- reason: Confluence REST v1 endpoint: label writes exist only in v1, the v2 spec has no equivalent -->
<!-- plugin-test: skip -->
```bash
curl -s -u "${ATLASSIAN_EMAIL}:${ATLASSIAN_API_TOKEN}" "${JSON[@]}" -X POST \
"${CONF_ROOT}/wiki/rest/api/content/${PAGE_ID}/label" \
-d '[{"prefix":"global","name":"release-1-2"}]'
```
Read labels via v2: `GET ${CONF}/pages/${PAGE_ID}/labels`. (→ `labels-content-properties.md`)
## Workflow: attach a file (v1 endpoint, multipart)
<!-- reason: Confluence REST v1 endpoint: attachment upload exists only in v1, the v2 spec has no equivalent -->
<!-- plugin-test: skip -->
```bash
curl -s -u "${ATLASSIAN_EMAIL}:${ATLASSIAN_API_TOKEN}" \
-H "X-Atlassian-Token: nocheck" \
-F "file=@./diagram.png" \
"${CONF_ROOT}/wiki/rest/api/content/${PAGE_ID}/child/attachment"
```
Read attachment metadata via v2: `GET ${CONF}/pages/${PAGE_ID}/attachments`. (→ `comments-attachments.md`)
## Workflow: full-text search (v1 CQL)
v2 lists filter by `space-id`/`title`/`status`. For real search use CQL on v1:
<!-- reason: Confluence REST v1 endpoint: CQL search exists only in v1, the v2 spec has no equivalent -->
<!-- plugin-test: skip -->
```bash
curl -s "${AUTH[@]}" \
"${CONF_ROOT}/wiki/rest/api/search?cql=space=PROJ%20AND%20text~%22login%20error%22"
```
(→ `pages-blogposts.md` Notes)
When a call fails (400/401/403/404/409/429), switch to the `troubleshoot` skill.
View on GitHub