CleanvoiceDocs
REST API

Uploads

How to provide files to the Cleanvoice API.

Try with your API key

Paste your key — all code examples on this page update instantly. Stays in your browser only, never sent anywhere.

Option 1: Public URL

POST https://api.cleanvoice.ai/v2/edits

Pass the file URL directly in the files array. The API fetches it — no upload step needed.

curl -X POST https://api.cleanvoice.ai/v2/edits \
  -H "X-API-Key: $CLEANVOICE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": {
      "files": ["https://example.com/episode.mp3"],
      "config": { "fillers": true }
    }
  }'
<?php
$ch = curl_init('https://api.cleanvoice.ai/v2/edits');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => [
        'X-API-Key: ' . getenv('CLEANVOICE_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'input' => [
            'files'  => ['https://example.com/episode.mp3'],
            'config' => ['fillers' => true],
        ],
    ]),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
echo $response['id'];
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

String body = """
    {
      "input": {
        "files": ["https://example.com/episode.mp3"],
        "config": {"fillers": true}
      }
    }""";

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.cleanvoice.ai/v2/edits"))
    .header("X-API-Key", System.getenv("CLEANVOICE_API_KEY"))
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(body))
    .build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
require 'net/http'
require 'json'

uri  = URI('https://api.cleanvoice.ai/v2/edits')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

request = Net::HTTP::Post.new(uri)
request['X-API-Key']    = ENV['CLEANVOICE_API_KEY']
request['Content-Type'] = 'application/json'
request.body = JSON.generate({
  input: {
    files:  ['https://example.com/episode.mp3'],
    config: { fillers: true },
  },
})

response = JSON.parse(http.request(request).body)
puts response['id']
{ "id": "edit_abc123" }

Use the id to poll for the result.

Works with any direct download URL: S3, GCS, Dropbox (?dl=1), Google Drive, or your own CDN.


Option 2: Local file upload

POST https://api.cleanvoice.ai/v2/upload?filename=...  →  PUT (signed URL)  →  POST https://api.cleanvoice.ai/v2/edits

For local files, use a three-step process: request a signed URL, PUT your file, then submit the edit.

Step 1 — Request a signed upload URL:

curl -X POST "https://api.cleanvoice.ai/v2/upload?filename=episode.mp3" \
  -H "X-API-Key: $CLEANVOICE_API_KEY"

Response:

{
  "signedUrl": "https://storage.example.com/episode.mp3?signature=..."
}

Step 2 — PUT your file to the upload URL:

SIGNED_URL="https://storage.example.com/episode.mp3?signature=..."

curl -X PUT "$SIGNED_URL" \
  -H "Content-Type: audio/mpeg" \
  --data-binary @/path/to/episode.mp3

Response: HTTP 200 OK (empty body)

Step 3 — Submit the edit using the signed URL without its query string:

FILE_URL="${SIGNED_URL%%\?*}"

curl -X POST https://api.cleanvoice.ai/v2/edits \
  -H "X-API-Key: $CLEANVOICE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "input": { "files": ["'"$FILE_URL"'"], "config": { "fillers": true } } }'

Response:

{ "id": "edit_abc123" }
<?php
$apiKey   = getenv('CLEANVOICE_API_KEY');
$filePath = '/path/to/episode.mp3';

// Step 1 — Request a signed upload URL
$ch = curl_init('https://api.cleanvoice.ai/v2/upload?filename=episode.mp3');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ["X-API-Key: $apiKey"],
]);
$upload = json_decode(curl_exec($ch), true);
curl_close($ch);
// $upload = ["signedUrl" => "..."]

$signedUrl = $upload['signedUrl'];
$fileUrl   = strtok($signedUrl, '?');

// Step 2 — PUT the file
$fh = fopen($filePath, 'rb');
$ch = curl_init($signedUrl);
curl_setopt_array($ch, [
    CURLOPT_PUT            => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Content-Type: audio/mpeg'],
    CURLOPT_INFILE         => $fh,
    CURLOPT_INFILESIZE     => filesize($filePath),
]);
curl_exec($ch); // HTTP 200 OK
curl_close($ch);
fclose($fh);

// Step 3 — Submit the edit
$ch = curl_init('https://api.cleanvoice.ai/v2/edits');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => [
        "X-API-Key: $apiKey",
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'input' => [
            'files'  => [$fileUrl],
            'config' => ['fillers' => true],
        ],
    ]),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
