Skip to main content

Etchv Python SDK

Official server-side Python client for Etchv: embed and detect invisible forensic watermarks in images, PDFs and videos.

Install

pip install etchv-sdk

Requires Python 3.14. Import it as etchv. Fully typed.

Quickstart

Create an API key in the Etchv dashboard and set ETCHV_API_KEY on your server. Embedding needs the watermarks:embed scope, detection watermarks:detect, and both use credits.

import os
from pathlib import Path
from etchv import Etchv

with Etchv(os.environ["ETCHV_API_KEY"]) as client:
    result = client.embed_image(Path("photo.jpg").read_bytes(),
                                {"recipient": "customer-123"}, filename="photo.jpg")
    Path(result.filename).write_bytes(result.image)  # write bytes as-is

    detection = client.detect_image(result.image)
    print(detection.watermarked, detection.watermark_id, detection.confidence)

Use embed_document / detect_document for PDF, DOCX and PPTX and embed_video / detect_video for MP4, MOV, WebM, Matroska, MPEG and AVI (supported codecs). Image uploads are limited to 50 MB; document and video uploads to 20 MB. Detection takes the files Etchv delivered: up to 192 MB for images, 64 MB for PDFs and 100 MB for DOCX, PPTX and video. Detection recovers a SHA-256 digest of your data, not the data itself.

Large files

Files over 40 MB are uploaded once to a signed URL and then referenced by ID, so the request never carries the file. This is automatic in every embed, detect and submit method; retries reuse the same upload. Image and document detection above 95 MB runs as a background job and the call waits for it. Change the threshold with Etchv(..., large_file_threshold=...), or upload explicitly:

upload = client.upload_file("detect", delivered, filename="delivered.tiff")
upload["upload_id"]  # send as the upload_id form field instead of file

GPU processing

Business and Enterprise plans can pass accelerator="gpu" to any embed, detect or submit method (other plans get PermissionDeniedError). GPU operations use 3× credits. If no GPU is ready, the job runs on CPU at normal credits instead. result.accelerator reports the hardware actually used ("gpu" or "cpu"), and job receipts include accelerator_requested and accelerator.

result = client.embed_video(video_bytes, {"recipient": "customer-123"}, accelerator="gpu")
print(result.accelerator)  # "gpu", or "cpu" after a fallback

Async jobs

job = client.submit_embed("documents", pdf_bytes, {"recipient": "customer-123"},
                          filename="report.pdf", idempotency_key="report-001")
status = client.get_job(job["request_id"])  # ["status"]: queued, running, retrying, succeeded, failed
result = client.get_embed_result(job["request_id"])  # waits for the file

submit_detection, get_job(..., detect=True) and get_detection_result work the same way. Reusing an idempotency_key with the same input returns the saved result (kept 24 hours) without another charge.

Many files at once

A batch watermarks up to 100 images, PDF, DOCX and PPTX documents, and videos with one call. submit_batch creates the batch, uploads every file to its own signed URL (four at a time, never with your API key) and starts it. Each item has its own forensic data; the filename's extension sets the media type.

from pathlib import Path

batch = client.submit_batch(
    [{"filename": p.name, "file": p, "data": {"recipient": p.stem}} for p in Path("in").glob("*.pdf")],
    archive=True,  # also zip every result into one download
)
client.wait_for_batch(batch.batch_id, timeout=1800)  # honors Retry-After between polls

for item in client.iter_batch_results(batch.batch_id):
    if item.ok:
        Path("out", item.result.filename).write_bytes(item.result.image)
    else:
        print(item.filename, item.error_code)  # not charged, or refunded

client.download_batch_archive_to(batch.batch_id, "out.zip")  # streams to disk

file is bytes or a path. A batch costs the same credits per file as single requests. Files that never arrive or fail their checks are rejected (upload_not_received, invalid_input, ...) without a charge, and failed files are refunded, so one bad file never stops the rest. Results are kept 24 hours; the archive (with archive=True) holds every successful result plus manifest.json and is limited to 1 GB. download_batch_archive_to streams it to a path or file object; download_batch_archive returns it as bytes.

More than 100 items raise ValueError before any request; split larger sets into several batches. Pass webhook_id to get one watermark.batch.completed, watermark.batch.failed or watermark.batch.cancelled event when the batch ends, and accelerator or storage_destination_id as for single files. Retries reuse the same idempotency_key (generated when omitted). If an upload or the start still fails, BatchSubmitError carries batch_id and idempotency_key; calling submit_batch again with that key and the same items uploads only the files that have not arrived and starts the batch. A batch not started within 24 hours expires, and resuming it raises GoneError.

Files already in one zip (up to 55 MB) can go in one request; list every member:

batch = client.submit_batch_zip(Path("in.zip"), [{"filename": "in/a.png", "data": {"recipient": "a"}}])

Also: get_batch, cancel_batch (files still waiting are canceled and refunded; queued and running files finish) and list_batches(limit=20, before=...).

Also included

  • API key check: check_api_key() (no credits used).
  • Assets: list_assets, get_asset, update_asset, download_asset, delete_asset, delete_assets.
  • Webhooks: create_webhook, list_webhooks, update_webhook, delete_webhook, list_webhook_deliveries, redeliver_webhook; verify deliveries with verify_webhook(raw_body, headers, signing_secret), which raises WebhookVerificationError.
  • Customer storage (S3, GCS, Azure): create_storage_destination, list_storage_destinations, update_storage_destination, verify_storage_destination, delete_storage_destination, create_storage_delivery, list_storage_deliveries, get_storage_delivery, retry_storage_delivery, download_storage_delivery.

Errors

API failures raise EtchvError or a subclass such as AuthenticationError, PermissionDeniedError, ConflictError, GoneError, RateLimitError or DeadlineExceededError. Each carries status_code, detail and request_id (quote it to support). Messages never include your API key. Structured API errors also set code (for example rate_limited or concurrency_limited), message (also shown in the error text) and limit, and retry_after holds the Retry-After seconds (None when not sent). Embedding, video detection and job calls retry HTTP 429, 502, 503 and 504 within timeout, waiting for Retry-After (up to 5 seconds per wait) on 429.

from etchv import EtchvError

try:
    client.detect_image(image)
except EtchvError as error:
    print(error.status_code, error.request_id)

License: MIT (covers this SDK, not the hosted Etchv service).

Questions or bug reports: open an issue here or email hello@etchv.com.

Metadata

Release files for etchv-sdk 1.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for etchv-sdk 1.3.0
File Size Uploaded Provenance
etchv_sdk-1.3.0.tar.gz 35.6 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for etchv-sdk 1.3.0
File Interpreter ABI Platform
etchv_sdk-1.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 58.8 kB

Release files / etchv_sdk-1.3.0.tar.gz

Download URL etchv_sdk-1.3.0.tar.gz
Size 35.6 kB
Tags Source
SHA-256 checksum
How to use checksums
b5086a1c6cdfd086b7553cf49698e317c279de5b18e8b999ba111cce13bae193
BLAKE2b-256 checksum
How to use checksums
50bac505ecc211e3ae45698ee9d7b23d051dd50479b6602321d76742c1a044c6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / etchv_sdk-1.3.0-py3-none-any.whl

Download URL etchv_sdk-1.3.0-py3-none-any.whl
Size 23.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f8d81462c199fefe716a15bec2251e690514c61a02b23995c133103ccae9d2a0
BLAKE2b-256 checksum
How to use checksums
e3528525507f83e04c91e614af440d5c672ed20e97c32cd000968fe230977e04
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for etchv-sdk 1.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page