SDKs

C# SDK

C# and .NET watermarking SDK for Etchv. Watermark images, PDFs and videos, detect watermarks, and handle assets, async jobs and webhooks in .NET.

Needs .NET 10. Create one EtchvClient and reuse it; it is safe to use from many threads at once. Dispose of it when your app shuts down. MIT licensed.

Install

Shell
# From your project folder: clone next to it, not inside it
git clone --branch v1.0.0 https://github.com/etchv-labs/csharp-sdk.git ../etchv-csharp-sdk
dotnet add reference ../etchv-csharp-sdk/src/Etchv/Etchv.csproj

Install from the tagged source on GitHub. There is no official NuGet package.

Embed a file

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

C#
using Etchv;

using var client = new EtchvClient(Environment.GetEnvironmentVariable("ETCHV_API_KEY")!);
var key = await client.GetApiKeyInfoAsync(); // OrganizationId, KeyId, Scopes; no credits used
var result = await client.EmbedImageAsync(await File.ReadAllBytesAsync("photo.jpg"),
    new Dictionary<string, object?> { ["asset"] = "photo-123" },
    new RequestOptions(Filename: "photo.jpg"));
await File.WriteAllBytesAsync(result.Filename, result.Bytes);
var detection = await client.DetectImageAsync(result.Bytes, new RequestOptions(Filename: result.Filename));
Console.WriteLine($"{detection.Watermarked} {detection.WatermarkId} {detection.Confidence:P0}");

GetApiKeyInfoAsync checks your API key and uses no credits. Save the output bytes as they are. Do not re-encode the file.

Images, documents and video

MediaEmbed / detect methods
ImagesEmbedImageAsync / DetectImageAsync
PDFsEmbedDocumentAsync / DetectDocumentAsync
VideosEmbedVideoAsync / DetectVideoAsync
Images
Embed / detect methods
EmbedImageAsync / DetectImageAsync
PDFs
Embed / detect methods
EmbedDocumentAsync / DetectDocumentAsync
Videos
Embed / detect methods
EmbedVideoAsync / DetectVideoAsync

For every media type, result.Bytes holds the watermarked file 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 frame or page. It does not return the JSON you sent when you embedded the file. Result fields · Credits.

Client configuration

new EtchvClient(key, baseUrl, timeout, httpClient) sets the timeout and an optional HttpClient. The default timeout is 120 seconds. If you use your own HTTP handler, it must turn off redirects.

Every operation takes a CancellationToken. Cancelling raises OperationCanceledException. Use RequestOptions(Filename: ..., IdempotencyKey: ...) for media requests. The examples below assume you already have a client.

Retries and recovery

