SDKs

Node.js SDK

Node.js watermarking SDK for Etchv. Watermark images, PDFs and videos, detect watermarks, and manage assets from your server. TypeScript types included.

Needs Node.js 22.12 or later. Includes TypeScript types and works with ES modules and CommonJS. No runtime dependencies. MIT licensed.

Install

Shell
npm install @etchv-labs/sdk

Embed and detect

Set ETCHV_API_KEY on your server. This example needs watermarks:embed and watermarks:detect, an active plan and available credits.

TypeScript
import { readFile, writeFile } from 'node:fs/promises';
import { Etchv } from '@etchv-labs/sdk';

const apiKey = process.env.ETCHV_API_KEY;
if (!apiKey) throw new Error('Set ETCHV_API_KEY on your server');
const client = new Etchv({ apiKey });
const { organization_id, scopes } = await client.getApiKeyInfo(); // no credits used
const result = await client.embedImage(
  await readFile('photo.jpg'),
  { recipient: 'customer-123' },
  { filename: 'photo.jpg' },
);
await writeFile(result.filename, result.image);
const detection = await client.detectImage(result.image);
console.log(detection.watermarkId, detection.confidence);

getApiKeyInfo checks your key and uses no credits. Save the output bytes exactly as returned; don't re-encode them.

Documents and video

MediaEmbed / detect methods
ImagesembedImage / detectImage
PDFsembedDocument / detectDocument
VideosembedVideo / detectVideo
Images
Embed / detect methods
embedImage / detectImage
PDFs
Embed / detect methods
embedDocument / detectDocument
Videos
Embed / detect methods
embedVideo / detectVideo

For every media type, the watermarked file is in result.image (Uint8Array), in its original format. Embedding also returns watermarkId, requestId, contentType, filename, assetId, sourceAssetId and storageDeliveryId.

Detection returns the watermark it found, a confidence score and results for each page or frame. It does not return the original JSON. Result fields · Credits.

Client configuration

new Etchv({ apiKey, baseUrl, timeout, fetch })

OptionDefault / behavior
baseUrlhttps://api.etchv.com
timeout120000 milliseconds
fetchOptional transport override
Per-call signal, timeoutCancellation and deadline override
Media filename, idempotencyKeyFile name and stable retry key
baseUrl
Default / behavior
https://api.etchv.com
timeout
Default / behavior
120000 milliseconds
fetch
Default / behavior
Optional transport override
Per-call signal, timeout
Default / behavior
Cancellation and deadline override
Media filename, idempotencyKey
Default / behavior
File name and stable retry key

Errors and recovery

