ТЭ Тэрил

Документация API

Справочник ручек

Все ответы — JSON. Удачный ответ несёт "ok": true, отказ — "ok": false и объект error (см. errors.md).

Авторизация на всех ручках, кроме GET /v1/health и GET /r/{token}:

Authorization: Bearer pk_…

GET /v1/health

Без ключа. Ставьте в мониторинг.

{ "ok": true, "version": 1, "model": "deepseek/deepseek-v4-pro-0813" }

model — какой моделью сервис сейчас работает. Меняется без предупреждения: это наш выбор по цене за сошедшуюся просьбу, а не часть договора.


GET /v1/usage

Остаток монет учётной записи, на которую выдан ключ.

{ "ok": true, "usage": {
  "plan": "seat", "status": "active",
  "coins_left": 940, "coins_used": 60, "coins_included": 1000,
  "extra_coins": 0,
  "period_end": "2026-10-01T00:00:00.000Z",
  "cabinet": "https://teril.example.ru/cabinet" } }

statustrial, active, past_due или canceled. Монеты добирает мебельная компания в своём кабинете; вы можете только увидеть остаток.


POST /v1/conversations

Завести разговор — или получить уже заведённый.

{
  "external_id": "lead-7781",
  "order_id": "кухня-иванов",
  "webhook_url": "https://ваш-сервис/hooks/teril",
  "meta": { "источник": "сайт", "менеджер": 12 }
}
ПолеОбяз.Что это
external_idнетВаш id лида. По нему разговор находится повторно
order_idнетИмя заказа. Не задано — lead-<external_id>
webhook_urlнетКуда слать исход. Не задан — общий адрес партнёра
metaнетЛюбой ваш JSON. Мы его храним и не читаем

201 — завели новый. 200 — вернули тот же по external_id.

{ "ok": true, "conversation": {
  "id": "3f2b…", "external_id": "lead-7781", "order_id": "кухня-иванов",
  "turn": 0, "running_turn": null, "has_project": false,
  "expires_at": "2026-10-18T09:11:00.000Z" } }

running_turn — id идущего хода или null. has_project — собрано ли уже что-нибудь.


GET /v1/conversations/{id}

Тот же объект conversation. Разговор чужого партнёра не находится вовсе (404).


DELETE /v1/conversations/{id}

204, без тела. Удаляются и переписка, и картинки, и история ходов. Необратимо. Делайте, когда лид закрыт: сам разговор и так умрёт через 30 дней после последней просьбы.


POST /v1/conversations/{id}/turns

Просьба клиента. Отвечает 202.

{
  "text": "Кухня 3 метра вдоль окна, мойка под окном",
  "images": [{ "mime": "image/png", "b64": "iVBORw0KGgo…", "name": "план.png" }]
}
ПолеОбяз.Что это
textдаСлова клиента как есть, по-русски
imagesнетДо двух картинок, каждая ≤ 5 МБ. mime: image/png, image/jpeg, image/webp, image/gif. name можно слать — он принимается и никуда не идёт

Заголовок Idempotency-Key (рекомендуется): повтор с тем же ключом вернёт тот же ход и не купит второго вопроса модели. Без него повтор — это второй ход и вторые деньги.

{ "ok": true, "turn": { "id": "b41c…", "state": "queued" } }

409 conflict_busy — по этому разговору уже идёт ход. Дождитесь исхода или остановите его.


GET /v1/turns/{id}

Состояние и исход хода. Запасной путь к тому, что приходит вебхуком.

{ "ok": true, "turn": {
  "id": "b41c…",
  "state": "done",
  "status": "built",
  "result": { "…": "см. turns.md" } } }

Поля status, result и error появляются по мере того, как им есть что сказать: у идущего хода их нет вовсе, у неудачного вместо result приходит error. Не полагайтесь на то, что поле present-but-null — его просто нет.

state: queuedrunningdone | stopped | failed.

Опрашивать имеет смысл раз в 5–10 секунд и не дольше десяти минут.


POST /v1/turns/{id}/stop

Оборвать идущий ход: клиент передумал, дописал, ушёл.

{ "ok": true, "turn_id": "b41c…", "state": "running" }

409 — ход уже кончился либо идёт на другом узле сервиса и остановить его нечем. Второй случай честно говорит об этом текстом: мы не притворяемся, что остановили.

Остановленный ход не списывает монет.


GET /v1/conversations/{id}/project

Документ заказа — то, из чего собирается мебель. Его читает проектировщик и наши же инструменты; вам он нужен, если вы храните состояние у себя или показываете состав словами.

