Skip to content

The Hoody API requires authentication for all operations. There are two authentication systems, each designed for different use cases.

After understanding what the Hoody API does (platform management), you need to understand how to authenticate to actually use it.


Official Technical Reference:

This Foundation page explains authentication concepts and best practices. For complete endpoint documentation:

User Authentication (JWT Tokens):

Automation (Auth Tokens):


Hoody provides two distinct authentication methods:

JWT Tokens (User Sessions)

Use for: Browser sessions, interactive work

Terminal window
POST /api/v1/users/auth/login

Returns:

  • token (access, 1 day)
  • refreshToken (7 days)

Characteristics:

  • โœ… Short-lived (secure)
  • โœ… Refresh-able (no re-login)
  • โœ… User-specific
  • โŒ Not ideal for automation

Auth Tokens (Automation)

Use for: Scripts, AI agents, CI/CD, integrations

Terminal window
POST /api/v1/auth/tokens

Returns:

  • hdy_... token (long-lived)

Characteristics:

  • โœ… Long-lived (configurable)
  • โœ… IP whitelist support
  • โœ… Revocable anytime
  • โœ… Per-token permissions
  • โœ… Ideal for automation

Step 1: Login

Terminal window
# Login with email and password (or use --username instead of --email)
hoody auth login --email you@example.com --password your_password
POST Login with username and password
/api/v1/users/auth/login
Click "Run" to execute the request

Response:

{
"statusCode": 200,
"message": "Login successful",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "63f8b0e5c9a1b2d3e4f5a6b7",
"username": "your_username",
"alias": "Your Display Name",
"is_banned": false,
"created_at": "2025-10-21T10:00:00.000Z",
"updated_at": "2025-10-21T10:00:00.000Z"
}
}
}

Step 2: Use Access Token

Terminal window
# CLI stores the token automatically after login
hoody projects list

Step 3: Refresh When Expired

Terminal window
# CLI handles token refresh automatically
# If your session expired, simply re-login
hoody auth login --username your_username --password your_password
POST Refresh access token when expired (after 1 day)
/api/v1/users/auth/refresh
Click "Run" to execute the request

Returns: New access token + new refresh token (both rotated for security)


Step 1: Create Auth Token (one-time setup)

Terminal window
# Login first
hoody auth login --username your_username --password your_password
# Create a long-lived automation token with IP whitelist
hoody auth create \
--alias "Production Automation Token" \
--ip-whitelist "203.0.113.10,203.0.113.20" \
--expires-at "2027-04-12T00:00:00Z"
POST Create a long-lived automation token with IP whitelist
/api/v1/auth/tokens
Click "Run" to execute the request

Response:

{
"statusCode": 201,
"message": "Auth token created successfully",
"data": {
"token": "hdy_abc123XyZ456...",
"id": "63f8b0e5c9a1b2d3e4f5a6b7",
"alias": "Production Automation Token",
"prefix": "hdy_",
"ip_whitelist": ["203.0.113.10", "203.0.113.20"],
"expires_at": "2027-04-12T00:00:00.000Z",
"is_enabled": true,
"last_used_at": null,
"last_used_ip": null,
"created_at": "2025-11-09T15:00:00.000Z",
"updated_at": "2025-11-09T15:00:00.000Z"
}
}

Step 2: Use Auth Token (forever, until it expires or is revoked)

Terminal window
# Store token and use with CLI
export HOODY_TOKEN="hdy_abc123XyZ456..."
# All subsequent commands use this token
hoody projects list
hoody containers list

Benefits:

  • โœ… No credentials in code (secure)
  • โœ… IP whitelist enforcement (restrict to specific IPs)
  • โœ… Expiration control (ISO 8601 date, or never)
  • โœ… Instant revocation (disable or delete token)
  • โœ… Audit trail (last_used_at, last_used_ip)

IP Whitelisting:

POST Create token with IP whitelist for CI/CD pipeline
/api/v1/auth/tokens
Click "Run" to execute the request

Multiple formats for expiration:

{
"expires_at": "2026-12-31T23:59:59Z"
}
Terminal window
# List all your auth tokens
hoody auth list
GET List all your auth tokens
/api/v1/auth/tokens
Click "Run" to execute the request

