Skip to main content

Recovery

Transcodes, re-wraps, and platform pipelines strip embedded manifests. When an asset arrives with no embedded manifest, the verifier can recover the credential from the content itself — provided you configure a registry at create and tell verify which binders to try.

Configure a registry

import { AssetSdk, DEV_CA } from '@limboai/verifox-sdk-verifier';

const verifier = AssetSdk.create({ trustAnchors: [DEV_CA()], registry: "https://api.trylimbo.com" });

The registry option is the registry endpoint (e.g. https://api.trylimbo.com). Omit it and the verifier is embedded-only: verify checks embedded manifests and never recovers.

How verify recovers

verify(bytes, binders) resolves in order and stops at the first hit:

  1. Embedded manifest — if the asset still carries one, that wins.
  2. Exact content hash — always tried when a registry is configured: a byte-identical copy whose manifest was stripped but whose bytes are otherwise untouched (also the remote-storage signing case). No binder needed.
  3. Binders, in the order you pass them — each derives a soft binding from the content and looks it up; the first that recovers a credential wins.

Recovery is explicit: verify(bytes, []) does embedded + exact-hash only. Soft recovery happens only for the binders you supply.

verify's first argument is a Uint8Array or a Node RangeSource. A multi-GB video master streams from disk without being loaded whole, while a small image materializes its bytes so a binder can derive a soft binding from the pixels. See File streaming.

Binders

A binder is a soft-binding strategy you compose into verify. The SDK ships three:

import { Binder } from '@limboai/verifox-sdk-verifier';

Binder.iscc() // image ISCC (io.iscc.v0) — free, client-side, image-only
Binder.watermark({ endpoint, token }) // a watermark via your deployment's detect endpoint (authed)
Binder.held({ alg, value }) // a soft binding you already hold (e.g. an out-of-band video ISCC)
  • Binder.iscc() recomputes the image's ISCC Image-Code and recovers by it — surviving the re-encodes (re-compress, resize, screenshot) an exact hash cannot. Free and keyless, computed in the browser; no round-trip to derive it.
  • Binder.watermark({ endpoint, token }) POSTs the pixels to your watermark detect endpoint (endpoint is the full URL; token, your API key, is sent as a bearer credential) and recovers by the watermark it resolves. The credentials live on the binder — the verifier itself stays credential-free.
  • Binder.held({ alg, value }) contributes a binding you already have. Use it for a video master, whose ISCC is computed out-of-band at sign time (the browser build has no video-ISCC path), or a cached watermark handle.

Compose by priority

Order is priority. The exact hash is always first (strongest — byte-cryptographic); then your binders in list order, so put the strongest first:

// browser verifier — free, keyless
const results = await verifier.verify(image, [Binder.iscc()]);

// backend verifier — try ISCC, then a watermark via your authed endpoint
const results = await verifier.verify(image, [
Binder.iscc(),
Binder.watermark({
endpoint: "https://api.trylimbo.com/api/v1/watermarks/detect",
token: process.env.LIMBO_API_KEY,
}),
]);

// a video master whose ISCC you carry alongside it
const results = await verifier.verify(master, [Binder.held({ alg: "io.iscc.v0", value })]);

Derive a binding standalone

Each binder can derive its binding without verifying — the "detect" primitive:

const binding = await Binder.iscc().detect(image); // { alg, value } | undefined
const binding = await Binder.watermark({ endpoint, token }).detect(image); // { alg, value } | undefined

It returns the { alg, value } the content resolves to, or undefined when the binder finds nothing (an unwatermarked image, an undecodable one). Use it to pre-derive, cache, or inspect a binding before verifying — then pass it back as Binder.held(binding).

Multiple credentials

Recovery can return several credentials, since different clients or re-signs can bind a manifest to the same content, so verify returns an array and your trust anchors decide which to honor. Each recovered manifest goes through the same full cryptographic validation as an embedded one. Map over the array (or filter by manifest.validation_state) instead of taking the first.

Nothing to recover

When neither the exact hash nor any binder recovers anything, verify resolves with an empty array — a normal "not recoverable" outcome, not a failure to surface as tampering or a broken signature. Treat it as "unsigned / unknown provenance" in your UI.

The verifier stays credential-free

There is no detect() on the verifier SDK and no watermark credential baked into it. Watermark detection is authed and server-side, so its endpoint and token live on the Binder.watermark(...) you construct — a browser verifier simply omits that binder and uses Binder.iscc() alone. Detection is no longer a separate SDK call; it's a binder you either hand to verify or call detect on.

See also