{ "ok": true, "project": {
  "id": "кухня-иванов", "kind": "kitchen", "ceiling": 2700,
  "room": { "walls": [ { "id": "задняя", "turn": "right", "length": 3000 } ] },
  "runs": [ { "id": "задняя-низ-1", "wall": "задняя", "tier": "base",
              "countertop": true, "plinth": true,
              "nodes": [ { "id": "мойка", "at": "module", "code": "sink-straight", "w": 800 } ] } ],
  "items": [], "obstacles": [] } }

404, пока в разговоре ничего не собрано.

Документ большой и подробный; разбирать его целиком не нужно. Практически полезны runs[].nodes[] — состав по порядку, у каждого узла id, code (модуль каталога) и w (ширина в миллиметрах).


GET /v1/conversations/{id}/bom

Расход материалов. Подробно — bom.md.

{ "ok": true, "order_id": "кухня-иванов", "turn": 3, "bom": {
  "sheets": [ { "material": "ldsp16", "name": "ЛДСП 16 мм", "parts": 78, "area_mm2": 15253450 } ],
  "linear": [],
  "edges":  [ { "edge": "pvc1carcass", "name": "Кромка корпуса", "length_mm": 70164 } ],
  "pieces": [ { "article": "LEG-ADJ-100", "name": "Опора регулируемая 100", "qty": 22 } ] } }

GET /v1/conversations/{id}/render

Объёмный вид заказа.

Вид принадлежит РАЗГОВОРУ, а не ходу. Он доступен после ЛЮБОГО хода, пока в разговоре что-то собрано, и просить его можно когда угодно — хоть перед первым ходом дня. Статус последнего хода на это не влияет вовсе: question и rejected ничего не разобрали, и картинка после них ровно та же, что была. Не собрано ещё ничего — 404 not_found.

ПараметрЗначенияПо умолчанию
formatsvg, htmlsvg
viewfront, isofront
carcassвыкрас из палитрыclear
facadeвыкрас из палитрыclear
link1нет

Без link — тело: image/svg+xml или text/html.

С link=1 — подписанная ссылка вместо тела:

{ "ok": true, "url": "https://teril.example.ru/r/eyJ…", "expires_at": "2026-09-19T…" }

carcass и facade красят вид СПЛОШНЫМИ ПАНЕЛЯМИ — порознь корпус и фасад, потому что порознь их и называют («белый верх, дуб низ»). Выкрасы:

clear, white, milk, sand, oak, walnut, wenge, grey, graphite, black, olive, blue.

clear — не цвет, а его отсутствие: вид остаётся чертежом, залитым бумагой и обведённым краской, и это же по умолчанию. Неизвестное имя — 400 invalid_request со списком: опечатка в цвете иначе уехала бы клиенту картинкой не того тона, и узнали бы вы об этом от него.

Цвет здесь ВРЁТ НАРОЧНО и ничего не решает: декор выбирают по выкрасу у поставщика, а не по экрану телефона, — поэтому имена родовые, а не артикулы. Геометрия при этом та же самая, из которой выйдут детали.

На странице (format=html) палитра идёт строкой кнопок под видом: клиент перебирает цвета сам, не ходя к вам за новой ссылкой. Выбранное в запросе — то, что он увидит первым; там, где скриптов нет вовсе (предпросмотр в мессенджере), останется оно же. Подписанная ссылка несёт выкрасы в себе: параметром их клиенту не дать — подпись покрывает только тело.

view=iso показывает и маркеры техники (место под холодильник, трубу, котёл); front — только мебель. Клиенту обычно нужен front.

format=png отвечает 501 png_unsupported: ради картинки, которая получается из SVG, пришлось бы держать браузер в рантайме. Нужен растр — сконвертируйте SVG у себя.


GET /r/{token}

Страница вида по подписанной ссылке. Без ключа — её открывает ваш клиент.

Страница самодостаточна: чертёж внутри неё, наружу она не ходит ни за чем. Живёт сутки; просроченная и подделанная отвечают одинаково — 404.

Содержимое адресуется парой «разговор + номер хода», поэтому ссылка неизменна: после следующего хода запросите новую.


POST /v1/conversations/{id}/handoff

Передать заказ на рабочее место мебельной компании.

{ "seat": "Цех на Ленина", "order_id": "кухня-иванов-2" }

seat — id места или его название из кабинета компании. order_id — имя, под которым заказ ляжет на место; не задано — имя заказа разговора.

{ "ok": true, "handoff": {
  "seat_id": 5, "seat_title": "Цех на Ленина",
  "order_id": "кухня-иванов-2", "turn": 3 } }

403 — такого места у этой учётной записи нет либо оно отозвано. 400 — в разговоре ещё ничего не собрано.

Что эта ручка делает и чего не делает — handoff.md.

Эта страница разметкой: /docs/api/reference.md · вся документация одним файлом: /llms-full.txt