Response shows usage tracking:

{
"data": [
{
"id": "63f8b0e5c9a1b2d3e4f5a6b7",
"alias": "CI/CD Pipeline",
"prefix": "hdy_",
"ip_whitelist": ["203.0.113.50"],
"expires_at": "2026-02-07T15:00:00.000Z",
"is_enabled": true,
"last_used_at": "2025-11-09T14:30:00.000Z",
"last_used_ip": "203.0.113.50",
"created_at": "2025-11-09T10:00:00.000Z",
"updated_at": "2025-11-09T14:30:00.000Z"
}
]
}

Audit your tokens:

  • Check last_used_at to identify unused tokens
  • Verify last_used_ip matches expected sources
  • Review ip_whitelist restrictions

Disable without deleting:

Terminal window
# Disable a token without deleting it
hoody auth update $TOKEN_ID --enabled false
PUT Disable a token without deleting it
/api/v1/auth/tokens/{id}
Click "Run" to execute the request

Permanently delete:

Terminal window
# Permanently delete a token
hoody auth delete $TOKEN_ID
DELETE Permanently delete a token
/api/v1/auth/tokens/{id}
Click "Run" to execute the request

// NEVER do this
const response = await fetch('https://api.hoody.icu/api/v1/projects', {
headers: {
'Authorization': 'Bearer hdy_abc123hardcoded'
}
});

Restrict tokens to specific sources:

POST Create token that only works from your office IP
/api/v1/auth/tokens
Click "Run" to execute the request

If token is leaked: It wonโ€™t work from other IPs.

Short-lived for temporary access:

{
"alias": "Contractor Access",
"expires_at": "2026-05-12T00:00:00Z"
}

Long-lived for permanent infrastructure:

{
"alias": "Production Services",
"expires_at": "2027-04-12T00:00:00Z"
}

Review and rotate regularly.

Donโ€™t share one token across multiple systems:

Terminal window
# Create separate tokens
POST /api/v1/auth/tokens { "alias": "GitHub Actions CI", ... }
POST /api/v1/auth/tokens { "alias": "Monitoring System", ... }
POST /api/v1/auth/tokens { "alias": "AI Agent Orchestrator", ... }

If one service is compromised: Revoke only that token, others continue working.


The Auth Token system is designed for AI orchestration:

// AI agent configuration (environment variables)
const HOODY_TOKEN = process.env.HOODY_TOKEN; // hdy_... token
const HOODY_API = 'https://api.hoody.icu';
// AI can now orchestrate infrastructure
async function aiAgentWorkflow(task) {
const headers = {
'Authorization': `Bearer ${HOODY_TOKEN}`,
'Content-Type': 'application/json'
};
// AI decides: "Need a container to process this task"
const container = await fetch(`${HOODY_API}/api/v1/projects/${projectId}/containers`, {
method: 'POST',
headers,
body: JSON.stringify({
name: `ai-task-${Date.now()}`,
server_id: 'your-server-id',
hoody_kit: true
})
}).then(r => r.json());
// Wait for container to be running
await waitForStatus(container.data.id, 'running');
// Get container URLs and use them
const terminalUrl = `https://${projectId}-${container.data.id}-terminal-1.${container.data.server_name}.containers.hoody.icu`;
// AI executes commands in the new container
await fetch(`${terminalUrl}/execute`, {
method: 'POST',
body: JSON.stringify({ command: task.command })
});
// AI snapshots when done
await fetch(`${HOODY_API}/api/v1/containers/${container.data.id}/snapshots`, {
method: 'POST',
headers,
body: JSON.stringify({ alias: `task-${task.id}` })
});
return container;
}

The AI only needs:

  1. A Hoody Auth Token (environment variable)
  2. Understanding of HTTP (it already has this)

No SDK. No training. Just HTTP.


FeatureJWT (Login)Auth Token (hdy_โ€ฆ)
Lifetime1 day (access)
7 days (refresh)
Configurable (ISO 8601 date, โ€œtodayโ€, โ€œtomorrowโ€, or forever)
Use CaseUser sessionsAutomation, AI, scripts
RefreshYes (via refresh token)No (create new when expired)
IP WhitelistNoYes (optional)
RevocationLogout endpointDelete or disable
VisibilityManaged by browserOne-time show during creation
SecurityShort-lived = more secureLong-lived but IP-restricted

