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
npm install @etchv-labs/sdkEmbed and detect
Set ETCHV_API_KEY on your server. This example needs watermarks:embed and watermarks:detect, an active plan and available credits.
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
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 })
| Option | Default / behavior |
|---|---|
baseUrl | https: |
timeout | 120000 milliseconds |
fetch | Optional transport override |
Per-call signal, timeout | Cancellation and deadline override |
Media filename, idempotencyKey | File name and stable retry key |
baseUrl- Default / behavior
https:/ /api. etchv. com
timeout- Default / behavior
120000milliseconds
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.
| Situation | Action |
|---|---|
| Client timeout | The accepted job keeps running. Resume it, or resend the same inputs with the same key |
| Resume a job | getEmbedResult( / getDetectionResult( |
| Changed inputs | Use a new key. Reusing the old key returns 409 |
| Collect a saved result | Within 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).
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
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.
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.
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
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.