Skip to content

Local mode's signature

POSThttps://api.kennmarq.com/v1/sign

In local mode the watermark and the Content Credentials are made on your machine, and the file never leaves it. Only its manifest comes here, to be signed with our key and time-stamped by our server. The Python SDK and the ComfyUI nodes do all of this for you; this page is for building it yourself.

How it works

  1. Make the manifest and embed it with the C2PA library (c2pa-python 0.38.1), signed with a throwaway certificate chain you make for the purpose. C2PA checks every signature against its certificate, so it can't sign with a key it doesn't hold.
  2. Send us the manifest store it embedded: about 13 KB of claim and assertions, never pixels. Turn thumbnails off.
  3. We check it (below), sign the same claim with our key, time-stamp the signature, and send the store back, the same size, with our signature in place of the throwaway one.
  4. Put it back in your file where the old one was. The file's hard binding leaves the manifest out, so it still holds. Then read the file with our roots (GET /v1/certificate) before you use it: it should read Trusted, with a validated time-stamp.
curl
curl https://api.kennmarq.com/v1/sign \
  -H "Authorization: Bearer $KENNMARQ_API_KEY" \
  -H "Content-Type: application/c2pa" \
  --data-binary @manifest.c2pa -D - -o signed.c2pa

What we sign, and what we don't

We sign only what a cloud mark of ours would carry, so our signature can't be lent to other claims:

What a manifest must hold for us to sign it
CheckIn detail
Every assertion the claim lists is in the manifest, with the hash the claim gives itand the manifest holds none it doesn't list
Only the assertions a mark carriesthe actions, the hard binding, the watermark's soft binding, the generator, ingredients: no thumbnail, which would hold pixels
An AI source typec2pa.created or c2pa.opened with trainedAlgorithmicMedia or compositeWithTrainedAlgorithmicMedia: we sign only AI-made files
A watermark comes with its actionc2pa.watermarked.bound and a TrustMark soft binding, both or neither
Our generatorthe claim names Kennmarq.com as its generator, and its hash algorithm is SHA-256

Each manifest is signed once: make a new one for each file.

Response

200, application/c2pa: the signed store, with X-Mark-Id, X-Watermark-Id (when the manifest has a watermark), X-Log-Index, X-Marks-Used, X-Marks-Limit and X-Request-Id. Each signature counts one mark.

  • The signature goes into our transparency log as a c2pa.local_signature entry. We never see your file, so the entry holds its hard binding (the hash in the manifest's c2pa.hash.data assertion) instead of its SHA-256.
  • With a watermark, the signed store is kept as the file's durable credential, without a file hash: a copy whose metadata was stripped still leads to it.
Credentials your file already carried travel with the manifest as ingredients, with any thumbnail their signer put in them. Leave them out if they shouldn't reach us.

Errors

Errors of POST /v1/sign
StatusCodeWhen
400invalid_requestNot a C2PA manifest store, a manifest signed before, or one we won't sign; the message says which check failed.
401missing_api_key, invalid_api_key, api_key_revokedSee authentication.
402quota_exceededThe month's marks are used up.
403insufficient_scope, ip_not_allowedThe key lacks the sign scope, or the address isn't allowlisted.
413payload_too_largeThe store is over 1 MB.
415unsupported_media_typeThe body isn't sent as application/c2pa.
429rate_limitedToo many calls this minute; retry after Retry-After seconds.
502mark_failedThe signature couldn't be made and checked. Nothing is charged.
503marker_unavailable, tsa_unavailableThe signing service or the time-stamp server is down for a moment. Nothing is charged; retry.

Error bodies follow the common format.