Skip to main content

Sign an asset

AssetSdk signs a single finished file: a photo, an audio track, or a video. sign accepts the asset bytes and returns the C2PA sidecar (the manifest as a standalone byte string) plus the registry manifestId it was stored under.

A complete example

publish-photo.ts
import { SignerClient, AssetSdk, embed } from "@limboai/verifox-sdk-issuer";
import { readFile, writeFile } from "node:fs/promises";

const client = await SignerClient.connect("https://api.trylimbo.com", {
headers: { authorization: `Bearer ${process.env.LIMBO_API_KEY}` },
});
const sdk = AssetSdk.create(client);

const edited = await readFile("edited.jpg");

const { sidecar } = await sdk.sign(edited, "photo.jpg", {
origin: { sourceType: "digitalCapture" },
actions: [{ kind: "edited" }, { kind: "published" }],
}, { storage: { mode: "embedded" } });

const signed = embed(edited, sidecar);
await writeFile("edited.signed.jpg", signed);

Connect once at startup and reuse the client; see Client.

The sign method

sign(
data: Uint8Array | RangeSource,
title: string,
manifest: ManifestSpec,
options: SignOptions,
): Promise<SignOutcome>

interface SignOutcome {
sidecar: Uint8Array; // C2PA manifest — use embed() to write it into the asset
manifestId: string; // registry id the signer stored the manifest under
}

interface SignOptions {
storage: ManifestStorage; // where the manifest lives, and its soft bindings
format?: string; // inferred for Uint8Array; required for RangeSource
}

type ManifestStorage =
| { mode: "embedded" }
| { mode: "remote"; bindings: SoftBinding[] }
| { mode: "both"; bindings: SoftBinding[] };

format is inferred from data when data is a Uint8Array (by content sniffing); pass it explicitly via options.format for a RangeSource.

  • data: asset bytes (Uint8Array) or a sync RangeSource for Node.js large files. Asset bytes never leave your process; the SDK hashes locally and sends only the hash. See File streaming for RangeSource.
  • title: the manifest title — a human-readable name of the asset.
  • manifest: the ManifestSpec — provenance only. Required: origin (CreatedOrigin | OpenedOrigin). Optional: identity, components, actions, assertions, and thumbnail. You supply provenance facts; the signer authors every C2PA structural assertion (lifecycle actions, ingredient relationships, hard bindings) from them.
  • options: required. storage — a ManifestStorage selecting the delivery mode via mode ({ mode: "embedded" }, { mode: "remote", bindings }, or { mode: "both", bindings }; see Storage modes). On the persisted modes, bindings are the soft bindings to index the manifest under — a Binding.iscc(image) code, a watermark token, or an out-of-band video ISCC, each an { alg, value } recoverable independently (embedded has no bindings field, so the pairing is impossible). format — the asset MIME type, inferred from data when omitted (required when data is a RangeSource — see Supported formats). See Soft binding.

Before anything is sent, sign validates the request locally against the SDK's request schema — required fields, action/component reference integrity, and the reserved assertion namespaces. A malformed request rejects synchronously with an SdkError (kind: "invalid", one fieldViolation per bad field) instead of after a round-trip. (The deployment's assertion allowlist is enforced by the service, not pre-checked locally.)

The return value is { sidecar, manifestId }: sidecar is the C2PA manifest as a standalone byte string (independent of the asset container); manifestId is the registry id the signer stored the manifest under.

What a sidecar is

You then choose what to do with the sidecar: embed it back into the asset container, or distribute it as a separate .c2pa file. That choice is the asset's storage mode, which also governs whether the manifest is additionally indexed for remote recovery.

Supported formats

Provide the correct MIME type and the SDK selects the appropriate hashing and embedding strategy.

CategoryFormats
ImagesJPEG, PNG, WebP, TIFF/DNG, AVIF, HEIC/HEIF, GIF, JPEG XL, SVG
Video filesMP4, MOV
AudioMP3, FLAC, WAV, M4A

A container format with no C2PA byte layout of its own, such as MXF, is signed through an opt-in container plugin, not the core signer.

See also