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.jpgThe 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 answers403: add the media again for new links. - Send each part with the
headersit lists. AContent-Typeother than the one you declared answers415, and a request withoutContent-Lengthanswers411(curl andfetchsend it). A file larger than thesizeyou declared answers413. - 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
sizebytes, the second the next, and so on. Each part must be exactly itssize, or it answers400. - Parts can go up in any order, or at the same time.
- Once every part is up,
POSTtocomplete.url: the answer is the media,ready. With a part missing it answers409and names it.
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);Import from a link
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
httporhttpsaddress, with at most 3 redirects; - answer with the file's
Content-Type, one of the types above, and itsContent-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
status | Meaning |
|---|---|
uploading | Waiting for its file |
ready | Can go on a post |
failed | Rizamo 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
profileIdto 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. maxFilesis 1 to 20, 10 when left out.noteis 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):statusisopen,doneonce the person sends their files, orexpired. Itsmediaare 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:fillorcrop), a video's codec, frame rate or header. The copy is kept beside your file, which stays as it is. - Each target's
changes, inPOST /v1/posts/validateand in the post, say what each copy changes:format,size,width,fill,crop,fpsorfaststart. - 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.