# Create project key

Create a new project API key. The raw secret can be replayed with the same intent for 60 seconds from successful creation, subject to current access and key state. Retries do not extend this window.

Endpoint: POST /workspaces/{workspace_id}/projects/{project_id}/keys
Version: 1.3.0
Security: Auth

## Security:

  - `Auth` (unknown)
    apiKey in header X-Api-Key scopes: mgt:project:api_key:write

## Path parameters:

  - `workspace_id` (string, required)
    Unique identifier (UUID) of the workspace.

  - `project_id` (string, required)
    Unique identifier (UUID) of the project.

## Header parameters:

  - `Idempotency-Key` (string, required)
    Case-sensitive opaque key for one mutation intent. Send exactly one header occurrence containing 1 to 128 visible ASCII bytes (0x21..0x7e). Commas are literal data; do not trim, lowercase or require UUID syntax. Reuse the same key for retries, with current authorization checked on every attempt. Repeated header occurrences are rejected.

## Request body:

  - `application/json` (unknown)
    API key creation payload.

## Request fields (application/json):

  - `name` (string, required)
    Human-readable key name.
    Example: Production key

  - `permissions` (array, required)
    Permissions to grant. Must all belong to the `api:*` scope.

  - `expires_at` (string)
    Optional expiration timestamp. Omit to create a non-expiring key.
    Example: 2025-01-15T10:30:00Z

## Response 201:

  - `201` (unknown)
    Newly created API key with raw secret, replayable by authorized retries for 60 seconds from successful creation.

