# API Keys

Create, update and revoke API keys over the Comvi REST API, and use a key to read the project it belongs to.

Canonical: https://comvi.io/docs/api/api-keys

An API key authenticates server-to-server calls. Each key is scoped to one project and carries a set of scopes.

Managing keys is session-only: the first five endpoints take a session cookie, and a Bearer key sent to them returns `401 AUTHENTICATION_REQUIRED`. The last two take a Bearer key and nothing else.

## Key object

| Field | Type | Meaning |
|-------|------|---------|
| `id` | `integer` | API key ID |
| `projectId` | `integer` | Project the key belongs to |
| `name` | `string` | Descriptive name |
| `description` | `string \| null` | Optional description |
| `keyPrefix` | `string` | The first 12 characters of the key, such as `comvi_a1b2c3`. The full value is never returned after creation |
| `lastUsedAt` | `string \| null` | ISO 8601 time of last use, `null` if never used |
| `expiresAt` | `string \| null` | Expiration, `null` for non-expiring keys |
| `isActive` | `boolean` | Whether the key is active |
| `permissions` | `string[]` | Scopes granted |
| `createdAt`, `updatedAt` | `string` | ISO 8601 timestamps |

```json
{
"id": 1, "projectId": 5, "name": "CI/CD Pipeline", "description": "Used by GitHub Actions for pulling translations",
"keyPrefix": "comvi_a1b2c3", "lastUsedAt": "2025-03-20T14:00:00Z", "expiresAt": null, "isActive": true,
"permissions": ["project:read", "translations:read", "schema:read"],
"createdAt": "2025-01-15T10:00:00Z", "updatedAt": "2025-01-15T10:00:00Z"
}
```

### `GET /v1/projects/{projectId}/api-keys`

List a project's keys.

Session cookie, with `api_key.view` on the project.

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

```bash
curl -b cookies.txt https://api.comvi.io/v1/projects/5/api-keys
```

Returns an array of key objects.

### `GET /v1/api-keys/{id}`

Read one key. The caller must have access to the key's project.

Session cookie.

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

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

Returns one key object.

### `POST /v1/projects/{projectId}/api-keys`

Create a key. The full value comes back once, as `token`; store it before closing the connection.

Session cookie, with `api_key.create` on the project.

| Parameter | In | Type | Required | Meaning |
|-----------|----|------|----------|---------|
| `projectId` | path | `integer` | Yes | Project ID |
| `name` | body | `string` | Yes | 1 to 255 characters |
| `description` | body | `string` | No | Up to 2000 characters |
| `expiresAt` | body | `string` | No | ISO 8601 expiration |
| `permissions` | body | `string[]` | Yes | Scopes to grant, at least one. Omitting it returns `422` |

| Scope | Grants |
|-------|--------|
| `project:read` | Read project metadata, locales, and namespaces |
| `translations:read` | Read and export translation keys, values, glossaries, translation memory, comments, and history |
| `translations:write` | Create and update translation keys, values, namespaces, locales, glossary entries, comments, and imports. Also deletes a namespace and clears translation values |
| `translations:review` | Mark translations reviewed |
| `translations:delete` | Delete translation keys |
| `schema:read` | Read key schema and type-generation metadata |

`project:read` is added to every key whether or not you request it, so the CLI and SDK loaders can call `GET /v1/project`. Granting a scope you do not hold returns `403 FORBIDDEN`.

```bash
curl -X POST -b cookies.txt -H "Content-Type: application/json" \
-d '{"name": "CI/CD Pipeline", "permissions": ["translations:read", "schema:read"]}' \
https://api.comvi.io/v1/projects/5/api-keys
```

Returns `201 Created` with the key object plus `token`.

```json
{
"id": 2, "projectId": 5, "name": "CI/CD Pipeline", "keyPrefix": "comvi_9f2c1a", "isActive": true,
"permissions": ["project:read", "translations:read", "schema:read"],
"token": "comvi_9f2c1a7b4e6d80351c2b3a4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f"
}
```

### `PUT /v1/api-keys/{id}`

Update a key's name, description, active status, expiration or scopes.

Session cookie.

| Parameter | In | Type | Required | Meaning |
|-----------|----|------|----------|---------|
| `id` | path | `integer` | Yes | API key ID |
| `name` | body | `string` | No | 1 to 255 characters |
| `description` | body | `string \| null` | No | Up to 2000 characters |
| `isActive` | body | `boolean` | No | Enable or disable the key |
| `expiresAt` | body | `string \| null` | No | Set or remove expiration |
| `permissions` | body | `string[]` | No | Replacement scopes. If present, at least one |

```bash
curl -X PUT -b cookies.txt -H "Content-Type: application/json" \
-d '{"name": "Production Sync Key", "permissions": ["translations:read", "translations:write", "schema:read"]}' \
https://api.comvi.io/v1/api-keys/1
```

Returns the updated key object.

### `DELETE /v1/api-keys/{id}`

Revoke a key. Requests using it fail immediately.

Session cookie.

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

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

Returns `204 No Content`.

### `GET /v1/project`

Read the project the key is bound to. Make this call first with a new key: it confirms the key works and which project it reaches.

Bearer key only, with `project:read`, which every key has. No parameters.

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

```json
{ "id": 5, "organizationId": 1, "name": "my-web-app", "description": "Main web application translations", "sourceLocale": "en" }
```

| Field | Type | Meaning |
|-------|------|---------|
| `id` | `integer` | Project ID |
| `organizationId` | `integer` | Organization the project belongs to |
| `name` | `string` | Project name |
| `description` | `string \| null` | Project description |
| `sourceLocale` | `string` | Source locale code |

### `GET /v1/project/locales`

Read the source locale and every locale attached to the key's project. A creator with language restrictions sees only the locales they can access.

Bearer key only, with `project:read`. No parameters.

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

```json
{
"sourceLocale": "en",
"locales": [
    { "id": 1, "code": "en", "name": "English", "nativeName": "English", "isCustom": false, "flagEmoji": "🇺🇸" },
    { "id": 2, "code": "uk", "name": "Ukrainian", "nativeName": "Українська", "isCustom": false, "flagEmoji": "🇺🇦" }
]
}
```

## Errors

| Status and code | When |
|-----------------|------|
| `400 UNIQUE_CONSTRAINT` | A key with this name already exists in the project |
| `400 INVALID_INPUT` | Unrecognised scopes, or a malformed payload |
| `401 AUTHENTICATION_REQUIRED` | No session cookie, or a Bearer key sent to a management endpoint |
| `401 API_KEY_INVALID` | The Bearer key is malformed or does not exist |
| `401 API_KEY_DISABLED` | The key has been deactivated |
| `401 API_KEY_EXPIRED` | The key has passed its expiration date |
| `401 API_KEY_PERMISSIONS_REVOKED` | The key's creator no longer holds the permissions it was granted |
| `403 FORBIDDEN` | No access to the key or project, or granting scopes you do not have |
| `403 READ_ONLY_MODE` | The organization is read-only. Applies to key creation |
| `404 API_KEY_NOT_FOUND` | The key does not exist |
| `404 PROJECT_NOT_FOUND` | The key's project no longer exists |
| `422 FIELD_REQUIRED` | `permissions` or `name` is missing |

## Next steps

- [Authentication](/docs/api/authentication) for scopes, sessions and which endpoints accept which
- [API keys in the platform](/docs/platform/api-keys) to manage keys from the dashboard
- [CLI](/docs/cli) to use a key with the Comvi CLI
