Messages
Для разработчиков

Messages

Сообщения принадлежат тикету. sender_type — кто написал: contact (клиент), operator, bot (системные сообщения и сообщения, отправленные через API), ai. is_internal: true — внутренняя заметка, клиент её не видит.

  • sender_id — id контакта для сообщений клиента, id пользователя для сообщений оператора. У bot-сообщений, отправленных через API, это id проекта (так они отличаются от системных, у которых null).
  • edited_at — когда сообщение правили, иначе null.
  • media — вложения. url в них относительный (/api/v1/media/…), скачивание — на странице Медиа.
GET/api/v1/tickets/{ticket_id}/messages

Сообщения тикета

По порядку, старые сверху. Внутренние заметки входят в список. Для несуществующего тикета — пустой список.

Query-параметры:

  • since — ISO 8601, сообщения, созданные в этот момент или позже. Время со смещением пояса переводится в UTC (13:30+02:00 — это 11:30 UTC), время без смещения считается UTC
  • page (по умолчанию 1), page_size (по умолчанию 50, максимум 200)
200 OK
{
  "items": [
    {
      "id": "f1...",
      "ticket_id": "8a3f...",
      "workspace_id": "1c2b...",
      "sender_type": "contact",
      "sender_id": "44e1...",
      "content": "Здравствуйте!",
      "is_internal": false,
      "media": [
        {
          "id": "m1...",
          "url": "/api/v1/media/m1...",
          "mime_type": "image/png",
          "original_name": "screenshot.png",
          "file_type": "png",
          "width": 1280,
          "height": 720,
          "size": 184523
        }
      ],
      "created_at": "2026-04-06T10:12:33.123456",
      "edited_at": null
    }
  ],
  "total": 4,
  "page": 1,
  "page_size": 50
}
POST/api/v1/tickets/{ticket_id}/messages

Написать в тикет от имени бота

Сообщение сохраняется с sender_type: bot и id проекта в sender_id. Поля тела:

  • content — текст, до 10 000 символов. Может быть пустым, если есть media_ids: тогда сообщение сохраняется с текстом [Attachment]. Пустой текст без файлов — 422 MESSAGE_EMPTY
  • is_internal — true для внутренней заметки, по умолчанию false
  • media_ids — id файлов этого проекта, загруженных через POST /api/v1/media/upload. Id не в формате UUID — 422 VALIDATION_ERROR; несуществующий файл или файл другого проекта — тоже 422 VALIDATION_ERROR, такие id перечислены в detail.media_ids. В обоих случаях ничего не сохраняется. Файл, который уже прикреплён к другому сообщению, копируется: в ответе у него новый id, исходное сообщение его не теряет
body
{
  "content": "Спасибо! Прикладываю инструкцию.",
  "is_internal": false,
  "media_ids": ["m1...", "m2..."]
}

Ответ — 201 и сообщение с прикреплёнными файлами.

Сообщение появляется в тикете в дашборде и в виджете на сайте, но не отправляется в Telegram, VK, WhatsApp и на email: клиенты из этих каналов его не получат. Статус тикета не меняется. Файлы прикрепляются вместе с сообщением, поэтому событие message.created уже содержит их в media.
PATCH/api/v1/messages/{message_id}

Изменить текст сообщения

Меняет content сообщения клиента, оператора или сообщения, которое отправлено через этот API, и ставит edited_at. Если сообщение оператора было отправлено в Telegram, текст меняется и там. Ограничения:

  • Из bot-сообщений редактируются только отправленные через API проекта (с id проекта в sender_id); операторы в дашборде их не правят. Системные bot-сообщения и ответы ai — 422 VALIDATION_ERROR с причиной System messages cannot be edited
  • Окно редактирования проекта: message_edit_window_minutes минут с момента отправки (по умолчанию 15, 0 — без ограничения)
  • В тикетах closed и resolved — только если в проекте включено message_edit_after_close
  • Пустой текст — 422 MESSAGE_EMPTY
body
{
  "content": "Обновлённый текст сообщения"
}

В ответ приходит сообщение целиком. Шлёт webhook message.edited. Текущие значения окна и политики — в GET /api/v1/workspace/settings:

GET /api/v1/workspace/settings
{
  "id": "1c2b...",
  "name": "Мой проект",
  "slug": "my-project",
  "timezone": "Europe/Warsaw",
  "message_edit_window_minutes": 15,
  "message_edit_after_close": false,
  "language": "ru"
}
POST/api/v1/messages

Входящее сообщение от внешнего контакта

Для случая, когда внешняя система (CRM, бот, форма на сайте) передаёт сообщение от клиента. Контакт ищется по telegram_id, затем по email и создаётся, если не найден. Сообщение добавляется в последний тикет контакта, который не закрыт и не решён; если такого нет, создаётся новый (тема — первые 120 символов текста, тикет учитывается в месячном лимите тарифа).

  • contact — обязательно: telegram_id (число) и/или email, плюс name. Без обоих идентификаторов — 422 VALIDATION_ERROR
  • message — текст, 1–10 000 символов, обязательно
  • session_id — принимается, но не используется

Вложений у этого эндпоинта нет.

body
{
  "contact": {
    "email": "ivan@example.com",
    "name": "Иван Петров"
  },
  "message": "Здравствуйте, не приходит код"
}
curl
curl -X POST https://api.support.forestsnet.com/api/v1/messages \
  -H "Authorization: Bearer sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{"contact": {"email": "ivan@example.com"}, "message": "Здравствуйте"}'
200 OK
{
  "ok": true,
  "message": { "id": "f2...", "ticket_id": "8a3f...", "sender_type": "contact", ... }
}

Если API-канал выключен на уровне платформы, приходит 200 с {"ok": false, "message": "API channel is disabled"}. Если контакт заблокирован, сообщение не сохраняется и приходит 200 с {"ok": false, "message": null, "reason": "contact_blocked"}.

IP-адрес запроса сохраняется в contact.extra_data.last_ip (время — в last_ip_at). Когда запрос шлёт ваш сервер, это адрес вашего сервера, а не клиента.
Была ли страница полезной?