Advanced API

Collections, workflows, asset management, collection-scoped verification, and webhooks. These endpoints are fully supported but not required for the standard scan → sign → verify workflow. See the main docs for the core API.

Assets & Collections

List all assets

GET/api/v1/assets

Return all assets in your account (standalone and collection assets) with optional filters, pagination, and collection embedding. Required scope: jobs.read.

Query parameters

ParameterTypeDescription
idstringFilter by one asset UUID. Repeat id to pass multiple values.
idsstringComma-separated list of asset UUIDs (alternative to repeated id).
statusstringFilter by status: pending, scanning, scanned, signing, complete, error.
collection_idstringFilter to one collection UUID.
standalonebooleanIf true, only return assets not in any collection.
include_archivedbooleanInclude archived assets. Default: false.
include_collectionsbooleanInclude each asset's collection object when available. Default: false.
Show common parameters (pagination · sort · fields)
limitintegerPage size. Default: 20, min: 1, max: 100.
offsetintegerNumber of records to skip. Default: 0.
sortstringColumn to sort by. Default: created_at. Allowed: created_at, completed_at, file_name, status.
orderstringSort direction: asc or desc. Default: desc.
fieldsstringComma-separated fields to include in each object. Omit to return all fields. Allowed: id, fileName, fileSize, mimeType, status, declaration, scannedMetadata, workflowId, collectionId, createdAt, completedAt, collection.

Response — 200 OK

json
{
  "assets": [
    {
      "id": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
      "fileName": "hero.png",
      "fileSize": 2219175,
      "mimeType": "image/png",
      "status": "complete",
      "collectionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "workflowId": null,
      "createdAt": "2026-05-03T12:34:56.000Z",
      "completedAt": "2026-05-03T12:35:02.000Z"
    }
  ],
  "total": 42,
  "limit": 20,
  "offset": 0,
  "hasMore": true,
  "sort": "created_at",
  "order": "desc"
}

Response — 200 OK (?include_collections=true)

json
{
  "assets": [
    {
      "id": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
      "fileName": "hero.png",
      "collectionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "collection": {
        "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "name": "Summer Campaign 2026",
        "description": null
      }
    }
  ],
  "total": 1,
  "limit": 20,
  "offset": 0,
  "hasMore": false
}
Show full collection response example (?include_assets=true)
json
{
  "collections": [
    {
      "id": "fb038a72-f808-4644-9a9a-371f1505f4eb",
      "name": "Q2 Campaign",
      "description": "Social and paid assets",
      "createdAt": "2026-05-03T17:20:23.428+00:00",
      "isArchived": false,
      "assetCount": 1,
      "completeCount": 1,
      "assets": [
        {
          "id": "d128be2d-54d6-4efa-9e2e-57e8d6a26b41",
          "fileName": "hero.jpg",
          "fileSize": 334257,
          "mimeType": "image/jpeg",
          "status": "complete",
          "declaration": {
            "aiModel": "dall-e-3",
            "purpose": "advertising",
            "humanReview": false,
            "modificationType": "generation"
          },
          "scannedMetadata": {
            "hasXMP": true,
            "aiModel": "dall-e-3",
            "creator": "dall-e-3",
            "hasC2PA": true,
            "rawFields": { "DigitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia" },
            "dateCreated": "2026-04-29T19:53:28.803Z",
            "c2paManifest": { "...": "see full scan response example for structure" }
          },
          "workflowId": null,
          "createdAt": "2026-05-03T17:20:24.099+00:00",
          "completedAt": "2026-05-03T17:20:41.031+00:00"
        }
      ]
    }
  ]
}

List collections

GET/api/v1/collections

List collections for your account. You can request one or many specific IDs via query filters. Required scope: jobs.read.

Query parameters

