Skip to content
Hoody.com

The User Profile Management API covers four areas of per-account functionality: retrieving and updating a user profile, inspecting per-account activity logs (including usage stats), checking and claiming beta free-tier server entitlement, and managing the encrypted user vault. Use these endpoints whenever you need to read a profile, modify account fields (alias, password, public key), audit request history, gate new server claims on invite codes, or store and retrieve secrets per-user or per-realm.

Authentication is a JWT in the Authorization header as Bearer <token> (or an auth token with the appropriate permissions). Note that tokens lacking the resources.read_account permission receive a reduced profile response in which email and other account PII are omitted.


Retrieve a user profile by ID. Admins can view any user; regular users can only view their own profile. This endpoint works even for banned users (read-only access). When authenticated with an auth token that lacks the resources.read_account permission, the response is reduced to identity fields; email and other account PII are omitted.

NameInTypeRequiredDescription
idpathstringYesUser ID to retrieve
{
"statusCode": 200,
"message": "User retrieved successfully",
"data": {
"id": "507f1f77bcf86cd799439011",
"username": "john_doe",
"alias": "John Doe",
"email": "john.doe@example.com",
"public_key": "a1b2c3d4e5f6789012345678901234567890abcdefabcdefabcdefabcdef1234",
"metadata": {
"department": "Engineering",
"location": "San Francisco"
},
"is_admin": false,
"is_banned": false,
"email_verified": true,
"avatar_url": "https://avatars.githubusercontent.com/u/1234567",
"signup_method": "email",
"free_tier_unlocked": true,
"free_tier_unlocked_at": "2026-06-24T10:00:00.000Z",
"free_tier_unlock_source": "invite_code",
"onboarding": {
"hub_tour_v1": "2026-06-30T10:00:00.000Z"
},
"created_at": "2024-12-01T10:00:00.000Z",
"updated_at": "2025-01-15T10:30:00.000Z"
}
}
const user = await client.api.users.get("507f1f77bcf86cd799439011");
Terminal window
curl https://api.hoody.icu/api/v1/users/507f1f77bcf86cd799439011 \
-H "Authorization: Bearer <token>"

Update a user profile. Regular users can update their own alias and password (with current_password verification). Admins can update any user and set is_admin / is_banned flags. When authenticated with an auth token that lacks the resources.read_account permission, the returned profile is reduced to identity fields; email and other account PII are omitted.

NameInTypeRequiredDescription
idpathstringYesUser ID to update
FieldTypeRequiredDescription
aliasstringNoNew display name/alias (1-100 characters)
public_keystringNoED25519 public key (exactly 64 hexadecimal characters)
metadataobjectNoCustom metadata object for storing additional user information
passwordstringNoNew password. Must be at least 12 characters, 3 of 4 character classes. Requires current_password for verification.
current_passwordstringNoCurrent password (required when setting a new password)
is_adminbooleanNoAdmin status (read-only)
is_bannedbooleanNoBan status. Banned users cannot access the API.
{
"statusCode": 200,
"message": "User updated successfully",
"data": {
"id": "507f1f77bcf86cd799439011",
"username": "john_doe",
"alias": "John Smith",
"email": "john.doe@example.com",
"public_key": "a1b2c3d4e5f6789012345678901234567890abcdefabcdefabcdefabcdef1234",
"metadata": {},
"is_admin": false,
"is_banned": false,
"email_verified": true,
"avatar_url": "https://avatars.githubusercontent.com/u/1234567",
"signup_method": "email",
"free_tier_unlocked": true,
"free_tier_unlocked_at": "2026-06-24T10:00:00.000Z",
"free_tier_unlock_source": "invite_code",
"onboarding": {
"hub_tour_v1": "2026-06-30T10:00:00.000Z"
},
"created_at": "2024-12-01T10:00:00.000Z",
"updated_at": "2025-01-15T14:45:00.000Z"
}
}
const updated = await client.api.users.update("507f1f77bcf86cd799439011", {
alias: "John Smith",
metadata: { department: "Engineering", location: "Remote" }
});
Terminal window
curl -X PUT https://api.hoody.icu/api/v1/users/507f1f77bcf86cd799439011 \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"alias": "John Smith",
"metadata": { "department": "Engineering", "location": "Remote" }
}'

Idempotently records a per-account onboarding/UI milestone (key -> ISO timestamp). Used by the /auth/home first-run tour (milestone hub_tour_v1) and reusable for any future onboarding step. Setting an already-present milestone is a no-op (the original timestamp is kept).

This endpoint takes no parameters.

