Examples
Upload a file as raw bytes
Direct binary upload with a presigned URL — for local files and large videos
When your file isn't hosted at a public URL (or it's a large video), upload the bytes directly. Three steps: get a presigned URL, PUT the bytes, complete.
Requires the media:write scope.
1. Start the upload
Declare the name, MIME type, and size — quotas are checked now, before any bytes move:
curl -X POST https://api.nimply.io/v1/media/uploads \
-H "Authorization: Bearer nim_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"fileName": "launch-video.mp4",
"contentType": "video/mp4",
"sizeBytes": 10485760
}'{
"mediaId": "5c4d37cf-…",
"uploadUrl": "https://…r2.cloudflarestorage.com/…?X-Amz-Signature=…",
"method": "PUT",
"headers": { "Content-Type": "video/mp4" },
"expiresInSeconds": 3600
}2. PUT the bytes
Send the raw file to uploadUrl with exactly the Content-Type you declared — it's part of the URL's signature:
curl -X PUT "<uploadUrl>" \
-H "Content-Type: video/mp4" \
--data-binary @launch-video.mp4No Authorization header here — the URL itself is the credential. It expires after an hour.
3. Complete
curl -X POST https://api.nimply.io/v1/media/uploads/<mediaId>/complete \
-H "Authorization: Bearer nim_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "sizeBytes": 10485760, "width": 1920, "height": 1080 }'The response is the finished media asset — use its id in a post's mediaIds. Thumbnails generate in the background.
All together in JavaScript
const API = "https://api.nimply.io";
const KEY = process.env.NIMPLY_API_KEY;
async function uploadFile(bytes, fileName, contentType) {
const ticket = await fetch(`${API}/v1/media/uploads`, {
method: "POST",
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ fileName, contentType, sizeBytes: bytes.byteLength }),
}).then((r) => r.json());
await fetch(ticket.uploadUrl, {
method: "PUT",
headers: ticket.headers,
body: bytes,
});
return fetch(`${API}/v1/media/uploads/${ticket.mediaId}/complete`, {
method: "POST",
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ sizeBytes: bytes.byteLength }),
}).then((r) => r.json());
}Gotchas
- Don't skip complete — until then the asset has a placeholder size, consumes no quota, and shouldn't be attached to posts.
- A mismatched
Content-Typeon the PUT gets a403 SignatureDoesNotMatchfrom storage. - Already have the file at a public URL?
POST /v1/mediais one call instead of three.