# Projects

Create, read, update, delete and inspect translation projects through the Comvi REST API.

Canonical: https://comvi.io/docs/api/projects

A project belongs to an organization and holds translation keys, locales and namespaces. An API key is bound to one project, so it can read and update that project but cannot list or create projects.

Reading or writing `mtSettings` and `cdnSettings` needs the `project.manage_settings` permission. No scope grants it, so a key created from the scope list never sees those two objects and gets `403 FORBIDDEN` when it sends them. To give a key the permission, name `project.manage_settings` in `permissions` when creating it through [`POST /v1/projects/{projectId}/api-keys`](/docs/api/api-keys); you can only grant a permission you hold. The key's own project comes back from [`GET /v1/project`](/docs/api/api-keys#get-v1project).

Common errors on every endpoint: `401 AUTHENTICATION_REQUIRED` (missing or invalid credentials, or a Bearer key on a session-only route), `403 FORBIDDEN`, `403 READ_ONLY_MODE` on writes, `409 TRANSACTION_CONFLICT` on create, update and delete (retry the request). See [Errors](/docs/api/errors).

### `GET /v1/projects`

List the projects of an organization, with integer cursor pagination.

Session cookie only.

| Parameter | In | Type | Required | Meaning |
|-----------|----|------|----------|---------|
| `organizationId` | query | `integer` | Yes | Organization to list |
| `cursor` | query | `integer` | No | `nextCursor` from the previous response. An integer, unlike the string cursor of other lists |
| `limit` | query | `integer` | No | 1 to 100, default 50 |

```bash
curl -b cookies.txt "https://api.comvi.io/v1/projects?organizationId=1&limit=10"
```

```json
{
"projects": [
    {
      "id": 1, "organizationId": 1, "name": "my-web-app",
      "description": "Main web application translations", "sourceLocale": "en",
      "locales": [{ "id": 1, "code": "en", "name": "English", "nativeName": "English", "isCustom": false, "flagEmoji": "🇺🇸" }],
      "namespaces": [{ "id": 1, "projectId": 1, "namespace": "default", "description": null, "isDefault": true, "createdAt": "2025-01-15T10:30:00.000Z", "updatedAt": "2025-01-15T10:30:00.000Z" }]
    }
],
"nextCursor": 2,
"hasMore": true
}
```

`hasMore` is always present. `nextCursor` is `null` on the last page.

| Status and code | When |
|-----------------|------|
| `403 ORGANIZATION_NOT_FOUND` | The caller is not a member of the organization |
| `422 FIELD_REQUIRED` | `organizationId` is missing |

### `GET /v1/projects/{id}`

Read one project with its locales, namespaces and settings.

Session cookie or API key. A key reads its own project only.

| Parameter | In | Type | Required | Meaning |
|-----------|----|------|----------|---------|
| `id` | path | `integer` | Yes | Project ID |

```bash
curl -H "Authorization: Bearer $COMVI_API_KEY" https://api.comvi.io/v1/projects/1
```

```json
{
"id": 1, "organizationId": 1, "name": "my-web-app",
"description": "Main web application translations",
"aiContext": "A project management SaaS for small engineering teams.",
"sourceLocale": "en",
"locales": [{ "id": 1, "code": "en", "name": "English", "nativeName": "English", "isCustom": false, "flagEmoji": "🇺🇸" }],
"namespaces": [{ "id": 1, "projectId": 1, "namespace": "default", "description": null, "isDefault": true, "createdAt": "…", "updatedAt": "…" }],
"mtSettings": { "enabledProviders": ["google", "deepl"], "primaryProvider": "google", "autoApply": false, "aiTranslationInstructions": null, "aiModels": null },
"cdnSettings": { "enabled": true, "locales": ["en", "uk"], "namespaces": ["default"], "statuses": ["translated"], "format": "json", "baseUrl": null, "lastDeployedAt": null }
}
```

`mtSettings` and `cdnSettings` appear only when the caller holds `project.manage_settings`. Without it both keys are absent, not empty. `locales[].aiContext` holds the per-language context set through [Languages](/docs/api/languages).

| Status and code | When |
|-----------------|------|
| `403 FORBIDDEN` | No permission to view the project, or a key used against another project |
| `404 PROJECT_NOT_FOUND` | Project does not exist |

### `POST /v1/projects`

Create a project in an organization. A `default` namespace, a translation memory and a glossary are created with it.

Session cookie only, with `project.create` in the organization.

| Parameter | In | Type | Required | Meaning |
|-----------|----|------|----------|---------|
| `organizationId` | body | `integer` | Yes | Organization to create the project in |
| `name` | body | `string` | Yes | 1 to 255 characters |
| `description` | body | `string` | No | Up to 1000 characters |
| `sourceLocaleCode` | body | `string` | Yes | Source language code, such as `en` |

```bash
curl -X POST -b cookies.txt -H "Content-Type: application/json" \
-d '{"organizationId": 1, "name": "my-web-app", "sourceLocaleCode": "en"}' \
https://api.comvi.io/v1/projects
```

Returns `201 Created` with the project object, in the shape of `GET /v1/projects/{id}` without `mtSettings` and `cdnSettings`.

| Status and code | When |
|-----------------|------|
| `400 LOCALE_NOT_FOUND` | `sourceLocaleCode` is not a valid locale |
| `403 FORBIDDEN` | No permission to create projects in the organization |
| `403 LIMIT_EXCEEDED` | Project limit reached for the plan |

### `PATCH /v1/projects/{id}`

Update name, description, AI context, source locale, machine translation settings or CDN settings. Send only the fields to change.

