Skip to main content
This guide walks you through embedding invisible watermarks with the v3 API — from creating an order to downloading the watermarked files. Every endpoint here is plain HTTPS and JSON, so the same calls work from a server or straight from the browser. Each step includes a React tab, and a full copy-paste component is at the bottom of the page.
The v2 watermark embed endpoint (POST /api/v2/orders/wtr-embed) ended support at 09:00 KST on October 9, 2026 (00:00 UTC). This guide uses the v3 API. If you already call v2, see Migrating from v2 to v3.

Prerequisites

  • A BIZ MORI API key (get one here)
  • An image or PDF file to watermark (jpg, jpeg, png, webp, bmp, tif, tiff, or pdf)
Use the automatically issued sk_test_ key to exercise the whole v3 flow without running watermark processing or consuming credits. Test orders complete about five seconds after the upload, and their download URLs return a fixed sample file instead of your watermarked output. Use a live API key when you need an actual watermarked file.

How it works

  1. Create an order with the file names and the watermark texts for each file.
  2. Upload each file with PUT to the presigned uploadUrl in the response. Processing starts automatically once every file is uploaded — there is no confirm step.
  3. Wait for the order to finish by polling GET /api/v3/orders/{orderId} or by receiving a webhook.
  4. Download the results: one call returns a URL for each output file and one URL for all of them.

Step 1: Create an order

Create a watermark embed order. Each file takes 1 to 10 watermark texts, and each text produces its own output file and uses 1 credit. The request below embeds two texts into one image, so it creates two output files and uses 2 credits.
Response:
  • files[] follows the order of files in your request. Use files[i].uploadUrl to upload the file you sent as files[i].
  • outputs[] has one entry per watermark text. Its fileId values (such as 501-1) appear only in this create response. The order details and the download response identify output files with numeric fileId values instead.
  • expiresAt is when the order files are deleted. Download your results before then.
One order can hold up to 100 files, and images and PDFs can be mixed. If a file is already hosted, send its public HTTPS URL in originalFileUrl instead of uploading it. BIZ MORI downloads the file (up to 32 MiB, redirects are not followed), and that file’s uploadUrl is null.
Reuse the same idempotencyKey only when you retry the same request. It returns the existing order instead of creating a new one. The same key with a different request body returns 409 WATERMARK_V2_CONFLICT.

Step 2: Upload files

PUT each file to its presigned uploadUrl. This is a direct upload to storage — no Authorization header needed. Send a Content-Type header that matches the file extension. The presigned URL is signed for that type, so any other value is rejected.
No confirm step needed. Processing starts automatically once every file in the order is uploaded.
A live uploadUrl expires after 900 seconds. If it expires, call GET /api/v3/orders/{orderId} to receive a fresh inputFiles[].uploadUrl for each file that is still waiting for upload. Upload every file within 12 hours of creating the order, or the order becomes failed. Test keys receive https://api.bizmori.com/api/v2/test-uploads/{signedToken} instead, valid for 1 hour; it accepts the same unauthenticated PUT and discards the file contents.

Step 3: Wait for the order to finish

Poll the order until status is complete, or set up webhooks to receive order.watermarkEmbed.completed or order.watermarkEmbed.failed instead of polling.
When the order is complete, result.outputFiles[] lists one output file per watermark text. The order details have the same shape for every order type; see Get order details for every field.
If processingDelayed is true, automatic retries are exhausted and the order is waiting for manual recovery. It stays inProgress, so keep polling or wait for the webhook. The order.watermarkEmbed.failed webhook includes an errorCode that explains why the order failed. See Webhooks for the event payloads and error codes.

Step 4: Download the results

Once status is complete, request the download URLs. One call returns a URL for each output file in files[] and one URL for all results in archiveUrl. If the order has one output file, archiveUrl points to that file; if it has several, it points to a ZIP archive.
Response:
  • files[] lists the same output files in the same order as result.outputFiles[], with the same fileId values. Match them by fileId to see which watermark text each file carries.
  • The URLs are valid for up to 3600 seconds, or less if the retention period ends sooner. Request new URLs each time you download instead of storing them. After expiresAt, this endpoint returns 400 ORDER_EXPIRED.
For a test order, archiveUrl and files[] point to fixed sample files (a ZIP when the order has several outputs). The samples are not your watermarked files.

Full React example

Everything above, wired into one component: single-file upload with progress, a watermark text list with add/remove and duplicate detection, backoff polling, and a download button that fetches a fresh URL on each click. No dependencies beyond React.
WatermarkEmbedUploader.jsx
import.meta.env.VITE_MORI_API_KEY is Vite’s syntax. Use process.env.NEXT_PUBLIC_MORI_API_KEY on Next.js, or whatever your bundler exposes to client code. crypto.randomUUID() needs a secure context: HTTPS and localhost are fine, a plain-HTTP LAN address is not.

Error handling

For the complete error code reference, see Error Codes.

Migrating from v2 to v3

The v3 request body has the same shape as v2, so most integrations only change the endpoint paths and how they read results.
v3 watermark extract cannot detect watermarks embedded with v2. Switching to the v3 endpoint does not make existing v2-watermarked images detectable, and there will be no exception for v2 detection after v2 support ends.
Things that stay the same: images and PDFs can be mixed in one order, originalFileUrl still skips the upload for hosted files, and processing still starts without a confirm call.
Use the v3 endpoints to get and download orders. GET /api/v3/orders/{orderId} and GET /api/v3/orders/{orderId}/download work for orders created with v2 or v3, in the same shape. The deprecated GET /api/v2/orders/{orderId} returns only the common fields of a v3 order, without its results.
After v2 support ends (from 09:00 KST on October 9, 2026, which is 00:00 UTC):
  • Authentication and permission checks still run first, so 401 and 403 errors are returned as before.
  • Authenticated requests to the v2 watermark embed and extract endpoints return 410 Gone with { "code": "WATERMARK_V2_API_SUNSET" }, for both live and sk_test_ keys. The Link header points to the v3 endpoint. Create new orders with v3.
  • v2 orders that have not started processing by then fail without being processed. The order details (errors.errorCode) and the failed webhook carry WATERMARK_V2_API_SUNSET with a message pointing to v3. The order is not charged, and its credits are restored.
  • Orders already processing at that time finish and record their result. No new run starts for them afterwards.
  • You can still get existing v2 orders and download results and reports that were already created, within the existing retention period. New v2 reports cannot be created or regenerated.
Until then, the request and response behavior is unchanged, except that every response from these endpoints includes the Deprecation: true, Sunset: Fri, 09 Oct 2026 00:00:00 GMT, and Link headers.

Next steps

Watermark Extract

Check a file for a watermark and create a report.

Test API keys

Exercise the v3 flow without consuming credits.

Webhooks

Get notified when processing completes.

API reference

See every field of the v3 watermark embed endpoint.