When a workflow input is a file, the SDK uploads it as multipart/form-data (the same shape as curl -F). No base64, no payload doubling.
from pathlib import Path
client.workflows.run("extract-invoice", input={
"contract_document": Path("contract.pdf"),
})Filename and MIME type are inferred automatically.
with open("contract.pdf", "rb") as f:
client.workflows.run("extract-invoice", input={"contract_document": f})Filename inferred from f.name.
client.workflows.run("extract-invoice", input={
"contract_document": {
"content": data,
"filename": "contract.pdf",
"mime_type": "application/pdf",
},
})client.workflows.run("compare-versions", input={
"original": Path("v1.pdf"),
"revised": Path("v2.pdf"),
"reference": Path("ref.pdf"),
})Each file becomes a top-level form field. Mix files and scalar inputs freely; scalars ride along in a single _json text field automatically.
Only top-level file values become multipart fields. Files inside lists or nested dicts stay in the JSON sidecar and the server won't see them as uploads:
# DON'T — `documents` becomes a JSON list, no upload.
client.workflows.run("compare", input={"documents": [Path("a.pdf"), Path("b.pdf")]})
# DO — flatten to top-level keys, your workflow accepts them by name.
client.workflows.run("compare", input={"document_0": Path("a.pdf"), "document_1": Path("b.pdf")})# Don't do this. Doubles the payload size and skips the optimised path.
import base64
client.workflows.run("extract-invoice", input={
"contract_document": base64.b64encode(data).decode(),
})The SDK picks multipart whenever it sees a Path, file-like object, or {"content": ..., "filename": ..., "mime_type": ...} dict. Plain strings pass through as scalar inputs.