Forum Discussion

jeffshurtliff's avatar
5 years ago

Getting 403 response when creating message with attachment using API v2 and Python

When creating messages using the Community API v2 and the Python requests library, it works fine when not trying to upload an attachment, as shown in the sanitized excerpt below.

 

 

>>> headers = {
    'li-api-session-key': 'XXX-XXXXXXXXXXXXXXX_XXXXXXXXXX-XXXXXXXXXXXXX',
    'Content-Type': 'application/json'
}
>>> payload = {
    'data': {
        'type': 'message',
        'board': {
            'id': 'product-knowledge-base'
        },
        'body': 'This is an <b>API TEST</b> done in Python <i>without</i> an attached file.',
        'subject': 'API Test without Attachment'
    }
}
>>> uri = 'https://khoros-stage.example.com/api/2.0/messages'
>>> response = requests.post(uri, data=json.dumps(payload, default=str), headers=headers)
>>> response
{
    'status': 'success',
    'message': '',
    'http_code': 200,
    'data': {
        'type': 'message',
        'id': '112',
        'href': '/messages/112',
        # ---SNIP---
    }
}

 

 

 

However, when I try to create a message with an attachment (per the documentation) I am getting an HTTP Status 403 response saying Access to the specified resource has been forbidden, as shown below.

 

 

>>> headers = {
    'li-api-session-key': 'XXX-XXXXXXXXXXXXXXX_XXXXXXXXXX-XXXXXXXXXXXXX',
    'Content-Type': 'multipart/form-data'
}
>>> message_json = {
    "data": {
        "type": "message",
        "board": {
            "id": "product-knowledge-base"
        },
        "body": "This is an <b>API TEST</b> done in Python with an attached file.",
        "subject": "API Test with Attachment",
        "attachments": {
            "list_item_type": "attachment",
            "items": [
                {
                    "type": "attachment",
                    "field": "attachment1",
                    "filename": "feedparser.pdf"
                }
            ]
        }
    }
}
>>> payload = {
    'api.request': (None, json.dumps(message_json, default=str)),
    'attachment1': ('feedparser.pdf', open('C:\\Users\\someuser\\Downloads\\feedparser.pdf', 'rb'))
}
>>> uri = 'https://khoros-stage.example.com/api/2.0/messages'
>>> response = requests.post(uri, files=payload, headers=headers)
>>> response
<Response [403]>

>>> response.text
'<html><head><title>Error report</title><style><!--H1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} H2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} H3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} B {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} P {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;}A {color : black;}A.name {color : black;}HR {color : #525D76;}--></style> </head><body><h1>HTTP Status 403 - </h1><HR size="1" noshade="noshade"><p><b>type</b> Status report</p><p><b>message</b> <u></u></p><p><b>description</b> <u>Access to the specified resource has been forbidden.</u></p></body></html>'

 

 

I've confirmed that the maximum file size for attachments in our stage environment is 5242880 bytes (5MB) and the attachment is only about 375kb.  I'm also performing the API call with a full admin user.

Does anyone have any idea what I could be doing wrong or why I would be getting that error?  

Thanks!!

  • WillB's avatar
    WillB
    5 years ago

    jeffshurtliff I got it working with the following code: 

     

    import requests
    
    headers = {
        'li-api-session-key': 'XXXXXXXXXX_XXXXXXXXXXX_XXXXXXXXXXXX',
    }
    
    files = {
        'api.request': (None, '{\n"data":{\n"type":"message",\n"subject":"This is the message subject",\n"board":{\n"type":"board",\n"id":"KB1"\n},\n"attachments":{\n"list_item_type":"attachment",\n"items":[\n{\n"type":"attachment",\n"field":"attachment1",\n"filename":"pdf1.PDF"\n},\n{\n"type":"attachment",\n"field":"attachment2",\n"filename":"pdf2.PDF"\n}\n]\n}\n}\n}'),
        'attachment1': ('/Users/will.bowen/Documents/pdf1.PDF', open('/Users/will.bowen/Documents/pdf1.PDF', 'rb')),
        'attachment2': ('/Users/will.bowen/Documents/pdf2.PDF', open('/Users/will.bowen/Documents/pdf2.PDF', 'rb')),
    }
    
    response = requests.post('https://rsalink-stage.rsa.com/api/2.0/messages', headers=headers, files=files)
    