ParameterTypeDescription
archivedbooleanReturn archived collections instead of active ones. Default: false.
idstringFilter by one collection UUID. Repeat id to pass multiple values.
idsstringComma-separated list of collection UUIDs (alternative to repeated id).
include_archivedbooleanInclude both active and archived collections. Overrides archived. Default: false.
include_assetsbooleanInclude the full assets array in every collection. Default: false.
Show common parameters (pagination · sort · fields)
limitintegerPage size. Default: 20, min: 1, max: 100.
offsetintegerNumber of records to skip. Default: 0.
sortstringColumn to sort by. Default: created_at. Allowed: created_at, name.
orderstringSort direction: asc or desc. Default: desc.
fieldsstringComma-separated fields to include in each object. Omit to return all fields. Allowed: id, name, description, createdAt, isArchived, assetCount, completeCount, assets.

Note

All GET list endpoints support conditional caching via the ETag response header. Send the value back in If-None-Match on subsequent requests — you will receive a 304 Not Modified with no body if the result has not changed.

Response — 200 OK (default)

json
{
  "collections": [
    {
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "name": "Q2 Campaign",
      "description": "Social and paid assets",
      "createdAt": "2026-05-03T12:34:56.000Z",
      "isArchived": false,
      "assetCount": 14,
      "completeCount": 11
    }
  ],
  "total": 42,
  "limit": 20,
  "offset": 0,
  "hasMore": true,
  "sort": "created_at",
  "order": "desc"
}

Get collection

GET/api/v1/collections/:id

Retrieve a single collection by ID. Required scope: jobs.read.

Path parameter

FieldTypeDescription
idstring *Collection ID (UUID).

Query parameters

ParameterTypeDescription
include_assetsbooleanInclude the full assets array in the response. Default: false.
Show common parameters (pagination · sort · fields)
limitintegerPage size. Default: 20, min: 1, max: 100. Applies to the embedded assets list when include_assets=true.
offsetintegerNumber of records to skip. Default: 0. Applies to the embedded assets list when include_assets=true.
sortstringColumn to sort by. Default: created_at. Allowed: created_at, completed_at, file_name, status.
orderstringSort direction: asc or desc. Default: desc.
fieldsstringComma-separated fields to include in each object. Omit to return all fields. Allowed: id, name, description, createdAt, isArchived, assetCount, completeCount, totalAssets, limit, offset, hasMore, sort, order, assets.

Response — 200 OK

