Many upload APIs expect the binary object and its JSON metadata in the same multipart request. Sending both parts together lets the server validate the metadata against the file before it stores the upload.
In cURL, each --form option becomes one part in the multipart/form-data body. Use @sample.txt when the part should be sent as a file upload with a filename, and use <metadata.json when the part should stay a normal multipart field whose value comes from a local file. Add ;type=application/json to the JSON part so the server sees that field as JSON content.
Most multipart mistakes come from the wrong field names or the wrong prefix on the JSON part. Keep the JSON in a separate metadata.json file so it is easy to review, copy the exact file and metadata field names from the API documentation, and change the JSON part to @metadata.json only when the API explicitly says metadata is uploaded as a file.
Related: How to upload files with cURL
Related: How to send binary data with cURL
$ printf 'Quarterly vendor onboarding packet.\n' > sample.txt
$ cat <<'EOF' > metadata.json
{
"description": "Vendor onboarding packet",
"tags": ["documents", "intake"],
"expires_in_days": 30
}
EOF
Do not leave secrets or personal data in a plaintext JSON file longer than needed.
$ curl --silent --show-error \
--form 'file=@sample.txt;type=text/plain' \
--form 'metadata=<metadata.json;type=application/json' \
https://httpbin.org/post
{
"args": {},
"data": "",
"files": {
"file": "Quarterly vendor onboarding packet.\n"
},
"form": {
"metadata": "{\n \"description\": \"Vendor onboarding packet\",\n \"tags\": [\"documents\", \"intake\"],\n \"expires_in_days\": 30\n}\n"
},
##### snipped #####
"url": "https://httpbin.org/post"
}
If metadata shows up under files instead of form, the JSON part was sent with @metadata.json and needs to use <metadata.json for a normal form field.
$ curl --silent --show-error \ --header 'Authorization: Bearer API_TOKEN' \ --form 'file=@sample.txt;type=text/plain' \ --form 'metadata=<metadata.json;type=application/json' \ https://api.example.com/uploads
Keep <metadata.json unless the API documentation explicitly says the metadata itself is uploaded as a file.
Related: How to send data in HTTP requests with cURL
$ rm sample.txt metadata.json