# Assets

An asset is a file, remote URL, or extracted child submitted to CalmLens for classification, together with its moderation report. Use these endpoints to upload, list, update, move, retry and delete assets.

Guides: [Uploading assets](/docs/guides/uploading-assets), [Asset lifecycle](/docs/guides/asset-lifecycle), [Delivering assets](/docs/guides/delivering-assets).

## Wait for moderation or queue it

For uploads, `queue=false` (the HTTP default) waits for classification and returns the decision. Set `queue=true` to return an asset ID while moderation runs, then receive an [`asset.classified`](/docs/api/events#event-asset-classified) event through a [webhook](/docs/guides/webhooks). Also handle [`asset.failed`](/docs/api/events#event-asset-failed) for processing failures.

The SDK moderate method selects `queue=false`; create selects `queue=true`. Text and images support both flows. Audio and video always run asynchronously and return `pending`, even with `queue=false`. See [Uploading assets](/docs/guides/uploading-assets#waiting-for-the-decision) for the complete flow.

## Endpoints

| Endpoint | Description |
| --- | --- |
| [`POST /projects/:projectId/assets`](#create-asset) | Upload a new asset to a project. |
| [`POST /projects/:projectId/assets/upload`](#upload-asset-file) | Upload a file by sending its raw bytes as the request body, with the `X-CalmLens-Upload: content` header and the file's `Content-Type`. |
| [`POST /projects/:projectId/assets/multipart-upload`](#start-multipart-file-upload) | Start a multipart upload by streaming the first part's raw bytes as the request body, with the `X-CalmLens-Upload: part` header and the file's `Content-Type`. |
| [`POST /assets/:assetId/part`](#upload-asset-part) | Upload a sequential part for an in-progress asset upload. |
| [`GET /projects/:projectId/assets/page`](#get-assets-page) | Get a paginated list of assets for a project |
| [`GET /projects/:projectId/assets/search`](#search-assets) | Search accessible assets by keyword or describe an image in natural language. |
| [`GET /assets/:assetId`](#get-asset) | Get a single asset by ID |
| [`GET /assets/:assetId/children`](#get-asset-children) | Get child assets of a parent asset |
| [`POST /assets/:assetId/retry`](#retry-asset) | Process an asset that failed (status error) again. |
| [`PATCH /assets/:assetId`](#update-asset) | Update an existing asset |
| [`POST /assets/:assetId/move`](#move-asset) | Move an asset into a folder, or to the project root |
| [`DELETE /assets/:assetId/content`](#remove-asset-content) | Remove stored content, including the content derived from it in child assets, while retaining the asset records |
| [`DELETE /assets/:assetId`](#delete-asset) | Delete an asset, its child assets and their stored content from a project |
| [`POST /projects/:projectId/assets/batch`](#run-asset-batch-operation) | Queue a bounded batch operation for selected assets |

## Endpoint details

### Create Asset

Upload a new asset to a project. Direct image uploads must be smaller than 10MB. Images from 10MB through 20MB must use multipart upload; CalmLens accumulates parts and enforces the final total, and checks `totalSizeInBytes` up front when you send it. Retained multipart image originals are served through CalmLens CDN URLs. Send an `Idempotency-Key` header to retry safely without creating a second asset.

**Endpoint:** `POST https://api.calmlens.com/v1/projects/:projectId/assets`

**Authentication:** API Key

#### Example request

```bash
curl -X POST "https://api.calmlens.com/v1/projects/{projectId}/assets?queue=true" \
  -H "X-Api-Key: your-api-key" \
  -H "Content-Type: application/json" \
  --data '{
  "name": "homepage-image.jpg",
  "url": "https://example.com/image.jpg",
  "visibility": "private",
  "processingOptions": {
    "keepContentAfterProcessing": true
  }
}'
```

#### Query parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `queue` | `boolean` | Yes | If true, queue the asset for asynchronous classification. If false, classify synchronously. Audio and video always run asynchronously and return pending. When this request starts a multipart upload, the choice is saved and applies when the final part arrives. |

#### Request body

```json
{
  "anyOf": [
    {
      "type": "object",
      "properties": {
        "name": {},
        "isFinal": {
          "not": {}
        },
        "description": {
          "anyOf": [
            {},
            {
              "type": "null"
            }
          ]
        },
        "visibility": {
          "anyOf": [
            {
              "type": "string",
              "enum": [
                "public",
                "private"
              ]
            },
            {
              "type": "null"
            }
          ]
        },
        "metadata": {
          "anyOf": [
            {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "id": {},
                  "value": {
                    "$ref": "#/$defs/__schema0"
                  }
                },
                "required": [
                  "id",
                  "value"
                ],
                "additionalProperties": false
              }
            },
            {
              "type": "null"
            }
          ]
        },
        "externalId": {
          "anyOf": [
            {},
            {
              "type": "null"
            }
          ]
        },
        "externalUrl": {
          "allOf": [
            {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uri"
                },
                {
                  "type": "null"
                }
              ]
            },
            {
              "type": "string",
              "format": "uri"
            }
          ]
        },
        "folderId": {
          "anyOf": [
            {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            },
            {
              "type": "null"
            }
          ]
        },
        "processingOptions": {
          "anyOf": [
            {
              "type": "object",
              "properties": {
                "keepContentAfterProcessing": {
                  "type": [
                    "boolean",
                    "null"
                  ]
                },
                "flaggedContentPolicy": {
                  "anyOf": [
                    {
                      "type": "object",
                      "properties": {
                        "action": {
                          "type": "string",
                          "enum": [
                            "ignore",
                            "alert",
                            "hide",
                            "purge"
                          ]
                        }
                      },
                      "required": [
                        "action"
                      ],
                      "additionalProperties": false
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "image": {
                  "anyOf": [
                    {
                      "type": "object",
                      "properties": {
                        "ocrEnabled": {
                          "type": [
                            "boolean",
                            "null"
                          ]
                        }
                      },
                      "additionalProperties": false
                    },
                    {
                      "type": "null"
                    }
                  ]
                }
              },
              "additionalProperties": false
            },
            {
              "type": "null"
            }
          ]
        },
        "content": {
          "anyOf": [
            {
              "type": "object",
              "properties": {
                "plainText": {
                  "type": "string"
                },
                "type": {
                  "type": "string"
                }
              },
              "required": [
                "plainText",
                "type"
              ],
              "additionalProperties": false
            },
            {
              "type": "object",
              "properties": {
                "base64": {
                  "type": "string"
                },
                "uint8Array": {
                  "not": {}
                },
                "buffer": {
                  "not": {}
                },
                "data": {
                  "not": {}
                },
                "type": {
                  "type": "string"
                }
              },
              "required": [
                "base64",
                "type"
              ],
              "additionalProperties": false
            },
            {
              "type": "object",
              "properties": {
                "uint8Array": {
                  "type": "array",
                  "items": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 255
                  }
                },
                "base64": {
                  "not": {}
                },
                "buffer": {
                  "not": {}
                },
                "data": {
                  "not": {}
                },
                "type": {
                  "type": "string"
                }
              },
              "required": [
                "uint8Array",
                "type"
              ],
              "additionalProperties": false
            },
            {
              "type": "object",
              "properties": {
                "buffer": {},
                "base64": {
                  "not": {}
                },
                "uint8Array": {
                  "not": {}
                },
                "data": {
                  "not": {}
                },
                "type": {
                  "type": "string"
                }
              },
              "required": [
                "buffer",
                "type"
              ],
              "additionalProperties": false
            },
            {
              "type": "object",
              "properties": {
                "data": {},
                "base64": {
                  "not": {}
                },
                "uint8Array": {
                  "not": {}
                },
                "buffer": {
                  "not": {}
                },
                "type": {
                  "type": "string"
                }
              },
              "required": [
                "data",
                "type"
              ],
              "additionalProperties": false
            }
          ]
        }
      },
      "required": [
        "name",
        "content"
      ],
      "additionalProperties": false
    },
    {
      "type": "object",
      "properties": {
        "name": {},
        "isFinal": {
          "not": {}
        },
        "description": {
          "anyOf": [
            {},
            {
              "type": "null"
            }
          ]
        },
        "visibility": {
          "anyOf": [
            {
              "type": "string",
              "enum": [
                "public",
                "private"
              ]
            },
            {
              "type": "null"
            }
          ]
        },
        "metadata": {
          "anyOf": [
            {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "id": {},
                  "value": {
                    "$ref": "#/$defs/__schema0"
                  }
                },
                "required": [
                  "id",
                  "value"
                ],
                "additionalProperties": false
              }
            },
            {
              "type": "null"
            }
          ]
        },
        "externalId": {
          "anyOf": [
            {},
            {
              "type": "null"
            }
          ]
        },
        "externalUrl": {
          "anyOf": [
            {
              "type": "string",
              "format": "uri"
            },
            {
              "type": "null"
            }
          ]
        },
        "folderId": {
          "anyOf": [
            {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            },
            {
              "type": "null"
            }
          ]
        },
        "processingOptions": {
          "anyOf": [
            {
              "type": "object",
              "properties": {
                "keepContentAfterProcessing": {
                  "type": [
                    "boolean",
                    "null"
                  ]
                },
                "flaggedContentPolicy": {
                  "anyOf": [
                    {
                      "type": "object",
                      "properties": {
                        "action": {
                          "type": "string",
                          "enum": [
                            "ignore",
                            "alert",
                            "hide",
                            "purge"
                          ]
                        }
                      },
                      "required": [
                        "action"
                      ],
                      "additionalProperties": false
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "image": {
                  "anyOf": [
                    {
                      "type": "object",
                      "properties": {
                        "ocrEnabled": {
                          "type": [
                            "boolean",
                            "null"
                          ]
                        }
                      },
                      "additionalProperties": false
                    },
                    {
                      "type": "null"
                    }
                  ]
                }
              },
              "additionalProperties": false
            },
            {
              "type": "null"
            }
          ]
        },
        "part": {
          "anyOf": [
            {
              "type": "object",
              "properties": {
                "plainText": {
                  "type": "string"
                },
                "base64": {
                  "not": {}
                },
                "uint8Array": {
                  "not": {}
                },
                "buffer": {
                  "not": {}
                },
                "data": {
                  "not": {}
                },
                "type": {
                  "type": "string"
                },
                "partNumber": {
                  "default": 1,
                  "type": "integer",
                  "exclusiveMinimum": 0,
                  "maximum": 9007199254740991
                },
                "isFinal": {
                  "default": false,
                  "type": "boolean"
                }
              },
              "required": [
                "plainText",
                "type",
                "partNumber",
                "isFinal"
              ],
              "additionalProperties": false
            },
            {
              "type": "object",
              "properties": {
                "base64": {
                  "type": "string"
                },
                "uint8Array": {
                  "not": {}
                },
                "buffer": {
                  "not": {}
                },
                "data": {
                  "not": {}
                },
                "type": {
                  "type": "string"
                },
                "partNumber": {
                  "default": 1,
                  "type": "integer",
                  "exclusiveMinimum": 0,
                  "maximum": 9007199254740991
                },
                "isFinal": {
                  "default": false,
                  "type": "boolean"
                }
              },
              "required": [
                "base64",
                "type",
                "partNumber",
                "isFinal"
              ],
              "additionalProperties": false
            },
            {
              "type": "object",
              "properties": {
                "uint8Array": {
                  "type": "array",
                  "items": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 255
                  }
                },
                "base64": {
                  "not": {}
                },
                "buffer": {
                  "not": {}
                },
                "data": {
                  "not": {}
                },
                "type": {
                  "type": "string"
                },
                "partNumber": {
                  "default": 1,
                  "type": "integer",
                  "exclusiveMinimum": 0,
                  "maximum": 9007199254740991
                },
                "isFinal": {
                  "default": false,
                  "type": "boolean"
                }
              },
              "required": [
                "uint8Array",
                "type",
                "partNumber",
                "isFinal"
              ],
              "additionalProperties": false
            },
            {
              "type": "object",
              "properties": {
                "buffer": {},
                "base64": {
                  "not": {}
                },
                "uint8Array": {
                  "not": {}
                },
                "data": {
                  "not": {}
                },
                "type": {
                  "type": "string"
                },
                "partNumber": {
                  "default": 1,
                  "type": "integer",
                  "exclusiveMinimum": 0,
                  "maximum": 9007199254740991
                },
                "isFinal": {
                  "default": false,
                  "type": "boolean"
                }
              },
              "required": [
                "buffer",
                "type",
                "partNumber",
                "isFinal"
              ],
              "additionalProperties": false
            },
            {
              "type": "object",
              "properties": {
                "data": {},
                "base64": {
                  "not": {}
                },
                "uint8Array": {
                  "not": {}
                },
                "buffer": {
                  "not": {}
                },
                "type": {
                  "type": "string"
                },
                "partNumber": {
                  "default": 1,
                  "type": "integer",
                  "exclusiveMinimum": 0,
                  "maximum": 9007199254740991
                },
                "isFinal": {
                  "default": false,
                  "type": "boolean"
                }
              },
              "required": [
                "data",
                "type",
                "partNumber",
                "isFinal"
              ],
              "additionalProperties": false
            }
          ]
        },
        "sizeInBytes": {
          "type": "integer",
          "exclusiveMinimum": 0,
          "maximum": 9007199254740991
        },
        "totalSizeInBytes": {
          "type": "integer",
          "exclusiveMinimum": 0,
          "maximum": 9007199254740991
        }
      },
      "required": [
        "name",
        "part"
      ],
      "additionalProperties": false
    },
    {
      "type": "object",
      "properties": {
        "name": {},
        "isFinal": {
          "not": {}
        },
        "description": {
          "anyOf": [
            {},
            {
              "type": "null"
            }
          ]
        },
        "visibility": {
          "anyOf": [
            {
              "type": "string",
              "enum": [
                "public",
                "private"
              ]
            },
            {
              "type": "null"
            }
          ]
        },
        "metadata": {
          "anyOf": [
            {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "id": {},
                  "value": {
                    "$ref": "#/$defs/__schema0"
                  }
                },
                "required": [
                  "id",
                  "value"
                ],
                "additionalProperties": false
              }
            },
            {
              "type": "null"
            }
          ]
        },
        "externalId": {
          "anyOf": [
            {},
            {
              "type": "null"
            }
          ]
        },
        "externalUrl": {
          "anyOf": [
            {
              "type": "string",
              "format": "uri"
            },
            {
              "type": "null"
            }
          ]
        },
        "folderId": {
          "anyOf": [
            {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            },
            {
              "type": "null"
            }
          ]
        },
        "processingOptions": {
          "anyOf": [
            {
              "type": "object",
              "properties": {
                "keepContentAfterProcessing": {
                  "type": [
                    "boolean",
                    "null"
                  ]
                },
                "flaggedContentPolicy": {
                  "anyOf": [
                    {
                      "type": "object",
                      "properties": {
                        "action": {
                          "type": "string",
                          "enum": [
                            "ignore",
                            "alert",
                            "hide",
                            "purge"
                          ]
                        }
                      },
                      "required": [
                        "action"
                      ],
                      "additionalProperties": false
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "image": {
                  "anyOf": [
                    {
                      "type": "object",
                      "properties": {
                        "ocrEnabled": {
                          "type": [
                            "boolean",
                            "null"
                          ]
                        }
                      },
                      "additionalProperties": false
                    },
                    {
                      "type": "null"
                    }
                  ]
                }
              },
              "additionalProperties": false
            },
            {
              "type": "null"
            }
          ]
        },
        "url": {
          "type": "string",
          "format": "uri"
        }
      },
      "required": [
        "name",
        "url"
      ],
      "additionalProperties": false
    }
  ],
  "$defs": {}
}
```

#### Response

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | Yes | Unique asset ID |
| `projectId` | `string` | Yes | ID of the project that owns the asset |
| `sizeInBytes` | `number,null` | No | Size of the uploaded content in bytes |
| `userId` | `unknown \| null` | No | ID of the user whose account created the asset |
| `parentId` | `string \| null` | No | ID of the parent asset for child assets CalmLens extracts during processing, such as images in a document or OCR text. `null` for uploaded assets. |
| `folderId` | `string \| null` | No | Folder containing the asset |
| `createdAt` | `integer` | Yes | When the asset was created, in milliseconds since the Unix epoch |
| `updatedAt` | `integer` | No | When the asset was last updated, in milliseconds since the Unix epoch |
| `checksums` | `object \| null` | No | Digests of the uploaded content |
| `imageData` | `object \| null` | No | Image properties, for image assets |
| `documentData` | `object \| null` | No | Document properties, for documents |
| `name` | `unknown` | Yes | Name of the asset |
| `fileFormat` | `unknown \| null` | No | MIME type of the stored content |
| `fileExtension` | `unknown \| null` | No | File extension of the stored content |
| `description` | `unknown \| null` | No | Optional description of the asset |
| `externalId` | `unknown \| null` | No | External identifier for the asset |
| `externalUrl` | `string \| null` | No | External URL associated with the asset |
| `previewImage` | `object \| null` | No | Browser-friendly preview generated for images, documents and websites |
| `storageId` | `unknown \| null` | No | Internal storage key of the retained content; `null` when nothing is stored |
| `uploadPartsReceived` | `integer \| null` | No | Number of parts received so far for a multipart upload |
| `type` | `image \| video \| audio \| plaintext-document \| multimedia-document \| website \| file` | Yes | Asset type, detected from the content's MIME type |
| `metadata` | `array<MetadataItem> \| null` | No | List of custom metadata items for the asset |
| `tags` | `array<unknown> \| null` | No | Array of tags for the asset |
| `report` | `object \| null` | No | Moderation report, set once classification finishes. See [Report format](/docs/guides/moderation-reports). |
| `moderationAction` | `ignore \| alert \| hide \| purge \| null` | No | The moderation action applied when this asset crossed a policy threshold |
| `status` | `uploading \| pending \| approved \| rejected \| error` | Yes | Processing and moderation status. See [Asset lifecycle](/docs/guides/asset-lifecycle). |
| `safetyHold` | `object \| null` | No | Internal delivery restriction for suspected illegal content. Held content cannot be viewed, changed, or deleted through normal asset operations. |
| `rejectionNote` | `unknown \| null` | No | Reason the asset was rejected, generated automatically or supplied by a reviewer |
| `extractedText` | `unknown \| null` | No | Extracted text stored inline for small content or in R2 for larger content |
| `processingOptions` | `object \| null` | No | Processing options supplied when the asset was uploaded |
| `visibility` | `public \| private \| null` | No | Visibility level of the asset |
| `storageDeletedAt` | `number,null` | No | When the stored content was removed, in milliseconds since the Unix epoch. The asset record and report remain. |

#### Error codes

| Status | Code | Description |
| --- | --- | --- |
| 401 | <a id="post-asset-error-auth-missing-token" />`auth/missing-token` | Authentication required. Send your project API key in the X-Api-Key header. |
| 401 | <a id="post-asset-error-auth-invalid-api-key" />`auth/invalid-api-key` | The API key is invalid, revoked or expired. Create a key under Developer → API Keys. |
| 403 | <a id="post-asset-error-auth-project-mismatch" />`auth/project-mismatch` | This API key belongs to a different project. Use a key created for the project that owns this resource. |
| 400 | <a id="post-asset-error-upload-file-too-large" />`upload/file-too-large` | File size exceeds limit. Maximum direct image size is 10MB, audio/video size is 50MiB, and other files is 5GB. |
| 400 | <a id="post-asset-error-upload-image-requires-multipart-upload" />`upload/image-requires-multipart-upload` | Images of 10MB or more must use a multipart upload. |
| 400 | <a id="post-asset-error-upload-multipart-image-too-small" />`upload/multipart-image-too-small` | Multipart image uploads must total at least 10MB. |
| 400 | <a id="post-asset-error-upload-multipart-image-too-large" />`upload/multipart-image-too-large` | Multipart image uploads cannot exceed 20MB. |
| 403 | <a id="post-asset-error-upload-limit-exceeded" />`upload/limit-exceeded` | Upload limit exceeded. You have used all your free uploads. Please upgrade to Plus or Pro for unlimited uploads. |
| 409 | <a id="post-asset-error-asset-duplicate-external-id" />`asset/duplicate-external-id` | An asset with this externalId already exists in the project. |
| 404 | <a id="post-asset-error-folder-not-found" />`folder/not-found` | Folder not found |
| 400 | <a id="post-asset-error-upload-invalid-mime-type" />`upload/invalid-mime-type` | Upload file type is not supported. Uploads must use a concrete file MIME type that CalmLens can store and process, such as image/png, image/jpeg, application/pdf, or text/plain. |
| 400 | <a id="post-asset-error-upload-invalid-payload" />`upload/invalid-payload` | Upload payload is invalid. |
| 409 | <a id="post-asset-error-upload-use-upload-part" />`upload/use-upload-part` | Use uploadPart for multipart upload parts after the first part |
| 400 | <a id="post-asset-error-idempotency-invalid-key" />`idempotency/invalid-key` | The Idempotency-Key header must be 1 to 255 printable ASCII characters, such as a UUID. |
| 409 | <a id="post-asset-error-idempotency-key-reused" />`idempotency/key-reused` | This Idempotency-Key was already used for a different request. Use a new key for each distinct request. |
| 409 | <a id="post-asset-error-idempotency-request-in-progress" />`idempotency/request-in-progress` | A request with this Idempotency-Key is still being processed. Retry after the Retry-After delay to receive its result. |

***

### Upload Asset File

Upload a file by sending its raw bytes as the request body, with the `X-CalmLens-Upload: content` header and the file's `Content-Type`. Asset fields are query parameters. This is the upload path the SDKs use for files. Send an `Idempotency-Key` header to retry safely without creating a second asset.

**Endpoint:** `POST https://api.calmlens.com/v1/projects/:projectId/assets/upload`

**Authentication:** API Key

#### Example request

```bash
curl -X POST "https://api.calmlens.com/v1/projects/{projectId}/assets/upload?name=cat.jpg&contentType=image%2Fjpeg&queue=true&visibility=private" \
  -H "X-Api-Key: your-api-key"
```

#### Query parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | Yes | Name of the asset |
| `contentType` | `string` | Yes | MIME type of the file, such as `image/jpeg`. Ambiguous types are rejected. |
| `queue` | `boolean` | Yes | If true, queue classification and return straight away. If false, wait for the decision. A multipart upload saves this choice and applies it when the final part arrives. |
| `description` | `string` | No | Optional description of the asset |
| `processingOptions` | `object \| null` | No | Processing options as URL-encoded JSON, such as `{"keepContentAfterProcessing":true}` |
| `visibility` | `public \| private` | No | Visibility of the asset |
| `metadata` | `array<MetadataItem> \| null` | No | Custom metadata as a URL-encoded JSON array of `{ id, value }` items |
| `externalId` | `string` | No | Your identifier for the asset |
| `externalUrl` | `string` | No | URL in your app associated with the asset |
| `folderId` | `string` | No | ID of the folder to upload into |
| `partNumber` | `integer` | No | Multipart uploads only: the part number, starting at 1 |
| `isFinal` | `boolean` | Yes | Multipart uploads only: true when this is the last part |
| `sizeInBytes` | `integer` | No | The exact size of this request's body in bytes. For a multipart upload, the size of the first part. |
| `totalSizeInBytes` | `integer` | No | Multipart uploads only: the size of the whole file in bytes, if known. CalmLens checks it against the upload limits before any part is stored, and the final part fails with `upload/size-mismatch` if the parts add up to a different size. |

#### Response

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | Yes | Unique asset ID |
| `projectId` | `string` | Yes | ID of the project that owns the asset |
| `sizeInBytes` | `number,null` | No | Size of the uploaded content in bytes |
| `userId` | `unknown \| null` | No | ID of the user whose account created the asset |
| `parentId` | `string \| null` | No | ID of the parent asset for child assets CalmLens extracts during processing, such as images in a document or OCR text. `null` for uploaded assets. |
| `folderId` | `string \| null` | No | Folder containing the asset |
| `createdAt` | `integer` | Yes | When the asset was created, in milliseconds since the Unix epoch |
| `updatedAt` | `integer` | No | When the asset was last updated, in milliseconds since the Unix epoch |
| `checksums` | `object \| null` | No | Digests of the uploaded content |
| `imageData` | `object \| null` | No | Image properties, for image assets |
| `documentData` | `object \| null` | No | Document properties, for documents |
| `name` | `unknown` | Yes | Name of the asset |
| `fileFormat` | `unknown \| null` | No | MIME type of the stored content |
| `fileExtension` | `unknown \| null` | No | File extension of the stored content |
| `description` | `unknown \| null` | No | Optional description of the asset |
| `externalId` | `unknown \| null` | No | External identifier for the asset |
| `externalUrl` | `string \| null` | No | External URL associated with the asset |
| `previewImage` | `object \| null` | No | Browser-friendly preview generated for images, documents and websites |
| `storageId` | `unknown \| null` | No | Internal storage key of the retained content; `null` when nothing is stored |
| `uploadPartsReceived` | `integer \| null` | No | Number of parts received so far for a multipart upload |
| `type` | `image \| video \| audio \| plaintext-document \| multimedia-document \| website \| file` | Yes | Asset type, detected from the content's MIME type |
| `metadata` | `array<MetadataItem> \| null` | No | List of custom metadata items for the asset |
| `tags` | `array<unknown> \| null` | No | Array of tags for the asset |
| `report` | `object \| null` | No | Moderation report, set once classification finishes. See [Report format](/docs/guides/moderation-reports). |
| `moderationAction` | `ignore \| alert \| hide \| purge \| null` | No | The moderation action applied when this asset crossed a policy threshold |
| `status` | `uploading \| pending \| approved \| rejected \| error` | Yes | Processing and moderation status. See [Asset lifecycle](/docs/guides/asset-lifecycle). |
| `safetyHold` | `object \| null` | No | Internal delivery restriction for suspected illegal content. Held content cannot be viewed, changed, or deleted through normal asset operations. |
| `rejectionNote` | `unknown \| null` | No | Reason the asset was rejected, generated automatically or supplied by a reviewer |
| `extractedText` | `unknown \| null` | No | Extracted text stored inline for small content or in R2 for larger content |
| `processingOptions` | `object \| null` | No | Processing options supplied when the asset was uploaded |
| `visibility` | `public \| private \| null` | No | Visibility level of the asset |
| `storageDeletedAt` | `number,null` | No | When the stored content was removed, in milliseconds since the Unix epoch. The asset record and report remain. |

#### Error codes

| Status | Code | Description |
| --- | --- | --- |
| 401 | <a id="post-asset-upload-error-auth-missing-token" />`auth/missing-token` | Authentication required. Send your project API key in the X-Api-Key header. |
| 401 | <a id="post-asset-upload-error-auth-invalid-api-key" />`auth/invalid-api-key` | The API key is invalid, revoked or expired. Create a key under Developer → API Keys. |
| 403 | <a id="post-asset-upload-error-auth-project-mismatch" />`auth/project-mismatch` | This API key belongs to a different project. Use a key created for the project that owns this resource. |
| 400 | <a id="post-asset-upload-error-upload-file-too-large" />`upload/file-too-large` | File size exceeds limit. Maximum direct image size is 10MB, audio/video size is 50MiB, and other files is 5GB. |
| 400 | <a id="post-asset-upload-error-upload-image-requires-multipart-upload" />`upload/image-requires-multipart-upload` | Images of 10MB or more must use a multipart upload. |
| 400 | <a id="post-asset-upload-error-upload-multipart-image-too-small" />`upload/multipart-image-too-small` | Multipart image uploads must total at least 10MB. |
| 400 | <a id="post-asset-upload-error-upload-multipart-image-too-large" />`upload/multipart-image-too-large` | Multipart image uploads cannot exceed 20MB. |
| 403 | <a id="post-asset-upload-error-upload-limit-exceeded" />`upload/limit-exceeded` | Upload limit exceeded. You have used all your free uploads. Please upgrade to Plus or Pro for unlimited uploads. |
| 409 | <a id="post-asset-upload-error-asset-duplicate-external-id" />`asset/duplicate-external-id` | An asset with this externalId already exists in the project. |
| 404 | <a id="post-asset-upload-error-folder-not-found" />`folder/not-found` | Folder not found |
| 400 | <a id="post-asset-upload-error-upload-invalid-mime-type" />`upload/invalid-mime-type` | Upload file type is not supported. Uploads must use a concrete file MIME type that CalmLens can store and process, such as image/png, image/jpeg, application/pdf, or text/plain. |
| 400 | <a id="post-asset-upload-error-upload-invalid-payload" />`upload/invalid-payload` | Upload payload is invalid. |
| 400 | <a id="post-asset-upload-error-idempotency-invalid-key" />`idempotency/invalid-key` | The Idempotency-Key header must be 1 to 255 printable ASCII characters, such as a UUID. |
| 409 | <a id="post-asset-upload-error-idempotency-key-reused" />`idempotency/key-reused` | This Idempotency-Key was already used for a different request. Use a new key for each distinct request. |
| 409 | <a id="post-asset-upload-error-idempotency-request-in-progress" />`idempotency/request-in-progress` | A request with this Idempotency-Key is still being processed. Retry after the Retry-After delay to receive its result. |

***

### Start Multipart File Upload

Start a multipart upload by streaming the first part's raw bytes as the request body, with the `X-CalmLens-Upload: part` header and the file's `Content-Type`. Pass `partNumber=1`, `isFinal`, the part's `sizeInBytes` and, if known, the file's `totalSizeInBytes` as query parameters, then send later parts to Upload Asset Part. The `queue` choice is saved with the upload and applies when the final part arrives.

**Endpoint:** `POST https://api.calmlens.com/v1/projects/:projectId/assets/multipart-upload`

**Authentication:** API Key

#### Example request

```bash
curl -X POST "https://api.calmlens.com/v1/projects/{projectId}/assets/multipart-upload?name=large-video.mp4&contentType=video%2Fmp4&queue=true&partNumber=1&isFinal=false" \
  -H "X-Api-Key: your-api-key"
```

#### Query parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | Yes | Name of the asset |
| `contentType` | `string` | Yes | MIME type of the file, such as `image/jpeg`. Ambiguous types are rejected. |
| `queue` | `boolean` | Yes | If true, queue classification and return straight away. If false, wait for the decision. A multipart upload saves this choice and applies it when the final part arrives. |
| `description` | `string` | No | Optional description of the asset |
| `processingOptions` | `object \| null` | No | Processing options as URL-encoded JSON, such as `{"keepContentAfterProcessing":true}` |
| `visibility` | `public \| private` | No | Visibility of the asset |
| `metadata` | `array<MetadataItem> \| null` | No | Custom metadata as a URL-encoded JSON array of `{ id, value }` items |
| `externalId` | `string` | No | Your identifier for the asset |
| `externalUrl` | `string` | No | URL in your app associated with the asset |
| `folderId` | `string` | No | ID of the folder to upload into |
| `partNumber` | `integer` | No | Multipart uploads only: the part number, starting at 1 |
| `isFinal` | `boolean` | Yes | Multipart uploads only: true when this is the last part |
| `sizeInBytes` | `integer` | No | The exact size of this request's body in bytes. For a multipart upload, the size of the first part. |
| `totalSizeInBytes` | `integer` | No | Multipart uploads only: the size of the whole file in bytes, if known. CalmLens checks it against the upload limits before any part is stored, and the final part fails with `upload/size-mismatch` if the parts add up to a different size. |

#### Response

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | Yes | Unique asset ID |
| `projectId` | `string` | Yes | ID of the project that owns the asset |
| `sizeInBytes` | `number,null` | No | Size of the uploaded content in bytes |
| `userId` | `unknown \| null` | No | ID of the user whose account created the asset |
| `parentId` | `string \| null` | No | ID of the parent asset for child assets CalmLens extracts during processing, such as images in a document or OCR text. `null` for uploaded assets. |
| `folderId` | `string \| null` | No | Folder containing the asset |
| `createdAt` | `integer` | Yes | When the asset was created, in milliseconds since the Unix epoch |
| `updatedAt` | `integer` | No | When the asset was last updated, in milliseconds since the Unix epoch |
| `checksums` | `object \| null` | No | Digests of the uploaded content |
| `imageData` | `object \| null` | No | Image properties, for image assets |
| `documentData` | `object \| null` | No | Document properties, for documents |
| `name` | `unknown` | Yes | Name of the asset |
| `fileFormat` | `unknown \| null` | No | MIME type of the stored content |
| `fileExtension` | `unknown \| null` | No | File extension of the stored content |
| `description` | `unknown \| null` | No | Optional description of the asset |
| `externalId` | `unknown \| null` | No | External identifier for the asset |
| `externalUrl` | `string \| null` | No | External URL associated with the asset |
| `previewImage` | `object \| null` | No | Browser-friendly preview generated for images, documents and websites |
| `storageId` | `unknown \| null` | No | Internal storage key of the retained content; `null` when nothing is stored |
| `uploadPartsReceived` | `integer \| null` | No | Number of parts received so far for a multipart upload |
| `type` | `image \| video \| audio \| plaintext-document \| multimedia-document \| website \| file` | Yes | Asset type, detected from the content's MIME type |
| `metadata` | `array<MetadataItem> \| null` | No | List of custom metadata items for the asset |
| `tags` | `array<unknown> \| null` | No | Array of tags for the asset |
| `report` | `object \| null` | No | Moderation report, set once classification finishes. See [Report format](/docs/guides/moderation-reports). |
| `moderationAction` | `ignore \| alert \| hide \| purge \| null` | No | The moderation action applied when this asset crossed a policy threshold |
| `status` | `uploading \| pending \| approved \| rejected \| error` | Yes | Processing and moderation status. See [Asset lifecycle](/docs/guides/asset-lifecycle). |
| `safetyHold` | `object \| null` | No | Internal delivery restriction for suspected illegal content. Held content cannot be viewed, changed, or deleted through normal asset operations. |
| `rejectionNote` | `unknown \| null` | No | Reason the asset was rejected, generated automatically or supplied by a reviewer |
| `extractedText` | `unknown \| null` | No | Extracted text stored inline for small content or in R2 for larger content |
| `processingOptions` | `object \| null` | No | Processing options supplied when the asset was uploaded |
| `visibility` | `public \| private \| null` | No | Visibility level of the asset |
| `storageDeletedAt` | `number,null` | No | When the stored content was removed, in milliseconds since the Unix epoch. The asset record and report remain. |

#### Error codes

| Status | Code | Description |
| --- | --- | --- |
| 401 | <a id="post-asset-multipart-upload-error-auth-missing-token" />`auth/missing-token` | Authentication required. Send your project API key in the X-Api-Key header. |
| 401 | <a id="post-asset-multipart-upload-error-auth-invalid-api-key" />`auth/invalid-api-key` | The API key is invalid, revoked or expired. Create a key under Developer → API Keys. |
| 403 | <a id="post-asset-multipart-upload-error-auth-project-mismatch" />`auth/project-mismatch` | This API key belongs to a different project. Use a key created for the project that owns this resource. |
| 400 | <a id="post-asset-multipart-upload-error-upload-file-too-large" />`upload/file-too-large` | File size exceeds limit. Maximum direct image size is 10MB, audio/video size is 50MiB, and other files is 5GB. |
| 400 | <a id="post-asset-multipart-upload-error-upload-image-requires-multipart-upload" />`upload/image-requires-multipart-upload` | Images of 10MB or more must use a multipart upload. |
| 400 | <a id="post-asset-multipart-upload-error-upload-multipart-image-too-small" />`upload/multipart-image-too-small` | Multipart image uploads must total at least 10MB. |
| 400 | <a id="post-asset-multipart-upload-error-upload-multipart-image-too-large" />`upload/multipart-image-too-large` | Multipart image uploads cannot exceed 20MB. |
| 403 | <a id="post-asset-multipart-upload-error-upload-limit-exceeded" />`upload/limit-exceeded` | Upload limit exceeded. You have used all your free uploads. Please upgrade to Plus or Pro for unlimited uploads. |
| 409 | <a id="post-asset-multipart-upload-error-asset-duplicate-external-id" />`asset/duplicate-external-id` | An asset with this externalId already exists in the project. |
| 404 | <a id="post-asset-multipart-upload-error-folder-not-found" />`folder/not-found` | Folder not found |
| 400 | <a id="post-asset-multipart-upload-error-upload-invalid-mime-type" />`upload/invalid-mime-type` | Upload file type is not supported. Uploads must use a concrete file MIME type that CalmLens can store and process, such as image/png, image/jpeg, application/pdf, or text/plain. |
| 400 | <a id="post-asset-multipart-upload-error-upload-invalid-payload" />`upload/invalid-payload` | Upload payload is invalid. |
| 400 | <a id="post-asset-multipart-upload-error-idempotency-invalid-key" />`idempotency/invalid-key` | The Idempotency-Key header must be 1 to 255 printable ASCII characters, such as a UUID. |
| 409 | <a id="post-asset-multipart-upload-error-idempotency-key-reused" />`idempotency/key-reused` | This Idempotency-Key was already used for a different request. Use a new key for each distinct request. |
| 409 | <a id="post-asset-multipart-upload-error-idempotency-request-in-progress" />`idempotency/request-in-progress` | A request with this Idempotency-Key is still being processed. Retry after the Retry-After delay to receive its result. |

***

### Upload Asset Part

Upload a sequential part for an in-progress asset upload. Image multipart uploads must total from 10MB through 20MB; each non-final part must be at least 5MiB. The final part is classified with the `queue` choice saved when the upload started unless you pass `queue` here. To stream raw bytes instead of JSON, send the `X-CalmLens-Upload: part` header and pass `partNumber`, `isFinal` and `sizeInBytes` as query parameters.

**Endpoint:** `POST https://api.calmlens.com/v1/assets/:assetId/part`

**Authentication:** API Key

#### Example request

```bash
curl -X POST "https://api.calmlens.com/v1/assets/987fcdeb-51a2-43d7-b890-123456789abc/part" \
  -H "X-Api-Key: your-api-key" \
  -H "Content-Type: application/json" \
  --data '{
  "part": {
    "base64": "<base64-encoded-part>",
    "partNumber": 2,
    "isFinal": false
  }
}'
```

#### Query parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `queue` | `boolean` | No | Overrides how the final part is classified: true queues it, false waits for the decision. When omitted, the upload uses the `queue` value sent when it started. Audio and video always run asynchronously and return pending. |

#### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `part` | `object \| object \| object \| object \| object` | Yes |  |

#### Response

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | Yes | Unique asset ID |
| `projectId` | `string` | Yes | ID of the project that owns the asset |
| `sizeInBytes` | `number,null` | No | Size of the uploaded content in bytes |
| `userId` | `unknown \| null` | No | ID of the user whose account created the asset |
| `parentId` | `string \| null` | No | ID of the parent asset for child assets CalmLens extracts during processing, such as images in a document or OCR text. `null` for uploaded assets. |
| `folderId` | `string \| null` | No | Folder containing the asset |
| `createdAt` | `integer` | Yes | When the asset was created, in milliseconds since the Unix epoch |
| `updatedAt` | `integer` | No | When the asset was last updated, in milliseconds since the Unix epoch |
| `checksums` | `object \| null` | No | Digests of the uploaded content |
| `imageData` | `object \| null` | No | Image properties, for image assets |
| `documentData` | `object \| null` | No | Document properties, for documents |
| `name` | `unknown` | Yes | Name of the asset |
| `fileFormat` | `unknown \| null` | No | MIME type of the stored content |
| `fileExtension` | `unknown \| null` | No | File extension of the stored content |
| `description` | `unknown \| null` | No | Optional description of the asset |
| `externalId` | `unknown \| null` | No | External identifier for the asset |
| `externalUrl` | `string \| null` | No | External URL associated with the asset |
| `previewImage` | `object \| null` | No | Browser-friendly preview generated for images, documents and websites |
| `storageId` | `unknown \| null` | No | Internal storage key of the retained content; `null` when nothing is stored |
| `uploadPartsReceived` | `integer \| null` | No | Number of parts received so far for a multipart upload |
| `type` | `image \| video \| audio \| plaintext-document \| multimedia-document \| website \| file` | Yes | Asset type, detected from the content's MIME type |
| `metadata` | `array<MetadataItem> \| null` | No | List of custom metadata items for the asset |
| `tags` | `array<unknown> \| null` | No | Array of tags for the asset |
| `report` | `object \| null` | No | Moderation report, set once classification finishes. See [Report format](/docs/guides/moderation-reports). |
| `moderationAction` | `ignore \| alert \| hide \| purge \| null` | No | The moderation action applied when this asset crossed a policy threshold |
| `status` | `uploading \| pending \| approved \| rejected \| error` | Yes | Processing and moderation status. See [Asset lifecycle](/docs/guides/asset-lifecycle). |
| `safetyHold` | `object \| null` | No | Internal delivery restriction for suspected illegal content. Held content cannot be viewed, changed, or deleted through normal asset operations. |
| `rejectionNote` | `unknown \| null` | No | Reason the asset was rejected, generated automatically or supplied by a reviewer |
| `extractedText` | `unknown \| null` | No | Extracted text stored inline for small content or in R2 for larger content |
| `processingOptions` | `object \| null` | No | Processing options supplied when the asset was uploaded |
| `visibility` | `public \| private \| null` | No | Visibility level of the asset |
| `storageDeletedAt` | `number,null` | No | When the stored content was removed, in milliseconds since the Unix epoch. The asset record and report remain. |

#### Error codes

| Status | Code | Description |
| --- | --- | --- |
| 401 | <a id="post-asset-part-error-auth-missing-token" />`auth/missing-token` | Authentication required. Send your project API key in the X-Api-Key header. |
| 401 | <a id="post-asset-part-error-auth-invalid-api-key" />`auth/invalid-api-key` | The API key is invalid, revoked or expired. Create a key under Developer → API Keys. |
| 403 | <a id="post-asset-part-error-auth-project-mismatch" />`auth/project-mismatch` | This API key belongs to a different project. Use a key created for the project that owns this resource. |
| 404 | <a id="post-asset-part-error-asset-not-found" />`asset/not-found` | Asset not found |
| 400 | <a id="post-asset-part-error-upload-file-too-large" />`upload/file-too-large` | File size exceeds limit. Maximum direct image size is 10MB, audio/video size is 50MiB, and other files is 5GB. |
| 400 | <a id="post-asset-part-error-upload-multipart-image-too-small" />`upload/multipart-image-too-small` | Multipart image uploads must total at least 10MB. |
| 400 | <a id="post-asset-part-error-upload-multipart-image-too-large" />`upload/multipart-image-too-large` | Multipart image uploads cannot exceed 20MB. |
| 400 | <a id="post-asset-part-error-upload-invalid-payload" />`upload/invalid-payload` | Upload payload is invalid. |
| 409 | <a id="post-asset-part-error-upload-not-open" />`upload/not-open` | Asset is not accepting additional parts |
| 409 | <a id="post-asset-part-error-upload-invalid-part-number" />`upload/invalid-part-number` | Multipart upload part number is invalid. |
| 409 | <a id="post-asset-part-error-upload-missing" />`upload/missing` | Multipart upload state is missing |
| 400 | <a id="post-asset-part-error-upload-size-mismatch" />`upload/size-mismatch` | The uploaded parts do not add up to the totalSizeInBytes declared when the multipart upload started. |
| 400 | <a id="post-asset-part-error-idempotency-invalid-key" />`idempotency/invalid-key` | The Idempotency-Key header must be 1 to 255 printable ASCII characters, such as a UUID. |
| 409 | <a id="post-asset-part-error-idempotency-key-reused" />`idempotency/key-reused` | This Idempotency-Key was already used for a different request. Use a new key for each distinct request. |
| 409 | <a id="post-asset-part-error-idempotency-request-in-progress" />`idempotency/request-in-progress` | A request with this Idempotency-Key is still being processed. Retry after the Retry-After delay to receive its result. |

***

### Get Assets Page

Get a paginated list of assets for a project

**Endpoint:** `GET https://api.calmlens.com/v1/projects/:projectId/assets/page`

**Authentication:** API Key

#### Example request

```bash
curl -X GET "https://api.calmlens.com/v1/projects/{projectId}/assets/page?pageIndex=0&pageSize=20" \
  -H "X-Api-Key: your-api-key"
```

#### Query parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `pageIndex` | `number` | Yes | The zero-based page index |
| `pageSize` | `number` | No | Number of items per page (1-100) |
| `ordering` | `array<object>` | No | Array of field ordering specifications |
| `filter` | `unknown \| null` | No |  |
| `folderId` | `string \| null` | No |  |

#### Response

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `pageIndex` | `number` | Yes | The zero-based page index |
| `pageSize` | `number` | Yes | Number of items per page (1-100) |
| `ordering` | `array<object>` | Yes | Array of field ordering specifications |
| `items` | `array<Asset>` | Yes | Items on this page |
| `total` | `number` | Yes | Total number of matching items |
| `hasMore` | `boolean` | Yes | Whether another page follows this one |

#### Error codes

| Status | Code | Description |
| --- | --- | --- |
| 401 | <a id="get-assets-page-error-auth-missing-token" />`auth/missing-token` | Authentication required. Send your project API key in the X-Api-Key header. |
| 401 | <a id="get-assets-page-error-auth-invalid-api-key" />`auth/invalid-api-key` | The API key is invalid, revoked or expired. Create a key under Developer → API Keys. |
| 403 | <a id="get-assets-page-error-auth-project-mismatch" />`auth/project-mismatch` | This API key belongs to a different project. Use a key created for the project that owns this resource. |
| 404 | <a id="get-assets-page-error-project-not-found" />`project/not-found` | Project not found |
| 404 | <a id="get-assets-page-error-folder-not-found" />`folder/not-found` | Folder not found |

***

### Search assets

Search accessible assets by keyword or describe an image in natural language. Semantic image search must be enabled in project settings. Results are ranked and limited to 20.

**Endpoint:** `GET https://api.calmlens.com/v1/projects/:projectId/assets/search`

**Authentication:** API Key

#### Example request

```bash
curl -X GET "https://api.calmlens.com/v1/projects/{projectId}/assets/search?query=a+red+car+at+night&mode=hybrid&limit=10" \
  -H "X-Api-Key: your-api-key"
```

#### Query parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `query` | `string` | Yes |  |
| `limit` | `integer` | Yes |  |
| `mode` | `keyword \| semantic \| hybrid` | Yes |  |

#### Response

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `query` | `string` | Yes |  |
| `items` | `array<object>` | Yes |  |
| `semanticStatus` | `disabled \| ready \| indexing \| unavailable` | Yes |  |

#### Error codes

| Status | Code | Description |
| --- | --- | --- |
| 401 | <a id="get-asset-search-error-auth-missing-token" />`auth/missing-token` | Authentication required. Send your project API key in the X-Api-Key header. |
| 401 | <a id="get-asset-search-error-auth-invalid-api-key" />`auth/invalid-api-key` | The API key is invalid, revoked or expired. Create a key under Developer → API Keys. |
| 403 | <a id="get-asset-search-error-auth-project-mismatch" />`auth/project-mismatch` | This API key belongs to a different project. Use a key created for the project that owns this resource. |

***

### Get Asset

Get a single asset by ID

**Endpoint:** `GET https://api.calmlens.com/v1/assets/:assetId`

**Authentication:** API Key

#### Example request

```bash
curl -X GET "https://api.calmlens.com/v1/assets/987fcdeb-51a2-43d7-b890-123456789abc" \
  -H "X-Api-Key: your-api-key"
```

#### Response

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | Yes | Unique asset ID |
| `projectId` | `string` | Yes | ID of the project that owns the asset |
| `sizeInBytes` | `number,null` | No | Size of the uploaded content in bytes |
| `userId` | `unknown \| null` | No | ID of the user whose account created the asset |
| `parentId` | `string \| null` | No | ID of the parent asset for child assets CalmLens extracts during processing, such as images in a document or OCR text. `null` for uploaded assets. |
| `folderId` | `string \| null` | No | Folder containing the asset |
| `createdAt` | `integer` | Yes | When the asset was created, in milliseconds since the Unix epoch |
| `updatedAt` | `integer` | No | When the asset was last updated, in milliseconds since the Unix epoch |
| `checksums` | `object \| null` | No | Digests of the uploaded content |
| `imageData` | `object \| null` | No | Image properties, for image assets |
| `documentData` | `object \| null` | No | Document properties, for documents |
| `name` | `unknown` | Yes | Name of the asset |
| `fileFormat` | `unknown \| null` | No | MIME type of the stored content |
| `fileExtension` | `unknown \| null` | No | File extension of the stored content |
| `description` | `unknown \| null` | No | Optional description of the asset |
| `externalId` | `unknown \| null` | No | External identifier for the asset |
| `externalUrl` | `string \| null` | No | External URL associated with the asset |
| `previewImage` | `object \| null` | No | Browser-friendly preview generated for images, documents and websites |
| `storageId` | `unknown \| null` | No | Internal storage key of the retained content; `null` when nothing is stored |
| `uploadPartsReceived` | `integer \| null` | No | Number of parts received so far for a multipart upload |
| `type` | `image \| video \| audio \| plaintext-document \| multimedia-document \| website \| file` | Yes | Asset type, detected from the content's MIME type |
| `metadata` | `array<MetadataItem> \| null` | No | List of custom metadata items for the asset |
| `tags` | `array<unknown> \| null` | No | Array of tags for the asset |
| `report` | `object \| null` | No | Moderation report, set once classification finishes. See [Report format](/docs/guides/moderation-reports). |
| `moderationAction` | `ignore \| alert \| hide \| purge \| null` | No | The moderation action applied when this asset crossed a policy threshold |
| `status` | `uploading \| pending \| approved \| rejected \| error` | Yes | Processing and moderation status. See [Asset lifecycle](/docs/guides/asset-lifecycle). |
| `safetyHold` | `object \| null` | No | Internal delivery restriction for suspected illegal content. Held content cannot be viewed, changed, or deleted through normal asset operations. |
| `rejectionNote` | `unknown \| null` | No | Reason the asset was rejected, generated automatically or supplied by a reviewer |
| `extractedText` | `unknown \| null` | No | Extracted text stored inline for small content or in R2 for larger content |
| `processingOptions` | `object \| null` | No | Processing options supplied when the asset was uploaded |
| `visibility` | `public \| private \| null` | No | Visibility level of the asset |
| `storageDeletedAt` | `number,null` | No | When the stored content was removed, in milliseconds since the Unix epoch. The asset record and report remain. |

#### Error codes

| Status | Code | Description |
| --- | --- | --- |
| 401 | <a id="get-asset-error-auth-missing-token" />`auth/missing-token` | Authentication required. Send your project API key in the X-Api-Key header. |
| 401 | <a id="get-asset-error-auth-invalid-api-key" />`auth/invalid-api-key` | The API key is invalid, revoked or expired. Create a key under Developer → API Keys. |
| 403 | <a id="get-asset-error-auth-project-mismatch" />`auth/project-mismatch` | This API key belongs to a different project. Use a key created for the project that owns this resource. |
| 404 | <a id="get-asset-error-asset-not-found" />`asset/not-found` | Asset not found |
| 404 | <a id="get-asset-error-project-not-found" />`project/not-found` | Project not found |

***

### Get Asset Children

Get child assets of a parent asset

**Endpoint:** `GET https://api.calmlens.com/v1/assets/:assetId/children`

**Authentication:** API Key

#### Example request

```bash
curl -X GET "https://api.calmlens.com/v1/assets/987fcdeb-51a2-43d7-b890-123456789abc/children?pageIndex=0&pageSize=20" \
  -H "X-Api-Key: your-api-key"
```

#### Query parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `pageIndex` | `number` | Yes | The zero-based page index |
| `pageSize` | `number` | No | Number of items per page (1-100) |
| `ordering` | `array<object>` | No | Array of field ordering specifications |

#### Response

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `pageIndex` | `number` | Yes | The zero-based page index |
| `pageSize` | `number` | Yes | Number of items per page (1-100) |
| `ordering` | `array<object>` | Yes | Array of field ordering specifications |
| `items` | `array<Asset>` | Yes | Items on this page |
| `total` | `number` | Yes | Total number of matching items |
| `hasMore` | `boolean` | Yes | Whether another page follows this one |

#### Error codes

| Status | Code | Description |
| --- | --- | --- |
| 401 | <a id="get-asset-children-error-auth-missing-token" />`auth/missing-token` | Authentication required. Send your project API key in the X-Api-Key header. |
| 401 | <a id="get-asset-children-error-auth-invalid-api-key" />`auth/invalid-api-key` | The API key is invalid, revoked or expired. Create a key under Developer → API Keys. |
| 403 | <a id="get-asset-children-error-auth-project-mismatch" />`auth/project-mismatch` | This API key belongs to a different project. Use a key created for the project that owns this resource. |

***

### Retry Asset

Process an asset that failed (status error) again. It returns to pending and is moderated like a new upload. Its content must still be stored.

**Endpoint:** `POST https://api.calmlens.com/v1/assets/:assetId/retry`

**Authentication:** API Key

#### Example request

```bash
curl -X POST "https://api.calmlens.com/v1/assets/{assetId}/retry" \
  -H "X-Api-Key: your-api-key"
```

#### Response

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | Yes | Unique asset ID |
| `projectId` | `string` | Yes | ID of the project that owns the asset |
| `sizeInBytes` | `number,null` | No | Size of the uploaded content in bytes |
| `userId` | `unknown \| null` | No | ID of the user whose account created the asset |
| `parentId` | `string \| null` | No | ID of the parent asset for child assets CalmLens extracts during processing, such as images in a document or OCR text. `null` for uploaded assets. |
| `folderId` | `string \| null` | No | Folder containing the asset |
| `createdAt` | `integer` | Yes | When the asset was created, in milliseconds since the Unix epoch |
| `updatedAt` | `integer` | No | When the asset was last updated, in milliseconds since the Unix epoch |
| `checksums` | `object \| null` | No | Digests of the uploaded content |
| `imageData` | `object \| null` | No | Image properties, for image assets |
| `documentData` | `object \| null` | No | Document properties, for documents |
| `name` | `unknown` | Yes | Name of the asset |
| `fileFormat` | `unknown \| null` | No | MIME type of the stored content |
| `fileExtension` | `unknown \| null` | No | File extension of the stored content |
| `description` | `unknown \| null` | No | Optional description of the asset |
| `externalId` | `unknown \| null` | No | External identifier for the asset |
| `externalUrl` | `string \| null` | No | External URL associated with the asset |
| `previewImage` | `object \| null` | No | Browser-friendly preview generated for images, documents and websites |
| `storageId` | `unknown \| null` | No | Internal storage key of the retained content; `null` when nothing is stored |
| `uploadPartsReceived` | `integer \| null` | No | Number of parts received so far for a multipart upload |
| `type` | `image \| video \| audio \| plaintext-document \| multimedia-document \| website \| file` | Yes | Asset type, detected from the content's MIME type |
| `metadata` | `array<MetadataItem> \| null` | No | List of custom metadata items for the asset |
| `tags` | `array<unknown> \| null` | No | Array of tags for the asset |
| `report` | `object \| null` | No | Moderation report, set once classification finishes. See [Report format](/docs/guides/moderation-reports). |
| `moderationAction` | `ignore \| alert \| hide \| purge \| null` | No | The moderation action applied when this asset crossed a policy threshold |
| `status` | `uploading \| pending \| approved \| rejected \| error` | Yes | Processing and moderation status. See [Asset lifecycle](/docs/guides/asset-lifecycle). |
| `safetyHold` | `object \| null` | No | Internal delivery restriction for suspected illegal content. Held content cannot be viewed, changed, or deleted through normal asset operations. |
| `rejectionNote` | `unknown \| null` | No | Reason the asset was rejected, generated automatically or supplied by a reviewer |
| `extractedText` | `unknown \| null` | No | Extracted text stored inline for small content or in R2 for larger content |
| `processingOptions` | `object \| null` | No | Processing options supplied when the asset was uploaded |
| `visibility` | `public \| private \| null` | No | Visibility level of the asset |
| `storageDeletedAt` | `number,null` | No | When the stored content was removed, in milliseconds since the Unix epoch. The asset record and report remain. |

#### Error codes

| Status | Code | Description |
| --- | --- | --- |
| 401 | <a id="retry-asset-error-auth-missing-token" />`auth/missing-token` | Authentication required. Send your project API key in the X-Api-Key header. |
| 401 | <a id="retry-asset-error-auth-invalid-api-key" />`auth/invalid-api-key` | The API key is invalid, revoked or expired. Create a key under Developer → API Keys. |
| 403 | <a id="retry-asset-error-auth-project-mismatch" />`auth/project-mismatch` | This API key belongs to a different project. Use a key created for the project that owns this resource. |
| 404 | <a id="retry-asset-error-asset-not-found" />`asset/not-found` | Asset not found |
| 409 | <a id="retry-asset-error-asset-not-retryable" />`asset/not-retryable` | Only assets that failed to process (status error) can be retried. |
| 409 | <a id="retry-asset-error-asset-content-unavailable" />`asset/content-unavailable` | This asset's content is no longer stored, so it can't be processed again. Upload it again instead. |
| 423 | <a id="retry-asset-error-asset-safety-held" />`asset/safety-held` | Asset access is restricted by a safety hold. |

***

### Update Asset

Update an existing asset

**Endpoint:** `PATCH https://api.calmlens.com/v1/assets/:assetId`

**Authentication:** API Key

#### Example request

```bash
curl -X PATCH "https://api.calmlens.com/v1/assets/987fcdeb-51a2-43d7-b890-123456789abc" \
  -H "X-Api-Key: your-api-key" \
  -H "Content-Type: application/json" \
  --data '{
  "name": "Updated asset name",
  "visibility": "private"
}'
```

#### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `unknown` | No | Name of the asset |
| `description` | `unknown \| null` | No | Optional description of the asset |
| `externalId` | `unknown \| null` | No | External identifier for the asset |
| `externalUrl` | `string \| null` | No | External URL associated with the asset |
| `metadata` | `array<MetadataItem> \| null` | No | List of custom metadata items for the asset |
| `tags` | `array<unknown> \| null` | No | Array of tags for the asset |
| `visibility` | `public \| private \| null` | No | Visibility level of the asset |

#### Response

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | Yes | Unique asset ID |
| `projectId` | `string` | Yes | ID of the project that owns the asset |
| `sizeInBytes` | `number,null` | No | Size of the uploaded content in bytes |
| `userId` | `unknown \| null` | No | ID of the user whose account created the asset |
| `parentId` | `string \| null` | No | ID of the parent asset for child assets CalmLens extracts during processing, such as images in a document or OCR text. `null` for uploaded assets. |
| `folderId` | `string \| null` | No | Folder containing the asset |
| `createdAt` | `integer` | Yes | When the asset was created, in milliseconds since the Unix epoch |
| `updatedAt` | `integer` | No | When the asset was last updated, in milliseconds since the Unix epoch |
| `checksums` | `object \| null` | No | Digests of the uploaded content |
| `imageData` | `object \| null` | No | Image properties, for image assets |
| `documentData` | `object \| null` | No | Document properties, for documents |
| `name` | `unknown` | Yes | Name of the asset |
| `fileFormat` | `unknown \| null` | No | MIME type of the stored content |
| `fileExtension` | `unknown \| null` | No | File extension of the stored content |
| `description` | `unknown \| null` | No | Optional description of the asset |
| `externalId` | `unknown \| null` | No | External identifier for the asset |
| `externalUrl` | `string \| null` | No | External URL associated with the asset |
| `previewImage` | `object \| null` | No | Browser-friendly preview generated for images, documents and websites |
| `storageId` | `unknown \| null` | No | Internal storage key of the retained content; `null` when nothing is stored |
| `uploadPartsReceived` | `integer \| null` | No | Number of parts received so far for a multipart upload |
| `type` | `image \| video \| audio \| plaintext-document \| multimedia-document \| website \| file` | Yes | Asset type, detected from the content's MIME type |
| `metadata` | `array<MetadataItem> \| null` | No | List of custom metadata items for the asset |
| `tags` | `array<unknown> \| null` | No | Array of tags for the asset |
| `report` | `object \| null` | No | Moderation report, set once classification finishes. See [Report format](/docs/guides/moderation-reports). |
| `moderationAction` | `ignore \| alert \| hide \| purge \| null` | No | The moderation action applied when this asset crossed a policy threshold |
| `status` | `uploading \| pending \| approved \| rejected \| error` | Yes | Processing and moderation status. See [Asset lifecycle](/docs/guides/asset-lifecycle). |
| `safetyHold` | `object \| null` | No | Internal delivery restriction for suspected illegal content. Held content cannot be viewed, changed, or deleted through normal asset operations. |
| `rejectionNote` | `unknown \| null` | No | Reason the asset was rejected, generated automatically or supplied by a reviewer |
| `extractedText` | `unknown \| null` | No | Extracted text stored inline for small content or in R2 for larger content |
| `processingOptions` | `object \| null` | No | Processing options supplied when the asset was uploaded |
| `visibility` | `public \| private \| null` | No | Visibility level of the asset |
| `storageDeletedAt` | `number,null` | No | When the stored content was removed, in milliseconds since the Unix epoch. The asset record and report remain. |

#### Error codes

| Status | Code | Description |
| --- | --- | --- |
| 401 | <a id="patch-asset-error-auth-missing-token" />`auth/missing-token` | Authentication required. Send your project API key in the X-Api-Key header. |
| 401 | <a id="patch-asset-error-auth-invalid-api-key" />`auth/invalid-api-key` | The API key is invalid, revoked or expired. Create a key under Developer → API Keys. |
| 403 | <a id="patch-asset-error-auth-project-mismatch" />`auth/project-mismatch` | This API key belongs to a different project. Use a key created for the project that owns this resource. |
| 404 | <a id="patch-asset-error-asset-not-found" />`asset/not-found` | Asset not found |
| 500 | <a id="patch-asset-error-asset-update-failed" />`asset/update-failed` | Failed to update asset |
| 409 | <a id="patch-asset-error-asset-duplicate-external-id" />`asset/duplicate-external-id` | An asset with this externalId already exists in the project. |

***

### Move Asset

Move an asset into a folder, or to the project root

**Endpoint:** `POST https://api.calmlens.com/v1/assets/:assetId/move`

**Authentication:** API Key

#### Example request

```bash
curl -X POST "https://api.calmlens.com/v1/assets/{assetId}/move" \
  -H "X-Api-Key: your-api-key"
```

#### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `folderId` | `string \| null` | Yes |  |

#### Response

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | Yes | Unique asset ID |
| `projectId` | `string` | Yes | ID of the project that owns the asset |
| `sizeInBytes` | `number,null` | No | Size of the uploaded content in bytes |
| `userId` | `unknown \| null` | No | ID of the user whose account created the asset |
| `parentId` | `string \| null` | No | ID of the parent asset for child assets CalmLens extracts during processing, such as images in a document or OCR text. `null` for uploaded assets. |
| `folderId` | `string \| null` | No | Folder containing the asset |
| `createdAt` | `integer` | Yes | When the asset was created, in milliseconds since the Unix epoch |
| `updatedAt` | `integer` | No | When the asset was last updated, in milliseconds since the Unix epoch |
| `checksums` | `object \| null` | No | Digests of the uploaded content |
| `imageData` | `object \| null` | No | Image properties, for image assets |
| `documentData` | `object \| null` | No | Document properties, for documents |
| `name` | `unknown` | Yes | Name of the asset |
| `fileFormat` | `unknown \| null` | No | MIME type of the stored content |
| `fileExtension` | `unknown \| null` | No | File extension of the stored content |
| `description` | `unknown \| null` | No | Optional description of the asset |
| `externalId` | `unknown \| null` | No | External identifier for the asset |
| `externalUrl` | `string \| null` | No | External URL associated with the asset |
| `previewImage` | `object \| null` | No | Browser-friendly preview generated for images, documents and websites |
| `storageId` | `unknown \| null` | No | Internal storage key of the retained content; `null` when nothing is stored |
| `uploadPartsReceived` | `integer \| null` | No | Number of parts received so far for a multipart upload |
| `type` | `image \| video \| audio \| plaintext-document \| multimedia-document \| website \| file` | Yes | Asset type, detected from the content's MIME type |
| `metadata` | `array<MetadataItem> \| null` | No | List of custom metadata items for the asset |
| `tags` | `array<unknown> \| null` | No | Array of tags for the asset |
| `report` | `object \| null` | No | Moderation report, set once classification finishes. See [Report format](/docs/guides/moderation-reports). |
| `moderationAction` | `ignore \| alert \| hide \| purge \| null` | No | The moderation action applied when this asset crossed a policy threshold |
| `status` | `uploading \| pending \| approved \| rejected \| error` | Yes | Processing and moderation status. See [Asset lifecycle](/docs/guides/asset-lifecycle). |
| `safetyHold` | `object \| null` | No | Internal delivery restriction for suspected illegal content. Held content cannot be viewed, changed, or deleted through normal asset operations. |
| `rejectionNote` | `unknown \| null` | No | Reason the asset was rejected, generated automatically or supplied by a reviewer |
| `extractedText` | `unknown \| null` | No | Extracted text stored inline for small content or in R2 for larger content |
| `processingOptions` | `object \| null` | No | Processing options supplied when the asset was uploaded |
| `visibility` | `public \| private \| null` | No | Visibility level of the asset |
| `storageDeletedAt` | `number,null` | No | When the stored content was removed, in milliseconds since the Unix epoch. The asset record and report remain. |

#### Error codes

| Status | Code | Description |
| --- | --- | --- |
| 401 | <a id="move-asset-error-auth-missing-token" />`auth/missing-token` | Authentication required. Send your project API key in the X-Api-Key header. |
| 401 | <a id="move-asset-error-auth-invalid-api-key" />`auth/invalid-api-key` | The API key is invalid, revoked or expired. Create a key under Developer → API Keys. |
| 403 | <a id="move-asset-error-auth-project-mismatch" />`auth/project-mismatch` | This API key belongs to a different project. Use a key created for the project that owns this resource. |
| 404 | <a id="move-asset-error-asset-not-found" />`asset/not-found` | Asset not found |
| 500 | <a id="move-asset-error-asset-update-failed" />`asset/update-failed` | Failed to update asset |
| 423 | <a id="move-asset-error-asset-safety-held" />`asset/safety-held` | Asset access is restricted by a safety hold. |
| 404 | <a id="move-asset-error-folder-not-found" />`folder/not-found` | Folder not found |

***

### Remove Asset Content

Remove stored content, including the content derived from it in child assets, while retaining the asset records

**Endpoint:** `DELETE https://api.calmlens.com/v1/assets/:assetId/content`

**Authentication:** API Key

#### Example request

```bash
curl -X DELETE "https://api.calmlens.com/v1/assets/987fcdeb-51a2-43d7-b890-123456789abc/content" \
  -H "X-Api-Key: your-api-key"
```

#### Response

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | Yes | Unique asset ID |
| `projectId` | `string` | Yes | ID of the project that owns the asset |
| `sizeInBytes` | `number,null` | No | Size of the uploaded content in bytes |
| `userId` | `unknown \| null` | No | ID of the user whose account created the asset |
| `parentId` | `string \| null` | No | ID of the parent asset for child assets CalmLens extracts during processing, such as images in a document or OCR text. `null` for uploaded assets. |
| `folderId` | `string \| null` | No | Folder containing the asset |
| `createdAt` | `integer` | Yes | When the asset was created, in milliseconds since the Unix epoch |
| `updatedAt` | `integer` | No | When the asset was last updated, in milliseconds since the Unix epoch |
| `checksums` | `object \| null` | No | Digests of the uploaded content |
| `imageData` | `object \| null` | No | Image properties, for image assets |
| `documentData` | `object \| null` | No | Document properties, for documents |
| `name` | `unknown` | Yes | Name of the asset |
| `fileFormat` | `unknown \| null` | No | MIME type of the stored content |
| `fileExtension` | `unknown \| null` | No | File extension of the stored content |
| `description` | `unknown \| null` | No | Optional description of the asset |
| `externalId` | `unknown \| null` | No | External identifier for the asset |
| `externalUrl` | `string \| null` | No | External URL associated with the asset |
| `previewImage` | `object \| null` | No | Browser-friendly preview generated for images, documents and websites |
| `storageId` | `unknown \| null` | No | Internal storage key of the retained content; `null` when nothing is stored |
| `uploadPartsReceived` | `integer \| null` | No | Number of parts received so far for a multipart upload |
| `type` | `image \| video \| audio \| plaintext-document \| multimedia-document \| website \| file` | Yes | Asset type, detected from the content's MIME type |
| `metadata` | `array<MetadataItem> \| null` | No | List of custom metadata items for the asset |
| `tags` | `array<unknown> \| null` | No | Array of tags for the asset |
| `report` | `object \| null` | No | Moderation report, set once classification finishes. See [Report format](/docs/guides/moderation-reports). |
| `moderationAction` | `ignore \| alert \| hide \| purge \| null` | No | The moderation action applied when this asset crossed a policy threshold |
| `status` | `uploading \| pending \| approved \| rejected \| error` | Yes | Processing and moderation status. See [Asset lifecycle](/docs/guides/asset-lifecycle). |
| `safetyHold` | `object \| null` | No | Internal delivery restriction for suspected illegal content. Held content cannot be viewed, changed, or deleted through normal asset operations. |
| `rejectionNote` | `unknown \| null` | No | Reason the asset was rejected, generated automatically or supplied by a reviewer |
| `extractedText` | `unknown \| null` | No | Extracted text stored inline for small content or in R2 for larger content |
| `processingOptions` | `object \| null` | No | Processing options supplied when the asset was uploaded |
| `visibility` | `public \| private \| null` | No | Visibility level of the asset |
| `storageDeletedAt` | `number,null` | No | When the stored content was removed, in milliseconds since the Unix epoch. The asset record and report remain. |

#### Error codes

| Status | Code | Description |
| --- | --- | --- |
| 401 | <a id="delete-asset-content-error-auth-missing-token" />`auth/missing-token` | Authentication required. Send your project API key in the X-Api-Key header. |
| 401 | <a id="delete-asset-content-error-auth-invalid-api-key" />`auth/invalid-api-key` | The API key is invalid, revoked or expired. Create a key under Developer → API Keys. |
| 403 | <a id="delete-asset-content-error-auth-project-mismatch" />`auth/project-mismatch` | This API key belongs to a different project. Use a key created for the project that owns this resource. |
| 404 | <a id="delete-asset-content-error-asset-not-found" />`asset/not-found` | Asset not found |
| 500 | <a id="delete-asset-content-error-asset-update-failed" />`asset/update-failed` | Failed to update asset |

***

### Delete Asset

Delete an asset, its child assets and their stored content from a project

**Endpoint:** `DELETE https://api.calmlens.com/v1/assets/:assetId`

**Authentication:** API Key

#### Example request

```bash
curl -X DELETE "https://api.calmlens.com/v1/assets/987fcdeb-51a2-43d7-b890-123456789abc" \
  -H "X-Api-Key: your-api-key"
```

#### Response

```json
{
  "type": "object",
  "properties": {},
  "additionalProperties": false
}
```

#### Error codes

| Status | Code | Description |
| --- | --- | --- |
| 401 | <a id="delete-asset-error-auth-missing-token" />`auth/missing-token` | Authentication required. Send your project API key in the X-Api-Key header. |
| 401 | <a id="delete-asset-error-auth-invalid-api-key" />`auth/invalid-api-key` | The API key is invalid, revoked or expired. Create a key under Developer → API Keys. |
| 403 | <a id="delete-asset-error-auth-project-mismatch" />`auth/project-mismatch` | This API key belongs to a different project. Use a key created for the project that owns this resource. |
| 404 | <a id="delete-asset-error-asset-not-found" />`asset/not-found` | Asset not found |

***

### Run Asset Batch Operation

Queue a bounded batch operation for selected assets

**Endpoint:** `POST https://api.calmlens.com/v1/projects/:projectId/assets/batch`

**Authentication:** API Key

#### Example request

```bash
curl -X POST "https://api.calmlens.com/v1/projects/{projectId}/assets/batch" \
  -H "X-Api-Key: your-api-key" \
  -H "Content-Type: application/json" \
  --data '{
  "assetIds": [
    "987fcdeb-51a2-43d7-b890-123456789abc"
  ],
  "operation": "remove-content"
}'
```

#### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `assetIds` | `array<string>` | Yes |  |
| `operation` | `delete \| remove-content` | Yes |  |

#### Response

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `queued` | `true` | Yes |  |

#### Error codes

| Status | Code | Description |
| --- | --- | --- |
| 401 | <a id="post-asset-batch-operation-error-auth-missing-token" />`auth/missing-token` | Authentication required. Send your project API key in the X-Api-Key header. |
| 401 | <a id="post-asset-batch-operation-error-auth-invalid-api-key" />`auth/invalid-api-key` | The API key is invalid, revoked or expired. Create a key under Developer → API Keys. |
| 403 | <a id="post-asset-batch-operation-error-auth-project-mismatch" />`auth/project-mismatch` | This API key belongs to a different project. Use a key created for the project that owns this resource. |
| 404 | <a id="post-asset-batch-operation-error-project-not-found" />`project/not-found` | Project not found |

## The asset object

Every endpoint that returns an asset returns this object. `report` follows the [Report format](/docs/guides/moderation-reports), and `status` moves through the [asset lifecycle](/docs/guides/asset-lifecycle). Child assets that CalmLens extracts during processing, such as images in a document, are assets too: their `parentId` is the asset they came from, and [Get Asset Children](#get-asset-children) lists them.

| Field | Type | Presence | Description |
| --- | --- | --- | --- |
| `id` | `string` | Required | Unique asset ID |
| `projectId` | `string` | Required | ID of the project that owns the asset |
| `sizeInBytes` | `number` | Optional, nullable | Size of the uploaded content in bytes |
| `userId` | `string` | Optional, nullable | ID of the user whose account created the asset |
| `parentId` | `string` | Optional, nullable | ID of the parent asset for child assets CalmLens extracts during processing, such as images in a document or OCR text. `null` for uploaded assets. |
| `folderId` | `string` | Optional, nullable | Folder containing the asset |
| `createdAt` | `integer` | Required | When the asset was created, in milliseconds since the Unix epoch |
| `updatedAt` | `integer` | Optional | When the asset was last updated, in milliseconds since the Unix epoch |
| `checksums` | `object` | Optional, nullable | Digests of the uploaded content |
| `checksums.md5` | `string` | Optional, nullable | MD5 digest of the uploaded content |
| `checksums.sha1` | `string` | Optional, nullable | SHA-1 digest of the uploaded content |
| `checksums.sha256` | `string` | Optional, nullable | SHA-256 digest of the uploaded content |
| `checksums.sha384` | `string` | Optional, nullable | SHA-384 digest of the uploaded content |
| `checksums.sha512` | `string` | Optional, nullable | SHA-512 digest of the uploaded content |
| `imageData` | `object` | Optional, nullable | Image properties, for image assets |
| `imageData.perceptualHash` | `string` | Optional, nullable | Perceptual hash used to match visually similar images |
| `imageData.width` | `integer` | Optional, nullable | Image width in pixels |
| `imageData.height` | `integer` | Optional, nullable | Image height in pixels |
| `documentData` | `object` | Optional, nullable | Document properties, for documents |
| `name` | `string` | Required | Name of the asset |
| `fileFormat` | `string` | Optional, nullable | MIME type of the stored content |
| `fileExtension` | `string` | Optional, nullable | File extension of the stored content |
| `description` | `string` | Optional, nullable | Optional description of the asset |
| `externalId` | `string` | Optional, nullable | External identifier for the asset |
| `externalUrl` | `string` | Optional, nullable | External URL associated with the asset |
| `previewImage` | `object` | Optional, nullable | Browser-friendly preview generated for images, documents and websites |
| `previewImage.id` | `string` | Nullable | Identifier of the stored preview image |
| `previewImage.fileFormat` | `string` | Nullable | File format of the preview, such as `webp` |
| `previewImage.thumbhash` | `string` | Nullable | ThumbHash placeholder you can render while the preview loads |
| `previewImage.averageColor` | `string` | Nullable | Average color of the preview |
| `previewImage.width` | `integer` | Nullable | Preview width in pixels |
| `previewImage.height` | `integer` | Nullable | Preview height in pixels |
| `storageId` | `string` | Optional, nullable | Internal storage key of the retained content; `null` when nothing is stored |
| `uploadPartsReceived` | `integer` | Optional, nullable | Number of parts received so far for a multipart upload |
| `type` | `"image" \| "video" \| "audio" \| "plaintext-document" \| "multimedia-document" \| "website" \| "file"` | Required | Asset type, detected from the content's MIME type |
| `metadata` | `MetadataItem[]` | Optional, nullable | List of custom metadata items for the asset |
| `metadata[].id` | `string` | Required | Identifier for the metadata item |
| `metadata[].value` | `JSON` | Required | Value of the metadata item |
| `tags` | `string[]` | Optional, nullable | Array of tags for the asset |
| `report` | `object` | Optional, nullable | Moderation report, set once classification finishes. See [Report format](/docs/guides/moderation-reports). |
| `moderationAction` | `"ignore" \| "alert" \| "hide" \| "purge"` | Optional, nullable | The moderation action applied when this asset crossed a policy threshold |
| `status` | `"uploading" \| "pending" \| "approved" \| "rejected" \| "error"` | Required | Processing and moderation status. See [Asset lifecycle](/docs/guides/asset-lifecycle). |
| `safetyHold` | `object` | Optional, nullable | Internal delivery restriction for suspected illegal content. Held content cannot be viewed, changed, or deleted through normal asset operations. |
| `safetyHold.placedAt` | `number` | Required | When the hold was placed, in milliseconds since the Unix epoch |
| `safetyHold.reason` | `"suspected-illegal-content"` | Required | Why the asset is held |
| `safetyHold.disposedAt` | `number` | Optional, nullable | When the held content was disposed of, in milliseconds since the Unix epoch |
| `rejectionNote` | `string` | Optional, nullable | Reason the asset was rejected, generated automatically or supplied by a reviewer |
| `extractedText` | `object` | Optional, nullable | Extracted text stored inline for small content or in R2 for larger content |
| `processingOptions` | `object` | Optional, nullable | Processing options supplied when the asset was uploaded |
| `processingOptions.keepContentAfterProcessing` | `boolean` | Optional, nullable | Whether to keep the asset content after processing. Default: `false`. |
| `processingOptions.flaggedContentPolicy` | `object` | Optional, nullable | Override the project moderation action for this asset. Ignore takes no action, alert keeps delivery enabled, hide blocks delivery while retaining content, and purge deletes content. |
| `processingOptions.flaggedContentPolicy.action` | `"ignore" \| "alert" \| "hide" \| "purge"` | Required |  |
| `processingOptions.image` | `object` | Optional, nullable | Options for image assets |
| `processingOptions.image.ocrEnabled` | `boolean` | Optional, nullable | Whether to extract image text into a child asset and run normal text moderation. Applies to images, including those extracted from multimedia documents and websites. Default: `false`. |
| `visibility` | `"public" \| "private"` | Optional, nullable | Visibility level of the asset. Default: `private`. |
| `storageDeletedAt` | `number` | Optional, nullable | When the stored content was removed, in milliseconds since the Unix epoch. The asset record and report remain. |

### The page object

List endpoints return a page of assets.

| Field | Type | Presence | Description |
| --- | --- | --- | --- |
| `pageIndex` | `number` | Required | The zero-based page index. Default: `0`. |
| `pageSize` | `number` | Required | Number of items per page (1-100) |
| `ordering` | `object[]` | Required | Array of field ordering specifications |
| `items` | `Asset[]` | Required | Items on this page |
| `total` | `number` | Required | Total number of matching items |
| `hasMore` | `boolean` | Required | Whether another page follows this one |
