Confluence REST API v2 — Create page with atlas_doc_format representation

I’m trying to use the Confluence REST API v2 API to create a new wiki page whose body is in the atlas_doc_format. I can’t seem to get anything except a 400 Bad Request response with a body of Invalid message. Does anyone have any suggestions on what I’m doing wrong with the request?

The POST request is being made to https://(my org).atlassian.net/wiki/api/v2/pages, with the following request body (formatted here for readability):

{
  "spaceId": 9867790169,
  "status": "draft",
  "title": "Equipment Sales",
  "body": {
    "representation": "atlas_doc_format",
    "value": {
      "body": {
        "type": "doc",
        "content": [
          {
            "type": "paragraph",
            "content": [
              {
                "type": "text",
                "text": "foo 3"
              }
            ]
          },
          {
            "type": "heading",
            "attrs": {
              "level": 2
            },
            "content": [
              {
                "type": "text",
                "text": "Background"
              }
            ]
          },
          {
            "type": "paragraph",
            "content": [
              {
                "type": "text",
                "text": "bar 3"
              }
            ]
          }
        ],
        "version": 1
      }
    }
  }
}

The ADF content was generated using the Python pyadf module.

What part is causing the invalid message?

Figured out yesterday that the body value needs to be JSON encoded, not passed as the underlying object. The correct form is like the following (value has been abbreviated).

{
  "spaceId": 9867790169,
  "status": "draft",
  "title": "Equipment Sales",
  "body": {
    "representation": "atlas_doc_format",
    "value": "{\"type\": \"doc\", \"content\": [{\"type\": \"paragraph\", \"content\": [{\"type\": \"text\", \"text\": \"foo 3\"}]}]}"
  }
}
2 Likes

what kind of encoding is this?

Very weird. The value entry is a sort of ‘escaped’ JSON. The same problem happens when you try to get the body of a page. You either get XML or you get this escaped JSON as a string. But no usable JSON.

It’s a JSON encoded string of the JSON serialized object for the ADF.

Given an ADF object, you’d serialize it to the JSON representation, then assign that string as the value.

As @GerbenWierda noted, the same thing happens when you get the body of a page in ADF format. You have to treat the body value as a JSON serialized value and decode it to get the actual object. That’s actually what clued me in to the need to do the opposite when creating/updating a page.

1 Like

That was the tip I needed. Looking into JSON.parse() now.

@KingChungHuang Could you share some code to go from a regular JSON object to this escaped object?

I’ve turned that into a separate question for the community as I have the same question.

Answer given in the other thread. Multiple JSON.stringify()'s

Here’s an extension of the Python example in the Create page documentation showing an ADF document being created and encoded.

In short, the key is to JSON encode the ADF body object and assign that as the body value in the request.

import json

from pyadf.document import Document
import requests
from requests.auth import HTTPBasicAuth


url = "https://{your-domain}/wiki/api/v2/pages"

auth = HTTPBasicAuth("email@example.com", "<api_token>")

headers = {
  "Accept": "application/json",
  "Content-Type": "application/json"
}

doc = Document()
doc.paragraph().text("Hello, world!")

# This is the ADF document body as an actual object.
body = doc.to_doc()["body"]

# The key is to serialize the ADF body as JSON for the create/update request,
# not assign the unserialized object directly as the body value.
payload = {
    "spaceId": 15,
    "status": "current",
    "title": "Example",
    "parentId": 34,
    "body": {
        "representation": "atlas_doc_format",
        "value": json.dumps(body),  # Serialize the ADF body as JSON.
    }
}

# The entire payload is then serialized as JSON for the create/update request.
payload_json = json.dumps(payload)

response = requests.request(
    "POST",
    url,
    data=payload_json,
    headers=headers,
    auth=auth
)

This also means that the reverse is true when getting a page in ADF format. You’d need to JSON decode the response, get the body value string, and then JSON decode that in order to get the ADF object.