Container Copy & Sync
Section titled “Container Copy & Sync”Use these endpoints to duplicate a container into another project or server (copy) and to pull a fresh incremental snapshot from a container’s source (sync). Both operations run asynchronously: the new or updated container starts automatically when the operation succeeds, and the response includes an operation_id you can correlate with the container.operation.completed or container.operation.failed event that reports the outcome. If a copy fails, the new container’s status becomes failed (it cannot be started); delete it and retry.
Copy a container
Section titled “Copy a container”POST /api/v1/containers/{id}/copy
Creates a copy of an existing container. The copy operation runs asynchronously and the new container will start automatically upon successful completion. If the copy fails, the new container’s status becomes failed (it cannot be started); delete it and retry the copy.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Unique identifier of the source container to copy |
Request body
Section titled “Request body”| Field | Type | Required | Default | Description |
|---|---|---|---|---|
target_project_id | string | Yes | ID of the project where the copy will be created (24-character hex) | |
target_server_id | string | No | ID of the server where the copy will be created (24-character hex; defaults to source server) | |
name | string | No | Name for the copied container (auto-generated if not provided); 3-100 chars, ^[a-zA-Z0-9-_]+$ | |
ssh_public_key | string | No | SSH public key for the copied container (must be unique, not inherited from source) | |
source_snapshot | string | No | Specific snapshot to copy from (copies latest state if not provided). An auth token needs containers.features.snapshots to use it | |
copy_firewall_rules | boolean | No | false | Whether to copy firewall rules from source container to target container |
copy_network_rules | boolean | No | false | Whether to copy network rules/settings from source container to target container |
kvm | boolean | No | Grant the COPY /dev/kvm passthrough (run full VMs). The copy NEVER inherits the source’s KVM grant, so this decides KVM for the copy independently, on the TARGET server. Available on rented or dedicated (bare-metal) targets ONLY (never free tier); rejected (403) otherwise. Defaults to false | |
dev_kvm | boolean | No | Accepted alias of kvm on input (kvm wins if both are sent and they must agree) |
Example request
Section titled “Example request”curl -X POST https://api.hoody.com/api/v1/containers/890abcdef12345678901cdef/copy \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "target_project_id": "67e89abc123def456789abcd", "name": "my-container-copy", "ssh_public_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJxn0a5M5c7Rd0PY6E8Q3Z8K0L3M4N5O6P7Q8R9S0T user@host", "copy_firewall_rules": true, "copy_network_rules": true, "kvm": true }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
await client.api.containers.copy('890abcdef12345678901cdef', { target_project_id: '67e89abc123def456789abcd', name: 'my-container-copy', ssh_public_key: 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJxn0a5M5c7Rd0PY6E8Q3Z8K0L3M4N5O6P7Q8R9S0T user@host', copy_firewall_rules: true, copy_network_rules: true, kvm: true,});{ "statusCode": 201, "message": "Container copy initiated successfully", "data": { "id": "a1b2c3d4e5f6789012345678", "name": "my-container-copy", "status": "copying", "source_container_id": "890abcdef12345678901cdef", "project_id": "67e89abc123def456789abcd", "project_alias": "my-project", "server_id": "6501a2b3c4d5e6f7a8b9c0d2", "server_name": "node-example-1", "subserver_name": "free", "server": { "name": "node-example-1", "country": "SG", "country_name": "Singapore", "city": "Singapore", "region": "Asia Pacific", "datacenter": "SIN-DC1", "is_free": true, "paid_subserver": false, "specs": { "cpu_cores": null, "ram_gb": null, "disk_gb": 50, "shared_compute": true }, "expires_at": null, "kvm_supported": true, "kvm_nested": false, "kvm_checked_at": "2026-10-07T12:00:00.000Z" }, "ssh_hostname": "67e89abc123def456789abcd-a1b2c3d4e5f6789012345678-ssh.node-example-1.containers.hoody.com", "env_sync": { "pending": false, "revision": 0, "applied_revision": 0, "verified_at": null, "attempted_at": null, "last_error": null, "exhausted": false }, "color": "#3B82F6", "container_image": "debian/13", "ai": false, "hoody_kit": false, "dev_kit": false, "kvm": true, "autostart": true, "ramdisk_scope": "container", "ramdisk": false, "is_default": false, "container_image_id": "6501a2b3c4d5e6f7a8b9c0d1", "environment_vars": {}, "volumes": {}, "ssh_public_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJxn0a5M5c7Rd0PY6E8Q3Z8K0L3M4N5O6P7Q8R9S0T user@host", "ssh_key_fingerprint": "SHA256:wBF5vmw/82L9++9kd8PEmcEkErv5hBWXC7aJsuXAyh8", "ssh_key_installed_at": "2026-10-07T12:00:00.000Z", "comment": null, "copy_firewall_rules": true, "copy_network_rules": true, "operation_id": "9f86d081884c7d659a2feaa0c55ad015", "created_at": "2026-10-07T12:00:00.000Z", "updated_at": "2026-10-07T12:00:00.000Z", "realm_ids": [] }}Responses
Section titled “Responses”Container copy initiated successfully.
{ "statusCode": 201, "message": "Container copy initiated successfully", "data": { "id": "a1b2c3d4e5f6789012345678", "name": "my-container-copy", "status": "copying", "source_container_id": "890abcdef12345678901cdef", "project_id": "67e89abc123def456789abcd", "project_alias": "my-project", "server_id": "6501a2b3c4d5e6f7a8b9c0d2", "server_name": "node-example-1", "subserver_name": "free", "server": { "name": "node-example-1", "country": "SG", "country_name": "Singapore", "city": "Singapore", "region": "Asia Pacific", "datacenter": "SIN-DC1", "is_free": true, "paid_subserver": false, "specs": { "cpu_cores": null, "ram_gb": null, "disk_gb": 50, "shared_compute": true }, "expires_at": null, "kvm_supported": true, "kvm_nested": false, "kvm_checked_at": "2026-10-07T12:00:00.000Z" }, "ssh_hostname": "67e89abc123def456789abcd-a1b2c3d4e5f6789012345678-ssh.node-example-1.containers.hoody.com", "env_sync": { "pending": false, "revision": 0, "applied_revision": 0, "verified_at": null, "attempted_at": null, "last_error": null, "exhausted": false }, "color": "#3B82F6", "container_image": "debian/13", "ai": false, "hoody_kit": false, "dev_kit": false, "kvm": true, "autostart": true, "ramdisk_scope": "container", "ramdisk": false, "is_default": false, "container_image_id": "6501a2b3c4d5e6f7a8b9c0d1", "environment_vars": {}, "volumes": {}, "ssh_public_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJxn0a5M5c7Rd0PY6E8Q3Z8K0L3M4N5O6P7Q8R9S0T user@host", "ssh_key_fingerprint": "SHA256:wBF5vmw/82L9++9kd8PEmcEkErv5hBWXC7aJsuXAyh8", "ssh_key_installed_at": "2026-10-07T12:00:00.000Z", "comment": null, "copy_firewall_rules": true, "copy_network_rules": true, "operation_id": "9f86d081884c7d659a2feaa0c55ad015", "created_at": "2026-10-07T12:00:00.000Z", "updated_at": "2026-10-07T12:00:00.000Z", "realm_ids": [] }}{ "statusCode": 400, "error": "Bad Request", "message": "Validation failed"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
VALIDATION_ERROR | Invalid input parameters | One or more request parameters failed validation | Check the error message for specific field requirements and correct your input |
INVALID_ID_FORMAT | Invalid ID format | The provided ID must be a 24-character hexadecimal string | Ensure the ID is exactly 24 characters long and contains only hexadecimal characters (0-9, a-f) |
INVALID_CONTAINER_NAME | Invalid container name | Container name must be 3-100 characters, alphanumeric with hyphens and underscores | Use a valid name between 3 and 100 characters containing only a-z, A-Z, 0-9, -, and _ |
SERVER_CONTAINER_LIMIT | Server container limit reached | The target server is at its maximum number of live containers (explicit max_containers, or the free-tier default) | Delete an existing container on this server, or create the container on a different server |
PROJECT_CONTAINER_LIMIT | Project container limit reached | The target project is at its owner-configured container quota (the project’s max_containers setting) | Delete an existing container in this project, raise the project quota, or use a different project |
{ "statusCode": 401, "error": "MISSING_TOKEN", "message": "Authentication token required"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MISSING_TOKEN | Authentication token missing | No authentication token was provided in the request | Include a valid JWT token in the Authorization header as Bearer <token> |
INVALID_TOKEN | Invalid authentication token | The provided authentication token is malformed or invalid | Obtain a new token by logging in again or using a valid auth token |
TOKEN_EXPIRED | Authentication token expired | The provided authentication token has expired | Obtain a new token by logging in again or refreshing your session |
AUTH_TOKEN_LINEAGE_REVOKED | Parent auth token no longer valid | The auth token was created by another auth token, and that token (or one above it) has been revoked, disabled or has expired, so this one no longer authenticates | Create a new auth token from a valid one, or sign in again |
INVALID_CREDENTIALS | Invalid credentials | The provided username/email or password is incorrect | Verify your credentials and try again, or use the password reset feature |
EMAIL_NOT_VERIFIED | Email not verified | Returned by the login endpoint when the password is correct but the account’s email address has not been verified yet | Complete email verification by clicking the link sent to your inbox, or call /auth/resend-verification, or complete a password reset which also implicitly verifies the email |
INVALID_OTP_CODE | Invalid OTP code | The provided 2FA code is incorrect or has expired | Generate a new code from your authenticator app and try again |
{ "statusCode": 403, "error": "INSUFFICIENT_PERMISSIONS", "message": "Insufficient permissions"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INSUFFICIENT_PERMISSIONS | Insufficient permissions | You do not have the required permissions to perform this action | Ask the resource owner to grant you access |
ACCOUNT_BANNED | Account banned | Your account has been banned and cannot access this resource | Contact support for information about your account status |
{ "statusCode": 404, "error": "Not Found", "message": "Source container not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
SOURCE_CONTAINER_NOT_FOUND | Source container not found | The source container specified for a copy or sync operation does not exist | Verify the source container ID is correct |
RESOURCE_NOT_FOUND | Resource not found | The requested resource does not exist or has been deleted | Verify the resource ID and ensure it exists |
{ "statusCode": 409, "error": "Conflict", "message": "Container name already in use within the project"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
CONTAINER_NAME_IN_USE | Container name already in use | A container with this name already exists in the project | Choose a different name for your container |
SSH_PUBLIC_KEY_IN_USE | SSH public key already in use | SSH public keys must be unique per container; a single public key cannot be assigned to multiple containers | Generate a new SSH key pair for this container, or remove the key from the other container before reusing it |
OPERATION_STATE_CONFLICT | Container state conflict | The operation cannot be performed because the container is not in the correct state | Check the container’s current status (for example, a container must be stopped to be started) |
{ "statusCode": 422, "error": "Validation Error", "message": "Validation failed: /name must NOT have fewer than 3 characters"}The copy failed. With the code CONTAINER_COPY_DISPATCH_FAILED the copy was never handed to the server and nothing was created. Any other 500 is ambiguous: the copy may have started, so check the target container before retrying.
{ "statusCode": 500, "error": "CONTAINER_COPY_DISPATCH_FAILED", "message": "Container copy could not be started on its host"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
CONTAINER_COPY_DISPATCH_FAILED | Container copy could not be started | The copy request could not be sent to the server, so no copy was created; its name, SSH key and quota slot are free again | Retry the request; if it keeps failing, try another target server or contact support |
The server could not be reached before anything was sent. Nothing was created; retry after the Retry-After header.
{ "statusCode": 503, "error": "CONTAINER_HOST_UNAVAILABLE", "message": "The server for this container could not be reached right now. Nothing was created. Retry shortly."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
CONTAINER_HOST_UNAVAILABLE | Container server temporarily unavailable | The server for this container could not be reached for a moment, so the create or copy request was never sent and nothing was created; its name, SSH key and quota slot are free again | Retry after the number of seconds in the Retry-After header |
Sync a copied container with its source
Section titled “Sync a copied container with its source”POST /api/v1/containers/{id}/sync
Performs an incremental sync from the source container to this container. Only works for containers that were created via copy operation. The sync runs asynchronously.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Unique identifier of the container to sync (must have been created via copy) |
This endpoint takes no request body.
Example request
Section titled “Example request”curl -X POST https://api.hoody.com/api/v1/containers/a1b2c3d4e5f6789012345678/sync \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
await client.api.containers.sync('a1b2c3d4e5f6789012345678');{ "statusCode": 200, "message": "Container sync initiated successfully", "data": { "container_id": "a1b2c3d4e5f6789012345678", "source_container_id": "890abcdef12345678901cdef", "status": "copying", "operation_id": "9f86d081884c7d659a2feaa0c55ad015" }}Responses
Section titled “Responses”Container sync initiated successfully.
{ "statusCode": 200, "message": "Container sync initiated successfully", "data": { "container_id": "a1b2c3d4e5f6789012345678", "source_container_id": "890abcdef12345678901cdef", "status": "copying", "operation_id": "9f86d081884c7d659a2feaa0c55ad015" }}{ "statusCode": 400, "error": "Bad Request", "message": "Invalid ID format"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_ID_FORMAT | Invalid ID format | The provided ID must be a 24-character hexadecimal string | Ensure the ID is exactly 24 characters long and contains only hexadecimal characters (0-9, a-f) |
SERVER_CONTAINER_LIMIT | Server container limit reached | The target server is at its maximum number of live containers (explicit max_containers, or the free-tier default) | Delete an existing container on this server, or create the container on a different server |
{ "statusCode": 401, "error": "MISSING_TOKEN", "message": "Authentication token required"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MISSING_TOKEN | Authentication token missing | No authentication token was provided in the request | Include a valid JWT token in the Authorization header as Bearer <token> |
INVALID_TOKEN | Invalid authentication token | The provided authentication token is malformed or invalid | Obtain a new token by logging in again or using a valid auth token |
TOKEN_EXPIRED | Authentication token expired | The provided authentication token has expired | Obtain a new token by logging in again or refreshing your session |
AUTH_TOKEN_LINEAGE_REVOKED | Parent auth token no longer valid | The auth token was created by another auth token, and that token (or one above it) has been revoked, disabled or has expired, so this one no longer authenticates | Create a new auth token from a valid one, or sign in again |
INVALID_CREDENTIALS | Invalid credentials | The provided username/email or password is incorrect | Verify your credentials and try again, or use the password reset feature |
EMAIL_NOT_VERIFIED | Email not verified | Returned by the login endpoint when the password is correct but the account’s email address has not been verified yet | Complete email verification by clicking the link sent to your inbox, or call /auth/resend-verification, or complete a password reset which also implicitly verifies the email |
INVALID_OTP_CODE | Invalid OTP code | The provided 2FA code is incorrect or has expired | Generate a new code from your authenticator app and try again |
{ "statusCode": 403, "error": "INSUFFICIENT_PERMISSIONS", "message": "Insufficient permissions"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INSUFFICIENT_PERMISSIONS | Insufficient permissions | You do not have the required permissions to perform this action | Ask the resource owner to grant you access |
ACCOUNT_BANNED | Account banned | Your account has been banned and cannot access this resource | Contact support for information about your account status |
{ "statusCode": 404, "error": "Not Found", "message": "Container not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
CONTAINER_NOT_FOUND | Container not found | The requested container does not exist or you do not have permission to access it | Verify the container ID is correct and that you have access to the project it belongs to |
{ "statusCode": 409, "error": "Conflict", "message": "Container was not created from a copy, sync is not possible."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
CONTAINER_NOT_COPIED | Container not a copy | The sync operation can only be performed on a container that was created by copying another | This operation is only valid for containers with a source_container_id |
OPERATION_STATE_CONFLICT | Container state conflict | The operation cannot be performed because the container is not in the correct state | Check the container’s current status (for example, a container must be stopped to be started) |
{ "statusCode": 422, "error": "Validation Error", "message": "Validation failed: request body is not valid JSON"}