Multipart File Uploads
View SourceTinkex can now build multipart/form-data requests without any external helpers. Pass a :files option to Tinkex.API.post/3 and the SDK will:
- Normalize file inputs (binaries, iodata,
File.Stream, or file paths) and read paths into memory - Flatten the request body into form fields using bracket notation (e.g.,
%{foo: %{bar: 1}}→"foo[bar]" => "1") - Generate multipart boundaries and set
Content-Typeautomatically (respects a caller-providedboundaryif present) - Preserve tuple metadata
(filename, content, content_type, headers)per Python SDK parity
Requires only
TINKER_API_KEYand works against any Tinker endpoint that expects multipart bodies.
Supported file inputs
You can supply files as a map or list (mirrors Python’s files API):
- Raw binaries or iodata:
%{"upload" => "hello"},%{"upload" => <<1, 2, 3>>} - File paths:
%{"upload" => "/tmp/file.txt"}(Tinkex reads the file and uses the basename as filename) - Tuples with metadata:
{filename, content}{filename, content, content_type}{filename, content, content_type, headers}(headers can be a map or list of{key, value})
Quickstart
{:ok, _} = Application.ensure_all_started(:tinkex)
config = Tinkex.Config.new(
api_key: System.fetch_env!("TINKER_API_KEY"),
base_url: System.get_env("TINKER_BASE_URL", "https://tinker.thinkingmachines.dev/services/tinker-prod")
)
files = %{
"file" =>
System.get_env("TINKER_UPLOAD_FILE") ||
"examples/uploads/sample_upload.bin"
}
body = %{note: "multipart demo"}
upload_path = "/"
case Tinkex.API.post(upload_path, body, config: config, files: files) do
{:ok, response} -> IO.inspect(response, label: "upload response")
{:error, error} -> IO.puts("Upload failed: #{inspect(error)}")
endIf you need to preview what will be sent, you can use the lower-level helpers directly:
{:ok, normalized} = Tinkex.Files.Transform.transform_files(files)
form_fields = Tinkex.Multipart.FormSerializer.serialize_form_fields(body)
{:ok, _body, content_type} = Tinkex.Multipart.Encoder.encode_multipart(form_fields, normalized)
IO.puts("Multipart Content-Type: #{content_type}")Header behavior
- If
:filesare provided (or you setcontent-type: multipart/form-data), Tinkex switches to multipart encoding and replaces the Content-Type header with one that includes the boundary (unless you already set a boundary). - If no files are provided, JSON encoding is preserved exactly as before.
Troubleshooting
- 415/422 responses usually mean the endpoint rejected the payload; check that the path expects multipart and that filenames/content-types match what the API requires.
- Paths are read eagerly into memory to match the Python SDK; for large payloads consider chunked uploads via a dedicated endpoint when available.