Confluence REST API for reading and updating wiki pages

In previous posts I wrote how to automate the work with Atlassian Jira, including automated ticket labeling. Now let’s try to use REST API of another popular Atlassian product – Confluence wiki engine.

Confluence REST API

What you may want to automate in Confluence? Obviously, it may be useful to read the pages that your colleagues regularly update and then use this data in some scripts as an input. You may also want to update your own Confluence pages, for example to post Vulnerability Scanning results. 😉

Reading pages

You can read the page by Id using this function:

import json
import requests

def get_page_json(page_id, expand = False):
    if expand:
        suffix = "?expand=" + expand 
                              #body.storage
    else:
        suffix = ""

    url="https://confluence.corporation.com/rest/api/content/" + page_id + suffix
    response = requests.get(url, auth=(user, password))
    response.encoding = "utf8"
    return json.loads(response.text)
    
print(get_page_json("287346883", "body.storage"))

Output:

{u'status': u'current', u'body': {u'_expandable': {u'export_view': u'', u'styled_view': u'', u'editor': u'', u'anonymous_export_view': u'', u'view': u''}, u'storage': {u'_expandable': {u'content': u'/rest/api/content/287346883'}, u'representation': u'storage', u'value': u'<p>Test content</p>'}}, u'title': u'Test page', u'_expandable': {u'operations': u'', u'ancestors': u'', u'container': u'/rest/api/space/INFO', u'descendants': u'/rest/api/content/287346883/descendant', u'space': u'/rest/api/space/INFO', u'version': u'', u'metadata': u'', u'children': u'/rest/api/content/287346883/child', u'history': u'/rest/api/content/287346883/history'}, u'extensions': {u'position': u'none'}, u'_links': {u'self': u'https://confluence.corporation.com/rest/api/content/287346883', u'collection': u'/rest/api/content', u'tinyui': u'/x/-QWIE', u'base': u'https://confluence.corporation.com', u'webui': u'/display/INFO/Test+page', u'context': u''}, u'type': u'page', u'id': u'287346883'}

Without “?expand=body.storage” Confluence will return detailed information about the version, but will not return the full content of the page to body -> storage -> value. 😉

As you can see, JSON data is very convenient. You can easily read the title and html content:

print(json_data['title'])
print(json_data['body']['storage']['value'])

Output:

Test page
<p>Test content</p>

Editing

OK. How can we change the content of this page? We need to send some minimal JSON to the Confluence API, for example using this function:

def set_page_json(page_id,json_content):
    headers = {
        'Content-Type': 'application/json',
    }

    response = requests.put("https://confluence.corporation.com/rest/api/content/" + page_id, headers=headers, data=json.dumps(json_content),
                 auth=(user, password))
    return(response.text)

What should be the structure of this JSON?

I cook it this way:

json_data = functions_confluence.get_page_json("277387103")

new_json_data = dict()
new_json_data['id'] = json_data['id']
new_json_data['type'] = json_data['type']
new_json_data['title'] = json_data['title']
new_json_data['type'] = json_data['type']
new_json_data['version'] = {"number": json_data['version']['number'] + 1}
if not 'key' in json_data:
    new_json_data['key'] = json_data['space']['key']
else:
    new_json_data['key'] = json_data['key']

new_json_data['body'] = {'storage':{'value':'<p>This is the updated text for the new page</p>','representation':'storage'}}

There should be something like this in new_json_data:

{'body': {'storage': {'representation': 'storage', 'value': '<p>This is the updated text for the new page</p>'}}, 'title': u'Test page', 'version': {'number': 2}, 'key': u'INFO', 'type': u'page', 'id': u'277387103'}

The response after sending JSON will be:

