Файл из 1С в Max: загрузка и отправка
Март 2026 · Редакция «Арете-ИТ»
Сообщение с файлом отправляется не одним запросом. Сначала Max выдаёт адрес для загрузки,
затем принимает двоичные данные и только после этого — сообщение с token вложения.
Для печатной формы из 1С к цепочке добавляется ещё один шаг: её нужно заранее сохранить в PDF.
От печатной формы к двоичным данным
Источник файла может быть любым: печатная форма документа, результат отчёта, присоединённый файл или содержимое хранилища значения. Для Max всё это в итоге имя файла и двоичные данные.
УПД, счета, акты и расчётные листки обычно передают в PDF — получатель откроет такой файл с телефона, а вёрстка не будет зависеть от установленного офисного пакета. Прайс-лист или детализацию отчёта иногда разумнее оставить в XLSX. Получение самой печатной формы зависит от конфигурации, поэтому универсального вызова для УТ, ERP, БП и ЗУП не существует.
// ПечатнаяФорма — уже сформированный ТабличныйДокумент. // Команда и параметры получения формы зависят от конфигурации и вида документа. Поток = Новый ПотокВПамяти; ПечатнаяФорма.Записать(Поток, ТипФайлаТабличногоДокумента.PDF); ДвоичныеДанные = Поток.ЗакрытьИПолучитьДвоичныеДанные(); ИмяФайла = "document.pdf";
Разборы конкретных документов: УПД, счёт, счёт-фактура, заказ, акт сверки, PDF, письмо, квитанция, расчётные листки.
POST /uploads — ещё не отправка
По документации
POST /uploads
с параметром type=file возвращает URL, по которому нужно загрузить один файл.
Предельный размер обычного файла — 4 ГБ. Для изображений, видео и аудио предусмотрены
отдельные типы и свои ограничения.
Четыре гигабайта — предел API, а не рекомендация для примера ниже: multipart-тело целиком собирается в памяти и при обрыве загружается заново. Для крупных вложений документация Max предлагает возобновляемую загрузку; обычным печатным формам PDF достаточно более простого multipart-варианта.
Адрес загрузки может вести на fu.oneme.ru, а не на основной домен API.
Это ожидаемо: используйте полученный URL целиком, вместе с путём и query-параметрами.
К сообщению можно приложить один объект file; совместно с ним допускается
только inline-клавиатура. Изображение или видео в то же сообщение добавлять нельзя.
Для нескольких документов создайте несколько сообщений с учётом лимита диалога.
Запрос = Новый HTTPЗапрос("/uploads?type=file");
Запрос.Заголовки.Вставить("Authorization", ТокенБота);
Ответ = Соединение.ОтправитьДляОбработки(Запрос);
Если Ответ.КодСостояния <> 200 Тогда
ВызватьИсключение Ответ.ПолучитьТелоКакСтроку();
КонецЕсли;
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(Ответ.ПолучитьТелоКакСтроку());
ОписаниеЗагрузки = ПрочитатьJSON(ЧтениеJSON);
Если НЕ ОписаниеЗагрузки.Свойство("url") Тогда
ВызватьИсключение "Max не вернул URL загрузки.";
КонецЕсли;
URLЗагрузки = ОписаниеЗагрузки.url; // Для type=file обычно домен fu.oneme.ru.
Разделитель = Строка(Новый УникальныйИдентификатор);
ПереводСтроки = Символы.ВК + Символы.ПС;
БезопасноеИмяФайла = СтрЗаменить(ИмяФайла, """", "_");
БезопасноеИмяФайла = СтрЗаменить(БезопасноеИмяФайла, Символы.ВК, "");
БезопасноеИмяФайла = СтрЗаменить(БезопасноеИмяФайла, Символы.ПС, "");
Поток = Новый ПотокВПамяти;
ЗаписьДанных = Новый ЗаписьДанных(Поток, КодировкаТекста.UTF8);
ЗаписьДанных.ЗаписатьСимволы("--" + Разделитель + ПереводСтроки);
ЗаписьДанных.ЗаписатьСимволы(
"Content-Disposition: form-data; name=""data""; filename=""" +
БезопасноеИмяФайла + """" + ПереводСтроки);
ЗаписьДанных.ЗаписатьСимволы(
"Content-Type: application/octet-stream" + ПереводСтроки + ПереводСтроки);
ЗаписьДанных.Записать(ДвоичныеДанные);
ЗаписьДанных.ЗаписатьСимволы(ПереводСтроки + "--" + Разделитель + "--" + ПереводСтроки);
ЗаписьДанных.Закрыть();
ТелоЗагрузки = Поток.ЗакрытьИПолучитьДвоичныеДанные();
СерверЗагрузки = СтрЗаменить(URLЗагрузки, "https://", "");
СерверЗагрузки = Лев(СерверЗагрузки, СтрНайти(СерверЗагрузки, "/") - 1);
АдресРесурса = СтрЗаменить(URLЗагрузки, "https://", "");
АдресРесурса = Сред(АдресРесурса, СтрНайти(АдресРесурса, "/"));
СоединениеЗагрузки = Новый HTTPСоединение(
СерверЗагрузки, 443, , , , 60, Новый ЗащищенноеСоединениеOpenSSL);
ЗапросЗагрузки = Новый HTTPЗапрос(АдресРесурса);
ЗапросЗагрузки.Заголовки.Вставить("Authorization", ТокенБота);
ЗапросЗагрузки.Заголовки.Вставить(
"Content-Length", Формат(ТелоЗагрузки.Размер(), "ЧГ="));
ЗапросЗагрузки.Заголовки.Вставить(
"Content-Type", "multipart/form-data; boundary=" + Разделитель);
ЗапросЗагрузки.УстановитьТелоИзДвоичныхДанных(ТелоЗагрузки);
ОтветЗагрузки = СоединениеЗагрузки.ОтправитьДляОбработки(ЗапросЗагрузки);
Если ОтветЗагрузки.КодСостояния <> 200 Тогда
ВызватьИсключение "Не удалось загрузить файл: " + ОтветЗагрузки.ПолучитьТелоКакСтроку();
КонецЕсли;
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(ОтветЗагрузки.ПолучитьТелоКакСтроку());
РезультатЗагрузки = ПрочитатьJSON(ЧтениеJSON);
ТокенВложения = РезультатЗагрузки.token;
Нагрузка = Новый Структура("token", ТокенВложения);
Вложение = Новый Структура("type, payload", "file", Нагрузка);
Вложения = Новый Массив;
Вложения.Добавить(Вложение);
Тело = Новый Структура;
Тело.Вставить("text", "УПД по реализации № 145");
Тело.Вставить("attachments", Вложения);
ПараметрыJSON = Новый ПараметрыЗаписиJSON(ПереносСтрокJSON.Нет, "");
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку(ПараметрыJSON);
ЗаписатьJSON(ЗаписьJSON, Тело);
Запрос = Новый HTTPЗапрос("/messages?user_id=" + Формат(ИдПользователя, "ЧГ="));
Запрос.Заголовки.Вставить("Authorization", ТокенБота);
Запрос.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
Запрос.УстановитьТелоИзСтроки(
ЗаписьJSON.Закрыть(), КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
Ответ = Соединение.ОтправитьДляОбработки(Запрос);
СтрокаОтвета = Ответ.ПолучитьТелоКакСтроку();
Если СтрНайти(СтрокаОтвета, "attachment.not.ready") > 0 Тогда
// Не ждите в пустом цикле: поставьте эту отправку в очередь на повтор
// через 5 секунд, затем увеличивайте интервал между попытками.
ВызватьИсключение "Max ещё обрабатывает вложение. Нужна отложенная повторная отправка.";
КонецЕсли;
Если Ответ.КодСостояния <> 200 Тогда
ВызватьИсключение СтрокаОтвета;
КонецЕсли;
Готовая процедура на встроенном языке
Ниже те же три шага собраны в одну процедуру. В ней нет внешних библиотек
и вызовов «Арете»: только стандартные объекты платформы 1С —
HTTPСоединение, HTTPЗапрос, JSON и потоки.
// Все используемые типы и методы входят в платформу 1С:Предприятие.
Процедура ОтправитьФайлВMax(
ТокенБота,
ИдПользователя,
ДвоичныеДанные,
ИмяФайла,
ТекстСообщения,
ВидВложения = "file") Экспорт
Если СтрНайти(",file,image,video,audio,", "," + ВидВложения + ",") = 0 Тогда
ВызватьИсключение "Неподдерживаемый тип вложения: " + ВидВложения;
КонецЕсли;
БезопасноеИмяФайла = СтрЗаменить(ИмяФайла, """", "_");
БезопасноеИмяФайла = СтрЗаменить(БезопасноеИмяФайла, Символы.ВК, "");
БезопасноеИмяФайла = СтрЗаменить(БезопасноеИмяФайла, Символы.ПС, "");
СоединениеAPI = НовоеHTTPSСоединение("platform-api2.max.ru", 30);
// 1. Получаем URL, предназначенный для загрузки одного файла.
ЗапросURL = Новый HTTPЗапрос("/uploads?type=" + ВидВложения);
ЗапросURL.Заголовки.Вставить("Authorization", ТокенБота);
ОтветURL = СоединениеAPI.ОтправитьДляОбработки(ЗапросURL);
ПроверитьОтветMax(ОтветURL, "Не удалось получить URL загрузки");
ОписаниеЗагрузки = ПрочитатьОтветJSON(ОтветURL);
URLЗагрузки = ОписаниеЗагрузки.url;
Если НЕ СтрНачинаетсяС(URLЗагрузки, "https://") Тогда
ВызватьИсключение "Max вернул URL загрузки без HTTPS.";
КонецЕсли;
ТокенВложения = Неопределено;
Если ОписаниеЗагрузки.Свойство("token") Тогда
ТокенВложения = ОписаниеЗагрузки.token; // video и audio
КонецЕсли;
// 2. Отправляем файл как multipart/form-data.
Разделитель = Строка(Новый УникальныйИдентификатор);
ПереводСтроки = Символы.ВК + Символы.ПС;
Поток = Новый ПотокВПамяти;
ЗаписьДанных = Новый ЗаписьДанных(Поток, КодировкаТекста.UTF8);
ЗаписьДанных.ЗаписатьСимволы("--" + Разделитель + ПереводСтроки);
ЗаписьДанных.ЗаписатьСимволы(
"Content-Disposition: form-data; name=""data""; filename=""" +
БезопасноеИмяФайла + """" + ПереводСтроки);
ЗаписьДанных.ЗаписатьСимволы(
"Content-Type: application/octet-stream" +
ПереводСтроки + ПереводСтроки);
ЗаписьДанных.Записать(ДвоичныеДанные);
ЗаписьДанных.ЗаписатьСимволы(
ПереводСтроки + "--" + Разделитель + "--" + ПереводСтроки);
ЗаписьДанных.Закрыть();
ТелоЗагрузки = Поток.ЗакрытьИПолучитьДвоичныеДанные();
БезСхемы = СтрЗаменить(URLЗагрузки, "https://", "");
ПозицияСлеша = СтрНайти(БезСхемы, "/");
Если ПозицияСлеша = 0 Тогда
ВызватьИсключение "Max вернул некорректный URL загрузки.";
КонецЕсли;
СерверЗагрузки = Лев(БезСхемы, ПозицияСлеша - 1);
АдресРесурса = Сред(БезСхемы, ПозицияСлеша);
СоединениеЗагрузки = НовоеHTTPSСоединение(СерверЗагрузки, 120);
ЗапросЗагрузки = Новый HTTPЗапрос(АдресРесурса);
ЗапросЗагрузки.Заголовки.Вставить("Authorization", ТокенБота);
ЗапросЗагрузки.Заголовки.Вставить(
"Content-Length", Формат(ТелоЗагрузки.Размер(), "ЧГ="));
ЗапросЗагрузки.Заголовки.Вставить(
"Content-Type", "multipart/form-data; boundary=" + Разделитель);
ЗапросЗагрузки.УстановитьТелоИзДвоичныхДанных(ТелоЗагрузки);
ОтветЗагрузки = СоединениеЗагрузки.ОтправитьДляОбработки(ЗапросЗагрузки);
ПроверитьОтветMax(ОтветЗагрузки, "Не удалось загрузить файл");
Если НЕ ЗначениеЗаполнено(ТокенВложения) Тогда
РезультатЗагрузки = ПрочитатьОтветJSON(ОтветЗагрузки);
ТокенВложения = РезультатЗагрузки.token; // file и image
КонецЕсли;
// 3. Создаём сообщение с token загруженного файла.
Нагрузка = Новый Структура("token", ТокенВложения);
Вложение = Новый Структура("type, payload", ВидВложения, Нагрузка);
Вложения = Новый Массив;
Вложения.Добавить(Вложение);
ТелоСообщения = Новый Структура;
ТелоСообщения.Вставить("text", ТекстСообщения);
ТелоСообщения.Вставить("attachments", Вложения);
ЗапросСообщения = Новый HTTPЗапрос(
"/messages?user_id=" + Формат(ИдПользователя, "ЧГ="));
ЗапросСообщения.Заголовки.Вставить("Authorization", ТокенБота);
ЗапросСообщения.Заголовки.Вставить(
"Content-Type", "application/json; charset=utf-8");
ЗапросСообщения.УстановитьТелоИзСтроки(
ЗаписатьВJSON(ТелоСообщения),
КодировкаТекста.UTF8,
ИспользованиеByteOrderMark.НеИспользовать);
ОтветСообщения = СоединениеAPI.ОтправитьДляОбработки(ЗапросСообщения);
СтрокаОтвета = ОтветСообщения.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8);
Если СтрНайти(СтрокаОтвета, "attachment.not.ready") > 0 Тогда
// Сохраните параметры операции в очереди и повторите шаг 3 позднее.
ВызватьИсключение "Max ещё обрабатывает вложение.";
КонецЕсли;
ПроверитьОтветMax(ОтветСообщения, "Не удалось отправить сообщение");
КонецПроцедуры
Функция НовоеHTTPSСоединение(Сервер, Таймаут)
ЗащищенноеСоединение = Новый ЗащищенноеСоединениеOpenSSL;
Возврат Новый HTTPСоединение(
Сервер, 443, , , , Таймаут, ЗащищенноеСоединение);
КонецФункции
Функция ЗаписатьВJSON(Значение)
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, Значение);
Возврат ЗаписьJSON.Закрыть();
КонецФункции
Функция ПрочитатьОтветJSON(Ответ)
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(
Ответ.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8));
Возврат ПрочитатьJSON(ЧтениеJSON);
КонецФункции
Процедура ПроверитьОтветMax(Ответ, Пояснение)
Если Ответ.КодСостояния <> 200 Тогда
ВызватьИсключение СтрШаблон(
"%1. HTTP %2: %3",
Пояснение,
Ответ.КодСостояния,
Ответ.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8));
КонецЕсли;
КонецПроцедуры
Если вложение ещё не готово
После загрузки Max некоторое время обрабатывает файл. Если сразу вызвать POST /messages,
сервер может вернуть attachment.not.ready. Такой ответ требует не новой загрузки,
а повторной отправки сообщения с тем же token. Интервал между попытками лучше увеличивать.
Не устраивайте ожидание пустым циклом внутри проведения документа: оно занимает рабочий процесс 1С и расходует процессорное время. Для повторов нужна очередь или фоновое задание. Сохраните token, адресата и текст в регистре сведений, а регламентному заданию поручите повторить только третий шаг через несколько секунд. Следующие интервалы можно увеличить, например до 10 и 15 секунд.
Повторное использование token
Часто используемый файл не обязательно загружать перед каждым сообщением. Сохраните соответствие
между контрольной суммой двоичных данных и token, а при следующей отправке используйте
уже известное значение. Если Max сообщит, что token недействителен, файл можно загрузить заново.
Для собственного кеша достаточно периодического регистра сведений с измерениями «бот»
и «контрольная сумма», ресурсами «token», «вид вложения» и «имя файла».
При ошибке недействительного token удалите запись, снова вызовите POST /uploads
и сохраните новый результат.
Как выглядит тот же вызов в подсистеме
В «Арете — сервер мессенджеров, Max edition» загрузка, кеш token, журнал и повторные попытки уже находятся в транспортном слое. Поэтому прикладной код сводится к подготовке параметров:
ИдПользователя = 123456789; // user_id из карточки получателя
СопроводительныйТекст = "Направляем документ из 1С.";
Параметры = Новый Структура;
Параметры.Вставить("user_id", ИдПользователя);
Параметры.Вставить("text", СопроводительныйТекст);
АСМ_М.ВложитьФайл(Параметры, ДвоичныеДанные, ИмяФайла);
АСМ_М.ОтправитьСообщение(Бот, Параметры);
Это не другой способ работы с Max, а готовая реализация той же последовательности, которая показана выше на стандартных средствах 1С.