rizamodocs

Media

Upload images and videos, import them from a link or ask a person for them, then put them on a post.

A file goes up first, as media. A post then names it by id in mediaIds. Adding media needs the scope media:write (create_media in MCP).

Rizamo takes image/jpeg, image/png, image/webp, video/mp4 and video/quicktime, up to 4 GB each.

Upload a file

Say what you will send

curl https://api.rizamo.com/v1/media \
  -H "Authorization: Bearer $RIZAMO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "contentType": "image/jpeg", "size": 482113, "alt": "A loaf of bread on the counter" }'

size is the file's exact size in bytes (wc -c < photo.jpg). The answer is the media, still uploading, and where to send the file:

{
  "media": { "id": "med_...", "type": "image", "status": "uploading", "contentType": "image/jpeg", "size": 482113 },
  "upload": {
    "parts": [{ "url": "https://api.rizamo.com/api/media/upload/...", "method": "PUT", "headers": { "content-type": "image/jpeg" }, "size": 482113 }],
    "complete": null,
    "expiresAt": "2026-11-04T16:00:00.000Z"
  }
}

Send the file

curl -X PUT "https://api.rizamo.com/api/media/upload/..." \
  -H "Content-Type: image/jpeg" \
  --data-binary @photo.jpg

The answer is the media, ready, with its width and height (and duration, for a video). It can go on a post now.

  • Upload links are signed, so they take no API key, and work for 6 hours (expiresAt). An expired one answers 403: add the media again for new links.
  • Send each part with the headers it lists. A Content-Type other than the one you declared answers 415, and a request without Content-Length answers 411 (curl and fetch send it). A file larger than the size you declared answers 413.
  • Sending files to these links does not count toward the limits.

Large files

A file up to 64 MB is one part. A larger one comes in parts of 64 MB, the last one with what is left:

  • The first part takes the file's first size bytes, the second the next, and so on. Each part must be exactly its size, or it answers 400.
  • Parts can go up in any order, or at the same time.
  • Once every part is up, POST to complete.url: the answer is the media, ready. With a part missing it answers 409 and names it.
upload.ts
import { openAsBlob } from "node:fs";

const file = await openAsBlob("video.mp4");

const res = await fetch("https://api.rizamo.com/v1/media", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RIZAMO_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({ contentType: "video/mp4", size: file.size }),
});
if (!res.ok) throw new Error(await res.text());
const { media, upload } = await res.json();

let start = 0;
for (const part of upload.parts) {
  const sent = await fetch(part.url, { method: part.method, headers: part.headers, body: file.slice(start, start + part.size) });
  if (!sent.ok) throw new Error(await sent.text());
  start += part.size;
}
if (upload.complete) {
  const done = await fetch(upload.complete.url, { method: upload.complete.method });
  if (!done.ok) throw new Error(await done.text());
}

console.log(media.id);
curl https://api.rizamo.com/v1/media \
  -H "Authorization: Bearer $RIZAMO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "url": "https://example.com/photos/bread.jpg", "alt": "A loaf of bread on the counter" }'

Rizamo downloads the file during the call, and the answer's media is ready (upload is null). The address must:

  • be a public http or https address, with at most 3 redirects;
  • answer with the file's Content-Type, one of the types above, and its Content-Length;
  • send the whole file within 30 seconds.

Otherwise the call answers 400 and says why. For large videos, or a slow host, upload the file instead.

States

statusMeaning
uploadingWaiting for its file
readyCan go on a post
failedRizamo could not read the file, such as a damaged video: add it again

Only ready media can go on a post. GET /v1/media/{id} (get_media) shows it, with url, a link to see the file that works for a few hours: get the media again for a fresh one.

Who reaches it, and how long it stays

  • Media is yours (the person, key or agent that added it) until a post uses it. It then belongs to that post's profile. Send profileId to put it in a profile at once.
  • A key or person limited to some profiles reaches their own media and their profiles' media.
  • Media no post uses is deleted 7 days after it was added.

Ask a person for files

When your agent or integration does not have the files, Rizamo makes a page where a person adds them, with no account (create_upload in MCP):

curl https://api.rizamo.com/v1/uploads \
  -H "Authorization: Bearer $RIZAMO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "note": "Photos of the new menu", "maxFiles": 5, "profileId": "prof_..." }'
  • Give the person url: only this answer has it. The page works for 24 hours.
  • maxFiles is 1 to 20, 10 when left out. note is shown on the page, with the name of the person who asked (for a key or an agent, the person who made or connected it) and the profile, and a line saying a Rizamo customer sent it.
  • Follow it with GET /v1/uploads/{id} (get_upload): status is open, done once the person sends their files, or expired. Its media are ready to put on a post.

What Rizamo changes for each network

Rizamo checks each file against every target's network when the post is made, so most file problems are refused then, not when it goes out.

  • Refused when attached: a video too short or too long for the format, a wide video for a format that takes only vertical video (Facebook reels and stories and YouTube Shorts), or files of a type or number the format does not take.
  • Fixed with a copy: a type the network does not take (Instagram takes only JPEG images), a file too large or too wide, an Instagram feed image of a shape Instagram refuses (options.instagram.fit: fill or crop), a video's codec, frame rate or header. The copy is kept beside your file, which stays as it is.
  • Each target's changes, in POST /v1/posts/validate and in the post, say what each copy changes: format, size, width, fill, crop, fps or faststart.
  • A video copy can take a few minutes. Rizamo starts it 2 hours before a scheduled post's time. A post published now waits for its copy, and its publishing window starts once the copy is ready.

Each network's own rules: Instagram, Facebook, TikTok, Threads, YouTube.

From upload to post

Add the image

Upload it as above, or import it from a link. Keep its media.id.

Check the post

curl https://api.rizamo.com/v1/posts/validate \
  -H "Authorization: Bearer $RIZAMO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "profileId": "prof_...",
    "targets": [{ "accountId": "acc_..." }],
    "text": "Fresh bread at 9.",
    "mediaIds": ["med_..."],
    "status": "scheduled",
    "scheduledAt": "2027-06-01T09:00:00Z"
  }'

ok is true when creating it would work. problems lists everything that would stop it, and targets[].changes what Rizamo does to the image for each network.

Schedule it

curl https://api.rizamo.com/v1/posts \
  -H "Authorization: Bearer $RIZAMO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "profileId": "prof_...",
    "targets": [{ "accountId": "acc_..." }],
    "text": "Fresh bread at 9.",
    "mediaIds": ["med_..."],
    "status": "scheduled",
    "scheduledAt": "2027-06-01T09:00:00Z"
  }'

scheduledAt must be in the future. Once the profile has posting times (Posting queues), send "queue": true instead to take the next free one; without them it answers 409 queue_empty. Keep the same Idempotency-Key if you send it again: the post is not created twice.

On this page