API reference
Every endpoint of the Watermark API v1, with its parameters, a request and what comes back.
All endpoints are under https://api.watermark.ws. Send JSON bodies and your key in the Authorization header (see Authentication). Processing endpoints answer 202 and finish in the background; read the request or listen for its webhook.
https://api.watermark.ws
curl https://api.watermark.ws/v1/account \
-H "Authorization: Bearer wm_test_…"
The request object
What every processing endpoint returns, and what GET /v1/requests/{id} and webhooks carry.
Fields
The request's id.
Always request.
Which endpoint started it, such as images/process.
Where the request is. Only succeeded is charged.
queuedprocessingsucceededfailedabortedfalse for a test key's request.
The URL the file was downloaded from.
What was sent besides the source: operations, output and the endpoint's own fields.
How far a video has got while it is processing.
Once succeeded.
Show 3 child fields
About the output: dimensions and format for images, duration and codec for videos, pages and size for PDFs.
The files made. Read endpoints make none.
Show 4 child fields
A signed link to download the file.
When the link stops working: when the result is deleted.
The file format, such as webp.
Bytes.
When the files are deleted, 7 days after the request finished.
Once failed. See Errors.
Show 2 child fields
Stable; branch on it.
For people.
An amount of money.
Show 3 child fields
Dollars, six decimals.
Always usd.
Millionths of a dollar.
As sent.
As sent.
When the request was accepted.
When it finished.
{
"id": 1234,
"object": "request",
"endpoint": "images/process",
"status": "succeeded",
"livemode": true,
"source": "https://example.com/photo.jpg",
"result": {
"data": {
"width": 1600,
"height": 1067,
"format": "WEBP",
"size": 183021
},
"files": [
{
"url": "https://api.watermark.ws/v1/results/1234?expires=1792335604&signature=…",
"url_expires_at": "2026-10-18T15:00:04Z",
"format": "webp",
"size": 183021
}
],
"deleted_at": "2026-10-18T15:00:04Z"
},
"price": {
"amount": "0.002000",
"currency": "usd",
"micro_usd": 2000
},
"customer": "cus_42",
"created_at": "2026-10-11T15:00:00Z",
"completed_at": "2026-10-11T15:00:04Z"
}
The event object
Posted to webhook_url when a request finishes, signed in the Watermark-Signature header. Webhooks shows how to check it.
Fields
The same on every retry of a delivery. Use it to ignore a repeat.
Always event.
What happened.
request.succeededrequest.failedrequest.abortedUnix time.
{
"id": "evt_1234_done",
"object": "event",
"type": "request.succeeded",
"created": 1791738000,
"data": {
"id": 1234,
"object": "request",
"status": "succeeded",
"result": {
"…": "…"
}
}
}
Errors
Every error has one shape, and an HTTP status that matches its type. Branch on code; show message to people. Errors lists every code.
| Status | Meaning |
|---|---|
| 401 | No key, or not a valid one. |
| 402 | No credit left, or the endpoint needs an account that has paid. |
| 404 | Not found on this account. |
| 409 | A conflict: the request has finished, or the Idempotency-Key was used for another request. |
| 422 | Invalid input, or a URL the API will not use. |
| 429 | Too many requests a minute (rate_limited, see Retry-After), or too many of the account's requests running at once (concurrency_limited). |
{
"error": {
"type": "invalid_request",
"code": "validation_failed",
"message": "The operations.0.width field must be between 1 and 20000.",
"param": "operations.0.width",
"request_id": "1234"
}
}
Images
JPEG, PNG, GIF, WebP, TIFF, BMP, HEIC and AVIF in; JPEG, PNG, WebP, GIF or TIFF out.
Process image
Run operations on an image, in order, and save the result.
Headers
Bearer wm_live_… or Bearer wm_test_…. See Authentication.
Makes a retry safe: the same key with the same body returns the first request instead of starting another. See Idempotency.
Body parameters
A public http or https URL of the file. See Sources.
Run in order, top to bottom. Each is {"type": …} plus that operation's parameters. See Operations.
How to save the result.
Show child field
The file format of the result.
jpgpngwebpgiftifWhere to post the request when it finishes. Defaults to the account's webhook, if set. See Webhooks.
Your own reference for who the request is for. Returned with it, and totalled by GET /v1/usage?group_by=customer.
Up to 20 keys of your own, returned with the request and in its webhooks.
curl -X POST https://api.watermark.ws/v1/images/process \
-H "Authorization: Bearer $WATERMARK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": "https://example.com/photo.jpg",
"operations": [
{"type": "resize", "width": 1600, "height": 1600},
{"type": "watermark", "image_url": "https://example.com/logo.png", "x": 40, "y": 40}
],
"output": {"format": "webp"},
"customer": "cus_42"
}'
$client = new \Watermark\Api\Client(getenv('WATERMARK_API_KEY'));
$request = $client->images->process('https://example.com/photo.jpg', [
['type' => 'resize', 'width' => 1600, 'height' => 1600],
['type' => 'watermark', 'image_url' => 'https://example.com/logo.png', 'x' => 40, 'y' => 40],
], [
'output' => ['format' => 'webp'],
'customer' => 'cus_42',
]);
# Waits for the result; add --no-wait to return as soon as it is queued.
watermark image process https://example.com/photo.jpg \
--op resize:width=1600,height=1600 \
--op watermark:image_url=https://example.com/logo.png,x=40,y=40 \
--format webp --customer cus_42 --out photo-watermarked.webp
{
"id": 1234,
"object": "request",
"endpoint": "images/process",
"status": "queued",
"livemode": true,
"source": "https://example.com/photo.jpg",
"created_at": "2026-10-11T15:00:00Z"
}
Read image
Read dimensions, format, colorspace and orientation.
Headers
Bearer wm_live_… or Bearer wm_test_…. See Authentication.
Makes a retry safe: the same key with the same body returns the first request instead of starting another. See Idempotency.
Body parameters
A public http or https URL of the file. See Sources.
Where to post the request when it finishes. Defaults to the account's webhook, if set. See Webhooks.
Your own reference for who the request is for. Returned with it, and totalled by GET /v1/usage?group_by=customer.
Up to 20 keys of your own, returned with the request and in its webhooks.
curl -X POST https://api.watermark.ws/v1/images/read \
-H "Authorization: Bearer $WATERMARK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": "https://example.com/photo.jpg"
}'
$client = new \Watermark\Api\Client(getenv('WATERMARK_API_KEY'));
$request = $client->images->read('https://example.com/photo.jpg');
# Waits for the result; add --no-wait to return as soon as it is queued.
watermark image read https://example.com/photo.jpg
{
"id": 1234,
"object": "request",
"endpoint": "images/read",
"status": "queued",
"livemode": true,
"source": "https://example.com/photo.jpg",
"created_at": "2026-10-11T15:00:00Z"
}
{
"frames": 1,
"orientation": "Undefined",
"format": "JPEG",
"colorspace": "sRGB",
"width": 900,
"height": 600,
"size": 97451
}
Videos
MP4, MOV, WebM, MKV, AVI, FLV, MPEG and MPEG-TS in; MP4 out. Needs an account that has bought credits.
Process video
Run operations on a video, in order, and save an MP4. Reports progress while it runs.
Headers
Bearer wm_live_… or Bearer wm_test_…. See Authentication.
Makes a retry safe: the same key with the same body returns the first request instead of starting another. See Idempotency.
Body parameters
A public http or https URL of the file. See Sources.
Run in order, top to bottom. Each is {"type": …} plus that operation's parameters. See Operations.
How to save the result.
Show child field
The file format of the result.
mp4Only keep the first this many seconds.
Where to post the request when it finishes. Defaults to the account's webhook, if set. See Webhooks.
Your own reference for who the request is for. Returned with it, and totalled by GET /v1/usage?group_by=customer.
Up to 20 keys of your own, returned with the request and in its webhooks.
curl -X POST https://api.watermark.ws/v1/videos/process \
-H "Authorization: Bearer $WATERMARK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": "https://example.com/clip.mp4",
"operations": [
{"type": "watermark", "image_url": "https://example.com/logo.png", "x": 24, "y": 24}
],
"webhook_url": "https://example.com/hooks/watermark"
}'
$client = new \Watermark\Api\Client(getenv('WATERMARK_API_KEY'));
$request = $client->videos->process('https://example.com/clip.mp4', [
['type' => 'watermark', 'image_url' => 'https://example.com/logo.png', 'x' => 24, 'y' => 24],
], [
'webhook_url' => 'https://example.com/hooks/watermark',
]);
# Waits for the result; add --no-wait to return as soon as it is queued.
watermark video process https://example.com/clip.mp4 \
--op watermark:image_url=https://example.com/logo.png,x=24,y=24 \
--webhook https://example.com/hooks/watermark --out clip-watermarked.mp4
{
"id": 1234,
"object": "request",
"endpoint": "videos/process",
"status": "queued",
"livemode": true,
"source": "https://example.com/clip.mp4",
"created_at": "2026-10-11T15:00:00Z"
}
Read video
Read dimensions, duration, codec, bitrate and frame rate.
Headers
Bearer wm_live_… or Bearer wm_test_…. See Authentication.
Makes a retry safe: the same key with the same body returns the first request instead of starting another. See Idempotency.
Body parameters
A public http or https URL of the file. See Sources.
Where to post the request when it finishes. Defaults to the account's webhook, if set. See Webhooks.
Your own reference for who the request is for. Returned with it, and totalled by GET /v1/usage?group_by=customer.
Up to 20 keys of your own, returned with the request and in its webhooks.
curl -X POST https://api.watermark.ws/v1/videos/read \
-H "Authorization: Bearer $WATERMARK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": "https://example.com/clip.mp4"
}'
$client = new \Watermark\Api\Client(getenv('WATERMARK_API_KEY'));
$request = $client->videos->read('https://example.com/clip.mp4');
# Waits for the result; add --no-wait to return as soon as it is queued.
watermark video read https://example.com/clip.mp4
{
"id": 1234,
"object": "request",
"endpoint": "videos/read",
"status": "queued",
"livemode": true,
"source": "https://example.com/clip.mp4",
"created_at": "2026-10-11T15:00:00Z"
}
Screenshot video
Save one frame as an image.
Headers
Bearer wm_live_… or Bearer wm_test_…. See Authentication.
Makes a retry safe: the same key with the same body returns the first request instead of starting another. See Idempotency.
Body parameters
A public http or https URL of the file. See Sources.
Which moment, in seconds from the start.
How to save the result.
Show child field
The file format of the result.
jpgpngWhere to post the request when it finishes. Defaults to the account's webhook, if set. See Webhooks.
Your own reference for who the request is for. Returned with it, and totalled by GET /v1/usage?group_by=customer.
Up to 20 keys of your own, returned with the request and in its webhooks.
curl -X POST https://api.watermark.ws/v1/videos/screenshot \
-H "Authorization: Bearer $WATERMARK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": "https://example.com/clip.mp4",
"seconds": 5,
"output": {
"format": "jpg"
}
}'
$client = new \Watermark\Api\Client(getenv('WATERMARK_API_KEY'));
$request = $client->videos->screenshot('https://example.com/clip.mp4', 5, [
'output' => ['format' => 'jpg'],
]);
# Waits for the result; add --no-wait to return as soon as it is queued.
watermark video screenshot https://example.com/clip.mp4 \
--format jpg --seconds 5 --out clip-frame.jpg
{
"id": 1234,
"object": "request",
"endpoint": "videos/screenshot",
"status": "queued",
"livemode": true,
"source": "https://example.com/clip.mp4",
"created_at": "2026-10-11T15:00:00Z"
}
Documents
PDF in and out.
Process document
Run operations on every page of a PDF.
Headers
Bearer wm_live_… or Bearer wm_test_…. See Authentication.
Makes a retry safe: the same key with the same body returns the first request instead of starting another. See Idempotency.
Body parameters
A public http or https URL of the file. See Sources.
Run in order, top to bottom. Each is {"type": …} plus that operation's parameters. See Operations.
Only keep the first this many pages.
Where to post the request when it finishes. Defaults to the account's webhook, if set. See Webhooks.
Your own reference for who the request is for. Returned with it, and totalled by GET /v1/usage?group_by=customer.
Up to 20 keys of your own, returned with the request and in its webhooks.
curl -X POST https://api.watermark.ws/v1/documents/process \
-H "Authorization: Bearer $WATERMARK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": "https://example.com/contract.pdf",
"operations": [
{"type": "watermark", "image_url": "https://example.com/logo.png", "x": 36, "y": 36, "diagonal_lines_color": "#c0c0c0", "diagonal_lines_opacity": 40}
]
}'
$client = new \Watermark\Api\Client(getenv('WATERMARK_API_KEY'));
$request = $client->documents->process('https://example.com/contract.pdf', [
['type' => 'watermark', 'image_url' => 'https://example.com/logo.png', 'x' => 36, 'y' => 36, 'diagonal_lines_color' => '#c0c0c0', 'diagonal_lines_opacity' => 40],
]);
# Waits for the result; add --no-wait to return as soon as it is queued.
watermark document process https://example.com/contract.pdf \
--op watermark:image_url=https://example.com/logo.png,x=36,y=36,diagonal_lines_color=#c0c0c0,diagonal_lines_opacity=40 \
--out contract-watermarked.pdf
{
"id": 1234,
"object": "request",
"endpoint": "documents/process",
"status": "queued",
"livemode": true,
"source": "https://example.com/contract.pdf",
"created_at": "2026-10-11T15:00:00Z"
}
Read document
Read the page count and page size.
Headers
Bearer wm_live_… or Bearer wm_test_…. See Authentication.
Makes a retry safe: the same key with the same body returns the first request instead of starting another. See Idempotency.
Body parameters
A public http or https URL of the file. See Sources.
Where to post the request when it finishes. Defaults to the account's webhook, if set. See Webhooks.
Your own reference for who the request is for. Returned with it, and totalled by GET /v1/usage?group_by=customer.
Up to 20 keys of your own, returned with the request and in its webhooks.
curl -X POST https://api.watermark.ws/v1/documents/read \
-H "Authorization: Bearer $WATERMARK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": "https://example.com/contract.pdf"
}'
$client = new \Watermark\Api\Client(getenv('WATERMARK_API_KEY'));
$request = $client->documents->read('https://example.com/contract.pdf');
# Waits for the result; add --no-wait to return as soon as it is queued.
watermark document read https://example.com/contract.pdf
{
"id": 1234,
"object": "request",
"endpoint": "documents/read",
"status": "queued",
"livemode": true,
"source": "https://example.com/contract.pdf",
"created_at": "2026-10-11T15:00:00Z"
}
Snapshot document
Render one page as an image.
Headers
Bearer wm_live_… or Bearer wm_test_…. See Authentication.
Makes a retry safe: the same key with the same body returns the first request instead of starting another. See Idempotency.
Body parameters
A public http or https URL of the file. See Sources.
Which page, from 1.
How to save the result.
Show child field
The file format of the result.
jpgpngWhere to post the request when it finishes. Defaults to the account's webhook, if set. See Webhooks.
Your own reference for who the request is for. Returned with it, and totalled by GET /v1/usage?group_by=customer.
Up to 20 keys of your own, returned with the request and in its webhooks.
curl -X POST https://api.watermark.ws/v1/documents/snapshot \
-H "Authorization: Bearer $WATERMARK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": "https://example.com/contract.pdf",
"page": 1,
"output": {
"format": "png"
}
}'
$client = new \Watermark\Api\Client(getenv('WATERMARK_API_KEY'));
$request = $client->documents->snapshot('https://example.com/contract.pdf', 1, [
'output' => ['format' => 'png'],
]);
# Waits for the result; add --no-wait to return as soon as it is queued.
watermark document snapshot https://example.com/contract.pdf \
--format png --page 1 --out contract-page.png
{
"id": 1234,
"object": "request",
"endpoint": "documents/snapshot",
"status": "queued",
"livemode": true,
"source": "https://example.com/contract.pdf",
"created_at": "2026-10-11T15:00:00Z"
}
Requests
Follow, list and abort requests.
List requests
The account's requests, newest first.
Query parameters
How many requests to return.
Only requests older than this id. Pass the last id of a page to get the next one.
curl "https://api.watermark.ws/v1/requests?limit=20" \
-H "Authorization: Bearer $WATERMARK_API_KEY"
$client = new \Watermark\Api\Client(getenv('WATERMARK_API_KEY'));
$requests = $client->requests->list(20);
watermark requests --limit 20
{
"object": "list",
"data": [
{
"id": 1234,
"object": "request",
"endpoint": "images/process",
"status": "succeeded",
"livemode": true,
"created_at": "2026-10-11T15:00:00Z"
}
],
"has_more": true
}
Get request
One request, with its result once it has succeeded. Download links in the result work until the result is deleted.
Path parameters
The request's id.
curl https://api.watermark.ws/v1/requests/1234 \
-H "Authorization: Bearer $WATERMARK_API_KEY"
$client = new \Watermark\Api\Client(getenv('WATERMARK_API_KEY'));
$request = $client->requests->get(1234);
watermark request get 1234
{
"id": 1234,
"object": "request",
"endpoint": "images/process",
"status": "succeeded",
"livemode": true,
"source": "https://example.com/photo.jpg",
"result": {
"data": {
"width": 1600,
"height": 1067,
"format": "WEBP",
"size": 183021
},
"files": [
{
"url": "https://api.watermark.ws/v1/results/1234?expires=1792335604&signature=…",
"url_expires_at": "2026-10-18T15:00:04Z",
"format": "webp",
"size": 183021
}
],
"deleted_at": "2026-10-18T15:00:04Z"
},
"price": {
"amount": "0.002000",
"currency": "usd",
"micro_usd": 2000
},
"customer": "cus_42",
"created_at": "2026-10-11T15:00:00Z",
"completed_at": "2026-10-11T15:00:04Z"
}
Download result
The link in result.files[].url: open it as given, with no key. It works until the result is deleted (url_expires_at) and redirects to the file. Anyone with the link can download the file.
Query parameters
The request's id.
Part of the link.
Part of the link.
curl -L -o photo-watermarked.webp "https://api.watermark.ws/v1/results/1234?expires=1792335604&signature=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
$request = $client->requests->get(1234);
file_put_contents('photo-watermarked.webp', file_get_contents($request['result']['files'][0]['url']));
# --out on a process command downloads the file for you; a link you already have is a plain download.
curl -L -o photo-watermarked.webp "https://api.watermark.ws/v1/results/1234?expires=1792335604&signature=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
Abort request
Stop a request that has not finished. It is not charged.
Path parameters
The request's id.
curl -X POST https://api.watermark.ws/v1/requests/1235/abort \
-H "Authorization: Bearer $WATERMARK_API_KEY"
$client = new \Watermark\Api\Client(getenv('WATERMARK_API_KEY'));
$request = $client->requests->abort(1235);
watermark request abort 1235
{
"id": 1235,
"object": "request",
"endpoint": "videos/process",
"status": "aborted",
"livemode": true,
"source": "https://example.com/clip.mp4",
"price": {
"amount": "0.000000",
"currency": "usd",
"micro_usd": 0
},
"created_at": "2026-10-11T15:00:00Z",
"completed_at": "2026-10-11T15:00:09Z"
}
Account
Balance, usage and the operation catalog.
Get account
The account behind the key: balance, tier, and the key's mode and limits.
curl https://api.watermark.ws/v1/account \
-H "Authorization: Bearer $WATERMARK_API_KEY"
$client = new \Watermark\Api\Client(getenv('WATERMARK_API_KEY'));
$account = $client->account->get();
watermark account
{
"object": "account",
"id": 7,
"name": "Acme Photos",
"email": "dev@example.com",
"tier": "paid",
"balance": {
"amount": "18.420000",
"currency": "usd",
"micro_usd": 18420000
},
"webhook_url": "https://example.com/hooks/watermark",
"livemode": true,
"limits": {
"requests_per_minute": 120,
"concurrent_requests": 10,
"max_source_bytes": 2147483648
},
"suspended": false,
"created_at": "2026-10-11T14:52:10Z"
}
Get usage
Requests, spend and bytes over at most 92 days, grouped by day, endpoint, customer or status.
Query parameters
First day, YYYY-MM-DD. Defaults to 29 days ago.
Last day, YYYY-MM-DD. Defaults to today.
What each row totals.
dayendpointcustomerstatusOnly this customer's requests.
curl "https://api.watermark.ws/v1/usage?from=2026-10-01&group_by=endpoint" \
-H "Authorization: Bearer $WATERMARK_API_KEY"
$client = new \Watermark\Api\Client(getenv('WATERMARK_API_KEY'));
$usage = $client->account->usage([
'from' => '2026-10-01',
'group_by' => 'endpoint',
]);
watermark usage --from 2026-10-01 --group-by endpoint
{
"object": "usage",
"from": "2026-10-01",
"to": "2026-10-13",
"group_by": "endpoint",
"data": [
{
"endpoint": "images/process",
"requests": 1840,
"succeeded": 1822,
"failed": 18,
"spent": "3.644000",
"spent_micro_usd": 3644000,
"source_bytes": 912455120,
"result_bytes": 301228873
}
]
}
List operations
Every operation, its parameters and the output formats, per media type: what a client needs to build requests. The same catalog, with pictures, is at https://api.watermark.ws/docs/operations.
curl https://api.watermark.ws/v1/operations \
-H "Authorization: Bearer $WATERMARK_API_KEY"
$client = new \Watermark\Api\Client(getenv('WATERMARK_API_KEY'));
$catalog = $client->account->operations();
watermark operations
{
"object": "operations",
"data": {
"image": {
"formats": [
"jpg",
"png",
"webp",
"gif",
"tif"
],
"operations": {
"resize": {
"description": "Resize to a width and height.",
"params": {
"width": {
"description": "The width in pixels.",
"required": true
},
"height": {
"description": "The height in pixels.",
"required": true
},
"preserve_aspect_ratio": {
"description": "Fit inside the box instead of stretching to it.",
"required": false,
"default": true
},
"crop_center": {
"description": "Fill the box and crop what overflows, centered.",
"required": false,
"default": false
}
}
}
}
}
}
}