Notifications & Events
Section titled “Notifications & Events”The Notifications and Events APIs expose the platform audit trail and the per-user inbox. The Events endpoints return the immutable record of resource changes (containers, projects, storage shares, billing, and the rest of the resource catalog) for the calling account, while the Notifications endpoints manage the user-facing inbox, including per-user mark-as-read state and unread counts. Use Events for forensic queries, paginated history exports, and account administration. Use Notifications for in-product banners, polling, and dismissal flows.
Events
Section titled “Events”The Events endpoints expose the platform’s immutable change log. Events are paginated, filterable by event type, resource type, and time range, and support both offset and keyset (after) paging. The retention window is 90 days from admission; cursors that point before the retained window return 410 (see the EVENTS_CURSOR_EXPIRED and EVENTS_HISTORY_RESET codes under GET /api/v1/events).
GET /api/v1/events
Section titled “GET /api/v1/events”Query event history with filtering, pagination, and sorting. Maximum 500 events per page.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit | query | integer | No | Number of events to return. Max 500. Default: 100. |
offset | query | integer | No | Number of events to skip. Default: 0. Ignored when after is supplied. |
sort_by | query | string | No | Field to sort by. Allowed values: created_at, event_type. Default: "created_at". |
sort_order | query | string | No | Sort direction. Allowed values: asc, desc. Default: "desc". In cursor mode, only asc is accepted. |
event_type | query | string | No | Filter by a specific event type from the Events catalog (for example container.running, notification.created, billing.payment.succeeded). |
resource_type | query | string | No | Filter by resource type. Allowed values: container, storage_share, notification, project, server, firewall, proxy_alias, proxy_permissions, auth_token, pool, activity_log, container_network, project_permission, vault, account, wallet, payment, invoice, payment_method, user_image, reservation, server_offer. |
resource_id | query | string | No | Filter by a specific resource ID (24-character hexadecimal). |
project_id | query | string | No | Filter by project ID. |
container_id | query | string | No | Filter by container ID. |
start_date | query | string | No | Filter events after this timestamp (ISO 8601). |
end_date | query | string | No | Filter events before this timestamp (ISO 8601). |
realm_id | query | string | No | Selects the realm scope in all modes. A realm_id that conflicts with the realm host or lies outside the realms of the token is refused with 403. |
after | query | string | No | Cursor mode: return events after this cursor, oldest first. Cannot be combined with offset, sort_by, sort_order other than asc, event_type, resource_type, resource_id, project_id, container_id, start_date, or end_date. The cursor is bound to the realm selected by realm_id; a mismatch is refused with 403. |
bootstrap | query | boolean | No | Return no events, only the resumable boundary (next_cursor) and latest_cursor for this scope. Cannot be combined with after. |
Request
Section titled “Request”curl -X GET "https://api.hoody.com/api/v1/events?limit=50&event_type=container.running&sort_order=desc" \ -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 item of client.api.events.listIterator({ limit: 50, event_type: 'container.running', sort_order: 'desc' })) { console.log(item);}Response
Section titled “Response”{ "statusCode": 200, "message": "Events retrieved successfully", "data": { "events": [ { "id": "507f1f77bcf86cd799439044", "event_type": "container.running", "resource_type": "container", "resource_id": "507f1f77bcf86cd799439033", "user_id": "507f1f77bcf86cd799439011", "payload": { "container": { "id": "507f1f77bcf86cd799439033", "name": "web-app-1", "status": "running", "project_id": "507f1f77bcf86cd799439022" } }, "realm_ids": [ "507f1f77bcf86cd799439022" ], "created_at": "2025-01-15T10:30:05.123Z" } ], "pagination": { "total": 1523, "limit": 100, "offset": 0, "has_more": true } }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
{ "statusCode": 400, "error": "Bad Request", "message": "Invalid date range: start_date cannot be after end_date"}| 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_PARAMETER_VALUE | Invalid parameter value | A parameter value is outside the allowed range or format | Ensure parameter values meet the documented constraints (min/max, format, regex) |
INVALID_DATE_RANGE | Invalid date range | The start_date is after the end_date | Ensure start_date is before or the same as end_date |
EVENTS_CURSOR_INVALID | Invalid events cursor | The after cursor is malformed, was tampered with, was issued for another account or realm, or was signed with a retired key | Discard the stored cursor and bootstrap again with bootstrap=true |
EVENTS_QUERY_CONFLICT | Conflicting history query | after cannot be combined with offset, sort_by, sort_order other than asc, event_type, resource_type, resource_id, project_id, container_id, start_date, or end_date; bootstrap cannot be combined with after | Use cursor mode (after, limit) on its own, or the offset mode without after |
{ "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": "INSUFFICIENT_PERMISSIONS", "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 |
The cursor no longer points into the retained event history. The response body carries a boundary_cursor and a latest_cursor to resume from.
{ "statusCode": 410, "error": "EVENTS_CURSOR_EXPIRED", "message": "This cursor is older than the retained event history; bootstrap again", "data": { "boundary_cursor": "AZ3qX0v8bE1yJm4Kc2Rt7wLpQn9sHfUdAa6oVgGiTzYx5N", "latest_cursor": "AZ3qX0v8bE1yJm4Kc2Rt7wLpQn9sHfUdAa6oVgGiTzYx5N", "retention_days": 90 }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
EVENTS_CURSOR_EXPIRED | Events cursor expired | The cursor points before the retained event history (events are kept 90 days from admission) | Refetch current state through GET, then resume from data.boundary_cursor |
EVENTS_HISTORY_RESET | Event history reset | Events were deleted by the account since this cursor was issued, so the history it points into no longer exists as it was | Refetch current state through GET, then resume from data.boundary_cursor |
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 /api/v1/events/{id}
Section titled “GET /api/v1/events/{id}”Retrieve detailed information about a specific event by its 24-character hexadecimal ID.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Event ID |
Request
Section titled “Request”curl -X GET "https://api.hoody.com/api/v1/events/507f1f77bcf86cd799439044" \ -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.events.get('507f1f77bcf86cd799439044');Response
Section titled “Response”{ "statusCode": 200, "message": "Event retrieved successfully", "data": { "id": "507f1f77bcf86cd799439044", "event_type": "container.running", "resource_type": "container", "resource_id": "507f1f77bcf86cd799439033", "user_id": "507f1f77bcf86cd799439011", "payload": { "container": { "id": "507f1f77bcf86cd799439033", "name": "web-app-1", "status": "running", "project_id": "507f1f77bcf86cd799439022" } }, "realm_ids": [ "507f1f77bcf86cd799439022" ], "created_at": "2025-01-15T10:30:05.123Z" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
{ "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 |
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": "INSUFFICIENT_PERMISSIONS", "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": "Event not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
EVENT_NOT_FOUND | Event not found | The requested event does not exist or has been deleted | Verify the event ID is correct and that you have access to this event |
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 /api/v1/events/stats
Section titled “GET /api/v1/events/stats”Get aggregated counts of events over an optional time range, broken down by event type and by resource type.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
start_date | query | string | No | Start of the time range (ISO 8601). |
end_date | query | string | No | End of the time range (ISO 8601). |
realm_id | query | string | No | Filter by realm. |
Request
Section titled “Request”curl -X GET "https://api.hoody.com/api/v1/events/stats?start_date=2025-01-01T00:00:00.000Z&end_date=2025-01-31T23:59:59.000Z" \ -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.events.getStats({ start_date: '2025-01-01T00:00:00.000Z', end_date: '2025-01-31T23:59:59.000Z',});Response
Section titled “Response”{ "statusCode": 200, "message": "Event statistics retrieved successfully", "data": { "total_events": 15234, "by_type": { "container.running": 3456, "container.stopped": 2134, "storage.share.created": 1523, "notification.created": 8121 }, "by_resource": { "container": 8765, "storage_share": 2456, "notification": 4013 }, "oldest_event": "2024-11-15T10:30:00.000Z", "newest_event": "2025-01-15T10:30:00.000Z" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
{ "statusCode": 400, "error": "Bad Request", "message": "Invalid date range: start_date cannot be after end_date"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_DATE_RANGE | Invalid date range | The start_date is after the end_date | Ensure start_date is before or the same as end_date |
{ "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": "INSUFFICIENT_PERMISSIONS", "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"}POST /api/v1/events/cleanup
Section titled “POST /api/v1/events/cleanup”Delete events older than the supplied retention window. The response distinguishes deleted rows from shared rows that the caller cannot remove because they belong to other realms or accounts.
Request Body
Section titled “Request Body”| Name | Type | Required | Description |
|---|---|---|---|
retention_days | integer | Yes | Delete events older than this many days. Allowed range: 1 to 365. |
Request
Section titled “Request”curl -X POST "https://api.hoody.com/api/v1/events/cleanup" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "retention_days": 30 }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
await client.api.events.purge({ retention_days: 30 });Response
Section titled “Response”{ "statusCode": 200, "message": "Old events cleaned up successfully", "data": { "deleted_count": 5432, "retention_days": 30, "cutoff_date": "2024-12-15T10:30:00.000Z" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
{ "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 |
{ "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"}DELETE /api/v1/events
Section titled “DELETE /api/v1/events”Bulk-delete events matching the supplied filters. At least one filter must be provided, or all must be set to confirm the deletion of every event.
Request Body
Section titled “Request Body”| Name | Type | Required | Description |
|---|---|---|---|
event_type | string | No | Delete all events of this type. Allowed values are the entries in the Events catalog. |
resource_type | string | No | Delete all events for this resource type. Allowed values: container, storage_share, notification, project, server, firewall, proxy_alias, proxy_permissions, auth_token, pool, activity_log, container_network, project_permission, vault, account, wallet, payment, invoice, payment_method, user_image, reservation, server_offer. |
resource_id | string | No | Delete all events for this resource (24-character hexadecimal). |
before_date | string | No | Delete events before this date (ISO 8601). |
realm_id | string | No | Delete events in this realm (24-character hexadecimal). |
Request
Section titled “Request”curl -X DELETE "https://api.hoody.com/api/v1/events" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "resource_type": "container", "before_date": "2024-12-15T00:00:00.000Z" }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
await client.api.events.clear({ resource_type: 'container', before_date: '2024-12-15T00:00:00.000Z',});Response
Section titled “Response”{ "statusCode": 200, "message": "Events deleted successfully", "data": { "deleted_count": 1523, "skipped_count": 0 }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
{ "statusCode": 400, "error": "Bad Request", "message": "Either filters or `all=true` must be provided for bulk delete"}| 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_BULK_DELETE_PARAMS | Invalid bulk delete parameters | You must provide at least one filter when performing a bulk delete, or set all=true to delete all events | Provide one or more filters (for example resource_type, event_type) or use all=true to confirm deletion of all events |
{ "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": "INSUFFICIENT_PERMISSIONS", "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"}DELETE /api/v1/events/{id}
Section titled “DELETE /api/v1/events/{id}”Permanently delete a single event from history.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Event ID to delete |
Request
Section titled “Request”curl -X DELETE "https://api.hoody.com/api/v1/events/507f1f77bcf86cd799439044" \ -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.events.delete('507f1f77bcf86cd799439044');Response
Section titled “Response”{ "statusCode": 200, "message": "Event deleted successfully", "data": { "deleted_count": 1, "skipped_count": 0 }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
{ "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 |
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 | The caller does not have the required permissions to perform this action | Contact the resource owner or administrator to request access |
ACCOUNT_BANNED | Account banned | The account has been banned and cannot access this resource | Contact support for information about the account status |
{ "statusCode": 404, "error": "Not Found", "message": "Event not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
EVENT_NOT_FOUND | Event not found | The requested event does not exist or has been deleted | Verify the event ID is correct and that you have access to this event |
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"}Notifications
Section titled “Notifications”The Notifications endpoints return the per-user inbox. Each item is a self-contained message with a title, a message, a type, a severity, and visibility flags. The authenticated list, summary, and mark-as-read endpoints share one credential rule: API tokens must hold the resources.read_account permission, and the mark-as-read endpoints refuse API tokens entirely. The public list at GET /api/v1/notifications/public is open and needs no credentials.
GET /api/v1/notifications/
Section titled “GET /api/v1/notifications/”Paginated list of the notifications visible to the authenticated user (global notifications plus those targeted to them), newest first. Results are paginated; pagination.next_cursor is the preferred way to walk the inbox because offset paging duplicates or skips rows when a new notification arrives mid-read.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
page | query | integer | No | Page number for offset paging. Default: 1. Ignored when before is supplied. |
limit | query | integer | No | Rows per page. Max 100. Default: 20. |
unread_only | query | boolean | No | Return only notifications the user has not read. Mutually exclusive with read_only. Default: false. |
read_only | query | boolean | No | Return only notifications the user has read (the archive half of the inbox). pagination.total counts the same filtered set, so it can drive page numbers directly. Mutually exclusive with unread_only; sending both is a 400, not an empty page. Default: false. |
before | query | string | No | Keyset cursor from a previous response’s pagination.next_cursor (format <created_at>,<id>). Prefer this over page for an inbox: offset paging duplicates or skips rows when a new notification arrives mid-read. |
Request
Section titled “Request”curl -X GET "https://api.hoody.com/api/v1/notifications/?limit=20&unread_only=true" \ -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 item of client.api.inbox.listIterator({ limit: 20, unread_only: true })) { console.log(item);}Response
Section titled “Response”{ "statusCode": 200, "message": "Notifications retrieved successfully", "data": [ { "id": "507f1f77bcf86cd799439030", "title": "System Maintenance Notice", "message": "Scheduled maintenance will occur on January 25th at 2:00 AM UTC.", "type": "MAINTENANCE", "severity": "WARNING", "is_public": true, "is_global": true, "expires_at": "2025-01-26T00:00:00.000Z", "created_at": "2025-01-20T10:00:00.000Z", "updated_at": "2025-01-20T10:00:00.000Z", "is_read": false, "read_at": null } ], "pagination": { "total": 41, "page": 1, "limit": 20, "totalPages": 3, "next_cursor": "2025-01-20T10:00:00.000Z,507f1f77bcf86cd799439030" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
{ "statusCode": 400, "error": "Bad Request", "message": "Invalid request"}{ "statusCode": 401, "error": "Unauthorized", "message": "Authentication required"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
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 |
Returned when the caller is an API auth token that does not hold the resources.read_account permission. JWT and account-login callers do not see this status.
{ "statusCode": 403, "error": "INSUFFICIENT_PERMISSIONS", "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"}{ "statusCode": 429, "error": "Too Many Requests", "message": "Rate limit exceeded"}{ "statusCode": 500, "error": "Internal Server Error", "message": "An unexpected error occurred"}GET /api/v1/notifications/public
Section titled “GET /api/v1/notifications/public”Return every public notification (those flagged is_public: true) without authentication. Use this as a fallback when an API token cannot read the inbox, or to render a status banner in a context where the user is not yet signed in.
Request
Section titled “Request”curl -X GET "https://api.hoody.com/api/v1/notifications/public"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
for await (const item of client.api.inbox.listAnnouncementsIterator()) { console.log(item);}Response
Section titled “Response”{ "statusCode": 200, "message": "Public notifications retrieved successfully", "data": [ { "id": "507f1f77bcf86cd799439030", "title": "System Maintenance Notice", "message": "Scheduled maintenance will occur on January 25th at 2:00 AM UTC.", "type": "MAINTENANCE", "severity": "WARNING", "is_public": true, "is_global": true, "expires_at": "2025-01-26T00:00:00.000Z", "created_at": "2025-01-20T10:00:00.000Z", "updated_at": "2025-01-20T10:00:00.000Z" } ]}| Error Code | Title | Description | Resolution |
|---|---|---|---|
{ "statusCode": 500, "error": "Internal Server Error", "message": "An unexpected error occurred"}GET /api/v1/notifications/summary
Section titled “GET /api/v1/notifications/summary”Lightweight polling endpoint: returns the unread count and the newest notification position without any notification bodies. Use this to detect new notifications; fetch the list only when something changed.
Request
Section titled “Request”curl -X GET "https://api.hoody.com/api/v1/notifications/summary" \ -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.inbox.getSummary();Response
Section titled “Response”{ "statusCode": 200, "message": "Notification summary retrieved successfully", "data": { "unread_count": 3, "is_capped": false, "unread_cap": 100, "latest_id": "507f1f77bcf86cd799439030", "latest_created_at": "2025-01-20T10:00:00.000Z", "poll_interval_seconds": 300 }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
{ "statusCode": 401, "error": "Unauthorized", "message": "Authentication required"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
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 |
Returned when the caller is an API auth token that does not hold the resources.read_account permission. JWT and account-login callers do not see this status.
{ "statusCode": 403, "error": "INSUFFICIENT_PERMISSIONS", "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": 429, "error": "Too Many Requests", "message": "Rate limit exceeded"}{ "statusCode": 500, "error": "Internal Server Error", "message": "An unexpected error occurred"}PUT /api/v1/notifications/{id}/read
Section titled “PUT /api/v1/notifications/{id}/read”Mark a single notification as read. The HTTP method is PUT; PATCH is also accepted on the same endpoint with identical behavior.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Unique identifier of the notification to mark as read |
Request
Section titled “Request”curl -X PUT "https://api.hoody.com/api/v1/notifications/507f1f77bcf86cd799439030/read" \ -H "Authorization: Bearer <jwt-from-account-login>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
await client.api.inbox.markRead('507f1f77bcf86cd799439030');Response
Section titled “Response”{ "statusCode": 200, "message": "Notification marked as read", "data": { "id": "507f1f77bcf86cd799439150", "notification_id": "507f1f77bcf86cd799439030", "is_read": true, "read_at": "2025-01-21T21:30:00.000Z" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
{ "statusCode": 400, "error": "Bad Request", "message": "Invalid ID format"}{ "statusCode": 401, "error": "Unauthorized", "message": "Authentication required"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
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 |
Returned when the caller is not a first-party account login. API auth tokens (with any permission), HTTP Basic, admin impersonation, and realm-scoped sessions all land here.
{ "statusCode": 403, "error": "Forbidden", "message": "First-party account login required"}| 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": "Notification 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"}{ "statusCode": 500, "error": "Internal Server Error", "message": "An unexpected error occurred"}PUT /api/v1/notifications/read-all
Section titled “PUT /api/v1/notifications/read-all”Mark every visible notification as read. The HTTP method is PUT; PATCH is also accepted on the same endpoint with identical behavior.
Request
Section titled “Request”curl -X PUT "https://api.hoody.com/api/v1/notifications/read-all" \ -H "Authorization: Bearer <jwt-from-account-login>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
await client.api.inbox.markAllRead();Response
Section titled “Response”{ "statusCode": 200, "message": "All notifications marked as read", "data": { "count": 5 }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
{ "statusCode": 401, "error": "Unauthorized", "message": "Authentication required"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
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 |
Returned when the caller is not a first-party account login. API auth tokens (with any permission), HTTP Basic, admin impersonation, and realm-scoped sessions all land here.
{ "statusCode": 403, "error": "Forbidden", "message": "First-party account login required"}| 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"}{ "statusCode": 500, "error": "Internal Server Error", "message": "An unexpected error occurred"}