Developers
Watermark API
Watermark and trace audio from your own code. Two endpoints, one bearer key, one credit per call. Same engine as the app.
Documentation reviewed 26 July 2026 by Berg Capital ehf.
Overview
The API embeds an inaudible, recipient-unique signature into an audio file, and later identifies which signature a leaked file carries. Use it to sign promos before they go out, or to check a file that turned up somewhere it shouldn't.
- Base URL:
https://listeningcopy.com - All requests use HTTPS. Files are sent as
multipart/form-data. - Watermarked output is always MP3 320. A signed file is a listening copy, not a master.
- Files are processed in memory and not retained. Only the signature id and your label are stored, so trace can name a match.
Authentication
Create a key in the Developers console. Send it as a bearer token on every request. Keep it secret; it spends your credits.
Authorization: Bearer lc_live_xxxxxxxxxxxxxxxxxxxxxxxxA missing or invalid key returns 401. Revoke a key any time in the console.
Credits & limits
Each watermark and each trace costs 1 credit. Your plan's monthly allowance is spent first, then purchased credits (which never expire). When you run out, calls return 402 and we email you a heads-up before that happens.
Check your balance any time with GET /v1/account. Keep uploads under 2 GB.
Watermark a file
POST /v1/watermark
Upload an audio file and a label identifying the recipient. Returns the signed MP3 and the signature id.
| Field | Type | Notes |
|---|---|---|
file | file | Required. WAV, FLAC, AIFF or MP3. |
label | text | Optional. Who this copy is for, e.g. "Jane Doe, radio promo". Returned by trace. |
The response body is the MP3. The signature id comes back in the X-Watermark-Id header, store it if you want to look it up later.
curl -X POST https://listeningcopy.com/v1/watermark \
-H "Authorization: Bearer lc_live_..." \
-F "file=@mix.wav" \
-F "label=Jane Doe, radio promo" \
-o watermarked.mp3 -D -Trace a file
POST /v1/trace
Upload a suspect file. If it carries one of your signatures, you get back the id and the label it was signed with. Add ?deep=1 for a slower, more robust scan that recovers signatures from heavily degraded files.
curl -X POST https://listeningcopy.com/v1/trace \
-H "Authorization: Bearer lc_live_..." \
-F "file=@leaked.mp3"Response:
{
"matched": true,
"results": [
{ "id": "a4f5ecf5a5918a644435c6b0f89c0f5a",
"label": "Jane Doe, radio promo",
"watermarkedAt": 1784923715799 }
]
}No match returns { "matched": false, "results": [] }. A trace still costs 1 credit whether or not it matches.
Account
GET /v1/account
Your current credit balance and total watermarks.
curl https://listeningcopy.com/v1/account -H "Authorization: Bearer lc_live_..."
# { "credits": 380, "watermarked": 214 }Errors
| Code | Meaning |
|---|---|
400 | No file attached, or an unknown request. |
401 | Missing or invalid API key. |
402 | Out of credits. Top up in the console. |
413 | File too large (over 2 GB). |
Examples
Node
import { readFileSync } from "node:fs";
const form = new FormData();
form.set("file", new Blob([readFileSync("mix.wav")]), "mix.wav");
form.set("label", "Jane Doe, radio promo");
const res = await fetch("https://listeningcopy.com/v1/watermark", {
method: "POST",
headers: { Authorization: "Bearer lc_live_..." },
body: form,
});
const id = res.headers.get("X-Watermark-Id");
const mp3 = Buffer.from(await res.arrayBuffer());Python
import requests
with open("mix.wav", "rb") as f:
r = requests.post(
"https://listeningcopy.com/v1/watermark",
headers={"Authorization": "Bearer lc_live_..."},
files={"file": f},
data={"label": "Jane Doe, radio promo"},
)
open("watermarked.mp3", "wb").write(r.content)
print(r.headers["X-Watermark-Id"])Questions? hello@listeningcopy.com · Manage keys in the console.