# Grant access

> Grant a login access to the workspace, or create a private-key principal and issue its key.

> Nevia is in beta and open to selected teams. Public launch in Dec 2026. The CLI installs with `curl -fsSL https://get.nevia.cloud | sh`, and the MCP server is at https://api.nevia.cloud/mcp. A person signs in once in the browser.
> Setup, sign-in and requesting access: [SKILL.md](https://nevia.cloud/SKILL.md). When Nevia fits and when it doesn't: [why.md](https://nevia.cloud/why.md).

> For the complete documentation index, see [llms.txt](https://nevia.cloud/llms.txt). This page's HTML version: https://nevia.cloud/docs/reference/api/access/createAccess

`POST https://api.nevia.cloud/v1/workspaces/{ws}/access`

## Authorization

- `bearerAuth`: HTTP bearer in the Authorization header

Send the token in the `Authorization: Bearer <token>` header. See [Authenticate](https://nevia.cloud/docs/reference/api.md#authenticate).

## Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `ws` | path | string | yes | Workspace id: 27 lowercase letters and digits. Match `^[A-Za-z0-9_-]{1,64}$`. |
| `Idempotency-Key` | header | string | yes | Retry key. A retry with the same key and body replays the first response. Length `1 <= length <= 255`. |

## Request body

Content type: `application/json`, required

Schema: `AccessCreateRequest`

Login or private-key principal to grant access to

## Behavior

Access takes effect at once. The key and key file are returned once, and an idempotent replay answers `key_file: null`.

## Example request

### cURL

```bash
curl -X POST "https://api.nevia.cloud/v1/workspaces/01d4cnjjq40hnnh0nwmkg35nv2h/access" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: 8f1c2a7e-3b4d-4c5e-9f0a-1b2c3d4e5f60" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "dana@example.com",
    "role": "editor"
  }'
```

### JavaScript

```js
const body = JSON.stringify({
  "email": "dana@example.com",
  "role": "editor"
})

fetch("https://api.nevia.cloud/v1/workspaces/01d4cnjjq40hnnh0nwmkg35nv2h/access", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${process.env.TOKEN}`,
    "Idempotency-Key": "8f1c2a7e-3b4d-4c5e-9f0a-1b2c3d4e5f60"
  },
  body
})
```

### Python

```python
import os
import requests

url = "https://api.nevia.cloud/v1/workspaces/01d4cnjjq40hnnh0nwmkg35nv2h/access"
body = """{
  "email": "dana@example.com",
  "role": "editor"
}"""
response = requests.request("POST", url, data = body, headers = {
  "Content-Type": "application/json", 
  "Authorization": f"Bearer {os.environ['TOKEN']}", 
  "Idempotency-Key": "8f1c2a7e-3b4d-4c5e-9f0a-1b2c3d4e5f60"
})