// $response = ["id" => "edit_abc123"]
echo $response['id'];
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Path;

String apiKey   = System.getenv("CLEANVOICE_API_KEY");
String filePath = "/path/to/episode.mp3";
HttpClient client = HttpClient.newHttpClient();

// Step 1 — Request a signed upload URL
HttpRequest step1 = HttpRequest.newBuilder()
    .uri(URI.create("https://api.cleanvoice.ai/v2/upload?filename=episode.mp3"))
    .header("X-API-Key", apiKey)
    .POST(HttpRequest.BodyPublishers.noBody())
    .build();

String uploadBody = client.send(step1, HttpResponse.BodyHandlers.ofString()).body();
// Parse signedUrl from uploadBody with your JSON library
// uploadBody = {"signedUrl": "..."}
String signedUrl = /* parse signedUrl */;
String fileUrl = signedUrl.split("\\?", 2)[0];

// Step 2 — PUT the file (replace signedUrl with the parsed value)
HttpRequest step2 = HttpRequest.newBuilder()
    .uri(URI.create(signedUrl))
    .header("Content-Type", "audio/mpeg")
    .PUT(HttpRequest.BodyPublishers.ofFile(Path.of(filePath)))
    .build();

client.send(step2, HttpResponse.BodyHandlers.discarding()); // HTTP 200 OK

// Step 3 — Submit the edit (replace fileUrl with signedUrl stripped of its query string)
String editBody = String.format("""
    {"input":{"files":["%s"],"config":{"fillers":true}}}""", fileUrl);

HttpRequest step3 = HttpRequest.newBuilder()
    .uri(URI.create("https://api.cleanvoice.ai/v2/edits"))
    .header("X-API-Key", apiKey)
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(editBody))
    .build();

HttpResponse<String> response = client.send(step3, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body()); // {"id":"edit_abc123"}
require 'net/http'
require 'json'

api_key   = ENV['CLEANVOICE_API_KEY']
file_path = '/path/to/episode.mp3'

# Step 1 — Request a signed upload URL
uri  = URI('https://api.cleanvoice.ai/v2/upload?filename=episode.mp3')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

req = Net::HTTP::Post.new(uri)
req['X-API-Key']    = api_key

upload = JSON.parse(http.request(req).body)
# upload = {"signedUrl" => "..."}
signed_url = upload['signedUrl']
file_url   = signed_url.split('?').first

# Step 2 — PUT the file
upload_uri = URI(signed_url)
Net::HTTP.start(upload_uri.host, upload_uri.port, use_ssl: true) do |h|
  put_req = Net::HTTP::Put.new(upload_uri)
  put_req['Content-Type']   = 'audio/mpeg'
  put_req.body_stream       = File.open(file_path, 'rb')
  put_req.content_length    = File.size(file_path)
  h.request(put_req) # HTTP 200 OK
end

# Step 3 — Submit the edit
edit_req = Net::HTTP::Post.new(URI('https://api.cleanvoice.ai/v2/edits'))
edit_req['X-API-Key']    = api_key
edit_req['Content-Type'] = 'application/json'
edit_req.body = JSON.generate({
  input: { files: [file_url], config: { fillers: true } },
})

response = JSON.parse(http.request(edit_req).body)
# response = {"id" => "edit_abc123"}
puts response['id']

Uploaded files are automatically deleted 7 days after processing.


Multi-track

For interviews or multi-speaker recordings, pass multiple file URLs and set upload_type to "multitrack":

{
  "input": {
    "files": [
      "https://example.com/host.mp3",
      "https://example.com/guest.mp3"
    ],
    "upload_type": "multitrack",
    "config": { "fillers": true }
  }
}

This is multi-track, not batch processing. Multiple files are treated as separate tracks of the same recording (e.g. host mic + guest mic). To process multiple independent files, create a separate edit request for each one.

Supported formats

TypeFormats
AudioWAV, MP3, OGG, FLAC, M4A, AIFF, AAC
VideoMP4, MOV, WebM, AVI, MKV

Getting public URLs for your files

If you don't have a direct download link yet, here's how to generate one from common storage providers:

  • Dropbox — Share the file and change ?dl=0 to ?dl=1
  • Google Drive — Share as "Anyone with the link", then use https://drive.google.com/uc?export=download&id=YOUR_FILE_ID
  • S3 — Generate a pre-signed GET URL with your AWS SDK or CLI