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:

1

POST /v1/scans

Upload the asset. Returns a recordId and metadata.

2

POST /v1/sign

Attach a declaration to the record. Returns a 30-minute download link for the signed binary and a permanent verifyUrl.

Warning

signedAssetUrl expires in 30 minutes. It is a one-time download link — not a permanent hosting URL. You must fetch the binary immediately after signing and upload it to your own CDN, S3 bucket, or CMS asset library. Only verifyUrl is permanent.

Note

You can also sign a batch of assets in one job with 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.

typescript

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.

FieldTypeDescription
recordIdstringStable ID for this asset record. Use as existingRecordId to update an existing signed asset.
signedAssetUrlstring30-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.
verifyUrlstringPermanent public verification URL. Safe to embed in your CMS and share publicly.
certificateUrlstringURL 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.

json
{
  "existingRecordId": "rec_01abc...",
  "declaration": {
    "aiModel": null,
    "modificationType": "edit",
    "purpose": "editorial",
    "humanReview": true
  }
}

Exponential back-off pattern

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

View API docs →