API tokens
Section titled “API tokens”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>.
List and inspect tokens
Section titled “List and inspect tokens”List auth tokens
Section titled “List auth tokens”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.
curl -X GET "https://api.hoody.com/api/v1/auth/tokens" \ -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 token of client.api.auth.tokens.listIterator()) { console.log(token.alias, token.is_enabled);}Response
Section titled “Response”{ "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.
{ "statusCode": 401, "error": "Unauthorized", "message": "Authentication token required"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MISSING_TOKEN | Authentication token missing | No authentication token was provided in the request | Include a valid JWT token in the Authorization header as Bearer <token> |
INVALID_TOKEN | Invalid authentication token | The provided authentication token is malformed or invalid | Obtain a new token by logging in again or using a valid auth token |
TOKEN_EXPIRED | Authentication token expired | The provided authentication token has expired | Obtain a new token by logging in again or refreshing your session |
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 |
INVALID_CREDENTIALS | Invalid credentials | The provided username/email or password is incorrect | Verify your credentials and try again, or use the password reset feature |
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 |
Forbidden: the credentials do not allow this operation.
{ "statusCode": 403, "error": "Forbidden", "message": "Insufficient permissions"}| 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 |
Get auth token by ID
Section titled “Get auth token by ID”GET /api/v1/auth/tokens/{id}
Returns the metadata for a single token. Token secret values are not included.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Unique identifier of the token |
curl -X GET "https://api.hoody.com/api/v1/auth/tokens/507f1f77bcf86cd799439011" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
const { data } = await client.api.auth.tokens.get('507f1f77bcf86cd799439011');console.log(data.alias, data.permissions);Response
Section titled “Response”{ "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.
{ "statusCode": 400, "error": "Bad Request", "message": "Invalid ID format"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_ID_FORMAT | Invalid ID format | The provided ID must be a 24-character hexadecimal string | Ensure the ID is exactly 24 characters long and contains only hexadecimal characters (0-9, a-f) |
{ "statusCode": 401, "error": "Unauthorized", "message": "Authentication token required"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MISSING_TOKEN | Authentication token missing | No authentication token was provided in the request | Include a valid JWT token in the Authorization header as Bearer <token> |
INVALID_TOKEN | Invalid authentication token | The provided authentication token is malformed or invalid | Obtain a new token by logging in again or using a valid auth token |
TOKEN_EXPIRED | Authentication token expired | The provided authentication token has expired | Obtain a new token by logging in again or refreshing your session |
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 |
INVALID_CREDENTIALS | Invalid credentials | The provided username/email or password is incorrect | Verify your credentials and try again, or use the password reset feature |
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 |
Forbidden: the credentials do not allow this operation.
{ "statusCode": 403, "error": "Forbidden", "message": "Insufficient permissions"}| 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": "Authentication token not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
TOKEN_NOT_FOUND | Authentication token not found | The requested authentication token does not exist or has been deleted | Verify the token ID is correct and that the token still exists |
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"}Get current auth token details
Section titled “Get current auth token details”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.
curl -X GET "https://api.hoody.com/api/v1/auth/tokens/me" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
const { data } = await client.api.auth.tokens.getCurrent();console.log(data.token.alias, data.restrictions.active_realm_id);Response
Section titled “Response”{ "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.
{ "statusCode": 401, "error": "Unauthorized", "message": "Authentication token required"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MISSING_TOKEN | Authentication token missing | No authentication token was provided in the request | Include a valid JWT token in the Authorization header as Bearer <token> |
INVALID_TOKEN | Invalid authentication token | The provided authentication token is malformed or invalid | Obtain a new token by logging in again or using a valid auth token |
TOKEN_EXPIRED | Authentication token expired | The provided authentication token has expired | Obtain a new token by logging in again or refreshing your session |
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 |
INVALID_CREDENTIALS | Invalid credentials | The provided username/email or password is incorrect | Verify your credentials and try again, or use the password reset feature |
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 |
{ "statusCode": 403, "error": "Forbidden", "message": "Insufficient permissions"}| 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 |
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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
public_key | path | string | Yes | ED25519 public key to resolve |
curl -X GET "https://api.hoody.com/api/v1/auth/tokens/public-profiles/a1b2c3d4e5f6789012345678901234567890abcdefabcdefabcdefabcdef1234" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
const { data } = await client.api.auth.tokens.getPublicProfile( 'a1b2c3d4e5f6789012345678901234567890abcdefabcdefabcdefabcdef1234');console.log(data.public_storage);Response
Section titled “Response”{ "statusCode": 200, "message": "Public profile retrieved successfully", "data": { "public_key": "a1b2c3d4e5f6789012345678901234567890abcdefabcdefabcdefabcdef1234", "public_storage": { "display_name": "Acme Integrations", "tier": "gold" } }}{ "statusCode": 400, "error": "Bad Request", "message": "Invalid public key format"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
VALIDATION_ERROR | Invalid input parameters | One or more request parameters failed validation | Check the error message for specific field requirements and correct your input |
INVALID_PUBLIC_KEY_FORMAT | Invalid public key format | Public key must be exactly 64 hexadecimal characters (ED25519 format) | Provide a valid 64-character ED25519 public key in hexadecimal format |
{ "statusCode": 401, "error": "Unauthorized", "message": "Authentication token required"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MISSING_TOKEN | Authentication token missing | No authentication token was provided in the request | Include a valid JWT token in the Authorization header as Bearer <token> |
INVALID_TOKEN | Invalid authentication token | The provided authentication token is malformed or invalid | Obtain a new token by logging in again or using a valid auth token |
TOKEN_EXPIRED | Authentication token expired | The provided authentication token has expired | Obtain a new token by logging in again or refreshing your session |
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 |
INVALID_CREDENTIALS | Invalid credentials | The provided username/email or password is incorrect | Verify your credentials and try again, or use the password reset feature |
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 |
Forbidden: the credentials do not allow this operation.
{ "statusCode": 403, "error": "Forbidden", "message": "Insufficient permissions"}| 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": "Authentication token not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
TOKEN_NOT_FOUND | Authentication token not found | The requested authentication token does not exist or has been deleted | Verify the token ID is correct and that the token still exists |
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"}List permission templates
Section titled “List permission templates”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.
curl -X GET "https://api.hoody.com/api/v1/auth/tokens/templates" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
const { data } = await client.api.auth.tokens.listTemplates();console.log(data);Response
Section titled “Response”Returns the permission-template catalog: one entry per template.
| Field | Type | Description |
|---|---|---|
statusCode | number | 200 |
message | string | Human-readable status message |
data | array | The templates |
data[].name | string | Template name, as accepted by permission_template on token creation |
data[].description | string | What the template is for |
data[].permissions | object | The 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 } } } } ]}Unauthorized: no credentials, or credentials that are invalid or expired.
{ "statusCode": 401, "error": "Unauthorized", "message": "Authentication token required"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MISSING_TOKEN | Authentication token missing | No authentication token was provided in the request | Include a valid JWT token in the Authorization header as Bearer <token> |
INVALID_TOKEN | Invalid authentication token | The provided authentication token is malformed or invalid | Obtain a new token by logging in again or using a valid auth token |
TOKEN_EXPIRED | Authentication token expired | The provided authentication token has expired | Obtain a new token by logging in again or refreshing your session |
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 |
INVALID_CREDENTIALS | Invalid credentials | The provided username/email or password is incorrect | Verify your credentials and try again, or use the password reset feature |
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 |
Forbidden: the credentials do not allow this operation.
{ "statusCode": 403, "error": "Forbidden", "message": "Insufficient permissions"}| 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 |
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.
Mint, copy, and revoke tokens
Section titled “Mint, copy, and revoke tokens”Create a new auth token
Section titled “Create a new auth token”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.
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
alias | string | No | Friendly token alias (letters, numbers, spaces, _, -). If omitted, the server generates a random animal name |
public_key | string | null | No | ED25519 public key as a 64-character hex string. Pass null to clear |
public_storage | object | null | No | Public JSON profile storage attached to the public key (max 64KB serialized) |
ip_whitelist | array | string | No | IPv4 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_template | string | null | No | One of full_access, external_customer, dev_team, finance_team, read_only, or null. Takes precedence over permissions |
permissions | object | No | Fine-grained TokenPermissions object. Any missing path defaults to deny |
realm_ids | array | No | Realm IDs this token is restricted to (24-char hex strings, at most 500) |
allow_no_realm | boolean | No | Whether the token can be used without a realm scope (defaults to true server-side) |
vault_access | boolean | No | Whether the token can access user vault endpoints (defaults to false server-side) |
event_access | boolean | No | Whether the token can access event streams and event history (defaults to true server-side) |
deny_reauthorization | boolean | No | Lockdown 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_at | string | number | No | ISO 8601 timestamp, Unix timestamp (seconds/ms), "today" (midnight tonight), or "tomorrow" (in 24 hours). Omit for non-expiring |
otp_code | string | No | TOTP code or backup code (max 12 chars). Required if 2FA is enabled and authenticating via JWT |
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 }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
const { data } = await client.api.auth.tokens.create({ 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,});console.log(data.token);Response
Section titled “Response”{ "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.
{ "statusCode": 400, "error": "Bad Request", "message": "Validation failed"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
VALIDATION_ERROR | Invalid input parameters | One or more request parameters failed validation | Check the error message for specific field requirements and correct your input |
UNKNOWN_FIELD | Unknown field | The body names a permission key that does not exist, the singular realm_id (the field is realm_ids, an array), or, on create, any other field the request body does not declare (for example expires_in: the expiry field is expires_at). An unknown permission key set to exactly false is ignored rather than refused, and on create the permissions tree is not checked when a permission_template takes precedence over it | Fix the spelling of each listed field or remove it; use realm_ids for realm restrictions and expires_at for an expiry |
MISSING_REQUIRED_FIELD | Required field missing | One or more required fields are missing from the request | Include all required fields as specified in the API documentation |
OTP_REQUIRED | 2FA verification required | This operation requires 2FA verification because your account has 2FA enabled | Provide an otp_code field with a valid TOTP code or backup code |
INVALID_ALIAS_FORMAT | Invalid alias format | Token alias must contain only letters, numbers, spaces, underscores, and hyphens | Use only allowed characters: letters (a-z, A-Z), numbers (0-9), spaces, underscores (_), and hyphens (-) |
INVALID_IP_FORMAT | Invalid IP address format | IP whitelist must contain valid IPv4 addresses or CIDR ranges | Provide valid IPv4 addresses (e.g., 192.168.1.1) or CIDR ranges (e.g., 192.168.1.0/24), or use * for all IPs |
IP_WHITELIST_TOO_LARGE | IP whitelist too large | ip_whitelist may hold at most 1000 entries, and its comma-separated string form at most 65536 characters | Use fewer entries, for example by merging addresses into CIDR ranges |
INVALID_REALM_ID_FORMAT | Invalid realm ID format | Realm IDs must be 24-character hexadecimal strings | Ensure all realm IDs are valid 24-character hex strings (e.g., 507f1f77bcf86cd799439011) |
INVALID_EXPIRATION_FORMAT | Invalid expiration format | Expiration must be an ISO 8601 date, Unix timestamp, today, tomorrow, or null | Use a valid date format: ISO 8601 string, Unix timestamp (seconds/milliseconds), today, tomorrow, or null for non-expiring |
INVALID_PUBLIC_KEY_FORMAT | Invalid public key format | Public key must be exactly 64 hexadecimal characters (ED25519 format) | Provide a valid 64-character ED25519 public key in hexadecimal format |
PUBLIC_STORAGE_TOO_LARGE | Public storage exceeds size limit | public_storage must not exceed 64KB serialized JSON | Reduce the size of the public storage payload and retry |
EXPIRATION_IN_PAST | Expiration date in the past | The expiration date cannot be in the past | Provide a future date for token expiration |
DUPLICATE_ALIAS | Token alias already exists | You already have an authentication token with this alias | Choose a different unique alias for this token |
{ "statusCode": 401, "error": "Unauthorized", "message": "Authentication token required"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MISSING_TOKEN | Authentication token missing | No authentication token was provided in the request | Include a valid JWT token in the Authorization header as Bearer <token> |
INVALID_TOKEN | Invalid authentication token | The provided authentication token is malformed or invalid | Obtain a new token by logging in again or using a valid auth token |
TOKEN_EXPIRED | Authentication token expired | The provided authentication token has expired | Obtain a new token by logging in again or refreshing your session |
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 |
INVALID_CREDENTIALS | Invalid credentials | The provided username/email or password is incorrect | Verify your credentials and try again, or use the password reset feature |
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 |
{ "statusCode": 403, "error": "TOKEN_CEILING_EXCEEDED", "message": "Child token cannot hold permission 'containers.create' that its creator lacks"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
TOKEN_CEILING_EXCEEDED | Token ceiling exceeded | The request would give a delegated token more than the token it was created from holds (or any token above that one): a permission, a realm, no-realm access, an IP range, vault or event access, a later expiry, or one more level of delegation. data.axis names which (permissions, realm_ids, allow_no_realm, ip_whitelist, expires_at, vault_access, event_access or depth); for permissions on an update made from a user session, data.paths lists every permission path that exceeds. Nothing was created or changed. | Narrow the value named in data.axis so it stays within the parent token, widen the parent token first, or create the token from the account instead of from a token |
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 |
The account or the creating token already has the maximum number of active tokens.
{ "statusCode": 409, "error": "AUTH_TOKEN_LIMIT_REACHED", "message": "This account already has the maximum number of active API tokens (1000). Delete tokens you no longer use, then try again."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
AUTH_TOKEN_LIMIT_REACHED | Active token limit reached | The account, or the token creating the new token, already has the maximum number of active authentication tokens. Expired tokens do not count; disabled tokens do. | Delete tokens you no longer use (or let them expire), then try again |
The request body failed schema validation, for example a permission_template that is not one of the listed templates.
{ "statusCode": 422, "error": "Validation Error", "message": "Validation failed: /permission_template must be equal to one of the allowed values"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
VALIDATION_ERROR | Request validation failed | The request body failed schema validation. | Correct the fields named in the message and retry. |
Copy an auth token
Section titled “Copy an auth token”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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Unique identifier of the token to copy |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
alias | string | No | Friendly alias for the copied token. If omitted, a deterministic alias like <source> copy is generated |
expires_at | string | number | null | No | Optional 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_code | string | No | TOTP code or backup code (max 12 chars). Required if 2FA is enabled and authenticating via JWT |
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" }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
const { data } = await client.api.auth.tokens.copy('507f1f77bcf86cd799439011', { alias: 'Production API Key Copy',});console.log(data.token);Response
Section titled “Response”{ "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.
{ "statusCode": 400, "error": "Bad Request", "message": "Validation failed"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
VALIDATION_ERROR | Invalid input parameters | One or more request parameters failed validation | Check the error message for specific field requirements and correct your input |
UNKNOWN_FIELD | Unknown field | The body names a permission key that does not exist, the singular realm_id (the field is realm_ids, an array), or, on create, any other field the request body does not declare (for example expires_in: the expiry field is expires_at). An unknown permission key set to exactly false is ignored rather than refused, and on create the permissions tree is not checked when a permission_template takes precedence over it | Fix the spelling of each listed field or remove it; use realm_ids for realm restrictions and expires_at for an expiry |
INVALID_ID_FORMAT | Invalid ID format | The provided ID must be a 24-character hexadecimal string | Ensure the ID is exactly 24 characters long and contains only hexadecimal characters (0-9, a-f) |
OTP_REQUIRED | 2FA verification required | This operation requires 2FA verification because your account has 2FA enabled | Provide an otp_code field with a valid TOTP code or backup code |
INVALID_ALIAS_FORMAT | Invalid alias format | Token alias must contain only letters, numbers, spaces, underscores, and hyphens | Use only allowed characters: letters (a-z, A-Z), numbers (0-9), spaces, underscores (_), and hyphens (-) |
INVALID_EXPIRATION_FORMAT | Invalid expiration format | Expiration must be an ISO 8601 date, Unix timestamp, today, tomorrow, or null | Use a valid date format: ISO 8601 string, Unix timestamp (seconds/milliseconds), today, tomorrow, or null for non-expiring |
EXPIRATION_IN_PAST | Expiration date in the past | The expiration date cannot be in the past | Provide a future date for token expiration |
DUPLICATE_ALIAS | Token alias already exists | You already have an authentication token with this alias | Choose a different unique alias for this token |
{ "statusCode": 401, "error": "Unauthorized", "message": "Authentication token required"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MISSING_TOKEN | Authentication token missing | No authentication token was provided in the request | Include a valid JWT token in the Authorization header as Bearer <token> |
INVALID_TOKEN | Invalid authentication token | The provided authentication token is malformed or invalid | Obtain a new token by logging in again or using a valid auth token |
TOKEN_EXPIRED | Authentication token expired | The provided authentication token has expired | Obtain a new token by logging in again or refreshing your session |
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 |
INVALID_CREDENTIALS | Invalid credentials | The provided username/email or password is incorrect | Verify your credentials and try again, or use the password reset feature |
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 |
{ "statusCode": 403, "error": "TOKEN_CEILING_EXCEEDED", "message": "Child token cannot hold permission 'containers.create' that its creator lacks"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
TOKEN_CEILING_EXCEEDED | Token ceiling exceeded | The request would give a delegated token more than the token it was created from holds (or any token above that one): a permission, a realm, no-realm access, an IP range, vault or event access, a later expiry, or one more level of delegation. data.axis names which (permissions, realm_ids, allow_no_realm, ip_whitelist, expires_at, vault_access, event_access or depth); for permissions on an update made from a user session, data.paths lists every permission path that exceeds. Nothing was created or changed. | Narrow the value named in data.axis so it stays within the parent token, widen the parent token first, or create the token from the account instead of from a token |
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": "Authentication token not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
TOKEN_NOT_FOUND | Authentication token not found | The requested authentication token does not exist or has been deleted | Verify the token ID is correct and that the token still exists |
The account or the creating token already has the maximum number of active tokens.
{ "statusCode": 409, "error": "AUTH_TOKEN_LIMIT_REACHED", "message": "This account already has the maximum number of active API tokens (1000). Delete tokens you no longer use, then try again."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
AUTH_TOKEN_LIMIT_REACHED | Active token limit reached | The account, or the token creating the new token, already has the maximum number of active authentication tokens. Expired tokens do not count; disabled tokens do. | Delete tokens you no longer use (or let them expire), then try again |
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"}Delete an auth token
Section titled “Delete an auth token”DELETE /api/v1/auth/tokens/{id}
Permanently revokes a token. Once deleted, the secret cannot authenticate any further requests.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Unique identifier of the token |
curl -X DELETE "https://api.hoody.com/api/v1/auth/tokens/507f1f77bcf86cd799439011" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
await client.api.auth.tokens.delete('507f1f77bcf86cd799439011');Response
Section titled “Response”{ "statusCode": 200, "message": "Auth token deleted successfully"}{ "statusCode": 400, "error": "Bad Request", "message": "Invalid ID format"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_ID_FORMAT | Invalid ID format | The provided ID must be a 24-character hexadecimal string | Ensure the ID is exactly 24 characters long and contains only hexadecimal characters (0-9, a-f) |
{ "statusCode": 401, "error": "Unauthorized", "message": "Authentication token required"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MISSING_TOKEN | Authentication token missing | No authentication token was provided in the request | Include a valid JWT token in the Authorization header as Bearer <token> |
INVALID_TOKEN | Invalid authentication token | The provided authentication token is malformed or invalid | Obtain a new token by logging in again or using a valid auth token |
TOKEN_EXPIRED | Authentication token expired | The provided authentication token has expired | Obtain a new token by logging in again or refreshing your session |
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 |
INVALID_CREDENTIALS | Invalid credentials | The provided username/email or password is incorrect | Verify your credentials and try again, or use the password reset feature |
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 |
{ "statusCode": 403, "error": "Forbidden", "message": "Insufficient permissions"}| 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": "Authentication token not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
TOKEN_NOT_FOUND | Authentication token not found | The requested authentication token does not exist or has been deleted | Verify the token ID is correct and that the token still exists |
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"}Update tokens
Section titled “Update tokens”Update an auth token (PUT)
Section titled “Update an auth token (PUT)”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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Unique identifier of the token to update |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
alias | string | No | New friendly alias (letters, numbers, spaces, _, -) |
public_key | string | null | No | ED25519 public key as 64-character hex string. Pass null to clear |
public_storage | object | null | No | Public JSON profile storage attached to the public key (max 64KB). Pass null to clear |
ip_whitelist | array | string | No | IPv4 addresses/CIDR ranges or *. Accepts an array or a comma-separated string |
permissions | object | No | Fine-grained TokenPermissions object. Any missing path defaults to deny |
realm_ids | array | No | Realm IDs this token is restricted to (24-char hex strings) |
allow_no_realm | boolean | No | Whether the token can be used without a realm scope |
vault_access | boolean | No | Whether the token can access user vault endpoints |
event_access | boolean | No | Whether the token can access event streams and event history |
expires_at | string | number | null | No | ISO 8601 timestamp, Unix timestamp (seconds/ms), today, tomorrow, or null to make the token non-expiring |
is_enabled | boolean | No | Enable or disable the token |
otp_code | string | No | TOTP code or backup code (max 12 chars). Required if 2FA is enabled and authenticating via JWT |
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 }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
const { data } = await client.api.auth.tokens.update('507f1f77bcf86cd799439011', { alias: 'Updated Production Key', ip_whitelist: ['*'], vault_access: false, expires_at: null, is_enabled: false,});console.log(data.alias, data.is_enabled);Response
Section titled “Response”{ "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" }}{ "statusCode": 400, "error": "Bad Request", "message": "Validation failed"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
VALIDATION_ERROR | Invalid input parameters | One or more request parameters failed validation | Check the error message for specific field requirements and correct your input |
INVALID_ID_FORMAT | Invalid ID format | The provided ID must be a 24-character hexadecimal string | Ensure the ID is exactly 24 characters long and contains only hexadecimal characters (0-9, a-f) |
OTP_REQUIRED | 2FA verification required | This operation requires 2FA verification because your account has 2FA enabled | Provide an otp_code field with a valid TOTP code or backup code |
INVALID_ALIAS_FORMAT | Invalid alias format | Token alias must contain only letters, numbers, spaces, underscores, and hyphens | Use only allowed characters: letters (a-z, A-Z), numbers (0-9), spaces, underscores (_), and hyphens (-) |
INVALID_IP_FORMAT | Invalid IP address format | IP whitelist must contain valid IPv4 addresses or CIDR ranges | Provide valid IPv4 addresses (e.g., 192.168.1.1) or CIDR ranges (e.g., 192.168.1.0/24), or use * for all IPs |
INVALID_REALM_ID_FORMAT | Invalid realm ID format | Realm IDs must be 24-character hexadecimal strings | Ensure all realm IDs are valid 24-character hex strings |
INVALID_EXPIRATION_FORMAT | Invalid expiration format | Expiration must be an ISO 8601 date, Unix timestamp, today, tomorrow, or null | Use a valid date format: ISO 8601 string, Unix timestamp (seconds/milliseconds), today, tomorrow, or null for non-expiring |
INVALID_PUBLIC_KEY_FORMAT | Invalid public key format | Public key must be exactly 64 hexadecimal characters (ED25519 format) | Provide a valid 64-character ED25519 public key in hexadecimal format |
PUBLIC_STORAGE_TOO_LARGE | Public storage exceeds size limit | public_storage must not exceed 64KB serialized JSON | Reduce the size of the public storage payload and retry |
EXPIRATION_IN_PAST | Expiration date in the past | The expiration date cannot be in the past | Provide a future date for token expiration |
DUPLICATE_ALIAS | Token alias already exists | You already have an authentication token with this alias | Choose a different unique alias for this token |
IP_WHITELIST_TOO_LARGE | IP whitelist too large | ip_whitelist may hold at most 1000 entries, and its comma-separated string form at most 65536 characters | Use fewer entries, for example by merging addresses into CIDR ranges |
UNKNOWN_FIELD | Unknown field | The body names a permission key that does not exist, or the singular realm_id (the field is realm_ids, an array). An unknown permission key set to exactly false is ignored rather than refused, and on create the permissions tree is not checked when a permission_template takes precedence over it | Fix the spelling of each listed field; use realm_ids for realm restrictions |
{ "statusCode": 401, "error": "Unauthorized", "message": "Authentication token required"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MISSING_TOKEN | Authentication token missing | No authentication token was provided in the request | Include a valid JWT token in the Authorization header as Bearer <token> |
INVALID_TOKEN | Invalid authentication token | The provided authentication token is malformed or invalid | Obtain a new token by logging in again or using a valid auth token |
TOKEN_EXPIRED | Authentication token expired | The provided authentication token has expired | Obtain a new token by logging in again or refreshing your session |
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 |
{ "statusCode": 403, "error": "TOKEN_CEILING_EXCEEDED", "message": "Child token cannot hold permission 'containers.create' that its creator lacks"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
TOKEN_CEILING_EXCEEDED | Token ceiling exceeded | The request would give a delegated token more than the token it was created from holds (or any token above that one): a permission, a realm, no-realm access, an IP range, vault or event access, a later expiry, or one more level of delegation. data.axis names which (permissions, realm_ids, allow_no_realm, ip_whitelist, expires_at, vault_access, event_access or depth); for permissions on an update made from a user session, data.paths lists every permission path that exceeds. Nothing was created or changed. | Narrow the value named in data.axis so it stays within the parent token, widen the parent token first, or create the token from the account instead of from a token |
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": "Authentication token not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
TOKEN_NOT_FOUND | Authentication token not found | The requested authentication token does not exist or has been deleted | Verify the token ID is correct and that the token still exists |
Re-enabling an expired token would pass the maximum number of active tokens, or realm_ids: [] would leave a token restricted to realms with no realm (an empty list means every realm).
{ "statusCode": 409, "error": "AUTH_TOKEN_LIMIT_REACHED", "message": "This account already has the maximum number of active API tokens (1000). Delete tokens you no longer use, then try again."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
AUTH_TOKEN_LIMIT_REACHED | Active token limit reached | The account, or the token creating the new token, already has the maximum number of active authentication tokens. Expired tokens do not count; disabled tokens do. | Delete tokens you no longer use (or let them expire), then try again |
LAST_REALM_REMOVAL | Last realm of a token | The request would leave a token that is restricted to realms with no realm. An empty realm_ids means every realm, so the token would gain access to all of them instead of losing one. Nothing was changed. | Delete or disable the token to revoke it, or add another realm before removing this one |
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"}Update an auth token (PATCH)
Section titled “Update an auth token (PATCH)”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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Unique identifier of the token to update |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
alias | string | No | New friendly alias (letters, numbers, spaces, _, -) |
public_key | string | null | No | ED25519 public key as 64-character hex string. Pass null to clear |
public_storage | object | null | No | Public JSON profile storage attached to the public key (max 64KB). Pass null to clear |
ip_whitelist | array | string | No | IPv4 addresses/CIDR ranges or *. Accepts an array or a comma-separated string |
permissions | object | No | Fine-grained TokenPermissions object. Any missing path defaults to deny |
realm_ids | array | No | Realm IDs this token is restricted to (24-char hex strings) |
allow_no_realm | boolean | No | Whether the token can be used without a realm scope |
vault_access | boolean | No | Whether the token can access user vault endpoints |
event_access | boolean | No | Whether the token can access event streams and event history |
expires_at | string | number | null | No | ISO 8601 timestamp, Unix timestamp (seconds/ms), today, tomorrow, or null to make the token non-expiring |
is_enabled | boolean | No | Enable or disable the token |
otp_code | string | No | TOTP code or backup code (max 12 chars). Required if 2FA is enabled and authenticating via JWT |
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 }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
const { data } = await client.api.auth.tokens.update('507f1f77bcf86cd799439011', { realm_ids: ['507f1f77bcf86cd799439011'], allow_no_realm: false,});console.log(data.realm_ids);Response
Section titled “Response”{ "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" }}{ "statusCode": 400, "error": "Bad Request", "message": "Validation failed"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
VALIDATION_ERROR | Invalid input parameters | One or more request parameters failed validation | Check the error message for specific field requirements and correct your input |
INVALID_ID_FORMAT | Invalid ID format | The provided ID must be a 24-character hexadecimal string | Ensure the ID is exactly 24 characters long and contains only hexadecimal characters (0-9, a-f) |
OTP_REQUIRED | 2FA verification required | This operation requires 2FA verification because your account has 2FA enabled | Provide an otp_code field with a valid TOTP code or backup code |
INVALID_ALIAS_FORMAT | Invalid alias format | Token alias must contain only letters, numbers, spaces, underscores, and hyphens | Use only allowed characters: letters (a-z, A-Z), numbers (0-9), spaces, underscores (_), and hyphens (-) |
INVALID_IP_FORMAT | Invalid IP address format | IP whitelist must contain valid IPv4 addresses or CIDR ranges | Provide valid IPv4 addresses (e.g., 192.168.1.1) or CIDR ranges (e.g., 192.168.1.0/24), or use * for all IPs |
INVALID_REALM_ID_FORMAT | Invalid realm ID format | Realm IDs must be 24-character hexadecimal strings | Ensure all realm IDs are valid 24-character hex strings |
INVALID_EXPIRATION_FORMAT | Invalid expiration format | Expiration must be an ISO 8601 date, Unix timestamp, today, tomorrow, or null | Use a valid date format: ISO 8601 string, Unix timestamp (seconds/milliseconds), today, tomorrow, or null for non-expiring |
INVALID_PUBLIC_KEY_FORMAT | Invalid public key format | Public key must be exactly 64 hexadecimal characters (ED25519 format) | Provide a valid 64-character ED25519 public key in hexadecimal format |
PUBLIC_STORAGE_TOO_LARGE | Public storage exceeds size limit | public_storage must not exceed 64KB serialized JSON | Reduce the size of the public storage payload and retry |
EXPIRATION_IN_PAST | Expiration date in the past | The expiration date cannot be in the past | Provide a future date for token expiration |
DUPLICATE_ALIAS | Token alias already exists | You already have an authentication token with this alias | Choose a different unique alias for this token |
IP_WHITELIST_TOO_LARGE | IP whitelist too large | ip_whitelist may hold at most 1000 entries, and its comma-separated string form at most 65536 characters | Use fewer entries, for example by merging addresses into CIDR ranges |
UNKNOWN_FIELD | Unknown field | The body names a permission key that does not exist, or the singular realm_id (the field is realm_ids, an array). An unknown permission key set to exactly false is ignored rather than refused, and on create the permissions tree is not checked when a permission_template takes precedence over it | Fix the spelling of each listed field; use realm_ids for realm restrictions |
{ "statusCode": 401, "error": "Unauthorized", "message": "Authentication token required"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MISSING_TOKEN | Authentication token missing | No authentication token was provided in the request | Include a valid JWT token in the Authorization header as Bearer <token> |
INVALID_TOKEN | Invalid authentication token | The provided authentication token is malformed or invalid | Obtain a new token by logging in again or using a valid auth token |
TOKEN_EXPIRED | Authentication token expired | The provided authentication token has expired | Obtain a new token by logging in again or refreshing your session |
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 |
{ "statusCode": 403, "error": "TOKEN_CEILING_EXCEEDED", "message": "Child token cannot hold permission 'containers.create' that its creator lacks"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
TOKEN_CEILING_EXCEEDED | Token ceiling exceeded | The request would give a delegated token more than the token it was created from holds (or any token above that one): a permission, a realm, no-realm access, an IP range, vault or event access, a later expiry, or one more level of delegation. data.axis names which (permissions, realm_ids, allow_no_realm, ip_whitelist, expires_at, vault_access, event_access or depth); for permissions on an update made from a user session, data.paths lists every permission path that exceeds. Nothing was created or changed. | Narrow the value named in data.axis so it stays within the parent token, widen the parent token first, or create the token from the account instead of from a token |
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": "Authentication token not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
TOKEN_NOT_FOUND | Authentication token not found | The requested authentication token does not exist or has been deleted | Verify the token ID is correct and that the token still exists |
{ "statusCode": 409, "error": "AUTH_TOKEN_LIMIT_REACHED", "message": "This account already has the maximum number of active API tokens (1000). Delete tokens you no longer use, then try again."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
AUTH_TOKEN_LIMIT_REACHED | Active token limit reached | The account, or the token creating the new token, already has the maximum number of active authentication tokens. Expired tokens do not count; disabled tokens do. | Delete tokens you no longer use (or let them expire), then try again |
LAST_REALM_REMOVAL | Last realm of a token | The request would leave a token that is restricted to realms with no realm. An empty realm_ids means every realm, so the token would gain access to all of them instead of losing one. Nothing was changed. | Delete or disable the token to revoke it, or add another realm before removing this one |
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"}Manage realm scope
Section titled “Manage realm scope”Add realm to auth token
Section titled “Add realm to auth token”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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Auth token ID |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
realm_id | string | Yes | Realm ID to add to the token (24-char hex string) |
otp_code | string | No | TOTP code or backup code (max 12 chars). Required if 2FA is enabled and authenticating via JWT |
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" }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
const { data } = await client.api.auth.tokens.addRealm('507f1f77bcf86cd799439011', { realm_id: '507f1f77bcf86cd799439012',});console.log(data.realm_ids);Response
Section titled “Response”{ "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.
{ "statusCode": 400, "error": "Bad Request", "message": "Invalid realm ID format"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
OTP_REQUIRED | 2FA verification required | This operation requires 2FA verification because your account has 2FA enabled | Provide an otp_code field with a valid TOTP code or backup code |
INVALID_REALM_ID_FORMAT | Invalid realm ID format | Realm IDs must be 24-character hexadecimal strings | Ensure all realm IDs are valid 24-character hex strings (e.g., 507f1f77bcf86cd799439011) |
{ "statusCode": 401, "error": "Unauthorized", "message": "Authentication token required"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MISSING_TOKEN | Authentication token missing | No authentication token was provided in the request | Include a valid JWT token in the Authorization header as Bearer <token> |
INVALID_TOKEN | Invalid authentication token | The provided authentication token is malformed or invalid | Obtain a new token by logging in again or using a valid auth token |
TOKEN_EXPIRED | Authentication token expired | The provided authentication token has expired | Obtain a new token by logging in again or refreshing your session |
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 |
INVALID_CREDENTIALS | Invalid credentials | The provided username/email or password is incorrect | Verify your credentials and try again, or use the password reset feature |
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 |
{ "statusCode": 403, "error": "TOKEN_CEILING_EXCEEDED", "message": "Child token cannot hold permission 'containers.create' that its creator lacks"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
TOKEN_CEILING_EXCEEDED | Token ceiling exceeded | The request would give a delegated token more than the token it was created from holds (or any token above that one): a permission, a realm, no-realm access, an IP range, vault or event access, a later expiry, or one more level of delegation. data.axis names which (permissions, realm_ids, allow_no_realm, ip_whitelist, expires_at, vault_access, event_access or depth); for permissions on an update made from a user session, data.paths lists every permission path that exceeds. Nothing was created or changed. | Narrow the value named in data.axis so it stays within the parent token, widen the parent token first, or create the token from the account instead of from a token |
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": "Authentication token 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"}Remove realm from auth token
Section titled “Remove realm from auth token”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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Auth token ID |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
realm_id | string | Yes | Realm ID to remove from the token (24-char hex string) |
otp_code | string | No | TOTP code or backup code (max 12 chars). Required if 2FA is enabled and authenticating via JWT |
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" }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
const { data } = await client.api.auth.tokens.removeRealm('507f1f77bcf86cd799439011', { realm_id: '507f1f77bcf86cd799439012',});console.log(data.realm_ids);Response
Section titled “Response”{ "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.
{ "statusCode": 400, "error": "Bad Request", "message": "Invalid realm ID format"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
OTP_REQUIRED | 2FA verification required | This operation requires 2FA verification because your account has 2FA enabled | Provide an otp_code field with a valid TOTP code or backup code |
INVALID_REALM_ID_FORMAT | Invalid realm ID format | Realm IDs must be 24-character hexadecimal strings | Ensure all realm IDs are valid 24-character hex strings (e.g., 507f1f77bcf86cd799439011) |
{ "statusCode": 401, "error": "Unauthorized", "message": "Authentication token required"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MISSING_TOKEN | Authentication token missing | No authentication token was provided in the request | Include a valid JWT token in the Authorization header as Bearer <token> |
INVALID_TOKEN | Invalid authentication token | The provided authentication token is malformed or invalid | Obtain a new token by logging in again or using a valid auth token |
TOKEN_EXPIRED | Authentication token expired | The provided authentication token has expired | Obtain a new token by logging in again or refreshing your session |
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 |
INVALID_CREDENTIALS | Invalid credentials | The provided username/email or password is incorrect | Verify your credentials and try again, or use the password reset feature |
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 |
{ "statusCode": 403, "error": "TOKEN_CEILING_EXCEEDED", "message": "Child token cannot hold permission 'containers.create' that its creator lacks"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
TOKEN_CEILING_EXCEEDED | Token ceiling exceeded | The request would give a delegated token more than the token it was created from holds (or any token above that one): a permission, a realm, no-realm access, an IP range, vault or event access, a later expiry, or one more level of delegation. data.axis names which (permissions, realm_ids, allow_no_realm, ip_whitelist, expires_at, vault_access, event_access or depth); for permissions on an update made from a user session, data.paths lists every permission path that exceeds. Nothing was created or changed. | Narrow the value named in data.axis so it stays within the parent token, widen the parent token first, or create the token from the account instead of from a token |
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": "Authentication token not found"}The realm is the last one of a token that is restricted to realms. An empty realm list means every realm, so it is not removed.
{ "statusCode": 409, "error": "LAST_REALM_REMOVAL", "message": "A token restricted to realms cannot be left with no realm: an empty realm_ids means every realm. Delete or disable the token to revoke it, or add another realm first."}| Error Code | Title | Description | Resolution |
|---|---|---|---|
LAST_REALM_REMOVAL | Last realm of a token | The request would leave a token that is restricted to realms with no realm. An empty realm_ids means every realm, so the token would gain access to all of them instead of losing one. Nothing was changed. | Delete or disable the token to revoke it, or add another realm before removing this one |
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"}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.
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
public_key | string | null | No | ED25519 public key (64 hex chars). Pass null to clear |
public_storage | object | null | No | Public JSON profile (max 64KB serialized). Pass null to clear |
At least one of public_key or public_storage must be supplied.
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" } }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
await client.api.auth.tokens.updatePublicProfile({ public_key: 'a1b2c3d4e5f6789012345678901234567890abcdefabcdefabcdefabcdef1234', public_storage: { username_hint: 'acme-team', avatar: 'https://cdn.example.com/avatar.png', },});Response
Section titled “Response”{ "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" }}{ "statusCode": 400, "error": "Bad Request", "message": "Validation failed"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
VALIDATION_ERROR | Invalid input parameters | One or more request parameters failed validation | Check the error message for specific field requirements and correct your input |
INVALID_PUBLIC_KEY_FORMAT | Invalid public key format | Public key must be exactly 64 hexadecimal characters (ED25519 format) | Provide a valid 64-character ED25519 public key in hexadecimal format |
PUBLIC_STORAGE_TOO_LARGE | Public storage exceeds size limit | public_storage must not exceed 64KB serialized JSON | Reduce the size of the public storage payload and retry |
{ "statusCode": 401, "error": "Unauthorized", "message": "Authentication token required"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MISSING_TOKEN | Authentication token missing | No authentication token was provided in the request | Include a valid JWT token in the Authorization header as Bearer <token> |
INVALID_TOKEN | Invalid authentication token | The provided authentication token is malformed or invalid | Obtain a new token by logging in again or using a valid auth token |
TOKEN_EXPIRED | Authentication token expired | The provided authentication token has expired | Obtain a new token by logging in again or refreshing your session |
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 |
{ "statusCode": 403, "error": "Forbidden", "message": "Insufficient permissions"}| 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 |
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.
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" } }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
await client.api.auth.tokens.updatePublicProfile({ 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" }}{ "statusCode": 400, "error": "Bad Request", "message": "Validation failed"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
VALIDATION_ERROR | Invalid input parameters | One or more request parameters failed validation | Check the error message for specific field requirements and correct your input |
INVALID_PUBLIC_KEY_FORMAT | Invalid public key format | Public key must be exactly 64 hexadecimal characters (ED25519 format) | Provide a valid 64-character ED25519 public key in hexadecimal format |
PUBLIC_STORAGE_TOO_LARGE | Public storage exceeds size limit | public_storage must not exceed 64KB serialized JSON | Reduce the size of the public storage payload and retry |
{ "statusCode": 401, "error": "Unauthorized", "message": "Authentication token required"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MISSING_TOKEN | Authentication token missing | No authentication token was provided in the request | Include a valid JWT token in the Authorization header as Bearer <token> |
INVALID_TOKEN | Invalid authentication token | The provided authentication token is malformed or invalid | Obtain a new token by logging in again or using a valid auth token |
TOKEN_EXPIRED | Authentication token expired | The provided authentication token has expired | Obtain a new token by logging in again or refreshing your session |
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 |
{ "statusCode": 403, "error": "Forbidden", "message": "Insufficient permissions"}| 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 |
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"}