SDK Usage
The @certivu/sdk package is a pure HTTP wrapper around the Certivu API. All signing, watermarking, and hashing happens server-side — no private keys or crypto libraries needed in your application.
Install
Section titled “Install”npm install @certivu/sdkInitialize
Section titled “Initialize”import { CertivuClient } from '@certivu/sdk'
const certivu = new CertivuClient({ apiKey: 'ctv_key_abc123', // required generatorId: 'gen_xyz', // required for signing baseUrl: 'https://api.certivu.ai', // optional, default shown timeoutMs: 60000, // optional, per-request timeout (default 60s)})Sign content
Section titled “Sign content”const { token, record_id, signedContent } = await certivu.sign({ content: imageBuffer, // Buffer or Uint8Array model: 'stable-diffusion-xl',})// signedContent is a Uint8Array — the signed content with watermark and token embedded// save or serve signedContent; the original buffer is unchangedThe SDK POSTs content to POST /v1/sign as multipart. The API:
- Embeds the watermark ID via a format-appropriate resilient watermark
- Hashes the watermarked content with SHA-3
- Signs with ML-DSA-65 using the server-managed generator key
- Stores the provenance record
- Injects the
ctv_token into a format-native container (XMP, ID3v2, HTML meta,cervatom, etc.) - Returns the signed content bytes
If an identical input is signed twice, the API returns the existing record and sets deduplicated: true on the result (surfaced via the X-Certivu-Deduplicated response header) — no extra quota is consumed.
Batch sign
Section titled “Batch sign”Sign up to 50 items in one call. signBatch returns a positional array — each entry is a successful SignResponse or { error: string } for that item, so one failure never aborts the rest:
const results = await certivu.signBatch([ { content: image1, model: 'stable-diffusion-xl' }, { content: audio1, model: 'my-tts-v2' },])
for (const r of results) { if ('error' in r) console.error('sign failed:', r.error) else console.log('signed:', r.record_id)}For high throughput, call sign() directly with your own concurrency control.
Verify content
Section titled “Verify content”// Token provided — fast pathconst result = await certivu.verify({ content: imageBuffer, token: 'ctv_7f3kx9mq2...',})
// No token — auto-extracted from XMP then watermarkconst result = await certivu.verify({ content: imageBuffer })Result shape
Section titled “Result shape”{ authentic: boolean, tampered: boolean, confidence: 'high' | 'medium' | 'low' | 'none', token_source?: 'provided' | 'xmp' | 'watermark' | 'phash', signals: { watermark_found: boolean, record_found: boolean, signature_valid: boolean, phash_match: boolean, }, provenance?: { org: string, model: string, signed_at: string, }, reason?: string,}Batch verify
Section titled “Batch verify”For platforms processing many images at once (up to 50 per call):
const results = await certivu.verifyBatch([ { content: image1, token: token1 }, { content: image2 }, // token optional per item { content: image3, token: token3 },])Returns an array of VerificationResult in the same order.
Audit log
Section titled “Audit log”const log = await certivu.getAuditLog({ page: 1, limit: 50 })
// log.events — array of { event_id, type, timestamp, metadata }// log.total — total event countToken status
Section titled “Token status”Lightweight lookup by ctv_ token — no image upload needed. The response is CDN-cacheable (Cache-Control: public, max-age=3600), making it ideal for badge endpoints or status checks before embedding a token.
const status = await certivu.getTokenStatus('ctv_7f3kx9mq2...')
console.log(status.generator_status) // "active" | "revoked"console.log(status.model)console.log(status.signed_at)console.log(status.org_id)
// As of v2.4.0 the response also includes the org's display name and// branding (display_name, logo_url, primary_color, support_url) — used to// render org-branded badges and the public certificate page.console.log(status.org_name) // "Acme AI"console.log(status.branding) // { display_name, logo_url, primary_color, support_url } | nullThe branding fields power the public certificate page at https://certivu.ai/certificate?t=<ctv_token>. Configure them via PATCH /v1/account/branding (Growth+, owner/admin) — see Branded certificates.
Error handling
Section titled “Error handling”Any non-2xx API response throws a typed CertivuError. Inspect status (or the predicate getters) to branch on the failure kind; upgradeUrl is populated on 402 quota/plan errors when the API supplies it.
import { CertivuClient, CertivuError } from '@certivu/sdk'
try { const result = await certivu.sign({ content, model })} catch (err) { if (err instanceof CertivuError) { if (err.isQuota) { // 402 — signing quota or plan gate hit console.log('Upgrade at:', err.upgradeUrl) } else if (err.isAuth) { // 401 — invalid or missing API key console.error('Check your API key') } else { console.error(`Certivu ${err.status}:`, err.message) } } else { throw err // network/timeout or other error }}CertivuError exposes status, code (machine-readable error code, when present), and upgradeUrl, plus predicate getters: isValidation (400), isAuth (401), isQuota (402), isForbidden (403), and isNotFound (404).
| Status | Getter | Meaning |
|---|---|---|
400 | isValidation | Validation error or invalid signature |
401 | isAuth | Bad API key |
402 | isQuota | Signature quota exhausted or plan gate hit |
403 | isForbidden | Generator doesn’t belong to your org, or is revoked |
404 | isNotFound | Generator or resource not found |
Analytics
Section titled “Analytics”Fetch verification analytics for your org. Plan-gated: Free = 7-day window, Starter = 30-day, Growth+ = 90-day + per-record drill-down.
// Overview — pass days: 7 | 30 | 90 (default: max for your plan)const overview = await certivu.getAnalyticsOverview({ days: 30 })
console.log(overview.total_verifications)console.log(overview.tamper_count)console.log(overview.daily_trend) // Array<{ date, count, tamper_count }>console.log(overview.top_records) // Array<{ record_id, model, count, tamper_count }>
// Per-record drill-down (Growth+ required)const record = await certivu.getRecordAnalytics('rec-uuid')console.log(record.confidence_breakdown) // { high, medium, low, none }console.log(record.recent_events)Webhooks
Section titled “Webhooks”Manage webhook endpoints programmatically. Growth+ plan required to create endpoints.
// List all endpoints for your orgconst { endpoints, webhooks_enabled } = await certivu.listWebhooks()
// Register a new endpointconst endpoint = await certivu.createWebhook({ url: 'https://your-app.example.com/certivu', events: ['record.created', 'verify.tamper_detected', 'quota.warning'],})// endpoint.secret is shown once — store it immediately
// Remove an endpointawait certivu.deleteWebhook(endpoint.webhook_id)See the Webhooks guide for signature verification and payload shapes.
Compliance attestation
Section titled “Compliance attestation”Generate a factual attestation of your org’s provenance and verification activity, mapped to EU AI Act Article 50. Enterprise plan only.
// Fetch the attestation as JSONconst attestation = await certivu.getAttestation({ from: '2026-01-01', to: '2026-03-31',})
console.log(attestation.summary.records_signed)console.log(attestation.eu_ai_act_article_50.mapping)
// Export a downloadable JSON or HTML documentconst html = await certivu.exportAttestation({ format: 'html', // 'json' | 'html' from: '2026-01-01', to: '2026-03-31',})// html is the printable attestation document — write it to disk or attach to recordsAn attestation is a factual record of activity, not a certification, legal opinion, or guarantee of compliance. See the Attestation API reference.
Branded certificates
Section titled “Branded certificates”Growth+ orgs can put their own brand on the public certificate page at https://certivu.ai/certificate?t=<ctv_token>.
Branding is configured from the dashboard — open Settings → Certificate Branding (owner/admin only) and set a display name, logo URL, accent color, and support URL. It is not part of the API-key SDK surface; it is an account-level setting managed through the session-authenticated dashboard (PATCH /v1/account/branding under the hood).
Once set, the branding fields are returned on getTokenStatus() and rendered on the public certificate page and the <certivu-badge> widget:
const status = await certivu.getTokenStatus('ctv_7f3kx9mq2...')console.log(status.org_name) // 'Acme AI'console.log(status.branding) // { display_name, logo_url, primary_color, support_url } | nullPost-quantum C2PA
Section titled “Post-quantum C2PA”Embed a real, ML-DSA-signed C2PA manifest into a signed JPEG/PNG, bound to a record. Pass the signed asset bytes so the C2PA hard binding covers what Certivu verifies. Requires a Growth plan or above; assets up to 50 MB.
const embedded = await certivu.embedC2pa(recordId, signedImageBytes)// embedded: Uint8Array — the asset with the C2PA manifest embeddedThe manifest is interoperable with the Content Credentials ecosystem but signed with ML-DSA-65 instead of classical RSA/ECC — standard verifiers parse it but report the algorithm as unrecognized. Verify results also surface third-party C2PA signals (manifest_count, signature_issuer, is_certivu) on the c2pa field.
Other clients
Section titled “Other clients”- Python —
pip install certivu— see the Python SDK guide - CLI —
curl -fsSL https://certivu.ai/install.sh | sh— see the CLI guide - HTTP — all endpoints documented at api.certivu.ai/docs