json
{
  "id": "fb038a72-f808-4644-9a9a-371f1505f4eb",
  "name": "API TESTING",
  "description": "this is a collection i have made to test the public api",
  "createdAt": "2026-05-03T17:20:23.428+00:00",
  "isArchived": false,
  "assetCount": 2,
  "completeCount": 2,
  "totalAssets": 2,
  "limit": 20,
  "offset": 0,
  "hasMore": false,
  "sort": "created_at",
  "order": "desc"
}
Show full collection response example (?include_assets=true)
json
{
  "id": "fb038a72-f808-4644-9a9a-371f1505f4eb",
  "name": "API TESTING",
  "description": "this is a collection i have made to test the public api",
  "createdAt": "2026-05-03T17:20:23.428+00:00",
  "isArchived": false,
  "assetCount": 2,
  "completeCount": 2,
  "totalAssets": 2,
  "limit": 20,
  "offset": 0,
  "hasMore": false,
  "assets": [
    {
      "id": "5b294960-a754-4907-aeb1-3913543a196a",
      "fileName": "aan.png",
      "fileSize": 2219175,
      "mimeType": "image/png",
      "status": "complete",
      "declaration": {
        "aiModel": "dall-e-3",
        "purpose": "advertising",
        "humanReview": false,
        "modificationType": "generation"
      },
      "scannedMetadata": {
        "hasXMP": false,
        "aiModel": "dall-e-3",
        "hasC2PA": true,
        "rawFields": {
          "DigitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia"
        },
        "c2paManifest": {
          "active_manifest": "urn:c2pa:2af52d28-d242-4e13-b126-147720f765e7",
          "validation_state": "Valid",
          "validation_status": [
            {
              "url": "self#jumbf=/c2pa/urn:c2pa:2af52d28-d242-4e13-b126-147720f765e7/c2pa.signature",
              "code": "signingCredential.untrusted",
              "explanation": "signing certificate untrusted"
            }
          ],
          "validation_results": {
            "activeManifest": {
              "failure": [
                { "url": "self#jumbf=.../c2pa.signature", "code": "signingCredential.untrusted", "explanation": "signing certificate untrusted" }
              ],
              "success": [
                { "url": "OCSP_RESPONSE", "code": "signingCredential.ocsp.notRevoked", "explanation": "certificate not revoked" },
                { "url": "self#jumbf=.../c2pa.signature", "code": "timeStamp.validated", "explanation": "timestamp message digest matched: OpenAI TSA Leaf" },
                { "url": "self#jumbf=.../c2pa.signature", "code": "claimSignature.validated", "explanation": "claim signature valid" },
                { "url": "self#jumbf=.../c2pa.assertions/c2pa.actions.v2", "code": "assertion.hashedURI.match", "explanation": "hashed uri matched" },
                { "url": "self#jumbf=.../c2pa.assertions/c2pa.hash.data", "code": "assertion.dataHash.match", "explanation": "data hash valid" }
              ],
              "informational": [
                { "url": "self#jumbf=.../c2pa.signature", "code": "timeStamp.untrusted", "explanation": "timestamp cert untrusted: OpenAI TSA Leaf" }
              ]
            }
          },
          "manifests": {
            "urn:c2pa:2af52d28-d242-4e13-b126-147720f765e7": {
              "label": "urn:c2pa:2af52d28-d242-4e13-b126-147720f765e7",
              "title": "image.png",
              "instance_id": "xmp:iid:a8324672-a72d-4bb1-8b0f-78c51b921d6c",
              "claim_version": 2,
              "claim_generator_info": [
                {
                  "name": "OpenAI Media Service API",
                  "specVersion": "2.2.0",
                  "org.contentauth.c2pa_rs": "0.79.2",
                  "icon": { "format": "image/svg+xml", "identifier": "self#jumbf=...c2pa.icon" }
                }
              ],
              "signature_info": {
                "alg": "Es256",
                "time": "2026-04-29T19:55:02.160659+00:00",
                "issuer": "OpenAI OpCo, LLC",
                "common_name": "OpenAI Media Service",
                "cert_serial_number": "471440979205905794604524502504016020624908622401"
              },
              "assertions": [
                {
                  "label": "c2pa.actions.v2",
                  "created": true,
                  "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",
                  "created": true,
                  "data": { "ocspVals": ["<base64-encoded OCSP response — truncated>"] }
                }
              ]
            }
          }
        }
      },
      "workflowId": null,
      "createdAt": "2026-05-03T17:20:25.42+00:00",
      "completedAt": "2026-05-03T17:20:43.371+00:00"
    },
    {
      "id": "d128be2d-54d6-4efa-9e2e-57e8d6a26b41",
      "fileName": "aab.jpg",
      "fileSize": 334257,
      "mimeType": "image/jpeg",
      "status": "complete",
      "declaration": {
        "aiModel": "dall-e-3",
        "purpose": "advertising",
        "humanReview": false,
        "modificationType": "generation"
      },
      "scannedMetadata": {
        "hasXMP": true,
        "aiModel": "dall-e-3",
        "creator": "dall-e-3",
        "hasC2PA": true,
        "dateCreated": "2026-04-29T19:53:28.803Z",
        "rawFields": {
          "Owner": "ArtTem nv",
          "Artist": "dall-e-3",
          "Credit": "ArtTem nv",
          "Marked": true,
          "Source": "Signet EU — EU AI Act Article 50 Compliance Platform",
          "creator": "dall-e-3",
          "subject": ["ai-generated", "article-50", "EU-AI-Act", "social_media", "dall-e-3"],
          "Copyright": "© ArtTem nv. EU AI Act Article 50 disclosure.",
          "CreateDate": "2026-04-29T19:53:28.805Z",
          "DocumentID": "xmp.id:cf743374-8631-4f08-aff6-08506297c31d",
          "InstanceID": "xmp.iid:cf743374-8631-4f08-aff6-08506297c31d",
          "UsageTerms": { "lang": "x-default", "value": "Social media / Personal" },
          "CreatorTool": "dall-e-3",
          "DateCreated": "2026-04-29T19:53:28.803Z",
          "description": "EU AI Act Article 50(2) declaration — dall-e-3 — Social media / Personal",
          "AISystemUsed": "dall-e-3",
          "Instructions": "Raw AI generation",
          "MetadataDate": "2026-04-29T19:53:28.803Z",
          "WebStatement": "https://notarai.io/verify/cf743374-8631-4f08-aff6-08506297c31d",
          "DigitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia",
          "CVterm": {
            "CvId": "http://cv.iptc.org/newscodes/digitalsourcetype/",
            "CvTermId": "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia",
            "CvTermName": "Trained algorithmic media"
          },
          "rights": { "lang": "x-default", "value": "AI-generated content. EU AI Act Article 50(2) disclosure." }
        },
        "c2paManifest": {
          "active_manifest": "urn:c2pa:6da52611-407a-4549-bbc4-8cd63449e535",
          "validation_state": "Valid",
          "validation_status": [
            {
              "url": "self#jumbf=/c2pa/urn:c2pa:6da52611-407a-4549-bbc4-8cd63449e535/c2pa.signature",
              "code": "signingCredential.untrusted",
              "explanation": "signing certificate untrusted"
            }
          ],
          "validation_results": {
            "activeManifest": {
              "failure": [
                { "url": "self#jumbf=.../c2pa.signature", "code": "signingCredential.untrusted", "explanation": "signing certificate untrusted" }
              ],
              "success": [
                { "url": "self#jumbf=.../c2pa.signature", "code": "timeStamp.validated", "explanation": "timestamp message digest matched: DigiCert SHA256 RSA4096 Timestamp Responder 2025 1" },
                { "url": "self#jumbf=.../c2pa.signature", "code": "claimSignature.validated", "explanation": "claim signature valid" },
                { "url": "self#jumbf=.../c2pa.assertions/c2pa.hash.data", "code": "assertion.dataHash.match", "explanation": "data hash valid" },
                { "url": "self#jumbf=.../c2pa.assertions/c2pa.actions.v2", "code": "assertion.hashedURI.match", "explanation": "hashed uri matched" },
                { "url": "self#jumbf=.../c2pa.assertions/c2pa.training-mining", "code": "assertion.hashedURI.match", "explanation": "hashed uri matched" },
                { "url": "self#jumbf=.../c2pa.assertions/stds.schema-org.CreativeWork", "code": "assertion.hashedURI.match", "explanation": "hashed uri matched" }
              ],
              "informational": [
                { "url": "self#jumbf=.../c2pa.signature", "code": "timeStamp.untrusted", "explanation": "timestamp cert untrusted: DigiCert SHA256 RSA4096 Timestamp Responder 2025 1" }
              ]
            }
          },
          "manifests": {
            "urn:c2pa:6da52611-407a-4549-bbc4-8cd63449e535": {
              "label": "urn:c2pa:6da52611-407a-4549-bbc4-8cd63449e535",
              "instance_id": "xmp:iid:95ef8b31-692e-4080-b0aa-8be2f9721bfc",
              "claim_version": 2,
              "thumbnail": {
                "format": "image/jpeg",
                "identifier": "self#jumbf=/c2pa/urn:c2pa:6da52611-407a-4549-bbc4-8cd63449e535/c2pa.assertions/c2pa.thumbnail.claim"
              },
              "claim_generator_info": [
                {
                  "name": "Signet EU",
                  "version": "1.0",
                  "org.contentauth.c2pa_rs": "0.78.4",
                  "signet.eu/ai-act-article50": "compliant"
                }
              ],
              "signature_info": {
                "alg": "Es256",
                "time": "2026-04-29T19:53:29+00:00",
                "issuer": "Signet EU",
                "common_name": "Signet EU Dev",
                "cert_serial_number": "13852155904119981449"
              },
              "assertions": [
                {
                  "label": "c2pa.actions.v2",
                  "data": {
                    "actions": [
                      {
                        "action": "c2pa.created",
                        "when": "2026-04-29T19:53:28.805Z",
                        "softwareAgent": { "name": "dall-e-3", "version": "unknown" },
                        "digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia",
                        "parameters": {
                          "purpose": "social_media",
                          "algorithm": "dall-e-3",
                          "humanReview": true,
                          "organization": "ArtTem nv",
                          "reviewerName": "Arthur",
                          "modificationType": "generation"
                        }
                      },
                      {
                        "action": "c2pa.published",
                        "when": "2026-04-29T19:53:28.805Z",
                        "digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia",
                        "parameters": { "purpose": "social_media", "publisher": "ArtTem nv" }
                      }
                    ]
                  }
                },
                {
                  "label": "c2pa.training-mining",
                  "data": {
                    "entries": {
                      "c2pa.ai_generative_training": { "use": "notAllowed" },
                      "c2pa.ai_generative_inference": { "use": "notAllowed" }
                    }
                  }
                },
                {
                  "label": "stds.schema-org.CreativeWork",
                  "kind": "Json",
                  "data": {
                    "@context": "https://schema.org",
                    "@type": "CreativeWork",
                    "dateCreated": "2026-04-29T19:53:28.805Z",
                    "description": "EU AI Act Article 50(2) declaration — dall-e-3 — Social media / Personal",
                    "author": [{ "name": "dall-e-3", "@type": "SoftwareApplication" }],
                    "publisher": { "name": "ArtTem nv", "@type": "Organization" },
                    "reviewer": [{ "name": "Arthur", "@type": "Person", "affiliation": { "name": "ArtTem nv", "@type": "Organization" } }],
                    "keywords": ["ai-generated", "article-50", "EU-AI-Act", "social_media", "dall-e-3"],
                    "url": "https://notarai.io/verify/cf743374-8631-4f08-aff6-08506297c31d",
                    "additionalProperty": [
                      { "name": "euAiActArticle", "@type": "PropertyValue", "value": "50(2)" },
                      { "name": "aiModificationType", "@type": "PropertyValue", "value": "generation" },
                      { "name": "aiDisclosurePurpose", "@type": "PropertyValue", "value": "social_media" },
                      { "name": "humanEditorialReview", "@type": "PropertyValue", "value": "true" },
                      { "name": "signingPlatform", "@type": "PropertyValue", "value": "Signet EU" },
                      { "name": "reviewerName", "@type": "PropertyValue", "value": "Arthur" }
                    ]
                  }
                }
              ]
            }
          }
        }
      },
      "workflowId": null,
      "createdAt": "2026-05-03T17:20:24.099+00:00",
      "completedAt": "2026-05-03T17:20:41.031+00:00"
    }
  ]
}

