Skip to main content
Version: Latest

Confluence (user passthrough)

Proxy for the Atlassian Confluence Cloud REST API via the managed gateway.

What this connector does

Gives agents gated, per-user access to Confluence for:

  • Searching content with CQL.
  • Reading pages (with body), their children, labels, and attachments.
  • Listing spaces and the pages within them.
  • Reading and posting page (footer) comments.
  • Creating and updating pages.

All operations are gated by PBAC policy. Every call flows: agent → gateway → /introspect (with this connector's resource_type) → OPA decision → Confluence API → response.

This connector ships in idp_passthrough mode only: the gateway forwards each calling user's own Atlassian OAuth token (captured when they log in to PolicyArc through the Atlassian IdP), as Authorization: Bearer. Confluence enforces its own space and page permissions, and its audit shows the real person. There is no shared service-account mode — Atlassian Cloud exposes no service identity at the OAuth layer (see the Atlassian products page).

API surface

Content and spaces use the Confluence v2 API (/wiki/api/v2/*), Atlassian's current REST API. CQL search uses the v1 endpoint (/wiki/rest/api/search) because v2 has no CQL equivalent.

Because the v2 API is used, the connector requests granular Atlassian OAuth scopes (read:page:confluence, write:page:confluence, read:space:confluence, read:comment:confluence, write:comment:confluence, read:attachment:confluence, read:label:confluence, search:confluence, plus read:me and offline_access) — not the classic read:confluence-content.all family, which the v2 API rejects with HTTP 401 "scope does not match". Grant these granular scopes on the Atlassian 3LO app's Confluence API permission.

Two Confluence quirks the tools document inline:

  • Body representation. Page and comment bodies use the storage representation (Confluence XHTML), e.g. {"representation": "storage", "value": "<p>Hello</p>"} — not plain text.
  • Optimistic locking on update. update_page requires the next version number. Call get_page first, read version.number, then pass version: {"number": N+1}.

Prerequisites

  • A Confluence Cloud account with access to the spaces/pages you want to reach.
  • An Atlassian IdP registered in PolicyArc (provider key atlassian). Pick Atlassian in the IdP setup wizard — the bundled preset fills in the endpoints and the required audience=api.atlassian.com authorize param. You supply the Client ID / secret from an Atlassian OAuth 2.0 (3LO) app (developer.atlassian.com) whose callback URL is the AS's /oauth2/callback, with the Confluence API permission granted for the connector's granular scopes (see above).
  • Your site's cloud ID — get it from https://<site>.atlassian.net/_edge/tenant_info.

See Atlassian products — multi-connector setup for the full 3LO walkthrough (shared with the Jira connector).

Install

Provide two setup fields: Atlassian site (the subdomain, e.g. acme) and Atlassian cloud ID (the UUID). Passthrough installs need no secret. On install, the gateway merges this connector's upstream_scopes into the Atlassian IdP row, so users re-consent to the union on their next login.

Verify

Request a token with a Confluence scope and call through the gateway as a logged-in user (passthrough needs a user session, so client_credentials will return a 502 "no stored IdP token"):

curl "https://<your-as>/gateway/confluence/wiki/api/v2/spaces" \
-H "Authorization: Bearer <user-token>"

A 200 with the spaces JSON confirms: token → introspect allowed → gateway proxied → Confluence responded as the user.

What can go wrong

SymptomCauseFix
502 "no stored IdP token"Caller never logged in through the atlassian IdP (or used client_credentials, which has no user context)Re-authenticate via Atlassian.
403 from ConfluenceThe user lacks permission on the space/page, or the token is missing a scopeCheck the user's Confluence permissions and that the Atlassian app granted the scope.
400 on create/updateBody sent as plain text instead of the storage representationWrap content as {"representation": "storage", "value": "<p>...</p>"}.
409 on updateStale version.numberRe-read with get_page and pass version.number + 1.
403 from the gatewayAgent lacks the route's PBAC scopeRequest a token with the matching confluence:* scope.
Stored IdP token missing required scopesConnector installed after the user logged inUser signs out and back in to re-consent.

Reference

Manifest reference

  • ID: identos.confluence
  • Version: 1.1.0
  • Resource type: urn:connector:identos:confluence
  • Capabilities: mcp

Supported auth modes

TypeDetails
idp_passthroughrequires IdP atlassian; setup fields: site, cloudid

Setup fields

IDLabelDefaultSecret?Notes
siteAtlassian sitenoplaceholder: acme / The subdomain of your Atlassian cloud URL (e.g. 'acme' for acme.atlassian.net). Used in resource IDs and to locate the cloud ID.
cloudidAtlassian cloud IDnoplaceholder: 11223344-5566-7788-9900-aabbccddeeff / UUID identifying your Atlassian site. Get from https://{site}.atlassian.net/_edge/tenant_info (the cloudId field). Required because Atlassian Cloud OAuth tokens route through https://api.atlassian.com/ex/confluence/\{cloudid\}/, not the site URL directly.

Scopes

Scope
confluence:content:read
confluence:content:write
confluence:content:delete
confluence:space:read
confluence:comments:read
confluence:comments:write

Routes

MethodPatternScopeResource template
GET/wiki/rest/api/searchconfluence:content:read
GET/wiki/api/v2/pages/{id}confluence:content:readconfluence://{{site}}/pages/{{id}}
GET/wiki/api/v2/pagesconfluence:content:read
GET/wiki/api/v2/pages/{id}/childrenconfluence:content:readconfluence://{{site}}/pages/{{id}}
GET/wiki/api/v2/pages/{id}/labelsconfluence:content:readconfluence://{{site}}/pages/{{id}}
POST/wiki/rest/api/content/{id}/labelconfluence:content:writeconfluence://{{site}}/pages/{{id}}
DELETE/wiki/rest/api/content/{id}/labelconfluence:content:writeconfluence://{{site}}/pages/{{id}}
GET/wiki/api/v2/pages/{id}/attachmentsconfluence:content:readconfluence://{{site}}/pages/{{id}}
GET/wiki/api/v2/attachments/{id}confluence:content:readconfluence://{{site}}/attachments/{{id}}
GET/wiki/api/v2/spacesconfluence:space:read
GET/wiki/api/v2/spaces/{id}confluence:space:readconfluence://{{site}}/spaces/{{id}}
GET/wiki/api/v2/spaces/{id}/pagesconfluence:space:readconfluence://{{site}}/spaces/{{id}}
GET/wiki/api/v2/pages/{id}/footer-commentsconfluence:comments:readconfluence://{{site}}/pages/{{id}}
GET/wiki/api/v2/pages/{id}/inline-commentsconfluence:comments:readconfluence://{{site}}/pages/{{id}}
GET/wiki/api/v2/footer-comments/{id}confluence:comments:readconfluence://{{site}}/comments/{{id}}
GET/wiki/api/v2/pages/{id}/versionsconfluence:content:readconfluence://{{site}}/pages/{{id}}
GET/wiki/api/v2/folders/{id}confluence:content:readconfluence://{{site}}/folders/{{id}}
GET/wiki/api/v2/blogpostsconfluence:content:read
GET/wiki/api/v2/blogposts/{id}confluence:content:readconfluence://{{site}}/blogposts/{{id}}
POST/wiki/api/v2/pagesconfluence:content:write
POST/wiki/api/v2/blogpostsconfluence:content:write
POST/wiki/api/v2/foldersconfluence:content:write
PUT/wiki/api/v2/pages/{id}confluence:content:writeconfluence://{{site}}/pages/{{id}}
PUT/wiki/rest/api/content/{id}/move/{position}/{targetId}confluence:content:writeconfluence://{{site}}/pages/{{id}}
PUT/wiki/api/v2/footer-comments/{id}confluence:comments:writeconfluence://{{site}}/comments/{{id}}
POST/wiki/api/v2/footer-commentsconfluence:comments:write
POST/wiki/api/v2/inline-commentsconfluence:comments:write
DELETE/wiki/api/v2/pages/{id}confluence:content:deleteconfluence://{{site}}/pages/{{id}}
DELETE/wiki/api/v2/folders/{id}confluence:content:deleteconfluence://{{site}}/folders/{{id}}
DELETE/wiki/api/v2/footer-comments/{id}confluence:comments:writeconfluence://{{site}}/comments/{{id}}

MCP tools

NameScopeDescription
search_pagesconfluence:content:readSearch Confluence content with CQL (Confluence Query Language). Example cql: "type=page AND space=DEV AND text ~ "onboarding"". Returns {results, _links:{next}, ...}; pass the opaque cursor from _links.next (or start) to page. This is Confluence's v1 search endpoint — the v2 API has no CQL equivalent.
get_pageconfluence:content:readGet a single Confluence page by numeric id. By default the body is NOT included — pass body-format=storage (or atlas_doc_format) to get the content. The response carries version.number, which you MUST read and increment before calling update_page (optimistic locking).
list_pagesconfluence:content:readList pages, optionally filtered by space id, title, or status. Paginated via the opaque cursor from _links.next.
get_page_childrenconfluence:content:readList the direct child pages of a Confluence page (one level). Paginated via cursor.
list_page_labelsconfluence:content:readList the labels attached to a Confluence page.
add_labelconfluence:content:writeAdd a label to a Confluence page (or other content) by id. Sends a single label object as the body; Confluence also accepts an array but this tool adds one at a time. This is the v1 labels endpoint — v2 has no label-write route.
remove_labelconfluence:content:writeRemove a label from a Confluence page by id. The label is passed as the name query parameter (this form tolerates '/' in label names). Reversible by re-adding, so it is not marked always-ask.
list_page_attachmentsconfluence:content:readList attachments on a Confluence page (metadata only — title, mediaType, fileSize, downloadLink).
get_attachmentconfluence:content:readGet a single attachment's metadata by id (title, mediaType, fileSize, comment, version, and a downloadLink). Returns metadata, not the binary content.
list_spacesconfluence:space:readList Confluence spaces the caller can see. Use the returned numeric space id for list_pages/list_space_pages and the key (e.g. DEV) in CQL. Paginated via cursor.
get_spaceconfluence:space:readGet a single Confluence space by its numeric id.
list_space_pagesconfluence:space:readList pages in a Confluence space. Paginated via cursor.
list_page_commentsconfluence:comments:readList footer comments on a Confluence page. (Footer comments are the page-level comment thread; inline comments are list_inline_comments.)
list_inline_commentsconfluence:comments:readList root-level inline comments on a Confluence page (comments anchored to a text selection, distinct from footer comments). Paginated via cursor.
get_commentconfluence:comments:readGet a single footer comment by its id, including its body and version.number (needed to update_comment).
list_page_versionsconfluence:content:readList the version history of a Confluence page (each entry has number, authorId, createdAt, and the change message). Paginated via cursor.
get_folderconfluence:content:readGet a single Confluence folder by its numeric id (title, parentId, spaceId). Folders are a v2 content type that organise pages within a space.
list_blog_postsconfluence:content:readList blog posts, optionally filtered by space id, title, or status. Paginated via the opaque cursor from _links.next.
get_blog_postconfluence:content:readGet a single blog post by numeric id. By default the body is NOT included — pass body-format=storage (or atlas_doc_format) to get the content.
create_pageconfluence:content:writeCreate a Confluence page. The body uses the storage representation (Confluence XHTML), NOT plain text. Minimal valid shape: {"spaceId": "123456", "status": "current", "title": "My page", "body": {"representation": "storage", "value": "<p>Hello</p>"}}. Get spaceId from list_spaces. To nest under a parent, add "parentId": "<pageId>".
create_blog_postconfluence:content:writeCreate a Confluence blog post. Same body caveat as create_page — the body uses the storage representation (Confluence XHTML). Shape: {"spaceId": "123456", "status": "current", "title": "My post", "body": {"representation": "storage", "value": "<p>Hello</p>"}}.
create_folderconfluence:content:writeCreate a folder in a Confluence space to organise pages. Shape: {"spaceId": "123456", "title": "My folder"}. To nest under an existing folder or page, add "parentId". Folders are a v2 content type; this needs the write:folder:confluence Atlassian scope.
update_pageconfluence:content:writeUpdate a Confluence page, overwriting its body. OPTIMISTIC LOCKING: call get_page first, read version.number, and pass version:{number: N+1}. The body uses the storage representation (same caveat as create_page). Required fields: id, status, title, body, version. Example: {"id": "123", "status": "current", "title": "My page", "body": {"representation": "storage", "value": "<p>Updated</p>"}, "version": {"number": 4, "message": "edit via agent"}}.
move_pageconfluence:content:writeMove a Confluence page relative to another page. position = 'append' makes the page a child of targetId; 'before'/'after' place it as a sibling immediately before/after targetId. Nothing is deleted — the page is relocated — but it overwrites the page's parent, so it is marked a write. This is the v1 content-move endpoint (v2 has no move).
update_commentconfluence:comments:writeUpdate a footer comment, overwriting its body. OPTIMISTIC LOCKING like update_page: call get_comment first, read version.number, pass version:{number: N+1}. Body uses the storage representation. Shape: {"id": "123", "body": {"representation": "storage", "value": "<p>Edited.</p>"}, "version": {"number": 2}}.
add_commentconfluence:comments:writeAdd a footer comment to a Confluence page. Body uses the storage representation. Shape: {"pageId": "123", "body": {"representation": "storage", "value": "<p>Looks good.</p>"}}. To reply to an existing comment, add "parentCommentId".
add_inline_commentconfluence:comments:writeAdd an inline comment anchored to a text selection on a page. Shape: {"pageId": "123", "body": {"representation": "storage", "value": "<p>Question.</p>"}, "inlineCommentProperties": {"textSelection": "the exact highlighted text", "textSelectionMatchCount": 1, "textSelectionMatchIndex": 0}}. The textSelection must match text in the page body.
delete_pageconfluence:content:deleteDelete a Confluence page by id. By default this moves the page to the trash (restorable); it still removes the page from its space. Needs the delete:page:confluence Atlassian scope.
delete_folderconfluence:content:deleteDelete a Confluence folder by id. Moves the folder to the trash; its contents go with it, so this can remove far more than one page. Needs the delete:folder:confluence Atlassian scope.
delete_commentconfluence:comments:writeDelete a footer comment by id. This is permanent — comments are not trashed like pages. Kept on comments:write (not the content:delete scope) because it removes a single comment, not page content.