{"openapi":"3.1.0","info":{"title":"Watermark Removal API","summary":"Remove repeated text and image watermarks from PDFs.","description":"Send the PDF and the watermark text as `multipart/form-data`. Every\nsuccessful call returns a `ProcessingReport` whose `outcome.status` says\nwhether anything was removed; a `200` on its own does not mean the\ndocument changed. The engine only removes occurrences it can match\nexactly and safely, so the rest of the document is left untouched.\n\n**Access.** Requests without an `X-API-Key` header share a per-IP quota\n(10 per minute, 60 per hour).\nKeyed clients get their own quota and attribution. Every response carries\n`RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`; `429`\nresponses add `Retry-After`.\n\n**Errors.** Non-2xx responses are JSON with a `detail` field, except\n`422`, which uses FastAPI's validation error list. `X-Request-ID` is\nechoed on every response; include it when reporting a problem.\n\nGuides and worked examples: https://watermarkremoval.com/api-docs\n","contact":{"name":"Watermark Removal","url":"https://watermarkremoval.com/api-docs"},"version":"1.0.0"},"paths":{"/api/v1/remove-watermark/stats":{"post":{"tags":["watermark"],"summary":"Inspect what would change","description":"Run the full removal pipeline and return only the report.\n\nThe processed file is discarded. Use this to preview the outcome or to\ncollect statistics without downloading the PDF; it costs the same\nprocessing time and quota as the file endpoint.","operationId":"remove_watermark_stats","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_remove_watermark_stats"}}}},"responses":{"200":{"description":"Processing finished; read processing_report.outcome to learn whether anything was removed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WatermarkRemovalResponse"}}},"headers":{"X-Request-ID":{"schema":{"type":"string"},"description":"Correlation id for this request; quote it when reporting a problem."},"RateLimit-Limit":{"schema":{"type":"integer"},"description":"Requests allowed per hour for the caller's tier."},"RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests left in the current hour window."},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until the hour window resets."}}},"400":{"description":"Input rejected: the part is not application/pdf, the file is too large or cannot be parsed or repaired, or watermark_text is outside the hint contract.","content":{"application/json":{"example":{"detail":"Invalid file type. Only PDF files are allowed."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unknown API key.","content":{"application/json":{"example":{"detail":"Invalid API key"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded for the caller's tier. Denied requests do not consume quota.","content":{"application/json":{"example":{"detail":"Rate limit exceeded"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."},"RateLimit-Limit":{"schema":{"type":"integer"},"description":"Requests allowed per hour for the caller's tier."},"RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests left in the current hour window."},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until the hour window resets."}}},"500":{"description":"Processing failed after the input was accepted. Retry once; then report the X-Request-ID.","content":{"application/json":{"example":{"detail":"Internal server error","request_id":"0b8c1d2e-6f3a-4b5c-9d7e-8f1a2b3c4d5e"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string"},"description":"Optional API key. Unknown keys are rejected with 401; requests without a key use the anonymous per-IP quota."},{"name":"X-Request-ID","in":"header","required":false,"schema":{"type":"string"},"description":"Client-chosen correlation id, echoed on the response. Generated when absent."}]}},"/api/v1/remove-watermark":{"post":{"tags":["watermark"],"summary":"Remove a watermark and download the PDF","description":"Remove the given watermark and stream back the processed PDF.\n\nThe report that the stats endpoint returns as JSON is attached here as\nthe X-Processing-Report and X-Watermark-Stats response headers, so one\ncall yields both the file and the verdict.","operationId":"remove_watermark","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_remove_watermark"}}}},"responses":{"200":{"description":"The processed PDF. Returned even when nothing was removed; read the X-Processing-Report header's outcome before treating the file as cleaned.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}},"headers":{"X-Processing-Report":{"schema":{"type":"string"},"description":"JSON-encoded ProcessingReport (same shape as the stats endpoint's processing_report), kept under 8 KiB so proxies accept the response. When it would not fit, diagnostics and then pages keep their first entries and diagnostics_truncated / pages_truncated give the number omitted; summary, outcome and warnings are never cut. The stats endpoint returns the full report."},"X-Watermark-Stats":{"schema":{"type":"string"},"description":"JSON-encoded WatermarkStats (same shape as the stats endpoint's statistics), kept under 5 KiB. When it would not fit, page_statistics and then locations keep their first entries and page_statistics_truncated / locations_truncated give the number omitted; the counters are never cut. The stats endpoint returns the full list."},"Content-Disposition":{"schema":{"type":"string"},"description":"Suggested download name, for example attachment; filename=processed_report.pdf."},"X-Request-ID":{"schema":{"type":"string"},"description":"Correlation id for this request; quote it when reporting a problem."},"RateLimit-Limit":{"schema":{"type":"integer"},"description":"Requests allowed per hour for the caller's tier."},"RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests left in the current hour window."},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until the hour window resets."}}},"400":{"description":"Input rejected: the part is not application/pdf, the file is too large or cannot be parsed or repaired, or watermark_text is outside the hint contract.","content":{"application/json":{"example":{"detail":"Invalid file type. Only PDF files are allowed."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unknown API key.","content":{"application/json":{"example":{"detail":"Invalid API key"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded for the caller's tier. Denied requests do not consume quota.","content":{"application/json":{"example":{"detail":"Rate limit exceeded"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."},"RateLimit-Limit":{"schema":{"type":"integer"},"description":"Requests allowed per hour for the caller's tier."},"RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests left in the current hour window."},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until the hour window resets."}}},"500":{"description":"Processing failed after the input was accepted. Retry once; then report the X-Request-ID.","content":{"application/json":{"example":{"detail":"Internal server error","request_id":"0b8c1d2e-6f3a-4b5c-9d7e-8f1a2b3c4d5e"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string"},"description":"Optional API key. Unknown keys are rejected with 401; requests without a key use the anonymous per-IP quota."},{"name":"X-Request-ID","in":"header","required":false,"schema":{"type":"string"},"description":"Client-chosen correlation id, echoed on the response. Generated when absent."}]}},"/api/v1/image-watermarks/candidates":{"post":{"tags":["image-watermarks"],"summary":"List separately selectable image drawings (not detected logos)","operationId":"image_watermark_candidates","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageCandidates"}}},"headers":{"X-Request-ID":{"schema":{"type":"string"},"description":"Correlation id for this request; quote it when reporting a problem."},"RateLimit-Limit":{"schema":{"type":"integer"},"description":"Requests allowed per hour for the caller's tier."},"RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests left in the current hour window."},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until the hour window resets."}}},"400":{"description":"Invalid PDF, selector, confirmation or unsupported document."},"401":{"description":"Invalid API key."},"413":{"description":"Input, document complexity, resource or output budget exceeded."},"429":{"description":"Caller quota exceeded."},"503":{"description":"Worker busy, timed out or safe resource limits unavailable."}},"parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string"},"description":"Optional API key. Unknown keys are rejected with 401; requests without a key use the anonymous per-IP quota."},{"name":"X-Request-ID","in":"header","required":false,"schema":{"type":"string"},"description":"Client-chosen correlation id, echoed on the response. Generated when absent."}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file","page_number"],"properties":{"file":{"type":"string","format":"binary","contentMediaType":"application/pdf"},"page_number":{"type":"integer","minimum":1}}}}}}}},"/api/v1/image-watermarks/preview":{"post":{"tags":["image-watermarks"],"summary":"Preview removal of one explicitly confirmed whole image","operationId":"image_watermark_preview","responses":{"200":{"description":"Candidate PDF; compare with the original and confirm again before downloading.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}},"headers":{"X-Request-ID":{"schema":{"type":"string"},"description":"Correlation id for this request; quote it when reporting a problem."},"RateLimit-Limit":{"schema":{"type":"integer"},"description":"Requests allowed per hour for the caller's tier."},"RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests left in the current hour window."},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until the hour window resets."}}},"400":{"description":"Invalid PDF, selector, confirmation or unsupported document."},"401":{"description":"Invalid API key."},"413":{"description":"Input, document complexity, resource or output budget exceeded."},"429":{"description":"Caller quota exceeded."},"503":{"description":"Worker busy, timed out or safe resource limits unavailable."}},"parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string"},"description":"Optional API key. Unknown keys are rejected with 401; requests without a key use the anonymous per-IP quota."},{"name":"X-Request-ID","in":"header","required":false,"schema":{"type":"string"},"description":"Client-chosen correlation id, echoed on the response. Generated when absent."}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file","page_number","instruction_index","source_sha256","confirmed"],"properties":{"file":{"type":"string","format":"binary","contentMediaType":"application/pdf"},"page_number":{"type":"integer","minimum":1},"instruction_index":{"type":"integer","minimum":0},"source_sha256":{"type":"string","pattern":"^[0-9a-f]{64}$"},"confirmed":{"type":"boolean","const":true}}}}}}}},"/":{"get":{"tags":["meta"],"summary":"Service entry point","operationId":"root","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/health":{"get":{"tags":["meta"],"summary":"Liveness and engine version","description":"Report service status plus `engine_version`, the deployed git revision.","operationId":"health","responses":{"200":{"description":"Service healthy; qpdf is available.","content":{"application/json":{"schema":{}}}},"503":{"description":"Service degraded or a dependency is missing."}}}},"/health/details":{"get":{"tags":["meta"],"summary":"Dependency and runtime details","operationId":"health_details","responses":{"200":{"description":"Service healthy; qpdf is available.","content":{"application/json":{"schema":{}}}},"503":{"description":"Service degraded or a dependency is missing."}}}}},"components":{"schemas":{"Body_remove_watermark":{"properties":{"file":{"type":"string","format":"binary","contentMediaType":"application/pdf","title":"File","description":"The PDF to process. The multipart part must carry Content-Type application/pdf; uploads above 10 MB are rejected with 400."},"watermark_text":{"type":"string","title":"Watermark Text","description":"The watermark as it appears in the document (the hint). Case and spacing are normalized. Limits: at most 256 characters, 4 non-empty lines and 24 words, with at least 3 letters or digits per line. Long, body-like text is rejected with 400."}},"type":"object","required":["file","watermark_text"],"title":"Body_remove_watermark"},"Body_remove_watermark_stats":{"properties":{"file":{"type":"string","format":"binary","contentMediaType":"application/pdf","title":"File","description":"The PDF to process. The multipart part must carry Content-Type application/pdf; uploads above 10 MB are rejected with 400."},"watermark_text":{"type":"string","title":"Watermark Text","description":"The watermark as it appears in the document (the hint). Case and spacing are normalized. Limits: at most 256 characters, 4 non-empty lines and 24 words, with at least 3 letters or digits per line. Long, body-like text is rejected with 400."}},"type":"object","required":["file","watermark_text"],"title":"Body_remove_watermark_stats"},"ErrorResponse":{"properties":{"detail":{"type":"string","title":"Detail","description":"Human-readable reason for the failure."},"request_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Request Id","description":"Present on unhandled 500 responses; equals the X-Request-ID header."}},"type":"object","required":["detail"],"title":"ErrorResponse","description":"Body of every non-2xx response except 422, which uses FastAPI's validation error list.","examples":[{"detail":"Invalid file type. Only PDF files are allowed."}]},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"ImageBox":{"properties":{"left":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Left"},"top":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Top"},"width":{"type":"number","maximum":1.0,"exclusiveMinimum":0.0,"title":"Width"},"height":{"type":"number","maximum":1.0,"exclusiveMinimum":0.0,"title":"Height"}},"type":"object","required":["left","top","width","height"],"title":"ImageBox"},"ImageCandidate":{"properties":{"instruction_index":{"type":"integer","minimum":0.0,"title":"Instruction Index"},"bbox":{"$ref":"#/components/schemas/ImageBox"},"pixel_width":{"type":"integer","exclusiveMinimum":0.0,"title":"Pixel Width"},"pixel_height":{"type":"integer","exclusiveMinimum":0.0,"title":"Pixel Height"}},"type":"object","required":["instruction_index","bbox","pixel_width","pixel_height"],"title":"ImageCandidate"},"ImageCandidates":{"properties":{"source_sha256":{"type":"string","pattern":"^[0-9a-f]{64}$","title":"Source Sha256"},"page_number":{"type":"integer","minimum":1.0,"title":"Page Number"},"page_count":{"type":"integer","maximum":32.0,"minimum":1.0,"title":"Page Count"},"candidates":{"items":{"$ref":"#/components/schemas/ImageCandidate"},"type":"array","maxItems":64,"title":"Candidates"},"unsupported_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Unsupported Reason"}},"type":"object","required":["source_sha256","page_number","page_count","candidates","unsupported_reason"],"title":"ImageCandidates"},"PageStatistics":{"properties":{"text_watermarks":{"type":"integer","title":"Text Watermarks","description":"Text occurrences removed on this page.","default":0},"image_watermarks":{"type":"integer","title":"Image Watermarks","description":"Image draws removed on this page.","default":0}},"type":"object","title":"PageStatistics"},"ProcessingDiscovery":{"properties":{"status":{"type":"string","title":"Status","description":"Informational discovery status. Current values: 'found', 'target_present', 'uncorroborated', 'no_candidate', 'no_text', 'contract_rejected', 'error'. The set may grow; treat unknown values as informational."},"discovered_target":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Discovered Target","description":"Watermark string the engine proposed from the document text, truncated to 256 characters."},"similarity":{"type":"number","title":"Similarity","description":"Similarity between the supplied hint and the discovered target, 0 to 1.","default":0.0},"adopted":{"type":"boolean","title":"Adopted","description":"True when the discovered target was added to the match set for this run.","default":false}},"type":"object","required":["status"],"title":"ProcessingDiscovery","description":"Product-facing summary of the target-discovery phase."},"ProcessingOutcome":{"properties":{"status":{"type":"string","enum":["removed","nothing_removed"],"title":"Status","description":"'removed' when at least one occurrence was removed, otherwise 'nothing_removed'."},"reason":{"anyOf":[{"type":"string","enum":["no_text_layer","target_not_found","target_found_not_removed","undetermined"]},{"type":"null"}],"title":"Reason","description":"Set only when status is 'nothing_removed'. 'no_text_layer': the document has no extractable text (scanned or image-only). 'target_not_found': the hint does not appear in the text. 'target_found_not_removed': the hint is present but no occurrence passed the safety gates. 'undetermined': the text pass was skipped for another reason (see diagnostics)."},"discovery":{"anyOf":[{"$ref":"#/components/schemas/ProcessingDiscovery"},{"type":"null"}],"description":"Target-discovery summary; null when discovery did not run."}},"type":"object","required":["status"],"title":"ProcessingOutcome","description":"Honest, product-facing verdict for one processing run.\n\nstatus is \"removed\" or \"nothing_removed\"; reason is set only for\nnothing_removed and is one of: \"no_text_layer\",\n\"target_found_not_removed\", \"target_not_found\", \"undetermined\"."},"ProcessingPageReport":{"properties":{"page":{"type":"integer","title":"Page","description":"1-based page number."},"text_watermarks":{"type":"integer","title":"Text Watermarks","description":"Text occurrences removed on this page. A shared form drawn on several pages counts once per page, so the per-page sum can exceed the run total. A page listed only with 'chunk_text' evidence changed but could not be counted, so do not treat 0 as 'unchanged'.","default":0},"image_watermarks":{"type":"integer","title":"Image Watermarks","description":"Image draws removed on this page.","default":0},"location_count":{"type":"integer","title":"Location Count","description":"Number of evidence entries recorded for this page.","default":0},"location_types":{"items":{"type":"string"},"type":"array","title":"Location Types","description":"Distinct evidence kinds on this page, for example ['text'], ['image'] or ['chunk_text']."},"has_reported_change":{"type":"boolean","title":"Has Reported Change","description":"True when any strategy changed this page. The reliable per-page signal.","default":false}},"type":"object","required":["page"],"title":"ProcessingPageReport","description":"Per-page view of what changed. Only pages with evidence appear in the report."},"ProcessingReport":{"properties":{"summary":{"$ref":"#/components/schemas/ProcessingSummary"},"outcome":{"anyOf":[{"$ref":"#/components/schemas/ProcessingOutcome"},{"type":"null"}],"description":"Verdict for the run; a 200 response never implies removal on its own."},"pages":{"additionalProperties":{"$ref":"#/components/schemas/ProcessingPageReport"},"type":"object","title":"Pages","description":"Per-page reports keyed by 1-based page number as a string."},"warnings":{"items":{"type":"string"},"type":"array","title":"Warnings","description":"Non-fatal issues encountered while processing."},"diagnostics":{"additionalProperties":true,"type":"object","title":"Diagnostics","description":"Compact engine diagnostics (phase timings and counts). Informational; keys may change."}},"type":"object","required":["summary"],"title":"ProcessingReport","description":"Same payload as the X-Processing-Report header on the file endpoint, which may be cut to fit."},"ProcessingSummary":{"properties":{"text_watermarks_found":{"type":"integer","title":"Text Watermarks Found","description":"Text occurrences matched by any strategy.","default":0},"text_watermarks_removed":{"type":"integer","title":"Text Watermarks Removed","description":"Text occurrences actually removed.","default":0},"image_watermarks_found":{"type":"integer","title":"Image Watermarks Found","description":"Image draws matched as watermark stamps.","default":0},"image_watermarks_removed":{"type":"integer","title":"Image Watermarks Removed","description":"Image draws actually removed.","default":0},"patterns_matched":{"items":{"type":"string"},"type":"array","title":"Patterns Matched","description":"Distinct matched strings, including any adopted discovered target."}},"type":"object","title":"ProcessingSummary"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"WatermarkLocation":{"properties":{"start":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Start","description":"Byte offset where the matched text object starts (legacy text pass only)."},"end":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"End","description":"Byte offset where the matched text object ends (legacy text pass only)."},"type":{"type":"string","title":"Type","description":"Evidence kind: 'text' for an exact text match, 'image' for a removed image or form draw, 'chunk_text' for a page that changed without a placeable match (attributed by content diff)."},"content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content","description":"Matched text, when the strategy captured it."},"encoding":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Encoding","description":"How the match was found, for example 'plain', 'hex', 'TJ', 'aggregate', 'split', 'rotated', 'structured' or 'raster_stamp'."},"page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Page","description":"1-based page number, when known."},"properties":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Properties","description":"Strategy-specific details such as the occurrence count and the gate that admitted the match."},"position":{"anyOf":[{"type":"string"},{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Position","description":"Positional evidence; 'page_content_diff' for chunk_text locations."}},"type":"object","required":["type"],"title":"WatermarkLocation","description":"One piece of evidence for a change; the populated fields depend on the strategy."},"WatermarkRemovalResponse":{"properties":{"message":{"type":"string","title":"Message","description":"Human-readable status line."},"file_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Path","description":"Server-side name of the processed file. Not downloadable; call /remove-watermark for bytes."},"statistics":{"anyOf":[{"$ref":"#/components/schemas/WatermarkStats"},{"type":"null"}],"description":"Raw engine counters; the uncut version of the X-Watermark-Stats header."},"processing_report":{"anyOf":[{"$ref":"#/components/schemas/ProcessingReport"},{"type":"null"}],"description":"Structured report; the uncut version of the X-Processing-Report header."}},"type":"object","required":["message"],"title":"WatermarkRemovalResponse"},"WatermarkStats":{"properties":{"text_watermarks_found":{"type":"integer","title":"Text Watermarks Found","description":"Text occurrences matched by any strategy.","default":0},"text_watermarks_removed":{"type":"integer","title":"Text Watermarks Removed","description":"Text occurrences actually removed.","default":0},"image_watermarks_found":{"type":"integer","title":"Image Watermarks Found","description":"Image draws matched as watermark stamps.","default":0},"image_watermarks_removed":{"type":"integer","title":"Image Watermarks Removed","description":"Image draws actually removed.","default":0},"locations":{"items":{"$ref":"#/components/schemas/WatermarkLocation"},"type":"array","title":"Locations","description":"Evidence entries in the order the strategies recorded them."},"patterns_matched":{"items":{"type":"string"},"type":"array","title":"Patterns Matched","description":"Distinct matched strings, including any adopted discovered target."},"page_statistics":{"additionalProperties":{"$ref":"#/components/schemas/PageStatistics"},"type":"object","title":"Page Statistics","description":"Per-page counters keyed 'page_<n>' for every page with a placed match."}},"type":"object","title":"WatermarkStats","description":"Raw engine counters. Prefer processing_report for product decisions."}}},"tags":[{"name":"watermark","description":"Process a PDF. Both endpoints run the same pipeline and share one quota."},{"name":"meta","description":"Service discovery and health. Not rate limited."}]}