Skip to content
Hoody.com

API tokens are long-lived credentials for programmatic access to the Hoody control plane. Use these endpoints to list, mint, scope, delegate, copy, update, and revoke tokens that authenticate against the platform. The control plane identifies itself with these tokens in the Authorization header as Bearer <token>.

GET /api/v1/auth/tokens

Returns every auth token for the authenticated account. Token secret values are never included. When delegated-token subtree listings are capped, truncated is true and the returned array is incomplete.

This endpoint takes no parameters and accepts no request body.

Terminal window
curl -X GET "https://api.hoody.com/api/v1/auth/tokens" \
-H "Authorization: Bearer <token>"
{
"statusCode": 200,
"message": "Auth tokens retrieved successfully",
"data": [
{
"id": "507f1f77bcf86cd799439011",
"alias": "Production API Key",
"prefix": "hdy_",
"ip_whitelist": ["192.168.1.0/24", "10.0.0.1"],
"realm_ids": [],
"allow_no_realm": true,
"expires_at": "2025-12-31T23:59:59.000Z",
"is_enabled": true,
"vault_access": true,
"event_access": true,
"last_used_at": "2025-10-28T12:00:00.000Z",
"last_used_ip": "198.51.100.1",
"created_at": "2025-01-15T10:30:00.000Z",
"updated_at": "2025-01-15T14:45:00.000Z"
},
{
"id": "507f1f77bcf86cd799439022",
"alias": "Development Token",
"prefix": "hdy_",
"ip_whitelist": ["*"],
"realm_ids": ["507f1f77bcf86cd799439011"],
"allow_no_realm": false,
"expires_at": null,
"is_enabled": true,
"vault_access": false,
"event_access": true,
"last_used_at": null,
"last_used_ip": null,
"created_at": "2025-01-10T08:00:00.000Z",
"updated_at": "2025-01-10T08:00:00.000Z"
}
]
}

Each entry also carries public_key, public_storage, permissions, created_by_token_id, delegation_depth, and truncated. truncated is present and true only when the list of delegated tokens was too large to return in full.

GET /api/v1/auth/tokens/{id}

Returns the metadata for a single token. Token secret values are not included.

NameInTypeRequiredDescription
idpathstringYesUnique identifier of the token
Terminal window
curl -X GET "https://api.hoody.com/api/v1/auth/tokens/507f1f77bcf86cd799439011" \
-H "Authorization: Bearer <token>"
{
"statusCode": 200,
"message": "Auth token retrieved successfully",
"data": {
"id": "507f1f77bcf86cd799439011",
"alias": "Production API Key",
"prefix": "hdy_",
"ip_whitelist": ["192.168.1.0/24", "10.0.0.1"],
"realm_ids": [],
"allow_no_realm": true,
"expires_at": "2025-12-31T23:59:59.000Z",
"is_enabled": true,
"vault_access": true,
"event_access": true,
"last_used_at": "2025-10-28T12:00:00.000Z",
"last_used_ip": "198.51.100.1",
"created_at": "2025-01-15T10:30:00.000Z",
"updated_at": "2025-01-15T14:45:00.000Z"
}
}

data also carries public_key, public_storage, permissions, created_by_token_id, and delegation_depth.

GET /api/v1/auth/tokens/me

Introspects the token used for the current request and returns its metadata, plus a restrictions block that resolves the realm scope from the host the call is made on. This endpoint is allowed on the base api.hoody.com so that realm-scoped tokens can bootstrap realm discovery.

This endpoint takes no parameters and accepts no request body.