print(response.text)
```

## Responses

### 201

The access added, with a new private-key principal's key and key file.

Content type: `application/json`

Schema: `AccessCreateResponse`

Created access, with its key and key file

| Field | Type | Required | Description |
|---|---|---|---|
| `access` | Access | yes | Access of one principal to a workspace. |
| `key` | AccessKey \| null | yes | Key of the new principal. |
| `key_file` | KeyFile \| null | yes | Key file, returned once. |
| `operations` | array<OperationId> | yes | Operations this request started. |

Example:

```json
{
  "access": {
    "principal": {
      "id": "01d4cnjmn4fbb1r5btxb5mypv62",
      "auth": "email",
      "email": "string",
      "name": "string"
    },
    "role": "editor",
    "description": "string",
    "key": {
      "id": "01d4cnjq8r0tvwxyb3c4d5f6g7h",
      "principal": "01d4cnjmn4fbb1r5btxb5mypv62",
      "byok": true,
      "created_by": "01d4cnjmn4fbb1r5btxb5mypv62",
      "expires_at": "2026-09-10T12:34:56Z",
      "last_used_at": "2026-09-10T12:34:56Z",
      "created_at": "2026-09-10T12:34:56Z",
      "revoked_at": "2026-09-10T12:34:56Z"
    },
    "key_state": "active",
    "created_at": "2026-09-10T12:34:56Z"
  },
  "key": {
    "id": "01d4cnjq8r0tvwxyb3c4d5f6g7h",
    "principal": "01d4cnjmn4fbb1r5btxb5mypv62",
    "byok": true,
    "created_by": "01d4cnjmn4fbb1r5btxb5mypv62",
    "expires_at": "2026-09-10T12:34:56Z",
    "last_used_at": "2026-09-10T12:34:56Z",
    "created_at": "2026-09-10T12:34:56Z",
    "revoked_at": "2026-09-10T12:34:56Z"
  },
  "key_file": {
    "principal": "01d4cnjmn4fbb1r5btxb5mypv62",
    "subject": "393890846249869905",
    "key_id": "393890846384085404",
    "audience": "https://auth.example.com",
    "token_url": "https://api.example.com/v1/auth/token",
    "private_key": "string"
  },
  "operations": [
    "01d4cnjm89yk0r3xtv80dy0x7by"
  ]
}
```

### 202

An unknown email on the aiven.io domain or the allowlist: a pending invitation.

Content type: `application/json`

Schema: `InvitationResponse`

Invitation, with `resent` true when a pending one was refreshed

| Field | Type | Required | Description |
|---|---|---|---|
| `invitation` | Invitation | yes | Invitation for an email address that has not signed in. |
| `resent` | boolean | yes |  |
| `operations` | array<OperationId> | yes | Operations this request started. |

Example:

```json
{
  "invitation": {
    "id": "01d4cnjn1xy3n9dcwtjnxbxhf15",
    "workspace": "01d4cnjjq40hnnh0nwmkg35nv2h",
    "email": "dana@example.com",
    "role": "editor",
    "state": "pending",
    "invited_by": "01d4cnjmn4fbb1r5btxb5mypv62",
    "created_at": "2026-09-10T12:34:56Z",
    "updated_at": "2026-09-10T12:34:56Z",
    "expires_at": "2026-09-10T12:34:56Z",
    "last_sent_at": "2026-10-10T09:20:00Z",
    "email_sent": true,
    "accepted_by": "string",
    "accepted_at": null
  },
  "resent": true,
  "operations": [
    "01d4cnjm89yk0r3xtv80dy0x7by"
  ]
}
```

### 400

`validation_failed`: malformed body, unknown field, or invalid id or name. Fix `details.field` and retry. `validation_failed`: `issue_key` is false and a key field is sent. A principal created without a key takes no `public_key`, `expires_at` or `expires_in`. `validation_failed`: `expires_at` and `expires_in` are both sent. Send one. `validation_failed`: `expires_at` is outside 15 minutes to 90 days from now. `validation_failed`: `public_key` holds a private key. Send the public half only. `validation_failed`: `public_key` is not a PEM encoded public key. `validation_failed`: `public_key` is not an RSA key of at least 2048 bits. `validation_failed`: the identity provider refuses `public_key`. Send an RSA key of at least 2048 bits.

Content type: `application/json`

Schema: `ErrorEnvelope`

Body of every error response

| Field | Type | Required | Description |
|---|---|---|---|
| `error` | Error | yes |  |

Example `validation_failed: malformed body, unknown field, or invalid id or name. Fix \`details.field\` and retry`:

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Unknown field `colour` in the request body. Remove it; request bodies are strict.",
    "details": {
      "field": "colour",
      "in": "body"
    },
    "request_id": "req_01j9k3m2p7q4r8s1"
  }
}
```

Example `validation_failed: \`issue_key\` is false and a key field is sent. A principal created without a key takes no \`public_key\`, \`expires_at\` or \`expires_in\``:

```json
{
  "error": {
    "code": "validation_failed",
    "message": "public_key, expires_at and expires_in describe a key; with issue_key false there is none.",
    "details": {
      "field": "issue_key",
      "in": "body"
    },
    "request_id": "req_01j9k3m2p7q4r8s1"
  }
}
```

