Signing
POST /v1/sign — Server-side signing (recommended)
Section titled “POST /v1/sign — Server-side signing (recommended)”Upload content and receive it back watermarked and signed. The API handles format detection, watermark embedding, hashing, ML-DSA signing, and provenance token injection in one call. Counts as 1 signature against your plan quota.
Authentication required — Authorization: Bearer ctv_key_…
The generator must have been created without a manual public key (server-managed keypair). See generator registration.
Supported formats
Section titled “Supported formats”| Format | MIME types | Watermark | Token container |
|---|---|---|---|
| Image | image/jpeg, image/png, image/webp | DCT spread-spectrum | XMP metadata |
| Audio | audio/mpeg (MP3), audio/flac, audio/wav | DCT frame watermark (WAV 16-bit and 24-bit PCM) | ID3v2 TXXX / VORBIS_COMMENT / CTV RIFF chunk |
| Text | application/pdf, text/html, text/plain | ZWC steganography (plain text across all paragraphs; HTML body text nodes) | PDF /Metadata XMP stream / HTML <meta> |
| Video | video/mp4, video/quicktime (MOV), video/x-matroska (MKV), video/webm | pHash of first I-frame (server-side, via ffmpeg) | MP4/MOV: custom cerv atom · MKV/WebM: Certivu sentinel trailer |
Generators must declare which formats they support. Signing with an unlisted format returns 400 format_not_supported.
Request
Section titled “Request”POST /v1/signContent-Type: multipart/form-dataAuthorization: Bearer ctv_key_abc123| Field | Type | Required | Description |
|---|---|---|---|
content | file | Yes | Content to sign (image, audio, text, or video) |
model | string | Yes | AI model name (e.g. stable-diffusion-xl) |
generator_id | string | Yes | ID of the registered generator |
format | string | No | "image", "audio", "text", or "video". Auto-detected from magic bytes if omitted. |
Response 201
Section titled “Response 201”Signed content bytes (same format as input) with:
| Header | Description |
|---|---|
Content-Type | Format-appropriate MIME type |
X-Certivu-Token | ctv_… token — also embedded in content metadata |
X-Certivu-Record-Id | UUID of the stored provenance record |
X-Certivu-Format | Detected/applied format: image, audio, text, or video |
The returned content has:
- The
ctv_token injected into a format-appropriate container (XMP, ID3v2, VORBIS_COMMENT, RIFF chunk, PDF XMP, HTML meta, or ZWC) - The
watermark_idembedded via a format-appropriate resilient watermark - A SHA-3 content hash and ML-DSA signature stored in the Certivu record
Errors
Section titled “Errors”| Status | Code | Meaning |
|---|---|---|
400 | format_not_supported | Generator’s supported_formats does not include the detected/specified format |
400 | — | Missing fields or generator uses client-side keypair |
401 | — | Missing or invalid API key |
402 | signature_limit_reached | Free tier monthly limit hit |
403 | — | Generator does not belong to your organization |
404 | — | Generator not found or revoked |
413 | — | Content exceeds 50 MB |
SDK usage
Section titled “SDK usage”// Image (auto-detected)const { signedContent, token, record_id, format } = await certivu.sign({ content: imageBuffer, model: 'stable-diffusion-xl',})
// Audio (explicit format)const result = await certivu.sign({ content: mp3Buffer, model: 'musicgen-large', format: 'audio',})
// Textconst result = await certivu.sign({ content: pdfBuffer, model: 'gpt-4o', format: 'text',})
// Video (auto-detected from MP4/MOV/MKV/WebM magic bytes)const result = await certivu.sign({ content: mp4Buffer, model: 'sora-v1', format: 'video',})// result.signedContent has the ctv_ token embedded in a custom container atom/trailerPOST /v1/records — Client-side signing (advanced)
Section titled “POST /v1/records — Client-side signing (advanced)”For generators with client-managed keypairs. You compute the ML-DSA signature locally and submit the record. The content is not uploaded — only the hash and signature are stored.
Authentication required — Authorization: Bearer ctv_key_…
Request
Section titled “Request”POST /v1/recordsContent-Type: application/jsonAuthorization: Bearer ctv_key_abc123{ "watermark_id": "wm_7f3kx9mq2", "generator_id": "gen_abc123", "model": "stable-diffusion-xl", "content_hash": "sha3-256:a3f1...", "signature": "<ml-dsa base64>", "signed_payload": { "content_hash": "sha3-256:a3f1...", "generator_id": "gen_abc123", "model": "stable-diffusion-xl", "watermark_id": "wm_7f3kx9mq2" }, "phash": "a3f1c2d4e5b6a7f8"}| Field | Type | Required | Description |
|---|---|---|---|
watermark_id | string | Yes | Opaque ID embedded in the content watermark |
generator_id | string | Yes | ID of the registered generator that signed this |
model | string | Yes | Model name (e.g. stable-diffusion-xl) |
content_hash | string | Yes | sha3-256:<64 hex chars> — SHA-3 hash of the content |
signature | string | Yes | ML-DSA signature (base64) over signed_payload canonical JSON |
signed_payload | object | Yes | The exact payload that was signed |
phash | string | No | 16-char lowercase hex perceptual hash (64-bit DCT pHash). Enables fuzzy lookup (images and video) at verify time if container token or watermark is absent. Compute via computePhash() in @certivu/crypto. |
The phash field is outside the signed payload — it is a similarity lookup aid, not a security-critical field.
POST /v1/records/batch — Batch client-side signing (Growth+)
Section titled “POST /v1/records/batch — Batch client-side signing (Growth+)”Submit up to 50 records in a single request. Each item is processed in parallel and returns its own result. Batch signing requires Growth or above.
Authentication required — Authorization: Bearer ctv_key_…
POST /v1/records/batchContent-Type: application/jsonAuthorization: Bearer ctv_key_abc123{ "records": [ { "watermark_id": "wm_7f3kx9mq2", "generator_id": "gen_abc123", "model": "stable-diffusion-xl", "content_hash": "sha3-256:a3f1...", "signature": "<ml-dsa base64>", "signed_payload": { "..." : "..." } }, { "..." : "..." } ]}Each item in records follows the same schema as the single POST /v1/records body.
Response 207 Multi-Status
Section titled “Response 207 Multi-Status”{ "results": [ { "token": "ctv_7f3kx9mq2...", "record_id": "rec_uuid_1" }, { "token": "ctv_abc9876...", "record_id": "rec_uuid_2" } ]}Results are returned in the same order as the input array.
Errors
Section titled “Errors”| Status | Code | Meaning |
|---|---|---|
400 | — | Validation failed or any signature invalid |
402 | plan_required | Batch signing requires Growth or above |
402 | signature_limit_reached | Quota exhausted |
413 | — | Request body exceeds 10 MB |
Response 201
Section titled “Response 201”{ "token": "ctv_7f3kx9mq2...", "record_id": "rec_uuid"}| Field | Description |
|---|---|
token | Compact signed artifact — embed in XMP metadata or pass alongside content |
record_id | UUID of the stored provenance record |
Errors
Section titled “Errors”| Status | Code | Meaning |
|---|---|---|
400 | — | Validation failed or signature invalid |
401 | — | Missing or invalid API key |
402 | signature_limit_reached | Free tier monthly limit hit — upgrade to continue |
403 | — | Generator does not belong to your organization |
404 | — | Generator not found or revoked |
402 body
Section titled “402 body”{ "error": "signature_limit_reached", "message": "Free tier limit of 500 signatures/month reached. Upgrade at https://certivu.ai/pricing", "upgrade_url": "https://certivu.ai/pricing", "limit": 500, "current": 500}Fetch a record
Section titled “Fetch a record”GET /v1/records/:record_idReturns the full ProvenanceRecord. No authentication required.
Export C2PA manifest
Section titled “Export C2PA manifest”GET /v1/records/:record_id/c2paReturns a C2PA-compatible manifest JSON for the record. No authentication required. Cached for 1 hour.
{ "@context": { "c2pa": "https://c2pa.org/claims/v1", "certivu": "https://certivu.com/ns/" }, "claim_generator": "Certivu/2.5.0", "instance_id": "certivu:rec_uuid", "assertions": [ { "label": "c2pa.actions", "data": { "actions": [{ "action": "c2pa.created", "when": "..." }] } }, { "label": "certivu.provenance", "data": { "record_id": "...", "model": "...", "signature_algorithm": "ML-DSA-65 (NIST FIPS 204)" } }, { "label": "c2pa.hash.data", "data": { "name": "SHA3-256", "hash": "..." } } ]}This is a compatibility export following C2PA assertion conventions — it is not a fully-signed C2PA manifest. For a real embedded manifest, use the embed endpoint below.
Embed a post-quantum C2PA manifest
Section titled “Embed a post-quantum C2PA manifest”POST /v1/records/:record_id/c2pa/embedAuthorization: Bearer ctv_key_...Content-Type: multipart/form-dataEmbeds a real, ML-DSA-signed C2PA manifest into an uploaded JPEG or PNG, bound to the record. Unlike the JSON export above, this produces an actual embedded manifest (JUMBF) with a hard binding to the asset’s bytes.
Requires a Growth plan or above. Maximum asset size is 50 MB. The record was already charged at sign time, so embedding does not consume an additional signature.
| Field | Type | Description |
|---|---|---|
content | file | The signed JPEG/PNG asset (so the C2PA hard binding covers the same bytes Certivu verifies) |
Returns the asset with the manifest embedded (image/jpeg or image/png).
| Status | Code | Meaning |
|---|---|---|
402 | plan_required | Post-quantum C2PA embedding requires Growth or above |
403 | — | Generator has been revoked |
413 | — | Asset exceeds the 50 MB maximum |
curl -X POST https://api.certivu.ai/v1/records/$RECORD_ID/c2pa/embed \ -H "Authorization: Bearer $CERTIVU_API_KEY" \The manifest is signed with ML-DSA-65 (NIST FIPS 204) — “post-quantum C2PA”. It is structurally interoperable with the Content Credentials ecosystem, but standard C2PA verifiers will report the signature algorithm as unrecognized, since the industry currently expects classical RSA/ECC signatures. This is a deliberate, quantum-resistant trade-off — Certivu’s own verification validates the ML-DSA signature. Requires a generator with a server-managed keypair (created for POST /v1/sign).
Usage notes
Section titled “Usage notes”- Prefer
POST /v1/sign— it handles watermarking, hashing, signing, and token injection for you and returns signed, ready-to-use content - For
POST /v1/records:content_hashmust use formatsha3-256:<64 lowercase hex chars> signed_payloadmust be the exact object that was canonically serialized and signed — verification replays this- Compute
phashusingcomputePhash(rgbPixels, width, height)from@certivu/crypto— returns a 16-char lowercase hex string