Developer Documentation
The NotarAI REST API lets you integrate EU AI Act Article 50 compliance directly into your CI/CD pipelines, CMS platforms, and custom tooling. Scan files for AI metadata, apply C2PA manifests, and manage signing workflows programmatically.
Introduction
The NotarAI API is a REST API that accepts JSON bodies and returns JSON responses. All requests must be made over HTTPS. HTTP requests are rejected.
https://notarai.io/api/v1Authorization: Bearer <token>application/json (or multipart/form-data for file uploads)Path-based — /api/v1/...Responses use standard HTTP status codes. Successful responses return a JSON object with the resource data. Errors return a JSON object with an error string field and an optional code machine-readable identifier.
Storage retention policy
Uploaded and generated files are not retained forever. NotarAI applies automatic retention windows to control storage costs and keep account usage predictable.
| Object type | Retention |
|---|---|
| Original uploaded file (unsigned) | ~15 minutes after successful sign, or 72h for abandoned pending/error records |
| Signed output | Plan-based: Starter 7 days, Business 21 days, Enterprise 45 days |
| PDF certificate | Same retention as signed output |
Note
Tip
Authentication
All API requests are authenticated with a Bearer token. Tokens are created in the Developer settings and take the form ntr_live_key_<keyId>_<secret>.
Include the token in every request using the Authorization header:
Authorization: Bearer ntr_live_key_a1b2c3d4e5f6_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxWarning
Scopes
Each API key is created with one or more scopes. Requests that require a scope your key does not have will be rejected with 403 Forbidden.
| Scope | Access |
|---|---|
scan | Upload files and trigger AI metadata scans |
sign | Apply C2PA manifests and sign files |
jobs.read | List collections/assets and assign existing assets to collections |
workflows.read | List and inspect your signing workflows |
verify.read | Retrieve public verification records |
Quick Start
The typical workflow is: scan → sign → verify. Scan uploads a file and extracts AI metadata. Sign applies a C2PA manifest with a disclosure declaration. The sign wizard now includes an explicit Article 50 acknowledgment before the final submit action. Use sign-jobs and scan-jobs for large-volume async processing.
Standard Workflow (Start Here)
For most teams, the fastest path is the synchronous flow below. You can validate value end-to-end without job queues, polling, or webhooks.
| Step | Endpoint | Purpose |
|---|---|---|
| 1 | POST /api/v1/scans | Upload and scan one or more files for AI metadata. |
| 2 | POST /api/v1/sign | Apply a declaration and produce signed C2PA output. |
| 3 | GET /api/v1/verify/:id | Retrieve verification details for the signed asset. |
Tip
/v1/sign-jobs and /v1/scan-jobs when you need background processing for large batches.API Reference
Scan files
/api/v1/scansUpload one or more files to extract AI-generated content metadata. Each file is stored in your vault and analysed separately. Optionally assign uploads directly to an existing collection or create a new collection in the same request. Required scope: scan.
Request — multipart/form-data
| Field | Type | Description |
|---|---|---|
| file | File * | Repeat this field to upload multiple files. JPEG, PNG, WebP, or MP4. Max 20 MB per file. |
| collectionId | string | Optional. Existing collection UUID to attach all scanned assets to. |
| collectionName | string | Optional. Creates a new collection and attaches all scanned assets to it. |
| collectionDescription | string | Optional. Used only when collectionName is provided. |
Note
collectionId and collectionName are mutually exclusive. Use POST /api/v1/collections/:id/scans to re-scan assets already inside a collection.Response — 200 OK
{
"collectionId": "7dbfa9dd-4c48-4e39-a44c-0fc3c2206f73",
"createdCollection": true,
"results": [
{
"fileName": "hero.jpg",
"recordId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"metadata": {
"hasC2PA": false,
"hasXMP": true,
"aiModel": "dall-e-3"
}
},
{
"fileName": "video.mp4",
"error": "Unsupported MIME type"
}
]
}Show full scan response example (large)
{
"results": [
{
"fileName": "image.png",
"recordId": "f6da3344-0358-4db2-942a-16e9426d0e1f",
"metadata": {
"hasC2PA": true,
"hasXMP": false,
"aiModel": "dall-e-3",
"rawFields": {
"DigitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia"
},
"c2paManifest": {
"active_manifest": "urn:c2pa:2af52d28-d242-4e13-b126-147720f765e7",
"manifests": {
"urn:c2pa:2af52d28-d242-4e13-b126-147720f765e7": {
"title": "image.png",
"instance_id": "xmp:iid:a8324672-a72d-4bb1-8b0f-78c51b921d6c",
"claim_generator_info": [
{
"name": "OpenAI Media Service API",
"specVersion": "2.2.0",
"org.contentauth.c2pa_rs": "0.79.2"
}
],
"assertions": [
{
"label": "c2pa.actions.v2",
"data": {
"actions": [
{
"action": "c2pa.created",
"when": "2026-04-29T00:00:00Z",
"softwareAgent": {
"name": "gpt-image",
"version": "pre-2.0"
},
"digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia"
},
{
"action": "c2pa.converted",
"when": "2026-04-29T00:00:00Z"
}
]
}
},
{
"label": "c2pa.certificate-status",
"data": {
"ocspVals": [
"MIIE1goBAKCCBM8wggTLBgkrBgEFBQcwAQEEggS8MIIEuD...<very long value omitted>..."
]
}
}
],
"signature_info": {
"alg": "Es256",
"issuer": "OpenAI OpCo, LLC",
"common_name": "OpenAI Media Service",
"time": "2026-04-29T19:55:02.160659+00:00"
},
"claim_version": 2
}
},
"validation_status": [
{
"code": "signingCredential.untrusted",
"url": "self#jumbf=/c2pa/urn:c2pa:2af52d28-d242-4e13-b126-147720f765e7/c2pa.signature",
"explanation": "signing certificate untrusted"
}
],
"validation_results": {
"activeManifest": {
"success": [
{
"code": "claimSignature.validated",
"explanation": "claim signature valid"
},
{
"code": "assertion.dataHash.match",
"explanation": "data hash valid"
}
],
"informational": [
{
"code": "timeStamp.untrusted",
"explanation": "timestamp cert untrusted: OpenAI TSA Leaf"
}
],
"failure": [
{
"code": "signingCredential.untrusted",
"explanation": "signing certificate untrusted"
}
]
}
},
"validation_state": "Valid"
}
}
}
]
}Sign a file
/api/v1/signApply a C2PA manifest to one or more previously scanned files. The manifest embeds the AI disclosure declaration cryptographically. An Article 50 audit certificate PDF is also generated per asset. The signed result page shows whether the asset was processed with the node SDK or c2patool and whether the file format received an embedded C2PA manifest. Required scope: sign.
Note
Pass a single UUID string for recordId to sign one asset, or pass an array of UUIDs (max 50) to sign multiple assets in parallel. The response shape mirrors the input — a single object for single, a results array for batch.
Request body fields
| Field | Type | Description |
|---|---|---|
| recordId | string | string[] * | UUID of a single asset, or an array of UUIDs (max 50). |
Show declaration fields
| Field | Type | Description |
|---|---|---|
| declaration.aiModel | string * | Model identifier. Use the actual model name directly (for example: dall-e-3, flux-1.1-pro). |
| declaration.customModel | string | Required when declaration.aiModel is 'other'. |
| declaration.modificationType | string * | One of: generation, substantial_alteration, minor_edit. |
| declaration.modificationDescription | string | Optional details about what was changed. |
| declaration.purpose | string * | One of: journalism, advertising, entertainment, social_media, education, art, internal, other. |
| declaration.purposeContext | string | Optional context for the declared purpose. |
| declaration.humanReview | boolean * | Whether a human reviewed the content before publishing. |
| declaration.reviewerName | string | Required when declaration.humanReview is true. |
| declaration.organization | string | Publisher / organisation name for the certificate. |
Request - application/json
{
"recordId": ["3fa85f64-5717-4562-b3fc-2c963f66afa6", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"],
"declaration": {
"aiModel": "other",
"customModel": "my-custom-model-v1",
"modificationType": "generation",
"modificationDescription": "Text-to-image generation with prompt and negative prompt tuning",
"purpose": "advertising",
"purposeContext": "Paid social campaign for product launch",
"humanReview": true,
"reviewerName": "Jane Doe",
"organization": "Acme Studio"
}
}Allowed enum values: declaration.modificationType
generation - Fully generated by an AI model.substantial_alteration - Major edits / deepfake-like alteration.minor_edit - Limited AI-assisted editing.Allowed enum values: declaration.purpose
journalismadvertisingentertainmentsocial_mediaeducationartinternalotherResponse — batch (array recordId) — 200 OK
{
"results": [
{
"recordId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "complete",
"signedAssetUrl": "https://...",
"certificateUrl": "https://...",
"urlExpiresInSeconds": 600
},
{
"recordId": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
"status": "error",
"error": "Asset not yet scanned"
}
],
"count": 2
}The returned download URLs are short-lived and should be used immediately. If they expire, call the endpoint again with the same recordIdto receive fresh URLs. In batch mode, a failure on one asset does not abort the rest — check each result’s status field.
Get verification record
/api/v1/verify/:idRetrieve the public verification record for a signed asset. This endpoint is publicly accessible but requires scope verify.read when called with your API key. The record is identical to what is shown at https://notarai.io/verify/:id.
Path parameter
| Field | Type | Description |
|---|---|---|
| id | string * | Verification record ID (UUID) returned by POST /v1/sign. |
Response — 200 OK
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"fileName": "campaign-hero.jpg",
"mimeType": "image/jpeg",
"fileHash": "sha256:e3b0c44298fc1c149afb...",
"status": "complete",
"declaration": {
"aiModel": "dall-e-3",
"purpose": "advertising",
"modificationType": "generation",
"humanReview": false
},
"createdAt": "2026-05-03T12:34:56.000Z",
"completedAt": "2026-05-03T12:35:03.000Z",
"verifyUrl": "https://notarai.io/verify/3fa85f64-5717-4562-b3fc-2c963f66afa6"
}Sign Jobs (Async)
Sign jobs asynchronously apply declarations and signatures to many assets at once. This is the background processing API for high-volume signing.
Note
sign scope is required to create batch jobs. Reading job status requires either sign or jobs.read.Create a sign job
/api/v1/sign-jobsAsynchronously sign a set of assets. Supply either an assetIds array or a collectionId (not both).
Request body
| Field | Type | Description |
|---|---|---|
| assetIds | string[] | IDs of the assets to sign. Mutually exclusive with collectionId. |
| collectionId | string | Sign all pending assets in this collection. Mutually exclusive with assetIds. |
| declaration | object | C2PA signing declaration (same schema as POST /v1/sign). |
curl -X POST https://notarai.io/api/v1/sign-jobs \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"collectionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"declaration": {
"signerName": "Acme Corp",
"signerEmail": "publisher@acme.com",
"article50": true
}
}'{
"job": {
"id": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
"status": "queued",
"totalAssets": 250,
"completedAssets": 0,
"failedAssets": 0,
"collectionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"createdAt": "2026-05-07T10:00:00Z",
"startedAt": null,
"completedAt": null,
"expiresAt": "2026-05-14T10:00:00Z",
"error": null
}
}List sign jobs
/api/v1/sign-jobsReturns a paginated list of your batch jobs, newest first.
Query parameters
| Param | Default | Description |
|---|---|---|
| status | — | Filter by status: queued, processing, completed, failed, cancelled. |
| limit | 20 | Max results (max 100). |
| offset | 0 | Pagination offset. |
Get a sign job
/api/v1/sign-jobs/:idReturns a single job. Append ?items=true to include per-asset statuses in the items array.
{
"id": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
"status": "completed",
"totalAssets": 250,
"completedAssets": 248,
"failedAssets": 2,
"collectionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"createdAt": "2026-05-07T10:00:00Z",
"startedAt": "2026-05-07T10:00:01Z",
"completedAt": "2026-05-07T10:03:14Z",
"expiresAt": "2026-05-14T10:00:00Z",
"error": null
}Note
Scan Jobs (Async)
Scan jobs let you upload multiple files at once and scan them asynchronously in the background. Each file is stored and processed independently — results appear as individual scan records once the job completes.
Note
POST /api/v1/scans for synchronous single-file scanning. Use POST /api/v1/scan-jobs when you need to scan many files in one request without waiting for each result.Create a scan job
/api/v1/scan-jobsUpload one or more files as multipart/form-data. Each file is stored, queued, and scanned asynchronously. The response returns immediately with a job ID you can poll or receive via webhook.
Request fields (multipart/form-data)
| Field | Type | Required | Description |
|---|---|---|---|
| file | File | Yes (one or more) | File to scan. Repeat this field for each file. |
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| collectionId | string (UUID) | No | Attach all scanned assets to this collection. |
curl -X POST "https://notarai.io/api/v1/scan-jobs?collectionId=3fa85f64-5717-4562-b3fc-2c963f66afa6" \
-H "Authorization: Bearer $API_KEY" \
-F "file=@photo1.jpg" \
-F "file=@photo2.png"{
"job": {
"id": "7d94f943-d1c2-4f1c-8e31-4c3074df5412",
"status": "queued",
"totalAssets": 2,
"completedAssets": 0,
"failedAssets": 0,
"collectionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"createdAt": "2026-05-07T10:00:00Z",
"startedAt": null,
"completedAt": null,
"expiresAt": "2026-05-14T10:00:00Z",
"error": null
}
}List scan jobs
/api/v1/scan-jobsReturns a paginated list of your scan jobs, newest first.
Get a scan job
/api/v1/scan-jobs/:idReturns a single scan job. Append ?items=true to include per-asset scan statuses in the items array.
Advanced API
Collections, workflows, asset management, collection-scoped verification, and webhooks — documented separately for teams that need the full API surface.
CMS Integration guide
Automate signing from Contentful, Sanity, Strapi, S3, or any webhook-capable system — with Node.js, Python, and cURL examples.
Rate Limits
There are no hourly rate limits. The primary constraint is your plan's monthly quota. Exceeding it returns 429 Too Many Requests. A Retry-After header is included when the response is rate-limited.
Monthly quotas
| Plan | Scans / month | Signs / month |
|---|---|---|
| Starter | 500 | 500 |
| Business | 2 000 | 2 000 |
| Enterprise | 12 000 | 12 000 |
Note
Error Codes
All errors return a JSON body with an error human-readable message and a code machine-readable string.
{
"error": "API key does not have the required scope: sign",
"code": "INSUFFICIENT_SCOPE"
}| HTTP | Code | Description |
|---|---|---|
| 400 | INVALID_REQUEST | Missing or malformed request parameters. |
| 401 | UNAUTHORIZED | Missing or invalid Authorization header. |
| 403 | INSUFFICIENT_SCOPE | API key lacks the required scope. |
| 403 | KEY_REVOKED | The API key has been revoked. |
| 404 | NOT_FOUND | Resource does not exist or belongs to another account. |
| 413 | FILE_TOO_LARGE | Uploaded file exceeds the 20 MB limit. |
| 415 | UNSUPPORTED_MEDIA_TYPE | MIME type not accepted (JPEG, PNG, WebP only). |
| 422 | RECORD_NOT_PENDING | The record is not in a state that can be signed. |
| 429 | RATE_LIMITED | Too many requests. See Retry-After header. |
| 500 | INTERNAL_ERROR | Unexpected server error. Contact support if persistent. |
Changelog
- +POST /v1/collections — create a new collection
- +POST /v1/collections/:id/assets — upload files or assign existing assets by ID
- +POST /v1/scans — file upload and AI metadata extraction
- +POST /v1/sign — C2PA manifest signing with Article 50 declaration
- +GET /v1/collections — list collections
- +GET /v1/collections/:id — retrieve collection details
- +POST /v1/collections/:id/scans — scan all assets in a collection
- +POST /v1/collections/:id/sign — sign all assets in a collection
- +POST /v1/workflows/:id — run a workflow on uploaded files
- +POST /v1/workflows/:id/run-collection — run a workflow on a collection
- +GET /v1/verify/collection/:id — list verification records for a collection
- +GET /v1/workflows — list saved signing workflows
- +GET /v1/verify/:id — public verification record retrieval
- +Scoped API keys with Bearer token auth
- +Per-plan rate limiting with Retry-After headers