9 Replies

  • WillB's avatar
    WillB
    Khoros Alumni (Retired)
    5 years ago

    The error log is indicating the issue may be related to how the message is encoded. Specifically, the message is being rejected because no multipart boundary was found. I'm not too familiar with how python encodes api requests, but if you are able to successfully post a message with an attachment with curl or postman, you may want to double check how request.posts() is handling the attachments.

  • Thanks WillB! Is that error log something that I have access to view or is it only accessible to Khoros employees?

  • WillB's avatar
    WillB
    Khoros Alumni (Retired)
    5 years ago

    Unfortunately, it is only accessible to Khoros employees.

  • Would it be possible for you to provide the full error in case it has any clues to finding out the encoding issue? Because I’ve pushed the data in the way specified in the Python requests documentation and it still errors out. Thanks!
  • WillB's avatar
    WillB
    Khoros Alumni (Retired)
    5 years ago

    There's not much more to the message. 

     lithium.util.NullArgumentException: exception=org.apache.commons.fileupload.FileUploadException: the request was rejected because no multipart boundary was found,lastItem=null

    Can you provide a link to the python requests doc that you mentioned? I'm going to see if I can recreate the issue, and I want to make sure I'm using the same library.

  • Hi WillB,

    That was very helpful, thank you! I was able to find a Stack Overflow thread about the error which helped me realize that, despite it being in the Khoros dev docs tutorial examples, I needed to stop manually inserting the "content-type": "multipart/form-data" entry into the header when making the POST request, and just let it get defined as a multipart query on its own.

    This allowed the query to return a normal response from the Community API, albeit an error message.  See below.

    >>> response
    <Response [500]>
    
    >>> response.text
    '{"status":"error","message":"Unable to parse the required Content-Body from the request.","data":{"type":"error_data","code":308,"developer_message":"Unexpected character (\'-\' (code 45)) in numeric value: expected digit (0-9) to follow minus sign, for valid numeric value","more_info":""},"metadata":{}}'
    
    >>> response.request.body
    <MultipartEncoder: {'api.request': (None, '{"data": {"type": "message", "board": {"id": "product-knowledge-base"}, "body": "This is an <b>API TEST</b> done in Python with an attached file.", "subject": "API Test with Attachment", "attachments": {"list_item_type": "attachment", "items": [{"type": "attachment", "field": "attachment1", "filename": "jive_id_bookmarklet.pdf"}]}}}', 'application/json'), 'attachment1': ('jive_id_bookmarklet.pdf', <_io.BufferedReader name='C:\\Users\\shurtj\\Downloads\\jive_id_bookmarklet.pdf'>)}>

    I'm confused by the error though because from what I'm seeing, there is no field I'm using in the JSON that requires a numeric value and where a hyphen is found, unless it thinks the Board ID field should be an integer.  But that's not what the tutorial example shows...

    These are the documents I've been referencing, per your request:

    Thanks!

  • WillB's avatar
    WillB
    Khoros Alumni (Retired)
    5 years ago

    We typically see this error when the JSON is malformed. The snippet you posted seems fine to me, but I may have missed something, so please take a moment to double check. In the meantime, I'll continue looking into this today.

  • WillB's avatar
    WillB
    Khoros Alumni (Retired)
    5 years ago

    jeffshurtliff I got it working with the following code: 

     

    import requests
    
    headers = {
        'li-api-session-key': 'XXXXXXXXXX_XXXXXXXXXXX_XXXXXXXXXXXX',
    }
    
    files = {
        'api.request': (None, '{\n"data":{\n"type":"message",\n"subject":"This is the message subject",\n"board":{\n"type":"board",\n"id":"KB1"\n},\n"attachments":{\n"list_item_type":"attachment",\n"items":[\n{\n"type":"attachment",\n"field":"attachment1",\n"filename":"pdf1.PDF"\n},\n{\n"type":"attachment",\n"field":"attachment2",\n"filename":"pdf2.PDF"\n}\n]\n}\n}\n}'),
        'attachment1': ('/Users/will.bowen/Documents/pdf1.PDF', open('/Users/will.bowen/Documents/pdf1.PDF', 'rb')),
        'attachment2': ('/Users/will.bowen/Documents/pdf2.PDF', open('/Users/will.bowen/Documents/pdf2.PDF', 'rb')),
    }
    
    response = requests.post('https://rsalink-stage.rsa.com/api/2.0/messages', headers=headers, files=files)
    
  • Thank you very much, WillB!!  

    Your code snippet above helped me realize that I was inadvertently performing a JSON dump of the entire payload rather than just for the api.request portion.  Making that change allowed me to publish successfully with attachments.

    The finished function has been pushed to PyPI and GitHub and can be seen here.

    Thanks again!!