# Sonilo API full agent reference Use this file when you need to discover, call, or debug the Sonilo API from a coding agent. ## Hosts API base URL: ```text https://api.sonilo.com/v1 ``` Docs host: ```text https://platform.sonilo.com/docs ``` API reference: ```text https://platform.sonilo.com/docs/api ``` OpenAPI: ```text https://platform.sonilo.com/openapi.json ``` Errors guide: ```text https://platform.sonilo.com/docs/errors ``` Raw runnable examples index: ```text https://platform.sonilo.com/examples ``` Never send API requests to `platform.sonilo.com/docs`. That host serves documentation only. If you need to call an API, combine the API base URL with the endpoint path. ## Authentication Every API request requires: ```http Authorization: Bearer sk_your_api_key ``` Use a real server-side key. Do not expose the key in browser client code. A common environment variable name is `SONILO_API_KEY`. Troubleshooting: - 401 Unauthorized: key is missing, invalid, revoked, or not sent as a Bearer token. - 403 Forbidden: key is valid, but this account or workspace does not have access to the requested endpoint. - 402 Payment Required: the JSON `code` is `payment_required`. If the message says `Insufficient balance`, classify the run as `insufficient_balance` and add balance before retrying. - 429 Rate Limit Exceeded: wait and retry with backoff. If a `Retry-After` header is present, wait at least that many seconds. ## Billing Generations are billed by output duration at each product's per-second rate, drawn from the account balance. Every generating product has a minimum billable duration (a billing floor): a run shorter than its floor is billed as if it were exactly the floor length, independent of the requested `duration`. Runs longer than the floor are billed for their actual duration. - 10-second floor: `/v1/video-to-music`, `/v1/video-to-video-music`, `/v1/text-to-music`, `/v1/audio-ducking`, `/v1/video-to-sound`, `/v1/video-to-video-sound`, `/v1/dubbing`. - 3-second floor: `/v1/video-to-sfx`, `/v1/video-to-video-sfx`, `/v1/text-to-sfx`. For example, a 4-second text-to-music clip is billed as 10 seconds, and a 2-second text-to-sfx clip is billed as 3 seconds. ## Endpoint map ### Text to Music ```http POST https://api.sonilo.com/v1/text-to-music Content-Type: multipart/form-data Authorization: Bearer sk_your_api_key ``` Required fields: - `prompt`: string, max 1000 characters. - `duration`: integer seconds, usually 5 to 360. Optional fields: - `mode`: `stream` (default) or `async`. - `output_format`: `m4a` (default) or `wav`. Async only. In `stream` mode, returns `application/x-ndjson`: read one JSON object per line, with `title`, `audio_chunk`, `complete`, and `error` events. In `async` mode, returns `202 application/json` with a `task_id`; poll `GET /v1/tasks/{task_id}` until `status` is `succeeded` or `failed`. Official minimal Python smoke test for text-to-music -> `output.m4a`: ```python import base64 import json import os import sys import time from pathlib import Path import requests API_URL = "https://api.sonilo.com/v1/text-to-music" OUTPUT_FILE = Path("output.m4a") PROMPT = "30 seconds of upbeat electronic instrumental background music for a product launch video, no vocals." def retry_after_seconds(resp, attempt): value = resp.headers.get("Retry-After") if value: try: return max(1, int(value)) except ValueError: return 5 return min(2 ** attempt, 30) def open_generation(api_key): headers = { "Authorization": f"Bearer {api_key}", "User-Agent": "SoniloDocsSmokeTest/1.0 (+https://platform.sonilo.com/docs)", } files = { "prompt": (None, PROMPT), "duration": (None, "30"), } for attempt in range(1, 6): resp = requests.post(API_URL, headers=headers, files=files, stream=True, timeout=300) if resp.status_code != 429: return resp delay = retry_after_seconds(resp, attempt) resp.close() print(f"Rate limited. Retrying after {delay}s...", file=sys.stderr) time.sleep(delay) raise RuntimeError("rate_limit_exceeded: still receiving 429 after retries") def fail_for_http(resp): if resp.ok: return body = resp.text code = f"http_{resp.status_code}" message = body try: payload = resp.json() code = payload.get("code", code) message = payload.get("message", body) except ValueError: pass if resp.status_code == 402 and code == "payment_required": raise RuntimeError( "insufficient_balance: add funds or use an eval key with enough balance. " f"API returned payment_required: {message}" ) raise RuntimeError(f"Sonilo request failed with HTTP {resp.status_code}: {message}") def main(): api_key = os.environ.get("SONILO_API_KEY") if not api_key: raise RuntimeError("Set SONILO_API_KEY before running this script") chunks = [] complete = False with open_generation(api_key) as resp: fail_for_http(resp) for line in resp.iter_lines(decode_unicode=True): if not line: continue event = json.loads(line) if event.get("type") == "audio_chunk": chunks.append(base64.b64decode(event["data"])) elif event.get("type") == "complete": complete = True break elif event.get("type") == "error": code = event.get("code", "unknown_error") message = event.get("message", "Sonilo generation failed") raise RuntimeError(f"{code}: {message}") if not complete: raise RuntimeError("stream_incomplete: no complete event received") if not chunks: raise RuntimeError("empty_audio: complete event arrived without audio_chunk data") OUTPUT_FILE.write_bytes(b"".join(chunks)) print(f"Saved {OUTPUT_FILE} ({OUTPUT_FILE.stat().st_size} bytes)") if __name__ == "__main__": try: main() except Exception as exc: print(f"Error: {exc}", file=sys.stderr) sys.exit(1) ``` Do not generate placeholder media for failed runs. A passing text-to-music eval must produce `output.m4a` and the file size must be greater than 0 bytes after the `complete` event. ### Video to Music ```http POST https://api.sonilo.com/v1/video-to-music Content-Type: multipart/form-data Authorization: Bearer sk_your_api_key ``` Required input: exactly one of: - `video`: uploaded file. - `video_url`: publicly reachable video URL. Optional fields: - `prompt`: style or creative direction. - `segments`: JSON string with segment prompts. - `mode`: `stream` (default) or `async`. - `output_format`: `m4a` (default) or `wav`. Async only. - `preserve_speech`: async only. Keeps the source speech while composing a new soundtrack, and adds `vocals` (isolated stem) and `mux` (speech mixed with music) result tracks. - `ducking`: async only, on by default and free. Ducks the generated music under the speech so dialogue stays intelligible, and adds a `ducked` result track alongside the clean `audio`. With `preserve_speech` it ducks under the isolated vocals stem; otherwise under the video's original audio. Pass `ducking=false` to opt out. In `stream` mode, returns `application/x-ndjson`. In `async` mode (required for `output_format=wav`, `preserve_speech`, and `ducking`), returns `202 application/json` with a `task_id`; poll `GET /v1/tasks/{task_id}`. Coding-agent integration note: - If you are asked to write and test a Python script for this endpoint, make the script executable as `python script.py` with no command-line arguments. A good default is to read `SONILO_VIDEO_FILE` when set, otherwise create a tiny mp4 with ffmpeg for smoke testing. - Avoid `argparse.add_mutually_exclusive_group(required=True)` for the default smoke-test path unless your final verification command passes `--video` or `--video-url`. - Decode each `audio_chunk.data` value individually from base64, append raw bytes per `stream_index`, and write an `.m4a` file after the `complete` event. ### Video to Video (Music) ```http POST https://api.sonilo.com/v1/video-to-video-music Content-Type: multipart/form-data Authorization: Bearer sk_your_api_key ``` Required input: exactly one of: - `video`: uploaded file. - `video_url`: publicly reachable video URL. Optional fields: - `prompt`: style or creative direction. - `preserve_speech`: keeps the source speech and adds vocals + mux result tracks. Returns `202 application/json` with `task_id`. Poll `GET /v1/tasks/{task_id}`; the finished task carries the scored video, not audio. ### Text to Sound Effect ```http POST https://api.sonilo.com/v1/text-to-sfx Content-Type: multipart/form-data Authorization: Bearer sk_your_api_key ``` Required fields: - `prompt`: sound description. - `duration`: integer seconds, usually 1 to 180. Returns `202 application/json`: ```json { "task_id": "9f5f2f7e-...", "status": "processing" } ``` Poll `GET https://api.sonilo.com/v1/tasks/{task_id}` until `status` is `succeeded` or `failed`. ### Video to Sound Effect ```http POST https://api.sonilo.com/v1/video-to-sfx Content-Type: multipart/form-data Authorization: Bearer sk_your_api_key ``` Required input: exactly one of: - `video`: uploaded file. - `video_url`: publicly reachable video URL. Optional fields: - `prompt`: overall sound-effects direction. - `segments`: JSON string with `start`, `end`, and `prompt` values. - `audio_format`: output audio format. Returns `202 application/json` with `task_id`. Poll `GET /v1/tasks/{task_id}`. ### Video to Video (Sound Effect) ```http POST https://api.sonilo.com/v1/video-to-video-sfx Content-Type: multipart/form-data Authorization: Bearer sk_your_api_key ``` Required input: exactly one of: - `video`: uploaded file. - `video_url`: publicly reachable video URL. Optional fields: - `prompt`: overall sound-effects direction. - `segments`: JSON string with `start`, `end`, and `prompt` values. Returns `202 application/json` with `task_id`. Poll `GET /v1/tasks/{task_id}`; the finished task carries the source video with the sound effects muxed in. ### Video to Sound ```http POST https://api.sonilo.com/v1/video-to-sound Content-Type: multipart/form-data Authorization: Bearer sk_your_api_key ``` Required input: exactly one of: - `video`: uploaded file. - `video_url`: publicly reachable video URL. Optional fields: - `music_prompt`: style or creative direction for the music bed. - `sfx_prompt`: overall sound-effects direction. - `segments`: JSON string with segment prompts. - `preserve_speech`: keeps the source speech and adds vocals + mux result tracks. - `ducking`: on by default. Ducks the mix under the speech so dialogue stays intelligible. Scores one clip with a music bed AND sound effects in a single call. Prefer this over chaining `video-to-music` with `video-to-sfx`, which is billed twice. Returns `202 application/json` with `task_id`. Poll `GET /v1/tasks/{task_id}`; the finished task carries mixed audio under `output_url`, plus `music`, `music_processed`, and `sfx` stems. ### Video to Video (Sound) ```http POST https://api.sonilo.com/v1/video-to-video-sound Content-Type: multipart/form-data Authorization: Bearer sk_your_api_key ``` Same parameters as [Video to Sound](#video-to-sound). Returns `202 application/json` with `task_id`. Poll `GET /v1/tasks/{task_id}`; the finished task carries the source video with that mixed track muxed in. ### Audio Ducking ```http POST https://api.sonilo.com/v1/audio-ducking Content-Type: multipart/form-data Authorization: Bearer sk_your_api_key ``` Required voice input: exactly one of: - `voice_file`: uploaded voice or talking-video file. - `voice_url`: publicly reachable voice or talking-video URL. Required music input: exactly one of: - `music_file`: uploaded music file. - `music_url`: publicly reachable music URL. Returns `202 application/json` with `task_id`. Poll `GET /v1/tasks/{task_id}`. ### Dubbing ```http POST https://api.sonilo.com/v1/dubbing Content-Type: multipart/form-data Authorization: Bearer sk_your_api_key ``` Required input: exactly one of: - `video`: uploaded file. - `video_url`: publicly reachable video URL. Must be `https`. Optional fields: - `languages`: comma-separated list. Default `zh_cn,es,fr`. Supported: `en`, `zh_cn`, `ja`, `ko`, `pt`, `es`, `de`, `fr`, `it`, `ru`. Max video duration is 180 seconds. Billed per language. Unlike every other endpoint, dubbing has no free-trial runs. Returns `202 application/json` with `task_id`. Poll `GET /v1/tasks/{task_id}`; the finished task carries `outputs`, a map of language code to dubbed `.mp4` URL. There is no single `output_url`. ### Retrieve Task ```http GET https://api.sonilo.com/v1/tasks/{task_id} Authorization: Bearer sk_your_api_key ``` Used for async text-to-music, async video-to-music, video-to-video-music, text-to-sfx, video-to-sfx, video-to-video-sfx, video-to-sound, video-to-video-sound, audio-ducking, and dubbing tasks. Processing response: ```json { "task_id": "9f5f2f7e-...", "type": "text_to_sfx", "status": "processing" } ``` Succeeded async music response includes `audio` (one entry per stream); with `preserve_speech` it also includes `vocals` and `mux`, and with `ducking` it also includes `ducked`. Succeeded `video-to-video-music` response carries the scored video, not audio. Succeeded sound-effect response includes `audio.url`; video-to-sfx and video-to-video-sfx also include `video.url`. Succeeded `video-to-sound` response includes `output_url` plus `music`, `music_processed`, and `sfx` stems; succeeded `video-to-video-sound` carries the source video with that mix muxed in. Succeeded audio-ducking response includes `output_url` and `output_type`. Succeeded `dubbing` response includes `outputs`, a map of language code to dubbed `.mp4` URL — there is no single `output_url`. ### Account Services ```http GET https://api.sonilo.com/v1/account/services Authorization: Bearer sk_your_api_key ``` Returns enabled services and live account limits. ### Account Usage ```http GET https://api.sonilo.com/v1/account/usage Authorization: Bearer sk_your_api_key ``` Optional query: - `days`: integer lookback window. Default is 30. ## Curl examples Text to music: ```bash curl -N -X POST https://api.sonilo.com/v1/text-to-music \ -H "Authorization: Bearer sk_your_api_key" \ -F "prompt=lofi hiphop beat" \ -F "duration=60" ``` Video to music: ```bash curl -N -X POST https://api.sonilo.com/v1/video-to-music \ -H "Authorization: Bearer sk_your_api_key" \ -F "video=@/path/to/video.mp4" ``` Text to sound effect: ```bash curl -X POST https://api.sonilo.com/v1/text-to-sfx \ -H "Authorization: Bearer sk_your_api_key" \ -F "prompt=heavy rain on a tin roof with distant thunder" \ -F "duration=10" ``` Poll task: ```bash curl https://api.sonilo.com/v1/tasks/9f5f2f7e-... \ -H "Authorization: Bearer sk_your_api_key" ``` ## Canonical docs URLs - https://platform.sonilo.com/docs - https://platform.sonilo.com/docs/api - https://platform.sonilo.com/docs/errors - https://platform.sonilo.com/docs/evals - https://platform.sonilo.com/openapi.json - https://platform.sonilo.com/docs/api/text-to-music - https://platform.sonilo.com/docs/api/video-to-music - https://platform.sonilo.com/docs/api/video-to-video-music - https://platform.sonilo.com/docs/api/text-to-sfx - https://platform.sonilo.com/docs/api/video-to-sfx - https://platform.sonilo.com/docs/api/video-to-video-sfx - https://platform.sonilo.com/docs/api/video-to-sound - https://platform.sonilo.com/docs/api/video-to-video-sound - https://platform.sonilo.com/docs/api/audio-ducking - https://platform.sonilo.com/docs/api/dubbing - https://platform.sonilo.com/docs/api/get-task - https://platform.sonilo.com/docs/api/list-services - https://platform.sonilo.com/docs/api/get-usage Old docs URL aliases redirect to the canonical pages, including `/api-reference`, `/reference`, `/api-reference/video-to-music`, `/api-reference/tasks`, `/api-reference/retrieve-task`, `/api-reference/async-tasks`, `/reference/get-task`, `/reference/retrieve-task`, `/reference/async-tasks`, `/docs/video-to-music`, `/docs/video-to-sfx`, `/docs/endpoints/video-to-music`, `/docs/endpoints/audio-ducking`, `/docs/endpoints/video-to-sfx`, `/docs/tasks`, `/docs/retrieve-task`, `/docs/async-tasks`, `/docs/endpoints/tasks`, `/docs/endpoints/retrieve-task`, `/docs/endpoints/async-tasks`, `/docs/api-reference/tasks`, `/docs/reference/video-to-music`, `/docs/reference/audio-ducking`, `/docs/account/services`, `/docs/account/usage`, `/docs/api/account/services`, `/docs/api/account/usage`, `/tasks/{task_id}`, `/docs/api/v1/*`, `/api-reference/v1/*`, `/reference/v1/*`, `/docs/streaming`, `/docs/api/streaming`, `/api-reference/streaming`, `/reference/streaming`, `/docs/authentication`, `/docs/errors`, `/docs/api/errors`, and `/docs/rate-limits`. The `/docs/api/tasks`, `/docs/api/tasks/{task_id}`, `/docs/api/task/{task_id}`, `/docs/api/get-task/{task_id}`, `/docs/api/v1/tasks/{task_id}`, `/docs/api-reference`, `/docs/reference`, `/docs/api-reference/get-task`, `/docs/api-reference/get-task/{task_id}`, `/docs/api-reference/tasks`, `/docs/api-reference/tasks/get-task`, `/docs/api-reference/tasks/{task_id}`, `/docs/api-reference/task/{task_id}`, `/docs/api-reference/v1/tasks/{task_id}`, `/docs/reference/get-task`, `/docs/reference/get-task/{task_id}`, `/docs/reference/tasks`, `/docs/reference/tasks/get-task`, `/docs/reference/tasks/{task_id}`, `/docs/reference/task/{task_id}`, and `/docs/reference/v1/tasks/{task_id}` aliases serve the matching API docs directly so agent readers get a 200 page. ## Eval and agent tooling Custom eval graders should require generated output files to exist and have a file size greater than 0 bytes. For the official text-to-music smoke test, verify `output.m4a` after `complete`. If the API returns HTTP 402 with `payment_required` and an `Insufficient balance` message, classify the failure as `insufficient_balance`; do not count it as a successful generation and do not create placeholder files. Agent discovery checklist: - Context7: both SDKs are indexed as Context7 libraries at `https://context7.com/sonilo-ai/sonilo-js` and `https://context7.com/sonilo-ai/sonilo-python`; with the Context7 MCP server enabled, append `use context7` to a prompt. For direct retrieval use `https://platform.sonilo.com/llms-full.txt` and `https://platform.sonilo.com/openapi.json`. - MCP: use the Sonilo MCP server at `https://github.com/sonilo-ai/sonilo-mcp`. - Agent Skills: Sonilo agent skills and plugin instructions should point to `llms-full.txt`, `openapi.json`, and the no-argument Python smoke test. - CLI: install `sonilo-cli` from npm (`npm install -g sonilo-cli`) or PyPI (`pip install sonilo-cli`); both provide the `sonilo` command, which reads `SONILO_API_KEY` from the environment. Commands: account, usage, text-to-music, video-to-music, text-to-sfx, video-to-sfx, video-to-sound, video-to-video-sound, dubbing, tasks get, tasks wait. Docs: `https://platform.sonilo.com/docs/cli`. The no-argument Python smoke test remains the minimal script-only workflow. Raw runnable examples for low-token agent runs: - Index: `https://platform.sonilo.com/examples` - Text to music output file: `https://platform.sonilo.com/examples/text-to-music-output.py` - Video to SFX minimal workflow: `https://platform.sonilo.com/examples/video-to-sfx-minimal.py` - Account usage dashboard: `https://platform.sonilo.com/examples/account-usage-dashboard.py` - Sapient output-file grader helper: `https://platform.sonilo.com/examples/sapient-grader-output-file.py` Common guessed paths: - `/examples/python`, `/docs/examples`, `/docs/smoke-tests`, and `/docs/agent-smoke-tests` redirect to `/examples`. - `/docs/eval`, `/docs/custom-evals`, and `/docs/grader` redirect to `/docs/evals`. - `/docs/vocal-isolation`, `/docs/reference/vocal-isolation`, and `/docs/endpoints/vocal-isolation` redirect to `/docs/api/vocal-isolation`. - `/v1/vocal-isolation` is currently unavailable and is not in the active OpenAPI schema. Do not call it in new integrations; use `/v1/audio-ducking`, `/v1/video-to-sfx`, `/v1/video-to-music`, or `/v1/text-to-music` depending on the workflow. Use `https://platform.sonilo.com/docs/evals` for copyable custom eval prompt and grader guidance, including the Codex/Sapient file-capture requirement.