Example `validation_failed: \`expires_at\` and \`expires_in\` are both sent. Send one`:

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Send expires_at or expires_in, not both.",
    "details": {
      "field": "expires_in",
      "in": "body"
    },
    "request_id": "req_01j9k3m2p7q4r8s1"
  }
}
```

Example `validation_failed: \`expires_at\` is outside 15 minutes to 90 days from now`:

```json
{
  "error": {
    "code": "validation_failed",
    "message": "expires_at must be between 15 minutes and 90 days from now.",
    "details": {
      "field": "expires_at",
      "in": "body",
      "min_ms": 900000,
      "max_ms": 7776000000
    },
    "request_id": "req_01j9k3m2p7q4r8s1"
  }
}
```

Example `validation_failed: \`public_key\` holds a private key. Send the public half only`:

```json
{
  "error": {
    "code": "validation_failed",
    "message": "public_key holds a private key. Send the public half only; the private key stays with you.",
    "details": {
      "field": "public_key",
      "in": "body"
    },
    "request_id": "req_01j9k3m2p7q4r8s1"
  }
}
```

Example `validation_failed: \`public_key\` is not a PEM encoded public key`:

```json
{
  "error": {
    "code": "validation_failed",
    "message": "The public key could not be read. Send a PEM encoded RSA public key.",
    "details": {
      "field": "public_key",
      "in": "body"
    },
    "request_id": "req_01j9k3m2p7q4r8s1"
  }
}
```

Example `validation_failed: \`public_key\` is not an RSA key of at least 2048 bits`:

```json
{
  "error": {
    "code": "validation_failed",
    "message": "The public key must be RSA with at least 2048 bits.",
    "details": {
      "field": "public_key",
      "in": "body",
      "type": "ec",
      "bits": 256
    },
    "request_id": "req_01j9k3m2p7q4r8s1"
  }
}
```

Example `validation_failed: the identity provider refuses \`public_key\`. Send an RSA key of at least 2048 bits`:

```json
{
  "error": {
    "code": "validation_failed",
    "message": "The public key was refused. Send an RSA public key of at least 2048 bits, PEM encoded.",
    "details": {
      "field": "public_key",
      "in": "body"
    },
    "request_id": "req_01j9k3m2p7q4r8s1"
  }
}
```

### 401

`unauthorized`: the credential is missing, malformed, expired, or revoked. Send a valid bearer token.

Content type: `application/json`

Schema: `ErrorEnvelope`

Body of every error response

| Field | Type | Required | Description |
|---|---|---|---|
| `error` | Error | yes |  |

Example `unauthorized`:

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Missing credential. Send `Authorization: Bearer <token>` with an access token.",
    "details": {},
    "request_id": "req_01j9k3m2p7q4r8s1"
  }
}
```

### 403

`forbidden`: the token is read-only or scoped elsewhere, or the role cannot do this. Use a token or role that allows it.

Content type: `application/json`

Schema: `ErrorEnvelope`

Body of every error response

| Field | Type | Required | Description |
|---|---|---|---|
| `error` | Error | yes |  |

Example `forbidden`:

```json
{
  "error": {
    "code": "forbidden",
    "message": "You can view computer 01d4cnjk3xg90y576w8y6t4gdwm but your role does not allow computer#edit. Ask a workspace admin to change your role (PATCH /v1/workspaces/{ws}/access/{principal}).",
    "details": {
      "computer": "01d4cnjk3xg90y576w8y6t4gdwm",
      "permission": "computer#edit"
    },
    "request_id": "req_01j9k3m2p7q4r8s1"
  }
}
```

### 404

`not_found`: the workspace does not exist, or you cannot see it. Both return the same response. `not_found`: the `principal` id is not a known user.

Content type: `application/json`

Schema: `ErrorEnvelope`

Body of every error response

| Field | Type | Required | Description |
|---|---|---|---|
| `error` | Error | yes |  |

Example `not_found: the workspace does not exist, or you cannot see it. Both return the same response`:

```json
{
  "error": {
    "code": "not_found",
    "message": "Workspace 01d4cnjjq40hnnh0nwmkg35nv2h was not found. List the workspaces you can access with GET /v1/workspaces.",
    "details": {
      "workspace": "01d4cnjjq40hnnh0nwmkg35nv2h"
    },
    "request_id": "req_01j9k3m2p7q4r8s1"
  }
}
```

Example `not_found: the \`principal\` id is not a known user`:

