# Configuration API reference

Use a project API key to read and change the project's upload presets, settings and metadata fields. The same settings are in the portal, and a change made in either place shows in the other. The [policies](/docs/api/policies), [lenses](/docs/api/lenses) and [hash lists](/docs/api/hash-lists) have their own endpoints.

Guide: [Manage configuration in code](/docs/guides/configuration-as-code).

## Revisions

Every object here has a `revision`. Send back the one you read, and a change made since is refused with `409`, so concurrent deploys can't silently overwrite each other. A request that changes nothing keeps the revision, so you can send the same definition on every deploy.

- Each upload preset has its own revision. Send `revision: 0` to create a preset only if no preset has its key. A conflict is [`409 upload-preset/revision-conflict`](/docs/api/configuration#ensure-upload-presets-error-upload-preset-revision-conflict).
- A project's settings and metadata fields share one revision. A conflict is [`409 project-settings/revision-conflict`](/docs/api/configuration#update-project-settings-error-project-settings-revision-conflict).

Leave `revision` out to write whatever is stored.

## Permissions

Reading needs `upload-presets:read`, `settings:read` or `metadata:read`, and changing needs the matching `:write` permission. The [Configurator role](/docs/guides/authentication#roles) holds all of them, without access to members or API keys. Naming a policy on an upload preset also needs `policies:write`.

## Endpoints

| Endpoint                                                                                                | Description                                                                                            |
| ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| [`GET /projects/:projectId/upload-presets`](/docs/api/configuration#list-upload-presets)                | List a page of the project's upload presets, oldest first.                                             |
| [`PUT /projects/:projectId/upload-presets`](/docs/api/configuration#ensure-upload-presets)              | Create each upload preset, or replace it with the definition sent, by key.                             |
| [`DELETE /upload-presets/:uploadPresetId`](/docs/api/configuration#delete-upload-preset)                | Delete an upload preset.                                                                               |
| [`GET /projects/:projectId/settings`](/docs/api/configuration#get-project-settings)                     | Get the project's settings, with every default filled in, and the revision to send back with a change. |
| [`PUT /projects/:projectId/settings`](/docs/api/configuration#update-project-settings)                  | Replace the project's settings.                                                                        |
| [`GET /projects/:projectId/metadata-fields`](/docs/api/configuration#list-metadata-fields)              | List the project's metadata fields in display order, with the revision to send back with a change.     |
| [`PUT /projects/:projectId/metadata-fields`](/docs/api/configuration#ensure-metadata-fields)            | Create each metadata field, or replace it with the definition sent, by ID.                             |
| [`DELETE /projects/:projectId/metadata-fields/:fieldId`](/docs/api/configuration#delete-metadata-field) | Delete a metadata field definition.                                                                    |

## The upload preset object

| Field                                          | Type                    | Presence           | Description                                                                                                                                                                                                                                                                         |
| ---------------------------------------------- | ----------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                                           | `string`                | Required           | Unique upload preset ID                                                                                                                                                                                                                                                             |
| `projectId`                                    | `string`                | Required           | ID of the project that owns the preset                                                                                                                                                                                                                                              |
| `createdByUserId`                              | `string`                | Optional, nullable | ID of the user who created the preset                                                                                                                                                                                                                                               |
| `key`                                          | `string`                | Required           | Key you choose for the preset, unique in its project: lowercase letters, numbers and hyphens, at most 64 characters. Configuration files and the ensure endpoint address presets by key.                                                                                            |
| `revision`                                     | `integer`               | Required           | Counts changes to the preset, starting at 1. Send it back when you change the preset, so a change made since you read it isn't overwritten.                                                                                                                                         |
| `name`                                         | `string`                | Required           | Display name                                                                                                                                                                                                                                                                        |
| `access`                                       | `"token" \| "anyone"`   | Required           | Who can upload from a browser: `token` needs an upload token signed by your server, `anyone` accepts the preset ID alone                                                                                                                                                            |
| `enabled`                                      | `boolean`               | Required           | Whether the preset accepts uploads                                                                                                                                                                                                                                                  |
| `allowedContentTypes`                          | `string[]`              | Optional, nullable | MIME types the preset accepts, such as `image/*`. Empty or `null` accepts any supported type.                                                                                                                                                                                       |
| `maxSizeInBytes`                               | `integer`               | Optional, nullable | Largest upload the preset accepts, in bytes. `null` uses the standard upload limits.                                                                                                                                                                                                |
| `folderId`                                     | `string`                | Optional, nullable | Folder that uploads go into                                                                                                                                                                                                                                                         |
| `policyId`                                     | `string`                | Optional, nullable | Moderation policy for uploads through the preset, one of the organisation's policies. `null` uses the project's policy.                                                                                                                                                             |
| `visibility`                                   | `"public" \| "private"` | Required           | Visibility of uploaded assets                                                                                                                                                                                                                                                       |
| `processingOptions`                            | `object`                | Optional, nullable | Processing options for every upload through the preset, such as keeping content after moderation or reading text with OCR. Browser uploads can't change them.                                                                                                                       |
| `processingOptions.keepContentAfterProcessing` | `boolean`               | Optional, nullable | Whether to keep the asset content after processing. Default: `false`.                                                                                                                                                                                                               |
| `processingOptions.storeSensitiveMetadata`     | `boolean`               | Optional, nullable | Override the project's `storeSensitiveMetadata` setting for this upload. When false, embedded image metadata (EXIF, XMP and IPTC) is removed from the file before it is stored, and `fileMetadata` leaves out the creator and location. When true, the image is stored as uploaded. |
| `processingOptions.features`                   | `ProcessingFeatures`    | Optional, nullable | Optional analyses and delivery features for this asset, keyed by feature. Link detection runs unless turned off; streaming follows the project's setting unless set here; the others run only when turned on. Unknown features are rejected.                                        |
| `revealDecision`                               | `boolean`               | Required           | Whether browser uploads see the moderation decision for their own upload                                                                                                                                                                                                            |
| `dailyUploadLimit`                             | `integer`               | Optional, nullable | Most uploads the preset accepts from each client (IP address) per UTC day, counted separately for every client. Required when access is `anyone`.                                                                                                                                   |
| `allowedOrigins`                               | `string[]`              | Optional, nullable | Sites allowed to upload from the browser. Empty or `null` allows any site.                                                                                                                                                                                                          |
| `createdAt`                                    | `integer`               | Required           | When the preset was created, in milliseconds since the Unix epoch                                                                                                                                                                                                                   |
| `updatedAt`                                    | `integer`               | Optional           | When the preset was last updated, in milliseconds since the Unix epoch                                                                                                                                                                                                              |

## The project settings object

| Field                                  | Type      | Presence           | Description                                                                                                                                                                                                        |
| -------------------------------------- | --------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `projectId`                            | `string`  | Required           | ID of the project                                                                                                                                                                                                  |
| `revision`                             | `integer` | Required           | Counts changes to the project's settings and metadata fields, starting at 1. Send it back with a change, so a change made since you read it isn't overwritten.                                                     |
| `settings`                             | `object`  | Required           |                                                                                                                                                                                                                    |
| `settings.optOutJurisdictions`         | `"EU"[]`  | Optional           | Optional standards this project doesn't serve, such as `EU`. Jurisdictions with legal rules always apply and can't be opted out. Defaults to none. Default: `[]`.                                                  |
| `settings.blockUnmoderatedAssets`      | `boolean` | Optional           | When true, the CDN refuses to serve assets whose moderation is still pending. Defaults to true. Default: `true`.                                                                                                   |
| `settings.storeSensitiveMetadata`      | `boolean` | Optional           | When true, uploaded images keep their embedded metadata (EXIF, XMP and IPTC). Defaults to false. An upload's `processingOptions.storeSensitiveMetadata` overrides it. Default: `false`.                            |
| `settings.streamingEnabled`            | `boolean` | Optional           | Preview. Prepare an adaptive stream (HLS) of every audio and video upload that keeps its content. Billed per started minute of media. Defaults to false. Default: `false`.                                         |
| `settings.aiMediaSearchEnabled`        | `boolean` | Optional           | Index audio speech and video frames and transcripts for AI search. Billed per indexed media unit. Defaults to false; images are always searchable. Default: `false`.                                               |
| `settings.remoteUrlRule`               | `object`  | Optional, nullable | Restricts the domains this project accepts when content is submitted by URL, including websites. `null` accepts every public domain. Default: `null`.                                                              |
| `settings.websiteSubmissionsPerMinute` | `integer` | Optional, nullable | Lowers how many websites this project can submit per minute. `null` uses the platform limit of 60. Default: `null`.                                                                                                |
| `settings.humanReview`                 | `object`  | Optional           | Which automated decisions are sampled into the human review queue. Defaults to none. Default: `{"allowedSamplePercent":0,"restrictedSamplePercent":0,"uncertaintyMarginPercent":0,"reviewMissingEvidence":false}`. |

## The metadata fields object

| Field                  | Type                                                                     | Presence | Description                                                                                                                                                    |
| ---------------------- | ------------------------------------------------------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `projectId`            | `string`                                                                 | Required | ID of the project                                                                                                                                              |
| `revision`             | `integer`                                                                | Required | Counts changes to the project's settings and metadata fields, starting at 1. Send it back with a change, so a change made since you read it isn't overwritten. |
| `fields`               | `MetadataField[]`                                                        | Required | The project's metadata fields, in display order                                                                                                                |
| `fields[].id`          | `string`                                                                 | Required |                                                                                                                                                                |
| `fields[].title`       | `string`                                                                 | Required |                                                                                                                                                                |
| `fields[].description` | `string`                                                                 | Optional |                                                                                                                                                                |
| `fields[].type`        | `"string" \| "number" \| "boolean" \| "date" \| "url"`                   | Required |                                                                                                                                                                |
| `fields[].icon`        | `"tag" \| "hash" \| "user" \| "folder" \| "layers" \| "photo" \| "link"` | Required |                                                                                                                                                                |
| `fields[].color`       | `string`                                                                 | Required |                                                                                                                                                                |

## Endpoint details

### List Upload Presets

List a page of the project's upload presets, oldest first.

**Endpoint:** `GET https://api.calmlens.com/v1/projects/:projectId/upload-presets`

**Authentication:** API Key

#### Example request

```bash
curl -X GET \
  'https://api.calmlens.com/v1/projects/proj_8fK2mQx7Lp4NvB3tRz9Y/upload-presets?sort=createdAt' \
  -H 'X-Api-Key: your-api-key'
```

#### Query parameters

| Field      | Type      | Required | Description                                                                                                                                                                                            |
| ---------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `cursor`   | `string`  | No       | The previous page’s nextCursor; omit for the first page                                                                                                                                                |
| `pageSize` | `integer` | No       | Items per page, 1 to 100 (default 50)                                                                                                                                                                  |
| `sort`     | `string`  | No       | Sort order: a comma-separated list of fields from `createdAt`, `id`, each ascending unless prefixed with `-`. The default is `createdAt`. An `id` tie-breaker in the first field's direction is added. |

#### Response

| Field        | Type                  | Required | Description                                                      |
| ------------ | --------------------- | -------- | ---------------------------------------------------------------- |
| `items`      | `array<UploadPreset>` | Yes      | The items on this page                                           |
| `nextCursor` | `string,null`         | Yes      | Pass as cursor for the next page; null when no more items remain |
| `hasMore`    | `boolean`             | Yes      | Whether another page follows this one                            |

#### Error codes

| Status | Code                                                                                        | Description                                                                                                                                                                                                                   |
| ------ | ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 401    | <a id="list-upload-presets-error-api-key-missing" />`api-key/missing`                       | Authentication required. Send your project API key in the X-Api-Key header.                                                                                                                                                   |
| 401    | <a id="list-upload-presets-error-api-key-invalid" />`api-key/invalid`                       | The API key is invalid, revoked or expired. Create a key under Developer → API Keys.                                                                                                                                          |
| 403    | <a id="list-upload-presets-error-api-key-project-mismatch" />`api-key/project-mismatch`     | This API key belongs to a different project. Use a key created for the project that owns this resource.                                                                                                                       |
| 403    | <a id="list-upload-presets-error-auth-permission-denied" />`auth/permission-denied`         | This API key lacks a permission the request needs (details.permission names it, such as assets:download for an asset's folder, and details.role the least role that includes it), or the resource belongs to another project. |
| 400    | <a id="list-upload-presets-error-validation-invalid-request" />`validation/invalid-request` | The request's parameters, query or body are invalid. details.issues lists each problem, with its path and issue code.                                                                                                         |
| 404    | <a id="list-upload-presets-error-project-not-found" />`project/not-found`                   | Project not found                                                                                                                                                                                                             |
| 400    | <a id="list-upload-presets-error-cursor-invalid" />`cursor/invalid`                         | The cursor is invalid or belongs to a different query.                                                                                                                                                                        |
| 500    | <a id="list-upload-presets-error-server-internal-error" />`server/internal-error`           | An internal error occurred. Retry later, and quote requestId to support.                                                                                                                                                      |

***

### Ensure Upload Presets

Create each upload preset, or replace it with the definition sent, by key. Fields left out take their defaults. An unchanged preset is returned as it is, with the same revision, so deploys can send their presets every time. Every preset is checked before any is saved.

**Endpoint:** `PUT https://api.calmlens.com/v1/projects/:projectId/upload-presets`

**Authentication:** API Key

#### Example request

```bash
curl -X PUT \
  'https://api.calmlens.com/v1/projects/proj_8fK2mQx7Lp4NvB3tRz9Y/upload-presets' \
  -H 'X-Api-Key: your-api-key' \
  -H "Content-Type: application/json" \
  --data '{
  "uploadPresets": [
    {
      "key": "profile-photos",
      "revision": 2,
      "name": "Profile photos",
      "access": "token",
      "allowedContentTypes": [
        "image/*"
      ],
      "visibility": "public",
      "revealDecision": true,
      "allowedOrigins": [
        "https://example.com"
      ]
    }
  ]
}'
```

#### Request body

| Field           | Type            | Required | Description                                          |
| --------------- | --------------- | -------- | ---------------------------------------------------- |
| `uploadPresets` | `array<object>` | Yes      | Preset definitions to ensure, at most 50 per request |

#### Response

| Field           | Type                  | Required | Description                                     |
| --------------- | --------------------- | -------- | ----------------------------------------------- |
| `uploadPresets` | `array<UploadPreset>` | Yes      | The stored presets, in the order they were sent |

#### Error codes

| Status | Code                                                                                                          | Description                                                                                                                                                                                                                   |
| ------ | ------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 401    | <a id="ensure-upload-presets-error-api-key-missing" />`api-key/missing`                                       | Authentication required. Send your project API key in the X-Api-Key header.                                                                                                                                                   |
| 401    | <a id="ensure-upload-presets-error-api-key-invalid" />`api-key/invalid`                                       | The API key is invalid, revoked or expired. Create a key under Developer → API Keys.                                                                                                                                          |
| 403    | <a id="ensure-upload-presets-error-api-key-project-mismatch" />`api-key/project-mismatch`                     | This API key belongs to a different project. Use a key created for the project that owns this resource.                                                                                                                       |
| 403    | <a id="ensure-upload-presets-error-auth-permission-denied" />`auth/permission-denied`                         | This API key lacks a permission the request needs (details.permission names it, such as assets:download for an asset's folder, and details.role the least role that includes it), or the resource belongs to another project. |
| 400    | <a id="ensure-upload-presets-error-validation-invalid-request" />`validation/invalid-request`                 | The request's parameters, query or body are invalid. details.issues lists each problem, with its path and issue code.                                                                                                         |
| 415    | <a id="ensure-upload-presets-error-request-unsupported-content-type" />`request/unsupported-content-type`     | Send the request body as application/json.                                                                                                                                                                                    |
| 404    | <a id="ensure-upload-presets-error-project-not-found" />`project/not-found`                                   | Project not found                                                                                                                                                                                                             |
| 409    | <a id="ensure-upload-presets-error-upload-preset-revision-conflict" />`upload-preset/revision-conflict`       | The upload preset changed, or was created or deleted, since you read it. Retrieve it again and retry with its current revision.                                                                                               |
| 400    | <a id="ensure-upload-presets-error-upload-preset-daily-limit-required" />`upload-preset/daily-limit-required` | A preset that anyone can upload to needs a daily upload limit.                                                                                                                                                                |
| 400    | <a id="ensure-upload-presets-error-upload-preset-invalid-folder" />`upload-preset/invalid-folder`             | The folder isn't in this project. Choose a folder from this project and try again.                                                                                                                                            |
| 400    | <a id="ensure-upload-presets-error-upload-preset-invalid-policy" />`upload-preset/invalid-policy`             | The policy isn't one of this organisation's. Choose one of its policies and try again.                                                                                                                                        |
| 500    | <a id="ensure-upload-presets-error-server-internal-error" />`server/internal-error`                           | An internal error occurred. Retry later, and quote requestId to support.                                                                                                                                                      |

***

### Delete Upload Preset

Delete an upload preset. Browsers can no longer upload through it; assets already uploaded stay.

**Endpoint:** `DELETE https://api.calmlens.com/v1/upload-presets/:uploadPresetId`

**Authentication:** API Key

#### Example request

```bash
curl -X DELETE \
  'https://api.calmlens.com/v1/upload-presets/preset_4Lk8Qw2Xm9Rt5Vb3Nc7P' \
  -H 'X-Api-Key: your-api-key'
```

#### Response

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

#### Error codes

| Status | Code                                                                                         | Description                                                                                                                                                                                                                   |
| ------ | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 401    | <a id="delete-upload-preset-error-api-key-missing" />`api-key/missing`                       | Authentication required. Send your project API key in the X-Api-Key header.                                                                                                                                                   |
| 401    | <a id="delete-upload-preset-error-api-key-invalid" />`api-key/invalid`                       | The API key is invalid, revoked or expired. Create a key under Developer → API Keys.                                                                                                                                          |
| 403    | <a id="delete-upload-preset-error-api-key-project-mismatch" />`api-key/project-mismatch`     | This API key belongs to a different project. Use a key created for the project that owns this resource.                                                                                                                       |
| 403    | <a id="delete-upload-preset-error-auth-permission-denied" />`auth/permission-denied`         | This API key lacks a permission the request needs (details.permission names it, such as assets:download for an asset's folder, and details.role the least role that includes it), or the resource belongs to another project. |
| 400    | <a id="delete-upload-preset-error-validation-invalid-request" />`validation/invalid-request` | The request's parameters, query or body are invalid. details.issues lists each problem, with its path and issue code.                                                                                                         |
| 404    | <a id="delete-upload-preset-error-upload-preset-not-found" />`upload-preset/not-found`       | Upload preset not found. Check the preset ID in the portal.                                                                                                                                                                   |
| 500    | <a id="delete-upload-preset-error-server-internal-error" />`server/internal-error`           | An internal error occurred. Retry later, and quote requestId to support.                                                                                                                                                      |

***

### Get Project Settings

Get the project's settings, with every default filled in, and the revision to send back with a change.

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

**Authentication:** API Key

#### Example request

```bash
curl -X GET \
  'https://api.calmlens.com/v1/projects/proj_8fK2mQx7Lp4NvB3tRz9Y/settings' \
  -H 'X-Api-Key: your-api-key'
```

#### Response

| Field       | Type      | Required | Description                                                                                                                                                    |
| ----------- | --------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `projectId` | `string`  | Yes      | ID of the project                                                                                                                                              |
| `revision`  | `integer` | Yes      | Counts changes to the project's settings and metadata fields, starting at 1. Send it back with a change, so a change made since you read it isn't overwritten. |
| `settings`  | `object`  | Yes      |                                                                                                                                                                |

#### Error codes

| Status | Code                                                                                         | Description                                                                                                                                                                                                                   |
| ------ | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 401    | <a id="get-project-settings-error-api-key-missing" />`api-key/missing`                       | Authentication required. Send your project API key in the X-Api-Key header.                                                                                                                                                   |
| 401    | <a id="get-project-settings-error-api-key-invalid" />`api-key/invalid`                       | The API key is invalid, revoked or expired. Create a key under Developer → API Keys.                                                                                                                                          |
| 403    | <a id="get-project-settings-error-api-key-project-mismatch" />`api-key/project-mismatch`     | This API key belongs to a different project. Use a key created for the project that owns this resource.                                                                                                                       |
| 403    | <a id="get-project-settings-error-auth-permission-denied" />`auth/permission-denied`         | This API key lacks a permission the request needs (details.permission names it, such as assets:download for an asset's folder, and details.role the least role that includes it), or the resource belongs to another project. |
| 400    | <a id="get-project-settings-error-validation-invalid-request" />`validation/invalid-request` | The request's parameters, query or body are invalid. details.issues lists each problem, with its path and issue code.                                                                                                         |
| 404    | <a id="get-project-settings-error-project-not-found" />`project/not-found`                   | Project not found                                                                                                                                                                                                             |
| 500    | <a id="get-project-settings-error-server-internal-error" />`server/internal-error`           | An internal error occurred. Retry later, and quote requestId to support.                                                                                                                                                      |

***

### Update Project Settings

Replace the project's settings. Settings left out return to their defaults. Unchanged settings keep their revision. The project's policy and metadata fields have their own endpoints.

**Endpoint:** `PUT https://api.calmlens.com/v1/projects/:projectId/settings`

**Authentication:** API Key

#### Example request

```bash
curl -X PUT \
  'https://api.calmlens.com/v1/projects/proj_8fK2mQx7Lp4NvB3tRz9Y/settings' \
  -H 'X-Api-Key: your-api-key' \
  -H "Content-Type: application/json" \
  --data '{
  "revision": 4,
  "settings": {
    "optOutJurisdictions": [
      "EU"
    ],
    "blockUnmoderatedAssets": true
  }
}'
```

#### Request body

| Field                       | Type      | Required | Description                                                                                                                                                                                                                       |
| --------------------------- | --------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `revision`                  | `integer` | No       | The `revision` you last read. Settings or metadata fields changed since are refused with `project-settings/revision-conflict`. Omit it to replace them whatever their revision.                                                   |
| `settings`                  | `object`  | Yes      | The complete settings. Settings left out return to their defaults.                                                                                                                                                                |
| `searchIndexingBudgetUnits` | `number`  | No       | Needed when turning on `aiMediaSearchEnabled`: the most indexing units you accept for the project's existing media. A smaller value than the estimate is refused with `search/estimate-changed`, whose details give the estimate. |

#### Response

| Field       | Type      | Required | Description                                                                                                                                                    |
| ----------- | --------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `projectId` | `string`  | Yes      | ID of the project                                                                                                                                              |
| `revision`  | `integer` | Yes      | Counts changes to the project's settings and metadata fields, starting at 1. Send it back with a change, so a change made since you read it isn't overwritten. |
| `settings`  | `object`  | Yes      |                                                                                                                                                                |

#### Error codes

| Status | Code                                                                                                            | Description                                                                                                                                                                                                                   |
| ------ | --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 401    | <a id="update-project-settings-error-api-key-missing" />`api-key/missing`                                       | Authentication required. Send your project API key in the X-Api-Key header.                                                                                                                                                   |
| 401    | <a id="update-project-settings-error-api-key-invalid" />`api-key/invalid`                                       | The API key is invalid, revoked or expired. Create a key under Developer → API Keys.                                                                                                                                          |
| 403    | <a id="update-project-settings-error-api-key-project-mismatch" />`api-key/project-mismatch`                     | This API key belongs to a different project. Use a key created for the project that owns this resource.                                                                                                                       |
| 403    | <a id="update-project-settings-error-auth-permission-denied" />`auth/permission-denied`                         | This API key lacks a permission the request needs (details.permission names it, such as assets:download for an asset's folder, and details.role the least role that includes it), or the resource belongs to another project. |
| 400    | <a id="update-project-settings-error-validation-invalid-request" />`validation/invalid-request`                 | The request's parameters, query or body are invalid. details.issues lists each problem, with its path and issue code.                                                                                                         |
| 415    | <a id="update-project-settings-error-request-unsupported-content-type" />`request/unsupported-content-type`     | Send the request body as application/json.                                                                                                                                                                                    |
| 404    | <a id="update-project-settings-error-project-not-found" />`project/not-found`                                   | Project not found                                                                                                                                                                                                             |
| 409    | <a id="update-project-settings-error-project-settings-revision-conflict" />`project-settings/revision-conflict` | The project's settings or metadata fields changed since you read them. Retrieve them again and retry with the current revision.                                                                                               |
| 409    | <a id="update-project-settings-error-search-estimate-changed" />`search/estimate-changed`                       | Turning on AI media search needs a searchIndexingBudgetUnits of at least the current indexing estimate, which details.indexingUnits gives.                                                                                    |
| 500    | <a id="update-project-settings-error-server-internal-error" />`server/internal-error`                           | An internal error occurred. Retry later, and quote requestId to support.                                                                                                                                                      |

***

### List Metadata Fields

List the project's metadata fields in display order, with the revision to send back with a change.

**Endpoint:** `GET https://api.calmlens.com/v1/projects/:projectId/metadata-fields`

**Authentication:** API Key

#### Example request

```bash
curl -X GET \
  'https://api.calmlens.com/v1/projects/proj_8fK2mQx7Lp4NvB3tRz9Y/metadata-fields' \
  -H 'X-Api-Key: your-api-key'
```

#### Response

| Field       | Type                   | Required | Description                                                                                                                                                    |
| ----------- | ---------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `projectId` | `string`               | Yes      | ID of the project                                                                                                                                              |
| `revision`  | `integer`              | Yes      | Counts changes to the project's settings and metadata fields, starting at 1. Send it back with a change, so a change made since you read it isn't overwritten. |
| `fields`    | `array<MetadataField>` | Yes      | The project's metadata fields, in display order                                                                                                                |

#### Error codes

| Status | Code                                                                                         | Description                                                                                                                                                                                                                   |
| ------ | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 401    | <a id="list-metadata-fields-error-api-key-missing" />`api-key/missing`                       | Authentication required. Send your project API key in the X-Api-Key header.                                                                                                                                                   |
| 401    | <a id="list-metadata-fields-error-api-key-invalid" />`api-key/invalid`                       | The API key is invalid, revoked or expired. Create a key under Developer → API Keys.                                                                                                                                          |
| 403    | <a id="list-metadata-fields-error-api-key-project-mismatch" />`api-key/project-mismatch`     | This API key belongs to a different project. Use a key created for the project that owns this resource.                                                                                                                       |
| 403    | <a id="list-metadata-fields-error-auth-permission-denied" />`auth/permission-denied`         | This API key lacks a permission the request needs (details.permission names it, such as assets:download for an asset's folder, and details.role the least role that includes it), or the resource belongs to another project. |
| 400    | <a id="list-metadata-fields-error-validation-invalid-request" />`validation/invalid-request` | The request's parameters, query or body are invalid. details.issues lists each problem, with its path and issue code.                                                                                                         |
| 404    | <a id="list-metadata-fields-error-project-not-found" />`project/not-found`                   | Project not found                                                                                                                                                                                                             |
| 500    | <a id="list-metadata-fields-error-server-internal-error" />`server/internal-error`           | An internal error occurred. Retry later, and quote requestId to support.                                                                                                                                                      |

***

### Ensure Metadata Fields

Create each metadata field, or replace it with the definition sent, by ID. Other fields are kept, and new ones are added at the end. Unchanged fields keep their revision.

**Endpoint:** `PUT https://api.calmlens.com/v1/projects/:projectId/metadata-fields`

**Authentication:** API Key

#### Example request

```bash
curl -X PUT \
  'https://api.calmlens.com/v1/projects/proj_8fK2mQx7Lp4NvB3tRz9Y/metadata-fields' \
  -H 'X-Api-Key: your-api-key' \
  -H "Content-Type: application/json" \
  --data '{
  "revision": 4,
  "fields": [
    {
      "id": "userId",
      "title": "User",
      "type": "string",
      "icon": "user",
      "color": "#2563eb"
    }
  ]
}'
```

#### Request body

| Field      | Type                   | Required | Description                                                                                                                                                                     |
| ---------- | ---------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `revision` | `integer`              | No       | The `revision` you last read. Settings or metadata fields changed since are refused with `project-settings/revision-conflict`. Omit it to replace them whatever their revision. |
| `fields`   | `array<MetadataField>` | Yes      | Fields to create, or replace by ID. Other fields are kept; new ones are added at the end. A project has at most 50.                                                             |

#### Response

| Field       | Type                   | Required | Description                                                                                                                                                    |
| ----------- | ---------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `projectId` | `string`               | Yes      | ID of the project                                                                                                                                              |
| `revision`  | `integer`              | Yes      | Counts changes to the project's settings and metadata fields, starting at 1. Send it back with a change, so a change made since you read it isn't overwritten. |
| `fields`    | `array<MetadataField>` | Yes      | The project's metadata fields, in display order                                                                                                                |

#### Error codes

| Status | Code                                                                                                           | Description                                                                                                                                                                                                                   |
| ------ | -------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 401    | <a id="ensure-metadata-fields-error-api-key-missing" />`api-key/missing`                                       | Authentication required. Send your project API key in the X-Api-Key header.                                                                                                                                                   |
| 401    | <a id="ensure-metadata-fields-error-api-key-invalid" />`api-key/invalid`                                       | The API key is invalid, revoked or expired. Create a key under Developer → API Keys.                                                                                                                                          |
| 403    | <a id="ensure-metadata-fields-error-api-key-project-mismatch" />`api-key/project-mismatch`                     | This API key belongs to a different project. Use a key created for the project that owns this resource.                                                                                                                       |
| 403    | <a id="ensure-metadata-fields-error-auth-permission-denied" />`auth/permission-denied`                         | This API key lacks a permission the request needs (details.permission names it, such as assets:download for an asset's folder, and details.role the least role that includes it), or the resource belongs to another project. |
| 400    | <a id="ensure-metadata-fields-error-validation-invalid-request" />`validation/invalid-request`                 | The request's parameters, query or body are invalid. details.issues lists each problem, with its path and issue code.                                                                                                         |
| 415    | <a id="ensure-metadata-fields-error-request-unsupported-content-type" />`request/unsupported-content-type`     | Send the request body as application/json.                                                                                                                                                                                    |
| 404    | <a id="ensure-metadata-fields-error-project-not-found" />`project/not-found`                                   | Project not found                                                                                                                                                                                                             |
| 409    | <a id="ensure-metadata-fields-error-project-settings-revision-conflict" />`project-settings/revision-conflict` | The project's settings or metadata fields changed since you read them. Retrieve them again and retry with the current revision.                                                                                               |
| 500    | <a id="ensure-metadata-fields-error-server-internal-error" />`server/internal-error`                           | An internal error occurred. Retry later, and quote requestId to support.                                                                                                                                                      |

***

### Delete Metadata Field

Delete a metadata field definition. Assets keep the metadata values they hold.

**Endpoint:** `DELETE https://api.calmlens.com/v1/projects/:projectId/metadata-fields/:fieldId`

**Authentication:** API Key

#### Example request

```bash
curl -X DELETE \
  'https://api.calmlens.com/v1/projects/proj_8fK2mQx7Lp4NvB3tRz9Y/metadata-fields/example' \
  -H 'X-Api-Key: your-api-key'
```

#### Query parameters

| Field      | Type      | Required | Description                                                                                               |
| ---------- | --------- | -------- | --------------------------------------------------------------------------------------------------------- |
| `revision` | `integer` | No       | The `revision` you last read. Fields changed since are refused with `project-settings/revision-conflict`. |

#### Response

| Field       | Type                   | Required | Description                                                                                                                                                    |
| ----------- | ---------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `projectId` | `string`               | Yes      | ID of the project                                                                                                                                              |
| `revision`  | `integer`              | Yes      | Counts changes to the project's settings and metadata fields, starting at 1. Send it back with a change, so a change made since you read it isn't overwritten. |
| `fields`    | `array<MetadataField>` | Yes      | The project's metadata fields, in display order                                                                                                                |

#### Error codes

| Status | Code                                                                                                          | Description                                                                                                                                                                                                                   |
| ------ | ------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 401    | <a id="delete-metadata-field-error-api-key-missing" />`api-key/missing`                                       | Authentication required. Send your project API key in the X-Api-Key header.                                                                                                                                                   |
| 401    | <a id="delete-metadata-field-error-api-key-invalid" />`api-key/invalid`                                       | The API key is invalid, revoked or expired. Create a key under Developer → API Keys.                                                                                                                                          |
| 403    | <a id="delete-metadata-field-error-api-key-project-mismatch" />`api-key/project-mismatch`                     | This API key belongs to a different project. Use a key created for the project that owns this resource.                                                                                                                       |
| 403    | <a id="delete-metadata-field-error-auth-permission-denied" />`auth/permission-denied`                         | This API key lacks a permission the request needs (details.permission names it, such as assets:download for an asset's folder, and details.role the least role that includes it), or the resource belongs to another project. |
| 400    | <a id="delete-metadata-field-error-validation-invalid-request" />`validation/invalid-request`                 | The request's parameters, query or body are invalid. details.issues lists each problem, with its path and issue code.                                                                                                         |
| 404    | <a id="delete-metadata-field-error-project-not-found" />`project/not-found`                                   | Project not found                                                                                                                                                                                                             |
| 404    | <a id="delete-metadata-field-error-metadata-field-not-found" />`metadata-field/not-found`                     | The project has no metadata field with this ID.                                                                                                                                                                               |
| 409    | <a id="delete-metadata-field-error-project-settings-revision-conflict" />`project-settings/revision-conflict` | The project's settings or metadata fields changed since you read them. Retrieve them again and retry with the current revision.                                                                                               |
| 500    | <a id="delete-metadata-field-error-server-internal-error" />`server/internal-error`                           | An internal error occurred. Retry later, and quote requestId to support.                                                                                                                                                      |
