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.
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'}}
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:
Useful curl one-liners you can also find on the official website here: https://developer.atlassian.com/server/confluence/confluence-rest-api-examples/
Hi! My name is Alexander and I am a Vulnerability Management specialist. You can read more about me here. Currently, the best way to follow me is my Telegram channel @avleonovcom. I update it more often than this site. If you haven’t used Telegram yet, give it a try. It’s great. You can discuss my posts or ask questions at @avleonovchat.
А всех русскоязычных я приглашаю в ещё один телеграмм канал @avleonovrus, первым делом теперь пишу туда.
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!
Hi Lorenzo,
Did you find a way to just add new data without replacing the existing data ?
Pingback: Asset Inventory for Network Perimeter: from Declarations to Active Scanning | Alexander V. Leonov
Pingback: Asset Inventory for Internal Network: problems with Active Scanning and advantages of Splunk | Alexander V. Leonov
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.
@ 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.
Hi Ganesh,
Did you find a way to just add new data without replacing the existing data ?
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 ?
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.
Hi, how can i get all attachment version files via api rest?
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 ?
You should add
import requests
in the beginning of the code fragment
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?
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?
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.
Thanks a lot for the article. Was really helpful.
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