---
title: "Create a Cube"
method: POST
path: "/spaces/{spaceId}/cubes"
tags: ["Cubes"]
---

# Create a Cube

`POST /spaces/{spaceId}/cubes`

## Request

- Base URL: `https://krova.cloud/api/v1`
- URL: `https://krova.cloud/api/v1/spaces/{spaceId}/cubes`
- Auth: API key in header `X-API-KEY`

## Path parameters

- `spaceId` string, required

## Headers

- `Idempotency-Key` string

## Request body

- object
  - `name` string, required
  - `image` string, required
  - `resources` object, required
    - `vcpu` number, required
    - `ramGb` number, required
    - `diskGb` number, required
  - `sshPublicKey` string, required: SSH public key written to the Cube login user's authorized_keys at boot (~/.ssh/authorized_keys for the ubuntu or debian user; /root/.ssh/authorized_keys on Cubes created before the default-user change). GET /cubes/{cubeId}/ssh reports the login user for a given Cube. Must start with ssh-ed25519, ssh-rsa, ecdsa-sha2-*, ssh-dss, or sk-*@openssh.com.
  - `region` string: Region slug from /v1/regions (optional).
  - `userData` string: cloud-init script (max 16 KB, optional).
  - `terminationProtection` boolean: Create the Cube with termination protection already on, so it is never briefly deletable between create and a follow-up PATCH. Optional; anything other than a boolean is rejected with 400.

## Response `201`

Cube created

- object
  - `cube` Cube
    - `id` string, required
    - `name` string, required
    - `state` 'pending' | 'booting' | 'running' | 'stopped' | 'stopping' | 'resizing' | 'error' | 'deleted', required
    - `publicIpv4` string, nullable, required
    - `resources` object, required
      - `vcpu` integer, required
      - `ramGb` number, required
      - `diskGb` integer, required
    - `image` string, required
    - `sshUser` string, required
    - `costPerHour` number, required
    - `serverDomain` string, nullable: The hostname of the server currently hosting this Cube (e.g. `mango.krova.cloud`), shared by every Cube on that server. This is NOT the Cube's own SSH target — use `host` from GET .../ssh for that, which is the Cube's own stable hostname and survives migration to another server. Null when the Cube has no server assigned. Only present on the single-Cube GET; list and create responses omit it.
    - `terminationProtection` boolean, required: Per-cube opt-in flag. When true, customer-initiated destroy paths return 409 with `error.code = "termination_protected"`. Power-off, wake, restart, restore, network-reset, and snapshot ops are NOT termination and remain allowed. Default false.
    - `terminationProtectionChangedAt` string, date-time, nullable, required: ISO8601 timestamp of the most recent toggle of `terminationProtection`. Null when the flag has never been turned on.
    - `terminationProtectionChangedBy` string, nullable, required: Identifies the actor that last toggled the flag: `"user:<id>"` for a customer, `"admin:<id>"` for an operator, `"system:<job-name>"` for a worker-driven write. Null when the flag has never been turned on.
    - `createdAt` string, date-time, required
    - `updatedAt` string, date-time, required

## Other responses

- `400`: Bad request
- `401`: Missing or invalid API key
- `403`: Authenticated but lacks the required permission
- `429`: Rate limit exceeded (10 mutations / 60s)

---

[API](https://skmtc.dev/krova/apis/krova-cloud-api.md) · [All operations](https://skmtc.dev/krova/apis/krova-cloud-api/llms.txt) · [OpenAPI document](https://skmtc.dev/krova/apis/krova-cloud-api/revisions/46bb68281ce1?raw)
