How to update a page with Python using REST API?

I would like to write content to an existing page using the REST API, here's my current script:


# Reference:

import sys
import getpass

import json
import requests


PAGEID = 9470040

def main():

    username = raw_input('login: ')
    passwd = getpass.getpass()

    url = "{base}/{pageid}".format(
        base = BASE_URL, pageid = PAGEID)

    data = json.dumps(
        'id' : '%d' % PAGEID,
        'type' : 'page',
        'title' : 'Sandbox',
        'space' : {'key' : 'HSWc'},
        'body' :
            'storage' :
                'representation' : 'storage',
                'value' : """

    r = requests.put(
        data = data,
        auth = (username, passwd),
        headers = {
            'Content-Type' : 'application/json',
            'Accept' : 'application/json'


if __name__ == "__main__" : main()

But I'm getting "requests.exceptions.HTTPError: 400 Client Error: Bad Request"

There currently isn't any working documentation for using the REST API.  Please help!

3 answers

1 accepted

I've now got a working example.  The documentation does not specify all the required fields to make this work, but it does state that the next version number must be specified.

So root cause to my failure was not explicitly specifying the next version number.

After I corrected that, the newly modified page was orphaned (  But as sash pointed out, I found the same workaround.  Below is my complete working example:

$ python -h
usage: [-h] [-u USER] [-t TITLE] [-f FILE] pageid [html]
positional arguments:
  pageid                Specify the Conflunce page id to overwrite
  html                  Write the immediate html string to confluence page
optional arguments:
  -h, --help            show this help message and exit
  -u USER, --user USER  Specify the username to log into Confluence
  -t TITLE, --title TITLE
                        Specify a new title
  -f FILE, --file FILE  Write the contents of FILE to the confluence page
import argparse
import getpass
import sys

import json
import keyring
import requests

# Globals



def pprint(data):
    Pretty prints json data.
    print json.dumps(
        sort_keys = True,
        indent = 4,
        separators = (', ', ' : '))

def get_page_ancestors(auth, pageid):

    # Get basic page information plus the ancestors property

    url = '{base}/{pageid}?expand=ancestors'.format(
        base = BASE_URL,
        pageid = pageid)

    r = requests.get(url, auth = auth)


    return r.json()['ancestors']

def get_page_info(auth, pageid):

    url = '{base}/{pageid}'.format(
        base = BASE_URL,
        pageid = pageid)

    r = requests.get(url, auth = auth)


    return r.json()

def write_data(auth, html, pageid, title = None):

    info = get_page_info(auth, pageid)

    ver = int(info['version']['number']) + 1

    ancestors = get_page_ancestors(auth, pageid)

    anc = ancestors[-1]
    del anc['_links']
    del anc['_expandable']
    del anc['extensions']

    if title is not None:
        info['title'] = title

    data = {
        'id' : str(pageid),
        'type' : 'page',
        'title' : info['title'],
        'version' : {'number' : ver},
        'ancestors' : [anc],
        'body'  : {
            'storage' :
                'representation' : 'storage',
                'value' : str(html),

    data = json.dumps(data)

    url = '{base}/{pageid}'.format(base = BASE_URL, pageid = pageid)

    r = requests.put(
        data = data,
        auth = auth,
        headers = { 'Content-Type' : 'application/json' }


    print "Wrote '%s' version %d" % (info['title'], ver)
    print "URL: %s%d" % (VIEW_URL, pageid)

def get_login(username = None):
    Get the password for username out of the keyring.

    if username is None:
        username = getpass.getuser()

    passwd = keyring.get_password('confluence_script', username)

    if passwd is None:
        passwd = getpass.getpass()
        keyring.set_password('confluence_script', username, passwd)

    return (username, passwd)

def main():

    parser = argparse.ArgumentParser()

        default = getpass.getuser(),
        help = "Specify the username to log into Confluence")

        default = None,
        type = str,
        help = "Specify a new title")

        default = None,
        type = str,
        help = "Write the contents of FILE to the confluence page")

        type = int,
        help = "Specify the Conflunce page id to overwrite")

        type = str,
        default = None,
        nargs = '?',
        help = "Write the immediate html string to confluence page")

    options = parser.parse_args()

    auth = get_login(options.user)

    if options.html is not None and options.file is not None:
        raise RuntimeError(
            "Can't specify both a file and immediate html to write to page!")

    if options.html:
        html = options.html


        with open(options.file, 'r') as fd:
            html =

    write_data(auth, html, options.pageid, options.title)

if __name__ == "__main__" : main()

Thanks for posting this, which saved me a great deal of time.  One thing to be careful of is if you are renaming the page by giving it a new title, then that title cannot be the same as the any of the titles in the space.  REST API will return a 400 error if there's a name clash.

Hi Nick,

I am using your example. My code writes to the Confluence page only as text, I can't get Confluence to display it as a table. For example, I can write this:


<table><tbody><tr><th>a</th><th>b</th><th>c</th><th>d</th><th><p>e</p></th></tr><tr><td colspan="1">1</td><td colspan="1">2</td><td colspan="1">3</td><td colspan="1">4</td><td colspan="1">5</td></tr></tbody></table>


or I tried adding html elements to it, and it writes this:

<!DOCTYPE html> <html> <head> <title>Page Title</title> </head> <body> <h1>My First Heading</h1> <p>My first paragraph.</p> </body> </html>

If you are able to view this as a table, can you please tell me, what did you supply in the 'html' argument in your last example? 




Thanks so much for posting this.  It is very helpful.  One thing I have found is that one html file may upload with no issues while a different html file throws a

400 Client Error:  Bad Request for url: ....

I think it may have to do with any errors in the html, but I'm not sure about that.  Have you run into this and do you have any solutions?



@Dan McMahill

You could not post *any* HTML, it expects a storage format afaik

I should have said storage format, not html.  But the problem is the same.  If the storage format file has any bug at all, it won't upload.  Maybe there is a way to validate the file locally?

Hey Nick,

did you happen to come across this documentation already?
Following the example curl-request to create a new page, you also have an example on how to update a page.

I hope this helps!


That's the only example I could find on the planet. When I tried it, I got a strange error complaining about the header. I'm not at work today so I can't paste in the error message. But that's the example I'm trying to imitate with the script. It doesn't work.

Also, those example are using strange expressions: &#39; which I think just means char ' So I think bash doesn't like seeing &#39;, I've tried just using ' instead but no joy.

I see. Unfortunately I haven't messed with the API in a while and even then it was only JIRA and read-requests. Since the header is presented, it's strange that this would result in an error considering it matches the type of data provided. Yes, the &#39; are ' and are probably used for compatibility in case one decides to use single-quotes instead of quotes.

Take a look at the discussions here

It looks like it the issue is related to missing ancestors in your json

         "title":"TITLE OF THE PARENT PAGE"

Java example for quick ref

Suggest an answer

Log in or Sign up to answer
Atlassian Community Anniversary

Happy Anniversary, Atlassian Community!

This community is celebrating its one-year anniversary and Atlassian co-founder Mike Cannon-Brookes has all the feels.

Read more
Community showcase
Kesha Thillainayagam
Posted Apr 13, 2018 in Confluence

We want to hear how your non-technical teams are using Confluence!

Hi Community! Kesha (kay-sha) from the Confluence marketing team here! Can you share stories with us on how your non-technical (think Marketing, Sales, HR, legal, etc.) teams are using Confluen...

1,990 views 25 10
Join discussion

Atlassian User Groups

Connect with like-minded Atlassian users at free events near you!

Find a group

Connect with like-minded Atlassian users at free events near you!

Find my local user group

Unfortunately there are no AUG chapters near you at the moment.

Start an AUG

You're one step closer to meeting fellow Atlassian users at your local meet up. Learn more about AUGs

Groups near you