Embedding and video detection retry and check for results automatically. Image and PDF detection do not. Save an idempotency key (a value you choose so a retry isn't run twice) before uploading. Then you can recover the request even if your app restarts.

SituationAction
Client timeoutThe accepted job keeps running. Resume it, or resend the same inputs with the same key
Resume a jobgetEmbedResult(requestId) / getDetectionResult(requestId)
Changed inputsUse a new key. Reusing the old key returns 409
Collect a saved resultWithin 24 hours; see retention
Client timeout
Action
The accepted job keeps running. Resume it, or resend the same inputs with the same key
Resume a job
Action
getEmbedResult(requestId) / getDetectionResult(requestId)
Changed inputs
Action
Use a new key. Reusing the old key returns 409
Collect a saved result
Action
Within 24 hours; see retention
Error types and handling

API errors expose statusCode, detail and requestId. EtchvTimeoutError uses status 0 and includes the identifiers you need to recover the request. Network failures and invalid arguments can throw other errors.

EtchvError subclasses: AuthenticationError (401), PaymentRequiredError (402), PermissionDeniedError (403), NotFoundError (404), ConflictError (409), GoneError (410), InvalidRequestError (413/422), RateLimitError (429), ServiceUnavailableError (5xx).

TypeScript
import { EtchvError, EtchvTimeoutError } from '@etchv-labs/sdk';

try {
  await client.embedImage(bytes, data, { idempotencyKey });
} catch (error) {
  if (error instanceof EtchvTimeoutError) {
    // Retry later with the same idempotencyKey and input.
  } else if (error instanceof EtchvError) {
    console.error(error.statusCode, error.requestId, error.detail);
  } else {
    throw error; // Network failures and invalid arguments (TypeError).
  }
}

See HTTP errors and retry behavior. Save the request ID when reporting failures.

Asset library

Scopes: assets:read for downloads, assets:write for edits, assets:delete plus owner/admin for deletion. No credits used.

List, edit and download assets
TypeScript
const page = await client.listAssets({ kind: 'watermarked', limit: 25 });
for (const item of page.items) {
  const asset = await client.getAsset(item.id);
  const updated = await client.updateAsset(asset.id, {
    version: asset.version, metadata: { campaign: 'spring' },
  });
  if (updated.file_available) {
    const bytes = await client.downloadAsset(updated.id);
  }
}
// Pass cursor: page.next_cursor with the same filters for the next page.

An edit replaces all metadata and must send the asset’s current version. A 409 means the asset changed since you read it; reload and resolve the conflict. deleteAsset deletes one asset. deleteAssets deletes 1–50 at once: either all are deleted or none are. Asset fields, filters and retention.

Async jobs and webhooks

To get a receipt without waiting for the result, use submitEmbed / submitDetection. Media types: images, documents, videos.

Check status with getJob(requestId) for embedding and getJob(requestId, { detect: true }) for detection.

Submit and collect an async job

Load the media bytes before submitting.

TypeScript
const job = await client.submitEmbed('documents', pdfBytes, {delivery: 'delivery_001'}, {
  filename: 'document.pdf', idempotencyKey: 'delivery_001', webhookId: process.env.ETCHV_WEBHOOK_ID,
});
const status = await client.getJob(job.request_id); // queued, running, retrying, succeeded, failed
const result = await client.getEmbedResult(job.request_id);

const scan = await client.submitDetection('videos', videoBytes, { idempotencyKey: 'scan_001' });
const detection = await client.getDetectionResult(scan.request_id);

The webhook is optional (Java/Rust: pass null / None). Save the receipt. Job handling.

Manage endpoints and verify webhooks

Methods: createWebhook, listWebhooks, updateWebhook, deleteWebhook, listWebhookDeliveries, redeliverWebhookEvent. Use webhooks:read to inspect or webhooks:write plus owner/admin to manage. Save the signing secret; it is shown only once.

TypeScript
import { parseWebhookEvent, WebhookVerificationError } from '@etchv-labs/sdk';

try {
  const event = parseWebhookEvent(rawBody, req.headers, process.env.ETCHV_WEBHOOK_SECRET);
  // Deduplicate by event.id, enqueue your work, then return 2xx.
} catch (error) {
  if (error instanceof WebhookVerificationError) return res.sendStatus(400);
  throw error;
}

parseWebhookEvent verifies the signature, timestamp and event ID. verifyWebhookSignature returns a boolean; parseWebhookEvent throws WebhookVerificationError. Verify the raw request bytes, with a 300-second timestamp tolerance. Skip events you have already handled, queue your work, then return 2xx. Verification rules.

GPU processing

On Business or Enterprise plans, pass { accelerator: 'gpu' }. GPU processing uses 3× credits. If the job falls back to CPU, it uses normal credits. result.accelerator reports the accelerator used. See GPU processing.

Choose where results are stored

Connect your bucket, then pass its destination ID. You can also pass an object key, which is the file’s path in your bucket. Leave both out to use Etchv storage.

Deliver to your bucket
TypeScript
const job = await client.submitEmbed("documents", pdfBytes, {recipient: "customer-123"}, {
  filename: "report.pdf", storageDestinationId: destinationId,
  storageKey: "reports/watermarked.pdf", idempotencyKey: "report-export-001"
});
// After the watermark job reports succeeded:
if (job.storage_delivery_id) {
  const delivery = await client.getStorageDelivery(job.storage_delivery_id);
  if (delivery.status === "stored") {
    const bytes = await client.downloadStorageDelivery(delivery.id);
  }
}

After the job succeeds, check the delivery with storage:read. Checking earlier returns 404. When the status is stored, download the file with downloadStorageDelivery.

With storage:write and owner/admin access, retryStorageDelivery retries a failed or cancelled delivery at no cost. Manage destinations and deliveries with: createStorageDestination, verifyStorageDestination, updateStorageDestination, deleteStorageDestination, createStorageDelivery. See delivery recovery.

View source on GitHub

Your privacy, your choice

We use essential cookies to keep Etchv working. Optional analytics helps us improve the site. Analytics is on by default; you can turn it off in preferences. PostHog loads only if you accept all. Privacy policy

ETCHV

Privacy preferences

Choose what you allow on this browser. Analytics is enabled by default. You can turn it off, and change your choice at any time.

Essential

Always active

Supports secure sign-in, account sessions, site security, and remembering your privacy choice. These are needed for Etchv to work.

Analytics

Helps us understand visits and improve the website using page-view and device statistics, which do not use cookies. PostHog also measures visits and campaigns and may set cookies; it loads only after you save a choice with analytics on. Turning this off stops future analytics events.

We do not load advertising scripts. Meeting calendars load only when you open them. These preferences do not change your account, watermarking requests, or asset storage.

Your choice is remembered for 180 days on this browser. Privacy policy