# audiocutter.online — integration guide for AI pipelines > This document is for AI agents and LLM-driven pipelines. For the full human-facing > docs, see https://audiocutter.online/docs/ ## What this is audiocutter.online is a browser-based audio editor. The primary integration path for AI pipelines and partner workflows is **hosted sessions**: you hand the user a pre-configured editing project (audio files + fragments + effects), and their edits are POSTed back to your callback URL when they click Submit. Use it whenever your pipeline produces audio that benefits from a human refinement step before the next stage: TTS voiceover review, AI-generated music edits, auto-trim verification, podcast assembly QA, branded review with logos/colors. There is also a lighter-weight **postMessage API** for handing off a single audio file from a browser context, with no submit-back. It's documented near the bottom of this file. If you're choosing between the two, the rule is simple: | If you have... | Use | Submit-back? | | ------------------------------------------------------- | -------------------- | ------------ | | A multi-fragment project (+ optional branding/callback) | **Hosted sessions** | Yes | | A single audio file to hand off | postMessage API | No | If your pipeline needs the edited result back, you want hosted sessions. ## Integration shape ``` [Your pipeline] ├─ generates draft audio ├─ writes a manifest JSON ──hosted at any HTTPS URL with CORS headers──┐ ├─ redirects user to: │ │ https://audiocutter.online/?session= │ ▼ │ [Browser] │ ├─ fetches manifest ◄───────────────────────────────────────────┘ ├─ fetches each audio file (data: URI inline, or https:// URL) ├─ user edits in the editor ├─ user clicks Submit ▼ [Your callback] └─ receives multipart POST: { manifest: edited JSON, audio: rendered file } ``` ## Minimum viable manifest Most fields are defaulted. The bare minimum is a file source and a fragment that references it: ```json { "project": { "files": [{ "id": "f1", "source": "data:audio/mpeg;base64,SUQzBAAAAAAA..." }], "fragments": [{ "id": "fr1", "fileId": "f1" }] }, "session": { "output": { "mode": "multipart", "callbackUrl": "https://your.api/callback" } } } ``` `version`, project volume/fades/compression/filter, fragment name/start/fileStart/fileEnd/volume/fades — all defaulted. Omitted `fileEnd` means "the whole file". Override any field by including it explicitly. Time values are **microseconds** (1 second = 1_000_000). ## Fully specified manifest (for reference) ```json { "version": 2, "project": { "volume": 1, "fadeIn": { "duration": 0 }, "fadeOut": { "duration": 0 }, "compression": { "enabled": false, "threshold": -24, "ratio": 4, "knee": 30, "attack": 0.003, "release": 0.25, "makeupGain": 0 }, "filter": { "enabled": false, "type": "lowpass", "frequency": 350, "q": 1 }, "files": [ { "id": "file-1", "source": "data:audio/mpeg;base64,SUQzBAAAAAAA..." } ], "fragments": [ { "id": "frag-1", "name": "Draft clip", "start": 0, "fileId": "file-1", "fileStart": 0, "fileEnd": 5000000, "volume": 1, "fadeIn": 0, "fadeOut": 0 } ] }, "session": { "ui": { "title": "Refine the AI draft", "instruction": "Trim silence and submit.", "submitLabel": "Send to pipeline" }, "output": { "mode": "multipart", "callbackUrl": "https://your.api/callback", "format": "mp3" } } } ``` ## Field reference Only five fields are required. Everything else has a default — omit it and the loader fills it in. ### Required | Field | Type | Notes | | -------------------------------- | ------ | ---------------------------------------------------------------- | | `project.files[].id` | string | Unique per file. Fragments reference files by id. | | `project.files[].source` | string | `data:` URI, `https://` URL, or path resolved against manifest URL. | | `project.fragments[].id` | string | Unique per fragment. | | `project.fragments[].fileId` | string | Must match a `project.files[].id`. | ### Optional with defaults | Field | Type | Default | Notes | | ---------------------------------- | --------------- | -------------------------------- | ------------------------------------ | | `version` | integer | `2` | Schema version. | | `project.volume` | 0..1 | `1` | Project-level gain. | | `project.fadeIn.duration` | µs | `0` | Project fade-in. | | `project.fadeOut.duration` | µs | `0` | Project fade-out. | | `project.compression` | object | `{ enabled: false, ... }` | DynamicsCompressor params; disabled by default. | | `project.filter` | object | `{ enabled: false, type: 'lowpass', frequency: 350, q: 1 }` | Biquad filter; disabled by default. | | `project.files[].filename` | string | derived from MIME (only when `source` is a `data:` URI) | Display name; extension hints at format. **Required** when `source` is an `https://` URL or relative path. The v1 alias `originalFilename` is still accepted. | | `project.fragments[].name` | string | `""` | | | `project.fragments[].start` | µs | `0` | Position on the timeline. | | `project.fragments[].fileStart` | µs | `0` | Slice start within the source file. | | `project.fragments[].fileEnd` | µs | full file duration | Slice end. Omit to use the whole file. Resolved at load time from the decoded audio. | | `project.fragments[].volume` | 0..1 | `1` | | | `project.fragments[].fadeIn` | µs | `0` | | | `project.fragments[].fadeOut` | µs | `0` | | ### Optional, no default (omit → feature off) | Field | Type | Notes | | ------------------------------------ | ------------------------------------------------------- | ------------------------------------------- | | `session` | object | Omit → editor opens with no banner / submit-back. | | `session.ui.title` | string | Banner title. Plain text. | | `session.ui.instruction` | string | Banner subtitle. Plain text. | | `session.ui.submitLabel` | string | Submit button label. | | `session.ui.accentColor` | CSS color | Tints banner + Submit button. | | `session.ui.logoUrl` | `https://` URL | CORS-friendly image. | | `session.output` | object | Omit → no submit-back; user falls back to local download. | | `session.output.mode` | `multipart` \| `split` \| `presigned` | Required *within* `output`. Default to `multipart`. | | `session.output.format` | `mp3` \| `wav` \| `ogg` \| `mp4` \| `flac` \| `aac` \| `aiff` | Defaults to `wav`. | | `session.output.headers` | record | Sent on callback. Use for `Authorization`. | | `session.output.successRedirectUrl` | `https://` URL | Browser navigates here after a successful submit. | ### Mode-specific fields (under `session.output`) | Mode | Required keys | Optional keys | | ----------- | ------------------------------------------------------------ | ---------------------------------------------- | | `multipart` | `callbackUrl: https://...` | — | | `split` | `audioUploadUrl: https://...`, `manifestUrl: https://...` | `audioUploadMethod: 'POST'|'PUT'` (default `POST`), `audioUploadField: string` (default `'audio'`). Audio response forwarded as header `X-Audio-Upload-Response` on the manifest POST. | | `presigned` | `audioUrl: https://...` (pre-signed PUT), `manifestUrl: https://...` | Audio PUT with `Content-Type` of the chosen format; manifest POSTed after. | ## source forms `source` is one of: - `data:audio/mpeg;base64,...` — inline base64. Self-contained, no CORS hop, ~33% size overhead. Best for clips under ~5 MB. - `https://...` — absolute URL fetched as-is. Requires CORS-friendly response (see below). - relative path (e.g. `audio/clip.mp3` or `/audio/clip.mp3`) — resolved against the manifest URL via `new URL(source, manifestUrl)`. Most natural form when you host audio next to the manifest. `http://` absolute URLs and any other scheme are rejected. (Relative paths inherit the manifest URL's scheme, so a local `http://localhost` manifest can reference `http://localhost` audio — browsers treat `localhost` as a secure context.) ## Output modes — when to use | Mode | Use when | | ----------- | ------------------------------------------------------------------------------------------------- | | `multipart` | **Default.** One server, one endpoint. Simplest end-to-end. | | `split` | Audio store and metadata store are separate services. Or audio uploads need a different auth. | | `presigned` | You already have pre-signed PUT URLs (S3, R2, GCS) and want the audio to land directly in object storage. | When in doubt, choose `multipart`. ## Receiving the result — `multipart` **Python (FastAPI):** ```python from fastapi import FastAPI, File, UploadFile import json app = FastAPI() @app.post("/callback") async def receive(manifest: UploadFile = File(...), audio: UploadFile = File(...)): manifest_data = json.loads(await manifest.read()) audio_bytes = await audio.read() return {"ok": True} ``` **Node (Express + multer):** ```js import express from 'express' import multer from 'multer' const upload = multer() const app = express() app.post('/callback', upload.fields([{ name: 'manifest' }, { name: 'audio' }]), (req, res) => { const manifest = JSON.parse(req.files.manifest[0].buffer.toString()) const audio = req.files.audio[0].buffer res.json({ ok: true }) }) ``` ## CORS requirements (must-read) audiocutter.online is served with `Cross-Origin-Embedder-Policy: require-corp`. Every cross-origin resource must opt in or the browser refuses to load it. For the manifest JSON, every audio URL, and any logo URL, set: ``` Access-Control-Allow-Origin: https://audiocutter.online Cross-Origin-Resource-Policy: cross-origin ``` For the callback endpoint, respond to the CORS preflight with: ``` Access-Control-Allow-Origin: https://audiocutter.online Access-Control-Allow-Methods: POST, PUT, OPTIONS Access-Control-Allow-Headers: Content-Type, Authorization, X-Audio-Upload-Response ``` `data:` URI sources bypass CORS entirely — easiest path when your audio is small. ## Common patterns ### Generate → review → publish 1. AI generates draft audio. 2. Pipeline uploads the draft to storage, returns a signed URL. 3. Pipeline builds a manifest with that URL as `files[0].source` and a `multipart` callback. 4. User receives a link to `audiocutter.online/?session=...`. 5. User trims/edits/submits. Callback receives the final audio. Pipeline proceeds. ### Multi-step pipeline with HITL gate The session URL is generated and emailed/Slacked to the user. The pipeline's worker waits (polls, or subscribes to a webhook from your callback) until the callback fires. ### Branded review with logo + accent color Set `session.ui.logoUrl`, `accentColor`, `title`, `submitLabel` to match the partner brand. The editor remains audiocutter.online, but the banner above the editor surfaces the partner's identity and submission context. ## Limits & caveats - **Single user per session URL.** No multiplayer / locking. Each URL is one editing run. - **No resumability in v2.** If the user closes the tab before submitting, their edits are lost. - **CORS or it doesn't work.** Most integration failures trace to missing CORP/CORS headers. - **Time unit is microseconds**, not milliseconds or seconds. - **Audio is re-rendered.** The Submit flow renders the full project audio in the browser (via Web Audio + FFmpeg) before posting. Long projects = long render time. ## Versioning `PROJECT_FILE_VERSION = 2`. The loader accepts v1 manifests (with `zipPath` instead of `source`) for backwards compatibility, but new integrations should always emit v2. ## Alternative: postMessage API (single-file handoff) For the lighter case — open the editor from a browser context with one audio file pre-loaded, no submit-back — use the postMessage API instead of a hosted session: ```js const popup = window.open('https://audiocutter.online/import/') window.addEventListener('message', (event) => { if (event.data?.source === 'audiocutter' && event.data.type === 'ready') { popup.postMessage({ type: 'audio', file: file }, 'https://audiocutter.online') } }) ``` Contract: - Popup posts `{ source: 'audiocutter', type: 'ready' }` to `window.opener` once loaded. - Opener replies with `{ type: 'audio', file: File }` — targetOrigin must be `'https://audiocutter.online'`, not `'*'`. - Popup times out after **30 seconds** if no audio message arrives. - `window.open` must be called inside a user gesture handler (no `await` before it) or the popup is blocked. - The popup stores the file in IndexedDB then navigates to `/`; the opener relationship is severed after that. Full reference: https://audiocutter.online/docs/post-message-api/ Use hosted sessions instead whenever you need a multi-fragment project, branded UI, or the edited audio back. ## Where to learn more - postMessage API: https://audiocutter.online/docs/post-message-api/ - Hosted sessions (full guide): https://audiocutter.online/docs/hosted-sessions/ - Docs index: https://audiocutter.online/docs/