## Response 201 fields (application/json):

  - `item` (object, required)
    A project-scoped API key. The secret is returned in the creation response, including authorized retries within its replay window.
    Example: {"id":"0193d4a1-9f04-7e31-c5d6-7e8f90123456","name":"Backend service key","key_preview":"a1B2c3","workspace_id":"550e8400-e29b-41d4-a716-446655440000","project_id":"0193d4a1-7e02-7d29-8d8a-3b0e5a7c8f1…

  - `item.id` (string, required)
    Unique identifier (UUID).
    Example: 550e8400-e29b-41d4-a716-446655440000

  - `item.name` (string, required)
    Human-readable key name.
    Example: Production key

  - `item.key_preview` (string, required)
    First 6 characters of the random body, i.e. the 6 characters immediately after the `vilna_{type}_` prefix. For raw key `vilna_api_a1B2c3D4e5F6g7H8i9J0k1L2m3N4o5` the preview is `a1B2c3`. Safe to display in UIs for identifying a key without exposing the secret.
    Example: a1B2c3

  - `item.permissions` (array, required)
    Permissions granted to this key.

  - `item.created_at` (string, required)
    Timestamp when the resource was created. RFC 3339 / ISO 8601, UTC.
    Example: 2024-01-15T10:30:00Z

  - `item.expires_at` (string)
    Timestamp when the resource expires. RFC 3339 / ISO 8601, UTC. Absent if the resource never expires.
    Example: 2025-01-15T10:30:00Z

  - `item.revoked_at` (string)
    Timestamp when the resource was revoked. RFC 3339 / ISO 8601, UTC. Absent if the resource is active.
    Example: 2025-06-15T12:00:00Z

  - `item.last_used_at` (string)
    Timestamp of the most recent successful authentication with this key. RFC 3339 / ISO 8601, UTC. Absent until the key has been used at least once.
    Example: 2025-06-15T12:00:00Z

  - `raw_key` (string, required)
    The full secret key, 40 characters: `vilna_{type}_{body}` where `{type}`
is `api`, `mgt`, or `rpc` and `{body}` is 30 base62 characters.
Store it securely. The secret is returned in the creation response and can
be replayed by authorized retries for 60 seconds from successful creation,
subject to current access and key state. Retries do not extend this window,
and revoked or deleted keys are never restored. If the secret is lost after
the replay window, revoke the key and create another.
    Example: vilna_api_a1B2c3D4e5F6g7H8i9J0k1L2m3N4o5

## Response 400:

  - `400` (unknown)
    Bad request (e.g., missing or invalid fields). Invalid text may be reported as shared.text_invalid.

## Response 400 fields (application/problem+json):

  - `type` (string, required)
    A URI that identifies the error type.
    Example: https://docs.vilna.io/apis/errors#internal-error

  - `title` (string, required)
    A short summary of the error type.
Use `detail` for information specific to this occurrence.
    Example: Bad Request

  - `status` (integer, required)
    The HTTP status code for this error.
Matches the status code of the HTTP response.
    Example: 400

  - `detail` (string)
    A human-readable explanation of what went wrong in this specific case.
May be localized.
    Example: The address is already assigned to another invoice

  - `instance` (string)
    A URI that identifies this specific error occurrence.
Include this value when contacting support.
    Example: /traces/0123456789abcdef0123456789abcdef

  - `code` (string, required)
    Stable machine-readable error code for programmatic error handling. Unlike the HTTP `status` or free-form `detail`, this code is guaranteed not to change between versions for a given error condition, so it is safe to branch on in client code. Defaults to `unspecified` when the server has not assigned a specific code.
    Example: blockchain.not_found

  - `fields` (array)
    List of invalid fields in the request.

  - `fields.name` (string, required)
    The name of the invalid field.
    Example: max_len

  - `fields.reason` (string, required)
    Why this field is invalid.
    Example: 32

## Response 401:

  - `401` (unknown)
    Missing or invalid authentication credentials.

## Response 401 fields (application/problem+json):

  - `type` (string, required)
    A URI that identifies the error type.
    Example: https://docs.vilna.io/apis/errors#internal-error

  - `title` (string, required)
    A short summary of the error type.
Use `detail` for information specific to this occurrence.
    Example: Bad Request

  - `status` (integer, required)
    The HTTP status code for this error.
Matches the status code of the HTTP response.
    Example: 400

  - `detail` (string)
    A human-readable explanation of what went wrong in this specific case.
May be localized.
    Example: The address is already assigned to another invoice

  - `instance` (string)
    A URI that identifies this specific error occurrence.
Include this value when contacting support.
    Example: /traces/0123456789abcdef0123456789abcdef

  - `code` (string, required)
    Stable machine-readable error code for programmatic error handling. Unlike the HTTP `status` or free-form `detail`, this code is guaranteed not to change between versions for a given error condition, so it is safe to branch on in client code. Defaults to `unspecified` when the server has not assigned a specific code.
    Example: blockchain.not_found

## Response 403:

  - `403` (unknown)
    Insufficient permissions.

## Response 403 fields (application/problem+json):

  - `type` (string, required)
    A URI that identifies the error type.
    Example: https://docs.vilna.io/apis/errors#internal-error

  - `title` (string, required)
    A short summary of the error type.
Use `detail` for information specific to this occurrence.
    Example: Bad Request

  - `status` (integer, required)
    The HTTP status code for this error.
Matches the status code of the HTTP response.
    Example: 400

  - `detail` (string)
    A human-readable explanation of what went wrong in this specific case.
May be localized.
    Example: The address is already assigned to another invoice

  - `instance` (string)
    A URI that identifies this specific error occurrence.
Include this value when contacting support.
    Example: /traces/0123456789abcdef0123456789abcdef

  - `code` (string, required)
    Stable machine-readable error code for programmatic error handling. Unlike the HTTP `status` or free-form `detail`, this code is guaranteed not to change between versions for a given error condition, so it is safe to branch on in client code. Defaults to `unspecified` when the server has not assigned a specific code.
    Example: blockchain.not_found

## Response 404:

  - `404` (unknown)
    Requested resource does not exist.

## Response 404 fields (application/problem+json):

  - `type` (string, required)
    A URI that identifies the error type.
    Example: https://docs.vilna.io/apis/errors#internal-error

  - `title` (string, required)
    A short summary of the error type.
Use `detail` for information specific to this occurrence.
    Example: Bad Request

  - `status` (integer, required)
    The HTTP status code for this error.
Matches the status code of the HTTP response.
    Example: 400

  - `detail` (string)
    A human-readable explanation of what went wrong in this specific case.
May be localized.
    Example: The address is already assigned to another invoice

  - `instance` (string)
    A URI that identifies this specific error occurrence.
Include this value when contacting support.
    Example: /traces/0123456789abcdef0123456789abcdef

  - `code` (string, required)
    Stable machine-readable error code for programmatic error handling. Unlike the HTTP `status` or free-form `detail`, this code is guaranteed not to change between versions for a given error condition, so it is safe to branch on in client code. Defaults to `unspecified` when the server has not assigned a specific code.
    Example: blockchain.not_found

## Response 409:

  - `409` (unknown)
    Request conflicts with the current resource state.

## Response 409 fields (application/problem+json):

  - `type` (string, required)
    A URI that identifies the error type.
    Example: https://docs.vilna.io/apis/errors#internal-error

  - `title` (string, required)
    A short summary of the error type.
Use `detail` for information specific to this occurrence.
    Example: Bad Request

  - `status` (integer, required)
    The HTTP status code for this error.
Matches the status code of the HTTP response.
    Example: 400

  - `detail` (string)
    A human-readable explanation of what went wrong in this specific case.
May be localized.
    Example: The address is already assigned to another invoice

  - `instance` (string)
    A URI that identifies this specific error occurrence.
Include this value when contacting support.
    Example: /traces/0123456789abcdef0123456789abcdef

  - `code` (string, required)
    Stable machine-readable error code for programmatic error handling. Unlike the HTTP `status` or free-form `detail`, this code is guaranteed not to change between versions for a given error condition, so it is safe to branch on in client code. Defaults to `unspecified` when the server has not assigned a specific code.
    Example: blockchain.not_found

## Response 422:

  - `422` (unknown)
    Request preconditions were not met.

## Response 422 fields (application/problem+json):

  - `type` (string, required)
    A URI that identifies the error type.
    Example: https://docs.vilna.io/apis/errors#internal-error

  - `title` (string, required)
    A short summary of the error type.
Use `detail` for information specific to this occurrence.
    Example: Bad Request

  - `status` (integer, required)
    The HTTP status code for this error.
Matches the status code of the HTTP response.
    Example: 400

  - `detail` (string)
    A human-readable explanation of what went wrong in this specific case.
May be localized.
    Example: The address is already assigned to another invoice

  - `instance` (string)
    A URI that identifies this specific error occurrence.
Include this value when contacting support.
    Example: /traces/0123456789abcdef0123456789abcdef

  - `code` (string, required)
    Stable machine-readable error code for programmatic error handling. Unlike the HTTP `status` or free-form `detail`, this code is guaranteed not to change between versions for a given error condition, so it is safe to branch on in client code. Defaults to `unspecified` when the server has not assigned a specific code.
    Example: blockchain.not_found

  - `fields` (array)
    List of fields that failed precondition validation.

  - `fields.name` (string, required)
    The name of the field that failed validation.
    Example: status

  - `fields.reason` (string, required)
    Why the precondition failed for this field.
    Example: Cannot transition from archived to active state

## Response 429:

  - `429` (unknown)
    Resource quota exceeded. The response includes the effective limit for this scope. The applicable code and limit depend on the operation; examples illustrate different limits.

## Response 429 fields (application/problem+json):

  - `type` (string, required)
    A URI that identifies the error type.
    Example: https://docs.vilna.io/apis/errors#internal-error

  - `title` (string, required)
    A short summary of the error type.
Use `detail` for information specific to this occurrence.
    Example: Bad Request

  - `status` (integer, required)
    The HTTP status code for this error.
Matches the status code of the HTTP response.
    Example: 400

  - `detail` (string)
    A human-readable explanation of what went wrong in this specific case.
May be localized.
    Example: The address is already assigned to another invoice

  - `instance` (string)
    A URI that identifies this specific error occurrence.
Include this value when contacting support.
    Example: /traces/0123456789abcdef0123456789abcdef

  - `code` (string, required)
    Stable machine-readable code identifying the exhausted resource quota. The code is always
supplied by the service; clients branch on it instead of the human-readable detail.
    Example: address.quota_exceeded

  - `max` (integer, required)
    Effective maximum for this resource in the caller's scope. Zero prohibits new admissions;
it does not disable resources already held. This is the configured limit, not current usage.
    Example: 100

## Response default:

  - `default` (unknown)
    Unexpected error.

## Response default fields (application/problem+json):

  - `type` (string, required)
    A URI that identifies the error type.
    Example: https://docs.vilna.io/apis/errors#internal-error

  - `title` (string, required)
    A short summary of the error type.
Use `detail` for information specific to this occurrence.
    Example: Bad Request

  - `status` (integer, required)
    The HTTP status code for this error.
Matches the status code of the HTTP response.
    Example: 400

  - `detail` (string)
    A human-readable explanation of what went wrong in this specific case.
May be localized.
    Example: The address is already assigned to another invoice

  - `instance` (string)
    A URI that identifies this specific error occurrence.
Include this value when contacting support.
    Example: /traces/0123456789abcdef0123456789abcdef

  - `code` (string, required)
    Stable machine-readable error code for programmatic error handling. Unlike the HTTP `status` or free-form `detail`, this code is guaranteed not to change between versions for a given error condition, so it is safe to branch on in client code. Defaults to `unspecified` when the server has not assigned a specific code.
    Example: blockchain.not_found

## Response 400 examples:

  - `The create-project request body contains malformed JSON` (unknown)

## Response 401 examples:

  - `Authentication credentials are missing or invalid` (unknown)

## Response 403 examples:

  - `The management key lacks the required permission` (unknown)

## Response 404 examples:

  - `The project does not exist` (unknown)

  - `The workspace member does not exist` (unknown)

## Response 409 examples:

  - `The user is already a member of the workspace` (unknown)

## Response 429 examples:

  - `The workspace has reached its project quota` (unknown)

  - `Members and active invitation reservations occupy all workspace seats` (unknown)

  - `The scope has reached its quota for the requested key type` (unknown)