Session cookie or API key with `translations:write`. Sending `mtSettings` or `cdnSettings` also needs `project.manage_settings`; without it the whole request fails with `403`.

| Parameter | In | Type | Required | Meaning |
|-----------|----|------|----------|---------|
| `id` | path | `integer` | Yes | Project ID |
| `name` | body | `string` | No | 1 to 255 characters |
| `description` | body | `string` | No | Up to 1000 characters |
| `aiContext` | body | `string \| null` | No | Project-level context for AI translation, up to 8000 characters. `null` clears it |
| `sourceLocaleCode` | body | `string` | No | New source language code |
| `mtSettings` | body | `object` | No | Machine-translation settings; fields in the `mtSettings` table |
| `cdnSettings` | body | `object` | No | Content Deploy settings; fields in the `cdnSettings` table |

| `mtSettings` field | Type | Meaning |
|--------------------|------|---------|
| `enabledProviders` | `string[]` | Any of `google`, `deepl`, `aws`, `azure`, `openai`, `anthropic` |
| `primaryProvider` | `string \| null` | Default provider |
| `autoApply` | `boolean` | Apply MT suggestions automatically |
| `aiTranslationInstructions` | `string \| null` | Custom instructions for AI providers |
| `aiModels` | `object \| null` | Model per provider, such as `{"openai": "gpt-4o"}` |

| `cdnSettings` field | Type | Meaning |
|---------------------|------|---------|
| `enabled` | `boolean` | Enable CDN publishing |
| `locales` | `string[]` | Locale codes to publish |
| `namespaces` | `string[]` | Namespaces to publish |
| `statuses` | `string[]` | Statuses to publish: `translated`, `not_reviewed`, `not_translated` |
| `format` | `string` | `json` or `i18next-v4` |

```bash
curl -X PATCH -H "Authorization: Bearer $COMVI_API_KEY" -H "Content-Type: application/json" \
-d '{"name": "my-web-app-v2", "aiContext": "A project management SaaS for small engineering teams."}' \
https://api.comvi.io/v1/projects/1
```

Returns the full updated project, in the shape of `GET /v1/projects/{id}`.

| Status and code | When |
|-----------------|------|
| `403 FORBIDDEN` | No permission to edit the project, or to manage its settings |
| `404 PROJECT_NOT_FOUND` | Project does not exist |
| `404 LOCALE_NOT_FOUND` | `sourceLocaleCode` is invalid |

### `DELETE /v1/projects/{id}`

Soft-delete a project. Its data is retained but no longer accessible.

Session cookie only.

| Parameter | In | Type | Required | Meaning |
|-----------|----|------|----------|---------|
| `id` | path | `integer` | Yes | Project ID |

```bash
curl -X DELETE -b cookies.txt https://api.comvi.io/v1/projects/1
```

Returns `204 No Content`.

| Status and code | When |
|-----------------|------|
| `403 FORBIDDEN` | No permission to delete the project |
| `404 PROJECT_NOT_FOUND` | Project does not exist |

### `GET /v1/projects/stats`

Translation statistics for the projects the caller can see in an organization.

Session cookie or API key with `translations:read`. A key gets its own project only.

| Parameter | In | Type | Required | Meaning |
|-----------|----|------|----------|---------|
| `organizationId` | query | `integer` | Yes | Organization ID |

```bash
curl -H "Authorization: Bearer $COMVI_API_KEY" "https://api.comvi.io/v1/projects/stats?organizationId=1"
```

```json
[{ "projectId": 1, "keyCount": 250, "translatedPct": 85.5, "notReviewedPct": 10.2, "notTranslatedPct": 4.3 }]
```

| Status and code | When |
|-----------------|------|
| `403 ORGANIZATION_NOT_FOUND` | The caller, or the key's project, is not in the organization |
| `403 FORBIDDEN` | The key lacks `translations:read`, or its creator lost `translation.view` |

### `POST /v1/projects/{projectId}/ai-context/generate`

Sample the project's key names and source texts and ask an LLM for a draft project description covering domain, audience, tone and terminology. The draft is not saved; store it by sending it as `aiContext` to `PATCH /v1/projects/{id}`.

Session cookie only, with `project.edit`. Rate limit: 5 requests per hour per project. No request body.

| Parameter | In | Type | Required | Meaning |
|-----------|----|------|----------|---------|
| `projectId` | path | `integer` | Yes | Project ID |

```bash
curl -X POST -b cookies.txt https://api.comvi.io/v1/projects/1/ai-context/generate
```

```json
{ "draft": "A project management SaaS for small engineering teams. The tone is direct and informal.", "keyCount": 250, "sampledCount": 120 }
```

| Field | Type | Meaning |
|-------|------|---------|
| `draft` | `string` | The generated description, not stored |
| `keyCount` | `integer` | Translation keys in the project |
| `sampledCount` | `integer` | Keys sampled and sent to the model |

| Status and code | When |
|-----------------|------|
| `403 FORBIDDEN` | No permission to edit the project |
| `404 PROJECT_NOT_FOUND` | Project does not exist |
| `422 NOT_ENOUGH_KEYS` | Fewer than 30 keys. `meta` carries `keyCount` and `required` |
| `429 TOO_MANY_REQUESTS` | More than 5 requests in the last hour for the project |
| `502 INTERNAL_ERROR` | The model call failed |
| `504 INTERNAL_ERROR` | The model call timed out. `meta.timeout` is `true` |

## Next steps

- [Translations](/docs/api/translations) for keys and values in the project
- [Languages](/docs/api/languages) to add and remove locales
- [Namespaces](/docs/api/namespaces) to group keys
