2025, Dec 28 23:00

How to fix INVALID_CONTENT_TYPE when uploading invoice attachments via Square Invoices API v42+ (Python SDK)

Resolve INVALID_CONTENT_TYPE on Square Invoices API v42+ uploads with the Python SDK: send multipart data with an image_file tuple; note 1KB Sandbox limit.

Square Invoices API v42+: fixing INVALID_CONTENT_TYPE when uploading attachments

Upgrading to the newer Square API versions (42+) surfaces a breaking change that trips up multipart uploads for invoice attachments. In development, attempts to attach a tiny JPEG to an invoice can fail with a 400 and the message INVALID_REQUEST_ERROR INVALID_CONTENT_TYPE: Received multiple request parts. Please only supply zero or one parts of type application/json. In the Sandbox, there is also a strict 1000-byte total-attachments limit that returns BAD_REQUEST when exceeded, which makes diagnostics even trickier.

Reproducing the failure

The request is made with multipart/form-data. The code below shows how the failing call was structured: a JSON request part plus an image_file part that was populated with a filesystem path (a readable stream was tried as well, with the same result).

img_path = "local/path/to/file.jpg"
dedupe_token = "some-unique_key_like_square_invoice_id"
fh = open(img_path, "rb")
try:
    upload_resp = SQ_CLIENT.invoices.create_invoice_attachment(
        invoice_id=invoice_ref.id,
        # also tried passing `fh` here, same error
        image_file=img_path,
        request={
            "description": f"Invoice-{img_path}",
            "idempotency_key": dedupe_token,
        },
    )
except ApiError as err:
    print(f"Attachment failed: {err}")

On the wire, the successful API Explorer call had Content-Disposition: form-data; name="file"; filename="900b.jpeg" and included binary image data. The failing SDK call sent name="image_file" whose body was the literal file path text, and the JSON part appeared with application/json;charset=utf-8. The server responded that it received multiple application/json parts or an unexpected shape.

What actually breaks

The endpoint accepts HTTP multipart/form-data with up to two parts: a JSON request part and a single image_file part. The image_file part must carry a readable stream of the file (file object or bytes), not just a path string. When the multipart body includes more than one application/json part or the file field isn’t a stream, the service rejects the payload with INVALID_CONTENT_TYPE and the “multiple request parts” detail.

The field name difference seen in raw requests (file in the API Explorer versus image_file in SDK code) is not the root cause here; the documented input is image_file. The critical problem is the payload shape and how the Python SDK expects the file to be provided.

Separately, the Sandbox enforces a tiny total attachments cap of 1000 bytes. Larger files trigger BAD_REQUEST with Total size of all attachments exceeds Sandbox limit: 1000 bytes. A ~900-byte JPEG can succeed; ~988 bytes can fail due to this limit. That limit is real in Sandbox and must be respected during tests.

The fix: provide a Core File tuple (filename, stream-or-bytes, content_type)

The invoices.create_invoice_attachment call needs image_file supplied as a file payload that includes a readable stream. In the Python SDK, this is achieved by passing a tuple that includes filename, the stream or bytes, and the MIME type. The form then contains exactly one JSON request part and one image_file part with proper Content-Disposition and Content-Type.

import mimetypes
blob_path = attachment_path
io_handle = open(blob_path, "rb")
media_type, _ = mimetypes.guess_type(blob_path)
result = SQ_CLIENT.invoices.create_invoice_attachment(
    invoice_id=invoice_ref.id,
    image_file=(
        blob_path,           # filename
        blob_path,           # can also be `io_handle`
        media_type,
    ),
    request={
        "idempotency_key": dedupe_token,
        "description": f"Invoice-{blob_path}",
    },
)

This shape ensures the multipart part arrives as image_file with a readable stream, Content-Type (for example, image/jpeg or application/pdf), and an optional filename. Supported formats include GIF, JPEG, PNG, TIFF, BMP, and PDF. For additional specifics on how the SDK represents file payloads, see core.File in the Square Python SDK.

Why this matters during a v42+ upgrade

Version 42+ tightened the request contract around multipart handling for invoice attachments. If the file is not sent as a true stream and the JSON part is duplicated or mis-typed, the platform rejects the request with INVALID_CONTENT_TYPE. In Sandbox, the 1000-byte cap can mask or compound the issue, returning BAD_REQUEST even when the payload is otherwise correct. Using the proper tuple for image_file and keeping the JSON to a single request part avoids both the structural error and unnecessary retries.

Wrap-up

When attaching files to invoices with the Square Python SDK in v42+, send exactly one JSON request part and one image_file part that includes a readable stream. Pass image_file as a tuple of (filename, stream-or-bytes, content_type) so the multipart encoding matches what the API Explorer generates. While testing in Sandbox, keep files under the 1000-byte total-attachments limit to rule out size-related failures. With those details aligned, uploads that previously failed with INVALID_CONTENT_TYPE should succeed consistently.