Watermark API
Reference

API reference

View as Markdown

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.

The same reference as Markdown, and as an OpenAPI 3.1 spec for Postman, Insomnia or a code generator.
Base URL
https://api.watermark.ws
Authentication
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

id integer

The request's id.

object string

Always request.

endpoint string

Which endpoint started it, such as images/process.

status string

Where the request is. Only succeeded is charged.

queuedprocessingsucceededfailedaborted
livemode boolean

false for a test key's request.

source string

The URL the file was downloaded from.

input object

What was sent besides the source: operations, output and the endpoint's own fields.

progress integer, 0 to 100

How far a video has got while it is processing.

result object

Once succeeded.

Show 3 child fields
result.data object

About the output: dimensions and format for images, duration and codec for videos, pages and size for PDFs.

result.files array

The files made. Read endpoints make none.

Show 4 child fields
result.files[].url URL

A signed link to download the file.

result.files[].url_expires_at timestamp

When the link stops working: when the result is deleted.

result.files[].format string

The file format, such as webp.

result.files[].size integer or null

Bytes.

result.deleted_at timestamp

When the files are deleted, 7 days after the request finished.

error object

Once failed. See Errors.

Show 2 child fields
error.code string

Stable; branch on it.

error.message string

For people.

price Money object

An amount of money.

Show 3 child fields
price.amount string

Dollars, six decimals.

price.currency string

Always usd.

price.micro_usd integer

Millionths of a dollar.

customer string

As sent.

metadata object

As sent.

created_at timestamp

When the request was accepted.

completed_at timestamp

When it finished.

