4

I am trying to upload an attachment to Confluence via the REST API, using Python Requests. I always get either a "415 unsupported media type" error or a "500 internal server error", depending on how I send the request.

There are several bits of information about how to do this with other languages, or using Python via the now deprecated XMLRPC API, or for the JIRA REST API which seems to behave slightly different.

This is how, according to all that information, the code should look like:

def upload_image():
    url = 'https://example.com/confluence/rest/api/content/' + \
          str(PAGE_ID) + '/child/attachment/'
    headers = {'X-Atlassian-Token': 'no-check'}
    files = {'file': open('image.jpg', 'rb')}
    auth = ('USR', 'PWD')
    r = requests.post(url, headers=headers, files=files, auth=auth)
    r.raise_for_status()

What is missing is the correct content-type header. There is different information out there:

  • use the correct content-type for the file, in this case image/jpeg
  • use application/octet-stream
  • use application/json
  • use multipart/form-data

(The Confluence version I am using is 5.8.10)

Arne Mertz
  • 24,171
  • 3
  • 51
  • 90

1 Answers1

5

Using the correct content-type is not the only issue here. Using it at the right place is equally important. For the file upload, the content-type has to be provided with the file, not as a header of the request itself.

Even though the Python Requests documentation explicitly writes that the files argument is used for uploading multipart-encoded files, the content-type needs to be explicitly set to the correct type for the attachment.
While it is not entirely correct (see comments below), multipart/form-data will work as well, so we can use it as a fallback if we really can't determine the correct content-type:

def upload_image():
    url = 'https://example.com/confluence/rest/api/content/' + \
          str(PAGE_ID) + '/child/attachment/'
    headers = {'X-Atlassian-Token': 'no-check'} #no content-type here!
    file = 'image.jpg'

    # determine content-type
    content_type, encoding = mimetypes.guess_type(file)
    if content_type is None:
        content_type = 'multipart/form-data'

    # provide content-type explicitly
    files = {'file': (file, open(file, 'rb'), content_type)}

    auth = ('USR', 'PWD')
    r = requests.post(url, headers=headers, files=files, auth=auth)
    r.raise_for_status()
Arne Mertz
  • 24,171
  • 3
  • 51
  • 90
  • No, that doesn't seem right. You need to specify the media/mime type for the attachment. In your example this should be `image/jpeg`, not `multipart/form-data` which would be used as the HTTP request's `Content-Type` header (`requests` does this correctly). With respect to the code in your question, probably the Confluence server noticed that there was _no_ content type specified for the file, and refused to accept it. You should set `files` like this: `files = {'file': ('image.jpg', open('image.jpg', 'rb'), 'image/jpeg')}` – mhawke Feb 09 '16 at 09:12
  • The documentation for this API operation is https://docs.atlassian.com/atlassian-confluence/REST/latest/#d3e626 – mhawke Feb 09 '16 at 09:14
  • @mhawke I tried that, it gives a 415 error as well. The documentation you link does not mention the content-type except that `The name of the multipart/form-data parameter that contains attachments must be "file"`. – Arne Mertz Feb 09 '16 at 10:09
  • You're right, it is not _explicitly_ mentioned, but that is how it should be in HTTP. You can check by trying out the `curl` example in the documentation. The HTTP request generated by `curl` sets these headers for the attachment: `Content-Disposition: form-data; name="file"; filename="myfile.txt"` and `Content-Type: text/plain`. If you alter the example to use a JPG image file `curl` sets `Content-Type: image/jpeg` as you would expect. Can you try out the example using `curl` with your Confluence instance and see if that works? First try a text file then, if that works, try an image file. – mhawke Feb 09 '16 at 10:44
  • @mhawke I tried curl and checked again, providing the correct content_type for the file. It works now, no idea why it would not work the first time I tried. I changed my answer accordingly. – Arne Mertz Feb 09 '16 at 12:55
  • Thank you for the perfect answer to why requests was not formulating my POST correctly - I was also getting the 415 error from the JIRA rest api, but adding content type to the files attribute fixed it. – KayCee Apr 27 '17 at 09:52