# MMAT Studio API v1

API позволяет использовать MMAT Studio из собственного приложения: хранить рабочие бакеты, управлять графами систем и нодами, запускать MMAT и получать результат, ход выполнения и файлы. Данные и запуски используют существующее хранилище и изолированный исполнитель Studio.

Базовый адрес проверочного стенда:

[https://mmat-studio-api.staging.app.mmat.tech/api/v1](https://mmat-studio-api.staging.app.mmat.tech/api/v1)

Документация HTTP-запросов: [открыть](https://mmat-studio-api.staging.app.mmat.tech/api/v1/docs). Машинная схема: [OpenAPI](https://mmat-studio-api.staging.app.mmat.tech/api/v1/openapi.json). Этот адрес относится к отдельному API-стенду; браузерная авторизация основного Studio на нём не подключена.

Исходники SDK: [Python](https://mmat-studio-api.staging.app.mmat.tech/api/v1/sdk/python/mmat_studio.py), [пример Python](https://mmat-studio-api.staging.app.mmat.tech/api/v1/sdk/python/example.py), [JavaScript](https://mmat-studio-api.staging.app.mmat.tech/api/v1/sdk/javascript/mmat-studio.mjs).

## Что хранится в API

**Бакет** — переиспользуемая рабочая среда: профиль модели, навыки, разрешённые подключения, текстовые файлы и память. В запросах он называется `bucket`.

**Система** — граф, связывающий ноды. В запросах он называется `system`. Нода `agent` может использовать бакет; `input`, `condition`, `merge` и `output` передают и обрабатывают текст.

**Запуск** — выполнение сохранённого графа или одного MMAT с назначенным бакетом. Он хранит вход, состояния нод, события, выход и ссылки на полученные файлы.

В API v1 `/buckets` означает рабочие среды, `/systems` — графы. Старые браузерные маршруты Studio `/api/buckets` и `/api/environments` сохраняют прежнее назначение; новые интеграции используют `/api/v1`.

## Авторизация

Каждый защищённый запрос передаёт личный ключ API в заголовке `Authorization: Bearer <token>`. Ключ привязан к подтверждённому владельцу центрального аккаунта MMAT. Передать другого владельца в теле запроса нельзя. Чужие ресурсы недоступны и возвращают `404`.

| Право | Доступ |
| --- | --- |
| `read` | Читать бакеты, системы, каталог, навыки, состояния, события и результаты |
| `write` | Создавать и изменять бакеты, системы, ноды и навыки |
| `run` | Запускать и останавливать выполнение |

Права независимы. Для запуска с ожиданием результата нужны `run` и `read`. Для полного цикла создания, настройки и запуска нужны все три права.

Ключи выпускаются через управление аккаунтом Studio с действующей центральной сессией: `POST /api/clients` принимает `name`, `permissions` и `ttl_seconds`. У этой операции браузерная защита сессии и точного `Origin`. API-ключ не может выпускать новые ключи. `GET /api/clients` показывает только метаданные; `DELETE /api/clients/{id}` отзывает ключ. Обычный срок — 30 дней, допустимый — от 60 секунд до 90 дней. Одновременно доступно до 25 действующих ключей аккаунта.

Ответ выпуска содержит `credential` и единожды выдаваемый `token`. На сервере хранится хеш ключа. Сохраните значение в защищённом серверном хранилище; SDK принимает его из переменной окружения. Не размещайте личный ключ в коде веб-страницы, логах или открытой переписке. Выдача ключей на проверочном стенде выполняется через разрешённый серверный контур MMAT; эта версия не предоставляет отдельного SSO-входа для стенда.

Заголовки с ключом не заменяют права подключений Vault. Профиль содержит только безопасный `vault_ref` и выбранные действия; значения внешних секретов получает серверный исполнитель. Список доступных подключений возвращает `GET /catalog`; используйте только строки с `ready: true` и нужным `supported_actions`.

## Создание, версии и повторы запросов

POST создания ресурсов, запуска и `actions` требует `Idempotency-Key`: 1–200 латинских букв, цифр или символов `.`, `_`, `:`, `-`. Создайте и сохраните ключ в своём приложении **до** отправки запроса. Повтор одной операции отправляйте с тем же ключом, адресом и телом: сервер вернёт исходный сохранённый ответ и заголовок `Idempotency-Replayed: true`.

Один ключ относится к одной логической операции владельца во всём API. Если использовать его для другого тела или маршрута, сервер вернёт `409`. Незавершённое резервирование также даёт `409`; проверьте фактическое состояние до новой записи. SDK не повторяют запросы автоматически и не создают ключи при ошибках. `POST /runs/{id}/stop` безопасно повторяется без ключа.

Изменения бакетов, файлов, памяти, систем, графов, нод и навыков передают `expected_version`, полученный при чтении ресурса. Номер относится ко всему ресурсу: запись файла увеличивает версию бакета, запись графа — версию системы. Если версия устарела, сервер возвращает `409`. Прочитайте текущую версию и согласуйте изменения в своём приложении. Не заменяйте номер автоматически, иначе можно потерять чужое обновление.

Рабочий бакет блокируется на время запуска. В режиме `read_write` только успешное выполнение сохраняет допустимые изменения текстовых файлов и памяти. `read_only` позволяет использовать контекст без его сохранения. Итоговые файлы запуск сохраняет отдельно от содержимого бакета. Замороженные, архивные и удалённые бакеты не запускаются.

## Маршруты

Все пути таблиц добавляются к базовому `/api/v1`. JSON-объекты ресурсов возвращаются в оболочке: `{"bucket": ...}`, `{"system": ...}`, `{"run": ...}`, `{"skill": ...}`. Списки используют соответствующее множественное число.

| Метод и путь | Назначение |
| --- | --- |
| `GET /health` | Публичная проверка сервиса |
| `GET /docs`, `GET /openapi.json` | Публичные документация и схема |
| `GET /me` | Владелец и права текущего ключа |
| `GET /catalog` | Доступные навыки, модели, подключения и фактические ограничения |
| `GET /buckets`, `POST /buckets` | Список и создание рабочих бакетов |
| `GET /buckets/{id}?version=N` | Текущая или сохранённая версия бакета |
| `PATCH /buckets/{id}` | Изменение полей с `expected_version` |
| `DELETE /buckets/{id}` | Перевод в корзину |
| `POST /buckets/{id}/actions` | `freeze`, `archive`, `restore`, `duplicate`, `template`, `trash` |
| `GET /buckets/{id}/versions` | Сохранённые версии |
| `GET /buckets/{id}/files/{path}` | Текстовый файл: `name`, `content`, `version` |
| `PUT /buckets/{id}/files/{path}` | Создание или замена файла: `content`, `expected_version`; ответ `bucket` |
| `DELETE /buckets/{id}/files/{path}?expected_version=N` | Удаление файла с проверкой версии |
| `GET /buckets/{id}/memory` | Память: `content`, `version` |
| `PUT /buckets/{id}/memory` | Память: `content`, `expected_version`; ответ `bucket` |
| `POST /buckets/{id}/runs` | Запуск одного MMAT с профилем бакета |
| `GET /buckets/{id}/runs` | Последние запуски, использующие бакет |
| `GET /systems`, `POST /systems` | Список и создание графов |
| `GET /systems/{id}`, `PATCH /systems/{id}`, `DELETE /systems/{id}` | Чтение, изменение и корзина системы |
| `POST /systems/{id}/actions` | `activate`, `freeze`, `archive`, `restore`, `duplicate`, `template`, `trash` |
| `GET /systems/{id}/graph`, `PUT /systems/{id}/graph` | Граф в формате интерфейса Studio; запись с `expected_version` |
| `GET /systems/{id}/nodes`, `PUT /systems/{id}/nodes` | Ноды и связи без координат интерфейса; запись с `expected_version` |
| `POST /systems/{id}/runs`, `GET /systems/{id}/runs` | Запуск системы и история |
| `GET /runs/{id}` | Текущее состояние, выход, ноды и файлы |
| `POST /runs/{id}/stop` | Запрос остановки |
| `GET /runs/{id}/nodes/{node_id}` | Вход, действия, выход, ошибки и файлы одной ноды |
| `GET /runs/{id}/events?after_seq=N&limit=100` | Последовательные события для опроса |
| `GET /runs/{id}/artifacts/{index}` | Файл итогового результата |
| `GET /runs/{id}/nodes/{node_id}/artifacts/{index}` | Файл конкретной ноды |
| `GET /skills`, `POST /skills` | Собственные навыки владельца |
| `GET /skills/{id}?version=N`, `PATCH /skills/{id}`, `DELETE /skills/{id}` | Чтение версии, изменение и архивирование навыка |
| `GET /skills/{id}/versions`, `GET /skills/{id}/export?version=N` | История и экспорт `SKILL.md` |
| `POST /skills/{id}/actions` | `archive`, `restore`, `duplicate` |

### Бакет и прямой запуск

Создание бакета принимает:

```json
{
  "name": "Рабочий контекст",
  "description": "Факты и правила проекта",
  "profile": {
    "model": "gpt-5.4",
    "skills": [],
    "skill_settings": {},
    "connections": [],
    "timeout_seconds": 180
  },
  "files": [{"name": "facts/source.txt", "content": "Проверенные исходные данные"}],
  "memory": "Задача проекта и накопленные факты"
}
```

Приведённый пустой `connections` описывает хранение контекста. Для выполнения ноды `agent` нужно назначить разрешённое подключение модели из каталога. Его форма в профиле:

```json
{"vault_ref": "<vault_ref из каталога вашего аккаунта>", "actions": ["comet_text_generation"]}
```

Прямой запуск принимает `prompt`, необязательный `input_text`, `mode` и необязательный `expected_version` бакета. Он не требует заранее создавать систему:

```json
{
  "prompt": "Прочитай исходные данные бакета. Составь краткую сводку только по проверенным фактам.",
  "input_text": "Отдельно укажи, каких данных нет.",
  "mode": "read_only",
  "expected_version": 1
}
```

### Система и ноды

Создание системы принимает `name`, `description`, цвет `color` и шаблон `template`: `empty` или `basic`. `/nodes` принимает весь граф в простой форме:

```json
{
  "expected_version": 1,
  "nodes": [
    {"id": "input", "kind": "input", "label": "Вход"},
    {
      "id": "agent",
      "kind": "agent",
      "label": "MMAT",
      "prompt": "Прочитай бакет и обработай входные данные. Верни сводку проверенных фактов.",
      "bucket_id": "<id созданного бакета>",
      "mode": "read_only",
      "overrides": []
    },
    {"id": "output", "kind": "output", "label": "Результат"}
  ],
  "edges": [
    {"source": "input", "target": "agent"},
    {"source": "agent", "target": "output"}
  ]
}
```

`bucket_id` задаётся только для `agent`. Такая нода наследует профиль бакета. Чтобы задать собственное поле, перечислите его в `overrides`: `model`, `skills`, `skill_settings`, `connections`, `timeout_seconds`. Доступны также `skill_versions` и `config`; точная схема находится в OpenAPI. Для условной ноды `config` содержит `operator` (`contains`, `equals`, `not_empty`, `nonempty`) и строку `value`. Граф должен быть ациклическим.

Запуск системы принимает `input_text` и необязательный `node_id` для запуска выбранной ноды. Назначенные навыки и разрешения проверяются сервером; текст промпта не расширяет доступ.

### Навыки

Собственный навык содержит обязательные `name`, `description` и `instructions`. Дополнительно можно указать локальную `input_schema` в JSON Schema, `output_format`, `example_input`, `required_actions`. Это инструкции для MMAT; произвольные внешние инструменты не подключаются. `required_actions` допускает только `comet_text_generation`, `required_tools` должен быть пустым.

Изменение создаёт новую сохраняемую версию. Схема системы фиксирует используемые версии; экспорт возвращает текст Markdown, а не JSON.

## Получение результата и остановка

Создание запуска возвращает `202` и `run` со статусом `queued`. Ответ означает принятие запуска. Последующее выполнение может завершиться ошибкой; проверяйте `GET /runs/{id}`.

`completed`, `failed`, `cancelled` и `interrupted` — конечные состояния. Готовый текстовый результат имеет одновременно `status: "completed"`, `ready: true` и непустой `output`. Результаты нод находятся в `nodes`; исходный итог сохраняется также в `result.output`. Ссылки `url` в артефактах ведут на защищённые v1-маршруты; скачивание тоже требует Bearer-ключ с `read`.

Для наблюдения используйте `events`: сохраняйте `next_seq` и отправляйте его как `after_seq` при следующем опросе. Ответ содержит `events` с `seq`, `created_at`, `data`, а также `status` и `terminal`. Допустимый `limit` — от 1 до 400. Это опрос HTTP; webhook и SSE в этой версии не предоставляются.

`stop` устанавливает запрос остановки. Дождитесь конечного состояния, чтобы подтвердить завершение работы среды. Таймаут метода SDK `wait_run`/`waitRun` только прекращает ожидание клиента; он не останавливает запуск на сервере.

## SDK Python

Файл `sdk/python/mmat_studio.py` работает на стандартной библиотеке Python 3.10+. Добавьте его в проект или в `PYTHONPATH`. SDK не требует сторонних пакетов.

```python
import os
from mmat_studio import MMATStudio

client = MMATStudio(
    os.environ["MMAT_STUDIO_BASE_URL"],
    os.environ["MMAT_STUDIO_TOKEN"],
)

# Ключ уже сохранён приложением как идентификатор одной операции.
bucket = client.create_bucket(
    "Контекст проекта",
    memory="Проверенные факты проекта.",
    idempotency_key=os.environ["MMAT_STUDIO_CREATE_KEY"],
)

# Содержимое памяти и файлов возвращается полным JSON-ответом.
memory = client.get_memory(bucket["id"])
client.put_memory(
    bucket["id"], "Проверенные факты. Новое уточнение.",
    expected_version=memory["version"],
)
```

Пример запуска уже настроенного бакета:

```python
run = client.run_bucket(
    os.environ["MMAT_STUDIO_BUCKET_ID"],
    "Прочитай бакет и верни сводку проверенных фактов.",
    mode="read_only",
    idempotency_key=os.environ["MMAT_STUDIO_RUN_KEY"],
)
finished = client.wait_run(run["id"], timeout=300, poll_interval=1)
if finished["status"] == "completed":
    print(finished["output"])
else:
    print("Запуск завершён со статусом:", finished["status"])
```

SDK возвращает объект ресурса без внешней оболочки для CRUD и запусков. Методы файлов, памяти, графа, нод и событий возвращают полный ответ с версиями. `stop_run(id, wait=True)` запрашивает остановку и ждёт конечного состояния. `download_artifact(url)` возвращает байты, проверяя адрес API и путь; ключ не передаётся другому серверу или по редиректу.

Вспомогательная `new_idempotency_key()` создаёт UUID. Сохранение ключа остаётся обязанностью вызывающего приложения. Исключения `APIError` содержат `status` и допустимый машинный `code`; исходный текст ошибок сервера и заголовки не добавляются в сообщение. `APIProtocolError` означает некорректный ответ или `completed` без готового текстового выхода. Конечные состояния ошибки и отмены возвращаются как объект запуска — проверьте его статус.

## SDK JavaScript

`sdk/javascript/mmat-studio.mjs` использует стандартный `fetch` в Node.js 18+ и современных браузерах. Личный ключ храните в серверной части приложения.

```javascript
import { MMATStudio } from './mmat-studio.mjs';

const client = new MMATStudio(
  process.env.MMAT_STUDIO_BASE_URL,
  process.env.MMAT_STUDIO_TOKEN,
);
const run = await client.runBucket(
  process.env.MMAT_STUDIO_BUCKET_ID,
  'Прочитай бакет и верни сводку проверенных фактов.',
  { mode: 'read_only', idempotencyKey: process.env.MMAT_STUDIO_RUN_KEY },
);
const finished = await client.waitRun(run.id, { timeoutMs: 300000 });
if (finished.status === 'completed') console.log(finished.output);
else console.log('Запуск завершён со статусом:', finished.status);
```

Названия методов соответствуют Python в camelCase: `createBucket`, `updateBucket`, `putFile`, `putMemory`, `createSystem`, `putNodes`, `runSystem`, `getRun`, `runEvents`, `stopRun`. Версия передаётся через опцию `expectedVersion`, ключ повторов — `idempotencyKey`. Например, `putNodes(id, nodes, edges, {expectedVersion: 1})`. Ошибки и правило готового результата одинаковы для обоих SDK.

SDK ограничивают JSON-ответы 8 МиБ и скачивание одного артефакта 128 МиБ; можно задать меньший предел `max_bytes`/`maxBytes`. Адрес должен использовать HTTPS; HTTP разрешён только для локальной разработки. Редиректы отключены.

## Текущие ограничения

| Возможность | Ограничение |
| --- | --- |
| Текстовые файлы бакета | До 31 пользовательского UTF-8 файла и отдельная память `MEMORY.md` |
| Размер файла и памяти | До 128 КиБ каждый; суммарно до 512 КиБ |
| Имя файла | Относительный путь до 160 символов и 5 частей; без скрытых папок, `..`, пустых частей и `MEMORY.md` |
| Граф | До 40 нод и 160 связей; ациклический |
| Параллельные запуски | До 2 на аккаунт и 8 всего; один рабочий бакет блокируется запуском |
| Модели | `gpt-5.4` и `gpt-4.1-mini` через разрешённое подключение Comet |
| Нода MMAT | До 12 навыков, одно подключение модели; таймаут 15–900 секунд |
| Запуск | Ручной запрос API; расписания не подключены |
| Внешние инструменты | Не подключены; доступна операция `comet_text_generation` |
| Содержимое бакета | Текстовые файлы и память; двоичные результаты доступны как артефакты запуска |

Проверяйте `catalog.capabilities` и `catalog.limits` перед включением функции в своё приложение.

## Коды ошибок

| Код | Действие клиента |
| --- | --- |
| `401` | Проверить срок и отзыв ключа |
| `403` | Проверить право API и разрешённое действие подключения |
| `404` | Проверить ресурс в пределах своего аккаунта |
| `409` | Прочитать фактическое состояние: версия, блокировка или сохранённый запрос |
| `413` | Уменьшить размер запроса |
| `422` | Проверить поля, профиль, граф и ограничения |
| `429` | Дождаться завершения запусков или снятия ограничения |
| `5xx` или сбой соединения | Проверить состояние; при повторе создания использовать исходный ключ и тело |

Сервер не отражает исходные значения полей запроса в ошибках валидации. Для журналов приложения используйте статус, свой идентификатор операции и безопасные метаданные; исключите Bearer-ключ и внешние секреты.
