Nodaro Docs
DocumentationNode ReferenceModelsAI Agents (MCP)DevelopersSelf-hostingResearch
REST API

Voice and media

Voices, voice changing, multi-speaker recast, dubbing, video import, captions, trimming, audio tools and social publishing, all as REST endpoints on Nodaro.

The voice and media endpoints expose Nodaro's voice and media tools as plain REST. They cover the voice catalog, voice changing and multi-speaker recasting, dubbing, voice design, video import, captions, trimming, audio tools, transcription and social publishing. Most of them are jobs: the POST returns { jobId } at once, you poll GET /v1/jobs/:id/status until the status is completed, and you read the result from output_data.

The routes work on every edition, with the exceptions marked below, and take a bearer token. See Authentication, and Jobs for polling many jobs at once. To send local files, upload them first; see Uploads.

Voices

MethodPathWhat it does
GET/v1/voicesThe premade voice catalog: name, voice_id, gender, accent and age.
GET/v1/voices/librarySearch the shared Voice Library with search, gender, language, page, page_size and more.
GET/v1/voice-clonesThe voice clones you made before cloning was retired.
PATCH/v1/voice-clones/:idRename or edit one of those clones.
DELETE/v1/voice-clones/:idDelete one of those clones.
POST/v1/voice-clones, /v1/voice-clones/from-urlRetired. Both answer 410 voice_cloning_retired.

Wherever a route accepts a voice, pass a catalog voice's voice_id, a premade voice's name such as Rachel, or a clone's elevenlabsVoiceId. Clones made before the retirement keep working everywhere.

A Voice Library result may carry recommendedProvider: the text-to-speech model the voice is verified on. When your app has no model picker, send it as provider on Text to Speech, so the voice sounds like its preview. verifiedProviders lists every model the voice is verified on; when your app has a picker, override the user's choice only when it is not in that list. hasMore in the response drives "load more".

import { createClient, StaticTokenAuth } from '@nodaro/sdk'

const client = createClient({
  baseUrl: 'https://app.nodaro.ai',
  auth: new StaticTokenAuth(process.env.NODARO_API_KEY!),
})

const { voices, hasMore } = await client.voices.searchLibrary({ search: 'deep', language: 'en' })
const voice = voices[0]
await client.nodes.run('text-to-speech', {
  text: 'Hello!',
  voice: voice.voice_id,
  voiceType: 'library',
  ...(voice.recommendedProvider ? { provider: voice.recommendedProvider } : {}),
})

Design a voice, or speak in a described voice

MethodPathBodyResult
POST/v1/voice-design{ text, voiceDescription, model?, loudness?, guidanceScale?, seed?, quality?, shouldEnhance? }A preview and a reusable voice id.
POST/v1/voice-remix{ text, voiceDescription }Speech in the described voice, without cloning.

For Voice Design, text is a preview line of 100 to 1,000 characters, loudness runs from -1 to 1 and guidanceScale from 0 to 100. For Voice Remix, text is 1 to 5,000 characters. Both return a job.

Change the voice in a recording

POST /v1/voice-changer replaces the voice in an audio track, or in a whole talking video, with one target voice. Send exactly one of audioUrl or videoUrl; when you send both, the video wins. For a video, Nodaro takes the audio out, changes the voice and puts it back on the original picture, and the job's output_data carries both videoUrl and audioUrl.

curl -X POST https://app.nodaro.ai/v1/voice-changer \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "videoUrl": "https://cdn.nodaro.ai/uploads/talking.mp4", "voiceId": "Aria", "removeBackgroundNoise": false }'
const { jobId } = await client.voices.change({
  videoUrl: 'https://cdn.nodaro.ai/uploads/talking.mp4',
  voiceId: 'Aria',
})
nodaro voice changer --voice Aria --video https://cdn.nodaro.ai/uploads/talking.mp4 --watch
FieldWhat it does
voiceIdRequired. The target voice.
audioUrl or videoUrlRequired, one of them. The recording to change.
modelThe speech-to-speech model.
stability, similarityBoost0 to 1. How steady the delivery is, and how close to the target voice.
style0 to 1, default 0. Exaggerates the delivery, at some cost in speed and steadiness.
useSpeakerBoostSharpens the likeness to the target voice, a little more slowly.
seedAn integer that makes the output repeatable.
removeBackgroundNoisetrue returns only the clean voice; false keeps the music and effects under the new voice.