List collection assets

GET/api/v1/collections/:id/assets

Return assets in a collection with optional filtering and pagination. Required scope: jobs.read.

Path parameter

FieldTypeDescription
idstring *Collection ID (UUID).

Query parameters

ParameterTypeDescription
statusstringFilter by status: pending, scanning, scanned, signing, complete, error.
include_collectionsbooleanInclude a collection object on each asset. Default: false.
Show common parameters (pagination · sort · fields)
limitintegerPage size. Default: 20, min: 1, max: 100.
offsetintegerNumber of records to skip. Default: 0.
sortstringColumn to sort by. Default: created_at. Allowed: created_at, completed_at, file_name, status.
orderstringSort direction: asc or desc. Default: desc.
fieldsstringComma-separated fields to include in each object. Omit to return all fields. Allowed: id, fileName, fileSize, mimeType, status, declaration, scannedMetadata, workflowId, createdAt, completedAt, collection.

Response — 200 OK

json
{
  "collectionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "assets": [
    {
      "id": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
      "fileName": "hero.png",
      "fileSize": 2219175,
      "mimeType": "image/png",
      "status": "complete",
      "declaration": { "aiModel": "dall-e-3", "purpose": "advertising", "modificationType": "generation", "humanReview": false },
      "scannedMetadata": { "aiModel": "dall-e-3", "hasC2PA": true, "contentType": "image" },
      "workflowId": null,
      "createdAt": "2026-05-03T12:34:56.000Z",
      "completedAt": "2026-05-03T12:35:02.000Z"
    }
  ],
  "total": 42,
  "limit": 20,
  "offset": 0,
  "hasMore": true,
  "sort": "created_at",
  "order": "desc"
}