A request that succeeded
{
    "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

id string

The same on every retry of a delivery. Use it to ignore a repeat.

object string

Always event.

type string

What happened.

request.succeededrequest.failedrequest.aborted
created integer

Unix time.

data Request object
POST to your webhook_url
{
    "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.

StatusMeaning
401No key, or not a valid one.
402No credit left, or the endpoint needs an account that has paid.
404Not found on this account.
409A conflict: the request has finished, or the Idempotency-Key was used for another request.
422Invalid input, or a URL the API will not use.
429Too many requests a minute (rate_limited, see Retry-After), or too many of the account's requests running at once (concurrency_limited).
Response 422 Unprocessable Content
{
    "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

POST/v1/images/process

Run operations on an image, in order, and save the result.

$0.002 per request Failed requests are free

Headers

Authorization header Required

Bearer wm_live_… or Bearer wm_test_…. See Authentication.

Idempotency-Key header, up to 255 characters

Makes a retry safe: the same key with the same body returns the first request instead of starting another. See Idempotency.

Body parameters

source URL Required

A public http or https URL of the file. See Sources.

operations array of operations, 1 to 20 Required

Run in order, top to bottom. Each is {"type": …} plus that operation's parameters. See Operations.

output object

How to save the result.

Show child field
output.format string default jpg

The file format of the result.

jpgpngwebpgiftif
webhook_url URL

Where to post the request when it finishes. Defaults to the account's webhook, if set. See Webhooks.

customer string, up to 64 characters

Your own reference for who the request is for. Returned with it, and totalled by GET /v1/usage?group_by=customer.

metadata object, up to 20 keys

Up to 20 keys of your own, returned with the request and in its webhooks.

Returns 202. Accepted and queued.

Returns 200. The request first sent with this Idempotency-Key.

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"
  }'
Response 202 Accepted
{
    "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

POST/v1/images/read

Read dimensions, format, colorspace and orientation.

$0.0005 per request Failed requests are free

Headers

Authorization header Required

Bearer wm_live_… or Bearer wm_test_…. See Authentication.

Idempotency-Key header, up to 255 characters

Makes a retry safe: the same key with the same body returns the first request instead of starting another. See Idempotency.

Body parameters

source URL Required

A public http or https URL of the file. See Sources.

webhook_url URL

Where to post the request when it finishes. Defaults to the account's webhook, if set. See Webhooks.

customer string, up to 64 characters

Your own reference for who the request is for. Returned with it, and totalled by GET /v1/usage?group_by=customer.

metadata object, up to 20 keys

Up to 20 keys of your own, returned with the request and in its webhooks.

Returns 202. Accepted and queued.

Returns 200. The request first sent with this Idempotency-Key.

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"
  }'
Response 202 Accepted
{
    "id": 1234,
    "object": "request",
    "endpoint": "images/read",
    "status": "queued",
    "livemode": true,
    "source": "https://example.com/photo.jpg",
    "created_at": "2026-10-11T15:00:00Z"
}
result.data once it has succeeded
{
    "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

POST/v1/videos/process

Run operations on a video, in order, and save an MP4. Reports progress while it runs.

$0.02 per started minute of video Failed requests are free

Headers

Authorization header Required

Bearer wm_live_… or Bearer wm_test_…. See Authentication.

Idempotency-Key header, up to 255 characters

Makes a retry safe: the same key with the same body returns the first request instead of starting another. See Idempotency.

Body parameters

source URL Required

A public http or https URL of the file. See Sources.

operations array of operations, 1 to 20 Required

Run in order, top to bottom. Each is {"type": …} plus that operation's parameters. See Operations.

output object

How to save the result.

Show child field
output.format string default mp4

The file format of the result.

mp4
max_duration integer, 1 to 7200

Only keep the first this many seconds.

webhook_url URL

Where to post the request when it finishes. Defaults to the account's webhook, if set. See Webhooks.

customer string, up to 64 characters

Your own reference for who the request is for. Returned with it, and totalled by GET /v1/usage?group_by=customer.

metadata object, up to 20 keys

Up to 20 keys of your own, returned with the request and in its webhooks.

Returns 202. Accepted and queued.

Returns 200. The request first sent with this Idempotency-Key.

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"
  }'
Response 202 Accepted
{
    "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

POST/v1/videos/read

Read dimensions, duration, codec, bitrate and frame rate.

$0.001 per request Failed requests are free

Headers

Authorization header Required

Bearer wm_live_… or Bearer wm_test_…. See Authentication.

Idempotency-Key header, up to 255 characters

Makes a retry safe: the same key with the same body returns the first request instead of starting another. See Idempotency.

Body parameters

source URL Required

A public http or https URL of the file. See Sources.

webhook_url URL

Where to post the request when it finishes. Defaults to the account's webhook, if set. See Webhooks.

customer string, up to 64 characters

Your own reference for who the request is for. Returned with it, and totalled by GET /v1/usage?group_by=customer.

metadata object, up to 20 keys

Up to 20 keys of your own, returned with the request and in its webhooks.

Returns 202. Accepted and queued.

Returns 200. The request first sent with this Idempotency-Key.

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"
  }'
Response 202 Accepted
{
    "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

POST/v1/videos/screenshot

Save one frame as an image.

$0.002 per request Failed requests are free

Headers

Authorization header Required

Bearer wm_live_… or Bearer wm_test_…. See Authentication.

Idempotency-Key header, up to 255 characters

Makes a retry safe: the same key with the same body returns the first request instead of starting another. See Idempotency.

Body parameters

source URL Required

A public http or https URL of the file. See Sources.

seconds integer, 0 to 86400 Required

Which moment, in seconds from the start.

output object

How to save the result.

Show child field
output.format string default jpg

The file format of the result.

jpgpng
webhook_url URL

Where to post the request when it finishes. Defaults to the account's webhook, if set. See Webhooks.

customer string, up to 64 characters

Your own reference for who the request is for. Returned with it, and totalled by GET /v1/usage?group_by=customer.

metadata object, up to 20 keys

Up to 20 keys of your own, returned with the request and in its webhooks.

Returns 202. Accepted and queued.

Returns 200. The request first sent with this Idempotency-Key.

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"
      }
  }'
Response 202 Accepted
{
    "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

POST/v1/documents/process

Run operations on every page of a PDF.

$0.004 per page Failed requests are free

Headers

Authorization header Required

Bearer wm_live_… or Bearer wm_test_…. See Authentication.

Idempotency-Key header, up to 255 characters

Makes a retry safe: the same key with the same body returns the first request instead of starting another. See Idempotency.

Body parameters

source URL Required

A public http or https URL of the file. See Sources.

operations array of operations, 1 to 20 Required

Run in order, top to bottom. Each is {"type": …} plus that operation's parameters. See Operations.

max_pages integer, 1 to 2000

Only keep the first this many pages.

webhook_url URL

Where to post the request when it finishes. Defaults to the account's webhook, if set. See Webhooks.

customer string, up to 64 characters

Your own reference for who the request is for. Returned with it, and totalled by GET /v1/usage?group_by=customer.

metadata object, up to 20 keys

Up to 20 keys of your own, returned with the request and in its webhooks.

Returns 202. Accepted and queued.

Returns 200. The request first sent with this Idempotency-Key.

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}
    ]
  }'
