Skip to content

Media and video

A post carries its media as URLs. The routes here put a file into the media library and give back the id and path to pass as image[] on Create a post.

POST /upload multipart upload
POST /upload-from-url the server fetches the file
POST /generate-video AI video, saved to the library
POST /video/function a function of a video generator (voices…)
POST /upload
Content-Type: multipart/form-data

One field, file. The type is read from the bytes, not from the name: image/jpeg, image/png, image/gif, image/webp, image/avif, image/bmp, image/tiff and video/mp4 are accepted.

Terminal window
curl -X POST "https://app.usenadia.com/api/public/v1/upload" \
-H "Authorization: your-api-key" \
-F "file=@reel.mp4"

Response 201, the row of the media library:

{
"id": "cm4med0001",
"name": "k3J9aQ2xZp.mp4",
"originalName": "reel.mp4",
"path": "https://media.usenadia.com/k3J9aQ2xZp.mp4",
"thumbnail": null,
"alt": null
}
Field Type Description
id string What a post takes as image[].id
name string Stored file name
originalName string | null The name the file was sent with
path string Public URL, what a post takes as image[].path
thumbnail string | null When one was generated
alt string | null When one was set

400 with No file provided when the field is missing, or when the bytes are not one of the accepted types.

POST /upload-from-url
{ "url": "https://example.com/reel.mp4" }

The server downloads the file itself and saves it. The URL must be a public HTTPS address (no private network), and its path must end in .png, .jpg, .jpeg, .gif, .webp or .mp4, before any query string. So in practice: MP4, JPEG, PNG, WebP and GIF. A signed URL that expires (R2, S3) is fine as long as the server can read it without signing in.

Response 201: the same media row as above. Errors, all 400:

msg When
File must have a valid extension… The path does not end in an accepted extension
Failed to fetch URL Network error, blocked address, or the origin answered non-2xx (an expired signature does that)
Unsupported file type. The bytes are not an accepted type, whatever the extension says

The file may weigh up to 500 MB and must arrive within 10 minutes. Beyond those, and when the workspace’s budget is spent, other statuses:

Status When
413 The file is over 500 MB (refused on its Content-Length, or cut as it streams)
429 60 imports per hour per workspace, or 2 already running at once: retry later (/upload and /upload-from-url also share a budget of 300 calls per hour)
507 The server is short of disk space: retry later

An AI assistant’s code sandbox usually has a file on its disk and no public URL. For that case there is a three-step flow, where the bytes go straight to UseNadia with curl and never through a tool call:

  1. createUploadUrl (name, Content-Type, exact size) answers a one-time URL under https://app.usenadia.com/api/uploads/…, valid 15 minutes, with the exact curl command to run.
  2. The sandbox runs that command: a PUT of the file, with the declared type and size. 201 when the bytes arrived.
  3. confirmUpload checks the bytes (type sniffed, a video must be readable with a duration) and saves the media; it answers the row plus size, duration, width, height and codec, so the client can compare with the file it meant to send.
POST /generate-video
Field Type Required Description
type string Yes The generator: image-text-slides, or veo3 when it is configured
output string Yes vertical or horizontal
customParams object Yes What the generator needs
{
"type": "image-text-slides",
"output": "vertical",
"customParams": {
"voice": "elevenlabs-voice-id",
"prompt": "Three things nobody tells you about Malta"
}
}

The video is rendered, then saved to the media library: the response is 201 with the same media row as an upload (id, name, originalName, path, thumbnail, alt), ready for a post. Each generation uses one AI video credit of the plan; 400 with type must be any of: … names the generators available right now.

POST /video/function
Field Type Required Description
identifier string Yes The generator, e.g. image-text-slides
functionName string Yes The function to call
params object No Its arguments, when it takes some

The one function today is loadVoices of image-text-slides, which lists the voices a slideshow can be read with:

{ "identifier": "image-text-slides", "functionName": "loadVoices" }

Response 201, the raw answer of the generator:

{ "voices": [ { "id": "21m00Tcm4TlvDq8ikWAM", "name": "Rachel", "preview_url": "https://…" } ] }

400 when the function does not exist on that generator.