Terminal window
curl -X GET "https://api.hoody.com/api/v1/auth/tokens/me" \
-H "Authorization: Bearer <token>"
{
"statusCode": 200,
"message": "Current auth token retrieved successfully",
"data": {
"token": {
"id": "507f1f77bcf86cd799439011",
"alias": "External Customer Token",
"prefix": "hdy_",
"ip_whitelist": ["*"],
"realm_ids": ["507f1f77bcf86cd799439012"],
"allow_no_realm": false,
"permissions": {
"containers": { "read": true, "create": true },
"resources": { "realms": true }
},
"expires_at": null,
"is_enabled": true,
"vault_access": false,
"event_access": true,
"created_at": "2025-01-15T10:30:00.000Z",
"updated_at": "2025-01-15T10:30:00.000Z"
},
"restrictions": {
"has_realm_restrictions": true,
"requires_realm_scope": true,
"allowed_realm_ids": ["507f1f77bcf86cd799439012"],
"allow_no_realm": false,
"active_realm_id": "507f1f77bcf86cd799439012"
}
}
}

data.restrictions.max_delegation_depth (integer) is the deepest delegation_depth a token may have on this server; a token whose children would be deeper cannot create tokens.

Get auth token public profile by public key

Section titled “Get auth token public profile by public key”

GET /api/v1/auth/tokens/public-profiles/{public_key}

Resolves and retrieves an auth token public profile storage object by ED25519 public key.

NameInTypeRequiredDescription
public_keypathstringYesED25519 public key to resolve
Terminal window
curl -X GET "https://api.hoody.com/api/v1/auth/tokens/public-profiles/a1b2c3d4e5f6789012345678901234567890abcdefabcdefabcdefabcdef1234" \
-H "Authorization: Bearer <token>"
{
"statusCode": 200,
"message": "Public profile retrieved successfully",
"data": {
"public_key": "a1b2c3d4e5f6789012345678901234567890abcdefabcdefabcdefabcdef1234",
"public_storage": {
"display_name": "Acme Integrations",
"tier": "gold"
}
}
}

GET /api/v1/auth/tokens/templates

Lists the permission-template catalog with each template’s resolved permission tree, read-only and free of secrets. The token-creation UI uses this to offer delegation without mirroring server templates client-side.

This endpoint takes no parameters and accepts no request body.

Terminal window
curl -X GET "https://api.hoody.com/api/v1/auth/tokens/templates" \
-H "Authorization: Bearer <token>"

Returns the permission-template catalog: one entry per template.

FieldTypeDescription
statusCodenumber200
messagestringHuman-readable status message
dataarrayThe templates
data[].namestringTemplate name, as accepted by permission_template on token creation
data[].descriptionstringWhat the template is for
data[].permissionsobjectThe fully resolved permission tree the template grants, in the same shape as a token’s permissions

Abridged example: one template, with only its containers and projects branches shown; each real tree carries every branch (financial, resources, …).

{
"statusCode": 200,
"message": "Permission templates retrieved successfully",
"data": [
{
"name": "read_only",
"description": "Read-only access for monitoring",
"permissions": {
"containers": {
"create": false,
"read": true,
"update": false,
"delete": false,
"actions": { "start": false, "stop": false, "restart": false, "exec": false, "logs": true },
"features": { "ai": false, "hoody_kit": false, "snapshots": false, "networking": false, "kvm": false, "env_values": false }
},
"projects": {
"create": false,
"read": true,
"update": false,
"delete": false,
"members": { "invite": false, "remove": false, "change_roles": false }
}
}
}
]
}

containers.features.env_values decides whether a token sees the values of a container’s environment variables. The full_access and dev_team templates grant it; read_only, external_customer and finance_team do not. Without it, the environment variable endpoints list every key with a null value and values_redacted: true. It grants no access on its own: listing still needs containers.read and changing a variable still needs containers.update. Tokens that existed before this permission was introduced were granted it wherever they held containers.read or containers.update.

POST /api/v1/auth/tokens

Mints a new long-term auth token with optional IP restrictions, realm restrictions, permission template, expiration, and vault access. The full token secret is returned in the response and is the only time the secret will be shown.

A token created by another token can never hold more than that token (or any token above it). A request that would exceed it is refused with 403 TOKEN_CEILING_EXCEEDED, and data.axis names the limit it crossed. The same check applies when a delegated token is copied, updated, or has a realm added or removed.