See Voice Changer for prices and tips.

Recast several speakers

Voice Changer Pro detects each speaker in a recording and gives each one a different voice, keeping the words and the timing. It runs on Nodaro Cloud. A self-hosted install connected to Nodaro Cloud can run the one-shot recast through that connection; the analyze and export steps need Nodaro Cloud itself. See Cloud connect.

MethodPathWhat it does
POST/v1/voice-changer-proRecast every mapped speaker, as a finished video or as separate stems.
POST/v1/voice-changer-pro/analyzeDetect the speakers without recasting. Nodaro Cloud only.
POST/v1/voice-changer-pro/exportRender a finished video from your own mix of the stems. Nodaro Cloud only.

Map voices to speakers

orderedVoices maps speakers by detection order: speaker 0 gets entry 0, speaker 1 gets entry 1, and so on, with 1 to 8 entries. Speakers past the end of the list keep their own voice. Each entry is one of:

  • A voice id, such as "Rachel".
  • null, a keep-slot: that speaker keeps their own voice, and later speakers are still recast. Keep-slots cost nothing, but at least one entry must be a voice.
  • An object with per-speaker settings: { voiceId, engine?, stability?, similarityBoost?, style?, useSpeakerBoost?, seed?, volumeMode?, volume? }. engine is sts (the default, speech-to-speech) or v3, which performs the line again from the transcript with ElevenLabs v3 and takes a stability of 0, 0.5 or 1 only. volumeMode is match (the default), normalize or manual, with volume from 0 to 200 percent.

Before recasting, Nodaro always separates the voices from the music and effects. The rest of the body controls the result:

FieldWhat it does
audioUrl or videoUrlRequired, one of them. For a video, the recast audio goes back on the original picture.
preserveBackgroundMix the music and effects back under the new voices. Default true.
separationQualityfast (the default, keeps more of the voice) or best (a finer split).
removeBackgroundNoiseAlso denoise the result.
musicVolumeMode, musicVolumeThe level of the kept background: match (the default), normalize, or manual with 0 to 200 percent.
voiceFxA reverb or an echo on the voices only: { preset, wetDryMix?, delayMs?, decay? }. Reverb presets such as room, hall or church use wetDryMix; echo and custom use delayMs and decay.
outputvideo (the default, the finished result) or stems (dry, unleveled tracks for your own mix).
analysisA previous analyze job's output_data. The recast reuses its speakers and stems instead of detecting them again.

The recast is charged per mapped speaker; analyze and export have flat prices. See Voice Changer Pro for the prices.

The interactive flow: analyze, recast to stems, export

The one-shot recast renders a finished video in one call. The interactive flow lets you check the speakers before you pay for the recast, and mix the result before you render it.

Detect the speakers

POST /v1/voice-changer-pro/analyze with audioUrl or videoUrl, and optionally separationQuality and suggestTitle. The finished job's output_data has the separated vocalsUrl and backgroundUrl, and the detected languageCode and languageProbability. Its speakers list gives each speaker's id, segments, firstStartSec, wordCount and a snippet. It also has a suggestedTitle when you asked for one. Look for "speakers" that are not people, such as applause.

Recast to stems

POST /v1/voice-changer-pro with output: "stems" and the whole analysis output_data as analysis. The recast skips detection, so you can recast again with other voices without paying for detection again.

Mix and export

