Authentication
Section titled “Authentication”Nearly all Hoody API operations require authentication — only a few endpoints, such as GET /api/v1/notifications/public, are public. 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.
API Endpoints Summary
Section titled “API Endpoints Summary”Official Technical Reference:
This Foundation page explains authentication concepts and best practices. For complete endpoint documentation:
User Authentication (JWT Tokens):
- POST /api/v1/users/auth/login - Login with username/password
- POST /api/v1/users/auth/refresh - Refresh access token
- GET /api/v1/users/auth/me - Get current user profile
- POST /api/v1/users/auth/logout - Invalidate session
Automation (Auth Tokens):
- POST /api/v1/auth/tokens - Create long-lived token
- GET /api/v1/auth/tokens - List all tokens
- GET /api/v1/auth/tokens/{id} - Get token details
- PATCH /api/v1/auth/tokens/{id} - Update token configuration
- DELETE /api/v1/auth/tokens/{id} - Revoke token
Two Authentication Systems
Section titled “Two Authentication Systems”Hoody provides two distinct authentication methods:
JWT Tokens (User Sessions)
Use for: Browser sessions, interactive work
POST /api/v1/users/auth/loginReturns:
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
POST /api/v1/auth/tokensReturns:
hdy_...token (long-lived)
Characteristics:
- Long-lived (configurable)
- IP whitelist support
- Revocable anytime
- Per-token permissions
- Ideal for automation
Authentication Workflow
Section titled “Authentication Workflow”For Interactive Use (Browser/CLI)
Section titled “For Interactive Use (Browser/CLI)”Step 1: Login
# Login with email and password (or use --username instead of --email)hoody auth login --email you@example.com --password your_passwordimport { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.icu' });
// Login with credentials — pass `email` OR `username` (not both required)const auth = await client.api.authentication.login({ email: 'you@example.com', password: 'your_password'});console.log(auth.data.token); // JWT access tokenconsole.log(auth.data.refreshToken); // Refresh token# Provide EITHER email OR username (only password is required)curl -X POST "https://api.hoody.icu/api/v1/users/auth/login" \ -H "Content-Type: application/json" \ -d '{"email": "you@example.com", "password": "your_password"}'# Alternative: -d '{"username": "your_username", "password": "your_password"}' 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
# CLI stores the token automatically after loginhoody projects list// Pass token to client constructorconst client = new HoodyClient({ baseURL: 'https://api.hoody.icu', token: auth.data.token});const projects = await client.api.projects.list();# Include token in Authorization headercurl "https://api.hoody.icu/api/v1/projects/" \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."Step 3: Refresh When Expired
# CLI handles token refresh automatically# If your session expired, simply re-loginhoody auth login --username your_username --password your_password// The refresh token is the bearer credential for this call —// build a client whose token IS the refresh tokenconst refreshClient = new HoodyClient({ baseURL: 'https://api.hoody.icu', token: auth.data.refreshToken});const refreshed = await refreshClient.api.authentication.refreshToken({ refreshToken: auth.data.refreshToken});console.log(refreshed.data.token); // New access token# The refresh token goes in BOTH the Authorization header and the bodycurl -X POST "https://api.hoody.icu/api/v1/users/auth/refresh" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $REFRESH_TOKEN" \ -d '{"refreshToken": "'"$REFRESH_TOKEN"'"}'The refresh token must be sent both in the Authorization: Bearer header and in the refreshToken body field. The server verifies the header token and rejects the request with 401 Invalid refresh token if the two do not match byte for byte.
Returns: New access token + new refresh token (both rotated for security)
For Automation & AI (Recommended)
Section titled “For Automation & AI (Recommended)”Step 1: Create Auth Token (one-time setup)
# Login firsthoody auth login --username your_username --password your_password
# Create a long-lived automation token with IP whitelisthoody auth create \ --alias "Production Automation Token" \ --ip-whitelist "203.0.113.10,203.0.113.20" \ --expires-at "2027-04-12T00:00:00Z"import { HoodyClient } from 'hoody-sdk';
// Login first to get JWTconst client = new HoodyClient({ baseURL: 'https://api.hoody.icu' });const auth = await client.api.authentication.login({ username: 'your_username', password: 'your_password'});
// Create auth token using JWTconst jwtClient = new HoodyClient({ baseURL: 'https://api.hoody.icu', token: auth.data.token });const token = await jwtClient.api.authTokens.create({ alias: 'Production Automation Token', ip_whitelist: ['203.0.113.10', '203.0.113.20'], expires_at: '2027-04-12T00:00:00Z'});console.log(token.data.token); // hdy_... — save this immediately!# Login to get JWTcurl -X POST "https://api.hoody.icu/api/v1/users/auth/login" \ -H "Content-Type: application/json" \ -d '{"username": "your_username", "password": "your_password"}'
# Create a long-lived automation tokencurl -X POST "https://api.hoody.icu/api/v1/auth/tokens" \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{ "alias": "Production Automation Token", "ip_whitelist": ["203.0.113.10", "203.0.113.20"], "expires_at": "2027-04-12T00:00:00Z" }' 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)
# Store token and use with CLIexport HOODY_TOKEN="hdy_abc123XyZ456..."
# All subsequent commands use this tokenhoody projects listhoody containers list// Use auth token in SDK clientconst client = new HoodyClient({ baseURL: 'https://api.hoody.icu', token: process.env.HOODY_TOKEN // hdy_abc123XyZ456...});
// All API calls are authenticatedconst projects = await client.api.projects.list();# Store in environment variable (never hardcode)export HOODY_TOKEN="hdy_abc123XyZ456..."
# Use in all API requestscurl "https://api.hoody.icu/api/v1/projects/" \ -H "Authorization: Bearer $HOODY_TOKEN"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)
Auth Token Management
Section titled “Auth Token Management”Creating Tokens with Security Features
Section titled “Creating Tokens with Security Features”alias accepts letters, digits, spaces, _ and - only (1 to 254 characters). Slashes and parentheses are rejected with 400.
IP Whitelisting:
Multiple formats for expiration:
{ "expires_at": "2026-12-31T23:59:59Z"}{ "expires_at": 1735689599}{ "expires_at": "tomorrow"}Accepts today (expires at midnight tonight) or tomorrow (expires in 24 hours).
{ "alias": "Production Services"}Omit expires_at entirely on create — the token then never expires. "expires_at": null is only valid on PATCH /api/v1/auth/tokens/{id}, to clear an existing expiry.
Listing and Auditing Tokens
Section titled “Listing and Auditing Tokens”# List all your auth tokenshoody auth listconst tokens = await client.api.authTokens.list();tokens.data.forEach(t => { console.log(t.alias, t.is_enabled, t.last_used_at);});curl "https://api.hoody.icu/api/v1/auth/tokens" \ -H "Authorization: Bearer $HOODY_TOKEN" 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_atto identify unused tokens - Verify
last_used_ipmatches expected sources - Review
ip_whitelistrestrictions
Revoking Tokens
Section titled “Revoking Tokens”Disable without deleting:
# The CLI cannot disable tokens — '--is-enabled' is presence-only (always sets to true).# Use the SDK or HTTP tab to disable. To re-enable a disabled token:hoody auth update $TOKEN_ID --is-enabledawait client.api.authTokens.update(tokenId, { is_enabled: false });curl -X PATCH "https://api.hoody.icu/api/v1/auth/tokens/$TOKEN_ID" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"is_enabled": false}' Permanently delete:
# Permanently delete a tokenhoody auth delete $TOKEN_IDawait client.api.authTokens.delete(tokenId);curl -X DELETE "https://api.hoody.icu/api/v1/auth/tokens/$TOKEN_ID" \ -H "Authorization: Bearer $HOODY_TOKEN" Security Best Practices
Section titled “Security Best Practices”1. Never Hardcode Credentials
Section titled “1. Never Hardcode Credentials”// NEVER do thisconst response = await fetch('https://api.hoody.icu/api/v1/projects/', { headers: { 'Authorization': 'Bearer hdy_abc123hardcoded' }});// Use environment variablesconst response = await fetch('https://api.hoody.icu/api/v1/projects/', { headers: { 'Authorization': `Bearer ${process.env.HOODY_TOKEN}` }});2. Use IP Whitelisting
Section titled “2. Use IP Whitelisting”Restrict tokens to specific sources:
If token is leaked: It won’t work from other IPs.
3. Set Expiration Appropriately
Section titled “3. Set Expiration Appropriately”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.
4. Create Dedicated Tokens per Service
Section titled “4. Create Dedicated Tokens per Service”Don’t share one token across multiple systems:
# Create separate tokensPOST /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.
For AI Agents
Section titled “For AI Agents”The Auth Token system is designed for AI orchestration:
// AI agent configuration (environment variables)const HOODY_TOKEN = process.env.HOODY_TOKEN; // hdy_... tokenconst HOODY_API = 'https://api.hoody.icu';
// AI can now orchestrate infrastructureasync 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}/api/v1/terminal/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:
- A Hoody Auth Token (environment variable)
- Understanding of HTTP (it already has this)
No SDK. No training. Just HTTP.
Token Comparison
Section titled “Token Comparison”| Feature | JWT (Login) | Auth Token (hdy_…) |
|---|---|---|
| Lifetime | 1 day (access) 7 days (refresh) | Configurable (ISO 8601 date, “today”, “tomorrow”, or forever) |
| Use Case | User sessions | Automation, AI, scripts |
| Refresh | Yes (via refresh token) | No (create new when expired) |
| IP Whitelist | No | Yes (optional) |
| Revocation | Logout endpoint | Delete or disable |
| Visibility | Managed by browser | One-time show during creation |
| Security | Short-lived = more secure | Long-lived but IP-restricted |
Complete Authentication Examples
Section titled “Complete Authentication Examples”Example 1: User Login Flow
Section titled “Example 1: User Login Flow”# 1. LoginLOGIN_RESPONSE=$(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": { ... }# }# }
# Capture the access and refresh tokens for steps 2-4ACCESS_TOKEN=$(printf '%s' "$LOGIN_RESPONSE" | jq -r '.data.token')REFRESH_TOKEN=$(printf '%s' "$LOGIN_RESPONSE" | jq -r '.data.refreshToken')
# 2. Use access token (valid 1 day)curl "https://api.hoody.icu/api/v1/projects/" \ -H "Authorization: Bearer $ACCESS_TOKEN"
# 3. Refresh before expiration (within 7 days)# The refresh token goes in BOTH the header and the body — they must matchcurl -X POST "https://api.hoody.icu/api/v1/users/auth/refresh" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $REFRESH_TOKEN" \ -d '{"refreshToken": "'"$REFRESH_TOKEN"'"}'
# 4. Logout (optional, invalidates session)curl -X POST "https://api.hoody.icu/api/v1/users/auth/logout" \ -H "Authorization: Bearer $ACCESS_TOKEN"Example 2: Create Automation Token
Section titled “Example 2: Create Automation Token”# 1. Login to get JWTcurl -X POST "https://api.hoody.icu/api/v1/users/auth/login" \ -d '{"username": "your_username", "password": "your_password"}' \ > login.json
# Extract JWTJWT=$(cat login.json | jq -r '.data.token')
# 2. Create auth token for CI/CDcurl -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 tokenAUTH_TOKEN=$(cat token.json | jq -r '.data.token')
# 3. Save to GitHub Secrets as HOODY_TOKENecho "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"Example 3: AI Agent Setup
Section titled “Example 3: AI Agent Setup”// AI agent configuration fileHOODY_TOKEN=hdy_abc123def456...HOODY_PROJECT_ID=63f8b0e5c9a1b2d3e4f5a6b7
// agent.jsimport '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}/api/v1/terminal/execute`, { method: 'POST', body: JSON.stringify({ command }) }); }}
// AI uses this class for ALL Hoody operationsconst agent = new HoodyAgent();await agent.spawnContainer('ai-workspace', { serverId: 'server-123' });The AI only needs environment variables. No password handling. No credential management.
Managing Tokens
Section titled “Managing Tokens”List All Tokens
Section titled “List All Tokens” Update Token Configuration
Section titled “Update Token Configuration”Change IP whitelist:
Extend expiration:
Temporarily disable:
Token Rotation Strategy
Section titled “Token Rotation Strategy”Best practice for production:
# 1. Create new tokenNEW_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 unusedcurl "https://api.hoody.icu/api/v1/auth/tokens/{old_token_id}" \ -H "Authorization: Bearer YOUR_JWT" \ | jq '.data.last_used_at'
# 5. Delete old tokencurl -X DELETE "https://api.hoody.icu/api/v1/auth/tokens/{old_token_id}" \ -H "Authorization: Bearer YOUR_JWT"Common Patterns
Section titled “Common Patterns”Pattern 1: Short-Lived Scripts
Section titled “Pattern 1: Short-Lived Scripts”For one-time operations:
Use the token for your migration, and it will auto-expire tomorrow.
Pattern 2: Per-Environment Tokens
Section titled “Pattern 2: Per-Environment Tokens”Different tokens for different environments:
Development token (permissive):
Staging token (IP-restricted):
Production token (strict):
Pattern 3: Emergency Revocation
Section titled “Pattern 3: Emergency Revocation”If a token is compromised:
1. Immediately disable:
2. Create replacement with different IP whitelist:
3. Update services, then delete old token:
Useful Questions
Section titled “Useful Questions”Should I use JWT tokens or Auth Tokens for my scripts?
Section titled “Should I use JWT tokens or Auth Tokens for my scripts?”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.
Can I create an Auth Token using another Auth Token?
Section titled “Can I create an Auth Token using another Auth Token?”Yes, if the parent token holds the resources.create_tokens permission. Such a token can mint child tokens whose permissions are clamped to a subset of its own (each child’s delegation_depth is the parent’s plus one; 0 means account-minted from a JWT). Tokens without resources.create_tokens — including any token created with deny_reauthorization: true, which strips that permission — cannot create more tokens, so leave it off for leaf tokens you hand to scripts.
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.
What happens when my JWT access token expires?
Section titled “What happens when my JWT access token expires?”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_idsis non-empty (orallow_no_realm: false), use realm-scoped hosts likehttps://{realmId}.api.hoody.icu. - Use
GET /api/v1/auth/tokens/meonhttps://api.hoody.icuto discover allowed realms before selecting a realm host.
How secure are Auth Tokens with no IP whitelist?
Section titled “How secure are Auth Tokens with no IP whitelist?”Without IP whitelist, a leaked token can be used from anywhere. While the token itself is cryptographically strong (a long random string), IP whitelisting adds defense-in-depth. Use it for production tokens, skip it for low-sensitivity automation.
Can Auth Tokens expire while my script is running?
Section titled “Can Auth Tokens expire while my script is running?”Yes. If a long-running script spans the expiration time, it will start getting 401 errors. For long processes, use a far-future ISO 8601 date, or omit expires_at at creation so the token never expires — 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.
Can I use Auth Tokens with realm-scoped APIs?
Section titled “Can I use Auth Tokens with realm-scoped APIs?”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_idsare restricted to those realm IDs. - Tokens with
allow_no_realm: falsecannot use base host for resource operations. - Realm-restricted tokens can still call
GET /api/v1/auth/tokens/meon base host to bootstrap realm discovery.
How do I rotate Auth Tokens for zero-downtime updates?
Section titled “How do I rotate Auth Tokens for zero-downtime updates?”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.
Troubleshooting
Section titled “Troubleshooting”Login Fails (Invalid Credentials)
Section titled “Login Fails (Invalid Credentials)”Problem: Login returns 401 with “Invalid credentials”
Solutions:
- Verify username/password:
- Check account status:
- Account might be banned (
is_banned: true) - Contact support if legitimate user
- Account might be banned (
JWT Token Expired
Section titled “JWT Token Expired”Problem: Requests return 401 after some time
Cause: Access token expires after 1 day
Solution - Use refresh token:
# The refresh token goes in BOTH the header and the body — they must matchcurl -X POST "https://api.hoody.icu/api/v1/users/auth/refresh" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $REFRESH_TOKEN" \ -d '{"refreshToken": "'"$REFRESH_TOKEN"'"}'Returns new access token + new refresh token.
Refresh token expired? (after 7 days) - Login again
Auth Token Not Working
Section titled “Auth Token Not Working”Problem: hdy_... token returns 403 Forbidden
Check IP whitelist:
Compare the ip_whitelist array with your current IP (run curl https://ifconfig.me in terminal).
Solution - Update whitelist:
Token Creation Returns 403 “Auth tokens cannot manage auth tokens”
Section titled “Token Creation Returns 403 “Auth tokens cannot manage auth tokens””Problem: Creating an Auth Token with another Auth Token returns 403
Cause: The calling token does not hold the resources.create_tokens permission. Tokens created with deny_reauthorization: true never hold it.
Solution: Grant resources.create_tokens when you create the parent token, or mint the child from a JWT obtained via login:
# 1. Login firstcurl -X POST "https://api.hoody.icu/api/v1/users/auth/login" \ -d '{"username": "your_username", "password": "your_password"}' \ > login.json
# 2. Extract JWTJWT=$(cat login.json | jq -r '.data.token')
# 3. Create Auth Token with JWTcurl -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"}'Lost Auth Token Value
Section titled “Lost Auth Token Value”Problem: Created token but didn’t save the hdy_... value
Reality: Cannot retrieve token value after creation
Solution:
- Disable the old token:
- Create new token:
Automation Breaking Randomly
Section titled “Automation Breaking Randomly”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:
Solutions:
-
Use CIDR range instead of single IP: Instead of
"203.0.113.50/32", use"203.0.113.0/24"(allows entire subnet) -
Remove IP whitelist for non-sensitive automation:
To lift IP restrictions, set ip_whitelist to ["*"] (allow all). An empty array is rejected with 400 — the array form requires at least one entry.
- Use static IP for automation servers
What’s Next
Section titled “What’s Next”Now that you can authenticate:
- Create Projects - Organize your containers
- Spawn Containers - Create your first HTTP computer
- Configure Networking - Set up routing and firewall
- 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.