Container snapshots
Section titled “Container snapshots”Container snapshots capture the full filesystem state of a container at a point in time. They let you back up a container before a risky change, restore a container to a known good state, or label recurring checkpoints with a friendly alias. The endpoints on this page cover listing, creating, restoring, aliasing, and deleting snapshots on a single container.
List snapshots
Section titled “List snapshots”GET /api/v1/containers/{id}/snapshots
Section titled “GET /api/v1/containers/{id}/snapshots”Return every snapshot that currently exists on a container, along with the container’s snapshot cap and the current count.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Unique identifier of the container to retrieve snapshots for |
Request
Section titled “Request”curl -X GET "https://api.hoody.com/api/v1/containers/{id}/snapshots" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
for await (const item of client.api.snapshots.listIterator(containerId)) { console.log(item);}Response
Section titled “Response”{ "statusCode": 200, "message": "Snapshots retrieved successfully", "data": { "container_id": "507f1f77bcf86cd799439011", "project_id": "507f1f77bcf86cd799439033", "snapshot_count": 2, "max_snapshots": 10, "snapshots": [ { "name": "snap-20250115-103000", "alias": "backup-2025-01-15", "created_at": "2025-01-15T10:30:00.000Z", "last_used_at": "2025-01-15T14:00:00.000Z", "expires_at": "2025-02-15T10:30:00.000Z", "stateful": false, "size": 2147483648 }, { "name": "snap-20250110-080000", "alias": "weekly-backup", "created_at": "2025-01-10T08:00:00.000Z", "last_used_at": null, "expires_at": null, "stateful": false, "size": 1073741824 } ] }}{ "statusCode": 400, "error": "Bad Request", "message": "Invalid container id"}{ "statusCode": 401, "error": "Unauthorized", "message": "Authentication required"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
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 |
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. It is only returned after the password has been checked. The response carries data.email so the client can offer a ‘resend verification email’ action without asking again. | Complete email verification by clicking the link sent to your inbox, or call /auth/resend-verification to receive a new link, 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": "Forbidden", "message": "You do not have permission to access this container"}| 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"}Validation Error: the request does not match the operation schema, or its JSON body cannot be parsed. message names each failing check, and data, when present, lists them one entry per failing field.
{ "statusCode": 422, "error": "Validation Error", "message": "Validation failed: /name must NOT have fewer than 3 characters"}{ "statusCode": 500, "error": "Internal Server Error", "message": "An unexpected error occurred"}Create a snapshot
Section titled “Create a snapshot”POST /api/v1/containers/{id}/snapshots
Section titled “POST /api/v1/containers/{id}/snapshots”Capture a new snapshot of a container. Optional fields let you attach a human-friendly alias and an expiry window in days.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Unique identifier of the container to create snapshot for |
Request body
Section titled “Request body”| Name | Type | Required | Description |
|---|---|---|---|
alias | string | No | Optional user-friendly alias for the snapshot. Maximum 100 characters. |
expiry | integer | No | Expiry in days, between 1 and 3650. Values outside this range are rejected before the snapshot is created. |
Request
Section titled “Request”curl -X POST "https://api.hoody.com/api/v1/containers/{id}/snapshots" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "alias": "pre-deployment-backup", "expiry": 30 }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
await client.api.snapshots.create(containerId, { alias: 'pre-deployment-backup', expiry: 30,});Response
Section titled “Response”{ "statusCode": 201, "message": "Snapshot created successfully", "data": { "container_id": "507f1f77bcf86cd799439011", "project_id": "507f1f77bcf86cd799439033", "snapshot": { "name": "snap-20250115-145500", "alias": "pre-deployment-backup", "created_at": "2025-01-15T14:55:00.000Z", "last_used_at": null, "expires_at": "2025-02-14T14:55:00.000Z", "stateful": false, "size": 2147483648 } }}{ "statusCode": 400, "error": "CONTAINER_SNAPSHOT_LIMIT", "message": "This container has reached its snapshot limit.", "data": { "field": "container_snapshot_limit", "container_id": "507f1f77bcf86cd799439011", "server_id": "507f1f77bcf86cd799439022", "current_count": 10, "limit": 10 }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
CONTAINER_SNAPSHOT_LIMIT | Container snapshot limit reached | The container already holds the maximum number of snapshots allowed for its server. Containers on free-tier servers are capped lower than those on paid servers and rentals; the applicable limit is returned as max_snapshots when listing snapshots. | Delete an existing snapshot on this container before creating a new one. |
SNAPSHOT_PRESPAWN_FORBIDDEN | Snapshots not allowed on a prespawn container | A prespawn container is not ready for snapshots yet, so a snapshot cannot be taken now. | Wait until the container is set up for your account, then create snapshots. |
VALIDATION_ERROR | Invalid input parameters | One or more request parameters failed validation. | Check the error message for specific field requirements and correct your input. |
{ "statusCode": 401, "error": "Unauthorized", "message": "Authentication required"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
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 |
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. It is only returned after the password has been checked. The response carries data.email so the client can offer a ‘resend verification email’ action without asking again. | Complete email verification by clicking the link sent to your inbox, or call /auth/resend-verification to receive a new link, 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": "Forbidden", "message": "You do not have permission to snapshot this container"}| 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"}{ "statusCode": 409, "error": "SNAPSHOT_OPERATION_IN_PROGRESS", "message": "Another snapshot operation may be running for this container, or the service is temporarily unavailable. Nothing was created; retry shortly."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
SNAPSHOT_OPERATION_IN_PROGRESS | Snapshot creation already running | Another snapshot creation is already in flight for this container. Concurrent creations are rejected so the snapshot limit cannot be exceeded. | Retry after a few seconds (see the Retry-After header). |
container_finalizing | Container is still being set up | The container is still being set up and refuses changes until that completes. | Retry in a few seconds. |
RESOURCE_ALREADY_EXISTS | Resource already exists | A resource with this identifier already exists. | Use a different identifier or update the existing resource instead. |
Validation Error: the request does not match the operation schema, or its JSON body cannot be parsed. message names each failing check, and data, when present, lists them one entry per failing field.
{ "statusCode": 422, "error": "Validation Error", "message": "Validation failed: /name must NOT have fewer than 3 characters"}{ "statusCode": 500, "error": "Internal Server Error", "message": "An unexpected error occurred"}{ "statusCode": 503, "error": "SNAPSHOT_INVENTORY_UNAVAILABLE", "message": "Unable to verify this container's snapshot inventory. No changes were made."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
SNAPSHOT_INVENTORY_UNAVAILABLE | Snapshot inventory could not be verified | The container’s live snapshot list could not be read, so the snapshot limit could not be checked. The request is refused rather than creating a snapshot that might exceed it. | Retry shortly. If it persists, contact support. |
Restore from a snapshot
Section titled “Restore from a snapshot”PUT /api/v1/containers/{id}/snapshots/{name}
Section titled “PUT /api/v1/containers/{id}/snapshots/{name}”Replace the container’s current filesystem with the contents of a named snapshot. The server also accepts PATCH for this endpoint with identical behaviour; the canonical verb is PUT to match the SDK.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Unique identifier of the container to restore |
name | path | string | Yes | The snapshot’s canonical name as returned by the list endpoint. For a snapshot created with an alias this is the sanitized alias (letters, digits, underscore, hyphen; leading hyphens stripped); without an alias, or when sanitization leaves nothing, a timestamped snap-YYYYMMDD-HHMMSS. |
Request
Section titled “Request”curl -X PUT "https://api.hoody.com/api/v1/containers/{id}/snapshots/{name}" \ -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.snapshots.restore(containerId, snapshotName);Response
Section titled “Response”{ "statusCode": 200, "message": "Container restored from snapshot successfully", "data": { "success": true, "message": "Container restored from snapshot successfully", "snapshot": { "name": "snap-20250115-103000", "alias": "backup-2025-01-15", "created_at": "2025-01-15T10:30:00.000Z", "last_used_at": "2025-01-15T15:00:00.000Z", "expires_at": "2025-02-15T10:30:00.000Z", "stateful": false, "size": 2147483648 } }}{ "statusCode": 400, "error": "Bad Request", "message": "Invalid snapshot name"}{ "statusCode": 401, "error": "Unauthorized", "message": "Authentication required"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
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 |
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. It is only returned after the password has been checked. The response carries data.email so the client can offer a ‘resend verification email’ action without asking again. | Complete email verification by clicking the link sent to your inbox, or call /auth/resend-verification to receive a new link, 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": "Forbidden", "message": "You do not have permission to restore this container"}| 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": "Snapshot not found"}Validation Error: the request does not match the operation schema, or its JSON body cannot be parsed. message names each failing check, and data, when present, lists them one entry per failing field.
{ "statusCode": 422, "error": "Validation Error", "message": "Validation failed: /name must NOT have fewer than 3 characters"}{ "statusCode": 500, "error": "Internal Server Error", "message": "An unexpected error occurred"}Update a snapshot alias
Section titled “Update a snapshot alias”PUT /api/v1/containers/{id}/snapshots/{name}/alias
Section titled “PUT /api/v1/containers/{id}/snapshots/{name}/alias”Set a new alias on an existing snapshot, or clear the alias by passing null. The server also accepts PATCH for this endpoint with identical behaviour; the canonical verb is PUT to match the SDK.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Unique identifier of the container |
name | path | string | Yes | The snapshot’s canonical name as returned by the list endpoint. For a snapshot created with an alias this is the sanitized alias (letters, digits, underscore, hyphen; leading hyphens stripped); without an alias, or when sanitization leaves nothing, a timestamped snap-YYYYMMDD-HHMMSS. |
Request body
Section titled “Request body”| Name | Type | Required | Description |
|---|---|---|---|
alias | string | Yes | New alias for the snapshot. Maximum 100 characters. Pass null to remove the existing alias. |
Request
Section titled “Request”curl -X PUT "https://api.hoody.com/api/v1/containers/{id}/snapshots/{name}/alias" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "alias": "production-baseline" }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
await client.api.snapshots.setAlias(containerId, snapshotName, { alias: 'production-baseline',});Response
Section titled “Response”{ "statusCode": 200, "message": "Snapshot alias updated successfully", "data": { "container_id": "507f1f77bcf86cd799439011", "project_id": "507f1f77bcf86cd799439033", "snapshot": { "name": "snap-20250115-103000", "alias": "production-baseline", "created_at": "2025-01-15T10:30:00.000Z", "last_used_at": "2025-01-15T14:00:00.000Z", "expires_at": "2025-02-15T10:30:00.000Z", "stateful": false, "size": 2147483648 } }}{ "statusCode": 400, "error": "Bad Request", "message": "Invalid alias"}{ "statusCode": 401, "error": "Unauthorized", "message": "Authentication required"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
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 |
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. It is only returned after the password has been checked. The response carries data.email so the client can offer a ‘resend verification email’ action without asking again. | Complete email verification by clicking the link sent to your inbox, or call /auth/resend-verification to receive a new link, 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": "Forbidden", "message": "You do not have permission to modify this snapshot"}| 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": "Snapshot not found"}Validation Error: the request does not match the operation schema, or its JSON body cannot be parsed. message names each failing check, and data, when present, lists them one entry per failing field.
{ "statusCode": 422, "error": "Validation Error", "message": "Validation failed: /name must NOT have fewer than 3 characters"}{ "statusCode": 500, "error": "Internal Server Error", "message": "An unexpected error occurred"}{ "statusCode": 503, "error": "SNAPSHOT_INVENTORY_UNAVAILABLE", "message": "Unable to verify this container's snapshot inventory. No changes were made."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
SNAPSHOT_INVENTORY_UNAVAILABLE | Snapshot inventory could not be verified | The container’s live snapshot list could not be read, so the snapshot limit could not be checked. The request is refused rather than creating a snapshot that might exceed it. | Retry shortly. If it persists, contact support. |
Delete a snapshot
Section titled “Delete a snapshot”DELETE /api/v1/containers/{id}/snapshots/{name}
Section titled “DELETE /api/v1/containers/{id}/snapshots/{name}”Permanently remove a snapshot from a container. The container itself is untouched.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Unique identifier of the container |
name | path | string | Yes | The snapshot’s canonical name as returned by the list endpoint. For a snapshot created with an alias this is the sanitized alias (letters, digits, underscore, hyphen; leading hyphens stripped); without an alias, or when sanitization leaves nothing, a timestamped snap-YYYYMMDD-HHMMSS. |
Request
Section titled “Request”curl -X DELETE "https://api.hoody.com/api/v1/containers/{id}/snapshots/{name}" \ -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.snapshots.delete(containerId, snapshotName);Response
Section titled “Response”{ "statusCode": 200, "message": "Snapshot deleted successfully", "data": { "container_id": "507f1f77bcf86cd799439011", "project_id": "507f1f77bcf86cd799439033", "snapshot_name": "snap-20250110-080000" }}{ "statusCode": 400, "error": "Bad Request", "message": "Invalid snapshot name"}{ "statusCode": 401, "error": "Unauthorized", "message": "Authentication required"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
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 |
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. It is only returned after the password has been checked. The response carries data.email so the client can offer a ‘resend verification email’ action without asking again. | Complete email verification by clicking the link sent to your inbox, or call /auth/resend-verification to receive a new link, 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": "Forbidden", "message": "You do not have permission to delete this snapshot"}| 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": "Snapshot not found"}Validation Error: the request does not match the operation schema, or its JSON body cannot be parsed. message names each failing check, and data, when present, lists them one entry per failing field.
{ "statusCode": 422, "error": "Validation Error", "message": "Validation failed: /name must NOT have fewer than 3 characters"}{ "statusCode": 500, "error": "Internal Server Error", "message": "An unexpected error occurred"}