Basecamp 3 API upload/insert image error 422 : Unprocessable Entity in Django

I have a Django project that will insert a comment with image to basecamp 3. So after I got a key token (Authorization using Oauth 2) I need to insert a comment with image to basecamp todos. The image is from URL contained in JSON that user input. The JSON is look like this:

{
   "uuid":"763eadb9-7c6b-4383-9013-34859e0ab062",
   "event":"capture.new",
   "id":"1",
   "url":{
      "id":"2",
      "url":"https://pagescreen.io",
      "http_code":"200",
      "title":"Pagescreen (test)",
      "description":""
   },
   "permalink":"https://i.pagescreen.io/i/123.png",
   "generated":"1",
   "status":"1",
   "requested_on":"2022-12-14T07:05:10+00:00",
   "last_updated":"2022-12-14T07:05:38+00:00",
   "options":{
      "delay":"0",
      "format":"png",
      "fullpage":"1",
      "viewport":{
         "width":"1440",
         "height":"960"
      }
   },
   "automation_id":"1",
   "file":{
      "width":"1440",
      "height":"11216",
      "size":"639570"
   },
   "colors":{
      "rgb(109, 118, 139)":304,
      "rgb(255, 255, 255)":169,
      "rgb(51, 51, 51)":4,
      "rgba(0, 0, 0, 0)":523,
      "rgb(0, 0, 0)":82,
      "rgb(239, 239, 239)":5,
      "rgb(33, 68, 146)":27,
      "rgb(0, 131, 221)":37,
      "rgb(120, 129, 149)":27,
      "rgba(255, 255, 255, 0.063)":2,
      "rgba(255, 255, 255, 0.314)":2,
      "rgba(255, 255, 255, 0.5)":2,
      "rgb(255, 194, 10)":5,
      "rgb(0, 26, 62)":42,
      "rgb(70, 188, 102)":11,
      "rgb(238, 82, 83)":5,
      "rgb(255, 165, 0)":6,
      "rgb(61, 129, 214)":22,
      "rgb(0, 122, 255)":4,
      "rgb(30, 144, 255)":8,
      "rgb(248, 248, 248)":6,
      "rgb(240, 240, 240)":7,
      "rgb(80, 102, 144)":15,
      "rgba(0, 0, 0, 0.1)":1,
      "rgb(0, 201, 183)":4
   },
   "navigation":{
      "previous":{
         "id":"1"
      },
      "next":null
   }
}

So from that JSON I saved the image from permalink (“permalink”:”https://i.pagescreen.io/i/123.png”) to my local storage project.

Then after saved the image, I upload/attach it to basecamp API (Attach endpoint) like in this documentation https://github.com/basecamp/bc3-api/blob/master/sections/attachments.md. So my code look like this:

def upload_attachment_to_basecamp(access_token, account_id, file_path, file_name):
    basecamp_url = f'https://3.basecampapi.com/{account_id}/attachments.json?name={file_name}'

    headers = {
        'Authorization': f'Bearer {access_token}',
        'Content-Type': 'image/png',
    }

    with open(file_path, 'rb') as file:
        files = {'file': (file_name, file)}

        response = requests.post(basecamp_url, headers=headers, files=files)

        if response.status_code == 201:
            attachable_sgid = response.json().get('attachable_sgid')
            return {'status': 'success', 'attachable_sgid': attachable_sgid}
        else:
            return {'status': 'error', 'message': f'Failed to upload attachment to Basecamp. Status Code: {response.status_code}'}

So after that I get the attachable_sgid, then I use this sgid for attaching my image to basecamp comment. This is a documentation https://github.com/basecamp/bc3-api/blob/master/sections/rich_text.md#inserting-an-image-or-file-attachment. And my code to post a comment is look like this:

@csrf_exempt
@require_POST
def post_image_to_basecamp(request):
    ACCESS_TOKEN = 'BAhbB0kixxx'

    ACCOUNT_ID = '361xxx'
    IMAGE_NAME = 'image.png' 

    # Get the image from permalink
    data = json.loads(request.body.decode('utf-8'))
    image_url = data.get('image_url')
    print(image_url)

    if image_url:
        # Get the image from permalink
        image_response = requests.get(image_url)

        if image_response.status_code == 200:
            image_content = image_response.content
            image_path = os.path.join(settings.MEDIA_ROOT, 'images', IMAGE_NAME)
            with open(image_path, 'wb') as image_file:
                image_file.write(image_content)

            # Save and upload image
            upload_result = upload_attachment_to_basecamp(ACCESS_TOKEN, ACCOUNT_ID, image_path, IMAGE_NAME)

            if upload_result['status'] == 'success':
                attachable_sgid = upload_result['attachable_sgid']

                # Tag <bc-attachment>
                attachment_tag = f'<bc-attachment sgid="{attachable_sgid}"></bc-attachment>'

                # URL API Basecamp for posting a comment
                api_url = f'https://3.basecampapi.com/{ACCOUNT_ID}/buckets/14151271/recordings/6935867532/comments.json'

                headers_comment = {
                    'Authorization': f'Bearer {ACCESS_TOKEN}',
                    'Content-Type': 'image/png'
                }

                # Data caption, image, any
                data = {
                    'content': f'<div>My image <br> {attachment_tag}</div>',
                }

                response = requests.post(api_url, json=data, headers=headers_comment)

                if response.status_code == 201:
                    result = response.json()
                    return JsonResponse({'status': 'success', 'attachable_sgid': attachable_sgid, 'result': result})
                else:
                    return JsonResponse({'status': 'error', 'message': f'Failed to post comment. Status Code: {response.status_code}'})
            else:
                return JsonResponse({'status': 'error', 'message': f'Failed to upload image to Basecamp. {upload_result["message"]}'})
        else:
            return JsonResponse({'status': 'error', 'message': f'Failed to fetch image from URL. Status Code: {image_response.status_code}'})
    else:
        return JsonResponse({'status': 'error', 'message': 'Image URL is required'})

(xxx = I censored)
After that I got a error 422 which is a unprocessable entity/content, but if I change the header ‘Content-type’ from image to ‘application/json’ the image its uploaded but its blank.

I need to post a comment with an image in basecamp from my local project, the result I’m expecting is the image is showing with preview.

Leave a Comment