# Botovod API reference for agents

Base URL: `https://botovod.srgkslv.ru`. [OpenAPI JSON](https://botovod.srgkslv.ru/agents/openapi.json). [Onboarding](https://botovod.srgkslv.ru/agents/quickstart.md).

## Authentication

Provisioning: authenticated owner `session` cookie, plus `X-CSRF-Token` on writes. Acquire both through `POST /api/admin/session` with a JSON email/password body. Keep one cookie-preserving HTTP client. `GET /api/admin/session` inspects the session and returns its CSRF token. `DELETE /api/admin/session` signs out (204).

Runtime: `Authorization: Bearer <bot-token>` on `/api/bot/*`. The token represents exactly one bot. It cannot list/create bots or access admin endpoints. Bots never specify their own `bot_id` in runtime payloads; the server derives it from the token.

## Provisioning

`GET /api/admin/bots` returns `{"bots":[...]}` with id, name, slug, description, status, quick_commands and relative chat_url. It never returns tokens.

`POST /api/admin/bots` requires JSON name (1–255 characters), slug (1–64 lowercase ASCII alphanumeric characters with hyphens between groups), optional description (up to 1000 characters) and quick_commands (up to 30 nonempty strings, each up to 255 characters; default `["status","help"]`). Returns 201 with bot data and a one-time token. Duplicate slug: 409 `{"detail":{"code":"bot_exists","slug":"..."}}`. The existing bot remains unchanged. No automatic token rotation is provided by this API.

## Chat messages

`POST /api/bot/messages`

```json
{"content":"Backup finished","message_type":"text","severity":"info","metadata":{}}
```

Returns `{"ok":true,"message_id":123}`. Message types: `text`, `event`, `error`, `command_result`. Severity values used by the UI: `info`, `warning`, `critical`. Store optional structured data in metadata. Chat content is plain text; do not depend on full Markdown or HTML rendering.

## Inbox events

`POST /api/bot/events`

```json
{"title":"Backup complete","body":"All files saved","severity":"info","also_post_to_chat":true,"metadata":{"job":"nightly-backup"}}
```

Returns `{"ok":true,"event_id":321,"message_id":123}` when posted to chat; otherwise message_id is absent. Warning/critical events always also post to chat; info events post to chat only when also_post_to_chat is true. Inbox events are what the Android client checks for system notifications. Chat-only messages do not create Inbox events.

## Commands

`GET /api/bot/commands/pending` claims all currently pending commands:

```json
{"commands":[{"id":15,"text":"status","created_at":"2026-10-26T12:00:00","metadata":{"user_message_id":100}}]}
```

An empty queue returns `{"commands":[]}`. Claimed commands are not returned again immediately. By default a command can be reclaimed after 300 seconds if it was not completed. Treat delivery as at least once and run one worker per bot. Do not cache this GET or use it to inspect the queue without processing.

`POST /api/bot/commands/15/complete`, success:

```json
{"status":"done","result":"The service is running","metadata":{}}
```

Failure:

```json
{"status":"failed","error":"The upstream service timed out","metadata":{}}
```

Returns ok, command_id, status, and message_id; repeated completion includes idempotent=true. Done requires nonempty result or metadata.image_url; failed requires nonempty error. A command belonging to another bot returns 404. Retry completion by the same command ID after an ambiguous timeout, without rerunning its side effects.

## Heartbeat

`POST /api/bot/heartbeat` with `{"status":"online","metadata":{}}` returns `{"ok":true}`. Status may be omitted to update only last-seen time. Recommended statuses: online, offline, error, unknown. Metadata is accepted here for compatibility but is currently not persisted. Send about every 60 seconds; send offline on graceful shutdown if possible.

## PDF commands and result images

A PDF uploaded by the owner through the UI creates a command whose text is `pdf:<upload-id>` and metadata includes `kind=pdf`, upload_id and filename. The starter does not process PDFs; implement a handler if needed.

Download it with `GET /api/bot/uploads/<upload-id>` and your bot's Bearer header. Success returns PDF bytes.

Upload a result image via `POST /api/bot/uploads/image` using multipart field `file`. Supported filename extensions: .png, .jpg, .jpeg, .webp. The response includes `ok`, `file_id`, `url`, `path`, `kind`. Use `url`, which is a relative public image URL; `path` is a server filesystem path and should not be shown to the user. Uploaded image links are not private signed URLs; do not upload confidential material expecting link-level access control.

Then complete the command, for example:

```json
{"status":"done","result":"Analysis complete","metadata":{"image_url":"/uploads/images/<file-id>.png"}}
```

## Errors and retries

| HTTP | Action |
| --- | --- |
| 400 | Fix the request semantics; read detail. |
| 401 | Missing/invalid credentials or expired owner session; stop and recover access. |
| 403 | Admin CSRF missing/mismatch; get the token for the same authenticated cookie. |
| 404 | Unknown upload/command or command belongs to another bot. |
| 409 | Bot slug exists; reuse saved credentials rather than creating duplicates. |
| 413 | Request exceeds an application/proxy limit; reduce upload/payload size. |
| 422 | JSON/form schema validation failed; inspect detail and fix fields. |
| 429 | Back off; login lockout after five failed passwords lasts 15 minutes. |
| 5xx / network | Bounded exponential backoff with jitter; do not blindly duplicate non-idempotent writes. |

Use request timeouts around 30 seconds. Provisioning and runtime are separate clients/environments. Never log Authorization, session cookies, passwords, or token-bearing creation responses.
