Container images
Section titled “Container images”The container images API lets you discover public images, browse the catalog, import free images into your account, purchase paid images, and rate images you have used. Use these endpoints to populate the image picker when launching a container, manage the images you already own, and capture community feedback on the catalog.
Image icons
Section titled “Image icons”GET /api/v1/images/{id}/icon
Section titled “GET /api/v1/images/{id}/icon”Retrieve the icon for a container image as a PNG. The endpoint always returns a PNG payload: if the image has no icon, is not public, or does not exist, a generic placeholder PNG is returned.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Unique identifier of the container image to retrieve icon for |
curl -X GET https://api.hoody.com/api/v1/images/{id}/icon \ -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.images.getIcon('507f1f77bcf86cd799439011');{ "statusCode": 200, "description": "PNG image. Falls back to a generic placeholder PNG when the image has no icon, is not public, or does not exist.", "content": { "application/json": { "schema": { "type": "string", "format": "binary", "description": "PNG image. Falls back to a generic placeholder PNG when the image has no icon, is not public, or does not exist." } } }}Response statuses
Section titled “Response statuses”{ "description": "PNG image. Falls back to a generic placeholder PNG when the image has no icon, is not public, or does not exist.", "content": { "application/json": { "schema": { "type": "string", "format": "binary", "description": "PNG image. Falls back to a generic placeholder PNG when the image has no icon, is not public, or does not exist." } } }}{ "statusCode": 400, "error": "Bad Request", "message": "Invalid icon format"}{ "statusCode": 422, "error": "Unprocessable Entity", "message": "params/id must match pattern \"^[0-9a-f]{24}$\""}{ "statusCode": 500, "error": "Internal Server Error", "message": "Failed to retrieve image icon"}Public images
Section titled “Public images”GET /api/v1/images/public
Section titled “GET /api/v1/images/public”List public container images with optional filters for OS, architecture, price, rating, and free-text search. Results are paginated.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
os | query | string | No | Filter images by operating system (the platform currently carries debian/13 only) |
architecture | query | string | No | Filter images by CPU architecture, for example amd64, arm64, armhf |
min_price | query | number | No | Minimum price filter for paid images, 0 includes free images |
max_price | query | number | No | Maximum price filter for paid images, useful for budget constraints |
min_rating | query | number | No | Minimum average rating filter, filters images with rating >= this value (0 to 5 stars) |
max_rating | query | number | No | Maximum average rating filter, filters images with rating at most this value (0 to 5 stars) |
search | query | string | No | Search term to filter images by name, description, or tags |
page | query | integer | No | Page number for pagination, starts from 1. Default: 1 |
limit | query | integer | No | Number of images to return per page, maximum 100 items. Default: 20 |
sort_by | query | string | No | Field to sort images by. Allowed: alias, added_date, price, rating |
sort_order | query | string | No | Sort direction. Allowed: asc, desc |
curl -X GET "https://api.hoody.com/api/v1/images/public?os=debian&architecture=amd64&page=1&limit=20" \ -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.images.listPublicIterator({ os: 'debian', architecture: 'amd64', page: 1, limit: 20 })) { console.log(item);}{ "statusCode": 200, "message": "Public images retrieved successfully", "data": { "images": [ { "id": "507f1f77bcf86cd799439021", "alias": "debian/13", "description": "Debian 13 (Trixie) - Default base image", "image_name": "debian/13", "architecture": "amd64", "os": "debian", "release": "13", "variant": "default", "size": 512000000, "price": 0, "added_date": "2025-01-10T08:00:00.000Z", "average_rating": 4.5, "rating_count": 142, "icon_url": "/api/v1/images/507f1f77bcf86cd799439021/icon" } ], "pagination": { "total": 87, "page": 1, "limit": 20, "totalPages": 5 } }}Response statuses
Section titled “Response statuses”{ "statusCode": 200, "message": "Public images retrieved successfully", "data": { "images": [ { "id": "507f1f77bcf86cd799439021", "alias": "debian/13", "description": "Debian 13 (Trixie) - Default base image", "image_name": "debian/13", "architecture": "amd64", "os": "debian", "release": "13", "variant": "default", "size": 512000000, "price": 0, "added_date": "2025-01-10T08:00:00.000Z", "average_rating": 4.5, "rating_count": 142, "icon_url": "/api/v1/images/507f1f77bcf86cd799439021/icon" } ], "pagination": { "total": 87, "page": 1, "limit": 20, "totalPages": 5 } }}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": "Failed to retrieve public images"}GET /api/v1/images/public/{id}
Section titled “GET /api/v1/images/public/{id}”Fetch details of a single public container image, including its serial, variant, and rating summary.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Unique identifier of the public container image to retrieve details for |
curl -X GET https://api.hoody.com/api/v1/images/public/507f1f77bcf86cd799439021 \ -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.images.getPublic('507f1f77bcf86cd799439021');{ "statusCode": 200, "message": "Public image details retrieved successfully", "data": { "id": "507f1f77bcf86cd799439021", "alias": "debian/13", "description": "Debian 13 (Trixie) - Default base image", "image_name": "debian/13", "architecture": "amd64", "os": "debian", "release": "13", "serial": "20250110", "variant": "default", "size": 512000000, "price": 0, "added_date": "2025-01-10T08:00:00.000Z", "average_rating": 4.5, "rating_count": 142, "icon_url": "/api/v1/images/507f1f77bcf86cd799439021/icon" }}Response statuses
Section titled “Response statuses”{ "statusCode": 200, "message": "Public image details retrieved successfully", "data": { "id": "507f1f77bcf86cd799439021", "alias": "debian/13", "description": "Debian 13 (Trixie) - Default base image", "image_name": "debian/13", "architecture": "amd64", "os": "debian", "release": "13", "serial": "20250110", "variant": "default", "size": 512000000, "price": 0, "added_date": "2025-01-10T08:00:00.000Z", "average_rating": 4.5, "rating_count": 142, "icon_url": "/api/v1/images/507f1f77bcf86cd799439021/icon" }}{ "statusCode": 404, "error": "Not Found", "message": "Public image 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": "Failed to retrieve public image details"}User images
Section titled “User images”GET /api/v1/images/user
Section titled “GET /api/v1/images/user”List the authenticated user’s imported and purchased container images. Each entry includes the user’s own rating (user_rating, has_rated) alongside the catalog-wide average and count.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
page | query | integer | No | Page number for pagination, starts from 1. Default: 1 |
limit | query | integer | No | Number of images to return per page, maximum 100 items. Default: 20 |
sort_by | query | string | No | Field to sort user images by. Allowed: created_at |
sort_order | query | string | No | Sort direction. Allowed: asc, desc |
curl -X GET "https://api.hoody.com/api/v1/images/user?page=1&limit=20" \ -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.images.listIterator({ page: 1, limit: 20 })) { console.log(item);}{ "statusCode": 200, "message": "User images retrieved successfully", "data": { "images": [ { "id": "507f1f77bcf86cd799439021", "alias": "debian/13", "description": "Debian 13 (Trixie) - Default base image", "image_name": "debian/13", "architecture": "amd64", "os": "debian", "release": "13", "variant": "default", "size": 512000000, "price": 0, "user_rating": 5, "has_rated": true, "average_rating": 4.5, "rating_count": 142, "icon_url": "/api/v1/images/507f1f77bcf86cd799439021/icon" } ], "pagination": { "total": 12, "page": 1, "limit": 20, "totalPages": 1 } }}Response statuses
Section titled “Response statuses”{ "statusCode": 200, "message": "User images retrieved successfully", "data": { "images": [ { "id": "507f1f77bcf86cd799439021", "alias": "debian/13", "description": "Debian 13 (Trixie) - Default base image", "image_name": "debian/13", "architecture": "amd64", "os": "debian", "release": "13", "variant": "default", "size": 512000000, "price": 0, "user_rating": 5, "has_rated": true, "average_rating": 4.5, "rating_count": 142, "icon_url": "/api/v1/images/507f1f77bcf86cd799439021/icon" } ], "pagination": { "total": 12, "page": 1, "limit": 20, "totalPages": 1 } }}{ "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": "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 |
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": "Failed to retrieve user images"}Image actions
Section titled “Image actions”POST /api/v1/images/import/{id}
Section titled “POST /api/v1/images/import/{id}”Import a free public container image into the authenticated user’s account. Returns a 400 if the image is not free or has already been imported.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Unique identifier of the public container image to import |
curl -X POST https://api.hoody.com/api/v1/images/import/507f1f77bcf86cd799439021 \ -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.images.import('507f1f77bcf86cd799439021');{ "statusCode": 200, "message": "Free image imported successfully", "data": {}}Response statuses
Section titled “Response statuses”{ "statusCode": 200, "message": "Free image imported successfully", "data": {}}{ "statusCode": 400, "error": "Bad Request", "message": "Image is not free or already imported"}{ "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": "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": "Image 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": "Failed to import image"}POST /api/v1/images/purchase/{id}
Section titled “POST /api/v1/images/purchase/{id}”Purchase a paid container image. The response includes the amount charged and the account balance that remains after the purchase. A 400 is returned when the account has insufficient balance or the user already owns the image.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Unique identifier of the paid container image to purchase |
curl -X POST https://api.hoody.com/api/v1/images/purchase/507f1f77bcf86cd799439021 \ -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.images.buy('507f1f77bcf86cd799439021');{ "statusCode": 200, "message": "Image purchased successfully", "data": { "price_paid": 5.99, "remaining_balance": 44.01 }}Response statuses
Section titled “Response statuses”{ "statusCode": 200, "message": "Image purchased successfully", "data": { "price_paid": 5.99, "remaining_balance": 44.01 }}{ "statusCode": 400, "error": "Bad Request", "message": "Insufficient balance or image already owned"}{ "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": "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": "Image 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": "Failed to purchase image"}POST /api/v1/images/rate/{id}
Section titled “POST /api/v1/images/rate/{id}”Submit a rating for a container image. The rating field is required and must be between 0 and 5 (inclusive). The response returns the new rating, the updated catalog average, and the new rating count.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Unique identifier of the container image to rate |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
rating | number | Yes | Rating for the image, between 0 and 5 (inclusive) |
curl -X POST https://api.hoody.com/api/v1/images/rate/507f1f77bcf86cd799439021 \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "rating": 5 }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
await client.api.images.rate('507f1f77bcf86cd799439021', { rating: 5 });{ "statusCode": 200, "message": "Image rated successfully", "data": { "new_rating": 5, "average_rating": 4.6, "rating_count": 143 }}Response statuses
Section titled “Response statuses”{ "statusCode": 200, "message": "Image rated successfully", "data": { "new_rating": 5, "average_rating": 4.6, "rating_count": 143 }}{ "statusCode": 400, "error": "Bad Request", "message": "Invalid rating value (must be 0-5)"}{ "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": "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": "Image 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": "Failed to rate image"}