# WriterHub API

WriterHub хранит посты Студии, библиотеки авторов, проектные скиллы и корзину
отдельно для каждого профиля. Эта инструкция предназначена и для человека, и
для AI-агента, которому передали ссылку на документацию и персональный API-ключ.

Production URL:

```text
https://writerhub.tea-gpt.ru
```

Полезные ссылки:

- понятная HTML-документация: `/docs`;
- этот полный Markdown для AI: `/docs/ai.md`;
- краткая карта для AI: `/llms.txt`;
- OpenAPI: `/api/openapi.json`;
- ReDoc: `/api/redoc`;
- Swagger без отправки запросов: `/api/docs`.

## Быстрый старт для AI-агента

1. Полностью прочитайте эту страницу.
2. Получите от пользователя API-ключ WriterHub.
3. Не помещайте ключ в URL, текст ответа, логи, файлы проекта или Git.
4. Проверьте соединение через публичный `GET /api/v1/health`.
5. Перед записью прочитайте текущий ресурс и его `revision`, если она есть.
6. Выполните минимальную необходимую операцию.
7. Перечитайте изменённый ресурс и проверьте результат.
8. Не удаляйте и не очищайте данные без прямой команды пользователя.

Проверка:

```bash
curl --fail --silent --show-error \
  https://writerhub.tea-gpt.ru/api/v1/health
```

## Авторизация и изоляция профилей

Защищённые запросы используют персональный Bearer API-ключ:

```http
Authorization: Bearer <WRITERHUB_PROFILE_API_KEY>
```

Пример:

```bash
curl --fail --silent --show-error \
  -H "Authorization: Bearer $WRITERHUB_API_KEY" \
  https://writerhub.tea-gpt.ru/api/v1/studio/drafts
```

API-ключ сам определяет профиль. В URL и теле запроса нет `user_id`, который
можно подменить. Ключ получает доступ только к данным своего владельца.

Не передавайте ключ:

- в query string;
- в ссылке на документацию;
- в prompt или публичном сообщении;
- в исходном коде;
- в скриншоте;
- в журнале команд.

В браузере ключи создаются через меню профиля → **API-ключи**. Там выбираются
название, права и необязательный срок. Просмотр, замена и отзыв требуют пароль.

## Права API-ключа

Права разделены на чтение и запись:

- `studio:read`, `studio:write`;
- `authors:read`, `authors:write`;
- `skills:read`, `skills:write`;
- `trash:read`, `trash:write`.

Право `write` всегда сопровождается соответствующим `read`. Недостаточные права
возвращают `403`.

## Общие правила записи

### Ревизии

Черновики, папки и редактируемые публикации имеют `revision`. Передавайте
полученное значение как `if_revision`. Если ресурс уже изменился, сервер вернёт
`409`, а не затрёт новое изменение.

Правильный порядок:

1. `GET` ресурса;
2. сохранить его `revision`;
3. отправить `PATCH` с `if_revision`;
4. при `409` снова выполнить `GET`, объединить изменения и повторить.

### Идемпотентность

Читающие запросы безопасно повторять. Повторный commit одного задания импорта
идемпотентен. Создание ресурса без явно заданного ID может создать дубликат,
поэтому после сетевой ошибки сначала проверьте список ресурсов.

### Формат ответа и ошибок

JSON-ошибка:

```json
{
  "detail": "Описание ошибки"
}
```

Основные статусы:

- `200`, `201` — операция выполнена;
- `400` — некорректные данные;
- `401` — авторизация отсутствует;
- `403` — ключ неверен или не имеет права;
- `404` — ресурс не найден;
- `409` — конфликт ревизий или дубликат;
- `410` — ключ отозван или истёк;
- `413` — импорт слишком большой;
- `422` — тело не соответствует OpenAPI;
- `429` — превышен лимит чувствительных auth-операций;
- `503` — серверная конфигурация не завершена.

## Студия: папки и посты

### Прочитать состояние

```http
GET /api/v1/studio/state
GET /api/v1/studio/folders
GET /api/v1/studio/drafts
```

`GET /api/v1/studio/state` возвращает всё состояние Студии. Для обычной работы
предпочитайте точные folder/draft endpoints.

### Создать папку

```http
POST /api/v1/studio/folders
Content-Type: application/json

{
  "name": "Идеи для канала"
}
```

Можно передать стабильный `id`, если интеграция заранее его контролирует:

```json
{
  "id": "content-plan",
  "name": "Контент-план"
}
```

### Переименовать папку

```http
PATCH /api/v1/studio/folders/{folder_id}

{
  "name": "Готово к публикации",
  "if_revision": 0
}
```

### Удалить папку

```http
DELETE /api/v1/studio/folders/{folder_id}?if_revision=1
```

Посты из удалённой папки переходят в системный раздел «Без папки». Сами посты
не удаляются.

### Создать пост

```http
POST /api/v1/studio/drafts
Content-Type: application/json

{
  "title": "Рабочее название",
  "customTitle": "Пост про AI-агентов",
  "folderId": "content-plan",
  "html": "<b>Заголовок</b><br><br>Текст поста",
  "versions": [
    {
      "html": "<b>Заголовок</b><br><br>Текст поста",
      "label": "Создано AI"
    }
  ]
}
```