Response — 200 OK (?include_collections=true)

json
{
  "collectionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "assets": [
    {
      "id": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
      "fileName": "hero.png",
      "status": "complete",
      "collection": {
        "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "name": "Summer Campaign 2026",
        "description": null
      }
    }
  ],
  "total": 1,
  "limit": 20,
  "offset": 0,
  "hasMore": false
}

List workflows

GET/api/v1/workflows

Return saved signing workflows owned by your account. You can request one or many specific IDs via query filters. Required scope: workflows.read.

Note

This endpoint does not accept a request body.

Query parameters

ParameterTypeDescription
idstringFilter by one workflow UUID. Repeat id to pass multiple values.
idsstringComma-separated list of workflow UUIDs (alternative to repeated id).
Show common parameters (pagination · sort · fields)
limitintegerPage size. Default: 20, min: 1, max: 100.
offsetintegerNumber of records to skip. Default: 0.
sortstringColumn to sort by. Default: created_at. Allowed: created_at, name.
orderstringSort direction: asc or desc. Default: desc.
fieldsstringComma-separated fields to include in each object. Omit to return all fields. Allowed: id, name, declaration, createdAt.

Response — 200 OK

json
{
  "workflows": [
    {
      "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
      "name": "Default social workflow",
      "declaration": {
        "aiModel": "dall-e-3",
        "modificationType": "generation",
        "purpose": "social_media",
        "humanReview": false,
        "organization": "Acme Studio"
      },
      "createdAt": "2026-05-03T12:34:56.000Z"
    }
  ],
  "total": 8,
  "limit": 20,
  "offset": 0,
  "hasMore": false,
  "sort": "created_at",
  "order": "desc"
}

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

