Skip to content

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.

Terminal window
npm install @certivu/sdk
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)
})

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 unchanged

The SDK POSTs content to POST /v1/sign as multipart. The API:

  1. Embeds the watermark ID via a format-appropriate resilient watermark
  2. Hashes the watermarked content with SHA-3
  3. Signs with ML-DSA-65 using the server-managed generator key
  4. Stores the provenance record
  5. Injects the ctv_ token into a format-native container (XMP, ID3v2, HTML meta, cerv atom, etc.)
  6. 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.


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.


// Token provided — fast path
const result = await certivu.verify({
content: imageBuffer,
token: 'ctv_7f3kx9mq2...',
})
// No token — auto-extracted from XMP then watermark
const result = await certivu.verify({ content: imageBuffer })
{
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,
}

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.


const log = await certivu.getAuditLog({ page: 1, limit: 50 })
// log.events — array of { event_id, type, timestamp, metadata }
// log.total — total event count

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 } | null

The 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.


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).

StatusGetterMeaning
400isValidationValidation error or invalid signature
401isAuthBad API key
402isQuotaSignature quota exhausted or plan gate hit
403isForbiddenGenerator doesn’t belong to your org, or is revoked
404isNotFoundGenerator or resource not found

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)

Manage webhook endpoints programmatically. Growth+ plan required to create endpoints.

// List all endpoints for your org
const { endpoints, webhooks_enabled } = await certivu.listWebhooks()
// Register a new endpoint
const 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 endpoint
await certivu.deleteWebhook(endpoint.webhook_id)

See the Webhooks guide for signature verification and payload shapes.


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 JSON
const 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 document
const 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 records

An attestation is a factual record of activity, not a certification, legal opinion, or guarantee of compliance. See the Attestation API reference.


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 } | null

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 embedded

The 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.