O'Hara Group

Open API Documentation for Confluence

Contents

Description

A Confluence macro for generating beautiful documentation from an Open API (a.k.a. Swagger)-compliant JSON or YAML definition.

Based on the open source Swagger UI, this app is a macro wrapper enabling API documentation to be easily embedded into a Confluence page.

Usage 🔗

  1. Start by creating/editing a page in Confluence
  2. Insert the macro either
  3. Specify your Open API-compliant JSON or YAML definition either

How it Works 🔗

The diagram below describes how the Cloud version of the app works.

  1. Confluence page requested
  2. Macro assets loaded
  3. (Optional) JSON/YAML URL fetched
  4. API documentation is generated

Notes 🔗

Authenticated URLs 🔗

URLs secured by HTTP Basic Authentication (username and password) or personal access tokens are supported. Simply provide a username and password or access token for the URL in the macro configuration dialog.

IMPORTANT SECURITY INFORMATION REGARDING URL CREDENTIALS!

URLs are fetched from the client browser. In other words, it is the user's browser (not the Confluence server) that makes the network request to the server hosting your JSON/YAML definition.

Therefore credentials that are required to access the URL need to be made available to the browser.

Values entered in the username/password fields in the macro configuration dialog are stored with the Confluence page content and will be visible in the HTML source of the page.

Our recommendation is to use credentials that grant only the minimum access necessary. In most cases, you should limit the credentials to read-only access to the URL.

The underlying assumption is that if a user is permitted to view the rendered API documentation in Confluence, there is no risk in the same user having read access to the source JSON/YAML definition; so you should bear this in mind when deciding how/where to host your API definition files.

Configuring CORS 🔗

When accessing JSON/YAML definitions at a URL or in a git repository, it is important to note that it will be fetched by the client browser (not the Confluence Server) when the page is rendered.

The URL must be accessible to the end-user's browser, and the fetch request is subject to certain browser restrictions.

CORS (Cross Origin Resource Sharing) is a browser security mechanism that allows Javascript on a page loaded from one "origin" (the app) to access a resource hosted at a different origin (the server hosting your JSON/YAML definition).

Without proper CORS configuration, browsers will block such cross-origin requests, and you may see an error that looks like this:

The above error indicates that Javascript loaded from *.cdn.prod.atlassian-dev.net (the app) attempted to fetch a JSON file from petstore.swagger.io but was prevented due to missing CORS headers.

To resolve this, the origin hosting the JSON/YAML resource being accessed (petstore.swagger.io in the example above) must return a special HTTP header indicating that the origin making the request (*.cdn.prod.atlassian-dev.net in the example above) is allowed.

For most public URLs, a wildcard value is appropriate:

Access-Control-Allow-Origin: *

This allows requests from any origin to access the JSON/YAML content.

For private URLs (requiring credentials or a token to access), CORS specifies that the "*" wildcard cannot be used for the header value. You must provide a specific origin value.

(Confluence Server/Data Center)
Access-Control-Allow-Origin: https://{your Confluence server}

(Confluence Cloud)
Access-Control-Allow-Origin: https://{your_unique_installation_id}.cdn.prod.atlassian-dev.net

For Confluence Cloud, the origin value is specific to your installation. To find your unique installation origin, refer to the macro editor:

We have moved

In April 2026, our Confluence Cloud app moved to Atlassian Forge.

If you had previously configured CORS settings as per above, you may need to update from either:

Access-Control-Allow-Origin: https://confluence-open-api.oharagroup.net

Access-Control-Allow-Origin: https://confluence-open-api.herokuapp.com

to:

Access-Control-Allow-Origin: https://{your_unique_installation_id}.cdn.prod.atlassian-dev.net


Troubleshooting CORS

If you are experiencing CORS related issues, see our comprehensive Guide to troubleshooting CORS.

More information on CORS 🔗

To help understand, let's walk through an example of a page served by Confluence Cloud that includes the Open API Documentation for Confluence macro, configured to display API documentation for the URL https://petstore.swagger.io/v2/swagger.json

In simple cases (such as when no credentials are needed to access the URL), the way it works is as follows:

When Confluence renders the page, the Open API Documentation for Confluence macro runs inside an IFRAME, e.g.