FieldTypeRequiredDescription
milestonestringYesMilestone key (e.g. hub_tour_v1). Must match `^(hub_tour_v[0-9]3
{
"statusCode": 200,
"message": "Onboarding milestone recorded",
"data": {
"onboarding": {
"hub_tour_v1": "2026-06-30T10:00:00.000Z"
}
}
}
const result = await client.api.users.markOnboardingMilestone({
milestone: "hub_tour_v1"
});
Terminal window
curl -X POST https://api.hoody.icu/api/v1/users/me/onboarding \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{ "milestone": "hub_tour_v1" }'

Retrieve activity logs for the authenticated user with optional filtering by date range, status code, HTTP method, and realm.

NameInTypeRequiredDescription
pagequeryintegerNoPage number (default: 1)
limitqueryintegerNoResults per page (default: 50)
start_datequerystringNoFilter logs after this date
end_datequerystringNoFilter logs before this date
errors_onlyquerystringNoShow only errors (status ≥ 400). Accepted values: "true", "false".
min_statusqueryintegerNoMinimum status code
max_statusqueryintegerNoMaximum status code
methodquerystringNoFilter by HTTP method. Accepted values: "GET", "POST", "PUT", "PATCH", "DELETE".
realm_idquerystringNoFilter by realm ID
{
"statusCode": 200,
"message": "Activity logs retrieved successfully",
"data": [
{
"id": "1a9c3592695c087a8f35ceae",
"user_id": "507f1f77bcf86cd799439011",
"realm_id": "507f1f77bcf86cd799439011",
"method": "GET",
"path": "/api/v1/projects",
"status_code": 200,
"ip_address": "192.168.1.1",
"user_agent": "Mozilla/5.0...",
"created_at": "2025-11-18T20:00:00Z"
},
{
"id": "1a9c3592695c087a8f35ceaf",
"user_id": "507f1f77bcf86cd799439011",
"realm_id": "507f1f77bcf86cd799439011",
"method": "POST",
"path": "/api/v1/containers",
"status_code": 201,
"ip_address": "192.168.1.1",
"user_agent": "Mozilla/5.0...",
"created_at": "2025-11-18T20:01:00Z"
}
],
"metadata": {
"total": 100,
"page": 1,
"limit": 50,
"pages": 2
}
}
const logs = await client.api.activity.listIterator({
page: 1,
limit: 50,
errors_only: "true"
});
Terminal window
curl "https://api.hoody.icu/api/v1/users/auth/activity?errors_only=true&limit=20" \
-H "Authorization: Bearer <token>"

Retrieve storage usage statistics for activity logs, including total bytes, total records, and the configured retention window.

This endpoint takes no parameters.

{
"statusCode": 200,
"message": "Activity stats retrieved successfully",
"data": {
"total_size_bytes": 1048576,
"total_records": 5000,
"oldest_record": "2025-10-18T20:00:00Z",
"newest_record": "2025-11-18T20:00:00Z",
"retention_days": 30
}
}
const stats = await client.api.activity.getStats();
Terminal window
curl https://api.hoody.icu/api/v1/users/auth/activity/stats \
-H "Authorization: Bearer <token>"

Returns whether the beta gate is enabled, whether this account is unlocked, whether it already owns a free server, and an advisory, point-in-time reason a claim is currently blocked. Never attempts a claim. Auth-token callers must hold the resources.read_account permission (account-level status); tokens without it receive 403. JWT/owner callers are unaffected.

This endpoint takes no parameters.

{
"statusCode": 200,
"message": "Free-tier status retrieved",
"data": {
"gate_enabled": true,
"unlocked": false,
"has_free_server": false,
"can_claim": false,
"claim_blocked_reason": "invite_required",
"signup_code_failed": false
}
}

The claim_blocked_reason field is an advisory snapshot and follows the priority: already_claimed -> invite_required -> pool_empty -> none. The signup_code_failed flag is true only while invite_required AND the invite code memorized from the signup link definitively failed to redeem (already used, disabled, expired, or unknown) — the UI should explain the dead link and offer manual entry.

const status = await client.api.users.getFreeTierStatus();
Terminal window
curl https://api.hoody.icu/api/v1/users/me/free-tier-status \
-H "Authorization: Bearer <token>"

Redeem a single-use invite code to unlock free-tier server claiming during the beta, then immediately provision a free server. Idempotent if already unlocked. No-op (no code consumed) when the beta gate is disabled. Requires a first-party JWT; auth tokens, HTTP Basic, and impersonation are rejected.

This endpoint takes no parameters.

FieldTypeRequiredDescription
codestringYesThe invite code (case/format-insensitive). 1-64 characters.
{
"statusCode": 200,
"message": "Invite code redeemed. Your free server is being prepared.",
"data": {
"unlocked": true,
"required": true,
"claim_blocked_reason": "none",
"server": {
"id": "srv_01HZ8J9YEXAMPLE",
"name": "free-tier-507f1f77bcf86cd799439011",
"region": "us-east-1"
},
"project": {
"id": "507f1f77bcf86cd799439012",
"name": "Default Project"
},
"container": null
}
}

The container field is created asynchronously and may be null at the time of redemption. claim_blocked_reason is pool_empty if the account was unlocked but no server could be claimed yet (retry shortly); otherwise none.

const result = await client.api.users.redeemInviteCode({
code: "HOODY-7Q4K-9F2M-3B8T-XR5W-2HKD-1"
});
Terminal window
curl -X POST https://api.hoody.icu/api/v1/users/me/redeem-invite \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{ "code": "HOODY-7Q4K-9F2M-3B8T-XR5W-2HKD-1" }'

Manually claim a free-tier server and create the default project and container. Idempotent — safe to call if already provisioned. If the beta gate is enabled and the account has not been unlocked, this endpoint returns 403 with FREE_TIER_INVITE_REQUIRED; redeem an invite code at POST /api/v1/users/me/redeem-invite first.

This endpoint takes no parameters.

FieldTypeRequiredDescription
regionstringNoOptional preferred region override (lowercase letters, digits, and hyphens; up to 50 characters).
{
"statusCode": 200,
"data": {
"server": {
"id": "srv_01HZ8J9YEXAMPLE",
"name": "free-tier-507f1f77bcf86cd799439011",
"region": "us-east-1"
},
"project": {
"id": "507f1f77bcf86cd799439012",
"name": "Default Project"
},
"container": {
"id": "507f1f77bcf86cd799439013",
"name": "default-container"
},
"blocked_reason": null
}
}

The blocked_reason field is set when automatic repair cannot proceed: admin_recovery_required indicates that the default container is quarantined (needs_reclaim) and only an admin reclaim can replace it.

const result = await client.api.users.retrySetup({
region: "us-east-1"
});
Terminal window
curl -X POST https://api.hoody.icu/api/v1/users/me/retry-setup \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{ "region": "us-east-1" }'

The encrypted vault stores per-user or per-realm key-value pairs. Values can be any UTF-8 string (JSON, encrypted data, plain text) — the API does not validate content, so encrypt sensitive data before storing it. Per-realm scoping is controlled via the realm_id query parameter (a 24-character hex ID). When omitted and not on a realm subdomain, the vault defaults to global scope (realm_id = ""). The realm_id value is case-insensitive; uppercase is normalized to lowercase.

List all keys in your encrypted vault with metadata (key names, sizes, timestamps). Values are not included in this response — use GET /api/v1/vault/keys/{key} to retrieve individual values.

NameInTypeRequiredDescription
realm_idquerystringNoTarget a specific realm (24-char hex). When omitted and not on a realm subdomain, defaults to global scope (realm_id = ""). Case-insensitive — uppercase is normalized to lowercase.
{
"statusCode": 200,
"message": "Vault keys retrieved successfully",
"data": [
{
"key": "my-encrypted-notes",
"realm_id": "507f1f77bcf86cd799439011",
"metadata": {},
"size_bytes": 2048,
"created_at": "2025-11-14T18:00:00.000Z",
"updated_at": "2025-11-14T18:15:00.000Z"
},
{
"key": "config.json",
"realm_id": "",
"metadata": null,
"size_bytes": 512,
"created_at": "2025-11-14T17:30:00.000Z",
"updated_at": "2025-11-14T17:30:00.000Z"
}
]
}
const keys = await client.api.vault.listIterator({
realm_id: "507f1f77bcf86cd799439011"
});
Terminal window
curl "https://api.hoody.icu/api/v1/vault/keys?realm_id=507f1f77bcf86cd799439011" \
-H "Authorization: Bearer <token>"

Retrieve a specific key-value pair from your encrypted vault by key name.

NameInTypeRequiredDescription
keypathstringYesVault key name (alphanumeric, dots, underscores, hyphens)
realm_idquerystringNoTarget a specific realm (24-char hex). When omitted and not on a realm subdomain, defaults to global scope (realm_id = ""). Case-insensitive — uppercase is normalized to lowercase.
{
"statusCode": 200,
"message": "Vault key retrieved successfully",
"data": {
"key": "my-encrypted-notes",
"realm_id": "507f1f77bcf86cd799439011",
"value": "{\"notes\": \"My important notes\", \"encrypted\": true}",
"metadata": {},
"size_bytes": 53,
"created_at": "2025-11-14T18:00:00.000Z",
"updated_at": "2025-11-14T18:15:00.000Z"
}
}
const entry = await client.api.vault.get("my-encrypted-notes", {
realm_id: "507f1f77bcf86cd799439011"
});
Terminal window
curl "https://api.hoody.icu/api/v1/vault/keys/my-encrypted-notes?realm_id=507f1f77bcf86cd799439011" \
-H "Authorization: Bearer <token>"

Create or update a key-value pair in your personal encrypted vault. Values can be any UTF-8 string — the API does not validate content, so encrypt sensitive data before storing it. Returns 200 if the key was updated, 201 if it was created.

NameInTypeRequiredDescription
keypathstringYesVault key name (alphanumeric, dots, underscores, hyphens)
realm_idquerystringNoTarget a specific realm (24-char hex). When omitted and not on a realm subdomain, defaults to global scope (realm_id = ""). Case-insensitive — uppercase is normalized to lowercase.
FieldTypeRequiredDescription
valuestringYesValue to store. Any UTF-8 string — JSON, encrypted data, plain text.
metadataobjectNoOptional JSON metadata (max 256 KB). Counts toward your total vault storage limit. Useful for storing content-type, filename, upload date, etc.
{
"statusCode": 200,
"message": "Vault key updated successfully",
"data": {
"key": "my-encrypted-notes",
"realm_id": "507f1f77bcf86cd799439011",
"value": "{\"notes\": \"My important notes\", \"encrypted\": true}",
"metadata": {},
"size_bytes": 53,
"created_at": "2025-11-14T18:00:00.000Z",
"updated_at": "2025-11-14T18:15:00.000Z"
}
}
const stored = await client.api.vault.set("my-encrypted-notes", {
value: "{\"notes\": \"My important notes\", \"encrypted\": true}",
metadata: { filename: "api-keys.json", content_type: "application/json" }
}, {
realm_id: "507f1f77bcf86cd799439011"
});
Terminal window
curl -X PUT "https://api.hoody.icu/api/v1/vault/keys/my-encrypted-notes?realm_id=507f1f77bcf86cd799439011" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"value": "{\"notes\": \"My important notes\", \"encrypted\": true}",
"metadata": { "filename": "api-keys.json", "content_type": "application/json" }
}'

Permanently delete a key-value pair from your encrypted vault. This action cannot be undone.

NameInTypeRequiredDescription
keypathstringYesVault key name (alphanumeric, dots, underscores, hyphens)
realm_idquerystringNoTarget a specific realm (24-char hex). When omitted and not on a realm subdomain, defaults to global scope (realm_id = ""). Case-insensitive — uppercase is normalized to lowercase.
{
"statusCode": 200,
"message": "Vault key deleted successfully"
}
await client.api.vault.delete("my-encrypted-notes", {
realm_id: "507f1f77bcf86cd799439011"
});
Terminal window
curl -X DELETE "https://api.hoody.icu/api/v1/vault/keys/my-encrypted-notes?realm_id=507f1f77bcf86cd799439011" \
-H "Authorization: Bearer <token>"

Retrieve statistics about your vault usage. total_keys and total_size_bytes are scoped to the current realm. limit_mb, remaining_mb, and used_percentage reflect global vault usage across all realms.

NameInTypeRequiredDescription
realm_idquerystringNoTarget a specific realm (24-char hex). When omitted and not on a realm subdomain, defaults to global scope (realm_id = ""). Case-insensitive — uppercase is normalized to lowercase.
{
"statusCode": 200,
"message": "Vault statistics retrieved successfully",
"data": {
"total_keys": 5,
"total_size_bytes": 10240,
"total_size_mb": 0.009766,
"limit_mb": 50,
"used_percentage": 0.02,
"remaining_mb": 49.990234
}
}
const stats = await client.api.vault.getStats({
realm_id: "507f1f77bcf86cd799439011"
});
Terminal window
curl "https://api.hoody.icu/api/v1/vault/stats?realm_id=507f1f77bcf86cd799439011" \
-H "Authorization: Bearer <token>"

NameInTypeRequiredDescription
realm_idquerystringNoTarget a specific realm (24-char hex). When omitted and not on a realm subdomain, defaults to global scope (realm_id = ""). Case-insensitive — uppercase is normalized to lowercase.
{
"statusCode": 200,
"message": "Vault cleared successfully",
"data": {
"deleted_count": 5
}
}
await client.api.vault.clear({
realm_id: "507f1f77bcf86cd799439011"
});
Terminal window
curl -X DELETE "https://api.hoody.icu/api/v1/vault?realm_id=507f1f77bcf86cd799439011" \
-H "Authorization: Bearer <token>"