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
/api/v1/assetsReturn all assets in your account (standalone and collection assets) with optional filters, pagination, and collection embedding. Required scope: jobs.read.
Query parameters
| Parameter | Type | Description |
|---|---|---|
| id | string | Filter by one asset UUID. Repeat id to pass multiple values. |
| ids | string | Comma-separated list of asset UUIDs (alternative to repeated id). |
| status | string | Filter by status: pending, scanning, scanned, signing, complete, error. |
| collection_id | string | Filter to one collection UUID. |
| standalone | boolean | If true, only return assets not in any collection. |
| include_archived | boolean | Include archived assets. Default: false. |
| include_collections | boolean | Include each asset's collection object when available. Default: false. |
Show common parameters (pagination · sort · fields)
| limit | integer | Page size. Default: 20, min: 1, max: 100. |
| offset | integer | Number of records to skip. Default: 0. |
| sort | string | Column to sort by. Default: created_at. Allowed: created_at, completed_at, file_name, status. |
| order | string | Sort direction: asc or desc. Default: desc. |
| fields | string | Comma-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
{
"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)
{
"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)
{
"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
/api/v1/collectionsList collections for your account. You can request one or many specific IDs via query filters. Required scope: jobs.read.
Query parameters
| Parameter | Type | Description |
|---|---|---|
| archived | boolean | Return archived collections instead of active ones. Default: false. |
| id | string | Filter by one collection UUID. Repeat id to pass multiple values. |
| ids | string | Comma-separated list of collection UUIDs (alternative to repeated id). |
| include_archived | boolean | Include both active and archived collections. Overrides archived. Default: false. |
| include_assets | boolean | Include the full assets array in every collection. Default: false. |
Show common parameters (pagination · sort · fields)
| limit | integer | Page size. Default: 20, min: 1, max: 100. |
| offset | integer | Number of records to skip. Default: 0. |
| sort | string | Column to sort by. Default: created_at. Allowed: created_at, name. |
| order | string | Sort direction: asc or desc. Default: desc. |
| fields | string | Comma-separated fields to include in each object. Omit to return all fields. Allowed: id, name, description, createdAt, isArchived, assetCount, completeCount, assets. |
Note
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)
{
"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
/api/v1/collections/:idRetrieve a single collection by ID. Required scope: jobs.read.
Path parameter
| Field | Type | Description |
|---|---|---|
| id | string * | Collection ID (UUID). |
Query parameters
| Parameter | Type | Description |
|---|---|---|
| include_assets | boolean | Include the full assets array in the response. Default: false. |
Show common parameters (pagination · sort · fields)
| limit | integer | Page size. Default: 20, min: 1, max: 100. Applies to the embedded assets list when include_assets=true. |
| offset | integer | Number of records to skip. Default: 0. Applies to the embedded assets list when include_assets=true. |
| sort | string | Column to sort by. Default: created_at. Allowed: created_at, completed_at, file_name, status. |
| order | string | Sort direction: asc or desc. Default: desc. |
| fields | string | Comma-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
{
"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)
{
"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
/api/v1/collections/:id/assetsReturn assets in a collection with optional filtering and pagination. Required scope: jobs.read.
Path parameter
| Field | Type | Description |
|---|---|---|
| id | string * | Collection ID (UUID). |
Query parameters
| Parameter | Type | Description |
|---|---|---|
| status | string | Filter by status: pending, scanning, scanned, signing, complete, error. |
| include_collections | boolean | Include a collection object on each asset. Default: false. |
Show common parameters (pagination · sort · fields)
| limit | integer | Page size. Default: 20, min: 1, max: 100. |
| offset | integer | Number of records to skip. Default: 0. |
| sort | string | Column to sort by. Default: created_at. Allowed: created_at, completed_at, file_name, status. |
| order | string | Sort direction: asc or desc. Default: desc. |
| fields | string | Comma-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
{
"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)
{
"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
/api/v1/workflowsReturn saved signing workflows owned by your account. You can request one or many specific IDs via query filters. Required scope: workflows.read.
Note
Query parameters
| Parameter | Type | Description |
|---|---|---|
| id | string | Filter by one workflow UUID. Repeat id to pass multiple values. |
| ids | string | Comma-separated list of workflow UUIDs (alternative to repeated id). |
Show common parameters (pagination · sort · fields)
| limit | integer | Page size. Default: 20, min: 1, max: 100. |
| offset | integer | Number of records to skip. Default: 0. |
| sort | string | Column to sort by. Default: created_at. Allowed: created_at, name. |
| order | string | Sort direction: asc or desc. Default: desc. |
| fields | string | Comma-separated fields to include in each object. Omit to return all fields. Allowed: id, name, declaration, createdAt. |
Response — 200 OK
{
"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
/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"
}Get verification records by collection
/api/v1/verify/collection/:idReturn all completed verification records for a collection. Required scope: verify.read.
Path parameter
| Field | Type | Description |
|---|---|---|
| id | string * | Collection ID (UUID). |
Response — 200 OK
{
"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
/api/v1/collectionsCreate a new empty collection. Required scope: jobs.read.
Request body
| Field | Type | Description |
|---|---|---|
| name | string * | Collection name. Max 255 characters. |
| description | string | Optional description. Max 1000 characters. |
Request — application/json
{
"name": "Summer Campaign 2026",
"description": "All assets for the summer campaign"
}Response — 201 Created
{
"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
/api/v1/collections/:id/signApply 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
| Field | Type | Description |
|---|---|---|
| id | string * | Collection ID (UUID) of the collection to sign. |
Request body
| 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. |
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_mediaeducationartinternalotherRequest — application/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
{
"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
status field of each result entry.Scan all assets in a collection
/api/v1/collections/:id/scansTrigger a scan for every asset currently in a collection. Required scope: scan.
Path parameter
| Field | Type | Description |
|---|---|---|
| id | string * | Collection ID (UUID) of the collection to scan. |
Note
Response — 200 OK
{
"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
/api/v1/collections/:id/assetsAdd 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
| Field | Type | Description |
|---|---|---|
| id | string * | Collection ID (UUID) to add assets into. |
Request — multipart/form-data (upload new files)
| Field | Type | Description |
|---|---|---|
| file | File * | One or more files. Repeat the same field name to upload multiple files. Max 20 MB per file. |
Response — 200 OK (upload mode)
{
"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)
| Field | Type | Description |
|---|---|---|
| ids | string[] * | Array of asset UUIDs to assign to this collection. Max 100 per request. |
| recordIds | string[] | Alias for ids. |
| assetIds | string[] | Alias for ids. |
Response — 200 OK (assign mode)
{
"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
/api/v1/workflows/:idUpload 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
| Field | Type | Description |
|---|---|---|
| id | string * | Workflow ID (UUID). |
Request — multipart/form-data
| Field | Type | Description |
|---|---|---|
| file | File * | One or more files. Repeat the same field name to upload multiple files. Max 20 MB per file. |
Response — 200 OK
{
"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
Run a workflow on a collection
/api/v1/workflows/:id/run-collectionApply a saved workflow to every asset in a collection. Required scopes: scan, sign, workflows.read.
Path parameter
| Field | Type | Description |
|---|---|---|
| id | string * | Workflow ID (UUID). |
Request — application/json
{
"collectionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}Response — 200 OK
{
"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
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.
{
"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.
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
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.