FieldTypeRequiredDescription
aliasstringNoFriendly token alias (letters, numbers, spaces, _, -). If omitted, the server generates a random animal name
public_keystring | nullNoED25519 public key as a 64-character hex string. Pass null to clear
public_storageobject | nullNoPublic JSON profile storage attached to the public key (max 64KB serialized)
ip_whitelistarray | stringNoIPv4 addresses/CIDR ranges or *. Accepts an array or a comma-separated string. Defaults to *. At most 1000 entries, at most 65536 characters as a string
permission_templatestring | nullNoOne of full_access, external_customer, dev_team, finance_team, read_only, or null. Takes precedence over permissions
permissionsobjectNoFine-grained TokenPermissions object. Any missing path defaults to deny
realm_idsarrayNoRealm IDs this token is restricted to (24-char hex strings, at most 500)
allow_no_realmbooleanNoWhether the token can be used without a realm scope (defaults to true server-side)
vault_accessbooleanNoWhether the token can access user vault endpoints (defaults to false server-side)
event_accessbooleanNoWhether the token can access event streams and event history (defaults to true server-side)
deny_reauthorizationbooleanNoLockdown belt that strips resources.create_tokens and resources.vault, forces vault_access to false, and forces a bounded expiry. Rejected (400) when combined with an explicit create_tokens, resources.vault or vault_access grant. Defaults to false
expires_atstring | numberNoISO 8601 timestamp, Unix timestamp (seconds/ms), "today" (midnight tonight), or "tomorrow" (in 24 hours). Omit for non-expiring
otp_codestringNoTOTP code or backup code (max 12 chars). Required if 2FA is enabled and authenticating via JWT
Terminal window
curl -X POST "https://api.hoody.com/api/v1/auth/tokens" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"alias": "Production API Key",
"public_key": "a1b2c3d4e5f6789012345678901234567890abcdefabcdefabcdefabcdef1234",
"public_storage": {
"display_name": "Production Integrations",
"tier": "gold"
},
"ip_whitelist": ["192.168.1.0/24", "10.0.0.1"],
"vault_access": true,
"expires_at": 1767225599000
}'
{
"statusCode": 201,
"message": "Auth token created successfully",
"data": {
"token": "hdy_a1b2c3d4e5f67890abcdef1234567890",
"id": "507f1f77bcf86cd799439011",
"alias": "Production API Key",
"prefix": "hdy_",
"public_key": "a1b2c3d4e5f6789012345678901234567890abcdefabcdefabcdefabcdef1234",
"public_storage": {
"display_name": "Production Integrations",
"tier": "gold"
},
"ip_whitelist": ["192.168.1.0/24", "10.0.0.1"],
"realm_ids": [],
"allow_no_realm": true,
"expires_at": "2025-12-31T23:59:59.000Z",
"is_enabled": true,
"vault_access": true,
"event_access": true,
"last_used_at": null,
"last_used_ip": null,
"created_at": "2025-01-15T10:30:00.000Z",
"updated_at": "2025-01-15T10:30:00.000Z"
}
}

Warning: the recorded example above does not include data.permissions, although the response schema requires it. data.token is the full token including prefix and is shown only in this response. data also carries created_by_token_id and delegation_depth.

POST /api/v1/auth/tokens/{id}/copy

Copies an existing auth token’s configuration (permissions, realm restrictions, IP whitelist) into a new token with a new secret value. Pass an optional alias and an optional expiration override.

