pythonsquare

Square API for invoice attachments 'Received multiple request parts. Please only supply zero or one `parts` of type application/json.'


The new square api versions 42+ have breaking changes. Im trying to upgrade to ver v42, and I am testing in a local dev environment.

I keep getting the following error: *** square.core.api_error.ApiError: status_code: 400, body: {'errors': [{'category': 'INVALID_REQUEST_ERROR', 'code': 'INVALID_CONTENT_TYPE', 'detail': 'Received multiple request parts. Please only supply zero or one `parts` of type application/json.'}]}

when I try to upload an approx ~800 byte jpeg [very grainy] in the development sandbox for Square Invoice API using the following code:

pdf_filepath = 'local/path/to/file.jpg'
idem_key = 'some-unique_key_like_square_invoice_id'
f_stream = open(pdf_filepath, "rb")
try:
    # I have tried using a stream as well, still the same error
    invoice_pdf = SQUARE_CLIENT.invoices.create_invoice_attachment(
        invoice_id=square_original_invoice.id,
        # this also does not work
        # image_file=f_stream, 
        image_file=pdf_filepath,
        request={
            "description": f"Invoice-{pdf_filepath}",
            "idempotency_key": idem_key,
        },
    )
except ApiError as e:
    print(f"ERROR _attach_pdf_to_vendor_payment with errors {e}")

In the online sandbox API, I get the 400 Response error:

// cache-control: no-cache
// content-type: application/json
// date: Wed, 30 Apr 2025 13:35:06 GMT
// square-version: 2025-04-16

{
  "errors": [
    {
      "code": "BAD_REQUEST",
      "detail": "Total size of all attachments exceeds Sandbox limit: 1000 bytes",
      "category": "INVALID_REQUEST_ERROR"
    }
  ]
}

Once I got a 900 byte jpg to upload in the API explorer (988 byte did not pass), but the SDK still errors using the same file.

Here is a successful API request upload VIA the API Explorer:

content-length: 1267
content-type: multipart/form-data; boundary=----WebKitFormBoundaryUUID38
square-version: 2025-04-16


user-agent: SquareExplorerGateway/1.0 SquareProperty ApiExplorer


    ------WebKitFormBoundaryUUID38
Content-Disposition: form-data;name="request"


{

  "idempotency_key": "UUID-123-456-7869"
}
------WebKitFormBoundaryUUID38
Content-Disposition: form-data;name="file";filename="900b.jpeg"
Content-Type: image/jpeg

����

Here is the unsuccessful API request via my django server, using the same file:

content-length: 568
content-type: multipart/form-data; boundary=djangoUUID
square-version: 2025-04-16
accept-encoding: gzip
accept: */*
user-agent: squareup/42.0.0.20250416


    --djangoUUID
Content-Disposition: form-data;name="request"
Content-Type: application/json;charset=utf-8

{
  "description": "Invoice-path/to/file/900b.jpeg",
  "idempotency_key": "path/to/original/file/normal-invoice.pdf"
}
--djangoUUID
Content-Disposition: form-data;name="image_file"
Content-Type: image/jpeg

/path/to/file/900b.jpeg
--djangoUUID--

Note the successful request : Content-Disposition: form-data;name="file";filename="900b.jpeg" Content-Type: image/jpeg

and the unsuccessful request: Content-Disposition: form-data;name="request" Content-Type: application/json;charset=utf-8 Content-Disposition: form-data;name="image_file" Content-Type: image/jpeg`

specifically: name="image_file" vs name="file" and application/json;charset=utf-8

The headers for the unsuccessful API call are:

{
    "date": "Sat, 03 May 2025 22:47:34 GMT",
    "content-type": "application/json",
    "transfer-encoding": "chunked",
    "connection": "keep-alive",
    "cf-ray": "ab-…-WER",
    "cf-cache-status": "DYNAMIC",
    "cache-control": "no-cache",
    "strict-transport-security": "max-age=631152000; includeSubDomains; preload",
    "x-envoy-decorator-operation": "/v2/invoices/**",
    "x-request-id": "58-…-80",
    "x-sq-dc": "aws",
    "x-sq-istio-migration-ingress-proxy": "sq-envoy",
    "x-sq-istio-migration-ingress-region": "us-west-2",
    "x-sq-region": "us-west-2",
    "vary": "Accept-Encoding",
    "server": "cloudflare"
}

The headers from the browser network inspector:

:authority          explorer-gateway.squareup.com
:method             POST
:path               /v2/invoices/inv:0-Ch…wI/attachments
:scheme             https
accept              application/json
accept-encoding.     gzip, deflate, br, zstd
accept-language.    en-US,en;q=0.9
authorization           Bearer AE…ju
cache-control               no-cache
content-length      1250
content-type.       multipart/form-data; boundary=----WebKitFormBoundaryCP3GAXwMvwBUTlwU
origin              https://developer.squareup.com
pragma              no-cache
priority                u=1, i
referer             https://developer.squareup.com/
sandbox-mode        true
sec-ch-ua           "Chromium";v="136", "Google Chrome";v="136", "Not.A/Brand";v="99"
sec-ch-ua-mobile    ?0
sec-ch-ua-platform  "macOS"
sec-fetch-dest      empty
sec-fetch-mode      cors
sec-fetch-site      same-site
square-version      2025-04-16
user-agent          Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36
x-square-property.  ApiExplorer

Notes:

Here is the test image

To try and send zero parts, if I do an API call without a request parameter:

invoice_pdf = SQUARE_CLIENT.invoices.create_invoice_attachment(
    invoice_id=self.square_original_invoice.id,
    image_file=pdf_filepath,
)

I get a response of INVALID_REQUEST_ERROR BAD_REQUEST Bad request.

If I do an API call with an empty request parameter:

invoice_pdf = SQUARE_CLIENT.invoices.create_invoice_attachment(
    invoice_id=self.square_original_invoice.id,
    image_file=pdf_filepath,
    request={},
)

I get the same error INVALID_REQUEST_ERROR INVALID_CONTENT_TYPE Received multiple request parts. Please only supply zero or one parts of type application/json.

I do remember the parameter name (file vs image_file) image being an issue with outdated docs, but the current docs show the newer parameter of image_file being correct

Related Docs https://developer.squareup.com/docs/invoices-api/attachments

This worked fine in the old pre 42 API, with minor syntax change, and I know the limit is now imposed to have a 1000 byte limit for the attachment, but why can't I upload attachments in the sandbox now?

Square's internal developer forum link : https://developer.squareup.com/forums/t/new-api-for-invoice-attachments-error-received-multiple-request-parts-please-only-supply-zero-or-one-parts-of-type-application-json/22126


Solution

  • Docs should read something like this:

    Uploads a file and attaches it to an invoice. This endpoint accepts HTTP multipart/form-data file uploads with a JSON request part and a image_file part. The image_file part must INCLUDE a readable stream in the form of a file (or bytes) [supported formats: GIF, JPEG, PNG, TIFF, BMP, or PDF.], and optionally filename, content_type, headers. See core.File for additional details related to the image_file

    Final working code:

    f_stream = open(attachment_filepath, "rb")
    mime_type, encoding = mimetypes.guess_type(attachment_filepath)
    invoice_pdf = SQUARE_CLIENT.invoices.create_invoice_attachment(
        invoice_id=self.square_original_invoice.id,
        image_file=(
            attachment_filepath,
            attachment_filepath, #can also be f_stream
            mime_type,
        ),
        request={
            "idempotency_key": idem_key,
            "description": f"Invoice-{attachment_filepath}",
        },
    )