CleanvoiceDocs
REST API

Quick Start

Make your first API call — examples in Shell, PHP, Java, and Ruby.

Use this pre-built prompt to get started faster.

Get your API key

Go to app.cleanvoice.ai/developer/api-keys and create a new key.

export CLEANVOICE_API_KEY="your_api_key_here"

Try it with your key

Try with your API key

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

The Cleanvoice REST API works with any HTTP client. Choose your language below — all examples use only standard library or widely-available HTTP clients.

Submit an edit

If your file is already online, pass the URL directly:

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,
        "long_silences": true,
        "normalize": true
      }
    }
  }'
<?php
$payload = json_encode([
    'input' => [
        'files'  => ['https://example.com/episode.mp3'],
        'config' => ['fillers' => true, 'long_silences' => true, 'normalize' => true],
    ],
]);

$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 => $payload,
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
echo $response['id']; // edit_abc123
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, "long_silences": true, "normalize": 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()); // {"id":"edit_abc123"}
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, long_silences: true, normalize: true },
  },
})

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

Save the id — you'll use it to poll for the result in the next step.

If your file is local, upload it first — see Uploads for full language examples. The three-step flow is:

  1. POST /v2/upload?filename=... → get signedUrl
  2. PUT your file to signedUrl
  3. Strip the query string from signedUrl, then submit the edit using that file URL

Poll for the result

#!/bin/bash
EDIT_ID="edit_abc123"

sleep 30  # wait before first poll

while true; do
  RESPONSE=$(curl -s "https://api.cleanvoice.ai/v2/edits/$EDIT_ID" \
    -H "X-API-Key: $CLEANVOICE_API_KEY")

  STATUS=$(echo "$RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['status'])")
  echo "Status: $STATUS"

  if [ "$STATUS" = "SUCCESS" ]; then
    URL=$(echo "$RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['result']['download_url'])")
    echo "Download URL: $URL"
    break
  elif [ "$STATUS" = "FAILURE" ]; then
    echo "Edit failed"
    break
  fi

  sleep 10
done
<?php
$editId = 'edit_abc123';
$apiKey = getenv('CLEANVOICE_API_KEY');

sleep(30); // wait before first poll

while (true) {
    $ch = curl_init("https://api.cleanvoice.ai/v2/edits/$editId");
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => ["X-API-Key: $apiKey"],
    ]);
    $data = json_decode(curl_exec($ch), true);
    curl_close($ch);

    echo 'Status: ' . $data['status'] . PHP_EOL;

    if ($data['status'] === 'SUCCESS') {
        echo 'Download URL: ' . $data['result']['download_url'] . PHP_EOL;
        break;
    } elseif ($data['status'] === 'FAILURE') {
        echo 'Edit failed' . PHP_EOL;
        break;
    }

    sleep(10);
}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

String apiKey = System.getenv("CLEANVOICE_API_KEY");
String editId = "edit_abc123";
HttpClient client = HttpClient.newHttpClient();

Thread.sleep(30_000); // wait before first poll

while (true) {
    HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://api.cleanvoice.ai/v2/edits/" + editId))
        .header("X-API-Key", apiKey)
        .GET()
        .build();

    String body = client.send(request, HttpResponse.BodyHandlers.ofString()).body();
    System.out.println(body);

    if (body.contains("\"SUCCESS\"")) {
        // extract download_url with your preferred JSON library
        break;
    } else if (body.contains("\"FAILURE\"")) {
        break;
    }

    Thread.sleep(10_000);
}
require 'net/http'
require 'json'

edit_id = 'edit_abc123'
api_key = ENV['CLEANVOICE_API_KEY']
uri     = URI("https://api.cleanvoice.ai/v2/edits/#{edit_id}")

sleep 30 # wait before first poll

loop do
  request = Net::HTTP::Get.new(uri)
  request['X-API-Key'] = api_key

  data = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
    JSON.parse(http.request(request).body)
  end

  puts "Status: #{data['status']}"

  if data['status'] == 'SUCCESS'
    puts "Download URL: #{data.dig('result', 'download_url')}"
    break
  elsif data['status'] == 'FAILURE'
    puts 'Edit failed'
    break
  end

  sleep 10
end
{
  "task_id": "edit_abc123",
  "status": "SUCCESS",
  "result": {
    "download_url": "https://cdn.cleanvoice.ai/cleaned/edit_abc123.mp3",
    "transcription": null,
    "summarization": null,
    "social_content": []
  }
}

result.download_url is a time-limited link to the cleaned audio file. Download it promptly after the job succeeds.

If you enable text outputs, the raw polling payload also uses result.transcription, result.summarization, and result.social_content. A recent live social_content=true run returned social_content as an object with newsletter, twitter_thread, and linkedin, and remained in POSTPROCESSING for several polls before SUCCESS.

Processing typically takes ~30 seconds for a 2–3 minute clip and 5–10 minutes for a 1-hour file. Wait at least 30 seconds before your first poll, then poll every 10 seconds.

Common presets

Noise reduction and loudness normalization — language-agnostic.

{
  "input": {
    "files": ["https://example.com/episode.mp3"],
    "config": {
      "remove_noise": true,
      "studio_sound": true,
      "normalize": true
    }
  }
}

Full spoken-word cleanup: fillers, silences, mouth sounds, breathing, stutters.

{
  "input": {
    "files": ["https://example.com/episode.mp3"],
    "config": {
      "fillers": true,
      "long_silences": true,
      "mouth_sounds": true,
      "breath": true,
      "stutters": true,
      "remove_noise": true,
      "normalize": true
    }
  }
}

Pass a video URL and set video to true — otherwise the file is treated as audio-only.

{
  "input": {
    "files": ["https://example.com/video.mp4"],
    "config": {
      "video": true,
      "remove_noise": true,
      "studio_sound": true,
      "normalize": true
    }
  }
}

Minimal processing — normalize loudness and trim excessive silences.

{
  "input": {
    "files": ["https://example.com/episode.mp3"],
    "config": {
      "long_silences": true,
      "normalize": true
    }
  }
}

Deliver results to your storage

Pass a pre-signed PUT URL in signed_url and Cleanvoice will upload the cleaned file directly to your bucket.

{
  "input": {
    "files": ["https://example.com/episode.mp3"],
    "config": {
      "signed_url": "https://your-bucket.s3.amazonaws.com/cleaned.mp3?X-Amz-Signature=...",
      "fillers": true,
      "normalize": true
    }
  }
}