Remove watermarks from images, PDFs, and videos via REST API. Authenticate with an API key and pay per request using your API credit balance.
Base URL
https://erasewatermark.io
Protocol
HTTPS only
Format
multipart/form-data
Pass your API key as a Bearer token in the Authorization header of every request. API keys start with wm_. You can create and revoke keys in the API Keys dashboard.
Authorization: Bearer wm_your_api_key_here
All endpoints are under https://erasewatermark.io/api/v1/watermark.
/api/v1/watermark/imageRemove watermark from an image. AI auto-detects the watermark.
Cost: 1 creditRequest body (multipart/form-data)
filerequired | file | Image file. Supported: JPG, PNG, WEBP, AVIF. Max 10 MB, 6000×6000 px. |
curl -X POST https://erasewatermark.io/api/v1/watermark/image \ -H "Authorization: Bearer wm_your_key" \ -F "[email protected]"
/api/v1/watermark/imageRemove watermark guided by a brush mask you draw over the watermark area.
Cost: 1 creditRequest body (multipart/form-data)
filerequired | file | Image file (same formats as auto). |
maskrequired | file | RGBA PNG mask at the same dimensions as the image. Drawn pixels must be (0,0,0,255) opaque black; undrawn pixels transparent (alpha=0). |
mask_brushrequired | file | Identical to mask — send the same PNG file for both fields. |
Headers
x-api-moderequired | string | Set to "MANUAL" to activate brush-guided removal. |
curl -X POST https://erasewatermark.io/api/v1/watermark/image \ -H "Authorization: Bearer wm_your_key" \ -H "x-api-mode: MANUAL" \ -F "[email protected]" \ -F "[email protected]" \ -F "[email protected]"
/api/v1/watermark/pdfasyncRemove watermarks from a PDF. Processing is asynchronous — poll task status.
Cost: 1 credit per pageRequest body
filerequired | file | PDF file. Max 50 MB. |
/api/v1/watermark/videoasyncRemove watermarks from a video. Processing is asynchronous.
Cost: 1 creditRequest body
filerequired | file | Video file. Supported: MP4, MOV, WEBM, AVI, MKV. Max 500 MB. |
All three endpoints return a task_id. Poll this endpoint until status is completed or failed. Image jobs complete synchronously (no polling needed in most cases).
/api/v1/watermark/tasks/{task_id}Poll processing status. Returns progress and download URL on completion.
Cost: freeResponse fields
status | string | "pending" | "processing" | "completed" | "failed" |
progress | number | 0–100 percentage (video only). |
download_url | string | Present when status = completed. Relative path — prepend base URL. |
file_id | string | Stable ID for the processed file. |
error | string | Error message when status = failed. |
curl https://erasewatermark.io/api/v1/watermark/tasks/abc123 \ -H "Authorization: Bearer wm_your_key"
/api/v1/watermark/download/{file_id}Stream the processed file. Add ?inline=true to serve inline instead of as attachment.
Cost: freecurl -O -J https://erasewatermark.io/api/v1/watermark/download/abc123 \ -H "Authorization: Bearer wm_your_key"
| Operation | Credits consumed |
|---|---|
| Image (auto or manual) | 1 |
| 1 per page | |
| Video | 1 |
| Task status poll | 0 |
| Download | 0 |
Credits are deducted when processing begins. Credits for failed jobs are returned to your balance automatically.
| HTTP status | Meaning |
|---|---|
| 200 | Success |
| 400 | Bad request — invalid file, unsupported format, or missing field |
| 401 | Unauthorized — missing or invalid API key |
| 402 | Insufficient API credits |
| 403 | Forbidden — you do not own this task |
| 404 | Task or file not found |
| 429 | Rate limited (anonymous users only) |
| 500 | Internal server error — please retry |
All error responses include a detail field with a human-readable message.
import requests, time
API_KEY = "wm_your_api_key"
BASE = "https://erasewatermark.io"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
# Remove watermark from an image (auto)
with open("photo.jpg", "rb") as f:
r = requests.post(f"{BASE}/api/v1/watermark/image",
headers=HEADERS, files={"file": f})
r.raise_for_status()
task_id = r.json()["task_id"]
# Poll until done
while True:
status = requests.get(f"{BASE}/api/v1/watermark/tasks/{task_id}",
headers=HEADERS).json()
if status["status"] == "completed":
break
if status["status"] == "failed":
raise Exception(status["error"])
time.sleep(2)
# Download result
dl = requests.get(f"{BASE}{status['download_url']}", headers=HEADERS)
with open("result.jpg", "wb") as f:
f.write(dl.content)
print("Done! Saved result.jpg")import fs from 'fs';
import FormData from 'form-data';
import fetch from 'node-fetch';
const API_KEY = 'wm_your_api_key';
const BASE = 'https://erasewatermark.io';
const headers = { Authorization: `Bearer ${API_KEY}` };
// Submit image
const form = new FormData();
form.append('file', fs.createReadStream('photo.jpg'));
const { task_id } = await fetch(`${BASE}/api/v1/watermark/image`,
{ method: 'POST', headers: { ...headers, ...form.getHeaders() }, body: form }
).then(r => r.json());
// Poll
let status;
do {
await new Promise(r => setTimeout(r, 2000));
status = await fetch(`${BASE}/api/v1/watermark/tasks/${task_id}`,
{ headers }).then(r => r.json());
} while (!['completed','failed'].includes(status.status));
if (status.status === 'failed') throw new Error(status.error);
// Download
const buf = await fetch(`${BASE}${status.download_url}`, { headers })
.then(r => r.arrayBuffer());
fs.writeFileSync('result.jpg', Buffer.from(buf));
console.log('Done!');