Set the level of each stem in your own interface; that costs nothing. Then POST /v1/voice-changer-pro/export with the source videoUrl and up to 16 tracks, each { url, gain, muted, kind? }. gain is 0 to 200, kind is voice or background, and at least one track must be unmuted. An optional voiceFx applies to the voice tracks only. The video stream is copied, never encoded again, so the export matches your preview. The finished job's output_data.videoUrl is the result.

AUTH="Authorization: Bearer $NODARO_API_KEY"
VIDEO="https://cdn.nodaro.ai/uploads/panel.mp4"

# 1. Detect the speakers
JOB=$(curl -s -X POST https://app.nodaro.ai/v1/voice-changer-pro/analyze -H "$AUTH" \
  -H 'Content-Type: application/json' -d "{\"videoUrl\":\"$VIDEO\",\"suggestTitle\":true}" | jq -r .jobId)
# ...poll until completed, then keep the analysis
ANALYSIS=$(curl -s "https://app.nodaro.ai/v1/jobs/$JOB/status" -H "$AUTH" | jq .data.output_data)

# 2. Recast to stems: speaker 2 keeps their own voice
JOB=$(curl -s -X POST https://app.nodaro.ai/v1/voice-changer-pro -H "$AUTH" -H 'Content-Type: application/json' \
  -d "{\"videoUrl\":\"$VIDEO\",\"orderedVoices\":[\"Rachel\",null,\"Aria\"],\"output\":\"stems\",\"analysis\":$ANALYSIS}" | jq -r .jobId)