<iframe src="https://{installation_id}.cdn.prod.atlassian-dev.net">
  [macro runs inside here]
</iframe>

Javascript running inside this IFRAME makes the following HTTP request to fetch the URL:

GET /v2/swagger.json HTTP/1.1
Host: petstore.swagger.io
Origin: https://{installation_id}.cdn.prod.atlassian-dev.net

The server hosting the URL responds with:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: application/json

[JSON data here]

The browser checks the Access-Control-Allow-Origin header in the response to see if the origin is permitted.

In this case, a value of * indicates that any origin is permitted, so the browser makes the JSON response available to the Javascript that initiated the request.

(If the origin was not permitted, the browser would instead throw an error and the Javascript would be prevented from accessing the JSON response.)

In cases where credentials are required to access the resource, a process known as "preflight" must first occur. This involves the browser first sending an OPTIONS request to the resource server:

OPTIONS /v2/swagger.json HTTP/1.1
Host: petstore.swagger.io
Origin: https://{installation_id}.cdn.prod.atlassian-dev.net
Access-Control-Request-Method: GET
Access-Control-Request-Headers: Authorization

The server hosting the URL responds with:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://{installation_id}.cdn.prod.atlassian-dev.net
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Authorization

The browser checks the Access-Control-Allow-* headers to make sure it is permitted to make the request, and if so the browser then proceeds with the actual GET request to fetch the resource.

For Confluence Server/Data Center, the app does not run in an IFRAME, so substitute https://{installation_id}.cdn.prod.atlassian-dev.net in the above examples with the URL of your Confluence server.

Mixed Content Errors 🔗

When using the 'Try It Out!' feature to test your API, watch for mixed content issues if your Confluence page is served over https:// and the API host in your JSON or YAML definition is http:// (or vice versa).

Some browsers (e.g. Chrome) will allow Open API to attempt the API request, but block it from reaching the network. 'Try It Out!' will display the request URL and request headers as if the request was sent, but the response headers will show {"error": "no response from server"}.

Other browsers (e.g. Firefox) block Open API from attempting the request at all. 'Try It Out!' will not show anything (so it appears like nothing happens when you click the button).

In either case, check the browser console for errors such as:

Mixed Content: The page at 'https://{your cloud instance}.atlassian.net' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint 'http://your.api.endpoint'. This request has been blocked; the content must be served over HTTPS.

or

Blocked loading mixed active content "http://your.api.endpoint"

Supported Java vendors (Server/Data Center version) 🔗

The app has been verified to support the following Java vendors:

Migrating between Server, Data Center and Cloud versions 🔗

Versions of Open API Documentation for Confluence are available for all Atlassian Confluence platforms (Server, Data Center and Cloud).

The features available in the app are the same, regardless of which platform you use.

Additionally, because data used by the macro is stored within the page content itself, migrating between platforms (e.g. moving from Server to Cloud, from Server to Data Center, or from Cloud back to self-hosted/on-premises) in most cases requires no additional steps.

For example, pages moved to the Cloud using the Confluence Cloud Migration Assistant should display the same as they did in your Server or Data Center instance, or vice-versa.

Confluence page attachments