Embedding and video detection retry and check job status for you. Image and PDF detection do not. Before you upload, save an idempotency key (a value you choose so a retry isn't run twice). With it, you can recover the request even after your program restarts.

SituationAction
Client timeoutThe accepted job keeps running. Resume it, or resend the same inputs with the same key
Resume a jobGetEmbedResultAsync(requestId) / GetDetectionResultAsync(requestId)
Changed inputsUse a new key. Reusing a key with different inputs 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
GetEmbedResultAsync(requestId) / GetDetectionResultAsync(requestId)
Changed inputs
Action
Use a new key. Reusing a key with different inputs returns 409
Collect a saved result
Action
Within 24 hours. See retention
Error types and handling

An EtchvException has StatusCode, RequestId, IdempotencyKey, Detail, ErrorCode and ErrorStatus. Status 0 means a network failure or a client deadline.

C#
try
{
    var output = await client.GetEmbedResultAsync(requestId);
}
catch (EtchvException e) when (e.StatusCode == 410)
{
    // The saved result expired or its asset was deleted (e.ErrorStatus).
}
catch (EtchvException e)
{
    logger.LogError("Etchv {Status} request {RequestId}", e.StatusCode, e.RequestId);
}

See HTTP errors and retry behavior. When you report a failure, include the request ID.

Asset library

These calls use no credits. Downloads need the assets:read scope, and edits need assets:write. Deleting needs assets:delete and the owner or admin role.

List, edit and download assets
C#
var page = await client.ListAssetsAsync(new AssetListOptions(Kind: "watermarked"));
foreach (var item in page.Items) {
    var asset = await client.GetAssetAsync(item.Id);
    var updated = await client.UpdateAssetAsync(asset.Id, asset.Version,
        new Dictionary<string, object?> { ["metadata"] = new { campaign = "spring" } });
    if (updated.FileAvailable) {
        byte[] bytes = await client.DownloadAssetAsync(updated.Id);
    }
}
// Use NextCursor with the same filters to continue listing.

An edit replaces the metadata and must send the asset's current version. A 409 conflict means the asset changed since you read it, so fetch it again and redo your edit. DeleteAssetAsync deletes one asset. DeleteAssetsAsync deletes 1–50 assets as one action: either all are deleted or none are. Asset fields, filters and retention.

Async jobs and webhooks

To start a job without waiting for the result, call SubmitEmbedAsync or SubmitDetectionAsync. Set the media type to images, documents or videos. These calls return a JobReceipt. On other methods, the Async suffix only means C# async I/O. It does not mean a background job on the server.

Check an embedding job with GetJobAsync(requestId) and a detection job with GetJobAsync(requestId, detect: true).

Submit and collect an async job

Read the file into memory before you submit it.

C#
var job = await client.SubmitEmbedAsync("documents", pdfBytes,
    new Dictionary<string, object?> { ["delivery"] = "delivery_001" },
    new RequestOptions("document.pdf", "delivery_001"), webhookId);
var status = await client.GetJobAsync(job.RequestId);
if (status.Status == "succeeded")
{
    var output = await client.GetEmbedResultAsync(job.RequestId);
}

The webhook is optional. In Java or Rust, pass null or None to skip it. Save the receipt. Job handling.

Manage endpoints and verify webhooks

Methods: CreateWebhookAsync, ListWebhooksAsync, UpdateWebhookAsync, DeleteWebhookAsync, ListWebhookDeliveriesAsync, RedeliverWebhookAsync. Viewing webhooks needs webhooks:read. Managing them needs webhooks:write and the owner or admin role. The signing secret is shown only once, so save it.

C#
using var buffer = new MemoryStream();
await request.Body.CopyToAsync(buffer);
if (!WebhookSignature.Verify(buffer.ToArray(), request.Headers["X-Etchv-Timestamp"],
        request.Headers["X-Etchv-Signature"], signingSecret))
    return Results.Unauthorized();
// Parse JSON, check id == X-Etchv-Event-ID, deduplicate, enqueue work, then return 2xx.

WebhookSignature.Verify checks the signature and timestamp. Your handler must also check that the event ID in the body matches X-Etchv-Event-ID. Pass the raw request bytes and use a 300-second timestamp tolerance. Then skip events you already handled, queue the work and return 2xx. Verification rules.

GPU processing

On Business or Enterprise plans, pass new RequestOptions(Accelerator: Accelerator.Gpu). GPU processing uses 3× the credits. If processing falls back to CPU, it uses normal credits. result.Accelerator shows which one was used. See GPU processing.

Choose where results are stored

Connect your bucket. Then pass its destination ID and, if you like, an object key (the file's path in the bucket). Leave both out to use Etchv storage.

Deliver to your bucket
C#
var job = await client.SubmitEmbedAsync("documents", pdfBytes,
    new Dictionary<string, object?> { ["recipient"] = "customer-123" },
    new RequestOptions(Filename: "report.pdf", IdempotencyKey: "report-export-001",
        StorageDestinationId: destinationId, StorageKey: "reports/watermarked.pdf"));
// After the watermark job reports succeeded:
var delivery = await client.GetStorageDeliveryAsync((await client.GetJobAsync(job.RequestId)).StorageDeliveryId!);
if (delivery.Status == "failed") await client.RetryStorageDeliveryAsync(delivery.Id);

After the job succeeds, check the delivery with the storage:read scope. Checking before then returns 404. When the status is stored, download the file with DownloadStorageDeliveryAsync.

RetryStorageDeliveryAsync retries a failed or cancelled delivery at no cost. It needs storage:write and the owner or admin role. To manage destinations and deliveries, use CreateStorageDestinationAsync, VerifyStorageDestinationAsync, UpdateStorageDestinationAsync, DeleteStorageDestinationAsync, CreateStorageDeliveryAsync. 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