API Reference

API Documentation

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.

60s timeout25 MB maxREST / multipartBearer auth for API access

#Quick Start

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.

Text watermark
bash
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.webp
JavaScript
js
const 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 image
Python
python
import 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/watermark

Accepts multipart/form-data. Returns the watermarked image as binary with the appropriate Content-Type header.

POSThttps://apiwatermark.com/api/watermark

Content-Type: multipart/form-data · Max body size: 4.5 MB (direct) or 25 MB (via R2)

#Authentication

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.

#Parameters

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).

Source Image

ParameterTypeDefaultDescription
imagerequiredFileSource 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_keystringR2 object key from the presign endpoint. Alternative to direct upload for files > 4.5 MB.

Watermark Config

ParameterTypeDefaultDescription
type"text" | "image""text"Watermark type. Use text for text overlays, image for logo/image overlays.
textstring"© Your Brand"Watermark text. Only used when type=text. Supports dynamic variables: {{email}}, {{name}}, {{ip_address}}, {{timestamp}}.
font_sizenumber24Font size in pixels. Only used when type=text.
font_familystring"sans-serif"CSS font family. Only used when type=text. Note: server-side fonts are limited.
colorstring"#ffffff"Hex color for text watermark. Include the # prefix.
watermark_imageFileWatermark image file (PNG recommended for transparency). Required when type=image.
watermark_widthnumber150Width to resize the watermark image to (in pixels). Height scales proportionally.

Position & Layout

ParameterTypeDefaultDescription
positionstring"bottom-right"One of: "top-left", "top-center", "top-right", "center-left", "center", "center-right", "bottom-left", "bottom-center", "bottom-right", "tiled", "custom".
custom_xnumber0.5X position as 0-1 ratio (0=left, 1=right). Only used when position=custom.
custom_ynumber0.5Y position as 0-1 ratio (0=top, 1=bottom). Only used when position=custom.
opacitynumber0.5Watermark opacity. 0 = invisible, 1 = fully opaque.
paddingnumber20Padding from edges in pixels. Applied to all preset positions.
rotationnumber0Rotation in degrees, -180 to 180. Applied to the watermark.
tile_gapnumber100Gap between repeated watermarks in pixels. Only used when position=tiled.

Output

ParameterTypeDefaultDescription
output_format"jpeg" | "png" | "webp" | "avif""png"Output image format.
output_qualitynumber90Compression quality, 1-100. Lower = smaller file. Only affects JPEG, WebP, AVIF.

#Text Watermark Example

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.

Text watermark
bash
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.webp

#Image Watermark Example

Set type=image and attach a watermark_image file (PNG with transparency works best). Control size with watermark_width.

Image watermark (logo)
bash
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.jpg

#Tiled / Repeated Watermark

Set 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.

Tiled watermark
bash
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.png

#Batch Processing

Use 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.

Batch watermark ZIP
bash
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.zip

#Video Jobs

Video 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.

Video job flow
bash
# 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.mp4

#Large Files (R2 Upload)

Direct 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:

  1. Get a presigned URL — POST to /api/upload/presign with your file's MIME type and size.
  2. Upload directly to R2 — PUT the file to the returned URL. The file goes directly to Cloudflare R2, bypassing Vercel.
  3. Process — Send the returned key as r2_key in your watermark request.

#POST/api/upload/presign

FieldTypeDescription
contentTypestringMIME type: image/jpeg, image/png, image/webp, image/avif, video/mp4, video/quicktime, video/webm, or application/pdf
fileNamestringOriginal filename (used for extension detection)
fileSizenumberFile size in bytes (validated against 500 MB limit)
Full R2 upload flow
bash
# 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.jpg

Note: R2 objects are automatically deleted after processing. The presigned upload URL expires after 10 minutes.

#Error Codes

Error responses are JSON with an error field. Some 500 responses also include a details field.

StatusErrorCause
400Missing required field: imageNo image or r2_key provided.
400Invalid R2 key formatThe r2_key contains invalid characters or path traversal.
400Missing required field: watermark_imagetype=image but no watermark_image file.
400Invalid content typePresign endpoint: unsupported MIME type.
404Source image not found in storageR2 key references an expired or missing object.
413File too largeImage exceeds 25 MB limit.
500Failed to process watermarkServer error during Sharp processing.
503R2 storage is not configuredServer missing R2 environment variables.
Error response format
json
{
  "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"
}

#Rate Limits

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.

EndpointLimitWindow
POST /api/watermark20 requests1 minute (sliding window)
POST /api/upload/presign10 requests1 minute (sliding window)
POST /api/watermark/batch5 requests1 minute (sliding window)
POST /api/video/watermarkAPI key requiredPlan-based monthly usage

When rate limited, the API returns a 429 Too Many Requests response with Retry-After and X-RateLimit-* headers.

#OpenAPI Specification

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.

Fetch the OpenAPI spec
bash
curl https://apiwatermark.com/api/openapi.json | jq .

Ready to watermark?

Try the interactive editor or start making API calls right away.