One exception to the above is when a URL specified in the macro points to a Confluence page attachment (e.g. you have uploaded your JSON/YAML spec as a page attachment, and pointed the macro to the attachment's URL).

During migration between platforms, attachment URLs will likely change, and currently the migration process does not automatically remap the macro to point to the new attachment URL.

After the migration completes, the macro will still be pointing at the attachment in your original (source) Confluence instance.

Our team is investigating options to detect instances of the macro that specify page attachment URLs, and automatically remap these URLs during the migration process.

Until such time, any instances of the macro that uses a page attachment URL will need to be manually updated to the new attachment URL once the migration has completed.

The only changes that may be required are as follows:

Limitations 🔗

OAuth2 flows 🔗

If your API uses OAuth 2.0, the Open API specification defines the following flows that can be specified in your API definition:

However, Swagger UI (and therefore the Try it Out! feature in Open API Documentation for Confluence) currently only supports implicit.

Export to PDF/Word 🔗

The API documentation is generated dynamically in the browser using Javascript.

For this reason, it is not possible to include API documentation when exporting with Export to PDF or Export to Word, as these static export formats are generated on the Confluence server.

JSON/YAML definitions in git repositories 🔗

A question we are frequently asked is: Can I host my JSON/YAML definitions in a git repository? (e.g. GitHub, Bitbucket, GitLab)

📢 Public repositories 🔗

If your git repository is public, in either GitHub, Bitbucket Cloud or GitLab you can simply browse to the definition file in your repository and click the "Raw" button for the URL to use.

Service Example URL
GitHub
https://raw.githubusercontent.com/{owner}/{repo}/{branch}/{file path}
GitLab
https://gitlab.com/{project}/{repo}/raw/{branch}/{file path}
Bitbucket (Cloud)
https://bitbucket.org/{workspace}/{repo}/raw/{commit ref}/{file path}
Bitbucket (Server) 🛑
https://{server name}/mvc/projects/{project}/repos/{repo}/raw/{file path}
Azure DevOps ⚠️
https://dev.azure.com/{org}/{project}/_apis/git/repositories/{repo}/items?$format=octetStream&api-version=6.0&path={file path}

🛑 Bitbucket Server is known to have issues. Some customers have had success using a URL format as shown above (note the "/mvc" in the URL); however this may only work for certain older versions of Bitbucket Server.

⚠️ For public Azure DevOps repositories, you need to construct an API URL with the path to the file as shown above.

🌟 Static site hosting 🔗

Another option is to use the static site hosting offered by your Git provider.

For example, in Bitbucket you can create a repository (either public or private) named {your account}.bitbucket.io, and any files in that repository will be publicly accessible at https://{your account}.bitbucket.io/.

Service Example URL
GitHub Pages
https://{owner}.github.io/{repo}/
GitLab Pages
https://{your account or group}.gitlab.io/{project}/
Bitbucket static site hosting ⚠️
https://{your account}.bitbucket.io/{repo}/

⚠️ Files served from a Bitbucket static site have 15 minute cache expiry; so pushing new updates to your files may not be immediately visible.

🔐 Private repositories 🔗

If your repository is private, some additional steps are required to provide the necessary credentials.

Service Credentials Example URL
GitHub
  1. Create a personal access token
  2. Enter the token in the GitHub Access Token field in the macro configuration dialog
https://api.github.com/repos/{owner}/{repo}/contents/{file path}
GitLab
  1. Create a personal access token that has API scope
  2. Enter the token in the GitLab Access Token field in the macro config dialog
https://gitlab.com/api/v4/projects/{project id}/repository/files/{file path}
Bitbucket (Cloud) ⚠️
  1. Create a username/password that has read access to the repository
  2. Enter these values into the Username and Password fields in the macro configuration dialog

OR

  1. Create a workspace, project or repository access token that has read access to the repository
  2. Enter the token into the Bitbucket Access Token field in the macro configuration dialog
https://api.bitbucket.org/2.0/repositories/{workspace}/{repo}/src/{commit ref}/{file path}
Bitbucket (Server) 🛑 Not supported, see note below
https://{server name}/rest/api/1.0/projects/{project}/repos/{repo}/raw/{file path}
Azure DevOps
  1. Create a personal access token
  2. Enter the token in the Password field in the macro configuration dialog, and leave the Username field empty
https://dev.azure.com/{org}/{project}/_apis/git/repositories/{repo}/items?$format=octetStream&api-version=6.0&path={file path}

⚠️ If you use Bitbucket two-factor verification, you will need to create an app password with read access, and enter this into the Password field in the macro configuration dialog instead of the user password.

🛑 Bitbucket Server does support password protected URLs, and files hosted in private Bitbucket repositories can be accessed with a username/password, however Bitbucket currently doesn't return the correct CORS headers for a pre-flight OPTIONS request.


IMPORTANT SECURITY INFORMATION REGARDING GIT ACCESS TOKENS!

URLs are fetched from the client browser. In other words, it is the user's browser (not the Confluence server) that makes the network request to the server hosting your JSON/YAML definition.

Therefore credentials that are required to access the URL need to be made available to the browser.

Access tokens supplied either as part of the URL, or in the GitHub Access Token field in the macro configuration dialog are stored with the Confluence page content and will be visible in the HTML source of the page.

Our recommendations for using access tokens are:

The underlying assumption is that if a user is permitted to view the rendered API documentation in Confluence, there is no risk in the same user having read access to the source JSON/YAML definition; so you should bear this in mind when deciding how/where to host your API definition files.

Possible workaround for Bitbucket Server 🔗

If you are experiencing issues relating to CORS, one possible workaround is to set up a "proxy" in front of Bitbucket that injects the correct CORS headers. This can be any standard web server or reverse proxy such as nginx or Apache, or a small node.js/express application as shown in the example below.

Example: Nginx reverse proxy 🔗
location / {
  # If your JSON/YAML URL does not require any authentication, you can use a wildcard:
  #   Access-Control-Allow-Origin: *
  #
  # Otherwise, for Confluence Server/Data Center:
  #   Access-Control-Allow-Origin: https://{your Confluence server}
  #
  # For Confluence Cloud:
  #   Access-Control-Allow-Origin: https://{installation_id}.cdn.prod.atlassian-dev.net
	
  add_header 'Access-Control-Allow-Origin' '*';
  add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
	
  # If your JSON/YAML URL requires authentication, be sure to include the Authorization header
  #   Access-Control-Allow-Headers: Authorization,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type
	
  add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
	
  if ($request_method = 'OPTIONS') {
    # Tell client that this pre-flight info is valid for 20 days
		
    add_header 'Access-Control-Max-Age' 1728000;
    add_header 'Content-Type' 'text/plain; charset=utf-8';
    add_header 'Content-Length' 0;

    return 204;
  }
}
Example: node.js/express proxy 🔗

The proxy responds to OPTIONS requests from the browser with the correct CORS headers; and to GET requests by passing them through to your Git server.

const fs = require("fs");
const request = require("request");
const https = require("https");
const express = require("express");
const app = express();
			
/*
 * If your JSON/YAML URL does not require any authentication, you can use a wildcard:
 *   allowOrigin = "*";
 *
 * Otherwise, for Confluence Server/Data Center:
 *   allowOrigin = "https://{your Confluence server}"
 *
 * For Confluence Cloud:
 *   allowOrigin = "https://{installation_id}.cdn.prod.atlassian-dev.net"
 */
const allowOrigin = "https://{installation_id}.cdn.prod.atlassian-dev.net";
const allowMethods = "GET, POST, OPTIONS";
const allowHeaders = "Authorization, DNT, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type"

/*
 * Enter the name of your Bitbucket (or other Git) host here 
 * e.g. targetHost = "https://bitbucket.example.com"
 */
const targetHost = "http://{your Bitbucket server}"
	
/* Handles OPTIONS requests by returning the correct CORS headers */
app.options("/*", (_, res) => {
  try {
    res.setHeader("Access-Control-Allow-Origin", allowOrigin);
    res.setHeader("Access-Control-Allow-Methods", allowMethods);
    res.setHeader("Access-Control-Allow-Headers", allowHeaders);
    res.setHeader("Access-Control-Max-Age", 1728000);
    res.setHeader("Content-Type", "text/plain; charset=utf-8");
    res.setHeader("Content-Length", 0);
    res.status(204).send();
  } catch (e) {
    res.status(500).send(JSON.stringify(e));
  }
};
			
/*
 * Handles GET requests by passing them through to the target host
 * e.g. GET https://proxy.example.com/rest/api/1.0/projects/{project}/repos/{repo}/raw/{file path}
 * would be forwarded to GET https://bitbucket.example.com/rest/api/1.0/projects/{project}/repos/{repo}/raw/{file path}
 */
app.get("/*", (req, res) => {
  "use strict";

  try {
    const opts = {
      method: req.method,
      baseUrl: targetHost,
      url: req.originalUrl,
      headers: {
        authorization: req.headers.authorization
      }
    };

    console.log(`Proxying ${opts.method} request through to ${opts.baseUrl}${opts.url}`);

    request(opts).on("response", res => {
      res.headers["Access-Control-Allow-Origin"] = allowOrigin;
      res.headers["Access-Control-Allow-Methods"] = allowMethods;
      res.headers["Access-Control-Allow-Headers"] = allowHeaders;
    }).pipe(res);
  } catch (e) {
    console.log(e);
    res.status(500).send(JSON.stringify(e));
  }
}; 

/*
 * Place the key.pem & cert.pem files for your SSL/TLS certificate into the same directory as this file.
 * NOTE: Do not use a self-signed certificate, as it will not be trusted by your users' browser
 * Use a certificate issued by a certificate authority that matches the domain of your proxy server 
 */
https.createServer({
  key: fs.readFileSync("key.pem"),
  cert: fs.readFileSync("cert.pem")
}, app).listen(process.env.PORT || 5000);

Still having trouble? 🔗

Troubleshooting CORS

If you are experiencing CORS related issues, see our comprehensive Guide to troubleshooting CORS.

If your API documentation isn't displaying correctly, below is a list of known issues that may be the cause.

Update to the latest app version 🔗

We regularly publish updates to the app that include the latest Swagger UI version, new features and bug fixes.

If you are experiencing issues, one of the first things you should check is whether there is a newer version of the app available for your Confluence version.

Your Confluence administrator can check for updates via the Manage Apps page.

Check for errors in the browser console 🔗

A good tip to help diagnose the problem is to look in your browser's console for any errors. Some errors are caused by the browser itself or a conflict with other apps, and the only place these errors show is in the console.

To access the browser console, use the appropriate shortcut for your OS / browser:

On Windows 🔗
On Mac 🔗

HTTP vs HTTPS issues 🔗

If your Confluence page is served over https:// but you are using a http:// URL in the macro to load your Open API definition, some browsers (e.g. Chrome) will refuse to load the URL and you will simply see Failed to load spec.. In the console, you will see a message such as:

Mixed Content: The page at 'https://your.confluence.instance' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint 'http://your.apidefinition.url'. This request has been blocked; the content must be served over HTTPS.

or

Blocked loading mixed active content "http://your.apidefinition.url"

In this case, you will need to have your Open API JSON/YAML definition hosted on a https:// URL.

Incompatibility with other Confluence apps 🔗

There is a known issue when using the Numbered Headings app (prior to version 5.7.8) in combination with YAML embedded in the macro body.

The structure of YAML is defined by line-breaks and indentation, and when an affected version of the Numbered Headings plugin is enabled on a page, it strips out any line-ending characters in the embedded YAML and replaces them with spaces, which results in invalid YAML.

If you use both Numbered Headings and Open API Documentation for Confluence, and are experiencing issues with YAML embedded in the macro body, check that you are running Numbered Headings v5.7.8 or later.

To work around the issue, you can either:

Security & Privacy Statement 🔗

Open API Documentation for Confluence is a paid app for Atlassian Confluence Server & Data Center (on-premises installations) and Atlassian Confluence Cloud (cloud hosted).

Server & Data Center Version 🔗

The server version is implemented as a Java JAR, using the Atlassian SDK.

When installed, the app runs entirely within the Confluence server process.

Neither the app provider nor any other party is able to view the content of your Confluence pages.

Cloud Version 🔗

The cloud version runs entirely on the Atlassian platform via Forge.

No data is transmitted outside the Atlassian platform.

Neither the app provider nor any other party is able to view any content of your Confluence pages or users.

If an instance of the macro is configured to source an API definition from an authenticated URL, the password or Git access token is passed to the Atlassian Forge servers to be encrypted, and the encrypted value is returned to the Confluence Cloud servers for storage with the page content. The credentials passed to the Atlassian Forge servers for encryption are not stored, logged or used for any other purpose.

Runs on Atlassian? 🔗

All compute and storage for our app exists within the Atlassian environment, and no data leaves the Atlassian environment.

In fact, since moving our app to Atlassian Forge, the provision of our app no longer requires us to manage our own app servers or host our own databases at all.

If you ask us, we'd say this is the very definition of "Runs on Atlassian".

However, our macro allows you to supply an optional URL where your JSON/YAML Open API definition is located. When the macro loads, the users browser fetches this URL (with a GET request).

Even though no data is ever sent to that location and our app only reads from it, Atlassian deems any connection to a location outside of the Atlassian environment as "egress", which disqualifies our app from the Runs on Atlassian badge.

We remain hopeful that Customer Managed Egress will in future allow our app to be eligible for the Runs on Atlassian badge.

Effective as of February 24, 2026.