# Bot: Registrations **Page:** api/bot/registrations [Download Raw Markdown](./api/bot/registrations.md) --- # Bot: Registrations Register a channel bot into a container, start and stop its long-polling worker, manage its channel surface (commands and profile), read and set its admission policy, audit what it has done, and revoke chat-user sessions or every token lineage at once. Every operation on this page is a management-class endpoint. Calls require `Authorization: Bearer `, and the calling account must own the container the kit runs in. The channel bot token is sent once in the register body over HTTPS, verified with the channel, stored encrypted on the container, and never echoed back or logged. A new registration is created stopped; run `hoody bot start ` to begin polling. Every container hostname on this page follows the shape `{projectId}-{containerId}-bot-1.{server}.containers.hoody.com`. Replace the placeholders with the project id, container id, and server node of the container the kit runs in. ## Registration lifecycle Use these four endpoints to create a registration, list what you have, read one, and delete it. The channel bot token is sent once in the register body; what comes back is the registration without its token. ### `GET /api/v1/bot/registrations` List the registrations owned by the calling account. This endpoint takes no parameters. ```bash curl -X GET \ "https://{projectId}-{containerId}-bot-1.{server}.containers.hoody.com/api/v1/bot/registrations" \ -H "Authorization: Bearer $HOODY_TOKEN" ``` ```typescript import { HoodyClient } from 'hoody-sdk'; const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-bot-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN }); await client.bot.registrations.list(); ``` ```bash hoody bot list ``` ```json { "registrations": [ { "id": "reg_01HF7XK3MRW2P6G8N5Y4Z0VABC", "channel": "telegram", "label": "AcmeBot", "channel_bot_id": "7123456789", "channel_username": "@acme_bot", "state": "running", "created_at": 1700000000000, "updated_at": 1700000001000, "last_error_code": null, "last_error_at": null } ] } ``` | Error Code | Status | Title | Description | Resolution | |------------|--------|-------|-------------|------------| | `query_ambiguous` | 400 | Ambiguous query | The request could not be parsed. | Re-check the request shape and resend. | | `internal_error` | 500 | Internal server error | An unexpected error occurred while processing the request. | Retry the request. If it persists, capture the response and contact support. | | `admission_unavailable` | 503 | Admission unavailable | The kit cannot currently admit the request. | Retry the request after a short delay. | ### `POST /api/v1/bot/registrations` The channel bot token is read on the client side and sent in this body over HTTPS. It is verified with the channel, stored encrypted and never echoed. What comes back is the registration without its token. #### Request body | Name | Type | Required | Description | |------|------|----------|-------------| | `channel` | string | Yes | The channel the bot is registered with. The only supported value is `telegram`. | | `token` | string | Yes | The channel bot token. Read on the client side and sent over HTTPS; the kit verifies it with the channel, encrypts it on the container, and never echoes it. | | `label` | string | No | A short operator label (up to 64 characters). | ```bash curl -X POST \ "https://{projectId}-{containerId}-bot-1.{server}.containers.hoody.com/api/v1/bot/registrations" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "channel": "telegram", "token": "7123456789:AAF-AAaBcDeFgHiJkLmNoPqRsTuVwXyZ", "label": "AcmeBot" }' ``` ```typescript import { HoodyClient } from 'hoody-sdk'; const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-bot-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN }); await client.bot.registrations.create({ channel: 'telegram', token: '7123456789:AAF-AAaBcDeFgHiJkLmNoPqRsTuVwXyZ', label: 'AcmeBot' }); ``` ```bash hoody bot create --channel telegram --token "7123456789:AAF-AAaBcDeFgHiJkLmNoPqRsTuVwXyZ" --label "AcmeBot" ``` ```json { "id": "reg_01HF7XK3MRW2P6G8N5Y4Z0VABC", "channel": "telegram", "label": "AcmeBot", "channel_bot_id": "7123456789", "channel_username": "@acme_bot", "state": "stopped", "created_at": 1700000000000, "updated_at": 1700000000000, "last_error_code": null, "last_error_at": null } ``` | Error Code | Status | Title | Description | Resolution | |------------|--------|-------|-------------|------------| | `invalid_body` | 400 | Invalid body | The request body could not be parsed or fails schema validation. | Compare the body with the documented schema and resend. | | `unsupported_channel` | 400 | Unsupported channel | The supplied channel is not supported at this time. | Use a supported channel value (for example, `telegram`). | | `invalid_token` | 400 | Invalid token | The supplied token is missing, not a string, or empty. | Pass the bot token through the CLI's required `--token` flag. | | `invalid_label` | 400 | Invalid label | The supplied label is too long or otherwise invalid. | Use a label of 64 characters or fewer, or omit the field. | | `channel_token_rejected` | 400 | Channel token rejected | The channel rejected the stored token. On register, nothing was stored. | Verify the token is correct and not revoked at the channel, then re-register with `hoody bot create --token --channel telegram`. | | `query_ambiguous` | 400 | Ambiguous query | The request could not be parsed. | Re-check the request shape and resend. | | `registration_duplicate` | 409 | Duplicate registration | A registration already exists for this channel bot token. | List registrations and reuse the existing one, or supply a different token. | | `internal_error` | 500 | Internal server error | An unexpected error occurred while processing the request. | Retry the request. If it persists, capture the response and contact support. | | `channel_unavailable` | 503 | Channel unavailable | The channel API is not reachable. | Verify the channel is up and retry the request. | | `admission_unavailable` | 503 | Admission unavailable | The kit cannot currently admit the request. | Retry the request after a short delay. | ### `GET /api/v1/bot/registrations/{registrationId}` Read one registration. #### Parameters | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `registrationId` | path | string | Yes | Registration id returned by register or list. | ```bash curl -X GET \ "https://{projectId}-{containerId}-bot-1.{server}.containers.hoody.com/api/v1/bot/registrations/{registrationId}" \ -H "Authorization: Bearer $HOODY_TOKEN" ``` ```typescript import { HoodyClient } from 'hoody-sdk'; const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-bot-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN }); await client.bot.registrations.get(registrationId); ``` ```bash hoody bot get {registrationId} ``` ```json { "id": "reg_01HF7XK3MRW2P6G8N5Y4Z0VABC", "channel": "telegram", "label": "AcmeBot", "channel_bot_id": "7123456789", "channel_username": "@acme_bot", "state": "running", "created_at": 1700000000000, "updated_at": 1700000001000, "last_error_code": null, "last_error_at": null } ``` | Error Code | Status | Title | Description | Resolution | |------------|--------|-------|-------------|------------| | `query_ambiguous` | 400 | Ambiguous query | The request could not be parsed. | Re-check the request shape and resend. | | `registration_not_found` | 404 | Registration not found | No registration exists at the supplied id. | List registrations to find the correct id. | | `internal_error` | 500 | Internal server error | An unexpected error occurred while processing the request. | Retry the request. If it persists, capture the response and contact support. | | `admission_unavailable` | 503 | Admission unavailable | The kit cannot currently admit the request. | Retry the request after a short delay. | ### `DELETE /api/v1/bot/registrations/{registrationId}` Deletes the registration and its stored channel token, and stops its poller. #### Parameters | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `registrationId` | path | string | Yes | Registration id returned by register or list. | ```bash curl -X DELETE \ "https://{projectId}-{containerId}-bot-1.{server}.containers.hoody.com/api/v1/bot/registrations/{registrationId}" \ -H "Authorization: Bearer $HOODY_TOKEN" ``` ```typescript import { HoodyClient } from 'hoody-sdk'; const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-bot-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN }); await client.bot.registrations.delete(registrationId); ``` ```bash hoody bot delete {registrationId} ``` The response has no body. | Error Code | Status | Title | Description | Resolution | |------------|--------|-------|-------------|------------| | `query_ambiguous` | 400 | Ambiguous query | The request could not be parsed. | Re-check the request shape and resend. | | `registration_not_found` | 404 | Registration not found | No registration exists at the supplied id. | List registrations to find the correct id. | | `internal_error` | 500 | Internal server error | An unexpected error occurred while processing the request. | Retry the request. If it persists, capture the response and contact support. | | `admission_unavailable` | 503 | Admission unavailable | The kit cannot currently admit the request. | Retry the request after a short delay. | ## Polling The registration state is stored as `running` or `stopped`, so a started registration comes back up at the next start of the kit. The long-polling worker is brought up here. ### `POST /api/v1/bot/registrations/{registrationId}/start` Records the intent to poll and starts the worker. The registration state is stored, so a started registration is brought back up at the next start of the kit. #### Parameters | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `registrationId` | path | string | Yes | Registration id returned by register or list. | ```bash curl -X POST \ "https://{projectId}-{containerId}-bot-1.{server}.containers.hoody.com/api/v1/bot/registrations/{registrationId}/start" \ -H "Authorization: Bearer $HOODY_TOKEN" ``` ```typescript import { HoodyClient } from 'hoody-sdk'; const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-bot-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN }); await client.bot.registrations.start(registrationId); ``` ```bash hoody bot start {registrationId} ``` ```json { "id": "reg_01HF7XK3MRW2P6G8N5Y4Z0VABC", "channel": "telegram", "label": "AcmeBot", "channel_bot_id": "7123456789", "channel_username": "@acme_bot", "state": "running", "created_at": 1700000000000, "updated_at": 1700000003000, "last_error_code": null, "last_error_at": null } ``` The intent is recorded, but this build wired no poller, so nothing is polling and health reports `polling.active: 0`. ```json { "id": "reg_01HF7XK3MRW2P6G8N5Y4Z0VABC", "channel": "telegram", "label": "AcmeBot", "channel_bot_id": "7123456789", "channel_username": "@acme_bot", "state": "running", "created_at": 1700000000000, "updated_at": 1700000003000, "last_error_code": null, "last_error_at": null, "polling": "unavailable" } ``` | Error Code | Status | Title | Description | Resolution | |------------|--------|-------|-------------|------------| | `query_ambiguous` | 400 | Ambiguous query | The request could not be parsed. | Re-check the request shape and resend. | | `registration_not_found` | 404 | Registration not found | No registration exists at the supplied id. | List registrations to find the correct id. | | `internal_error` | 500 | Internal server error | An unexpected error occurred while processing the request. | Retry the request. If it persists, capture the response and contact support. | | `admission_unavailable` | 503 | Admission unavailable | The kit cannot currently admit the request. | Retry the request after a short delay. | ### `POST /api/v1/bot/registrations/{registrationId}/stop` Stop long-polling for a registration. The registration and its stored channel token are kept. #### Parameters | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `registrationId` | path | string | Yes | Registration id returned by register or list. | ```bash curl -X POST \ "https://{projectId}-{containerId}-bot-1.{server}.containers.hoody.com/api/v1/bot/registrations/{registrationId}/stop" \ -H "Authorization: Bearer $HOODY_TOKEN" ``` ```typescript import { HoodyClient } from 'hoody-sdk'; const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-bot-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN }); await client.bot.registrations.stop(registrationId); ``` ```bash hoody bot stop {registrationId} ``` ```json { "id": "reg_01HF7XK3MRW2P6G8N5Y4Z0VABC", "channel": "telegram", "label": "AcmeBot", "channel_bot_id": "7123456789", "channel_username": "@acme_bot", "state": "stopped", "created_at": 1700000000000, "updated_at": 1700000004000, "last_error_code": null, "last_error_at": null } ``` | Error Code | Status | Title | Description | Resolution | |------------|--------|-------|-------------|------------| | `query_ambiguous` | 400 | Ambiguous query | The request could not be parsed. | Re-check the request shape and resend. | | `registration_not_found` | 404 | Registration not found | No registration exists at the supplied id. | List registrations to find the correct id. | | `internal_error` | 500 | Internal server error | An unexpected error occurred while processing the request. | Retry the request. If it persists, capture the response and contact support. | | `admission_unavailable` | 503 | Admission unavailable | The kit cannot currently admit the request. | Retry the request after a short delay. | ## Channel surface Two operations publish the bot's surface to the channel: the registered commands and the bot profile (name, descriptions, default admin rights). The profile is stored before it is published, so a channel failure leaves the stored value ready to be republished by repeating the request. On `profile update`, an absent key leaves the stored value alone, `null` stops the kit managing that field, and an empty string clears the field at the channel. The three are different operations. ### `POST /api/v1/bot/registrations/{registrationId}/commands/sync` Runs the bot's one publication reconciliation, the same one an activation runs, and answers with the readback diff: commands per scope, obsolete scopes deleted, the menu button, and the stored profile. Idempotent: a second run reports `changed: false`. #### Parameters | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `registrationId` | path | string | Yes | Registration id returned by register or list. | ```bash curl -X POST \ "https://{projectId}-{containerId}-bot-1.{server}.containers.hoody.com/api/v1/bot/registrations/{registrationId}/commands/sync" \ -H "Authorization: Bearer $HOODY_TOKEN" ``` ```typescript import { HoodyClient } from 'hoody-sdk'; const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-bot-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN }); await client.bot.registrations.syncCommands(registrationId); ``` ```bash hoody bot commands sync {registrationId} ``` ```json { "registration_id": "reg_01HF7XK3MRW2P6G8N5Y4Z0VABC", "changed": true, "written": ["default|en"], "unchanged": [], "deleted": [], "trimmed": [], "rejected": [], "menu_button_changed": false, "profile_changed": false } ``` | Error Code | Status | Title | Description | Resolution | |------------|--------|-------|-------------|------------| | `channel_token_rejected` | 400 | Channel token rejected | The registration's stored channel token was rejected by the channel. The registration is preserved. | Rotate the token at the channel and re-register, or repeat the call to retry once the stored token is accepted again. | | `query_ambiguous` | 400 | Ambiguous query | The request could not be parsed. | Re-check the request shape and resend. | | `registration_not_found` | 404 | Registration not found | No registration exists at the supplied id. | List registrations to find the correct id. | | `internal_error` | 500 | Internal server error | An unexpected error occurred while processing the request. | Retry the request. If it persists, capture the response and contact support. | | `channel_sync_failed` | 502 | Channel sync failed | The channel rejected the publication. | Inspect the registration's `last_error_code` and correct the input. | | `channel_unavailable` | 503 | Channel unavailable | The channel API is not reachable. | Verify the channel is up and retry the request. | | `admission_unavailable` | 503 | Admission unavailable | The kit cannot currently admit the request. | Retry the request after a short delay. | ### `PUT /api/v1/bot/registrations/{registrationId}/profile` Stores the bot profile and publishes it to the channel. An absent key leaves the stored value unchanged, `null` stops the kit managing that field, and an empty string clears it at the channel. The three are different operations. The profile is stored before it is published, so a channel failure leaves the stored value ready to be republished by repeating the request. #### Parameters | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `registrationId` | path | string | Yes | Registration id returned by register or list. | #### Request body | Name | Type | Required | Description | |------|------|----------|-------------| | `name` | string | No | The bot name to publish (up to 64 characters). | | `description` | string | No | The bot description to publish (up to 512 characters). | | `short_description` | string | No | The bot short description to publish (up to 120 characters). | | `language_code` | string | No | The IETF tag the three texts belong to. | | `administrator_rights` | object | No | Default administrator rights to publish for groups and channels. Each sub-object maps capability names to booleans. | ```bash curl -X PUT \ "https://{projectId}-{containerId}-bot-1.{server}.containers.hoody.com/api/v1/bot/registrations/{registrationId}/profile" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Acme Bot", "description": "Acme operations bot.", "short_description": "Acme ops", "language_code": "en" }' ``` ```typescript import { HoodyClient } from 'hoody-sdk'; const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-bot-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN }); await client.bot.registrations.updateProfile(registrationId, { name: 'Acme Bot', description: 'Acme operations bot.', short_description: 'Acme ops', language_code: 'en' }); ``` ```bash hoody bot profile update {registrationId} \ --name "Acme Bot" \ --description "Acme operations bot." \ --short-description "Acme ops" \ --language-code en ``` ```json { "registration_id": "reg_01HF7XK3MRW2P6G8N5Y4Z0VABC", "profile": { "name": "Acme Bot", "description": "Acme operations bot.", "short_description": "Acme ops", "language_code": "en" }, "changed": true, "written": ["name", "description", "short_description"], "unchanged": [], "refused": [] } ``` | Error Code | Status | Title | Description | Resolution | |------------|--------|-------|-------------|------------| | `invalid_body` | 400 | Invalid body | The request body could not be parsed or fails schema validation. | Compare the body with the documented schema and resend. | | `invalid_profile` | 400 | Invalid profile | The supplied profile is not acceptable. | Ensure each field is within its length limit and resend. | | `channel_token_rejected` | 400 | Channel token rejected | The registration's stored channel token was rejected by the channel. The registration and any profile already sent are preserved. | Rotate the token at the channel and re-register, or repeat the call to retry once the stored token is accepted again. | | `query_ambiguous` | 400 | Ambiguous query | The request could not be parsed. | Re-check the request shape and resend. | | `registration_not_found` | 404 | Registration not found | No registration exists at the supplied id. | List registrations to find the correct id. | | `internal_error` | 500 | Internal server error | An unexpected error occurred while processing the request. | Retry the request. If it persists, capture the response and contact support. | | `channel_sync_failed` | 502 | Channel sync failed | The channel rejected the publication. | Inspect the registration's `last_error_code` and correct the input. | | `channel_unavailable` | 503 | Channel unavailable | The channel API is not reachable. | Verify the channel is up and retry the request. | | `admission_unavailable` | 503 | Admission unavailable | The kit cannot currently admit the request. | Retry the request after a short delay. | ## Policy Each registration stores a `mode` (a stored label, `single` or `multi`) and two allowlists (`users` and `chats`). Admission is decided by the allowlists; `mode` is a label the kit records, not a constraint on the number of signed-in accounts. `mode` is a stored label per registration, `single` or `multi`. The gate checks the two allowlists and nothing else, so `mode` does not count signed-in accounts. The chat allowlist defaults to `null`, which means direct messages only. A non-empty list is exhaustive and includes direct messages: a DM's chat id is the user's own id, so a list naming only a group also stops every DM, because a read command typed in a group posts one person's account data into the room. Name the DM ids too if you want them served. An empty array admits no chat at all. The users allowlist defaults to `null`, which means every user is admitted; an empty array is an allowlist that admits nobody, and is not the same as `null`. Both allowlists are enforced before a chat user is looked up and before a browser login is admitted, and a refusal is written to the audit log. ### `GET /api/v1/bot/registrations/{registrationId}/policy` Returns the registration's mode and allowlists as the gate enforces them. A null `users` allowlist admits every user; a null `chats` allowlist means direct messages only, so a group is admitted only when the `chats` list names it; an empty array admits nobody. #### Parameters | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `registrationId` | path | string | Yes | Registration id returned by register or list. | ```bash curl -X GET \ "https://{projectId}-{containerId}-bot-1.{server}.containers.hoody.com/api/v1/bot/registrations/{registrationId}/policy" \ -H "Authorization: Bearer $HOODY_TOKEN" ``` ```typescript import { HoodyClient } from 'hoody-sdk'; const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-bot-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN }); await client.bot.registrations.getPolicy(registrationId); ``` ```bash hoody bot policy get {registrationId} ``` ```json { "registration_id": "reg_01HF7XK3MRW2P6G8N5Y4Z0VABC", "mode": "single", "allowlists": { "users": null, "chats": null } } ``` | Error Code | Status | Title | Description | Resolution | |------------|--------|-------|-------------|------------| | `query_ambiguous` | 400 | Ambiguous query | The request could not be parsed. | Re-check the request shape and resend. | | `registration_not_found` | 404 | Registration not found | No registration exists at the supplied id. | List registrations to find the correct id. | | `internal_error` | 500 | Internal server error | An unexpected error occurred while processing the request. | Retry the request. If it persists, capture the response and contact support. | | `admission_unavailable` | 503 | Admission unavailable | The kit cannot currently admit the request. | Retry the request after a short delay. | ### `PUT /api/v1/bot/registrations/{registrationId}/policy` Sets the mode and allowlists. `mode` is required; an absent allowlist key is left alone, an explicit `null` takes that list out of force, and an empty array admits nobody. The answer is the policy read back from the store. The allowlists take effect on the next update the bot receives: an unadmitted actor or chat is refused before the chat user is looked up, and the refusal is written to the audit log. #### Parameters | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `registrationId` | path | string | Yes | Registration id returned by register or list. | #### Request body | Name | Type | Required | Description | |------|------|----------|-------------| | `mode` | string | Yes | The stored mode label, `single` or `multi`. | | `allowlists` | object | No | The allowlists to apply. An absent key leaves the stored list alone; an explicit `null` takes it out of force; an empty array admits nobody. | | `allowlists.users` | array | No | Channel user ids this bot serves. `null` admits every user; an empty array admits nobody. | | `allowlists.chats` | array | No | Chat ids this bot serves. `null` means direct messages only; a non-empty list is exhaustive and includes direct messages; an empty array admits no chat. | ```bash curl -X PUT \ "https://{projectId}-{containerId}-bot-1.{server}.containers.hoody.com/api/v1/bot/registrations/{registrationId}/policy" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "mode": "single", "allowlists": { "users": null, "chats": null } }' ``` ```typescript import { HoodyClient } from 'hoody-sdk'; const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-bot-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN }); await client.bot.registrations.updatePolicy(registrationId, { mode: 'single', allowlists: { users: null, chats: null } }); ``` ```bash hoody bot policy update {registrationId} --mode single --allowlists null ``` ```json { "registration_id": "reg_01HF7XK3MRW2P6G8N5Y4Z0VABC", "mode": "single", "allowlists": { "users": null, "chats": null } } ``` | Error Code | Status | Title | Description | Resolution | |------------|--------|-------|-------------|------------| | `invalid_body` | 400 | Invalid body | The request body could not be parsed or fails schema validation. | Compare the body with the documented schema and resend. | | `invalid_policy` | 400 | Invalid policy | The supplied policy is not acceptable. | Ensure `mode` is one of `single` or `multi` and that each allowlist is an array of strings (or `null`), with at most 1000 entries. | | `query_ambiguous` | 400 | Ambiguous query | The request could not be parsed. | Re-check the request shape and resend. | | `registration_not_found` | 404 | Registration not found | No registration exists at the supplied id. | List registrations to find the correct id. | | `internal_error` | 500 | Internal server error | An unexpected error occurred while processing the request. | Retry the request. If it persists, capture the response and contact support. | | `admission_unavailable` | 503 | Admission unavailable | The kit cannot currently admit the request. | Retry the request after a short delay. | ## Audit log Every action the kit takes on behalf of a registration lands in a per-registration log. The log is redacted on write and paged by cursor. A 90-day retention window applies; purging inside the window is refused unless waived. A cutoff inside the 90-day retention window is refused rather than clamped. Pass `all=true` to waive the floor. ### `GET /api/v1/bot/registrations/{registrationId}/logs` Returns the redacted audit log, newest first. Paging is by cursor (`next_before_id`), not by offset, so a cursor stays valid across the retention sweep. #### Parameters | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `registrationId` | path | string | Yes | Registration id returned by register or list. | | `actor` | query | string | No | Narrow the page to one chat user. Matched exactly against the stored `actor` value, which carries the channel prefix, for Telegram `telegram:`, not the bare id the revoke path takes. | | `since` | query | integer | No | Drop entries older than this epoch-millisecond timestamp. A filter on the page, not a cursor: paging continues past it. | | `limit` | query | integer | No | Page size; capped by the store at 500. The answer reports the size actually used. | | `before_id` | query | integer | No | The cursor: the `next_before_id` of the previous page. Omit for the newest page. | ```bash curl -X GET \ "https://{projectId}-{containerId}-bot-1.{server}.containers.hoody.com/api/v1/bot/registrations/{registrationId}/logs?actor=telegram%3A7123456789&limit=100" \ -H "Authorization: Bearer $HOODY_TOKEN" ``` ```typescript import { HoodyClient } from 'hoody-sdk'; const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-bot-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN }); await client.bot.registrations.listLogs(registrationId, { actor: 'telegram:7123456789', limit: 100 }); ``` ```bash hoody bot logs list {registrationId} --actor "telegram:7123456789" --limit 100 ``` ```json { "registration_id": "reg_01HF7XK3MRW2P6G8N5Y4Z0VABC", "entries": [ { "id": 1001, "at": 1700000123000, "actor": "telegram:7123456789", "user_id": "usr_01HF7XK3MRW2P6G8N5Y4Z0VDEF", "action": "login", "outcome": "allowed", "target": null, "detail": null } ], "next_before_id": null, "limit": 100 } ``` | Error Code | Status | Title | Description | Resolution | |------------|--------|-------|-------------|------------| | `invalid_query` | 400 | Invalid query | A query parameter was malformed. | Re-check the named parameter and supply a valid value. | | `query_ambiguous` | 400 | Ambiguous query | The request could not be parsed. | Re-check the request shape and resend. | | `registration_not_found` | 404 | Registration not found | No registration exists at the supplied id. | List registrations to find the correct id. | | `internal_error` | 500 | Internal server error | An unexpected error occurred while processing the request. | Retry the request. If it persists, capture the response and contact support. | | `admission_unavailable` | 503 | Admission unavailable | The kit cannot currently admit the request. | Retry the request after a short delay. | ### `DELETE /api/v1/bot/registrations/{registrationId}/logs` Deletes this registration's audit entries at or below a cutoff. A cutoff inside the 90-day retention window is refused rather than clamped; `all=true` waives the floor. #### Parameters | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `registrationId` | path | string | Yes | Registration id returned by register or list. | | `older_than` | query | integer | No | Delete entries at or below this epoch-millisecond timestamp. Defaults to the 90-day retention boundary. | | `all` | query | string | No | Waive the 90-day retention floor. Without it a cutoff inside the retention window is refused, never clamped. Literal values: `true`, `false`. | ```bash curl -X DELETE \ "https://{projectId}-{containerId}-bot-1.{server}.containers.hoody.com/api/v1/bot/registrations/{registrationId}/logs?older_than=1700000000000&all=true" \ -H "Authorization: Bearer $HOODY_TOKEN" ``` ```typescript import { HoodyClient } from 'hoody-sdk'; const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-bot-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN }); await client.bot.registrations.purgeLogs(registrationId, { older_than: 1700000000000, all: 'true' }); ``` ```bash hoody bot logs purge {registrationId} --older-than 1700000000000 --all true -y ``` ```json { "registration_id": "reg_01HF7XK3MRW2P6G8N5Y4Z0VABC", "deleted": 12, "cutoff": 1700000000000, "retention_floor": 1697400000000, "all": false } ``` | Error Code | Status | Title | Description | Resolution | |------------|--------|-------|-------------|------------| | `invalid_query` | 400 | Invalid query | A query parameter was malformed. | Re-check the named parameter and supply a valid value. | | `query_ambiguous` | 400 | Ambiguous query | The request could not be parsed. | Re-check the request shape and resend. | | `registration_not_found` | 404 | Registration not found | No registration exists at the supplied id. | List registrations to find the correct id. | | `logs_purge_refused` | 409 | Log purge refused | The cutoff falls inside the 90-day retention window. | Pass `all=true` to waive the floor, or supply an older cutoff. | | `internal_error` | 500 | Internal server error | An unexpected error occurred while processing the request. | Retry the request. If it persists, capture the response and contact support. | | `admission_unavailable` | 503 | Admission unavailable | The kit cannot currently admit the request. | Retry the request after a short delay. | ## Sessions and tokens Revoke a single chat user's lineage, or every chat user of the registration at once. The leaf is deleted through the parent, the parent is forgotten, and any intents, wizards and subscriptions bound to that lineage are invalidated. Neither endpoint can delete the root parent token; that is the account holder's own and lives behind `hoody auth tokens delete `. The response's `manual_deletes` array lists any parent id whose deletion did not succeed, so the operator can finish the cleanup with the auth tokens command. ### `POST /api/v1/bot/registrations/{registrationId}/sessions/{channelUserId}/revoke` Revokes one chat user's login as the operator: the leaf is deleted through the parent, the parent is forgotten, and the intents, wizards and subscriptions bound to that lineage are invalidated. A parent the platform refused to delete is reported by id for `hoody auth tokens delete`. #### Parameters | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `registrationId` | path | string | Yes | Registration id returned by register or list. | | `channelUserId` | path | string | Yes | The channel's own id for the chat user (the Telegram user id), without a channel prefix. The audit log's `actor` column spells the same user differently, `telegram:`, so a value copied from there is not accepted here. | ```bash curl -X POST \ "https://{projectId}-{containerId}-bot-1.{server}.containers.hoody.com/api/v1/bot/registrations/{registrationId}/sessions/7123456789/revoke" \ -H "Authorization: Bearer $HOODY_TOKEN" ``` ```typescript import { HoodyClient } from 'hoody-sdk'; const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-bot-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN }); await client.bot.registrations.revokeSession(registrationId, '7123456789'); ``` ```bash hoody bot sessions revoke {registrationId} 7123456789 ``` ```json { "registration_id": "reg_01HF7XK3MRW2P6G8N5Y4Z0VABC", "channel_user_id": "7123456789", "user_id": "usr_01HF7XK3MRW2P6G8N5Y4Z0VDEF", "revoked": true, "kind": "lineage", "parent_token_id": "tok_01HF7XK3MRW2P6G8N5Y4Z0VGHI", "leaf_token_id": "tok_01HF7XK3MRW2P6G8N5Y4Z0VJKL", "pasted_token_id": null, "leaf_deleted": true, "invalidated": { "intents": 0, "wizards": 0, "subscriptions": 0 }, "manual_deletes": ["tok_01HF7XK3MRW2P6G8N5Y4Z0VGHI"], "sessions_deleted": 1, "browser_sessions_revoked": 0 } ``` | Error Code | Status | Title | Description | Resolution | |------------|--------|-------|-------------|------------| | `query_ambiguous` | 400 | Ambiguous query | The request could not be parsed. | Re-check the request shape and resend. | | `registration_not_found` | 404 | Registration not found | No registration exists at the supplied id. | List registrations to find the correct id. | | `session_not_found` | 404 | Session not found | No session exists for the supplied channel user id. | Confirm the channel user id (the bare Telegram id, not the audit log's `actor` value). | | `internal_error` | 500 | Internal server error | An unexpected error occurred while processing the request. | Retry the request. If it persists, capture the response and contact support. | | `admission_unavailable` | 503 | Admission unavailable | The kit cannot currently admit the request. | Retry the request after a short delay. | ### `POST /api/v1/bot/registrations/{registrationId}/tokens/revoke-all` Revokes every chat user of this registration. A failure does not stop the sweep: each one is recorded and the rest are still revoked. #### Parameters | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `registrationId` | path | string | Yes | Registration id returned by register or list. | ```bash curl -X POST \ "https://{projectId}-{containerId}-bot-1.{server}.containers.hoody.com/api/v1/bot/registrations/{registrationId}/tokens/revoke-all" \ -H "Authorization: Bearer $HOODY_TOKEN" ``` ```typescript import { HoodyClient } from 'hoody-sdk'; const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-bot-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN }); await client.bot.registrations.revokeAllTokens(registrationId); ``` ```bash hoody bot tokens revoke {registrationId} --all ``` ```json { "registration_id": "reg_01HF7XK3MRW2P6G8N5Y4Z0VABC", "users": 2, "revoked": 2, "skipped": 0, "sessions_deleted": 2, "browser_sessions_revoked": 0, "failed": [], "manual_deletes": [ "tok_01HF7XK3MRW2P6G8N5Y4Z0VGHI", "tok_01HF7XK3MRW2P6G8N5Y4Z0VMNO" ] } ``` | Error Code | Status | Title | Description | Resolution | |------------|--------|-------|-------------|------------| | `query_ambiguous` | 400 | Ambiguous query | The request could not be parsed. | Re-check the request shape and resend. | | `registration_not_found` | 404 | Registration not found | No registration exists at the supplied id. | List registrations to find the correct id. | | `internal_error` | 500 | Internal server error | An unexpected error occurred while processing the request. | Retry the request. If it persists, capture the response and contact support. | | `admission_unavailable` | 503 | Admission unavailable | The kit cannot currently admit the request. | Retry the request after a short delay. |