Terminal window
# 1. Login
curl -X POST "https://api.hoody.icu/api/v1/users/auth/login" \
-H "Content-Type: application/json" \
-d '{
"username": "dev_user",
"password": "strong_password_here"
}'
# Response includes tokens
# {
# "data": {
# "token": "eyJhbG...",
# "refreshToken": "eyJhbG...",
# "user": { ... }
# }
# }
# 2. Use access token (valid 1 day)
curl "https://api.hoody.icu/api/v1/projects" \
-H "Authorization: Bearer eyJhbG..."
# 3. Refresh before expiration (within 7 days)
curl -X POST "https://api.hoody.icu/api/v1/users/auth/refresh" \
-H "Content-Type: application/json" \
-d '{"refreshToken": "eyJhbG..."}'
# 4. Logout (optional, invalidates session)
curl -X POST "https://api.hoody.icu/api/v1/users/auth/logout" \
-H "Authorization: Bearer eyJhbG..."
Terminal window
# 1. Login to get JWT
curl -X POST "https://api.hoody.icu/api/v1/users/auth/login" \
-d '{"username": "your_username", "password": "your_password"}' \
> login.json
# Extract JWT
JWT=$(cat login.json | jq -r '.data.token')
# 2. Create auth token for CI/CD
curl -X POST "https://api.hoody.icu/api/v1/auth/tokens" \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"alias": "GitHub Actions Deployment",
"ip_whitelist": ["140.82.112.0/20"],
"expires_at": "2027-04-12T00:00:00Z"
}' > token.json
# Extract auth token
AUTH_TOKEN=$(cat token.json | jq -r '.data.token')
# 3. Save to GitHub Secrets as HOODY_TOKEN
echo "HOODY_TOKEN=$AUTH_TOKEN"
# 4. Use in GitHub Actions workflow
# - name: Deploy via Hoody API
# env:
# HOODY_TOKEN: ${{ secrets.HOODY_TOKEN }}
# run: |
# curl "https://api.hoody.icu/api/v1/containers/$CONTAINER_ID/start" \
# -H "Authorization: Bearer $HOODY_TOKEN"
.env
// AI agent configuration file
HOODY_TOKEN=hdy_abc123def456...
HOODY_PROJECT_ID=63f8b0e5c9a1b2d3e4f5a6b7
// agent.js
import 'dotenv/config';
class HoodyAgent {
constructor() {
this.token = process.env.HOODY_TOKEN;
this.projectId = process.env.HOODY_PROJECT_ID;
this.api = 'https://api.hoody.icu';
}
async callAPI(endpoint, options = {}) {
return fetch(`${this.api}${endpoint}`, {
...options,
headers: {
'Authorization': `Bearer ${this.token}`,
'Content-Type': 'application/json',
...options.headers
}
});
}
async spawnContainer(name, config) {
const response = await this.callAPI(
`/api/v1/projects/${this.projectId}/containers`,
{
method: 'POST',
body: JSON.stringify({
name,
server_id: config.serverId,
hoody_kit: true,
...config
})
}
);
return response.json();
}
async executeInContainer(containerId, command) {
// First get container details to construct service URL
const container = await this.callAPI(`/api/v1/containers/${containerId}`)
.then(r => r.json());
// Construct terminal URL
const terminalUrl = `https://${this.projectId}-${containerId}-terminal-1.${container.data.server_name}.containers.hoody.icu`;
// Execute command (no auth needed if container permissions are open)
return fetch(`${terminalUrl}/execute`, {
method: 'POST',
body: JSON.stringify({ command })
});
}
}
// AI uses this class for ALL Hoody operations
const agent = new HoodyAgent();
await agent.spawnContainer('ai-workspace', { serverId: 'server-123' });

The AI only needs environment variables. No password handling. No credential management.


GET List all your auth tokens
/api/v1/auth/tokens
Click "Run" to execute the request

Change IP whitelist:

PUT Update token IP whitelist
/api/v1/auth/tokens/{token_id}
Click "Run" to execute the request

