Presign a large media upload
Creates presigned URLs so large media is uploaded straight to object storage instead of through this API.
POST /upload accepts the file itself, but its request body is capped at 100 MB by our edge: a bigger multipart request is rejected with 413 before it ever reaches the API. That ceiling is an edge limit, not a platform one. The real per-media ceilings are the maxFileSize of the presign route you choose (up to 10 GiB) and, at publication time, the target platform's own limits.
Nothing changes for files under 100 MB: keep sending them to POST /upload as a file part exactly as before. Presign only what is too big for that path.
Phase 1 - presign
Declare each file's exact byte length; it is signed into the URLs you get back.
curl -X POST "https://api.multi-upload-tool.com/api/v1/upload/presign" \
-H "x-api-key: kmb_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"route":"tiktok_videos","files":[{"name":"clip.mp4","size":734003200,"type":"video/mp4"}]}'
Phase 2 - upload the bytes
They travel directly to storage and never touch this API or its edge, which is what removes the 100 MB ceiling.
Single-request routes (photos, instagram_photos, threads_photos, x_photos, bluesky_photos, thumbnails, documents) return uploadUrl and uploadHeaders. PUT the whole file to uploadUrl, sending exactly the headers returned: content-type and content-length are part of the signature, so changing or omitting either fails the PUT with SignatureDoesNotMatch. Valid for 1 hour.
Multipart routes (youtube_videos, tiktok_videos, pinterest_videos, threads_videos, x_videos, linkedin_videos) return multipart. For each entry of multipart.parts, PUT the byte range starting at (partNumber - 1) * partSize and size bytes long to that part's uploadUrl, with Content-Length equal to that part's size. Keep each response's ETag header, quotes stripped. Parts may be uploaded in parallel, and a single failed part can be retried by re-PUTting the same URL. Once every part is stored, POST to multipart.completeUrl with Content-Type: application/xml and the parts sorted ascending:
<CompleteMultipartUpload><Part><ETag>abc123</ETag><PartNumber>1</PartNumber></Part><Part><ETag>def456</ETag><PartNumber>2</PartNumber></Part></CompleteMultipartUpload>
If you abandon the upload, send DELETE to multipart.abortUrl: parts left behind are stored and billed and are not cleaned up for you. Part and complete URLs last 6 hours and cannot be refreshed, so upload parts concurrently rather than one after another, and presign again if the window lapses.
Phase 3 - publish
Each presigned file comes back with two URLs for the same object. Pass one of them as the video, photo or file field of POST /upload - those fields already accept a URL string - or of POST /upload/bulk to fan the same media out across accounts.
- bucketUrl - use it for large video. The API reads the object straight from storage, so the file is never streamed back through this API and its edge on the way to the platform. It points at private storage and is not fetchable on its own.
- accessUrl - use it when you also need a URL anyone can fetch, and for photos. It is served from a domain the platforms have verified, which is what lets TikTok and the other pull-based platforms fetch the media. Pass it through unchanged; a rehosted copy on your own domain is rejected by those platforms.
curl -X POST "https://api.multi-upload-tool.com/api/v1/upload" \
-H "x-api-key: kmb_YOUR_TOKEN" \
-F "accountId=101" \
-F "video=https://multi-upload-tools.s3.eu-central-003.backblazeb2.com/public-api/42/1756226400000-a1b2c3d4-clip.mp4" \
-F "title=My Video"
Routes
route selects the validation preset applied to the files you declare. It does not tie the media to a platform - any presigned object may be published to any account - but choosing the preset that matches your target rejects an oversized or wrong-typed file here, instead of at publication time where the platform's own limits still apply.
| route | multipart | max files | max size per file | accepted types |
|---|---|---|---|---|
| youtube_videos | yes | 1 | 10 GiB | video/*, application/octet-stream |
| tiktok_videos | yes | 1 | 4 GiB | video/mp4, video/webm |
| pinterest_videos | yes | 1 | 2 GiB | video/mp4, video/quicktime, video/x-m4v |
| threads_videos | yes | 1 | 1 GiB | video/mp4, video/quicktime |
| x_videos | yes | 1 | 1 GiB | video/mp4, video/quicktime |
| linkedin_videos | yes | 1 | 500 MiB | video/mp4 |
| documents | no | 1 | 100 MiB | PDF, PPT, PPTX, DOC, DOCX |
| photos | no | 35 | 20 MiB | image/jpeg, image/png, image/webp |
| thumbnails | no | 1 | 20 MiB | image/jpeg, image/png, image/webp, image/gif |
| instagram_photos | no | 10 | 8 MiB | image/jpeg, image/png, image/webp |
| threads_photos | no | 20 | 8 MiB | image/jpeg, image/png |
| x_photos | no | 4 | 5 MiB | image/jpeg, image/png, image/webp, image/gif |
| bluesky_photos | no | 4 | 2 000 000 bytes | image/jpeg, image/png, image/webp, image/gif |
A declared size of 0 is rejected: a zero-byte multipart upload can never be completed.
Quota and rate limit
Presigning is metered per user rather than per IP, so a fleet of workers behind one egress address is not throttled as if it were one client, and one busy token cannot spend another customer's allowance. The limit is 120 requests / 15 min; exceeding it returns 429 with retryAfter in seconds. A far looser per-IP backstop of 600 requests / 15 min also applies, and answers 429 the same way.
The request body only declares files, so it is capped at 256 KB - a bigger one is refused with 413 and code: BODY_TOO_LARGE without being read.
The bytes you declare are charged against a rolling 30-day allowance that depends on your plan. Every successful response carries a usage block - what is used including this request, the allowance, and what is left - so you can see the headroom without failing first. Exceeding it returns 403 with code: STORAGE_QUOTA_EXCEEDED. The debit happens when the URLs are issued, using the sizes you declared, and is not refunded if you never upload the bytes: presigning and walking away spends the allowance. Plans with no configured allowance report "unlimited": true and are never blocked.
Request
- Base URL: https://api.multi-upload-tool.com/api/v1
- URL: https://api.multi-upload-tool.com/api/v1/upload/presign
- Auth: API key in header x-api-key
Headers
Act inside this team. Ignored when the token is already pinned to a team; membership is verified on every request.
Request body
Example request
{
"files": [
{
"name": "clip.mp4",
"size": 734003200,
"type": "video/mp4"
}
]
}Response
One presigned entry per declared file, plus the caller's current storage usage.
Changes
No recorded changes to this operation across all 1 revision of this API.