```json
{
  "error": {
    "code": "not_found",
    "message": "No user 01d4cnjmn4fbb1r5btxb5mypv62 is known to Nevia. To add someone who has not signed in, invite them by email with POST /v1/workspaces/{ws}/access.",
    "details": {
      "principal": "01d4cnjmn4fbb1r5btxb5mypv62"
    },
    "request_id": "req_01j9k3m2p7q4r8s1"
  }
}
```

### 409

`access_conflict`: the principal already has access. PATCH changes its role. `name_conflict`: another live private-key principal in the workspace has this name. `limit_exceeded`: the workspace already has 25 private-key principals, the most allowed. `idempotency_conflict`: the `Idempotency-Key` was already used with a different body.

Content type: `application/json`

Schema: `ErrorEnvelope`

Body of every error response

| Field | Type | Required | Description |
|---|---|---|---|
| `error` | Error | yes |  |

Example `access_conflict`:

```json
{
  "error": {
    "code": "access_conflict",
    "message": "01d4cnjmn4fbb1r5btxb5mypv62 already has access to workspace 01d4cnjjq40hnnh0nwmkg35nv2h. Change the role with PATCH /v1/workspaces/01d4cnjjq40hnnh0nwmkg35nv2h/access/01d4cnjmn4fbb1r5btxb5mypv62.",
    "details": {
      "workspace": "01d4cnjjq40hnnh0nwmkg35nv2h",
      "principal": "01d4cnjmn4fbb1r5btxb5mypv62"
    },
    "request_id": "req_01j9k3m2p7q4r8s1"
  }
}
```

Example `name_conflict`:

```json
{
  "error": {
    "code": "name_conflict",
    "message": "Workspace 01d4cnjjq40hnnh0nwmkg35nv2h already has a private-key principal named api-staging. Pick another name, or rename that one with PATCH.",
    "details": {
      "workspace": "01d4cnjjq40hnnh0nwmkg35nv2h",
      "name": "api-staging",
      "kind": "service_account"
    },
    "request_id": "req_01j9k3m2p7q4r8s1"
  }
}
```

Example `limit_exceeded`:

```json
{
  "error": {
    "code": "limit_exceeded",
    "message": "Workspace 01d4cnjjq40hnnh0nwmkg35nv2h already has 25 private-key principals, the most allowed (25). Remove one first.",
    "details": {
      "kind": "private_key",
      "limit": 25,
      "live": 25,
      "workspace": "01d4cnjjq40hnnh0nwmkg35nv2h"
    },
    "request_id": "req_01j9k3m2p7q4r8s1"
  }
}
```

Example `idempotency_conflict`:

```json
{
  "error": {
    "code": "idempotency_conflict",
    "message": "Idempotency-Key 8f1c2a7e-3b4d-4c5e-9f0a-1b2c3d4e5f60 was already used for a different POST /v1/workspaces/{ws}/access request. Use a new key for a new request.",
    "details": {
      "idempotency_key": "8f1c2a7e-3b4d-4c5e-9f0a-1b2c3d4e5f60"
    },
    "request_id": "req_01j9k3m2p7q4r8s1"
  }
}
```

### 422

`not_supported`: the principal is a private-key principal from another workspace, which acts only where it was created. `invite_not_allowed`: the email is outside the aiven.io domain and the allowlist.

Content type: `application/json`

Schema: `ErrorEnvelope`

Body of every error response

| Field | Type | Required | Description |
|---|---|---|---|
| `error` | Error | yes |  |

Example `not_supported`:

```json
{
  "error": {
    "code": "not_supported",
    "message": "01d4cnjmn4fbb1r5btxb5mypv62 signs in with a private key and belongs to another workspace; such a principal acts only where it was created. Create one here with POST /v1/workspaces/01d4cnjjq40hnnh0nwmkg35nv2h/access and `auth: \"private_key\"`.",
    "details": {
      "field": "principal",
      "in": "body",
      "principal": "01d4cnjmn4fbb1r5btxb5mypv62"
    },
    "request_id": "req_01j9k3m2p7q4r8s1"
  }
}
```

Example `invite_not_allowed`:

```json
{
  "error": {
    "code": "invite_not_allowed",
    "message": "ada@example.com is not allowed to join Nevia. Invite an address on the aiven.io domain or on the allowlist.",
    "details": {
      "workspace": "01d4cnjjq40hnnh0nwmkg35nv2h",
      "email": "ada@example.com"
    },
    "request_id": "req_01j9k3m2p7q4r8s1"
  }
}
```

### 429

`rate_limited`: slow down. Retry after `Retry-After`.

Content type: `application/json`

Schema: `ErrorEnvelope`

Body of every error response

| Field | Type | Required | Description |
|---|---|---|---|
| `error` | Error | yes |  |

Example `rate_limited`:

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Retry after 2 seconds.",
    "details": {
      "retry_after_seconds": 2
    },
    "request_id": "req_01j9k3m2p7q4r8s1"
  }
}
```

### 500

`request_failed`, or the failed action's own code, such as `creation_failed`, `authorization_failed` or `command_execution_failed`. Quote `request_id` when reporting. The diagnostic stays in server logs.

Content type: `application/json`

Schema: `ErrorEnvelope`

Body of every error response

| Field | Type | Required | Description |
|---|---|---|---|
| `error` | Error | yes |  |

Example `request_failed`:

```json
{
  "error": {
    "code": "request_failed",
    "message": "request failed",
    "details": {},
    "request_id": "req_01j9k3m2p7q4r8s1"
  }
}
```

### 503

`creation_failed`: The action is temporarily unavailable, named by its code (`creation_failed`, `wake_failed`, ...). Wait at least `Retry-After` seconds (60 by default). `details.retryable` says whether repeating the request is safe. Keep the original `Idempotency-Key`. Signing in again does not help. `creation_failed`: the deployment has no identity provider for private keys. Nothing was changed. Retry after an operator configures it. `creation_failed`: the identity provider failed while creating the private-key principal. Nothing was kept. Retry the request. `creation_failed`: the access grant could not be written. Nothing was changed. Retry the request.

Content type: `application/json`

Schema: `ErrorEnvelope`

Body of every error response

| Field | Type | Required | Description |
|---|---|---|---|
| `error` | Error | yes |  |

Example `creation_failed: The action is temporarily unavailable, named by its code (\`creation_failed\`, \`wake_failed\`, ...). Wait at least \`Retry-After\` seconds (60 by default). \`details.retryable\` says whether repeating the request is safe. Keep the original \`Idempotency-Key\`. Signing in again does not help`:

```json
{
  "error": {
    "code": "creation_failed",
    "message": "The computer could not be created. Try again later, and quote request_id if it persists.",
    "details": {
      "retryable": true
    },
    "request_id": "req_01j9k3m2p7q4r8s1"
  }
}
```

Example `creation_failed: the deployment has no identity provider for private keys. Nothing was changed. Retry after an operator configures it`:

```json
{
  "error": {
    "code": "creation_failed",
    "message": "Private-key access is not configured on this deployment. Quote request_id when reporting.",
    "details": {
      "reason": "not configured",
      "retryable": false
    },
    "request_id": "req_01j9k3m2p7q4r8s1"
  }
}
```

Example `creation_failed: the identity provider failed while creating the private-key principal. Nothing was kept. Retry the request`:

```json
{
  "error": {
    "code": "creation_failed",
    "message": "access could not be created",
    "details": {
      "retryable": true
    },
    "request_id": "req_01j9k3m2p7q4r8s1"
  }
}
```

Example `creation_failed: the access grant could not be written. Nothing was changed. Retry the request`:

```json
{
  "error": {
    "code": "creation_failed",
    "message": "access could not be created",
    "details": {
      "retryable": true
    },
    "request_id": "req_01j9k3m2p7q4r8s1"
  }
}
```