Extend expiration:

PUT Extend token expiration
/api/v1/auth/tokens/{token_id}
Click "Run" to execute the request

Temporarily disable:

PUT Temporarily disable a token
/api/v1/auth/tokens/{token_id}
Click "Run" to execute the request

Best practice for production:

Terminal window
# 1. Create new token
NEW_TOKEN=$(curl -X POST "https://api.hoody.icu/api/v1/auth/tokens" \
-H "Authorization: Bearer YOUR_JWT" \
-d '{"alias": "Production V2", "expires_at": "2027-04-12T00:00:00Z"}' \
| jq -r '.data.token')
# 2. Update your services with new token
# (Deploy new environment variable to all services)
# 3. Wait 24-48 hours for old token usage to drop
# 4. Check old token is unused
curl "https://api.hoody.icu/api/v1/auth/tokens/{old_token_id}" \
-H "Authorization: Bearer YOUR_JWT" \
| jq '.data.last_used_at'
# 5. Delete old token
curl -X DELETE "https://api.hoody.icu/api/v1/auth/tokens/{old_token_id}" \
-H "Authorization: Bearer YOUR_JWT"

For one-time operations:

POST Create a temporary token that auto-expires tomorrow
/api/v1/auth/tokens
Click "Run" to execute the request

Use the token for your migration, and it will auto-expire tomorrow.

Different tokens for different environments:

Development token (permissive):

POST Create permissive development token
/api/v1/auth/tokens
Click "Run" to execute the request

Staging token (IP-restricted):

POST Create IP-restricted staging token
/api/v1/auth/tokens
Click "Run" to execute the request

Production token (strict):

POST Create strict production token
/api/v1/auth/tokens
Click "Run" to execute the request

If a token is compromised:

1. Immediately disable:

PUT Immediately disable compromised token
/api/v1/auth/tokens/{compromised_token_id}
Click "Run" to execute the request

2. Create replacement with different IP whitelist:

POST Create replacement token with new IP restrictions
/api/v1/auth/tokens
Click "Run" to execute the request

3. Update services, then delete old token:

DELETE Delete the compromised token after services are updated
/api/v1/auth/tokens/{compromised_token_id}
Click "Run" to execute the request

Always use Auth Tokens (hdy_...) for scripts and automation. JWTs from login are designed for short-lived user sessions and expire after 1 day. Auth Tokens can live for years with IP whitelisting and revocation support.

No. You must use a JWT from user login to create Auth Tokens. This prevents token proliferationโ€”if an Auth Token is compromised, it canโ€™t create more tokens. Always keep one user account secure for Auth Token management.

How do I share API access with my team without sharing passwords?

Section titled โ€œHow do I share API access with my team without sharing passwords?โ€

Create individual Auth Tokens for each team member with specific IP whitelists. Each person gets their own hdy_... token, and you can revoke any token independently if someone leaves the team.

After 1 day, the access token expires. Use your refresh token (valid 7 days) to get a new access token via POST /api/v1/users/auth/refresh. If the refresh token also expires, you must login again with username/password.

Can I use the same Auth Token across multiple servers or applications?

Section titled โ€œCan I use the same Auth Token across multiple servers or applications?โ€

Yes, but treat that as convenience, not best practice. A single token can work across multiple projects/servers, yet isolation is stronger with separate tokens per app/environment.

For realm-restricted tokens:

  • If realm_ids is non-empty (or allow_no_realm: false), use realm-scoped hosts like https://{realmId}.api.hoody.icu.
  • Use GET /api/v1/auth/tokens/me on https://api.hoody.icu to discover allowed realms before selecting a realm host.

Without IP whitelist, a leaked token can be used from anywhere. While the token itself is cryptographically strong (60+ character random string), IP whitelisting adds defense-in-depth. Use it for production tokens, skip it for low-sensitivity automation.

Yes. If a long-running script spans the expiration time, it will start getting 401 errors. For long processes, use generous expiration (a far-future ISO 8601 date or null) or implement token refresh logic that creates a new token before the old one expires.

Whatโ€™s the difference between disabling and deleting an Auth Token?

Section titled โ€œWhatโ€™s the difference between disabling and deleting an Auth Token?โ€

