Server Management API
Section titled “Server Management API”This page groups three families of operations on the Hoody control plane:
- An IP-introspection utility that reports what the Hoody edge saw about the caller.
- The
api:Metaendpoints that publish current platform facts, the API signing key, and social counters. All of these are unauthenticated. - The
api:Server Commandsendpoints that list and execute predefined commands on a server you own.
Use the meta endpoints to render “what version of Hoody is this?” or “how many followers does Hoody have?” widgets on a public page. Use the server-commands endpoints when you want to automate routine operations on a node without granting shell access.
IP utilities
Section titled “IP utilities”GET /api/v1/ip
Section titled “GET /api/v1/ip”Retrieves information about the caller’s IP address, including geolocation and network details. Returns null for ip_info if the geolocation lookup did not produce a result.
This endpoint takes no parameters.
curl -X GET https://api.hoody.com/api/v1/ipimport { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
await client.api.ip.get();{ "statusCode": 200, "message": "IP information retrieved successfully", "data": { "ip": "8.8.8.8", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "headers": { "accept-language": "en-US,en;q=0.9", "accept-encoding": "gzip, deflate, br" }, "referer": "https://example.com/docs", "timestamp": "2024-01-15T10:00:00Z", "is_logged": true, "protocol": "https", "ip_info": { "ip": "8.8.8.8", "hostname": "dns.google", "city": "Mountain View", "region": "California", "country": "US", "loc": "37.4056,-122.0775", "postal": "94043", "timezone": "America/Los_Angeles", "asn": {}, "is_anycast": true, "is_mobile": false, "is_anonymous": false, "is_satellite": false, "is_hosting": true } }}Public platform metadata
Section titled “Public platform metadata”The endpoints in this section are intentionally public. None of them require authentication, and all of them can be called safely from a browser. They return small, cacheable facts about the Hoody fleet.
GET /api/v1/meta/config
Section titled “GET /api/v1/meta/config”Returns a flat key/value map of current Hoody platform facts, for example kernel, the kernel version the server fleet runs.
Flat by contract. Every value is a JSON scalar: string, number, boolean, or null. There are no nested objects and no arrays, and keys cannot contain dots, so the response is always one level deep.
Keys with a shipped default are always present. An administrator can override any of them and can add further keys; an override that is removed reverts to the shipped default rather than disappearing. The response is served with Cache-Control: no-store so an operator change is visible immediately.
This endpoint takes no parameters.
curl -X GET https://api.hoody.com/api/v1/meta/config{ "statusCode": 200, "message": "Hoody public configuration", "data": { "kernel": "7.2.9-hoody1", "platform_version": "3.14.0", "min_client_version": "3.0.0", "maintenance_mode": false, "signup_enabled": true, "default_region": "us" }}{ "statusCode": 429, "error": "META_CONFIG_RATE_LIMITED", "message": "Rate limit exceeded for this endpoint. Please try again later."}{ "statusCode": 503, "error": "META_CONFIG_UNAVAILABLE", "message": "The configuration store is not available."}GET /api/v1/meta/config/{key}
Section titled “GET /api/v1/meta/config/{key}”Returns a single value from the public configuration map. Same data as GET /api/v1/meta/config, for callers that need exactly one key.
Returns 404 when the key is neither stored nor shipped as a default.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
key | path | string | Yes | Configuration key, for example kernel. Must match ^[a-z][a-z0-9_-]{0,63}$, lowercase, starting with a letter, then letters, digits, underscores, or hyphens. Keys are never normalized, so a-b and a_b are distinct, and dots are refused so nesting cannot be simulated. |
curl -X GET https://api.hoody.com/api/v1/meta/config/kernel{ "statusCode": 200, "message": "Hoody public configuration value", "data": { "key": "kernel", "value": "7.2.9-hoody1" }}{ "statusCode": 404, "error": "META_CONFIG_KEY_NOT_FOUND", "message": "No such configuration key."}{ "statusCode": 422, "error": "META_CONFIG_KEY_INVALID", "message": "Key must match ^[a-z][a-z0-9_-]{0,63}$ ... Nested keys are not supported."}{ "statusCode": 429, "error": "META_CONFIG_RATE_LIMITED", "message": "Rate limit exceeded for this endpoint. Please try again later."}{ "statusCode": 503, "error": "META_CONFIG_UNAVAILABLE", "message": "The configuration store is not available."}GET /api/v1/meta/public-key
Section titled “GET /api/v1/meta/public-key”Returns the ED25519 public key(s) used by Hoody to sign all API responses (X-Hoody-Signature header), identity claims issued at login, and container authorization claims.
Third-party verification flow:
- Fetch this endpoint once (cache for 24 hours or more).
- Locate the key by
kidfrom thekeys[]array. - For response signatures, parse
X-Hoody-Signature: t=<ts>,kid=<id>,m=<method>,s=<status>,path=<url>,sig=<hex>and verifysigagainst the newline-joined tuple${t}\n${method}\n${status}\n${path}\n${responseBody}. - For identity and container claims, verify
claim.signature_hexagainstclaim.payload_b64bytes. - If
kidin a signature or claim does not match any cached key, re-fetch this endpoint.
This endpoint takes no parameters.
curl -X GET https://api.hoody.com/api/v1/meta/public-keyimport { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
await client.api.meta.getPublicKey();{ "statusCode": 200, "message": "Hoody API signing public key", "data": { "keys": [ { "kid": "v1", "algorithm": "ed25519", "public_key_hex": "8c8d683c125761bd9157e3a6f98c30d81cd7f2be4d16062a8342d1fcd2ca474a", "public_key_b64": "jI1oPBJXYb2RV+Om+YwwwlzX8r5NFgYqg0LRzSykd0o=", "public_key_b64url": "jI1oPBJXYb2RV-Om-YwwwlzX8r5NFgYqg0LRzSykd0o" } ], "active_kid": "v1", "usage": [ "response-signing", "identity-claims", "container-claims" ], "signing_format": { "response_header": "X-Hoody-Signature: t=<unix_ts>,kid=<key_id>,m=<method>,s=<status>,path=<request_url>,sig=<hex>", "response_signed_data": "<t>\n<method>\n<status>\n<path>\n<response_body_utf8_string>", "identity_claim_signed_data": "base64url(JSON.stringify(claim_payload)) — the b64url string itself (UTF-8 bytes)", "container_claim_signed_data": "base64url(JSON.stringify(container_claim_payload)) — the b64url string itself (UTF-8 bytes)", "replay_tolerance_seconds": 300 } }}The successful response is also accompanied by an X-Hoody-Signature response header:
X-Hoody-Signature: t=1710000000,kid=v1,m=GET,s=200,path=/api/v1/meta/public-key,sig=abcdef1234abcdef1234abcdef1234abcdef1234abcdef1234abcdef1234abcdef1234abcdef1234abcdef1234abcdef1234abcdef1234abcdef1234abcdef12{ "statusCode": 503, "error": "SIGNING_NOT_CONFIGURED", "message": "Response signing is not available"}GET /api/v1/meta/social-stats
Section titled “GET /api/v1/meta/social-stats”Returns counters for the public Hoody social channels: GitHub stars, Telegram members, Discord members (total + currently online), X followers, and LinkedIn followers.
Counters may lag behind their source channels. When a channel cannot be read, its last known value is kept.
A field is null when no value is known for it yet, or when the counter is not being shown. A field that had a number can become null later, so handle null on every call.
No authentication required.
This endpoint takes no parameters.
curl -X GET https://api.hoody.com/api/v1/meta/social-stats{ "statusCode": 200, "message": "Hoody social counters", "data": { "github": 1234, "telegram": 5678, "discord": 910, "discord_online": 42, "x": 837, "linkedin": 560, "fetchedAt": "2026-05-02T12:00:00.000Z" }}GET /api/v1/meta/subserver-plans
Section titled “GET /api/v1/meta/subserver-plans”Returns the paid subserver plans currently on public sale, ordered for display. Each plan carries its resource limits, monthly price, hold policy, and the marketing text used to render it. Texts are translated per field with an English fallback, and text_locales reports which locale each field was served from.
No authentication is required.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
locale | query | string | No | Language tag such as en, fr or pt-BR. Overrides the Accept-Language header. Unknown or invalid values are served as en. |
curl -X GET "https://api.hoody.com/api/v1/meta/subserver-plans?locale=fr"{ "statusCode": 200, "message": "Public subserver plans", "data": { "requested_locale": "fr", "plans": [ { "id": "plan_std_2024_01", "family_id": "subserver-standard", "type": "standard", "sort_order": 1, "price_cents_per_month": 999, "hold_days": 7, "resources": { "cpu_cores": 2, "ram_gb": 4, "disk_limit_gb": 50, "disk_io_read_mib": 200, "disk_io_write_mib": 100, "network_limit_mbps": 100, "max_containers": 5, "snapshots_per_container": 3, "ai_multiplier": 1 }, "text": { "title": "Standard", "description": "Un sous-serveur equilibr\u00e9 pour la plupart des charges de travail.", "type_label": "Standard", "features": [ "2 vCPU", "4 Go de RAM", "50 Go de SSD" ] }, "text_locales": { "title": "en", "description": "fr", "type_label": "en", "features": "fr" } }, { "id": "plan_pro_2024_01", "family_id": "subserver-pro", "type": "standard", "sort_order": 2, "price_cents_per_month": 2999, "hold_days": 14, "resources": { "cpu_cores": 8, "ram_gb": 16, "disk_limit_gb": 200, "disk_io_read_mib": 800, "disk_io_write_mib": 400, "network_limit_mbps": 500, "max_containers": 20, "snapshots_per_container": 10, "ai_multiplier": 4 }, "text": { "title": "Pro", "description": "Plus de coeurs, de memoire et de stockage pour les charges intensives.", "type_label": "Pro", "features": [ "8 vCPU", "16 Go de RAM", "200 Go de SSD" ] }, "text_locales": { "title": "en", "description": "fr", "type_label": "en", "features": "fr" } } ] }}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"}Server commands
Section titled “Server commands”The endpoints in this section let you list the predefined commands available on a server and execute them. Listing returns command metadata such as the slug, risk level, parameter schema, and rate limits. Execution dispatches the command over SSH and returns a synchronous result, or accepts the command for asynchronous execution if wait: false is set.
GET /api/v1/servers/{serverId}/available-commands
Section titled “GET /api/v1/servers/{serverId}/available-commands”Returns the list of predefined commands available for execution on a server. Use this to drive a UI that lets users pick a command and fill in its parameters, rather than letting free-form shell text reach the server.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
serverId | path | string | Yes | Server ID to get available commands for. |
category | query | string | No | Filter by command category. |
risk_level | query | string | No | Filter by maximum risk level. One of low, medium, high, critical. |
curl -X GET "https://api.hoody.com/api/v1/servers/507f1f77bcf86cd799439012/available-commands?category=system&risk_level=medium" \ -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.servers.commands.listIterator('507f1f77bcf86cd799439012', { category: 'system', risk_level: 'medium' })) { console.log(item);}{ "statusCode": 200, "message": "Available commands retrieved successfully", "data": { "commands": [ { "id": "507f1f77bcf86cd799439015", "name": "Restart Service", "slug": "restart-service", "description": "Restart a system service", "category": "system", "risk_level": "medium", "requires_confirmation": true, "parameter_schema": { "type": "object", "required": [ "service_name" ], "properties": { "service_name": { "type": "string" } } }, "example_parameters": { "service_name": "nginx" }, "default_timeout": 300, "cooldown_seconds": 600, "rate_limit_per_hour": 10, "rate_limit_per_day": 50 } ], "server_info": { "id": "507f1f77bcf86cd799439012", "name": "node-example-1", "is_ready": true, "rental_status": "active" } }}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": 403, "error": "Forbidden", "message": "You do not have permission to list commands on this server"}| 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 |
| 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 |
POST /api/v1/servers/{serverId}/execute-command
Section titled “POST /api/v1/servers/{serverId}/execute-command”Executes a predefined command on a server. The command is identified by either command_id (24-character hexadecimal) or command_slug (lowercase alphanumeric with hyphens), exactly one of which is required. Parameters are validated against the command’s parameter_schema and then substituted into the command template.
Set wait: false to accept the command for asynchronous execution and return immediately with a 202 Accepted and an estimated completion time. Set wait: true (the default) to block until the command completes and return its full output and exit code.
High-risk commands return a confirmation challenge on first invocation; the caller re-submits with confirmation_token to actually run the command.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
serverId | path | string | Yes | Server ID to execute command on. |
Request Body
Section titled “Request Body”| Field | Type | Required | Default | Description |
|---|---|---|---|---|
command_id | string | No | — | Command ID to execute. Must match ^[0-9a-f]{24}$. Either command_id or command_slug is required. |
command_slug | string | No | — | Command slug to execute. Must match ^[a-z0-9-]+$. Either command_id or command_slug is required. |
parameters | object | No | — | Parameters for command template processing, validated against parameter_schema. |
wait | boolean | No | true | Wait for command completion before returning. When false, returns 202 Accepted immediately. |
timeout | number | No | — | Command timeout in seconds (1 to 7200). Cannot exceed the command’s max_timeout. |
confirmation_token | string | No | — | Confirmation token returned by a prior confirmation challenge for high-risk commands. |
{ "command_slug": "restart-service", "parameters": { "service_name": "nginx" }, "wait": true, "timeout": 300}curl -X POST "https://api.hoody.com/api/v1/servers/507f1f77bcf86cd799439012/execute-command" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "command_slug": "restart-service", "parameters": { "service_name": "nginx" }, "wait": true, "timeout": 300 }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
await client.api.servers.commands.run('507f1f77bcf86cd799439012', { command_slug: 'restart-service', parameters: { service_name: 'nginx' }, wait: true, timeout: 300,});{ "statusCode": 200, "message": "Command executed successfully", "data": { "command_log_id": "507f1f77bcf86cd799439016", "command_id": "507f1f77bcf86cd799439015", "status": "completed", "output": "Service restarted successfully", "exit_code": 0, "execution_time": 2453, "start_time": "2025-01-15T16:00:00.000Z", "end_time": "2025-01-15T16:00:02.453Z" }}{ "statusCode": 202, "message": "Command accepted for execution", "data": { "command_log_id": "507f1f77bcf86cd799439016", "command_id": "507f1f77bcf86cd799439015", "status": "pending", "estimated_completion": "2025-01-15T16:05:00.000Z" }}{ "statusCode": 400, "error": "Bad Request", "message": "Invalid command parameters", "data": { "missing_params": [ "service_name" ] }}{ "statusCode": 403, "error": "Forbidden", "message": "Not authorized to execute this command on server"}| 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": "Command or server 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": 429, "error": "Too Many Requests", "message": "Rate limit exceeded for this command", "data": { "retry_after": 3600, "rate_limit_type": "hourly" }}| 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 |