NameInTypeRequiredDescription
idpathstringYesUnique identifier of the token to copy
FieldTypeRequiredDescription
aliasstringNoFriendly alias for the copied token. If omitted, a deterministic alias like <source> copy is generated
expires_atstring | number | nullNoOptional expiration override. ISO 8601 timestamp, Unix timestamp (seconds/ms), today, tomorrow, or null to make the copy non-expiring. If omitted, the source expiration is copied when still in the future
otp_codestringNoTOTP code or backup code (max 12 chars). Required if 2FA is enabled and authenticating via JWT
Terminal window
curl -X POST "https://api.hoody.com/api/v1/auth/tokens/507f1f77bcf86cd799439011/copy" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{ "alias": "Production API Key Copy" }'
{
"statusCode": 201,
"message": "Auth token copied successfully",
"data": {
"token": "hdy_f0e1d2c3b4a5968778695a4b3c2d1e0f1234567890abcdef",
"id": "507f1f77bcf86cd799439099",
"alias": "Production API Key Copy",
"prefix": "hdy_",
"ip_whitelist": ["192.168.1.0/24", "10.0.0.1"],
"realm_ids": ["507f1f77bcf86cd799439011"],
"allow_no_realm": false,
"expires_at": "2025-12-31T23:59:59.000Z",
"is_enabled": true,
"vault_access": true,
"event_access": true,
"last_used_at": null,
"last_used_ip": null,
"created_at": "2025-01-20T08:30:00.000Z",
"updated_at": "2025-01-20T08:30:00.000Z"
}
}

Warning: the recorded example above does not include data.permissions, although the response schema requires it. data.token is the full copied token including prefix and is shown only in this response. data also carries public_key, public_storage, created_by_token_id, and delegation_depth.

DELETE /api/v1/auth/tokens/{id}

Permanently revokes a token. Once deleted, the secret cannot authenticate any further requests.

NameInTypeRequiredDescription
idpathstringYesUnique identifier of the token
Terminal window
curl -X DELETE "https://api.hoody.com/api/v1/auth/tokens/507f1f77bcf86cd799439011" \
-H "Authorization: Bearer <token>"
{
"statusCode": 200,
"message": "Auth token deleted successfully"
}

PUT /api/v1/auth/tokens/{id}

Updates an existing auth token in place. Supports alias, public key/profile storage, IP whitelist, permissions, realm restrictions, vault access, event access, expiration, and the enabled flag. PATCH is accepted on the same path with identical behaviour.

NameInTypeRequiredDescription
idpathstringYesUnique identifier of the token to update
FieldTypeRequiredDescription
aliasstringNoNew friendly alias (letters, numbers, spaces, _, -)
public_keystring | nullNoED25519 public key as 64-character hex string. Pass null to clear
public_storageobject | nullNoPublic JSON profile storage attached to the public key (max 64KB). Pass null to clear
ip_whitelistarray | stringNoIPv4 addresses/CIDR ranges or *. Accepts an array or a comma-separated string
permissionsobjectNoFine-grained TokenPermissions object. Any missing path defaults to deny
realm_idsarrayNoRealm IDs this token is restricted to (24-char hex strings)
allow_no_realmbooleanNoWhether the token can be used without a realm scope
vault_accessbooleanNoWhether the token can access user vault endpoints
event_accessbooleanNoWhether the token can access event streams and event history
expires_atstring | number | nullNoISO 8601 timestamp, Unix timestamp (seconds/ms), today, tomorrow, or null to make the token non-expiring
is_enabledbooleanNoEnable or disable the token
otp_codestringNoTOTP code or backup code (max 12 chars). Required if 2FA is enabled and authenticating via JWT
Terminal window
curl -X PUT "https://api.hoody.com/api/v1/auth/tokens/507f1f77bcf86cd799439011" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"alias": "Updated Production Key",
"ip_whitelist": ["*"],
"vault_access": false,
"expires_at": null,
"is_enabled": false
}'
{
"statusCode": 200,
"message": "Auth token updated successfully",
"data": {
"id": "507f1f77bcf86cd799439011",
"alias": "Updated Production Key",
"prefix": "hdy_",
"public_key": "a1b2c3d4e5f6789012345678901234567890abcdefabcdefabcdefabcdef1234",
"public_storage": {},
"ip_whitelist": ["*"],
"realm_ids": [],
"allow_no_realm": true,
"expires_at": null,
"is_enabled": false,
"vault_access": false,
"event_access": true,
"last_used_at": "2025-10-28T12:00:00.000Z",
"last_used_ip": "198.51.100.1",
"created_at": "2025-01-15T10:30:00.000Z",
"updated_at": "2025-01-15T14:45:00.000Z"
}
}

