API v1 · REST · JSON

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.

Base URLhttps://notarai.io/api/v1
AuthAuthorization: Bearer <token>
Content-Typeapplication/json (or multipart/form-data for file uploads)
VersioningPath-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 typeRetention
Original uploaded file (unsigned)~15 minutes after successful sign, or 72h for abandoned pending/error records
Signed outputPlan-based: Starter 7 days, Business 21 days, Enterprise 45 days
PDF certificateSame retention as signed output

Note

Deletions are executed by a scheduled cleanup job. Removal happens shortly after the retention timestamp, depending on cron frequency.

Tip

Active storage caps are plan-based: Starter 5 GB, Business 25 GB, Enterprise 100 GB. Upload and sign operations are rejected once the active storage cap is reached.

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:

http
Authorization: Bearer ntr_live_key_a1b2c3d4e5f6_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Warning

API keys carry full access to your account within the granted scopes. Never expose them in client-side code, browser environments, or public repositories. Store them as environment variables on your server.

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.

ScopeAccess
scanUpload files and trigger AI metadata scans
signApply C2PA manifests and sign files
jobs.readList collections/assets and assign existing assets to collections
workflows.readList and inspect your signing workflows
verify.readRetrieve 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.

bash

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.

StepEndpointPurpose
1POST /api/v1/scansUpload and scan one or more files for AI metadata.
2POST /api/v1/signApply a declaration and produce signed C2PA output.
3GET /api/v1/verify/:idRetrieve verification details for the signed asset.

Tip

Use /v1/sign-jobs and /v1/scan-jobs when you need background processing for large batches.

API Reference

Scan files

POST/api/v1/scans

Upload 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

FieldTypeDescription
fileFile *Repeat this field to upload multiple files. JPEG, PNG, WebP, or MP4. Max 20 MB per file.
collectionIdstringOptional. Existing collection UUID to attach all scanned assets to.
collectionNamestringOptional. Creates a new collection and attaches all scanned assets to it.
collectionDescriptionstringOptional. 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

json
{
  "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)
json
{
  "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

POST/api/v1/sign

Apply 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

Final sign submission requires the user to acknowledge that the declaration is accurate and that false or incomplete disclosures do not satisfy EU AI Act Article 50.

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
FieldTypeDescription
recordIdstring | string[] *UUID of a single asset, or an array of UUIDs (max 50).
Show declaration fields
FieldTypeDescription
declaration.aiModelstring *Model identifier. Use the actual model name directly (for example: dall-e-3, flux-1.1-pro).
declaration.customModelstringRequired when declaration.aiModel is 'other'.
declaration.modificationTypestring *One of: generation, substantial_alteration, minor_edit.
declaration.modificationDescriptionstringOptional details about what was changed.
declaration.purposestring *One of: journalism, advertising, entertainment, social_media, education, art, internal, other.
declaration.purposeContextstringOptional context for the declared purpose.
declaration.humanReviewboolean *Whether a human reviewed the content before publishing.
declaration.reviewerNamestringRequired when declaration.humanReview is true.
declaration.organizationstringPublisher / organisation name for the certificate.

Request - application/json

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
journalism
advertising
entertainment
social_media
education
art
internal
other

Response — batch (array recordId) — 200 OK

json
{
  "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

GET/api/v1/verify/:id

Retrieve 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

FieldTypeDescription
idstring *Verification record ID (UUID) returned by POST /v1/sign.

Response — 200 OK

json
{
  "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

A sign scope is required to create batch jobs. Reading job status requires either sign or jobs.read.

Create a sign job

POST/api/v1/sign-jobs

Asynchronously sign a set of assets. Supply either an assetIds array or a collectionId (not both).

Request body

FieldTypeDescription
assetIdsstring[]IDs of the assets to sign. Mutually exclusive with collectionId.
collectionIdstringSign all pending assets in this collection. Mutually exclusive with assetIds.
declarationobjectC2PA signing declaration (same schema as POST /v1/sign).
bash
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
    }
  }'
json
{
  "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

GET/api/v1/sign-jobs

Returns a paginated list of your batch jobs, newest first.

Query parameters

ParamDefaultDescription
statusFilter by status: queued, processing, completed, failed, cancelled.
limit20Max results (max 100).
offset0Pagination offset.

Get a sign job

GET/api/v1/sign-jobs/:id

Returns a single job. Append ?items=true to include per-asset statuses in the items array.

json
{
  "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

Job records expire 7 days after creation. After that the record is deleted.

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

Use 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

POST/api/v1/scan-jobs

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

FieldTypeRequiredDescription
fileFileYes (one or more)File to scan. Repeat this field for each file.

Query parameters

ParameterTypeRequiredDescription
collectionIdstring (UUID)NoAttach all scanned assets to this collection.
bash
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"
json
{
  "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

GET/api/v1/scan-jobs

Returns a paginated list of your scan jobs, newest first.

Get a scan job

GET/api/v1/scan-jobs/:id

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

View Advanced API →

CMS Integration guide

Automate signing from Contentful, Sanity, Strapi, S3, or any webhook-capable system — with Node.js, Python, and cURL examples.

Read guide →

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

PlanScans / monthSigns / month
Starter500500
Business2 0002 000
Enterprise12 00012 000

Note

A per-key burst guard of 60 scans/min and 30 signs/min is enforced as infrastructure protection. Legitimate batch pipelines will not hit this ceiling.

Error Codes

All errors return a JSON body with an error human-readable message and a code machine-readable string.

json
{
  "error": "API key does not have the required scope: sign",
  "code": "INSUFFICIENT_SCOPE"
}
HTTPCodeDescription
400INVALID_REQUESTMissing or malformed request parameters.
401UNAUTHORIZEDMissing or invalid Authorization header.
403INSUFFICIENT_SCOPEAPI key lacks the required scope.
403KEY_REVOKEDThe API key has been revoked.
404NOT_FOUNDResource does not exist or belongs to another account.
413FILE_TOO_LARGEUploaded file exceeds the 20 MB limit.
415UNSUPPORTED_MEDIA_TYPEMIME type not accepted (JPEG, PNG, WebP only).
422RECORD_NOT_PENDINGThe record is not in a state that can be signed.
429RATE_LIMITEDToo many requests. See Retry-After header.
500INTERNAL_ERRORUnexpected server error. Contact support if persistent.

Changelog

2026-05-03
v1.0 — Initial releasecurrent
  • +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