Get verification records by collection

GET/api/v1/verify/collection/:id

Return all completed verification records for a collection. Required scope: verify.read.

Path parameter

FieldTypeDescription
idstring *Collection ID (UUID).

Response — 200 OK

json
{
  "collectionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "results": [
    {
      "id": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
      "verifyUrl": "https://notarai.io/verify/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
      "fileName": "hero.jpg",
      "mimeType": "image/jpeg",
      "fileHash": "sha256:e3b0c44298fc1c149afb...",
      "declaration": {
        "aiModel": "dall-e-3",
        "purpose": "advertising",
        "modificationType": "generation",
        "humanReview": false
      },
      "signedAt": "2026-05-03T12:35:03.000Z",
      "createdAt": "2026-05-03T12:34:56.000Z"
    }
  ],
  "count": 1
}

Collections & Workflows

Create a collection

POST/api/v1/collections

Create a new empty collection. Required scope: jobs.read.

Request body

FieldTypeDescription
namestring *Collection name. Max 255 characters.
descriptionstringOptional description. Max 1000 characters.

Request — application/json

json
{
  "name": "Summer Campaign 2026",
  "description": "All assets for the summer campaign"
}

Response — 201 Created

json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "name": "Summer Campaign 2026",
  "description": "All assets for the summer campaign",
  "createdAt": "2026-05-03T12:34:56.000Z",
  "isArchived": false,
  "assetCount": 0,
  "completeCount": 0
}

Sign all assets in a collection

POST/api/v1/collections/:id/sign

Apply an inline declaration and sign every asset in a collection in a single call. No workflow is required — the declaration is provided directly in the request body. Required scope: sign.

This endpoint uses the same declaration input schema as POST /v1/sign. The collection identifier is read from the URL path (:id), so norecordId field is sent in the request body.

Path parameter

FieldTypeDescription
idstring *Collection ID (UUID) of the collection to sign.

Request body

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

Request — application/json

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

Response — 200 OK