{"id":"277387103","type":"page","status":"current","title":"Test page", ....

And the content of the page should change.

Tables

What if we try to add a table?

new_json_data['body'] = {'storage':{'value':'<p><table><tr><th>head1</th><th>head2</th></tr><tr><td>value1</td><td>value2</td></tr><tr><td>value3</td><td>value4</td></tr></table></p>','representation':'storage'}}

Confluence table example

It’s simple!

Images and other attacments

Unfortunately, it’s not possible to send a picture in base64 this way =(

What if I attach a file to a page and use a link to it in code? It’s possible.

I’m uploading the file using this function:

def attach_file_to_page(page_id, filepath, filename):

    headers = {
        'X-Atlassian-Token': 'no-check',
    }

    files = {
        'file': (filename, open(filepath, 'rb')),
    }

    #delete attach with the same name
    response = requests.get("https://confluence.corporation.com/rest/api/content/" + page_id + "/child/attachment", auth=(user, password))
    for attachment in json.loads(response.text)['results']:
        if attachment['title'] == filename:
            requests.delete("https://confluence.corporation.com/rest/api/content/" + attachment['id'], headers=headers, auth=(user, password))

    #attack files
    response = requests.post("https://confluence.corporation.com/rest/api/content/" +page_id+ "/child/attachment", headers=headers, files=files,
                     auth=(user, password))
    return(response.text)

Note that before you upload a file, you need to verify that the file with such name is not attached already or you will get an error. So I check all the attached files and delete files with the specified name.

URL of the file will be https://confluence.corporation.com/download/attachments/277387103/test.jpg and we can use it in code:

functions_confluence.attach_file_to_page("277387103", "data/test.jpg", "test.jpg")
img_url = "https://confluence.corporation.com/download/attachments/277387103/test.jpg"

new_json_data['body'] = {'storage':{'value': '<img src="'+img_url+'"></img>','representation':'storage'}}

Output:
{"id":"277387103","type":"page","status":"current","title":"Test page", ...

And test.jpg</ code> will appear on the page:

Confluence image attached

Useful curl one-liners you can also find on the official website here: https://developer.atlassian.com/server/confluence/confluence-rest-api-examples/

17 thoughts on “Confluence REST API for reading and updating wiki pages

  1. Lorenzo

    Hey Alex, just wanted to thank you for such a wonderfully fantastic tutorial – this is JUST what I’ve been scouring the internet for. Your code worked so well, and it is so incredibly well-written, I feel bad for essentially copying it but there is nothing I can do to improve it!

    I did, however, have a small question. By using the body/storage/value functions, writing new json data to the confluence page overrides the existing data and replaces it with the content of new_json_data. Is there a way to add new data while keeping existing data on the page, without having to include all the existing data in the new_json_data dictionary?

    Thank you again for your tutorial!

    Reply
  2. Pingback: Asset Inventory for Network Perimeter: from Declarations to Active Scanning | Alexander V. Leonov

  3. Pingback: Asset Inventory for Internal Network: problems with Active Scanning and advantages of Splunk | Alexander V. Leonov

  4. Ganesh

    Hi,
    Your tutorial is really helpful 🙂
    @ Lorenzo
    The Value is just a HTML code , in your code you can append value as string and at end you can use the function set page json.

    Reply
  5. GANESH PERUMAL

    @ Lorenzo
    I misread your question ,
    I have same question with get_page_json can we get current data which is in confluence page , so that we can append instead of overwrite it.

    Reply
  6. Kapil Anandagiri

    I want to read the Table content , I used the confluence api expand=body.view through which i got the response , but all are so complex , is there any other way to get the exact row content alone ?

    Reply
  7. Norbert

    Hey,

    Thanks a lot for this, it’s been very useful to me.

    One thing I haven’t been able to figure out though, maybe you can help me. How can you specify “minorEdit=true” when doing a delete request (deleting an attachment)? In other requests I just have it in the data dict or the files dict, but there are no such fields in requests.delete.

    Reply
  8. Pavan

    Hi,
    when i copied the below code to test
    import json

    def get_page_json(page_id, expand = False):
    if expand:
    suffix = “?expand=” + expand
    #body.storage
    else:
    suffix = “”

    url=”https://new-wiki.optum.com/rest/api/content/” + page_id + suffix
    response = requests.get(url, auth=(user, password))
    response.encoding = “utf8”
    return json.loads(response.text)

    print(get_page_json(“287346883”))

    i am getting error on ‘requests’ as an unresolved reference , kindly advise me ?

    Reply
  9. Andy Farnham

    Hi, thanks for this. It’s been ver helpful. A lot of stuff is for jira/confluence cloud and it’s not as relevant for me.

    I see you have read and edit buy how would you create a blank page?

    Reply
  10. Joe Max

    Hello Alexander!

    Very useful and simple, I like what you’ve done here!

    I was curious about one thing though. I have a a text-based README.md file that I’m writing to my confluence page. The problem is, I cannot find a smooth way to keep the text format of the original file when I push it into the JSON write request like:

    ## Read in file
    with open(‘README.md’, ‘r’) as file:
    data = file.read()
    file.close()

    ## Prints fine with original formatting (Spaces, new lines etc)
    print(data)

    new_json_data[‘body’] = {‘storage’:{‘value’:data,’representation’:’storage’}}

    ## Formatting of processed_data is condensed and ruined:
    print(new_json_data)

    What would be the best way to keep the format of a textfile so it’s displayed the same on Confluence?

    Just to illustrate:

    ## This is a readme file

    There are interesting things to be read here.

    They are useful.

    The above is changed to

    “## This is a readme file. There are interesting things to be read here. They are useful”

    Which ruins the formatting. We want to keep the formatting above, is there any easy way to do so, or do we have to write logic to dynamically add tags when necessary?

    Reply
  11. MT

    hey alexander

    how can i separate the th and td section, because i want to read the row data from a file , currently the loop halts because the title exist after the 1st loop.

    or what is the other approach where in we dont specify the hardcoded value in the td section, since we have multiple values.can you please help me with the syntax, at the end i need a table with rows ..data taken/read from file.

    like { value }value4

    here the {value} can change.

    ————
    new_json_data[‘body’] = {‘storage’:{‘value’:’head1head2value1value2value3value4′,’representation’:’storage’}}
    ———–

    Thank you in advance.

    Reply
  12. Dastagiri

    Hi Alexander Leonov,
    Thank you so much for your detailed explanation. Even though I am not familiar with python, i understood something. What if i have data in CSV file and I want to fetch data from CSV file and create table it would be dynamic.
    Thanks,
    Dastagiri

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.