# Mirelo > Generated concatenation of SFX, MCP, Studio Docs, Developer Docs, and API Docs markdown. Canonical HTML pages remain on the marketing site. Machine-readable REST specifications: [OpenAPI v3](https://api.mirelo.ai/v3/openapi.json) and [OpenAPI v2](https://api.mirelo.ai/openapi.yaml) (for existing v2 integrations). --- # Mirelo SFX > Generate sound effects, foley, and ambience synced to your footage. Preserve dialogue, remove unwanted audio, and keep sound consistent across clips. HTML (canonical for search): https://mirelo.ai/sfx ## Capabilities ### Generate sound that fits your video. No more hunting for, trimming, and syncing individual effects. Upload your video and get footsteps, impacts, foley, and ambience that land exactly where they should. ### Remove baked-in music, keep your dialogue. Video models bake in music you never asked for. Instead of regenerating the video until it doesn’t, keep the clip you like and get clean sound around your dialogue. ### Keep sound consistent across clips. Every generated clip comes with its own audio, so stitched edits jump between ambiences and music. Run your full edit through Mirelo and it sounds like one continuous scene. ### Your full edit, one soundtrack. Long-form content shouldn’t mean sound designing clip by clip. Finish your edit, upload it once, and get one consistent soundtrack. Longer than 10 minutes? Talk to us about Enterprise. ## Who it's for ### Creators and editors Mute AI video, short-form, and production footage with synced sound in seconds, then refine only what needs fixing. ### Platforms and pipelines Chain video-to-sound into generation workflows so every clip ships with production-ready effects by default. ### Developers Call the Mirelo API for credits-based SFX generation and integrate into your product or pipeline. ## Get started - [Mirelo Studio](https://mirelo.ai/studio): Generate and edit sound effects in the browser — free to start. - [Editor plugins](https://mirelo.ai/plugins): Generate SFX on the timeline in Premiere Pro and DaVinci Resolve. - [Developer API](https://mirelo.ai/api-docs): Integrate video-to-sound into your platform with the Mirelo API. ## Frequently asked questions ### How does SFX 2.0 keep the dialogue? SFX 2.0 separates the speech from your clip and keeps it in the output while generating new effects and ambience around it. Speech preservation is on by default for video-to-SFX requests; turn it off when you want sound effects only. ### What length of video can SFX 2.0 process? SFX 2.0 supports videos up to 10 minutes in Studio and the API. For longer edits, contact us to discuss your requirements. [Contact us about Enterprise.](https://mirelo.ai/contact-sales) ### Does Mirelo SFX 2.0 sync sound to the action? Yes. Mirelo SFX 2.0 reads the visible action, timing, and environment, so effects land where the action lands instead of needing manual trimming and alignment. ### Where is SFX 2.0 available? SFX 2.0 is live in the [Mirelo Playground](https://mirelo.ai/studio/playground#video-to-sfx), [Mirelo Editor](https://mirelo.ai/studio), the [Mirelo API](https://mirelo.ai/api-docs), [Mirelo MCP](https://mirelo.ai/mcp) and as Mirelo plugins for [Adobe Premiere Pro](https://mirelo.ai/plugins/adobe), [DaVinci Resolve](https://mirelo.ai/plugins/davinci) and [Roblox](https://mirelo.ai/plugins/roblox) (coming soon). It is also available on our partner platforms including [Runway](https://runway.com/) and [Fal](https://fal.ai/models/mirelo-ai/sfx2.0/video-to-video), other platforms coming soon. ## Listening comparisons In the comparisons shown, Mirelo received the largest share of listener preferences for audio quality and synchronization. These results describe the evaluated clips, not every possible video. ### Mirelo SFX 2.0 vs. other video-to-SFX models | Model | Mirelo preferred | No preference | Competitor preferred | | --- | --- | --- | --- | | Sony Woosh | 81% | 10% | 9% | | Kling Foley | 63% | 19% | 18% | | Sonilo | 46% | 21% | 33% | ### Mirelo SFX 2.0 vs. native video model audio | Model | Mirelo preferred | No preference | Native audio preferred | | --- | --- | --- | --- | | Seedance 2.5 | 47% | 25% | 28% | | MiniMax H3 Max | 49% | 21% | 30% | | Flux 3 | 48% | 29% | 23% | ## Earlier models and iterative editing - [Mirelo SFX 1.6](https://mirelo.ai/models/1-6) - [Beyond generation: iterative editing](https://mirelo.ai/blog/beyond-generation-iterative-editing) ## Guides - [Mirelo SFX 2.0: change the sound, keep the video](https://mirelo.ai/blog/mirelo-sfx-2-0): what's new in 2.0: speech preservation on by default, videos up to 10 minutes. - [Preserve speech](https://mirelo.ai/blog/ai-sound-effects-keep-dialogue): add sound effects in the API while keeping a clip's existing dialogue. --- # Mirelo MCP > Connect Mirelo to Cursor, Claude, ChatGPT, and other MCP agents. Generate and edit SFX from chat — billed to Studio credits. HTML (canonical for search): https://mirelo.ai/mcp Connect URL (streamable HTTP): https://mcp.mirelo.ai/mcp Launch post: [Generate Sound Effects in Claude, ChatGPT and Cursor](https://mirelo.ai/blog/introducing-mirelo-mcp) ## Features ### Generate sound effects Create sound effects from text or video — no audio library required. ### Edit and extend audio Extend existing clips, fill in gaps with inpainting, or guide results with prompts. ### Track cost and progress See credit cost and time estimates upfront, then track jobs until they’re ready. ## Connect ### Claude #### Individual / Pro Steps for Claude Individual and Pro accounts. On Team or Enterprise, a workspace admin usually adds the connector first 1. **Open Connectors.** In Claude, open Customize → Connectors. 2. **Add a custom connector.** Click + → Add custom connector. Set the remote MCP server URL and leave OAuth Client ID / Secret empty. `https://mcp.mirelo.ai/mcp` 3. **Sign in with Mirelo.** Add the connector, then Connect and complete Sign in with Mirelo in the browser. 4. **Enable in chat.** In a chat, enable the connector via + → Connectors before asking Mirelo to generate audio. Claude.ai requires a public HTTPS URL — localhost will not work. 5. **Allow local file uploads.** Settings → Capabilities → Code execution → turn on Allow network egress → add the domain below under Additional allowed domains. Start a new chat afterward — or skip this and upload at https://mirelo.ai/studio/mcp-upload. `studio-project-assets.s3.eu-central-1.amazonaws.com` #### Team / Enterprise On Team or Enterprise, an admin adds the connector first. 1. **Admin adds the connector.** On Claude Team / Enterprise, an Owner (or admin with connector permissions) opens Organization settings → Connectors. 2. **Add a custom web connector.** Choose Add → Custom → Web. Set the remote MCP server URL, leave OAuth Client ID / Secret empty (Mirelo uses dynamic registration), and save. `https://mcp.mirelo.ai/mcp` 3. **Each member connects.** Members open Customize → Connectors, find Mirelo, then Connect → Sign in with Mirelo. 4. **Enable in chat.** In a chat, enable the connector via + → Connectors before asking Mirelo to generate audio. 5. **Allow local file uploads.** Owners: Organization settings → Capabilities → Code execution → turn on Allow network egress → add the domain below. Members: Settings → Capabilities when the org allows personal overrides. Start a new chat afterward — or skip this and upload at https://mirelo.ai/studio/mcp-upload. `studio-project-assets.s3.eu-central-1.amazonaws.com` ### Cursor 1. **Open MCP settings.** Select Customize from the left sidebar, then go to MCP. 2. **Add Server.** Click + New MCP Server to open your mcp.json file. 3. **Add Mirelo.** Paste this JSON into the file and save: ```json { "mcpServers": { "mirelo": { "url": "https://mcp.mirelo.ai/mcp" } } } ``` 4. **Authenticate.** Back in the MCPs tab, click Authenticate next to the mirelo server. 5. **Finish setup.** In the browser window that opens, click Allow, then Return to Cursor. ### Claude Code 1. **Add Mirelo.** In a terminal, add the remote HTTP server (Claude Code requires type http): `claude mcp add --transport http mirelo https://mcp.mirelo.ai/mcp` 2. **Or edit .mcp.json.** Prefer project config? Add this to a .mcp.json at your repo root and save: ```json { "mcpServers": { "mirelo": { "type": "http", "url": "https://mcp.mirelo.ai/mcp" } } } ``` 3. **Authenticate.** Start a Claude Code session, run /mcp, select mirelo, then complete Sign in with Mirelo in the browser. 4. **Use it.** Ask for sound generation once the server shows as connected — for example, “Generate a short SFX of glass breaking.” ### ChatGPT #### ChatGPT Mirelo is in ChatGPT's plugin directory, so there is no MCP config to write. If your workspace can't add directory plugins, add the server URL as a developer-mode app instead. Using Codex instead? 1. **Add Mirelo.** Open Mirelo in ChatGPT's plugin directory (Settings → Plugins → Browse directory, or the link below), add it, and complete Sign in with Mirelo. [Open Mirelo in ChatGPT](https://chatgpt.com/plugins/plugin_asdk_app_6a79d86a50308191b1232da432e63c9e) 2. **Use it in a chat.** In a new conversation, type @Mirelo (or pick Mirelo from the composer + menu), then ask something like “Check my Mirelo account credits” or “Generate a short SFX of glass breaking.” #### Codex Connect Mirelo from the Codex CLI or IDE. 1. **Add Mirelo from the terminal.** In a terminal, register the remote server and log in: ``` codex mcp add mirelo --url https://mcp.mirelo.ai/mcp codex mcp login mirelo ``` 2. **Sign in with Mirelo.** Complete Sign in with Mirelo in the browser that opens. 3. **Confirm the connection.** Confirm with codex mcp list or /mcp inside a Codex session. Same config is shared with the Codex IDE extension / ChatGPT desktop Codex host. ### Other Any MCP-compatible client that supports remote HTTP servers can connect to Mirelo. Detailed guides for more agents are coming soon. 1. **Add a remote MCP server.** In your agent’s MCP or connector settings, add a remote / URL server (not a local command). 2. **Set the server URL.** Paste the Mirelo MCP URL, save, and enable the server. `https://mcp.mirelo.ai/mcp` 3. **Sign in with Mirelo.** Complete Sign in with Mirelo when prompted, then confirm tools such as get_account, text_to_sfx, and video_to_sfx are available. ## Example prompts ### Drop in a video and get sound effects that match what’s actually happening on screen. > Here’s my gameplay clip — generate footstep and impact sounds that match the action. ### Extend an existing clip, with or without video guidance, instead of regenerating from scratch. > Extend this ambience loop to 45 seconds so it covers the whole scene. ### Select part of an existing sound and regenerate only that moment — the rest stays untouched. > There’s a glitch from 0:05 to 0:12 — regenerate just that section, keep the rest as is. ### Batch-generate SFX for a folder of clips. > I have several video clips in this folder. Please generate SFX for each. ### Quick SFX from a short text prompt. > Generate a short SFX of glass breaking on a tile floor. ### Create ambience for a scene. > Create a 10-second rainy city street ambience, distant traffic, soft footsteps. ### Sync sound effects to video while keeping dialogue clear. > Using my video, generate synced sound effects for the impact moments and keep dialogue clear. ### Iterate on the last take with a follow-up. > Make the last glass break softer and add a short room reverb tail. ### Check your account balance from the agent. > Check my Mirelo account credits. ## FAQ ### What is Mirelo MCP? Mirelo MCP is a hosted Model Context Protocol server. Once connected in an MCP-compatible agent, you can generate and edit video sound — SFX, Foley, ambience, and more — from the conversation. ### Which clients are supported? Any MCP-compatible agent that supports remote HTTP servers. We document Claude, Cursor, Claude Code, ChatGPT (including Codex), and other streamable HTTP MCP clients. ### Do I need a Mirelo account? Yes. When you connect, you Sign in with Mirelo. Generations bill to your Studio credits on that account. ### Do I need an API key? No. Authentication happens through Sign in with Mirelo in the browser, and usage is charged to your existing Studio credits. ### What is the server URL? https://mcp.mirelo.ai/mcp ### Why can’t Claude upload my video or audio? Claude’s code sandbox blocks outbound uploads by default. Allow studio-project-assets.s3.eu-central-1.amazonaws.com under Settings → Capabilities → Code execution → Additional allowed domains (Team / Enterprise Owners: Organization settings → Capabilities), then start a new chat. Allowlisting mcp.mirelo.ai does not help. Or upload at https://mirelo.ai/studio/mcp-upload and paste the asset_id back. ### Does ChatGPT use MCP config JSON? No. Mirelo is in ChatGPT's plugin directory: add it under Settings → Plugins → Browse directory, sign in with Mirelo, then type @Mirelo in a chat. Workspaces that can't add directory plugins can add the server URL as a developer-mode app instead. ### Does it generate music? Not in v1. Music generation is not yet exposed through MCP. Audio-to-MIDI transcription is available. ### How many generations do I get? MCP generations use the same credit system as Mirelo Studio. [See pricing](https://mirelo.ai/pricing). --- # Studio Docs > Learn how to use Mirelo Studio for video-to-sound workflows: upload video, generate AI sound effects for video, export results, and manage credits. HTML (canonical for search): https://mirelo.ai/docs ## Getting started Mirelo Studio is a web app for AI sound effects for video — video-to-sound generation, text-to-SFX, and iterative editing in one workspace. 1. Open Mirelo Studio and sign in (or create an account). 2. Create a project for the clip you want to sound design. 3. Choose a video-to-sound flow when you have footage, or text-to-SFX when you need off-screen or imaginary sounds. ## Uploading a video Video-conditioned generation works best when Studio can see the picture. Upload the clip you want matched sound for. 1. In your project, upload a video file from your computer. 2. Wait until the upload finishes and the timeline or preview shows your footage. 3. Keep the clip trimmed to the moment you care about so generation focuses on the right action. ## Generating sound effects from video Video-to-sound (also called video-to-SFX) generates AI sound effects timed to what happens on screen — useful for foley-style hits, whooshes, and environmental detail. 1. Select the video-based generation flow for your project. 2. Optionally add a short text hint that describes the sound you want (for example, “soft footsteps on wood”). 3. Run generation and review the candidates against the picture. 4. Pick the take that syncs best, or regenerate with a clearer hint. ## Generating from text prompts Text-to-SFX creates sounds from a written description when you do not have matching footage, or when you need layers that are not visible on camera. 1. Open a text-to-SFX or prompt-based generation tool in Studio. 2. Describe the sound clearly: source, material, space, and intensity. 3. Generate, listen, and refine the prompt until the timbre and timing fit your edit. ## Downloading and exporting results When a generation sounds right, export it for your NLE, game engine, or delivery pipeline. 1. Select the audio take you want to keep. 2. Download the file, or place it on the Studio timeline and export with your project workflow. 3. For editor plugins or API delivery, use the matching product surface (plugins pages or API docs). ## Managing credits and your account Generations consume credits according to your plan. Account and billing settings live in Studio. 1. Check your remaining credits in Studio before large batches. 2. Open account or billing settings to review your plan or purchase more usage. 3. Compare plans and included usage on the Pricing page when you need more capacity. ## Related resources - [Video to SFX](https://mirelo.ai/video-to-audio): How picture-locked SFX compares with text-to-SFX suites and open-source V2A. - [API documentation](https://mirelo.ai/api-docs): Integrate video-to-sound and related endpoints in your own pipeline. - [Support](https://mirelo.ai/support): Contact the team for Studio, billing, or API help. - [Pricing](https://mirelo.ai/pricing): Compare monthly plans, included credits, and enterprise options. --- # Integrating Mirelo AI Audio APIs into Creative Workflows > Learn how to integrate Mirelo's AI audio generation API into your creative workflow to automate sound effects, foley, and background ambiance production for video and games. HTML (canonical for search): https://mirelo.ai/developers/docs/guides ## What this guide covers Mirelo provides a developer API for generating custom sound effects, foley, and background ambiance from text and video, plus native plugins for Adobe Premiere Pro, DaVinci Resolve Studio, and Roblox Studio. This guide is for developers and creators who want to replace manual sound-design work with programmatic audio generation. 1. Set up the Developer API and store an API key. 2. Generate foley and ambiance from a text prompt, or from a video clip. 3. Drop the result into an editing timeline with a native plugin, or keep it in your own pipeline. ## Setting up the Mirelo Developer API The REST API lives at https://api.mirelo.ai. It exposes text-to-SFX and video-to-SFX generation, plus extend and inpaint. The interactive reference is at /api-docs, and the production v3 OpenAPI document is at https://api.mirelo.ai/v3/openapi.json. 1. Create an API key in Studio and send it as a Bearer token. 2. Call preflight on a generation before you spend credits. 3. Submit the create with ?wait=25, then poll if the job is still running. ## Getting your API key API requests authenticate with a Bearer token. Keys are issued in your Mirelo Studio account, begin with sk-, and are billed against Studio credits. 1. Sign in at mirelo.ai/studio. 2. Open Developer → API Keys (/studio/api-keys). 3. Click "Create key", give it a name, and copy it immediately. It is shown once. 4. The playground at /studio/playground runs requests before you write code. It does not create keys. 5. Hosted MCP (Cursor, Claude, and ChatGPT) signs you in through the browser instead of this key, and bills the same Studio credits. ## Configuring environment variables Store the key in an environment variable rather than hardcoding it. Send it on every request. 1. Set MIRELO_API_KEY to the key you copied. 2. Set MIRELO_IDEMPOTENCY_KEY to a unique identifier for each new generation. Reuse that value and the same request body when retrying the generation. 3. Send Authorization: Bearer $MIRELO_API_KEY. 4. Send Content-Type: application/json. ## Generating foley and ambiance from text Preflight the request to read its credit cost, then create the generation with ?wait=25. If the job is still running, poll urls.self and check the job's status before downloading audio. Send an Idempotency-Key on create and reuse it with the same body if you retry. The reference below covers response shapes, job outcomes, and download expiry. 1. POST /v3/text-to-sfx/generations/preflight with the same body you intend to generate. 2. Gate the create on the quoted credits. 3. POST /v3/text-to-sfx/generations?wait=25. 4. If the response is 202, poll urls.self until status is final. A successful HTTP response alone does not mean the job succeeded. ### Preflight ```bash curl https://api.mirelo.ai/v3/text-to-sfx/generations/preflight \ --request POST \ --header "Authorization: Bearer $MIRELO_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "model": "sfx-1.6", "duration_ms": 8000, "num_variants": 1, "input": { "prompt": "Heavy rain on a metal roof with distant thunder" } }' ``` ### Create ```bash curl https://api.mirelo.ai/v3/text-to-sfx/generations?wait=25 \ --request POST \ --header "Authorization: Bearer $MIRELO_API_KEY" \ --header "Content-Type: application/json" \ --header "Idempotency-Key: ${MIRELO_IDEMPOTENCY_KEY:?Set a unique key for this generation}" \ --data '{ "model": "sfx-1.6", "duration_ms": 8000, "num_variants": 1, "input": { "prompt": "Heavy rain on a metal roof with distant thunder" } }' ``` - [Credit preflight](https://mirelo.ai/api-docs#concept/credits-preflight) - [Waiting and polling](https://mirelo.ai/api-docs#concept/sync-vs-async) - [Retrying a generation safely](https://mirelo.ai/api-docs#concept/idempotency) - [Job outcomes and failures](https://mirelo.ai/api-docs#concept/job-outcomes) - [Downloading audio and refreshing expired links](https://mirelo.ai/api-docs#concept/downloads-expiry) ## Picture-locked foley from video For silent or AI-generated footage, video-to-SFX watches the picture and times effects to on-screen action. The clip is input.video on POST /v3/video-to-sfx/generations. Pass a public HTTP or HTTPS URL, or upload a local file with the two requests below. An optional input.prompt covers off-screen detail: the video still drives the timing. Text-to-SFX suites make you generate, then place and sync the audio yourself. 1. For a local file, POST /v3/assets with your API key and the file's content_type. The 201 response is an upload ticket with id, upload_url, fields, upload_expires_at, and max_bytes; it does not mean the file has arrived. 2. POST multipart/form-data to upload_url without an Authorization header. Send every returned fields entry first, then the file bytes as the final part named file. Keep the upload within max_bytes and start before upload_expires_at. 3. Wait for the storage upload's own 2xx response before using the asset id. Generating before the bytes arrive returns asset_not_ready, which can be retried after the upload finishes. 4. Send input.video as { "type": "url", "url": "https://…" } or { "type": "asset", "id": "…" }. 5. Add input.prompt only when you want to steer which sound to generate. 6. Preflight and create the same way as text-to-SFX, on /v3/video-to-sfx/generations. ### Create from a video URL ```bash curl https://api.mirelo.ai/v3/video-to-sfx/generations?wait=25 \ --request POST \ --header "Authorization: Bearer $MIRELO_API_KEY" \ --header "Content-Type: application/json" \ --header "Idempotency-Key: ${MIRELO_IDEMPOTENCY_KEY:?Set a unique key for this generation}" \ --data '{ "model": "sfx-1.6", "duration_ms": 10000, "input": { "video": { "type": "url", "url": "https://example.com/scene.mp4" }, "prompt": "Footsteps on wet concrete" } }' ``` - [File uploads and asset readiness](https://mirelo.ai/api-docs#concept/assets-sources) - [API errors and rate limits](https://mirelo.ai/api-docs#concept/errors-limits) ## Integrating with video editing software Native plugins connect generated audio to an editing timeline or a game engine. They are listed at /plugins. ### Adobe Premiere Pro Generate sound from a selected clip or a text prompt, then use Timeline Sync to place it in Premiere Pro. [Premiere Pro installation and usage](https://mirelo.ai/plugins/adobe) ### DaVinci Resolve Studio Generate audio and place it on the timeline with Timeline Sync. The plugin requires DaVinci Resolve Studio. [DaVinci Resolve Studio installation and usage](https://mirelo.ai/plugins/davinci) ### Roblox Studio Generate game-ready sound effects from a prompt and publish them to your Roblox account for use in an experience. [Roblox Studio installation and usage](https://mirelo.ai/plugins/roblox) ## What you can build with this The same API and plugins cover three common jobs. 1. Automated foley and ambiance: room tone, weather, and impact sounds from text prompts, with a credit preflight so the cost is known first. 2. Timeline-native sound design: Premiere Pro, DaVinci Resolve Studio, or Roblox Studio, with sync handled by the plugin. 3. Programmatic pipelines: the REST API on its own, or an MCP-connected agent in Cursor, Claude, or ChatGPT for on-demand generation. ## Related resources - [API Docs](https://mirelo.ai/api-docs): Interactive reference for every endpoint, including request and response schemas. - [API reference (Markdown)](https://mirelo.ai/api-docs.md): Read the v3 API concepts and examples as plain Markdown. - [OpenAPI v3](https://api.mirelo.ai/v3/openapi.json): Download the production v3 OpenAPI document. - [Plugins](https://mirelo.ai/plugins): Premiere Pro, DaVinci Resolve Studio, and Roblox Studio plugins. - [Studio Docs](https://mirelo.ai/docs): Studio workflow guide for uploading a clip and generating inside the app. - [API playground](https://mirelo.ai/studio/playground): Run a request in Studio before you write the client. --- # API Docs > Interactive reference for the Mirelo AI REST API. Generate sound effects, extend audio, and inpaint segments with a few lines of code. HTML explorer (canonical for search): https://mirelo.ai/api-docs Machine-readable OpenAPI v3 spec: https://api.mirelo.ai/v3/openapi.json This markdown page is the stable agent-facing reference. Endpoint request/response details live in the OpenAPI document; the HTML explorer at `/api-docs` is interactive and not a substitute for this text. ## Overview Use the Mirelo API to generate sound effects from text or video, extend and edit audio, and transcribe audio into MIDI. This reference describes the endpoints, request parameters, and responses for each workflow. ### Base URL ```text https://api.mirelo.ai ``` ### Start here Read [Developer Docs](https://mirelo.ai/developers/docs/guides) for API key setup and integration examples. See [Authentication](https://mirelo.ai/api-docs#concept/authentication) for how to authenticate requests and keep your key secure. ### Explore the API Generate sound effects: [Text to SFX](https://mirelo.ai/api-docs#text-to-sfx) turns a prompt into audio; [Video to SFX](https://mirelo.ai/api-docs#video-to-sfx) generates audio matched to footage. Edit audio: [Extend Audio](https://mirelo.ai/api-docs#extend-audio) continues an existing clip; [Inpaint Audio](https://mirelo.ai/api-docs#inpaint-audio) regenerates a selected region. Transcribe music: [Audio-to-MIDI Pro](https://mirelo.ai/api-docs#audio-to-midi) converts audio into MIDI and supports instrument detection. ### Machine-readable references Use the [API reference (Markdown)](https://mirelo.ai/api-docs.md) for a text version of the documentation, or the [OpenAPI v3](https://api.mirelo.ai/v3/openapi.json) specification for endpoint schemas. ## Authentication Every request must carry your API key as a Bearer token in the Authorization header. A key is tied to your account and can call every endpoint. ### Getting a key Go to [Studio → Settings → API Keys](https://mirelo.ai/studio/api-keys). Click "Create key", give it a name, and copy it right away — it is only shown once. ### Using your key Pass the key as a Bearer token on every request: `POST https://api.mirelo.ai/v3/text-to-sfx/generations?wait=25` ```json { "model": "sfx-2.0", "duration_ms": 8000, "input": { "prompt": "Thunder crack" } } ``` > **Warning:** Keep keys out of client-side code and version control. Rotate a key right away if it leaks. ### Check your account GET /v3/me confirms the key works and returns your balance: `GET https://api.mirelo.ai/v3/me` ```json { "account_type": "user", "id": "usr_abc123", "email": "you@example.com", "credits_available": 840, "spend_capacity": 840, "recovery_action": null, "recovery_url": null, "billing_mode": "metered", "provisioning_state": "ready", "provisioning_deadline": null } ``` credits_available is your ledger balance. spend_capacity is what the next request may spend: the balance minus credits reserved by your running jobs, plus any overage you have enabled. recovery_action and recovery_url tell you the one step to take when the account cannot pay for anything. ### Organization keys An organization key is unmetered: usage is logged, not charged to a credit balance, so credits_available and spend_capacity are null. Null here does not mean zero. Check billing_mode before you compare a quote against spend_capacity. email is null too: the organization is the payer, so there is no one user to name. ```json { "account_type": "organization", "id": "org_example", "email": null, "credits_available": null, "spend_capacity": null, "recovery_action": null, "recovery_url": null, "billing_mode": "unmetered", "provisioning_state": "ready", "provisioning_deadline": null } ``` ## Building a product You can embed Mirelo in your own application so your users generate sound or transcribe audio without leaving your product. This page states the operational facts. The legal rules are in the pages linked below — those pages win if anything here is shorter. ### Plans and commercial use Any plan can create an API key and call the API. The [Terms](https://mirelo.ai/terms) still apply: the Free plan is for non-commercial use. A paid subscription is what lets you ship a customer-facing product. Compare plans on [Pricing](https://mirelo.ai/pricing). ### What you may build The [API Terms](https://mirelo.ai/api-terms) let you embed the Services in your product. Do not resell, redistribute, or offer the API as a standalone service. You own Output to the extent intellectual property arises; you must have the rights to the Input you send, including other people's music and voices. ### Attribution If your users start or choose generations themselves, show a visible "Powered by Mirelo" credit. A fully automatic pipeline — no user initiating or influencing generation — does not need that badge. Free Studio Output has its own attribution rule in the Terms. ### Acceptable use Your End-Users must follow the [Acceptable Use Policy](https://mirelo.ai/acceptable-use-policy) and the Terms. You are responsible for their use and indemnify Mirelo if they breach those rules. We do not pre-approve applications. We can review content and suspend or terminate access when we have reason to believe the Terms or AUP were broken. ### Training Paid users can opt out of the training license in [Studio → Settings → Profile](https://mirelo.ai/studio/settings/profile). Free users cannot opt out: the training license is a condition of the free plan. Opting out stops us adding your Input and Output to our training data. Content already added stays there until you delete your account, and uses already underway are not unwound. A member's own setting does not cover organization API keys; email [legal@mirelo.ai](mailto:legal@mirelo.ai) about training on content generated with them. See the [Privacy Policy](https://mirelo.ai/privacy#processing-online-offer-improvement) for your right to object. ### Models and limits The API and Audio-to-MIDI Pro in Studio use the same Audio-to-MIDI model. See the [Audio-to-MIDI reference](https://mirelo.ai/api-docs#audio-to-midi) for input limits and request options. For sound effects, consult the [API reference](https://mirelo.ai/api-docs#text-to-sfx) and GET /v3/models for available models, supported operations, and duration limits. ### Result availability An uploaded asset can be used for 24 hours after its upload; after that, upload it again or pass a URL. Expired assets are then deleted from storage automatically. There is no delete route. Async jobs expire after 24 hours. After expiry, results and fresh download links are no longer available through the API. Download-link lifetimes before then vary by endpoint and API version, so follow the response schema and save every result promptly. Generated audio and its record are deleted 7 days after generation, or sooner for some endpoints, and every download link stops working then. If our training-data export is behind, deletion can wait for it for up to 7 more days. API results are not long-term storage. We do not publish an uptime SLA on self-serve. Ask about an enterprise agreement if you need one. ### How we handle misuse We are not obliged to monitor every request. If you believe stored content is illegal under EU or member-state law, email feedback@mirelo.ai with the location and why. We can remove content and restrict accounts. Circumventing limits, sharing keys, or missing required attribution are grounds to suspend API access. > **Info:** Treat the API as a generator, not a library. Copy results into your own storage as soon as a job finishes. ## Models Every create and preflight names a public model release in the body. There is no default or floating latest alias. GET /v3/models lists available releases and their supported operations. ### API version and model release | Identity | What it selects or records | | --- | --- | | Mirelo-Version | The shape and meaning of the HTTP/JSON contract | | model | A public model release, saved with the result | ```json { "model": "sfx-2.0" } ``` A job’s model identifies the model release used for that job. Retain it with the output. ### List the models `GET https://api.mirelo.ai/v3/models` ```json { "data": [ { "id": "sfx-1.6", "status": "general_availability", "audio": { "sample_rate": 44100, "channels": 1 }, "max_prompt_chars": 5000, "max_negative_prompt_chars": 0, "stems": [ "mix", "speech", "sfx" ], "controls": [ "loop", "preserve_speech", "multi_stem" ], "formats": [ "wav", "flac", "mp3_128", "mp3_192", "mp3_256", "mp3_320" ], "operations": { "text-to-sfx": { "duration_ms": { "min": 100, "max": 60000, "min_with_loop": 1000, "max_with_loop": 600000 }, "num_variants": { "min": 1, "max": 4 }, "controls": [ "loop" ] }, "video-to-sfx": { "duration_ms": { "min": 1000, "max": 600000, "max_with_preserve_speech": 60000 }, "num_variants": { "min": 1, "max": 4 }, "controls": [ "preserve_speech", "multi_stem" ], "media": [ "audio", "video" ] }, "extend": { "append_duration_ms": { "min": 1000, "min_with_loop": 1000, "max": 57000 }, "num_variants": { "min": 1, "max": 4 }, "controls": [ "loop" ] }, "inpaint": { "region_start_ms": { "min": 1000 }, "region_width_ms": { "min": 1000, "max": 8000 }, "num_variants": { "min": 1, "max": 4 }, "controls": [] } }, "credits_per_second": 10 }, { "id": "sfx-2.0", "status": "general_availability", "audio": { "sample_rate": 48000, "channels": 1 }, "max_prompt_chars": 2000, "max_negative_prompt_chars": 1000, "stems": [ "mix" ], "controls": [ "preserve_speech", "separate_speech" ], "formats": [ "wav", "flac", "mp3_128", "mp3_192", "mp3_256", "mp3_320" ], "operations": { "text-to-sfx": { "duration_ms": { "min": 100, "max": 600000 }, "num_variants": { "min": 1, "max": 4 }, "controls": [], "experimental": [ "input.negative_prompt" ] }, "video-to-sfx": { "duration_ms": { "min": 1000, "max": 600000 }, "num_variants": { "min": 1, "max": 4 }, "controls": [ "preserve_speech", "separate_speech" ], "media": [ "audio", "video" ], "experimental": [ "input.prompt", "input.negative_prompt" ] } }, "credits_per_second": 10 } ] } ``` GET /v3/models/{id} returns one model. Read operations for the family you are calling: duration limits (including the loop and preserve_speech variants), the num_variants ceiling, and which controls it accepts. > **Info:** Today the served models are sfx-1.6 and sfx-2.0. The formats, stems and controls listed on a model are the ones this deployment can actually produce. ### Where to read each limit | To know | Read | | --- | --- | | Which model ids exist | GET /v3/models | | Duration and num_variants limits | operations. on the model | | Audio formats you can ask for | formats on the model | | Whether loop, preserve_speech or multi_stem is available | controls on the model and on the family | | Which output types can be produced | stems on the model | | Price per second | credits_per_second on the model | | How long a prompt can be | max_prompt_chars on the model | | Whether a negative prompt is accepted, and how long | max_negative_prompt_chars on the model | | Inputs the model may not follow | operations..experimental | ### Negative prompts A negative prompt lists sounds you would rather not hear. Send it as input.negative_prompt on text-to-sfx or video-to-sfx, as a short comma-separated list: ```json { "input": { "prompt": "Heavy rain on a metal roof", "negative_prompt": "music, speech, thunder" } } ``` Not every model takes one. max_negative_prompt_chars on the model is the longest it accepts, and 0 means it takes none. Sending one to a model that publishes 0 is refused with capability_unsupported, and one over the limit is refused with invalid_request rather than cut short. Leave the field out when you have nothing to exclude. > **Info:** sfx-1.6 publishes max_negative_prompt_chars 0, so it takes no negative prompt. sfx-2.0 takes a negative prompt of up to 1000 characters. ### Experimental inputs Some models accept an input but do not always follow it. operations..experimental lists those inputs by their request path, such as input.prompt or input.negative_prompt. An experimental input usually steers the result, but check what comes back rather than relying on it. The list is per model and per family, because the same input can be followed closely by one model and loosely by another. When it is absent, the model has no experimental inputs on that family. An experimental input is still validated like any other. ## sfx-2.0 sfx-2.0 serves text-to-sfx and video-to-sfx. Requests have the same shape as on sfx-1.6: send "model": "sfx-2.0". The table lists what changes, and GET /v3/models/sfx-2.0 publishes the same limits. ### What changes from sfx-1.6 | | sfx-1.6 | sfx-2.0 | | --- | --- | --- | | Endpoint families | text-to-sfx, video-to-sfx, extend, inpaint | text-to-sfx, video-to-sfx | | text-to-sfx duration_ms | 100–60000, or 1000–600000 with controls.loop | 100–600000, no controls.loop | | text-to-sfx num_variants | 1–4 | 1–4 | | video-to-sfx duration_ms | 1000–600000, or 1000–60000 with speech preserved | 1000–600000, with or without speech preserved | | video-to-sfx num_variants | 1–4 | 1–4 | | controls.preserve_speech when left out | off | on | | Outputs with speech preserved | mix, speech, sfx | a single mix output | | input.audio | read only when controls.preserve_speech is true | read unless controls.preserve_speech is false | | A clip with no audio track and no input.audio, speech preserved | refused with invalid_video | accepted, and generated as a clip with no speech | | controls.separate_speech | not served | served | | controls.multi_stem | served | not served | | max_prompt_chars | 5000 | 2000 | | input.negative_prompt | not accepted | up to 1000 characters | | Experimental inputs | none | text-to-sfx: input.negative_prompt; video-to-sfx: input.prompt, input.negative_prompt | | Audio | 44.1 kHz mono | 48 kHz mono | | formats | wav, flac, mp3_128, mp3_192, mp3_256, mp3_320 | wav, flac, mp3_128, mp3_192, mp3_256, mp3_320 | | Price | 10 credits per second per variant, plus 8 credits per second of source audio when speech is preserved and found | 15 credits per second per variant, discounted 33% to 10 credits per second until December 12, 2026, whether or not speech is preserved | ### Behaviour to plan for On video-to-sfx, controls.preserve_speech is on by default. A request that leaves it out keeps the dialogue already in the clip, reads input.audio when you send one, and returns a single mix output with the speech in it. Send controls.preserve_speech: false to generate sound only. Preserving speech changes neither the price nor the duration ceiling: the speech is separated inside the same generation. On video-to-sfx, controls.separate_speech: true also returns the clip's speech as a speech output, at no extra cost. With controls.preserve_speech: false the outputs are mix (sound effects only), the same audio again as sfx, and speech. With speech preserved they are mix, which already carries the dialogue, and speech. A clip with no speech comes back without a speech output. On video-to-sfx, input.prompt and input.negative_prompt are guidance. The model describes the video itself and takes them into account, so it does not always follow them. Check what comes back. On text-to-sfx, input.prompt is what the model generates from. input.negative_prompt is experimental there, and in our tests it had only a weak effect. On text-to-sfx, controls.loop: true is refused rather than ignored. On video-to-sfx, controls.multi_stem: true is refused rather than ignored. sfx-2.0 does not serve extend or inpaint. ### Request `POST https://api.mirelo.ai/v3/video-to-sfx/generations?wait=25` ```json { "model": "sfx-2.0", "duration_ms": 10000, "input": { "video": { "type": "url", "url": "https://example.com/scene.mp4" }, "prompt": "Footsteps on gravel under the dialogue" } } ``` ## Assets & Sources Endpoints that take audio or video accept the file in two ways: as a public URL, or as an asset you uploaded first. Both give the same result. ### Option A — Public URL Pass the URL and the server fetches the file. It must be reachable with a plain GET; presigned links work. ```json { "input": { "video": { "type": "url", "url": "https://example.com/my-scene.mp4" } } } ``` > **Tip:** URL input is the fastest way to start. Use it when your files are already hosted (S3, a CDN) or for prototyping. ### Option B — Upload an asset first Ask for an upload ticket, POST the bytes to storage, then pass the asset id. Use this for private files, or to avoid re-fetching a large file on every request. 1. Call POST /v3/assets with the file's content_type: a plain audio/* or video/* type, with no charset or codecs. 2. POST a multipart/form-data body to upload_url. Send every entry of fields first, then the file as the last part, named file. Do not send an Authorization header. 3. Once your upload has returned 2xx, pass the id as { "type": "asset", "id": "…" }. Using it earlier fails with asset_not_ready, which you can retry. Step 1 — create an upload ticket: `POST https://api.mirelo.ai/v3/assets` ```json { "content_type": "video/mp4" } ``` ```bash # Response (201) { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "upload_url": "https://s3.amazonaws.com/...", "upload_expires_at": "2026-09-04T15:00:56.500Z", "max_bytes": 2516582400, "fields": { "key": "ast-1", "Policy": "...", "X-Amz-Algorithm": "AWS4-HMAC-SHA256" } } ``` Step 2 — POST the file to upload_url. Every fields entry goes first, the file last: ```bash curl -X POST "https://s3.amazonaws.com/..." \ -F "key=ast-1" \ -F "Policy=..." \ -F "X-Amz-Algorithm=AWS4-HMAC-SHA256" \ -F "file=@my-scene.mp4" ``` Step 3 — use the id wherever a request takes media: ```json { "input": { "video": { "type": "asset", "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" } } } ``` > **Info:** The ticket tells you max_bytes and upload_expires_at. A larger or empty body is refused with 400, and an edited field with 403. For a file over max_bytes, host it yourself and pass a URL. ### Which to use? | | URL | Asset | | --- | --- | --- | | Setup | None | Ticket + multipart POST | | File must be public | Yes | No | | Reuse across requests | Any time | Within 24 hours of upload | | Best for | Prototyping, hosted files | Private files, large files, batch jobs | ## Wait or poll Every create is one POST. You choose whether the server holds the connection while the job runs, or returns at once so you can poll. ### Wait on the create (up to 25 s) Add ?wait=25 to the create, or send Prefer: wait=25; the query parameter wins if both are present. A ?wait= value outside 1–25 is rejected, not clamped. Prefer is a hint, as RFC 7240 defines it: a longer wait is held for 25 s, and a value that is not a whole number of seconds is ignored. You get 200 with the finished job if it completes inside the window, or 202 with the job still queued or running. The body is the same job object either way, so handle both the same way. `POST https://api.mirelo.ai/v3/text-to-sfx/generations?wait=25` ```json { "model": "sfx-2.0", "duration_ms": 8000, "input": { "prompt": "Crackling campfire" } } ``` ```bash # 200 — finished inside the window { "id": "gen_01abc123def456", "object": "text_to_sfx.generation", "status": "succeeded", "model": "sfx-2.0", "seed": 42, "created_at": "2026-09-04T14:00:56.500Z", "completed_at": "2026-09-04T14:01:00.200Z", "expires_at": "2026-09-05T14:00:56.500Z", "credits": 80, "errors": [], "urls": { "self": "https://api.mirelo.ai/v3/text-to-sfx/generations/gen_01abc123def456" }, "result": { "output": { "index": 0, "type": "mix", "label": null, "start_ms": null, "end_ms": null, "category": null, "sound_start_ms": null, "sound_end_ms": null, "credits": 80, "variants_requested": 1, "status": "succeeded", "gain_db": null, "generation_id": "sg_01abc123", "variants": [ { "index": 0, "status": "succeeded", "files": { "audio": { "url": "https://cdn.mirelo.ai/output/abc123.wav", "url_expires_at": "2026-09-11T14:00:56.500Z", "bytes": 768044, "format": "wav", "sample_rate": 48000, "channels": 1, "duration_ms": 8000 } } } ] } } } ``` > **Tip:** Use wait for short clips and interactive tools. If a job may take longer than 25 s, create without wait and poll. ### Create, then poll A create without wait returns 202 and the job. Poll GET urls.self until status is final. A poll returns 200 even when the job failed, so branch on status, not on the HTTP code. HTTP errors are separate: 401 for a bad key, 404 once an expired job has been cleaned up, 429 at the rate or concurrency limit. 1. POST to the collection. The response has id, status and urls.self, the absolute URL to poll. 2. GET urls.self every 1–2 seconds. While the job runs, the Retry-After header suggests when to poll next. 3. Stop when status is succeeded, partially_succeeded, failed, canceled or expired. 4. Read result on succeeded or partially_succeeded. Check status on each output and each variant — an entry can be present and still have failed. ```bash # 202 — accepted { "id": "gen_01abc123def456", "object": "text_to_sfx.generation", "status": "queued", "model": "sfx-2.0", "created_at": "2026-09-04T14:00:56.500Z", "completed_at": null, "expires_at": "2026-09-05T14:00:56.500Z", "progress": 0, "credits": null, "errors": [], "urls": { "self": "https://api.mirelo.ai/v3/text-to-sfx/generations/gen_01abc123def456" }, "result": null } # 200 — still running { "id": "gen_01abc123def456", "object": "text_to_sfx.generation", "status": "running", "model": "sfx-2.0", "created_at": "2026-09-04T14:00:56.500Z", "completed_at": null, "expires_at": "2026-09-05T14:00:56.500Z", "progress": 0.42, "estimated_ms": 3500, "credits": null, "errors": [], "urls": { "self": "https://api.mirelo.ai/v3/text-to-sfx/generations/gen_01abc123def456" }, "result": null } ``` > **Info:** Jobs expire 24 hours after creation. A job can only be read with the API key that created it. Use progress and estimated_ms to show progress while it runs. ### Which should I use? | | Wait on create | Create, then poll | | --- | --- | --- | | Setup | ?wait=1–25 | Polling loop | | Best for | Short clips, quick prototypes | Long audio, batch, serverless | | HTTP timeout risk | Yes, if your client times out before the window ends | No | | Finished body | Job with result | Same job, after poll | ## Credits & Preflight Generation is charged in credits. On sfx-1.6 the price is 10 credits per second of billed audio, times num_variants. On sfx-2.0 the list price is 15 credits per second of billed audio, discounted 33% to 10 credits per second of billed audio until December 12, 2026, times num_variants. Preflight quotes the charge before you commit; the finished job reports what was actually debited in credits. Read credits_per_second on GET /v3/models rather than hardcoding the rate. ### Your balance GET /v3/me returns credits_available (your ledger balance) and spend_capacity (what the next request may spend, after reservations held by your running jobs and any overage). Compare a preflight quote against spend_capacity. `GET https://api.mirelo.ai/v3/me` ### What is billed | Family | Billed audio | | --- | --- | | Text to SFX | duration_ms × num_variants | | Video to SFX | duration_ms × num_variants | | Preserve speech on sfx-1.6 | The same, plus 8 credits per second of source audio once when speech is detected | | Preserve speech on sfx-2.0 | The same as Video to SFX, with no surcharge | | Separated stems | The plan's quote, scaled by num_variants | | Extend | append_duration_ms × num_variants — the source is never billed | | Inpaint | region width × num_variants — the rest of the file is never billed | | Audio-to-MIDI Pro | 2.5 credits per second of input audio (rounded to the nearest credit, minimum 1) | ### Preflight: POST the same body as create Every collection has a POST …/preflight route. Send the create body and you get the credit cost and the estimated processing time. Nothing is charged and no job starts. `POST https://api.mirelo.ai/v3/text-to-sfx/generations/preflight` ```json { "model": "sfx-2.0", "duration_ms": 8000, "num_variants": 1, "input": { "prompt": "Heavy rain on a metal roof" } } ``` ```bash # Response { "credits": 80, "estimated_ms": 4200, "credit_recovery": { "credits_required": 80, "credits_available": 840, "credit_shortfall": 0, "recovery_action": null, "recovery_url": null, "provisioning_state": "ready", "provisioning_deadline": null } } ``` > **Tip:** On a metered key, check credit_recovery before you create: provisioning_state must be "ready", recovery_action must be null, and credit_shortfall must be 0. Preflight does not reserve credits, so the create can still answer 402 if the balance changes in between. ### Unmetered keys On an unmetered key (billing_mode "unmetered" on GET /v3/me), credits in a quote is the list price for reporting, not a charge. credit_recovery is omitted, and the finished job reports credits: null. Do not compare the quote against spend_capacity, which is null too. ### Partial and failed jobs Billing is all-or-nothing on the variant count you requested. A partially_succeeded job reports the full charge, and a failed job can too: result_unreadable means audio was generated and billed but could not be read back, and that error is not retryable. See Failed & partial jobs. ### 402 and credit_recovery A create that cannot be paid answers 402. The error carries the same credit_recovery object preflight would have shown. Use it to decide the next step; do not parse the message. ```json { "error": { "code": "insufficient_credits", "message": "This request needs 20 credits; 8 are available.", "param": null, "retryable": false, "request_id": "req_01abc123def456", "credit_recovery": { "credits_required": 20, "credits_available": 8, "credit_shortfall": 12, "recovery_action": "enable_overage", "recovery_url": "https://mirelo.ai/studio/settings/billing", "provisioning_state": "ready", "provisioning_deadline": null } } } ``` ## How to preserve speech Keep the dialogue already in the clip. Same video-to-sfx collection: set controls.preserve_speech and the clip's own soundtrack is read. Generated sound is placed around the speech and ducked under it. > **Info:** The steps on this page are sfx-1.6's. On sfx-2.0, speech is preserved by default: send false to opt out. The job returns a single mix output, the duration ceiling and price do not change, and a clip with no audio track and no input.audio is not refused. sfx-2.0 under Guides lists the rest of what changes. 1. Send the video on input.video. Add input.audio only when the audio to preserve is a separate file, and keep it aligned with the clip: start_offset_ms selects the same window from both. 2. Set controls.preserve_speech to true. 3. Stay within the lower duration ceiling (max_with_preserve_speech: 60 s on sfx-1.6). Read it from GET /v3/models rather than hardcoding it. 4. Poll the job. When speech is found, result.outputs is mix, sfx, and speech. ### Request `POST https://api.mirelo.ai/v3/video-to-sfx/generations?wait=25` ```json { "model": "sfx-1.6", "duration_ms": 10000, "input": { "video": { "type": "url", "url": "https://example.com/scene.mp4" }, "prompt": "Classroom chatter under a pencil tap" }, "controls": { "preserve_speech": true } } ``` > **Info:** On sfx-1.6, preflight quotes the upper-bound price. The settled charge drops to the base video-to-sfx price when no speech is detected. > **Warning:** On sfx-1.6: a clip with no audio track at all, sent without input.audio, is refused with invalid_video rather than coming back having preserved nothing; preserve speech cannot be combined with controls.multi_stem, since that control already returns a fixed mix / sfx / speech triple; and the speech output is your own audio rather than generated audio, never persisted for feedback. ### What comes back On sfx-1.6, three outputs when speech is found: mix (speech under SFX), sfx (generated only), and speech (isolated dialogue); if nothing was detected, the speech output is omitted. ## How to generate separate stems Magic mode detects sound events and generates an independent clip for each. It does not split an existing mix into stems. It takes two requests: preflight detects the sounds and quotes a plan, then create runs that plan. 1. POST the create body, with controls.multi_stem set to true and no plan, to the preflight route. The response lists the detected outputs and includes plan_id and plan_expires_at. 2. Check the quote. Each entry in outputs has a label, a category (foreground, background or swoosh), start_ms, end_ms, sound_start_ms, sound_end_ms and its own credits. 3. POST the same body to the collection with plan set to that plan_id. Keep model, input, duration_ms, start_offset_ms and controls.multi_stem unchanged. num_variants may change; the charge scales with it. 4. Poll the job. Each detected sound is its own entry in result.outputs, with the same index, label, category and timing fields as the plan, so you can place it on your timeline. ### Step 1 — preflight with stems `POST https://api.mirelo.ai/v3/video-to-sfx/generations/preflight` ```json { "model": "sfx-1.6", "duration_ms": 10000, "input": { "video": { "type": "url", "url": "https://example.com/scene.mp4" } }, "controls": { "multi_stem": true } } ``` ```bash # Response { "credits": 71, "estimated_ms": 18000, "plan_id": "plan_4Qb8vT2nHk", "plan_expires_at": "2026-09-05T14:00:56.500Z", "outputs": [ { "index": 0, "type": "sfx", "label": "footsteps", "start_ms": 400, "end_ms": 4400, "category": "foreground", "sound_start_ms": 1400, "sound_end_ms": 3400, "credits": 40, "variants_requested": 1 }, { "index": 1, "type": "sfx", "label": "door close", "start_ms": 4100, "end_ms": 7200, "category": "foreground", "sound_start_ms": 5100, "sound_end_ms": 6200, "credits": 31, "variants_requested": 1 } ], "credit_recovery": { "credits_required": 71, "credits_available": 840, "credit_shortfall": 0, "recovery_action": null, "recovery_url": null, "provisioning_state": "ready", "provisioning_deadline": null } } ``` ### Step 2 — create with the plan `POST https://api.mirelo.ai/v3/video-to-sfx/generations?wait=25` ```json { "model": "sfx-1.6", "duration_ms": 10000, "input": { "video": { "type": "url", "url": "https://example.com/scene.mp4" } }, "controls": { "multi_stem": true }, "plan": "plan_4Qb8vT2nHk" } ``` > **Info:** start_ms and end_ms are the generated window, which reaches up to a second past the sound on each side (less near the start or end of the video) so you can crossfade it into its neighbours. sound_start_ms and sound_end_ms mark the sound itself: trim the clip to that span. Treat category as an open list and handle a value you do not recognise. > **Info:** Rules: omit input.prompt and seed, which this mode cannot honor. Do not combine with preserve_speech. Stems always come back as wav (another output.format is refused), start_offset_ms must stay 0, and a create that asks for stems without a plan is refused. A plan is valid for 24 hours and only for the body it was quoted for. If the body differs, the create fails with invalid_request on plan; an unknown or expired plan fails with not_found. > **Warning:** Preflight runs detection but does not reserve credits, so the create can still answer 402. If outputs is empty, nothing was detected and there is nothing to generate. ## Idempotency Creates accept an optional Idempotency-Key header. Send the same key with the same body again and you get the original job back instead of a second generation. The same key with a different body is refused with 409 idempotency_conflict. ### When to send one Send a key on any create you might retry: a client timeout, a 5xx, or a connection that dropped while you were waiting. Preflight, poll, models, me and assets do not take one. > **Info:** The key is yours to choose; a UUID is enough. A key is scoped to your account and to the collection you sent it to, and is remembered for 24 hours. ### Retry after a lost response Save the key and the exact body before you send the create. If the response is lost, send the same request again. You get the original job — 200 if it has finished, 202 if it is still running — and the Idempotent-Replayed: true header tells you it was a replay. Poll urls.self as usual. `POST https://api.mirelo.ai/v3/text-to-sfx/generations?wait=25` Header: `Idempotency-Key: 8f3c1e2a-5b7d-4c9e-9a1f-2d3e4f5a6b7c` ```json { "model": "sfx-2.0", "duration_ms": 8000, "input": { "prompt": "Heavy rain on a metal roof" } } ``` > **Warning:** Do not mint a new key just because a response was lost: that starts a second paid job. A 409 means the body changed, so find the original body and resend that. A job that already failed is not repaired by replaying its key. Starting a new generation is a new decision and may be charged again. ## Failed & partial jobs A 200 from a poll means the API returned the job, not that the job succeeded. Branch on status. Request problems come back in error; problems inside an accepted job come back in errors on the job. ### Partial success This job asked for two variants. One is downloadable; the other was generated but could not be read back. Both variants are listed, so do not compare variants.length to variants_requested — read status on the output and on each variant. The full charge for two variants remains. ```json { "id": "gen_partial_example", "object": "text_to_sfx.generation", "status": "partially_succeeded", "model": "sfx-2.0", "created_at": "2026-09-04T14:00:56.500Z", "completed_at": "2026-09-04T14:01:00.200Z", "expires_at": "2026-09-05T14:00:56.500Z", "credits": 160, "errors": [{ "output_index": 0, "variant_index": 1, "code": "result_unreadable", "message": "The generated file could not be read back.", "retryable": false }], "urls": { "self": "https://api.mirelo.ai/v3/text-to-sfx/generations/gen_partial_example" }, "result": { "output": { "index": 0, "type": "mix", "label": null, "start_ms": null, "end_ms": null, "category": null, "sound_start_ms": null, "sound_end_ms": null, "credits": 160, "variants_requested": 2, "status": "partially_succeeded", "gain_db": null, "generation_id": "sg_partial_example", "variants": [ { "index": 0, "status": "succeeded", "files": { "audio": { "url": "https://cdn.mirelo.ai/output/partial.wav", "url_expires_at": "2026-09-11T14:00:56.500Z", "bytes": 768044, "format": "wav", "sample_rate": 48000, "channels": 1, "duration_ms": 8000 } } }, { "index": 1, "status": "failed", "files": null } ] } } } ``` ### A failed job can still be charged Here audio was generated and debited, but the file could not be read back. result_unreadable is not retryable: a retry would generate and charge a second copy. Keep the job id and errors for support. When nothing was debited, credits is 0. On an organization key credits is always null. ```json { "id": "gen_failed_example", "object": "text_to_sfx.generation", "status": "failed", "model": "sfx-2.0", "created_at": "2026-09-04T14:00:56.500Z", "completed_at": "2026-09-04T14:01:00.200Z", "expires_at": "2026-09-05T14:00:56.500Z", "credits": 80, "errors": [{ "output_index": null, "variant_index": null, "code": "result_unreadable", "message": "Audio was generated and billed but could not be read back.", "retryable": false }], "urls": { "self": "https://api.mirelo.ai/v3/text-to-sfx/generations/gen_failed_example" }, "result": null } ``` ### What to do next | Outcome | Action | | --- | --- | | partially_succeeded | Download the variants whose status is succeeded. Report the others using output_index and variant_index from errors. | | failed, retryable false | Stop. Fix the input or contact support with the job id. Replaying the same idempotency key returns this same failed job. | | failed, retryable true | You may send the request again as a new create with a new key. It is billed as new work. | | canceled or expired | Stop polling. Anything you already downloaded is still yours; regenerating is a new job. | ## Downloads & expiry Download links are temporary. Save the audio in your own storage as soon as a job finishes. ### Poll a finished job again GET urls.self again with the same API key to read the job's links. Polling never extends a link you already have. Right after a job succeeds, we may still be storing our copy of its generated audio. Until that finishes, those links are short-lived; after it, polls return a new link to the stored copy with a later url_expires_at, and the same link every time from then on. So if a link to generated audio expires too soon for you, poll again a few seconds later. Audio we do not store never gets a longer-lived link: download it before its url_expires_at. We never store the isolated speech. Whether we store a preserve-speech mix that contains your speech depends on the model, so read each link's url_expires_at rather than assume. A GET is a read: it starts no work and costs no credits. `GET https://api.mirelo.ai/v3/text-to-sfx/generations/gen_01abc123def456` ### Lifetimes | Field | What it covers | When it ends | | --- | --- | --- | | Job — expires_at | The job and its results. 24 hours from creation. | GET no longer returns the results, and may return 404 once the job is cleaned up. Links you already have keep working until their own url_expires_at. | | Generated audio | The stored audio behind every download link. 7 days from generation, or sooner for some endpoints; up to 7 more days while the training-data export catches up. | The audio and its record are deleted; download links return 404. | | File — url_expires_at | One download link. Read the timestamp rather than assuming a fixed lifetime. A link to generated audio we store lasts until that audio is deleted, which is after the job expires. A link to audio we do not store can stop working soon after the job finishes. We never store the isolated speech; whether we store a preserve-speech mix that contains your speech depends on the model. | The link stops working. Polling the job again does not extend it, so download before then. | | Plan — plan_expires_at | A separated-stems plan. 24 hours from preflight. | Preflight again for a new plan. | | Upload — upload_expires_at | The window in which an asset upload may start. Currently one hour. | Mint a new ticket with POST /v3/assets and upload again. | > **Info:** A download link may redirect to storage. Follow the redirect, and do not send your API key with it — the link authorizes itself. Audio-to-MIDI download URLs are temporary too. Save every result promptly; after the job expires, you cannot poll it for fresh links. See [Building a product](https://mirelo.ai/api-docs#concept/building-on-the-api) for integration guidance. ## Errors & Limits Every error uses the same envelope. Treat code as an open list: a client should tolerate a value it does not know. ```json { "error": { "code": "invalid_request", "message": "num_variants is above the model's ceiling.", "param": "num_variants", "retryable": false, "request_id": "req_01abc123def456" } } ``` | Field | Meaning | | --- | --- | | error.code | Stable machine-readable code | | error.message | For humans; do not parse it | | error.param | The JSON body path that failed, or null | | error.retryable | Whether the same request may succeed later | | error.request_id | Quote this when you contact support | ### Concurrency and rate limits By default, up to 5 of your jobs run at the same time. The ceiling is set per key, so read Mirelo-Concurrency-Limit rather than assuming the default. Create and poll responses carry Mirelo-Concurrency-Limit, and carry Mirelo-Concurrency-Current except when the in-flight count could not be read. At the ceiling a create answers 429 with those headers and a Retry-After. The RateLimit-* headers describe the request-rate allowance; for generation, concurrency is the limit you reach first. ### Version pin Send Mirelo-Version to pin the API behaviour you tested against. The current version date is in the OpenAPI document. Without the header you get the latest. ### Whole milliseconds Every duration field is a whole number of milliseconds. Fractional values are rejected. ## Hosted MCP Mirelo runs an official, remotely hosted Model Context Protocol (MCP) server so AI agents — Cursor, Claude, and other MCP-capable clients — can generate audio directly from chat. Connect once, sign in with your existing Mirelo account, and the agent can check your credits, generate SFX, and poll long-running jobs. ### How it works Connecting uses Sign in with Mirelo (Clerk OAuth) — the same account as Studio — so there is no key to create, paste, or rotate, and no separate SDK on the generate path. Headless agents that cannot complete a sign-in can send a personal Mirelo API key (sk-…) as a Bearer token instead. Access is available to signed-in Studio accounts (free and paid); every tool call is billed against your normal Studio credits, exactly like generating from the Studio UI or the public API. ### Connect Copy this URL into your agent's remote MCP / connector settings: ```bash https://mcp.mirelo.ai/mcp ``` #### Cursor ```json { "mcpServers": { "mirelo": { "url": "https://mcp.mirelo.ai/mcp" } } } ``` 1. Add the JSON above to Cursor's MCP settings (or add a remote server pointing at the URL). 2. Click Connect, then complete Sign in with Mirelo. 3. Tools appear in the agent once OAuth succeeds. #### Claude and other clients Use the same https://mcp.mirelo.ai/mcp URL with your client's remote MCP / connector flow. If a client only supports stdio, wrap it with mcp-remote pointing at that URL (streamable HTTP; no extra flags needed over HTTPS). ### Tools | Tool | Mode | Notes | | --- | --- | --- | | get_account | — | Credits, shortfall for a minimum request, and provisioning state | | preflight | — | Credit cost + ETA for a generate/edit request: the operation plus the body you would send. Names the model the quote is for | | list_models | — | The models this account can use, each one's limits and options, and the default model | | create_upload | — | Preferred way to send a local file. Returns a pre-signed URL — the agent PUTs the file directly, so bytes never pass through the conversation | | inspect_asset | — | Read a create_upload asset's real duration, content type, and size before generating | | create_asset | — | Inline upload for very small files only (bytes ride in the tool call). Prefer create_upload for anything of real size | | text_to_sfx | async | Takes the v3 text-to-sfx body; returns the v3 job | | video_to_sfx | async | Takes the v3 video-to-sfx body; returns the v3 job. With controls.multi_stem, returns one clip per detected sound (quote it with preflight first to get the plan) | | extend_audio | async | Takes the v3 extend body, with an optional video; returns the v3 job | | inpaint_audio | async | Takes the v3 inpaint body, with an optional video; returns the v3 job | | audio_to_midi | async | Transcribe a recording into MIDI and MusicXML, with optional engraved PDFs; returns a job_id | | get_transcription_notes | — | Read a finished transcription's notes in pages. Only needed for note-level work; the job result is a compact summary | | get_job | async | Client-driven poll by job id (preferred when the agent can loop) | | wait_job | async | Block until a job is final | ### Async jobs All generate/edit tools are async-only. Call text_to_sfx, video_to_sfx, extend_audio, inpaint_audio, or audio_to_midi to get a job, then get_job to poll (or wait_job to block until it finishes). Prefer get_job when the agent can poll itself. > **Info:** Current scope: music, Roblox tools, and Studio projects/timeline editing are deferred and not exposed over MCP. ## Migrate from V2 This page maps V2 onto V3. Everywhere else, these docs describe V3 on its own terms. Audio-to-MIDI has no V3 collection yet: those paths stay /v2/audio-to-midi/… and are unchanged. ### Paths | V2 | V3 | | --- | --- | | /v2/text-to-sfx/v1.6/sync or /jobs | POST /v3/text-to-sfx/generations | | /v2/video-to-sfx/v1.6/… | POST /v3/video-to-sfx/generations | | /v2/video-to-sfx-preserve-speech/v1.6/… | Same video-to-sfx collection, with controls.preserve_speech | | /v2/extend-audio/v1.6/… and /with_video/… | POST /v3/extend/edits (video is the optional input.video) | | /v2/inpaint-audio/v1.6/… and /with_video/… | POST /v3/inpaint/edits (video is the optional input.video) | | GET …/preflight?duration_ms=&num_samples= | POST …/preflight with the same body as create | | /v2/video-to-sfx-stems/v1.6/analyze and /jobs | Video-to-SFX preflight and generations, with controls.multi_stem: true and plan on create | | GET /v2/me | GET /v3/me | | POST /v2/assets, then PUT to upload_url | POST /v3/assets, then a multipart POST with fields + file | | openapi.yaml | GET /v3/openapi.json | ### Request shape | V2 | V3 | | --- | --- | | Model in the URL (/v1.6/) | Required body field model (sfx-1.6) | | Flat prompt, duration_ms, loop, output_format | Nested input / controls / output | | num_samples | num_variants | | output_format: wav \| mp3 \| aac \| flac | output.format: wav, flac, mp3_128, mp3_192, mp3_256, mp3_320 | | { type: "url", video_url / audio_url } or { type: "asset", asset_id } | { type: "url", url } or { type: "asset", id } | | seed -1 or null for random | Omit seed; -1 is rejected | | Sync POST …/sync | POST the collection with ?wait=25 (or Prefer: wait=25) | | Async POST …/jobs, then GET …/jobs/{job_id} | POST the collection, then GET …/{id}; a poll is always 200 | ### Response shape | V2 | V3 | | --- | --- | | result_urls[] | Job envelope: id, status, result.output(s).variants[].files.audio | | job_id / job_url / errored | id / urls.self / failed | | estimated_completion_at, request echo | Gone — use estimated_ms, progress, and your own records | | Partial failure via shorter arrays | partially_succeeded; check status on each output and variant | | Flat error or status text | { error: { code, message, param, retryable, request_id } } | | 402 without structured recovery | 402 includes credit_recovery | ### Features that fold into one collection Preserve speech is no longer its own path: set controls.preserve_speech on video-to-sfx, and send input.audio only when the audio to preserve is not the clip's own. Extend-with-video and inpaint-with-video are the same edits collections with an optional input.video. On extend, loop and video cannot be combined, prepend_duration_ms is refused, and start_offset_ms needs a video. Multi-stem generation uses the same planner as V2: preflight with controls.multi_stem: true, then create with the returned plan_id as plan. Obtain a new V3 plan when migrating from V2; V2 plan ids cannot be used on V3. See how to generate separate stems. ### Headers Send Idempotency-Key on creates you might retry. Pin behaviour with Mirelo-Version. Read Mirelo-Concurrency-* (default ceiling 5) and RateLimit-*. ### Machine-readable spec The V3 OpenAPI document is GET /v3/openapi.json. It is public and not behind a flag. Use it to generate clients. The limits on these pages are read from the same model registry GET /v3/models serves. Audio-to-MIDI is included on its existing /v2/audio-to-midi/… paths until it has a V3 collection. ## Billing & retries ### Preflight prices your duration estimate GET /v2/audio-to-midi/v1.0/preflight applies max(1, round(seconds × 2.5)) to the duration_ms you submit, converted to seconds. It does not download or inspect the audio and charges nothing. `GET https://api.mirelo.ai/v2/audio-to-midi/v1.0/preflight?duration_ms=300000` ### Final billing uses the measured audio The sync and async create routes download the media, probe its real duration, and apply the same formula to that. A preflight for 300 seconds quotes 750 credits; media that measures 308.059 seconds bills 770. Keep the submitted duration accurate and treat the quote as an estimate. ### Retry synchronous requests safely Send Idempotency-Key (up to 128 characters) on every sync request you might retry: one stable key per logical transcription, the same body every time, reused only when the first response was ambiguous, such as after a client timeout. A completed request's key is remembered for 24 to 48 hours after the original request completes, depending on when the daily sweep runs. A 409 retry does not extend this window. ```bash operation_key="order-123" transcribe() { curl --fail-with-body --max-time 600 \ https://api.mirelo.ai/v2/audio-to-midi/v1.0/sync \ -H "Authorization: Bearer $MIRELO_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $operation_key" \ --data '{"audio":{"type":"url","audio_url":"https://example.com/song.mp3"}}' } transcribe # If the response was ambiguous, retry the identical request with the same key. transcribe ``` | Key reuse | Result | | --- | --- | | Same body; first request still running | 409 idempotency_conflict | | Same body; first request completed and still remembered | 409 idempotency_conflict | | Different body | 409 idempotency_conflict | | First attempt failed | The request runs again under the same key | | Any reuse after the key is forgotten (24 to 48 hours after the original request completes) | A new request runs and is billed | > **Warning:** A 409 means the retry did not start a second billable transcription, but it does not replay the original response or extend the key's retention window, so keep any success you receive. Sync accepts audio up to 12 minutes; longer recordings return 422 sync_too_long before transcription or billing. Use POST /v2/audio-to-midi/v1.0/jobs instead. A longer client timeout does not extend the server runtime. ### Prefer async for long or high-volume work Use POST /v2/audio-to-midi/v1.0/jobs for long files, batches, serverless functions, and pipelines that need a retrievable job_id instead of one long-lived connection, then poll job_url until the job succeeds or errors. Idempotency-Key is not supported on the async create route: resubmitting POST .../jobs can create and bill a separate job. ## Instrument detection The instruments field is exhaustive: an instrument left out cannot appear in the transcription, and one that is not playing pulls notes onto the wrong part. POST /v2/audio-to-midi/v1.0/instruments listens to sampled windows of your audio and suggests a list to start from. ### Detect, review, then transcribe 1. Upload the audio once as an asset, the same kind of asset_id an Audio-to-MIDI request takes. 2. Call POST /v2/audio-to-midi/v1.0/instruments with that asset. A URL source works too; the response returns the asset_id of the stored copy. 3. Review the suggestions and correct the list. 4. Transcribe with the same asset, your corrected instruments, and the instrument_detection_id. `POST https://api.mirelo.ai/v2/audio-to-midi/v1.0/instruments` ```json { "audio": { "type": "asset", "asset_id": "3f1f4c1e-8a4b-4b8e-9f9e-2b6c1d0a7e55" }, "max_credits": 0 } ``` ### Suggestions need review recommended_instruments is the detector's best list, ready to pass as instruments. instruments lists everything it admitted, with agreement: the largest share of one window's independent listens that named the instrument. Agreement is a rough signal of how often a label is right, not a probability. An instrument the detector missed carries no score at all. > **Warning:** Review every suggestion, not only the ones with low agreement, and add anything that is missing. There are no timestamps: the transcription result says exactly when each instrument plays. ### Pricing | Situation | Charge | | --- | --- | | A transcription that succeeds passes the instrument_detection_id | Free | | Fewer than 10 unused detections on your account in the last 24 hours | Free | | Past that allowance | Up to 50 credits, never more than transcribing the same audio; they come off the transcription that uses the detection within 30 days, in the same billing period | | The same audio again, under the same detector revision | Free; returns the cached answer | A detection is charged only when it succeeds. The allowance counts per account, across all of your API keys. Each response reports credits_charged and free_detections_remaining. > **Tip:** Send max_credits: 0 to run a detection only when it is free. Past the allowance it fails with 402 max_credits_exceeded and charges nothing, so an app can detect as soon as a user opens it and offer the paid detection only when they ask.