PATCH /api/v1/auth/tokens/{id}

Partial update alternative to PUT on the same path. Same request body schema and same response and error shapes.

NameInTypeRequiredDescription
idpathstringYesUnique identifier of the token to update
FieldTypeRequiredDescription
aliasstringNoNew friendly alias (letters, numbers, spaces, _, -)
public_keystring | nullNoED25519 public key as 64-character hex string. Pass null to clear
public_storageobject | nullNoPublic JSON profile storage attached to the public key (max 64KB). Pass null to clear
ip_whitelistarray | stringNoIPv4 addresses/CIDR ranges or *. Accepts an array or a comma-separated string
permissionsobjectNoFine-grained TokenPermissions object. Any missing path defaults to deny
realm_idsarrayNoRealm IDs this token is restricted to (24-char hex strings)
allow_no_realmbooleanNoWhether the token can be used without a realm scope
vault_accessbooleanNoWhether the token can access user vault endpoints
event_accessbooleanNoWhether the token can access event streams and event history
expires_atstring | number | nullNoISO 8601 timestamp, Unix timestamp (seconds/ms), today, tomorrow, or null to make the token non-expiring
is_enabledbooleanNoEnable or disable the token
otp_codestringNoTOTP code or backup code (max 12 chars). Required if 2FA is enabled and authenticating via JWT
Terminal window
curl -X PATCH "https://api.hoody.com/api/v1/auth/tokens/507f1f77bcf86cd799439011" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{ "realm_ids": ["507f1f77bcf86cd799439011"], "allow_no_realm": false }'
{
"statusCode": 200,
"message": "Auth token updated successfully",
"data": {
"id": "507f1f77bcf86cd799439011",
"alias": "Updated Production Key",
"prefix": "hdy_",
"public_key": "a1b2c3d4e5f6789012345678901234567890abcdefabcdefabcdefabcdef1234",
"public_storage": {},
"ip_whitelist": ["*"],
"realm_ids": ["507f1f77bcf86cd799439011"],
"allow_no_realm": false,
"expires_at": null,
"is_enabled": false,
"vault_access": false,
"event_access": true,
"last_used_at": "2025-10-28T12:00:00.000Z",
"last_used_ip": "198.51.100.1",
"created_at": "2025-01-15T10:30:00.000Z",
"updated_at": "2025-01-15T14:45:00.000Z"
}
}

POST /api/v1/auth/tokens/{id}/add-realm

Atomically adds a realm ID to an auth token. Idempotent: if the realm is already present the server returns success without modification.

NameInTypeRequiredDescription
idpathstringYesAuth token ID
FieldTypeRequiredDescription
realm_idstringYesRealm ID to add to the token (24-char hex string)
otp_codestringNoTOTP code or backup code (max 12 chars). Required if 2FA is enabled and authenticating via JWT
Terminal window
curl -X POST "https://api.hoody.com/api/v1/auth/tokens/507f1f77bcf86cd799439011/add-realm" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{ "realm_id": "507f1f77bcf86cd799439012" }'
{
"statusCode": 200,
"message": "Realm added to auth token successfully",
"data": {
"id": "507f1f77bcf86cd799439011",
"alias": "External Customer Token",
"prefix": "hdy_",
"ip_whitelist": ["*"],
"realm_ids": ["507f1f77bcf86cd799439012"],
"allow_no_realm": false,
"permissions": {
"containers": { "read": true, "create": true },
"resources": { "realms": true }
},
"is_enabled": true,
"vault_access": false,
"event_access": true,
"created_at": "2025-01-15T10:30:00.000Z",
"updated_at": "2025-01-15T10:30:00.000Z"
}
}

data also carries public_key, public_storage, expires_at, created_by_token_id, delegation_depth, last_used_at, and last_used_ip.

POST /api/v1/auth/tokens/{id}/remove-realm

