Uploading files (API & MCP)
- How to upload an image or file programmatically and get a reusable reference
- The two-step REST upload flow (and the one-step MCP tool)
- Where to use the resulting
{bucket, fullPath}reference
Many AutoTalk features take media — a product image for a WhatsApp catalog, a media message, a profile picture. Instead of hosting those files yourself, upload them to AutoTalk once and get a first-party storage reference you can reuse everywhere:
{ "bucket": "autotalk-prod", "fullPath": "companies/<id>/api/images/storage_manual_upload/<id>.png" }
That {bucket, fullPath} reference is accepted by send_message, Dynadata document fields, and functions like createWhatsappWebEvoProduct (in its images array) — see Where to use the reference.
REST: the two-step upload
Uploading over the REST API is a signed PUT so the bytes go straight to storage. Authenticate every call with your x-api-key.
1. Reserve a signed upload URL
curl -X POST https://api.autotalk.io/v1/storage/upload-url \
-H "x-api-key: sk-YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"mimeType": "image/png",
"fileSize": 41161,
"fileName": "product.png",
"purpose": "whatsapp_catalog_product_image"
}'
The response gives you a short-lived uploadUrl, the exact requiredHeaders, and an uploadIntentId:
{
"success": true,
"uploadIntentId": "...",
"uploadUrl": "https://storage.autotalk.io/...&X-Amz-Signature=...",
"uploadMode": "signed_put",
"bucket": "autotalk-prod",
"fullPath": "companies/<id>/api/images/whatsapp_catalog_product_image/<id>.png",
"category": "images",
"requiredHeaders": { "Content-Type": "image/png", "Content-Length": "41161", "x-amz-meta-...": "..." }
}
2. PUT the bytes
Upload the file directly to uploadUrl, sending every requiredHeaders entry exactly as given. The body size must match fileSize.
curl -X PUT "<uploadUrl>" \
-H "Content-Type: image/png" \
-H "Content-Length: 41161" \
--data-binary @product.png
3. Finalize
curl -X POST https://api.autotalk.io/v1/storage/upload-complete \
-H "x-api-key: sk-YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "uploadIntentId": "<uploadIntentId>" }'
The response is your reusable reference:
{ "success": true, "bucket": "autotalk-prod", "fullPath": "companies/<id>/api/images/whatsapp_catalog_product_image/<id>.png", "size": 41161, "contentType": "image/png", "category": "images", "fileName": "product.png" }
Large files: upload in parts
A single PUT is limited to 90 MB. For a file between 90 MB and 1 GB, add "multipart": true to the step-1 request. Without it, a file over 90 MB is rejected with 400 and a message telling you to add the flag. The response then has "uploadMode": "multipart_put", "uploadUrl": null, and a parts list instead of one URL:
{
"success": true,
"uploadIntentId": "...",
"uploadMode": "multipart_put",
"uploadUrl": null,
"partSize": 67108864,
"parts": [
{ "partNumber": 1, "offset": 0, "size": 67108864, "uploadUrl": "https://storage.autotalk.io/...&partNumber=1&...", "requiredHeaders": { "Content-Length": "67108864" } },
{ "partNumber": 2, "offset": 67108864, "size": 32891136, "uploadUrl": "https://storage.autotalk.io/...&partNumber=2&...", "requiredHeaders": { "Content-Length": "32891136" } }
],
"expiresAtMs": 1760000000000,
"bucket": "autotalk-prod",
"fullPath": "companies/<id>/api/documents/storage_manual_upload/<id>.pdf",
"category": "documents"
}
For each part, PUT the bytes from offset up to (not including) offset + size to that part's uploadUrl. Send its requiredHeaders and no Content-Type: the type was already set when the upload was reserved. Parts can be sent in any order, and a failed part can be sent again to the same URL. Every part must be sent before expiresAtMs, 60 minutes after the reservation.
Then call upload-complete as in step 3. AutoTalk checks that every part arrived at its exact size and joins them into one file.
- If some parts are still missing,
upload-completereturns409and the upload stays open. Send the missing parts and call it again. - If you abandon the upload, it expires after 60 minutes. Its parts are discarded and its quota reservation is released.
To read a stored object later, call GET /v1/storage/url?path=<fullPath>. It returns a signed download URL valid for up to 7 days — the expiry is pinned to the start of the UTC day, so the link lasts at least 6 days from the moment you mint it — and served public, max-age=3600, which makes it CDN-edge-cacheable. Treat the URL string itself as a bearer credential for that whole window: anyone holding it can fetch the object without authenticating, so do not log it, paste it into tickets, or forward it.
MCP: begin/complete or one-step inline
If you connect via the MCP server, use the file tools:
begin_file_upload({fileName, mimeType, fileSize})→ do the HTTP PUT to the returneduploadUrl→complete_file_upload({uploadIntentId}). Returns{bucket, fullPath}. Files up to 1 GB are accepted. Above 90 MB the tool answers withuploadMode: "multipart_put"and apartslist: PUT each part as described in Large files: upload in parts, then callcomplete_file_upload. No flag is needed on this tool.upload_file_inline({fileName, mimeType, data})— pass the base64-encoded bytes directly and get{bucket, fullPath}in a single call. Convenient for small files (≤ ~1 MB); larger files must use the two-step flow.
Where to use the reference
Pass {bucket, fullPath} (or, for images, either a public URL or the ref) wherever media is accepted:
- Send a media message —
send_messagebody.file = {bucket, fullPath}. - WhatsApp catalog —
createWhatsappWebEvoProduct/updateWhatsappWebEvoProductimages: [{bucket, fullPath}, ...]. - Profile / group picture —
updateWhatsappWebEvoProfilePicturepicture,updateWhatsappWebEvoGroupPictureimage. - Dynadata document fields — any storage-object field (e.g. a document's image).
Purposes and limits
purpose(optional, defaults tostorage_manual_upload) tags what the file is for. Allowed values:storage_manual_upload,whatsapp_catalog_product_image,template_message_media,chat_media_upload,scheduled_message_media,contact_avatar,company_logo.fileSizecan be up to 1 GB. OnePUTcan carry at most 90 MB, because each request has to pass through the CDN in front of AutoTalk's storage in one piece. Files over 90 MB are therefore uploaded in parts, each under that limit.POST /v1/storage/upload-urlneeds"multipart": truefor those files;begin_file_uploadswitches to parts on its own. A request over 1 GB is rejected immediately with a clear error. Send the exact byte count; the stored size must match it.- Uploaded bytes count toward your company storage quota; an upload that would exceed it returns
409. - Files live under your company prefix; you can only reference your own tenant's objects.
Next steps
- API Reference — authentication and the full endpoint list
- MCP Servers — connect an AI client and use the file tools