Service Request attachments using Python requests module

  • 31 May 2023
  • 2 replies

I can attach a file using Postman but cannot figure out how to do it in Python.  Below is a barebones script demonstrating a good response reading the record, followed by a bad response attempting to add an attachment.  I'm getting a 415 response (Unsupported Media Type).  Any assistance would be greatly appreciated!


$ python -V

Python 3.8.5


$ pip show requests |grep -e ^Name -e ^Version

Name: requests

Version: 2.26.0


$ python <my-account-name> 1168503


*** output ***


Get record type and status


url: https://*******,status&ids=1168503

session headers: {'Cookie': 'JSESSIONID=7A2B901424AB9ECF1C17DFCE4BC11FA1.inst05us-autoscaleapp-002187', 'Content-Type': 'application/json'}

prepared headers: {'Cookie': 'JSESSIONID=7A2B901424AB9ECF1C17DFCE4BC11FA1.inst05us-autoscaleapp-002187', 'Content-Type': 'application/json'}

status_code: 200


('[{"id":"1168503","canUpdate":true,"canDelete":false,"canArchive":false,"hasChildren":false,"info":[{"key":"sr_type","value":10,"valueClass":"","valueCaption":"Request","keyCaption":"Service '

'Record Type"},{"key":"status","value":57,"valueClass":0,"valueCaption":"Work '

'in Progress","keyCaption":"Status"}]}]')


Attach file to request


filepath: /tmp/sysaid_attachment.xlsx

filename: sysaid_attachment.xlsx

url: https://*******

session headers: {'Cookie': 'JSESSIONID=7A2B901424AB9ECF1C17DFCE4BC11FA1.inst05us-autoscaleapp-002187', 'Content-Type': 'application/json'}

prepped headers: {'Cookie': 'JSESSIONID=7A2B901424AB9ECF1C17DFCE4BC11FA1.inst05us-autoscaleapp-002187', 'Content-Type': 'application/json', 'Content-Length': '8813'}

body (1st 200 characters): b'--13913b69799838b597498be9bcb74aba\r\nContent-Disposition: form-data; name="file"; filename="sysaid_attachment.xlsx"\r\nContent-Type: application/json\r\n\r\nPK\x03\x04\x14\x00\x00\x00\x08\x00\xecm\xb6V\xe4\xc5\x13\xbd\x82\x00\x00\x00\xb1\x00\x00\x00\x10\x00\x00\x00docProps/app.xmlM\x8eM\x0b'

body (last 200 characters): b'lPK\x01\x02\x14\x03\x14\x00\x00\x00\x08\x00\xedm\xb6V\xa1-6u\xaf\x00\x00\x00\xfb\x01\x00\x00\x1a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x01.\x1d\x00\x00xl/_rels/workbook.xml.relsPK\x01\x02\x14\x03\x14\x00\x00\x00\x08\x00\xedm\xb6V\xf6\x8b\xb9 \x15\x01\x00\x00\xd7\x03\x00\x00\x13\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x01\x15\x1e\x00\x00[Content_Types].xmlPK\x05\x06\x00\x00\x00\x00\t\x00\t\x00>\x02\x00\x00[\x1f\x00\x00\x00\x00\r\n--13913b69799838b597498be9bcb74aba--\r\n'

status_code: 415

response headers: {'x-frame-options': 'SAMEORIGIN', 'x-content-type-options': 'nosniff', 'x-xss-protection': '1; mode=block', 'cache-control': 'no-cache', 'last-modified': 'Wed, 31 May 2023 19:07:10 GMT', 'content-type': 'text/html;charset=utf-8', 'content-language': 'en', 'content-length': '457', 'date': 'Wed, 31 May 2023 19:07:11 GMT', 'strict-transport-security': 'max-age=63072000; includeSubDomains;', 'set-cookie': 'SERVERID=inst05us-autoscale-app13|ZHea4|ZHea4; path=/'}