Atomically removes a realm ID from an auth token. Idempotent: if the realm is not present the server returns success without modification. The last realm of a token restricted to realms is never removed (409 LAST_REALM_REMOVAL): an empty realm list means every realm, so removing it would widen the token instead of revoking access. Delete or disable the token to revoke it.

NameInTypeRequiredDescription
idpathstringYesAuth token ID
FieldTypeRequiredDescription
realm_idstringYesRealm ID to remove from the token (24-char hex string)
otp_codestringNoTOTP code or backup code (max 12 chars). Required if 2FA is enabled and authenticating via JWT
Terminal window
curl -X POST "https://api.hoody.com/api/v1/auth/tokens/507f1f77bcf86cd799439011/remove-realm" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{ "realm_id": "507f1f77bcf86cd799439012" }'
{
"statusCode": 200,
"message": "Realm removed from auth token successfully",
"data": {
"id": "507f1f77bcf86cd799439011",
"alias": "External Customer Token",
"prefix": "hdy_",
"ip_whitelist": ["*"],
"realm_ids": [],
"allow_no_realm": false,
"permissions": {
"containers": { "read": true, "create": true },
"resources": { "realms": true }
},
"is_enabled": true,
"vault_access": false,
"event_access": true,
"created_at": "2025-01-15T10:30:00.000Z",
"updated_at": "2025-01-15T10:30:00.000Z"
}
}

data also carries public_key, public_storage, expires_at, created_by_token_id, delegation_depth, last_used_at, and last_used_ip.

Manage the calling token’s public profile

Section titled “Manage the calling token’s public profile”

Update the current auth token’s public profile

Section titled “Update the current auth token’s public profile”

PUT /api/v1/auth/tokens/me/public-profile

Updates the calling token’s own public_key and public_storage payload. Requires the resources.auth_token_public_profile permission on the auth token. PATCH is accepted on the same path with identical behaviour.

FieldTypeRequiredDescription
public_keystring | nullNoED25519 public key (64 hex chars). Pass null to clear
public_storageobject | nullNoPublic JSON profile (max 64KB serialized). Pass null to clear

At least one of public_key or public_storage must be supplied.

Terminal window
curl -X PUT "https://api.hoody.com/api/v1/auth/tokens/me/public-profile" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"public_key": "a1b2c3d4e5f6789012345678901234567890abcdefabcdefabcdefabcdef1234",
"public_storage": {
"username_hint": "acme-team",
"avatar": "https://cdn.example.com/avatar.png"
}
}'
{
"statusCode": 200,
"message": "Public profile updated successfully",
"data": {
"id": "507f1f77bcf86cd799439011",
"alias": "External Customer Token",
"prefix": "hdy_",
"public_key": "a1b2c3d4e5f6789012345678901234567890abcdefabcdefabcdefabcdef1234",
"public_storage": {
"username_hint": "acme-team",
"avatar": "https://cdn.example.com/avatar.png"
},
"is_enabled": true,
"created_at": "2025-01-15T10:30:00.000Z",
"updated_at": "2025-01-16T10:00:00.000Z"
}
}

Patch the current auth token’s public profile

Section titled “Patch the current auth token’s public profile”

PATCH /api/v1/auth/tokens/me/public-profile

Partial-update alternative to PUT on the same path. Same request body, same response, same error codes.

Terminal window
curl -X PATCH "https://api.hoody.com/api/v1/auth/tokens/me/public-profile" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{ "public_storage": { "username_hint": "acme-team" } }'

Refer to the PUT variant above for the full request body schema and response shapes. The 200, 400, 401, 403, and 422 responses use the same shapes:

{
"statusCode": 200,
"message": "Public profile updated successfully",
"data": {
"id": "507f1f77bcf86cd799439011",
"alias": "External Customer Token",
"prefix": "hdy_",
"public_key": null,
"public_storage": {
"username_hint": "acme-team"
},
"is_enabled": true,
"created_at": "2025-01-15T10:30:00.000Z",
"updated_at": "2025-01-16T10:05:00.000Z"
}
}