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:
- Embedded manifest — if the asset still carries one, that wins.
- 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.
- 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 (endpointis 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.