json
{
  "collectionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "results": [
    {
      "recordId": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
      "status": "complete",
      "signedAssetUrl": "https://...",
      "certificateUrl": "https://..."
    },
    {
      "recordId": "b1ffcd00-ad1c-5fg9-cc7e-7ccace491b22",
      "status": "error",
      "error": "Asset not yet scanned"
    }
  ],
  "count": 2
}

Note

Each asset is signed independently. A failure on one asset does not abort the rest — check the status field of each result entry.

Scan all assets in a collection

POST/api/v1/collections/:id/scans

Trigger a scan for every asset currently in a collection. Required scope: scan.

Path parameter

FieldTypeDescription
idstring *Collection ID (UUID) of the collection to scan.

Note

The collection ID is taken from the URL path. No JSON body is required for this endpoint.

Response — 200 OK

json
{
  "results": [
    {
      "fileName": "hero.png",
      "recordId": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
      "metadata": { "aiModel": "flux-1.1-pro", "contentType": "image" }
    },
    {
      "fileName": "banner.jpg",
      "error": "Failed to download original asset"
    }
  ],
  "count": 2
}

Each result mirrors the shape returned by POST /v1/scans. A failure on one asset does not abort the rest — check for the presence of error vs recordId per entry.

Add assets to a collection

POST/api/v1/collections/:id/assets

Add assets to a collection. Supports two modes depending on the request Content-Type: upload new files (multipart/form-data), or assign already-scanned assets by their IDs (application/json).

Path parameter

FieldTypeDescription
idstring *Collection ID (UUID) to add assets into.

Request — multipart/form-data (upload new files)

FieldTypeDescription
fileFile *One or more files. Repeat the same field name to upload multiple files. Max 20 MB per file.

Response — 200 OK (upload mode)

json
{
  "collectionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "results": [
    {
      "fileName": "hero.png",
      "recordId": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
      "metadata": { "aiModel": "flux-1.1-pro", "contentType": "image" }
    },
    {
      "fileName": "banner.jpg",
      "error": "Unsupported file type"
    }
  ]
}

A failure on one file does not abort the rest. Check for the presence of error vs recordId per entry. Scan rate limits apply per file. Required scope: scan.

Request — application/json (assign existing assets by ID)

FieldTypeDescription
idsstring[] *Array of asset UUIDs to assign to this collection. Max 100 per request.
recordIdsstring[]Alias for ids.
assetIdsstring[]Alias for ids.

Response — 200 OK (assign mode)

json
{
  "collectionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "assigned": [
    "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
    "b1ffcd00-ad1c-5fg9-cc7e-7ccace491b22"
  ],
  "notFound": []
}

IDs in notFound either do not exist or belong to a different account and were silently skipped. Required scope for assign mode: jobs.read.

Run a workflow

POST/api/v1/workflows/:id

Upload one or more files and apply a saved workflow’s declaration automatically. This is a convenience endpoint that chains scan + sign in a single call. Required scopes: scan, sign, workflows.read.

Path parameter

FieldTypeDescription
idstring *Workflow ID (UUID).

Request — multipart/form-data

FieldTypeDescription
fileFile *One or more files. Repeat the same field name to upload multiple files. Max 20 MB per file.

Response — 200 OK

json
{
  "workflowId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "results": [
    {
      "fileName": "hero.png",
      "recordId": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
      "status": "complete",
      "signedAssetUrl": "https://...",
      "certificateUrl": "https://...",
      "urlExpiresInSeconds": 600
    },
    {
      "fileName": "banner.jpg",
      "recordId": "b1ffcd00-ad1c-5fg9-cc7e-7ccace491b22",
      "status": "error",
      "error": "Sign failed"
    }
  ],
  "count": 2
}

Note

Results are per-file. A failure on one file does not abort the others.

Run a workflow on a collection

POST/api/v1/workflows/:id/run-collection

Apply a saved workflow to every asset in a collection. Required scopes: scan, sign, workflows.read.

Path parameter

FieldTypeDescription
idstring *Workflow ID (UUID).

Request — application/json

