CMS Integration
Automatically sign every asset your CMS publishes with two API calls. Works with Contentful, Sanity, Strapi, WordPress, S3 lifecycle events, or any webhook-capable system.
Why automate?
Manual signing works for one-off assets but doesn't scale. An automated pipeline means every published image, video, or document is signed before it reaches your audience — without changing your editorial workflow.
Zero friction
Editors publish as normal. Signing happens in the background via a webhook or CI step.
Idempotent
Pass the same recordId twice and NotarAI returns the existing record rather than creating a duplicate.
Provenance-first
The verifyUrl is permanent and safe to store in your CMS. Download the signed binary immediately and host it yourself.
The two-step flow
Every integration follows the same pattern regardless of which CMS you use:
POST /v1/scans
Upload the asset. Returns a recordId and metadata.
POST /v1/sign
Attach a declaration to the record. Returns a 30-minute download link for the signed binary and a permanent verifyUrl.
Warning
verifyUrl is permanent.Note
POST /v1/sign-jobs. See the Bulk Jobs section of the main docs.Integration examples
Pick your language below. Each tab shows the full scan → sign → download → re-upload flow. All examples use signedAssetUrl — a 30-minute pre-signed URL you must download immediately and push to your own storage.
Storing verifyUrl in your CMS
The sign response always includes verifyUrl— a permanent public URL that anyone can use to verify the asset's authenticity without a NotarAI account. Store it alongside the asset in your CMS so you can surface it in your UI.
| Field | Type | Description |
|---|---|---|
| recordId | string | Stable ID for this asset record. Use as existingRecordId to update an existing signed asset. |
| signedAssetUrl | string | 30-minute pre-signed download URL for the C2PA-embedded binary. Fetch immediately and re-upload to your own storage. Do not store or serve this URL. |
| verifyUrl | string | Permanent public verification URL. Safe to embed in your CMS and share publicly. |
| certificateUrl | string | URL to the notarisation certificate PDF. |
Error handling & retries
Rate limits
A 429 Too Many Requestsresponse means you've hit your plan's monthly quota. Inspect the Retry-After header for the reset time, or check your usage in the dashboard. Burst spikes within a month are also guarded per-minute — scan up to 60 requests/min and sign up to 30 requests/min.
Idempotency with existingRecordId
If the same asset is published more than once (e.g., a CMS re-publish), pass the original recordId as existingRecordId in the sign body. NotarAI will update the existing record instead of creating a duplicate.
{
"existingRecordId": "rec_01abc...",
"declaration": {
"aiModel": null,
"modificationType": "edit",
"purpose": "editorial",
"humanReview": true
}
}Exponential back-off pattern
async function fetchWithRetry(
url: string,
init: RequestInit,
maxAttempts = 4
): Promise<Response> {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const res = await fetch(url, init);
if (res.status !== 429 && res.status !== 503) return res;
const retryAfter = Number(res.headers.get("Retry-After") ?? 2 ** attempt);
await new Promise((r) => setTimeout(r, retryAfter * 1000));
}
throw new Error(`Gave up after ${maxAttempts} attempts`);
}Core API reference
Full parameter documentation for POST /v1/scans, POST /v1/sign, and GET /v1/verify/:id.