WriterHub принимает Telegram-compatible HTML. Не добавляйте `<script>`, CSS,
iframe или произвольные browser-теги.

### Прочитать и изменить пост

```http
GET   /api/v1/studio/drafts/{draft_id}
PATCH /api/v1/studio/drafts/{draft_id}
```

Пример изменения текста и перемещения в папку:

```json
{
  "html": "<b>Обновлённый заголовок</b><br><br>Новый текст",
  "folderId": "ready",
  "if_revision": 3
}
```

Чтобы переместить пост в «Без папки», передайте:

```json
{
  "folderId": "",
  "if_revision": 4
}
```

### Удалить пост

```http
DELETE /api/v1/studio/drafts/{draft_id}
```

Черновик переносится в корзину и может быть восстановлен.

### Whole-state API

```http
PUT  /api/v1/studio/state
POST /api/v1/studio/migrate
```

`PUT` заменяет серверное состояние и нужен только для синхронизации полного
клиента. `migrate` объединяет старый browser state с серверным. AI-агенту для
обычной работы эти операции не нужны.

## Авторы

Раздел авторов хранит реконструированные публикации из Telegram. Студия
предназначена для новых пользовательских черновиков, а авторы — для источников,
поиска, коллекций и AI-контекста.

### Список и создание профиля

```http
GET  /api/v1/authors
POST /api/v1/authors
```

Создание пустого профиля:

```json
{
  "id": "new-author",
  "name": "Имя автора",
  "description": "Темы и особенности автора"
}
```

`id` — постоянный slug: строчные латинские буквы, цифры и дефисы.

### Метаданные автора

```http
GET   /api/v1/authors/{author_id}
PATCH /api/v1/authors/{author_id}
```

```json
{
  "name": "Новое имя",
  "description": "Обновлённое описание",
  "avatar": "A"
}
```

### Импорт Telegram Desktop JSON

Импорт всегда двухфазный. Он может создать нового автора или добавить новый
канал существующему.

Анализ:

```http
POST /api/v1/authors/imports/analyze
Content-Type: multipart/form-data

file=<Telegram Desktop result.json>
author_id=<author slug>
author_name=<display name>
channel_id=<channel slug>
description=<optional description>
```

Пример cURL:

```bash
curl --fail --silent --show-error \
  -H "Authorization: Bearer $WRITERHUB_API_KEY" \
  -F "file=@result.json;type=application/json" \
  -F "author_id=new-author" \
  -F "author_name=Имя автора" \
  -F "channel_id=main-channel" \
  https://writerhub.tea-gpt.ru/api/v1/authors/imports/analyze
```

Проверьте в ответе:

- `source_messages`;
- `posts`;
- `pending_review`;
- `external_messages`;
- `polls_skipped`;
- `source_sha256`.

После осознанного подтверждения пользователя:

```http
POST /api/v1/authors/imports/{job_id}/commit
```

Не выполняйте commit автоматически, если анализ показывает неожиданный файл,
автора, канал или большое число элементов на проверке.

### Поиск публикаций

```http
GET /api/v1/authors/{author_id}/posts
```

Query parameters:

- `channel_id`;
- `collection_id`;
- `q` — поиск по заголовку и тексту;
- `date_from`, `date_to` — ISO-даты;
- `limit` — 1–500;
- `offset` — пагинация.

```text
/api/v1/authors/new-author/posts?q=агенты&limit=20&offset=0
```

### Одна публикация

```http
GET /api/v1/authors/{author_id}/posts/{post_id}
```

Ответ содержит метаданные и `parts` — исходные Telegram-сообщения, из которых
собрана публикация.

### Редактирование публикации

```http
PATCH /api/v1/authors/{author_id}/posts/{post_id}
```

Можно изменить заголовок, коллекцию, текст, форматирование, дату и доступные
метаданные частей. Нельзя подменить принадлежность сообщения другому посту или
каналу.

Простое изменение текста:

```json
{
  "title": "Уточнённый заголовок",
  "parts": [
    {
      "id": 123,
      "text": "Исправленный текст без специального форматирования."
    }
  ],
  "if_revision": 0
}
```

Структурированное форматирование:

```json
{
  "parts": [
    {
      "id": 123,
      "segments": [
        {"type": "bold", "text": "Важная мысль"},
        {"type": "plain", "text": "\n\nПродолжение текста."},
        {
          "type": "text_link",
          "text": "Источник",
          "href": "https://example.com"
        }
      ]
    }
  ],
  "if_revision": 1
}
```

Допустимые типы сегментов:

```text
plain, bold, italic, underline, strikethrough, spoiler, code, pre,
blockquote, text_link, link, custom_emoji, hashtag, mention, bot_command
```

Сервер сам создаёт безопасные `html` и `markdown`. Не отправляйте готовый HTML
для author parts.

### Удаление, merge и split