Disable sets is_enabled: falseโ€”token stops working but you can re-enable it later with the same ID. Delete permanently removes the tokenโ€”cannot be recovered. Use disable for temporary suspension, delete for permanent revocation.

Yes. Auth Tokens work with realm-scoped APIs ({realmId}.api.hoody.icu), and unrestricted tokens can also use the base API host.

Important behavior:

  • Tokens with non-empty realm_ids are restricted to those realm IDs.
  • Tokens with allow_no_realm: false cannot use base host for resource operations.
  • Realm-restricted tokens can still call GET /api/v1/auth/tokens/me on base host to bootstrap realm discovery.

Create the new token, deploy it to your services, verify the new token works, wait 24-48 hours, check old tokenโ€™s last_used_at is old, then delete the old token. Both tokens work simultaneously during the transition.


Problem: Login returns 401 with โ€œInvalid credentialsโ€

Solutions:

  1. Verify username/password:
POST Check for typos, ensure exact match
/api/v1/users/auth/login
Click "Run" to execute the request
  1. Check account status:
    • Account might be banned (is_banned: true)
    • Contact support if legitimate user

Problem: Requests return 401 after some time

Cause: Access token expires after 1 day

Solution - Use refresh token:

POST Refresh access token using refresh token
/api/v1/users/auth/refresh
Click "Run" to execute the request

Returns new access token + new refresh token.

Refresh token expired? (after 7 days) - Login again

Problem: hdy_... token returns 403 Forbidden

Check IP whitelist:

GET Get token details to check IP whitelist
/api/v1/auth/tokens/{token_id}
Click "Run" to execute the request

Compare the ip_whitelist array with your current IP (run curl https://ifconfig.me in terminal).

Solution - Update whitelist:

PUT Update token IP whitelist to include your current IP
/api/v1/auth/tokens/{token_id}
Click "Run" to execute the request

Problem: Canโ€™t create Auth Token, getting 401

Cause: Youโ€™re using an Auth Token to create another Auth Token

Solution: Use a JWT from login instead:

Terminal window
# 1. Login first
curl -X POST "https://api.hoody.icu/api/v1/users/auth/login" \
-d '{"username": "your_username", "password": "your_password"}' \
> login.json
# 2. Extract JWT
JWT=$(cat login.json | jq -r '.data.token')
# 3. Create Auth Token with JWT
curl -X POST "https://api.hoody.icu/api/v1/auth/tokens" \
-H "Authorization: Bearer $JWT" \
-d '{"alias": "My Token", "expires_at": "2027-04-12T00:00:00Z"}'

Problem: Created token but didnโ€™t save the hdy_... value

Reality: Cannot retrieve token value after creation

Solution:

  1. Disable the old token:
PUT Disable the old token
/api/v1/auth/tokens/{token_id}
Click "Run" to execute the request
  1. Create new token:
POST Create replacement token - SAVE THE TOKEN VALUE IMMEDIATELY
/api/v1/auth/tokens
Click "Run" to execute the request

Problem: Scripts work sometimes, fail other times with 403

Likely cause: IP whitelist + dynamic IP

Check if your IP changed:

Run curl https://ifconfig.me in terminal to get your current IP, then compare with token whitelist:

GET Get token details to check IP whitelist
/api/v1/auth/tokens/{token_id}
Click "Run" to execute the request

Solutions:

  1. Use CIDR range instead of single IP: Instead of "203.0.113.50/32", use "203.0.113.0/24" (allows entire subnet)

  2. Remove IP whitelist for non-sensitive automation:

PUT Remove IP whitelist restrictions
/api/v1/auth/tokens/{token_id}
Click "Run" to execute the request
  1. Use static IP for automation servers

Now that you can authenticate:

  1. Create Projects - Organize your containers
  2. Spawn Containers - Create your first HTTP computer
  3. Configure Networking - Set up routing and firewall
  4. Create Proxy Aliases - Get clean URLs for production

Everything starts with authentication. Everything else is HTTP.


User sessions use JWTs.
Automation uses Auth Tokens.
Never hardcode credentials.
Use environment variables. Use IP whitelists. Use expiration.

This is how you securely control infinite computers.