Uploads
Upload images, video and audio to Nodaro with POST /v1/upload, copy files from a URL, import social videos, trim stored media for free and list your library.
Uploads put your own images, video and audio into Nodaro's storage, so that any node can use them by URL. POST /v1/upload stores one file and returns its URL. Other routes copy a file from a URL, import a video from a social network, cut a stored file for free, or list the media you have stored.
Endpoints
| Method | Path | What it does |
|---|---|---|
POST | /v1/upload | Upload one file, as multipart form data. |
POST | /v1/save-to-storage | Copy a file from a URL into your storage, as a job. |
POST | /v1/download-video | Import a video from a social network or a direct video link. |
GET | /v1/download-video/progress/:downloadId | The import's progress, as server-sent events. |
POST | /v1/video-metadata | Read a video's duration, size and title without downloading it. |
POST | /v1/media/process | Trim or crop a stored video or audio file. Free and synchronous. |
GET | /v1/library | Your stored media, with a storage summary. |
Upload a file
Send the file as multipart form data in the file field. You can also send type, set to image, video or audio.
curl -s -X POST https://app.nodaro.ai/v1/upload \
-H "Authorization: Bearer $NODARO_API_KEY" \
-F "file=@portrait.png;type=image/png" \
-F "type=image"{
"data": {
"url": "https://…/portrait.png",
"assetId": "5d6e7f80-91a2-4b3c-8d4e-5f60718293a4",
"thumbnailUrl": "https://…/portrait-thumb.webp",
"category": "image",
"filename": "portrait.png",
"mimeType": "image/png",
"sizeBytes": 1482233,
"r2Key": "…/portrait.png"
}
}// In a browser, file comes from an <input type="file">.
// In Node 20 or newer, build it from bytes:
import { readFile } from 'node:fs/promises'
const file = new File([await readFile('portrait.png')], 'portrait.png', { type: 'image/png' })
const upload = await client.uploads.upload(file)
console.log(upload.url) // pass it as imageUrl, videoUrl or audioUrl| Field | Meaning |
|---|---|
url | The stored file's public URL. Use it in any node. |
assetId | The id of the file in your storage. |
thumbnailUrl | A thumbnail for images and video, or null for audio. |
category | image, video or audio, as the server classified the file. |
filename | The display name of the file. |
mimeType | The file type the server settled on. See below. |
sizeBytes | The stored size in bytes. |
r2Key | The file's key in storage. |
The CLI has no upload command. To copy a file that is already online into your storage from the terminal, use nodaro media save <url>.
Formats and file types
| Media | Formats |
|---|---|
| Image | PNG, JPEG, WebP |
| Video | MP4, MOV, WebM |
| Audio | MP3, WAV, M4A and AAC, OGG, WebM, FLAC. Up to 50 MB. |
Each media type has its own size limit. The type your client declares does not have to be the standard one, because the server works out the real type before it checks the file:
- Parameters are removed.
audio/webm;codecs=opusbecomesaudio/webm. - Common alternative spellings are mapped.
audio/vnd.dlna.adts, which Windows uses for a plain.aacfile, becomesaudio/aac,image/jpgbecomesimage/jpeg, andvideo/movbecomesvideo/quicktime. - An unhelpful type is read from the file name. With
application/octet-stream, or no type at all, the extension decides:clip.mp4is stored asvideo/mp4.
A type that still matches no accepted format answers 400 validation_error, with the list of accepted formats. The type the server settled on comes back as mimeType, and the file is served with it.
When an upload is refused
| Status | Code | Meaning |
|---|---|---|
| 400 | validation_error | The file is not in an accepted format. |
| 413 | Your storage is full. The SDK throws StorageExceededError, with your limit in limitBytes. | |
| 422 | upload_blocked | The deployment's upload policy refused the file before it was stored. Show the message to your user as it is. Only on deployments with such a policy. |
Use a URL instead
Most routes take media by URL, such as imageUrl, videoUrl, audioUrl and referenceImageUrls, so a file that is already online does not need an upload. The URL must be a public http or https address: a URL that points at a private network address, or uses another scheme, is refused.
Copy the file into Nodaro's storage when the URL is temporary, such as a signed link that expires, or when you want the file to stay available:
curl -s -X POST https://app.nodaro.ai/v1/save-to-storage \
-H "Authorization: Bearer $NODARO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"mediaUrl": "https://example.com/clip.mp4", "filename": "clip.mp4", "mediaType": "video"}'
# {"jobId":"…"} poll the job; its output carries the stored fileconst { jobId } = await client.media.saveToStorage({
mediaUrl: 'https://example.com/clip.mp4',
filename: 'clip.mp4',
mediaType: 'video',
})nodaro media save https://example.com/clip.mp4 --filename clip.mp4 --type video --watchIn a workflow, pass an uploaded file's URL to an Upload Image, Upload Video or Upload Audio node through the run's inputs. The media url fields of input nodes can always be overridden by a run. See Run a workflow with new input values.
Import a video from a social network
POST /v1/download-video imports a video from YouTube, TikTok, Instagram, X or Facebook, or from a direct link to a video file, into your storage. It answers with a downloadId, not a job id:
{ "url": "https://youtu.be/XXXX", "maxHeight": 720, "sectionStartSec": 30, "sectionEndSec": 90 }maxHeightlimits the resolution; leave it out for the best available.sectionStartSecandsectionEndSecfetch only that part of the video: send both or neither.- A video without a sound track fails, unless you send
requireAudio: false. - Each account can run 4 imports at once. A fifth answers
429 too_many_downloads. GET /v1/download-video/progress/:downloadIdstreams the progress as server-sent events, about twice a second:{ phase, percent, videoUrl?, thumbnailUrl?, error? }. The stream ends when the import iscompleted, with the storedvideoUrl, orfailed, with anerror. The progress is kept only for a short time after the import ends, so start reading it right away.- The finished video is saved in your library.
To check a video before you import it, POST /v1/video-metadata with { url } reads its duration, size and title without downloading it. From the terminal: nodaro media download <url> --max-height 720 --watch and nodaro media metadata <url>. The other media tools are in Voice and media.
Trim or crop a stored file
POST /v1/media/process cuts or crops a stored video or audio file. It is free and answers at once, without a job:
{
"sourceUrl": "https://…/interview.mp4",
"type": "video",
"trim": { "startTime": 12.5, "endTime": 48 },
"crop": { "x": 0, "y": 0, "width": 1080, "height": 1080 }
}The answer is { "data": { url, thumbnailUrl, assetId, sizeBytes, mimeType, metadata } }. format sets the output format: mp4 or webm for video, mp3, wav, m4a or aac for audio. With deleteSource: true, the source file is removed afterwards when it is yours and nothing else uses it, so the cut replaces the original. In the SDK, call client.media.process(input). For trimming inside a workflow, use the Trim Video node, which costs credits.
List your stored media
GET /v1/library returns your stored images, video and audio, with a summary of your storage. Filter it with type (image, video or audio) and page through it with limit and cursor. In the SDK, call client.library.list({ type: 'image' }).
Upload from an AI assistant
The MCP server offers three ways to upload, each for images, audio and video. They all need the assets:write scope.
| Way | Tools | Works in |
|---|---|---|
| In-chat file picker | upload_image_widget, upload_audio_widget, upload_video_widget | Claude.ai on the web. The image picker takes up to 10 files. |
| Browser upload page | request_image_upload, request_audio_upload, request_video_upload | Every MCP client. The tool returns a page to open in a browser and the file's final public URL. |
| Presigned upload URL | prepare_image_upload, prepare_audio_upload, prepare_video_upload | Clients that can run shell commands, such as Claude Code, Cursor or Cline. Send the file with curl -X PUT --data-binary @file. Not on Claude.ai web or Android. |
Frequently asked questions
Related
Nodes
Voice and media
Upload Image
Workflows
MCP tools reference
Last updated on
Executions
Follow a Nodaro workflow run node by node, read each node's result, list a workflow's past runs, and cancel a run now or after its running nodes finish.
Webhooks
Start a Nodaro workflow from any system with a Webhook Trigger URL, create schedules through the API, and send results to your server with Webhook Output.