history: <!doctype html><html lang="en"><head><title>HTTP Status 415 – Unsupported Media Type</title><style type="text/css">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;} h1 {font-size:22px;} h2 {font-size:16px;} h3 {font-size:14px;} p {font-size:12px;} a {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 415 – Unsupported Media Type</h1></body></html>


*** code ***




# tested in python3.8

# requires requests package:

# assumes a valid JSESSIONID is stored in cookiepath - no login routine included here

# set filepath to your attachment

# invoke with account name and record id

# Requests @ PyPI:

# Documentation:



import sys

import requests

from pprint import pprint, pformat



filepath = "/tmp/sysaid_attachment.xlsx"


def get_headers():


        #with open('/tmp/sysaid/%s/state/jsessionid' %account, 'r') as fd:

        with open(cookiepath, 'r') as fd:

            lines = fd.readlines()


    except FileNotFoundError:

        print("File not found")

        return {}


        h = { 'Cookie': lines[0].strip(), 'Content-Type': 'application/json' }


    return h


### CHECK ARGS ###


if len(sys.argv) != 3:

    sys.exit('Usage: ACCOUNT RECORD-ID')

if not sys.argv[2].isdigit():

    sys.exit('Usage: ACCOUNT RECORD-ID')



### INITIALIZE vars ###


account = sys.argv[1].strip()

reqid = sys.argv[2].strip()

url = ",status&ids=%s" %(account, reqid)

session = requests.Session()

headers = get_headers()

# should a HEAD request be sent here to make JSESSION persistent in session data?


### READ the record for type and status ###


req = requests.Request('GET', url=url, headers=headers)

prepped = req.prepare()

print("Get record type and status")


print("url: %s" %url.replace(account, "*******"))

print("session headers: %s" %str(headers))

print("prepared headers: %s" %str(prepped.headers))


# send prepared request

response = session.send(prepped)


# check status returned

sc = response.status_code

print("status_code: %s" %sc)

if sc == 200:




    sys.exit('Read failed. Aborting')


### ATTACH a file to the record ###


#if False:

if True:

    # create the request per the Requests doc:

    filename = filepath.split('/')[-1]

    url = "" %(account, reqid)


    # create attachment

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

    #files = {'file': (filename, open(filepath, 'rb'), 'application/octet-stream', {'Expires': '0'})}

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

    #files = {'file': (filename, open(filepath, 'rb'), 'application/octet-stream') }

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


    req = requests.Request('POST', url, files=files, headers=headers)

    #req = requests.Request('POST', url=url, data=files, headers=headers)

    prepped = req.prepare()


    # modify headers for this invocation (not persisted to session data)

    #prepped.headers['Content-Type'] = "application/json"

    #prepped.headers['Content-Type'] = "multipart/form-data"

    #prepped.headers['Content-Disposition'] = "multipart/form-data"

    #del prepped.headers['Content-Type']

    #del prepped.headers['Content-Disposition']



    print("Attach file to request")


    print("filepath: %s" %filepath)

    print("filename: %s" %filename)

    print("url: %s" %url.replace(account, "*******"))

    print("session headers: %s" %str(headers))

    print("prepped headers: %s" %str(prepped.headers))

    if len(prepped.body) < 200:

        print("body: %s" %prepped.body)


        print("body (1st 200 characters): %s" %prepped.body[:200])

        print("body (last 200 characters): %s" %prepped.body[-200:])


    # send prepared request

    response = session.send(prepped)


    # check status returned

    sc = response.status_code

    print("status_code: %s" %sc)

    if sc == 200:



    print("response headers: %s" %str(response.headers))

    print("history: %s" %str(response.text))


Best answer by cary512 1 June 2023, 17:24

View original

2 replies

Solved, thanks to this blog post:

Only the cookie header should be provided to  Removing the Content-Type header from the prepared request before sending didn’t work.  

    # create attachment

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

    cookieHeaders = { "Cookie": headers['Cookie'] }

    req = requests.Request('POST', url, files=files, headers=cookieHeaders)

    prepped = req.prepare()

    response = session.send(prepped)


After this I’m getting a 200 response.

Userlevel 3
Badge +4

Thank you, @cary512 for coming back and sharing your findings! 🙏
I’ll mark your question as solved.