Add text or image watermarks to any image with a single HTTP request. The same API surface also supports batch image ZIP output and authenticated video jobs. No SDK required.
The fastest way to watermark an image: send a POST request with your image and watermark config. The response body is the watermarked image binary.
curl -X POST https://apiwatermark.com/api/watermark \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "image=@photo.jpg" \
-F "type=text" \
-F "text=© 2026 MyBrand" \
-F "position=bottom-right" \
-F "opacity=0.5" \
-F "font_size=24" \
-F "output_format=webp" \
-o watermarked.webpconst form = new FormData();
form.append("image", fileInput.files[0]);
form.append("type", "text");
form.append("text", "© 2026 MyBrand");
form.append("position", "bottom-right");
form.append("opacity", "0.5");
form.append("output_format", "webp");
const res = await fetch("https://apiwatermark.com/api/watermark", {
method: "POST",
headers: { Authorization: "Bearer YOUR_API_KEY" },
body: form,
});
const blob = await res.blob();
// Save or display the watermarked imageimport requests
files = {"image": open("photo.jpg", "rb")}
data = {
"type": "text",
"text": "© 2026 MyBrand",
"position": "bottom-right",
"opacity": "0.5",
"font_size": "24",
"output_format": "webp",
}
res = requests.post(
"https://apiwatermark.com/api/watermark",
headers={"Authorization": "Bearer YOUR_API_KEY"},
files=files,
data=data,
)
with open("watermarked.webp", "wb") as f:
f.write(res.content)POST/api/watermarkAccepts multipart/form-data. Returns the watermarked image as binary with the appropriate Content-Type header.
https://apiwatermark.com/api/watermarkContent-Type: multipart/form-data · Max body size: 4.5 MB (direct) or 25 MB (via R2)
Programmatic API usage should send Authorization: Bearer aw_.... The interactive try pages can fall back to anonymous IP-based limits for image and batch demos, but video job creation and polling require authenticated ownership.
Image: API key optional, recommended for production use and monthly quota tracking.
Batch: API key optional, recommended for production use and monthly quota tracking.
Video: API key required for create, status, and download requests.
All parameters are sent as form fields in a multipart request. File fields use standard file upload. All other fields are strings (numbers as string values).
| Parameter | Type | Default | Description |
|---|---|---|---|
imagerequired | File | — | Source image file. Required unless r2_key is set. Max 25 MB for direct upload, or 4.5 MB on Vercel (use R2 for larger). |
r2_key | string | — | R2 object key from the presign endpoint. Alternative to direct upload for files > 4.5 MB. |
| Parameter | Type | Default | Description |
|---|---|---|---|
type | "text" | "image" | "text" | Watermark type. Use text for text overlays, image for logo/image overlays. |
text | string | "© Your Brand" | Watermark text. Only used when type=text. Supports dynamic variables: {{email}}, {{name}}, {{ip_address}}, {{timestamp}}. |
font_size | number | 24 | Font size in pixels. Only used when type=text. |
font_family | string | "sans-serif" | CSS font family. Only used when type=text. Note: server-side fonts are limited. |
color | string | "#ffffff" | Hex color for text watermark. Include the # prefix. |
watermark_image | File | — | Watermark image file (PNG recommended for transparency). Required when type=image. |
watermark_width | number | 150 | Width to resize the watermark image to (in pixels). Height scales proportionally. |
| Parameter | Type | Default | Description |
|---|---|---|---|
position | string | "bottom-right" | One of: "top-left", "top-center", "top-right", "center-left", "center", "center-right", "bottom-left", "bottom-center", "bottom-right", "tiled", "custom". |
custom_x | number | 0.5 | X position as 0-1 ratio (0=left, 1=right). Only used when position=custom. |
custom_y | number | 0.5 | Y position as 0-1 ratio (0=top, 1=bottom). Only used when position=custom. |
opacity | number | 0.5 | Watermark opacity. 0 = invisible, 1 = fully opaque. |
padding | number | 20 | Padding from edges in pixels. Applied to all preset positions. |
rotation | number | 0 | Rotation in degrees, -180 to 180. Applied to the watermark. |
tile_gap | number | 100 | Gap between repeated watermarks in pixels. Only used when position=tiled. |
| Parameter | Type | Default | Description |
|---|---|---|---|
output_format | "jpeg" | "png" | "webp" | "avif" | "png" | Output image format. |
output_quality | number | 90 | Compression quality, 1-100. Lower = smaller file. Only affects JPEG, WebP, AVIF. |
Set type=text and provide the text, font_size, color, and opacity fields. The text is rendered as an SVG overlay and composited onto the source image.
curl -X POST https://apiwatermark.com/api/watermark \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "image=@photo.jpg" \
-F "type=text" \
-F "text=© 2026 MyBrand" \
-F "position=bottom-right" \
-F "opacity=0.5" \
-F "font_size=24" \
-F "output_format=webp" \
-o watermarked.webpSet type=image and attach a watermark_image file (PNG with transparency works best). Control size with watermark_width.
curl -X POST https://apiwatermark.com/api/watermark \
-F "image=@photo.jpg" \
-F "type=image" \
-F "watermark_image=@logo.png" \
-F "position=center" \
-F "opacity=0.3" \
-F "watermark_width=200" \
-o branded.jpgSet position=tiled to repeat the watermark diagonally across the entire image. Use tile_gap to control spacing and rotation for the angle. Great for proof protection.
curl -X POST https://apiwatermark.com/api/watermark \
-F "image=@photo.jpg" \
-F "type=text" \
-F "text=DRAFT" \
-F "position=tiled" \
-F "opacity=0.15" \
-F "rotation=-30" \
-F "tile_gap=120" \
-F "font_size=48" \
-F "color=#ff0000" \
-o draft.pngUse POST /api/watermark/batch to process up to 20 images at once. The request accepts the same watermark fields as the single-image endpoint and returns a ZIP archive.
Accepted inputs: images[] for direct uploads or r2_keys[] for uploaded R2 objects.
curl -X POST https://apiwatermark.com/api/watermark/batch \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "images[]=@photo-1.jpg" \
-F "images[]=@photo-2.jpg" \
-F "type=text" \
-F "text=© 2026 MyBrand" \
-F "position=bottom-right" \
-o watermarked-batch.zipVideo watermarking is asynchronous. Upload the source file to R2, create a job with POST /api/video/watermark, then poll GET /api/video/watermark/[jobId] using the same API key. When the job is complete, add ?download=1 to get a direct redirect to the finished file.
# Step 1: Upload the source video via presigned URL
curl -X POST https://apiwatermark.com/api/upload/presign \
-H "Content-Type: application/json" \
-d '{"contentType":"video/mp4","fileName":"clip.mp4","fileSize":125000000}'
# Step 2: Create the video job with the same API key you will use for polling
curl -X POST https://apiwatermark.com/api/video/watermark \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sourceR2Key": "uploads/clip-123.mp4",
"sourceFileName": "clip.mp4",
"outputFormat": "mp4",
"watermarkConfig": {
"watermark": {
"type": "text",
"text": "© {{email}}",
"position": "bottom-right"
}
}
}'
# Step 3: Poll status or download when complete
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://apiwatermark.com/api/video/watermark/<jobId>
curl -L -H "Authorization: Bearer YOUR_API_KEY" \
"https://apiwatermark.com/api/video/watermark/<jobId>?download=1" \
-o watermarked.mp4Direct uploads to /api/watermark are limited to ~4.5 MB by Vercel's body size limit. For larger files (up to 25 MB), use the two-step presigned upload flow via Cloudflare R2:
/api/upload/presign with your file's MIME type and size.key as r2_key in your watermark request.POST/api/upload/presign| Field | Type | Description |
|---|---|---|
contentType | string | MIME type: image/jpeg, image/png, image/webp, image/avif, video/mp4, video/quicktime, video/webm, or application/pdf |
fileName | string | Original filename (used for extension detection) |
fileSize | number | File size in bytes (validated against 500 MB limit) |
# Step 1: Get a presigned URL
curl -X POST https://apiwatermark.com/api/upload/presign \
-H "Content-Type: application/json" \
-d '{"contentType":"image/jpeg","fileName":"large.jpg","fileSize":15000000}'
# Response: { "key": "uploads/abc123.jpg", "uploadUrl": "https://...", "expiresIn": 600 }
# Step 2: Upload directly to R2
curl -X PUT "<uploadUrl from step 1>" \
-H "Content-Type: image/jpeg" \
--data-binary @large.jpg
# Step 3: Process using R2 key
curl -X POST https://apiwatermark.com/api/watermark \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "r2_key=uploads/abc123.jpg" \
-F "type=text" \
-F "text=© MyBrand" \
-F "position=bottom-right" \
-o watermarked.jpgNote: R2 objects are automatically deleted after processing. The presigned upload URL expires after 10 minutes.
Error responses are JSON with an error field. Some 500 responses also include a details field.
| Status | Error | Cause |
|---|---|---|
| 400 | Missing required field: image | No image or r2_key provided. |
| 400 | Invalid R2 key format | The r2_key contains invalid characters or path traversal. |
| 400 | Missing required field: watermark_image | type=image but no watermark_image file. |
| 400 | Invalid content type | Presign endpoint: unsupported MIME type. |
| 404 | Source image not found in storage | R2 key references an expired or missing object. |
| 413 | File too large | Image exceeds 25 MB limit. |
| 500 | Failed to process watermark | Server error during Sharp processing. |
| 503 | R2 storage is not configured | Server missing R2 environment variables. |
{
"error": "Missing required field: image (or r2_key for R2 uploads)"
}
// For 500 errors:
{
"error": "Failed to process watermark",
"details": "Input image exceeds pixel limit"
}Alpha rate limits
Anonymous demo traffic is IP-rate-limited. Authenticated API-key traffic is governed primarily by your plan's monthly request allowance, while video job routes require authenticated ownership.
| Endpoint | Limit | Window |
|---|---|---|
| POST /api/watermark | 20 requests | 1 minute (sliding window) |
| POST /api/upload/presign | 10 requests | 1 minute (sliding window) |
| POST /api/watermark/batch | 5 requests | 1 minute (sliding window) |
| POST /api/video/watermark | API key required | Plan-based monthly usage |
When rate limited, the API returns a 429 Too Many Requests response with Retry-After and X-RateLimit-* headers.
A machine-readable OpenAPI 3.1 spec is available at /api/openapi.json. Use it to generate client SDKs, import into Postman, or integrate with any OpenAPI-compatible tooling.
curl https://apiwatermark.com/api/openapi.json | jq .Try the interactive editor or start making API calls right away.