json
{
  "collectionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}

Response — 200 OK

json
{
  "workflowId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "collectionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "results": [
    {
      "recordId": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
      "status": "complete",
      "signedAssetUrl": "https://...",
      "certificateUrl": "https://..."
    },
    {
      "recordId": "b1ffcd00-ad1c-5fg9-cc7e-7ccace491b22",
      "status": "error",
      "error": "Asset not yet scanned"
    }
  ],
  "count": 2
}

Webhooks

Webhooks let your server receive real-time notifications when asynchronous jobs complete. When a sign-job or scan-job finishes, NotarAI POSTs a signed JSON payload to every registered webhook endpoint that subscribes to the relevant event.

Note

Webhooks are configured in the Developer dashboard — no request-body fields required. If you prefer to poll instead, see the main docs for the sign-job and scan-job reference.

Registering a webhook endpoint

Go to Developer → Webhooks and add an endpoint URL with the events you want to subscribe to (e.g. job.completed, job.failed). NotarAI generates a signing secret for that endpoint — store it securely and use it to verify incoming payloads.

Payload format

The webhook POST body is JSON. The top-level event field is either job.completed or job.failed. All job details live under data.

For sign jobs, data.results contains one entry per asset with pre-signed download URLs valid for 1 hour. Use them immediately to download the signed file and certificate — no extra API call required.

json
{
  "event": "job.completed",
  "created_at": "2026-05-07T10:05:00.000Z",
  "data": {
    "job_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "job_type": "sign",
    "status": "completed",
    "total_assets": 2,
    "completed_assets": 2,
    "failed_assets": 0,
    "collection_id": null,
    "error": null,
    "results": [
      {
        "recordId": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
        "fileName": "hero.jpg",
        "status": "completed",
        "signedAssetUrl": "https://cdn.notarai.io/assets/signed/hero.jpg?token=...",
        "certificateUrl": "https://cdn.notarai.io/assets/certs/hero.pdf?token=..."
      },
      {
        "recordId": "b1ffcd00-1d1c-5f09-cc7e-7cc0ce490b22",
        "fileName": "banner.png",
        "status": "completed",
        "signedAssetUrl": "https://cdn.notarai.io/assets/signed/banner.png?token=...",
        "certificateUrl": "https://cdn.notarai.io/assets/certs/banner.pdf?token=..."
      }
    ]
  }
}

For scan jobs, data.results is omitted — poll GET /api/v1/scan-jobs/:id?items=true to retrieve per-asset scan metadata.

Signature verification

Every delivery includes a Notarize-Signature header in the format t=<unix_seconds>,v1=<hmac_sha256_hex>. The signed payload is timestamp.rawBody. Verify this on your server before processing the payload.

javascript
import { createHmac, timingSafeEqual } from "crypto";

function verifyWebhook(rawBody: string, secret: string, header: string): boolean {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const t = parts["t"];
  const v1 = parts["v1"];
  if (!t || !v1) return false;

  // Reject stale payloads to prevent replay attacks (>5 min old)
  const age = Math.floor(Date.now() / 1000) - Number(t);
  if (isNaN(age) || age > 300 || age < -60) return false;

  const expected = createHmac("sha256", secret)
    .update(`${t}.${rawBody}`)
    .digest("hex");

  const expectedBuf = Buffer.from(expected, "hex");
  const signatureBuf = Buffer.from(v1, "hex");
  if (expectedBuf.length !== signatureBuf.length) return false;
  return timingSafeEqual(expectedBuf, signatureBuf);
}

// In your request handler:
const sig = req.headers["notarize-signature"] as string;
const rawBody = await req.text();
if (!verifyWebhook(rawBody, process.env.WEBHOOK_SECRET!, sig)) {
  return new Response("Forbidden", { status: 403 });
}

Warning

Always use a constant-time comparison (e.g. timingSafeEqual) when verifying signatures. String equality (===) is vulnerable to timing attacks.

Retry policy

NotarAI uses QStash to deliver webhooks with automatic retries on non-2xx responses or network failures. Deliveries are retried up to 3 times with exponential back-off (5s, 30s, 5 min). After all retries are exhausted the delivery is marked failed and no further attempts are made.

Respond with any 2xx status as quickly as possible. For long-running processing, acknowledge the webhook immediately and handle the payload asynchronously.