```http
DELETE /api/v1/authors/{author_id}/posts/{post_id}
POST   /api/v1/authors/{author_id}/posts/merge
POST   /api/v1/authors/{author_id}/posts/split
```

Merge:

```json
{
  "post_id": "channel-000123",
  "target_post_id": "channel-000124"
}
```

Объединяются только соседние публикации одного канала.

Split:

```json
{
  "post_id": "channel-000123",
  "before_message_id": 124
}
```

Точка разделения должна быть одной из частей публикации, но не первой.

### Очередь проверки

```http
GET  /api/v1/authors/{author_id}/review
POST /api/v1/authors/{author_id}/review/approve
POST /api/v1/authors/{author_id}/review/remove-foreign
```

```json
{
  "review_ids": ["review-item-id"]
}
```

`approve` переносит выбранные элементы в основную библиотеку.
`remove-foreign` исключает внешние пересылки и сохраняет связанные ответы
автора, когда это возможно.

### Коллекции

```http
POST   /api/v1/authors/{author_id}/collections
PATCH  /api/v1/authors/{author_id}/collections/{collection_id}
DELETE /api/v1/authors/{author_id}/collections/{collection_id}
POST   /api/v1/authors/{author_id}/posts/collection
```

Создание:

```json
{
  "name": "AI-агенты",
  "description": "Подборка публикаций по теме"
}
```

Назначение публикации:

```json
{
  "post_id": "channel-000123",
  "collection_id": "ai-agenty"
}
```

Пустой `collection_id` убирает публикацию из коллекции.

### AI-контекст и экспорт

```http
GET  /api/v1/authors/{author_id}/context.md
POST /api/v1/authors/{author_id}/exports/markdown
```

`context.md` принимает фильтры поиска и возвращает готовый Markdown.

Экспорт:

```json
{
  "post_ids": ["channel-000123", "channel-000124"],
  "package": "combined"
}
```

`package` может быть `combined` или `zip`.

### Удаление автора

```http
DELETE /api/v1/authors/{author_id}
```

Автор переносится в корзину.

## Проектные скиллы

API управляет только скиллами конкретного WriterHub-профиля. Он не изменяет
личные Droid skills на компьютере пользователя.

### Читать

```http
GET /api/v1/skills
GET /api/v1/skills/{skill_id}
GET /api/v1/skills/{skill_id}/documents?path=SKILL.md
GET /api/v1/skills/{skill_id}/context.md
GET /api/v1/skills/{skill_id}/export.zip
```

### Создать

```http
POST /api/v1/skills
```

```json
{
  "manifest": {
    "id": "my-skill",
    "name": "My Skill",
    "description": "Назначение скилла",
    "version": "1.0",
    "tags": ["writing"],
    "documents": [
      {
        "path": "SKILL.md",
        "label": "Инструкции",
        "kind": "core",
        "editable": true
      }
    ]
  },
  "documents": {
    "SKILL.md": "# My Skill\n\nИнструкции."
  }
}
```

`SKILL.md` обязателен.

### Изменить

```http
PATCH /api/v1/skills/{skill_id}
PUT   /api/v1/skills/{skill_id}/documents
```

```json
{
  "path": "SKILL.md",
  "content": "# Обновлённые инструкции\n"
}
```

### Удалить

```http
DELETE /api/v1/skills/{skill_id}
```

Скилл переносится в корзину.

## Корзина

```http
GET    /api/v1/trash
POST   /api/v1/trash/{trash_id}/restore
DELETE /api/v1/trash/{trash_id}
```

Полное удаление требует точного подтверждения:

```json
{
  "confirm": "<trash_id>"
}
```

Элементы автоматически очищаются после 30 дней. До явного запроса пользователя
предпочитайте восстановимое удаление и не выполняйте permanent delete.

## Минимальный Python-клиент

```python
import os
import requests

BASE_URL = "https://writerhub.tea-gpt.ru"
HEADERS = {
    "Authorization": f"Bearer {os.environ['WRITERHUB_API_KEY']}",
    "Content-Type": "application/json",
}

response = requests.get(
    f"{BASE_URL}/api/v1/studio/drafts",
    headers=HEADERS,
    timeout=30,
)
response.raise_for_status()
print(response.json())
```

If `requests` is not installed yet:

```bash
python -m pip install "requests>=2,<3"
```

## Минимальный PowerShell-клиент

```powershell
$headers = @{
  Authorization = "Bearer $env:WRITERHUB_API_KEY"
}

Invoke-RestMethod `
  -Method Get `
  -Uri 'https://writerhub.tea-gpt.ru/api/v1/studio/drafts' `
  -Headers $headers
```

## Чек-лист перед завершением задачи

AI-агент должен проверить:

1. Использован правильный профильный ключ.
2. Ключ имеет только необходимые права.
3. Перед `PATCH` прочитана актуальная revision.
4. После записи ресурс перечитан.
5. При `409` чужое изменение не затёрто.
6. Удаление соответствовало прямой команде пользователя.
7. Секрет не попал в ответ, лог, URL или файл.
8. Пользователю сообщён фактический результат и выполненная проверка.
