Skip to main content
This guide walks you through checking a file for an invisible watermark with the v3 API — from creating an order to reading the result and downloading a PDF report. 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 extract endpoint (POST /api/v2/orders/wtr-extract) 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.
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.

Prerequisites

  • A BIZ MORI API key (get one here)
  • The original file the watermark was embedded into — the file you sent to watermark embed, not the watermarked output
  • The file to check:
    • If the original is an image (jpg, jpeg, png, webp, bmp, tif, or tiff), the file to check is an image.
    • If the original is a PDF, the file to check is a PDF, or a PNG or JPEG image captured from it.
Use the automatically issued sk_test_ key to exercise the whole v3 flow without running watermark extraction or consuming credits. Test uploads discard file contents, and the name of the file to check selects the result: _detected returns detected, anything else returns undetected, and _fail on either file fails the order.

How it works

  1. Create an order with the names of the original file and the file to check.
  2. Upload both files with PUT to the presigned URLs in the response. Extraction starts automatically once both are uploaded — there is no confirm step.
  3. Read the result by polling GET /api/v3/orders/{orderId} or by receiving a webhook.
  4. Create a PDF report for the result if you need one.

Step 1: Create an order

Create a watermark extract order. Each order checks one file and uses 1 credit.
Response:
files always has two entries: files[0] is the original file and files[1] is the file to check. In the order details, the same files appear in inputFiles[] with role set to original and query.
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 both 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. Extraction starts automatically once both files are 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 both files 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: Read the result

Poll the order until status is complete, then read result. You can also set up webhooks to receive order.watermarkExtract.completed or order.watermarkExtract.failed instead of polling.
Response (watermark detected):
Poll status to know when the order is done, then read result.detectionStatus for the outcome:
  • result.watermarkText is the embedded text. It is returned only when the result is detected, your account embedded the watermark, and exactly one embed record matches. Otherwise it is null.
  • result.reportSupported tells you whether you can create a PDF report for this result. It is false when you checked an image captured from a PDF.
  • result.reports[] lists the status of each report you requested for this order.
  • 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.
Webhook payloads keep the v2 field names: order.watermarkExtract.completed reports the outcome in statusCode, which uses the same values as result.detectionStatus, plus watermarkFound and watermarkInfo.text. See Webhooks for the full payload.

Step 4: Create a PDF report (optional)

When result.reportSupported is true, you can create a PDF report of the result in Korean (ko-KR), English (en-US), or Japanese (ja-JP). Reports for PDF files support ko-KR only. A report uses 1 credit, charged only when it is generated successfully. Request the report, then poll it until status is completed:
Response (completed):
The download.url expires after 300 seconds. Don’t store it — call GET /api/v3/orders/{orderId}/reports/{locale} again whenever you need to download the report. If a report is failed and failure.retryable is true, call the POST endpoint again to retry.
With a test key, reports are available only for a detected test order. They return completed immediately with a one-page sample PDF and do not use credits.

Full React example

Everything above, wired into one component: two file pickers, uploads with progress, backoff polling, the result rendered from detectionStatus, and a report button. No dependencies beyond React.
WatermarkExtractChecker.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.
The public result contains only detectionStatus, reportSupported, and watermarkText. It never includes a watermark ID (MID).

Error handling

For the complete error code reference, see Error Codes.

Migrating from v2 to v3

v3 always compares the file to check against its original, so the request now names both files.
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.
Use the v3 endpoint to get orders. GET /api/v3/orders/{orderId} works 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 result.
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 Embed

Embed invisible watermarks into images and PDFs.

Test API keys

Exercise the v3 flow without consuming credits.

Webhooks

Get notified when extraction completes.

API reference

See every field of the v3 watermark extract endpoint.