Nimply Developers
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.mp4

No 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-Type on the PUT gets a 403 SignatureDoesNotMatch from storage.
  • Already have the file at a public URL? POST /v1/media is one call instead of three.

On this page