Skip to content

Python SDK

The certivu Python package provides sync and async clients for signing and verifying AI-generated content. It is designed for AI/ML pipelines where Python is the primary language.

Terminal window
pip install certivu

Requires Python 3.9+.

from certivu import CertivuClient
client = CertivuClient(
api_key="ctv_key_abc123",
generator_id="your-generator-uuid", # required for signing
)

Config can also come from environment variables:

Terminal window
export CERTIVU_API_KEY=ctv_key_abc123
export CERTIVU_GENERATOR_ID=your-generator-uuid

import asyncio
from pathlib import Path
image_bytes = Path("output.jpg").read_bytes()
result = client.sign(
content=image_bytes,
model="stable-diffusion-xl",
)
print(result.token) # ctv_7f3kx9mq2...
print(result.record_id) # rec-uuid
print(len(result.watermarked_content)) # signed, watermarked image bytes — use this one

The API handles watermarking, hashing, and ML-DSA signing server-side. result.watermarked_content is the image with the ctv_ token in XMP metadata and the watermark embedded in the frequency domain.


Verification is always free — no API key required.

result = client.verify(content=image_bytes)
if result.authentic and result.confidence == "high":
print(f"Verified — {result.provenance.org} · {result.provenance.signed_at}")
elif result.tampered:
print("Content has been modified since signing")
else:
print(f"Not verified: {result.reason}")

Pass a token explicitly to skip watermark extraction:

result = client.verify(content=image_bytes, token="ctv_7f3kx9mq2...")
LevelSignalsMeaning
highWatermark ✓ + Record ✓ + Signature ✓Full chain intact
mediumRecord ✓ + Signature ✓Re-uploaded without watermark
lowPartialSomething is off
noneNothing foundNot signed by Certivu

Lightweight lookup without re-uploading the image — CDN-cacheable:

status = client.get_token_status("ctv_7f3kx9mq2...")
print(status.generator_status) # "active" or "revoked"
print(status.signed_at)

Up to 50 items per call:

results = client.verify_batch([
{"content": image1_bytes},
{"content": image2_bytes, "token": "ctv_..."},
])
for r in results:
print(r.authentic, r.confidence)

Up to 50 records per call (HTTP 207 multi-status):

results = client.sign_batch([
{"content": img1, "model": "sdxl"},
{"content": img2, "model": "sdxl"},
])
for r in results:
if "error" in r:
print("Failed:", r["error"])
else:
print(r["token"])

page = client.get_audit_log(page=1, limit=50)
for event in page.events:
print(event.type, event.timestamp)
print(f"Total: {page.total}")

All methods are available on AsyncCertivuClient:

from certivu import AsyncCertivuClient
async def main():
async with AsyncCertivuClient(api_key="ctv_key_abc123") as client:
result = await client.verify(content=image_bytes)
print(result.confident, result.confidence)
results = await client.verify_batch([
{"content": img} for img in image_list
])
asyncio.run(main())

from certivu import AuthError, QuotaError, NotFoundError, CertivuError
try:
result = client.sign(content=image_bytes, model="sdxl")
except QuotaError as e:
print("Quota exceeded. Upgrade at:", e.upgrade_url)
except AuthError:
print("Invalid API key")
except CertivuError as e:
print(f"Error {e.status_code}: {e}")

The Python SDK lives at packages/sdk-python/ in the Certivu repository.