Response 202 Accepted
{
    "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

POST/v1/documents/read

Read the page count and page size.

$0.0005 per request Failed requests are free

Headers

Authorization header Required

Bearer wm_live_… or Bearer wm_test_…. See Authentication.

Idempotency-Key header, up to 255 characters

Makes a retry safe: the same key with the same body returns the first request instead of starting another. See Idempotency.

Body parameters

source URL Required

A public http or https URL of the file. See Sources.

webhook_url URL

Where to post the request when it finishes. Defaults to the account's webhook, if set. See Webhooks.

customer string, up to 64 characters

Your own reference for who the request is for. Returned with it, and totalled by GET /v1/usage?group_by=customer.

metadata object, up to 20 keys

Up to 20 keys of your own, returned with the request and in its webhooks.

Returns 202. Accepted and queued.

Returns 200. The request first sent with this Idempotency-Key.

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"
  }'
Response 202 Accepted
{
    "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

POST/v1/documents/snapshot

Render one page as an image.

$0.002 per request Failed requests are free

Headers

Authorization header Required

Bearer wm_live_… or Bearer wm_test_…. See Authentication.

Idempotency-Key header, up to 255 characters

Makes a retry safe: the same key with the same body returns the first request instead of starting another. See Idempotency.

Body parameters

source URL Required

A public http or https URL of the file. See Sources.

page integer, 1 to 2000 default 1

Which page, from 1.

output object

How to save the result.

Show child field
output.format string default jpg

The file format of the result.

jpgpng
webhook_url URL

Where to post the request when it finishes. Defaults to the account's webhook, if set. See Webhooks.

customer string, up to 64 characters

Your own reference for who the request is for. Returned with it, and totalled by GET /v1/usage?group_by=customer.

metadata object, up to 20 keys

Up to 20 keys of your own, returned with the request and in its webhooks.

Returns 202. Accepted and queued.

Returns 200. The request first sent with this Idempotency-Key.

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"
      }
  }'
Response 202 Accepted
{
    "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

GET/v1/requests

The account's requests, newest first.

Query parameters

limit integer, 1 to 100 default 20

How many requests to return.

before integer

Only requests older than this id. Pass the last id of a page to get the next one.

Returns 200. A page of requests.

curl "https://api.watermark.ws/v1/requests?limit=20" \
  -H "Authorization: Bearer $WATERMARK_API_KEY"
Response 200 OK
{
    "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

GET/v1/requests/{id}

One request, with its result once it has succeeded. Download links in the result work until the result is deleted.

Path parameters

id integer Required

The request's id.

Returns 200. The request.

curl https://api.watermark.ws/v1/requests/1234 \
  -H "Authorization: Bearer $WATERMARK_API_KEY"
Response 200 OK
{
    "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

GET/v1/results/{id}

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

id integer Required

The request's id.

expires integer Required

Part of the link.

signature string Required

Part of the link.

Returns 302. Redirects to the file.

curl -L -o photo-watermarked.webp "https://api.watermark.ws/v1/results/1234?expires=1792335604&signature=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"

Abort request

POST/v1/requests/{id}/abort

Stop a request that has not finished. It is not charged.

Path parameters

id integer Required

The request's id.

Returns 200. The aborted request. One that has already finished is a 409 request_finished.

curl -X POST https://api.watermark.ws/v1/requests/1235/abort \
  -H "Authorization: Bearer $WATERMARK_API_KEY"
Response 200 OK
{
    "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

GET/v1/account

The account behind the key: balance, tier, and the key's mode and limits.

Returns 200. The account.

curl https://api.watermark.ws/v1/account \
  -H "Authorization: Bearer $WATERMARK_API_KEY"
Response 200 OK
{
    "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

GET/v1/usage

Requests, spend and bytes over at most 92 days, grouped by day, endpoint, customer or status.

Query parameters

from date

First day, YYYY-MM-DD. Defaults to 29 days ago.

to date

Last day, YYYY-MM-DD. Defaults to today.

group_by string default day

What each row totals.

dayendpointcustomerstatus
customer string, up to 64 characters

Only this customer's requests.

Returns 200. Usage.

curl "https://api.watermark.ws/v1/usage?from=2026-10-01&group_by=endpoint" \
  -H "Authorization: Bearer $WATERMARK_API_KEY"
Response 200 OK
{
    "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

GET/v1/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.

Returns 200. The catalog. The example shows one operation of one media type.

curl https://api.watermark.ws/v1/operations \
  -H "Authorization: Bearer $WATERMARK_API_KEY"
Response 200 OK
{
    "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
                        }
                    }
                }
            }
        }
    }
}