Перейти к содержанию

Формат протокола

Вызов

Адрес для вызова

https://api.sendsay.ru/general/api/v100/json/ACCOUNT

или

https://api.sendsay.ru/general/api/v100/json/ACCOUNT/

Где ACCOUNT - код вашего аккаунта (совпадает с основным логином).

Для вызовов, не требующих аккаунта (например, ping), используйте знак минус '-'.

Транспорт - https - HTTP 1.0/1.1 с TLSv1.2 или TLSv1.3 - RFC 2616https://datatracker.ietf.org/doc/html/rfc2616, RFC 5246https://datatracker.ietf.org/doc/html/rfc5246, RFC 8446https://datatracker.ietf.org/doc/html/rfc8446

или

Транспорт - https - HTTP 1.0/1.1 с GOST2012-GOST8912-GOST8912 - используйте хост apigost.sendsay.ru

Возможно ГОСТ-шифрование всего канала связи- свяжитесь со Службой поддержки.

Рекомендуемые расширения - SNI

Имя api.sendsay.ru имеет несколько ip-адресов. При невозможности соединения с каким-то из этих адресов необходимо повторять запрос с использованием других.

Предыдущая версия продолжает поддерживаться (https://sendsay.ru/api/doc/API-0.174-20190417.html)

Метод вызова

Метод вызова -POST- RFC 2616 section 5https://datatracker.ietf.org/doc/html/rfc2616#section-9.5

Максимальный размер запроса - в настоящий момент 400 Мбайт. Если вам этого недостаточно, свяжитесь со Службой поддержки.

Content-Type: запроса -application/json

Содержимое запроса -JSON-строка в кодировке UTF-8.

{
 "action" : "код вызываемого действия" -- обязательно

 прочие параметры, если нужны
}

Описание url-кодирования, применяемого в некоторых случаях, доступно в RFC 3986https://www.rfc-editor.org/rfc/rfc3986#section-2.1

Описание формата JSON доступно в RFC 7159https://datatracker.ietf.org/doc/html/rfc7159

Не забывайте кодировать символы, которые не могут быть непосредственно записаны в JSON.

"Красивое" форматирование пробелами совершенно не обязательно и его отсутствие может заметно уменьшить размер вашего запроса, что увеличит скорость его обработки.

Настоятельно рекомендуется пользоваться готовыми функциями вашего языка программирования для перевода структуры данных в json-строку.

Обширная практика Службы поддержки показывает, что самостоятельная реализация json-кодирования может привести к значительной переоценке своих навыков программирования в строну уменьшения и последующей депрессии на этой почве.

Дополнительные параметры запроса

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

Он никак не анализируется (за исключением описанного ниже) и просто возвращается обратно в параметре ответа request.id.

Параметр request-id можно передать одним из трёх способов:

  • в url запроса

https://api.sendsay.ru/general/api/v100/json/ACCOUNT?request.id=идентификатор-запроса-url-кодированный

https://api.sendsay.ru/general/api/v100/json/ACCOUNT/?request.id=идентификатор-запроса-url-кодированный

  • в содержимом запроса
{
 "action" : "идентификатор api-вызова" 

,"request.id" : "идентификатор запроса" 

 прочие параметры, если нужны
}
  • в HTTP заголовке X-Request-ID
X-Request-ID: url-кодированный-идентификатор-запроса

Базовые лимиты_вызовов

5 одновременно длящихся запросов на изменение данных или расчёт статистики.

10 одновременно длящихся запросов всего.

80 запросов в секунду.

Вы можете получить api-ошибку rate_limit, HTTP код ответа 503. Тайм-аут запроса - в зависимости от отношения системы к текущему превышению лимита.

Ошибки оформления вызова

  • double_request.id - request.id указан несколькими способами одновременно

  • account_missmatch - аккаунт в url и после аутентификации не совпали

Готовые библиотеки для работы

В данный момент доступны готовые библиотеки доступны наhttps://github.com/sendsay-ru/

Там же есть сторонние библиотеки для работы с нашем api.

Проверка request.id

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

Если request.id не пустой и action входит в число контролируемых, то повторный вызов с таким же request.id в пределах15 минутзаканчивается сразу ошибкой repeated_request.id

Контролируемые вызовы:

issue.send
issue.send.multi
member.import
member.import.probe
member.sendconfirm

Для не транзакционного issue.send и для member.import дополнительно в obj будет повторён ответ на первоначальный запрос (например, там будет полезный параметр track.id).

Можно управлять этим временем в сторону увеличения через параметр запроса

{
 "repeated_request_ttl" : время в секундах, время менее стандартного игнорируется, время более 2 часов - считается как 2 часа
}

Последовательность обработки

Последовательность обработки вызовов не гарантирована, так как они выполняются параллельно.

Если вам требуется твёрдая уверенность в том, что вызов Б нормально воспользуется результатами более раннего вызова А, то вы должны дождаться явного окончания вызова А и только потом посылать вызов Б.

Конкурентные вызовы изменяющие один и тот же объект настоятельно рекомендуется выполнять последовательно по одному.

Например, два вызова anketa.quest.add в случае, когда они сделаны для одной и той же анкеты и второй вызов послан до окончания первого, могут дать три разных результата:

  • добавятся оба вопроса - только первый - только второй

Также следует помнить о порядке обработки при удалении сложных объектов. Например, если удаляемый объект ссылается на другие необходимые ему объекты и они тоже при этом должны быть удалены, то сначала следует удалить основной объект и явно дождаться результата и только после этого удалять объекты, на которые он ссылался. Иначе, при параллельной обработке может сложиться ситуация, когда вспомогательный объект не будет удалён, так как ещё будет существовать основной (вызов удаления вспомогательного объекта дошёл до обработки ранее завершения удаления основного).

Не заданные параметры api-вызова

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

Если явно не указано иное, то необязательный параметр, который служит для указания текстовых строк, будет трактоваться как имеющий пустое значение, если он отсутствует.

Представление чисел

Как при вызове, так и при ответе, параметр, имеющий численное значение, может быть представлен в любом из двух видов:

  • число как json-число - 123

  • число как json-строка - "123"

Представление дат

Временная зона всех дат - MSK (MSD, если летнее время опять вернут).

Даты принимаются и возвращаются в виде строки в формате с точностью до секунд.

YYYYY-MM-DD hh:mm:ss

где

YYYY - год MM - месяц DD - день hh - час mm - минута ss - секунда

Лидирующие нули не обязательны ("2019-5-31 17:7:4")

Параметры с типом "дата" имеют пометку их точности, составленную из первых букв старшего и младшего полей, например:

Ys - от года до секунды, формат "YYYYY-MM-DD hh:mm:ss"

Yh - от года до часа, формат "YYYYY-MM-DD hh"

YD - от года до месяца, формат "YYYYY-MM-DD"

Ответ

Ответ выдаётся в формате JSON и кодировке UTF-8

Content-Type: application/json

http-статусы

При нормальном развитии событий ответ 200.

Необходимо уметь обрабатывать ответы 3xx, которые возможны при работе по HTTP.

Все прочие статусы должны трактоваться как неизвестная ошибка исполнения запроса.

Синхронность

Если иного не описано, то вызов является синхронным - запрошенное действие будет выполнено (или не выполнено при ошибке) к моменту ответа на вызов.

Если вызов помечен как асинхронный, то успешный ответ обозначает, что запрошенное действие принято и будет выполнено позже в порядке обработки очереди запросов на такое же действие.

Асинхронный вызов возвращает track.id - номер трекера для отслеживания с помощью track.get

Общие поля ответа

{

 "request.id" : "то, что было в запросе в параметре request.id" -- не обязательно

,"duration" : null или "время обработки запроса в секундах" -- не обязательно

,"_ehid" : null или "некоторая служебная строка для отладки в случае обращения в саппорт" -- не обязательно
}

Нормальный ответ

{

 <общие поля>

,<поля специфические для конкретного запроса>

}

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

При наличии ошибки (ошибок), препятствующих полному или частичному выполнению запроса в ответе, появится массив errors со списком описаний каждой ошибки.

Обратите внимание, что в зависимости от вызова и самой ошибки, поле explain может быть не только строкой, но и массивом, и объектом.

Часть вызовов может возвращать массив warnings аналогичный структуры для того, чтобы сообщить о событиях, помешавших выполнить вызов в какой-либо его части, но не позволяющих считать, что вызов ошибочен.

{

 <общие поля>

."errors" :  [
              {
               "id" : "код ошибки-1" 

              ,"explain" : "возможное более развёрнутое описание-1" 

              ,<возможно поля специфические для конкретного запроса-1>
              }
             ,{
               "id" : "код ошибки-2" 

              ,"explain" : "возможное более развёрнутое описание-2" 

              ,<возможно поля специфические для конкретного запроса-2>
              }

              ......

             ]
}

Специальный ответ "Смена пароля"

Получение ответа с ошибкой "error/auth/failed" и уточнением "force_change_password" означает, что для продолжения работы требуется сменить пароль с помощью данной комбинации login/sublogin.

Это может быть вызвано как автоматическими событиями (например истёк срок действия пароля), так и прямой установкой такого требования через вызов sys.user.set или sys.password.set

Для продолжения работы необходимо:

  • Сменить пароль, используя вызов sys.user.set с разовой аутентификацией

  • Повторить вызов, в ответ на который получено "force_change_password", заново, так как он не был выполнен. Возможно при этом потребуется получить новую сессию.

{
 <общие поля>

,"errors"   : [
               {
                "id: : "error/auth/failed" 

               ,"explain" : "force_change_password" 
               }
              ]
}

Кэширование

Часть вызовов поддерживает кэширование ответов, что позволяет быстрее получать ответ, если вы уверены, что данные не изменились, и не придумывать свою собственную систему кэширования. В данный момент это вызовы stat.uni и member.list.count.

Кэширование применяется только для вызовов, которые завершились без ошибок.

Для использования кэширования в вызове передаётся параметр cache с как минимум значениями mode и key.

В ответ вызова, использующего кэширование с режимами "use", "fetch" и "refresh", будет добавляться параметр cache с информацией о результате использования кэша. Если вызов асинхронный, то параметр cache будет отсутствовать в ответе на основной вызов: - всегда в режиме "use" - так как реальную работу проделывает и общается с кэшем фоновый процесс - в режиме "fetch" - при наличии данных в кэше, так как тоже будет вызван фоновый процесс

Если вызов асинхронный, то при режиме "fetch" и при отсутствии данных, основной вызов сразу завершится с hit == 0, а асинхронная часть даже не начнётся и её действия не будут выполнены. Проверяйте на наличие hit и его равенство 0 для установления факта не вызова асинхронной части.

Вызов:

{
 "action: "xxx" 

,"cache" : {
            "mode" : режим -- обязательно
                           --
                           -- ignore  - не использовать
                           --
                           -- use     - при наличии кэша - ответ из него
                           --         - при отсутствии - получить новый результат и закэшировать его
                           --
                           -- refresh - получить новый результат и закэшировать его
                           --
                           -- fetch   - при наличии кэша - ответ из него
                           --         - при отсутствии - зависит от вызова, но в общем случае нет даже самих ключей ответа
                           --                            проверяйте сначала на hit == 0
                           --         - в зависимости от вызова, не требуется передача всех или почти всех параметров необходимых
                           --           для работы вызова так как, при отсутствии данных в кэше, они не вычисляются заново.

            ,"key" : ключ кэша -- обязательно
                               --
                               -- до 64 печатных символов ASCII кроме пробела
                               -- для каждого вызова набор уникальных ключей свой
                               -- можно использовать один и тот же ключ с разными вызовами, они не будут смешаны
                               --
                               -- каждый ключ виден всем суб-логинам одного общего логина - один может что-то посчитать
                               -- и закэшировать, а остальные воспользоваться результатом

            ,"ttl" : желаемое максимальное время жизни кэша в секундах
                       -- не обязательно, применимо для use и refresh при записи к кэш
                       --
                       -- при отсутствии - теоретически время не ограничено
                       --
                       -- практически - любая запись в кэше может перестать существовать в любой момент
                       -- и данные будут получены путём обычного выполнения запроса
           }

 <прочие параметры вызова>
}

В ответ на "refresh":

{
 ....

 "cache" : {
            "hit" : 0

           ,"created" : "YYYY-MM-DD hh:mm:ss" -- дата и время занесения записи в кэш

           ,"expired"  : null или "YYYY-MM-DD hh:mm:ss" -- дата и время окончания времени жизни записи.
                                                        --  null - бесконечно, но прочитайте выше примечания к параметру ttl
           }

 ....
}

При отсутствии данных в кэше в ответе на "use" и "fetch" будет:

{
 ....

 "cache" : {
            "hit" : 0
           }

 ....
}

При использовании данных из кэша в ответе на "use" и "fetch" будет:

{
 ....

 "cache" : {
            "hit" : 1

           ,"created" : "YYYY-MM-DD hh:mm:ss" -- дата и время занесения записи в кэш

           ,"expired"  : null или "YYYY-MM-DD hh:mm:ss" -- дата и время окончания времени жизни записи.
                                                        --  null - бесконечно, но прочитайте выше примечания к параметру ttl
           }

 ....
}

Несколько запросов за один вызов

Параметры аутентификации session и one_time_auth работают только в основном запросе и игнорируются во вложенных.

Вызовы login и logout во вложенных запросах приводят в ошибке.

Запросы выполняются последовательно.

Запросы изолированы друг от друга и ничего не знают о результатах работы своих соседей. Т.е. схема "в одном запросе создать объект, а в следующем его изменить" в общем случае работать не будет.

Порядок ответов в "result" совпадает с порядком запросов в "do".

Вы можете получить ошибку о слишком большом расходе памяти, если рассчитываете несколько статистических запросов в одном batch - выделяемая память переиспользуется между запросами, но не освобождается - суммарный её расход в какой момент может превысить предел.

{
 "action" : "batch" 

,"stop_on_error" : 0|1 -- прекращать выполнение после первого же запроса закончившегося с ошибками
                       -- не обязательно. по умолчанию 0
,"do" : [

               { один запрос }

              ,{ другой запрос }

              ....
             ]
}

ответ

{
 <общие поля>

,"result" : [

               { один ответ }

              ,{ другой ответ }

              ....
             ]
}

Проверка доступности и аутентификации

Эти вызовы позволяют проверить, правильно ли вы работаете с API и аутентификацией.

Пинг без аутентификации

{

 "action" : "ping" 

}

ответ

{

 <общие поля>

,"pong" : "что-то, более-менее каждый раз разное" 

}

Пинг с аутентификацией

{

 "action" : "pong" 

}

ответ

{

 <общие поля>

,"ping" : "что-то, более-менее каждый раз разное" 

,"account" : "код аккаунта" 

,"sublogin" : "код саблогина" 

,"via" : "использованный способ аутентификации который дал сессию" -- sys.user.get:via

}

Возвращаемое значение

Часть вызовов имеет возможность указать, что результат их работы можно сохранить как отчёт или загрузить на клиентский сервер.

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

 <другие параметры вызова>

,"result" : [
             { "type" : .... способ-сохранения-результата-1 }
            ,{ "type" : .... способ-сохранения-результата-2 }
            ............
            ]

Если параметр отсутствует, задан как пустой скаляр или задан как пустой массив, то по умолчанию выбирается значение "response".

Если запрошенное действие "response" или "none", то работа вызова происходит синхронно, и его результат возвращается сразу в ответе.

Иначе вызов возвращает track.id - номер запроса для отслеживания с помощью track.get, а само действие производится асинхронно.

Возможные действия с результатом приведены ниже.

Во всех случаях использования http(s)/ftp(s) ссылок в них работает кастомизация текущим временем {DT...} или случайным числом {RND} как описано в разделе "Кастомизированные ссылки". Такая же кастомизация работает для параметра filename. Таким образом, при использовании вызовов в "Действиях по расписанию" имеется возможность получать каждый раз разные ссылки и имена файлов.

Везде, где можно указать format, можно указать для него необязательные параметры:

  • compress - zip, bzip2, gzip или пусто (не сжимать). По умолчанию не сжимать для xlsx и сжимать zip для csv, html и json.

  • separator - разделитель полей для формата csv. По умолчанию - запятая, игнорируется для других форматов.

  • always_quote - 0 или 1 - всегда обрамлять значение каждой ячейки для формата csv. По умолчанию - 0 - нет, игнорируется для других форматов.

  • utf8 - 0 или 1 - использовать ли utf-8 как кодировку данных для формата csv. Игнорируется для других форматов.

  • append - 0 или 1 - если такой файл уже есть, то не заменить его, а дописать в конец. только для не сжатого сохранения - "compress":""

Форматы csv и xlsx исторически созданы с расчётом на одно значение (число, строка, пусто) в ячейке. Если же значением ячейки является массив или хэш, то они будут закодированы в json-строку.

Для csv по умолчанию, для аккаунтов открытых до 28 февраля 2018 года используется windows-1251 (и включить utf-8 можно указав "utf8":1). Для аккаунтов, открытых с 28 февраля 2018 года по умолчанию используется кодировка utf-8 (вернуть windows-1251 можно, указав "utf8":0). Рекомендуется всегда использовать utf-8, так как, как минимум, в интернациональных email могут быть символы, не представленные в windows-1251, не говоря уже о данных подписчика.

Кодировка всех остальных форматов отчётов - utf-8.

Отправить результат на электронную почту

После вычисления результата на адреса, указанные в массиве to, высылается письмо с прикреплённым файлом.

Название файла задаётся в необязательном параметре filename, а при его отсутствии генерируется автоматически.

Формат файла задаётся в необязательном параметре format, а при его отсутствии используется csv.

Можно использовать черновик выпуска для настройки содержимого письма и указать его номер в необязательном параметре draft.

    { "type": "email_file", "to" : [ <email1>, .... ], "draft": <draft_id>, "filename": <filename>, "format": <xlsx|csv|html|json> },

Сохранить в Отчёты

После вычисления результата он сохраняется в хранилище отчётов.

Срок хранения файла - 90 дней, после чего он автоматически удаляется.

Название файла задаётся в необязательном параметре filename, а при его отсутствии генерируется автоматически. Название файла в дальнейшем доступно в результатах отслеживания запроса через track.get

Формат файла задаётся в необязательном параметре format, а при его отсутствии используется csv.

    { "type": "save", "to": <filename>, "format": <xlsx|csv|html|json> },

Загрузить на внешний ресурс пo http(s)

После вычисления результата он передаётся по ссылке указанной в to.

Если to не указан, то используется адрес владельца аккаунта.

Для загрузки по http(s) используется метод POST с типом multipart/form c именем переменной, указанным в name (необязательно, по умолчанию "file") и именем файла указанным в filename (необязательно, по умолчанию генерируется автоматически).

Формат файла задаётся в необязательном параметре format, а при его отсутствии используется csv.

    { "type": "url_file", "to" : <http(s)-url>, "name" : "<post-arg-name>", "filename": <post-filename>, "format": <xlsx|csv|html|json> },

Загрузить на внешний ресурс пo ftp(s)

После вычисление результата он загружается по ссылке, указанной в to.

Для загрузки по ftp(s) указывается сервер и каталог на сервере (параметр to), в который загружается файл с именем, указанным в filename (не обязательно, по умолчанию генерируется автоматически).

Формат файла задаётся в необязательном параметре format, а при его отсутствии используется csv

    { "type": "url_file", "to" : <ftp(s)-url>, "filename": <filename>, "format": <xlsx|csv|html|json> }

Отправить уведомление на электронную почту

После вычисления результата на адреса, указанные в массиве to высылается письмо с уведомлением об окончании расчёта.

Если to не указан, то используется адрес владельца аккаунта.

Обратите внимание, что сам результат в письме не содержится и его получение должно быть предусмотрено отдельно (email_file,save,url_file).

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

    { "type": "email_notify", "to" : [ <email1>, .... ], "draft": <draft_id> },

Отправить уведомление по sms

После вычисления результата на номера телефонов, указанные в массиве to, высылается сообщение с уведомлением об окончании расчёта.

Обратите внимание, что сам результат в письме не содержится и его получение должно быть предусмотрено отдельно (email_file,save,url_file).

    { "type": "sms", "to" : [ <cellphone1>, <cellphone2> ] },

Уведомить по внешней ссылке

После вычисления результата вызывается ссылка, указанная в to (методом GET).

Обратите внимание, что сам результат в вызове не содержится и его получение должно быть предусмотрено отдельно (email_file,save,url_file).

    { "type": "url_notify", "to" : <url> },

Вернуть в ответе

Результат вычисляется синхронно и возвращается в ответе.

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

Это действие предназначено для работы интерфейсов взаимодействия с человеком.

Хотя его и получится использовать при интеграции работы с API автоматических систем, но запросы могут быть автоматически прекращены системой по разным критериям (например, длительность выполнения).

Рекомендуется в случаях автоматизированного взаимодействия использовать схему работы с асинхронным получением ответа через запрос с сохранением результата в отчёты и последующим забором отчёта по готовности (можно определить по трекеру или получив callback) или с отправкой результата по готовности на ваш url.

    { "type": "response" },

Не возвращать

Это действие имеет смысл только для вызова Универсальной статистики при задании пересчёта кэша и позволяет сократить размер ответа.

    { "type": "none" },

Защищённые экземпляры сущностей

Большинство сущностей поддерживают защиту от изменения и удаления.

Такие сущности имеют свойствоprotected.

Оно принимает значения: 0 - не защищено, по умолчанию, 1 - защищено пользователем, -1 - защищено системой

При protected = 1 для изменения или удаления экземпляра необходимо в вызове передать дополнительный необязательный параметр"ignore_protection".

Значением ignore_protection должно быть значение id объекта.

Список сущностей которые поддерживают защиту:

Группы group. Черновики issue.draft. Форматы format. Анкета anketa. Формы form. Логины sys.user. Роли sys.role. Действия по расписанию cron. Триггеры sequence. DKIM-ключи issue.dkim. Адреса еmail-отправителей issue.emailsender. Названия sms-отправителей issue.emailsender.