Vérifier un fichier
POST https://api.kennmarq.com/v1/verify
GET https://api.kennmarq.com/v1/credentials/{watermark_id}
GET https://api.kennmarq.com/v1/verify/key
Ce que disent le manifeste C2PA et les filigranes invisibles d’une image, d’un morceau ou d’une vidéo, qu’ils viennent de nous ou de n’importe qui d’autre ; sur demande, sous forme de résultat signé par nous. Gratuit, sans clé. La page du vérificateur fait de même pour les images, dans votre navigateur, sans rien envoyer.
POST /v1/verify
Envoyez l’image en multipart/form-data dans file (PNG, JPEG ou WebP ; la requête fait au plus 4 Mo). Ajoutez signed=true pour obtenir un résultat signé. Nos serveurs de Zurich lisent le fichier en mémoire et ne conservent rien. Une image plus lourde, jusqu’à 25 Mo, passe par un envoi indiqué dans upload, ce qui nécessite la clé qui l’a créé ; ou vérifiez-la sur la page du vérificateur, dans votre navigateur, quelle que soit sa taille jusqu’à 25 Mo. Les morceaux (MP3, WAV, M4A) et les vidéos (MP4) sont lus de la même façon sur nos serveurs, jusqu’à 25 Mo, avec une clé (n’importe laquelle des vôtres, gratuitement ; rien n’est décompté) : voir morceaux et vidéos.
curl https://api.kennmarq.com/v1/verify -F file=@picture.jpg -F signed=true{
"object": "check",
"checked_at": "2026-11-12T10:00:00Z",
"checker": { "name": "Kennmarq.com", "version": "0.4.0" },
"file": { "type": "image/jpeg", "bytes": 482113, "width": 1024, "height": 768, "sha256": "…" },
"c2pa": {
"state": "Trusted",
"manifest": "urn:c2pa:3fa85f64-…",
"signer": { "common_name": "Kennmarq.com", "issuer": "Neuramare", "ours": true, "environment": "live" },
"signed_at": "2026-11-12T09:58:31+00:00",
"time_stamp": { "validated": true, "trusted": true },
"claim_generator": "Kennmarq.com 0.4.0",
"ai": { "digital_source_type": "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia", "generated": true },
"soft_binding": { "alg": "com.adobe.trustmark.Q", "value": "1*0101…" },
"failures": []
},
"watermark": { "found": true, "alg": "com.adobe.trustmark.Q", "schema": "BCH_5", "id": "1d455a804e9802a6", "matches_manifest": true },
"durable_credential": {
"watermark_id": "1d455a804e9802a6",
"manifest": "urn:c2pa:3fa85f64-…",
"environment": "live",
"asset_sha256": "…",
"same_file": true,
"log_index": 1834001,
"url": "https://api.kennmarq.com/v1/credentials/1d455a804e9802a6"
},
"log": { "leaf_index": 1834001, "status": "included", "proof_url": "https://api.kennmarq.com/v1/log/proof/1834001" },
"signed": { "jws": "eyJhbGciOiJFUzI1NiIs…", "key_id": "…", "log_index": 1834530 }
}c2pavautnullquand le fichier ne porte aucun manifeste.statevautTrustedquand la signature remonte à nos racines (ours),Validpour un autre signataire dont la signature est valide, etInvalidquand le fichier a été modifié après sa signature ;failuresen liste les codes.watermark.idest renseigné pour les filigranes de notre schéma (BCH_5) ; les autres filigranes TrustMark apparaissent comme trouvés, avec leur schéma, quand le centre de l’image donne exactement la même lecture, pour qu’une image sans marquage n’en affiche pas un par hasard. Le nôtre se lit aussi dans une image enregistrée sur le côté ou à l’envers.durable_credential: ce à quoi le filigrane mène dans nos registres, quand c’est nous qui avons signé le fichier. Quand une plateforme supprime les métadonnées, c’est ainsi que l’on retrouve les Content Credentials ;same_fileindique s’il s’agit exactement du fichier que nous avons signé, et vautnullpour un fichier marqué en mode local, qui ne nous est jamais parvenu.log: l’entrée du manifeste dans notre journal de transparence.- Morceaux et vidéos :
filecomporteseconds(et, pour une vidéo, sa taille) ;watermarkscomporte la lecture Video Seal de la vidéo (id, lu sur 64 images, etagreement, la part de ses 256 bits qui concordent) et celle d’AudioSeal (detected, la part du son lue comme marquée, etbitssur 16) ;c2paajouteclaims, les filigranes que le fichier porte selon le manifeste, et, pour une vidéo,watermark_record.watermarkest alors le Video Seal de la vidéo, qui mène à ses Content Credentials durables comme celui d’une image, ou l’AudioSeal du morceau, qui indique que le fichier a été marqué par nous mais ne porte aucun ID.
Résultats signés
Avec signed=true, signed.jws est un JWS (RFC 7515, compact) signé en ES256 par notre clé de vérificateur : sa charge utile est le résultat ci-dessus (le fichier, son manifeste et son filigrane, l’heure), plus iss, iat et un jti unique ; le kid de son en-tête désigne la clé. Avant d’être renvoyé, il est inscrit dans notre journal de transparence sous la forme d’une entrée checker.result (le SHA-256 du JWS, celui du fichier, et l’ID de la clé), à l’index indiqué dans X-Log-Index. N’importe quelle bibliothèque JWT le vérifie avec la clé fournie par GET /v1/verify/key.
import { decodeProtectedHeader, importSPKI, jwtVerify } from "jose";
const { jws } = result.signed;
const { kid } = decodeProtectedHeader(jws);
const key = await (await fetch(`https://api.kennmarq.com/v1/verify/key?key_id=${kid}`)).json();
const { payload } = await jwtVerify(jws, await importSPKI(key.public_key, "ES256"), { issuer: "Kennmarq.com" });
// payload.file.sha256 is the SHA-256 of the file that was checked; compare it with yours.
console.log(payload.file.sha256, payload.checked_at);GET /v1/credentials/{watermark_id}
Le magasin de manifestes C2PA auquel mène un filigrane, conservé depuis le moment où nous avons signé le fichier : en JSON (le magasin en base64 dans manifest_store), ou avec ?format=c2pa sous forme de fichier .c2pa que n’importe quel outil C2PA sait lire. Quiconque peut lire le filigrane peut le récupérer, c’est-à-dire quiconque détient l’image.
curl "https://api.kennmarq.com/v1/credentials/1d455a804e9802a6?format=c2pa" -o credential.c2paGET /v1/verify/key
La clé publique du vérificateur : key_id (SHA-256 de son SubjectPublicKeyInfo en DER), algorithm et public_key (PEM). Avec ?key_id= et le kid d’un JWS, la clé qui l’a signé, y compris les clés avec lesquelles nous ne signons plus ; sans ce paramètre, la plus récente.
Erreurs
| Statut | Code | Quand |
|---|---|---|
| 400 | invalid_request | Aucun fichier, un champ envoyé deux fois, un signed autre que true ou false, une image illisible, ou un ID de filigrane ou un key_id mal formé. |
| 400 | unknown_field | Un champ autre que file (ou upload) et signed. |
| 401 | missing_api_key, invalid_api_key, api_key_revoked | Un envoi a été indiqué sans la clé qui l’a créé. |
| 400 | option_not_available | url : la vérification d’un fichier par URL n’est pas encore disponible. |
| 404 | not_found | Pas de Content Credentials pour cet ID de filigrane, ou pas de clé de vérificateur avec ce key_id. |
| 404 | upload_not_found | L’envoi indiqué n’est pas utilisable par cette clé : inconnu, déjà utilisé, expiré ou appartenant à une autre clé. |
| 413 | payload_too_large | La requête dépasse 4 Mo : vérifiez les images plus lourdes sur la page du vérificateur, ou envoyez les fichiers via un envoi (jusqu’à 25 Mo). |
| 415 | unsupported_media_type | Pas en multipart, ou pas une image, un morceau ou une vidéo que nous savons lire. |
| 429 | rate_limited | Plus de 10 vérifications par minute ou de 200 par jour depuis une même adresse (une adresse IPv6 compte selon son /64 ; recherches : 120 par minute), les vérifications sans clé du jour sont épuisées pour tout le monde, ou, avec une clé, les appels par minute de votre organisation. |
| 503 | marker_unavailable, storage_unavailable | Le vérificateur (ou les envois) est indisponible, ou un résultat signé n’a pas passé la vérification avant son envoi. Réessayez. |