# 3. Export your mix
curl -s -X POST https://app.nodaro.ai/v1/voice-changer-pro/export -H "$AUTH" -H 'Content-Type: application/json' -d "{
  \"videoUrl\": \"$VIDEO\",
  \"tracks\": [
    { \"url\": \"<voice stem 0>\", \"gain\": 100, \"muted\": false },
    { \"url\": \"<voice stem 1>\", \"gain\": 90, \"muted\": false },
    { \"url\": \"<background stem>\", \"gain\": 70, \"muted\": false, \"kind\": \"background\" }
  ],
  \"voiceFx\": { \"preset\": \"hall\", \"wetDryMix\": 25 }
}"
const { jobId: analyzeJob } = await client.voices.analyze({ videoUrl, suggestTitle: true })
const analysis = (await client.jobs.get(analyzeJob)).output_data // once completed

const { jobId: recastJob } = await client.voices.recast({
  videoUrl,
  orderedVoices: ['Rachel', null, 'Aria'],
  output: 'stems',
  analysis,
})
const stems = (await client.jobs.get(recastJob)).output_data // once completed

await client.voices.exportMix({
  videoUrl,
  tracks: [
    { url: stems.tracks[0].url, gain: 100, muted: false },
    { url: stems.tracks[1].url, gain: 90, muted: false },
    { url: stems.backgroundUrl, gain: 70, muted: false, kind: 'background' },
  ],
  voiceFx: { preset: 'hall', wetDryMix: 25 },
})
nodaro voice analyze --video https://cdn.nodaro.ai/uploads/panel.mp4 --watch --json > analyze.json
jq .output_data analyze.json > analysis.json

nodaro voice recast --video https://cdn.nodaro.ai/uploads/panel.mp4 --voices Rachel,keep,Aria \
  --analysis-file analysis.json --output stems --watch

nodaro voice export --source https://cdn.nodaro.ai/uploads/panel.mp4 --tracks-file mix.json \
  --voice-fx hall --voice-fx-mix 25 --watch

In the CLI, the word keep in --voices is a keep-slot.

Dub into another language

POST /v1/dubbing translates and revoices an audio track or a whole video, keeping each speaker's voice. Send exactly one source: audioUrl, videoUrl, or sourceUrl (a public link, such as a YouTube or TikTok page, that the dubbing model fetches itself). A video comes back as a dubbed video: output_data.videoUrl, plus the dubbed track alone in output_data.audioUrl.

FieldWhat it does
targetLanguageRequired. An ISO code, for example es or pt-BR.
sourceLanguageThe spoken language. Detected when you omit it.
numSpeakers0 (the default) detects them; 1 to 20 improves the separation when you know the number.
startTime, endTimeDub only this window of the source, in seconds.
disableVoiceCloning, dropBackgroundAudioDo not reproduce each speaker's own voice, or drop the music and effects.
highestResolutionKeep the source resolution on a video.
useProfanityFilter, targetAccent, watermarkA profanity filter, an accent for the target language, and the dubbing model's watermark on a video.

A dub is priced per minute of the dubbed span, with a 1-minute minimum. The span can be up to 30 minutes; a longer one answers 413, so dub a window with startTime and endTime. See Dubbing.

POST /v1/download-video imports a social video (YouTube, TikTok, Instagram, X or Facebook) or a direct link to a video file into your storage. It returns { downloadId }, not a job id. The finished file lands in your library.

FieldWhat it does
urlRequired. The page or file to import.
maxHeightCap the resolution, for example 720. Omit it for the best available.
sectionStartSec, sectionEndSecImport only that range, in seconds. Send both or neither.
requireAudioA result without sound fails by default, because it usually means a degraded source. Send false to accept a silent clip.

Follow the download with GET /v1/download-video/progress/:downloadId, a stream of server-sent events. About every 500 ms it sends { phase, percent, videoUrl?, thumbnailUrl?, error? }, and it ends at completed, with the stored videoUrl, or at failed, with the error. Start listening as soon as the import starts, because the progress is kept only briefly after the download ends. At most 4 downloads run per account at once; a fifth answers 429 too_many_downloads.

DL=$(curl -s -X POST https://app.nodaro.ai/v1/download-video \
  -H "Authorization: Bearer $NODARO_API_KEY" -H 'Content-Type: application/json' \
  -d '{ "url": "https://www.youtube.com/watch?v=VIDEO_ID", "maxHeight": 720 }' | jq -r .downloadId)

curl -sN https://app.nodaro.ai/v1/download-video/progress/$DL \
  -H "Authorization: Bearer $NODARO_API_KEY"
const { downloadId } = await client.media.downloadVideo({
  url: 'https://www.youtube.com/watch?v=VIDEO_ID',
  maxHeight: 720,
})
for await (const event of client.media.downloadVideoProgress(downloadId)) {
  console.log(`${event.phase} ${event.percent}%`)
  if (event.phase === 'completed') console.log('stored at', event.videoUrl)
}
nodaro media download "https://www.youtube.com/watch?v=VIDEO_ID" --max-height 720 --watch
nodaro media metadata "https://www.youtube.com/watch?v=VIDEO_ID"

Two related routes answer at once:

  • POST /v1/video-metadata with { url } reads a video's duration, size, title and live status without downloading it. Use it to decide whether to import only a section.
  • POST /v1/save-to-storage with { mediaUrl, filename?, mediaType? } copies any external media URL into your storage, on the server. It returns a job.

Edit video

MethodPathWhat it doesPrice
POST/v1/trim-videoCut a video to a range.Trim Video
POST/v1/add-captionsBurn captions into a video.Add Captions
POST/v1/still-to-videoOne image and one audio track become an MP4.0 credits
POST/v1/slideshow2 to 100 images and an optional audio track become an MP4.0 credits
POST/v1/video-overlayPlace 1 to 20 timed image layers over a video.20 credits
POST/v1/media/processCut or crop a stored file, and answer at once.Free

Trim a video

POST /v1/trim-video takes videoUrl and the range in the unit you prefer: startTime and endTime in seconds, trimStartFrames and trimEndFrames, trimStartSeconds and trimEndSeconds, or keepFirstSeconds or keepLastSeconds.

Burn in captions

POST /v1/add-captions takes videoUrl and the words from the first source it finds, in this order: captions (word-timed entries), transcript, text on the subtitle style, then automatic transcription of the speech.

FieldWhat it does
stylesubtitle (a static block), or a kinetic style: word-highlight, karaoke, tiktok-words, word-pop or bouncy.
textOn subtitle: the caption itself, burned as one static block for the whole video. On a kinetic style: only the fallback when transcription finds nothing.
captionsWord-timed entries, { text, startMs, endMs }, one per word for the kinetic styles. A transcription's words fit as they are.
auto_transcribe, transcribe_providerTranscribe the speech when nothing else gives the words. A kinetic style needs word timings: incredibly-fast-whisper (the default) or elevenlabs-stt.
lookoutline (the default on kinetic styles) or clean (the default on subtitle).
maxWordsPerLine1 to 20 words per line, on top of the width of the frame. 1 or 2 gives a punchy read.
highlightColor, animateKinetic styles only; 400 on subtitle.
position, positionY, fontSize, fontFamily, fontWeight, color, backgroundColor, strokeColor, strokeWidth, uppercaseThe look of the text.
segmentsGive ranges of the video their own style, look and position.

A plain-text subtitle with no styling is the cheap render. A kinetic style, any styling field, timed captions, automatic transcription or segments renders at the higher price and keeps the source frame rate. See Add Captions for every option and both prices.

Turn stills into video

  • POST /v1/still-to-video takes { imageUrl, audioUrl, motion?, intensity?, resolution?, aspectRatio?, fps?, fit?, padColor? }. The video lasts as long as the audio; there is no duration field. motion is none (the default), zoom-in, zoom-out, pan-left, pan-right or ken-burns, at an intensity from 1 to 10. See Still to Video.
  • POST /v1/slideshow takes imageUrls (2 to 100), an optional audioUrl, imageDurations (seconds per image, null for automatic) or perImageDuration, a transition and the same look fields. With audio, the slideshow lasts as long as the audio; pinned durations that do not add up are scaled, and the job output says so. Without audio, it lasts the number of images times perImageDuration, in silence. See Slideshow.

Both render on the server with no AI model and cost 0 credits. resolution is 720p, 1080p or 4K, fps is 24 or 30, and fit: "contain" letterboxes with padColor instead of cropping.

Overlay images on a video

POST /v1/video-overlay places 1 to 20 image layers over a video in one render and keeps the base audio untouched. Each layer is { imageUrl, start, end?, preset?, corner?, anchor?, x?, y?, width?, height?, fit?, opacity?, animate?, zIndex? }. start and end are seconds; without end, the layer lasts until the video ends. preset is card, corner-badge or full-frame, and an explicit position or size overrides it. A layer with neither is a corner badge, bottom-right unless corner says otherwise.

outputAspect (16:9, 9:16, 1:1 or 4:5) changes the frame, with baseFit and backgroundColor. The job returns { videoUrl, thumbnailUrl, width, height, durationSec, warnings }. It costs 20 credits per run, whatever the number of layers, and each user may send 30 requests per minute. See Video Overlay.

Cut or crop a stored file at once

POST /v1/media/process cuts or crops a file you stored and answers in the same request, for free. The body is { sourceUrl, type, trim?, crop?, format?, deleteSource? }: type is video or audio, trim is { startTime, endTime }, crop is { x, y, width, height }, and format is mp4, webm, mp3, wav, m4a or aac. It returns { data: { url, thumbnailUrl, assetId, sizeBytes, mimeType, metadata } }. deleteSource: true deletes the source afterwards, when it is yours and nothing else uses it.

Edit audio

Each route returns a job.

MethodPathBodyWhat it does
POST/v1/audio-separation{ audioUrl, mode?, quality? }Split a track: vocal_instrumental (the default) or full stems, at auto, fast or best quality. See Audio Separation.
POST/v1/audio-isolation{ audioUrl }Keep the main voice and remove the background. See Voice Extractor.
POST/v1/audio-fx{ audioUrl, preset?, mix?, delayMs?, decay?, eqLow?, eqHigh? }A reverb, echo, telephone or megaphone effect. See Audio FX.
POST/v1/mix-audio{ audioUrls, trackVolumes? }Layer 2 to 20 tracks, each at 0 to 200 percent. See Mix Audio.
POST/v1/adjust-volume{ audioUrl or videoUrl, volume?, normalize?, fadeIn?, fadeOut? }Change the level, normalize, or fade. See Adjust Volume.
POST/v1/combine-audio{ segments: [{ url, startTime?, endTime? }] }Join segments end to end. See Combine Audio.
POST/v1/trim-audio{ audioUrl or videoUrl, startTime?, endTime?, audioFormat? }Cut audio, or take it out of a video, as mp3 (the default), wav or aac. See Trim Audio.
POST/v1/silence-detect{ audioUrl, thresholdDb?, minSilenceMs?, padMs? }Find the silent ranges of a recording. 10 credits. See Silence Detect.
POST/v1/audio-sync{ sources: [{ id, url }], reference? }Measure the offsets between 2 to 6 recordings of one conversation. See Audio Sync.

silence-detect defaults to thresholdDb: -35, minSilenceMs: 700 and padMs: 120, and accepts audio or video. Its output_data.json is { version, ranges: [{ startMs, endMs }], durationMs }.

audio-sync costs 10 credits per source after the first: 10 for 2 sources, 30 for 4, 50 for 6. The ids must be unique, and reference must be one of them (the first source by default). Its output_data.json is { version, reference, offsets: [{ sourceId, offsetMs, confidence, driftMsPerHour }], notes }, where a time on the reference equals the time on the source plus offsetMs. Drift is measured and noted, never corrected.

Transcribe speech

POST /v1/transcribe turns speech into text and returns a job. The body is { audioUrl, provider?, language?, diarize?, tagAudioEvents?, wordTimestamps? }, and audioUrl can also be a video.

providerWord timingsNotes
elevenlabs-sttAlwaysThe only engine that honors diarize (who spoke each word) and tagAudioEvents (laughter, applause).
incredibly-fast-whisperWith wordTimestamps: trueWithout the flag, the job succeeds, is charged, and returns phrases only.
whisperNeverPhrases only. wordTimestamps: true answers 400 validation_error before any credit is spent.

Omitting provider runs whisper, so name an engine whenever you need word timings. The finished job's output_data holds text, language, words (one { text, startMs, endMs, speaker? } per word, in milliseconds), json (the normalized transcript, also in milliseconds) and, on the older engines only, segments in seconds. words drops straight into captions on POST /v1/add-captions.

curl -X POST https://app.nodaro.ai/v1/transcribe \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "audioUrl": "https://cdn.nodaro.ai/uploads/talk.mp3", "provider": "elevenlabs-stt", "diarize": true }'
const { jobId } = await client.audio.transcribe({
  audioUrl: 'https://cdn.nodaro.ai/uploads/talk.mp3',
  provider: 'elevenlabs-stt',
})
// once completed, burn the words in as kinetic captions
const words = (await client.jobs.get(jobId)).output_data.words
await client.media.addCaptions({
  videoUrl: 'https://cdn.nodaro.ai/uploads/talk.mp4',
  captions: words,
  style: 'word-highlight',
  autoTranscribe: false,
})
nodaro audio transcribe --audio https://cdn.nodaro.ai/uploads/talk.mp3 --provider elevenlabs-stt --watch
nodaro jobs get <jobId> --json | jq '.output_data.words' > words.json
nodaro media add-captions https://cdn.nodaro.ai/uploads/talk.mp4 \
  --captions-file words.json --style word-highlight --no-auto-transcribe --watch

Publish to social networks

The connect flows open pop-up windows and are meant for the web app. Publishing works with a personal API token. OAuth app tokens cannot manage connections.

MethodPathWhat it does
GET/v1/social/providersThe supported networks and whether each is available on this deployment.
GET/v1/social/connectionsYour connected accounts.
DELETE/v1/social/connections/:idDisconnect an account.
POST/v1/social/telegram/connectConnect Telegram with a bot token: { botToken }.
POST/v1/social/connect/customConnect a network that uses fields instead of a sign-in: { platform, fields }.
POST/v1/social/publishPublish now. Returns a job. 10 credits.
POST/v1/social/scheduled-postsSchedule a post. 1 credit, charged when it publishes.
GET/v1/social/scheduled-postsYour scheduled posts, filtered by from, to and status.
PATCH/v1/social/scheduled-posts/:idEdit a post while it is queued or draft.
DELETE/v1/social/scheduled-posts/:idCancel a queued post. Its history is kept.

GET /v1/social/providers lists every network with { id, label, connectKind, editor, category, capabilities, available }. category is social (a feed you post to) or publishing (a site you publish articles on). A network the deployment has not set up is listed with available: false, never hidden. Networks that connect with fields (Bluesky, Dev.to, Hashnode, Medium, WordPress and Lemmy) describe their fields in customFields, and Nodaro checks the credential with the network before saving it.

POST /v1/social/publish takes { platform, action, connectionId?, caption?, mediaUrl or mediaItems, … }. Two failures need different handling:

  • 503 publish_retryable: nothing was posted. Sending the identical request again is safe.
  • 500 publish_failed: the outcome is unknown. Sending again may post twice, so check the network first.

A scheduled post takes { connectionId, action, scheduledAt, caption?, media? }, where each media item is { type, r2Key or url }. The media must be files stored on this deployment, which Nodaro turns into fresh links when the post goes out; links to other sites are refused. Editing a post that is already publishing answers 409 not_editable. See Publish to Social and Publishing to social.

SDK and CLI equivalents

AreaTypeScript SDKCLI
Voices and voice changingclient.voices.list, searchLibrary, listClones, deleteClone, change, recast, analyze, exportMix, design, remix, dubnodaro voice list, changer, recast, analyze, export, design, remix, dub, clones
Mediaclient.media.downloadVideo, downloadVideoProgress, videoMetadata, trimVideo, trimAudio, addCaptions, stillToVideo, slideshow, videoOverlay, saveToStorage, processnodaro media download, metadata, trim-video, trim-audio, add-captions, still-to-video, slideshow, video-overlay, save
Audioclient.audio.separate, isolate, applyFx, mix, adjustVolume, combine, transcribenodaro audio separate, isolate, fx, mix, adjust-volume, combine, transcribe
Editing helpersclient.edit.silenceDetect, audioSyncnodaro edit silence-detect, audio-sync

Over MCP, the same routes are tools such as voice_changer, voice_changer_pro, voice_changer_pro_analyze, voice_changer_pro_export, dubbing, transcribe, add_captions and separate_audio. See the MCP tools reference.

Errors

StatusCodeMeaning
400validation_errorA field is missing or invalid, for example wordTimestamps on whisper, a style field that needs a kinetic caption style, or an all-muted export.
400provider_not_configuredThe deployment has not set up that social network.
401unauthorizedThe token is missing, invalid or revoked.
402insufficient_creditsNodaro Cloud only. The account cannot cover the job.
404not_foundA Voice Changer Pro route was called on a deployment that does not serve it.
409not_editableThe scheduled post is already publishing.
410voice_cloning_retiredVoice cloning is no longer offered.
413—The dubbed span is longer than 30 minutes.
429too_many_downloads4 video imports are already running for your account.
500publish_failedThe post's outcome is unknown. Check the network before you send it again.
503publish_retryableNothing was posted. The identical request is safe to send again.

Frequently asked questions

Last updated on

On this page