API, Betta 0.114 2014-04-17

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

Вызов

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

https://pro.subscribe.ru/api

Метод вызова

Транспорт - HTTP ovet TLS (https) - RFC 2616 http://tools.ietf.org/html/rfc2616, RFC 2818 http://tools.ietf.org/html/rfc2818

Метод вызова - POST.

Не забывайте, что у метода POST есть два типа передачи данных:

- application/x-www-form-urlencoded - все параметры вызова передаются как в query-string и их значение надо передавать в кодировке urlencode (RFC 1738 http://tools.ietf.org/html/rfc1738)

- multipart/form-data - (RFC 2388 http://tools.ietf.org/html/rfc2388) - каждый параметр вызова передаётся в отдельной части и urlencoding не применяется

Параметры вызова

Описание формата JSON вы найдёте в RFC 4627 http://tools.ietf.org/html/rfc4627

вверх

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

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

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

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

Ответ

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

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

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

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

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

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


{

 "request.id" : "то что было в запросе в параметре request.id" 

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

}

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


{

 <общие поля>

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

}

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

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

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

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


{

 <общие поля>

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

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

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

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

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

              ......

             ]
}

вверх

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

При наличии в ответе поля REDIRECT необходимо

Так же стоит иметь счётчик перенаправлений что бы избежать зацикливания.

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


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

,"REDIRECT" : "/xxx/yyy" 

}

вверх

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

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

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

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


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

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

               ,"explain" : "force_change_password" 
               }
              ]
}

вверх

Кэширование

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

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

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

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

Если вызов асинхронный, то параметр cache будет отсутствовать в ответе на основной вызов:

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

Вызов:

{
 "action: "xxx" 

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

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

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

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

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

{
 ....

 "cache" : {
            "hit" : 0
           }

 ....
}

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

{
 ....

 "cache" : {
            "hit" : 1

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

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

 ....
}

вверх

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

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

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

{
 "action" : "batch" 

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

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

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

              ....
             ]
}

ответ

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

,"result" : [

               { один ответ }

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

              ....
             ]
}

вверх

Проверка работоспособности

Пинг без авторизации


{

 "action" : "ping" 

}

ответ


{

 <общие поля>

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

}

вверх

Пинг с авторизацией


{

 "action" : "pong" 

}

ответ


{

 <общие поля>

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

}

вверх

Авторизация

Настоятельно рекомендуется для вашей же безопасности при реализации автоматической работы по API создавать для этих целей отдельный суб-логин с максимально органиченными правами !

Авторизация по логину и паролю

Полученный в ответ номер сессии должен передаваться во всех запросах в json-параметре "session", кроме запросов "ping" и "login".

Время жизни сессии несколько часов. Рекомендуется завершать явным вызовом "logout".

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

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


{

 "action" : "login" 

,"login"  : "общий логин" 

,"sublogin" : "личный логин" 

,"passwd" : "пароль" 

}

ответ


{

 <общие поля>

,"session" : "номер сессии" 

,"login" : "общий логин для которого выдана авторизация" 

,"sublogin" : "личный логин для которого выдана авторизация" 

}

вверх

Авторизация AGSES-карт. Запрос.

Авторизация биометрических карт AGSES происходит в два этапа.

На этапе запроса вы получаете flicker-код, на этапе ответа сообщаете код полученный клиентом от своей карты
и, в случае успеха, в ответ получаете номер сессии.


{

 "action" : "login.agses.challenge" 

,"card" : номер карты, наличие лидирующих 0 не обязательно

}

ответ


{

 <общие поля>

,"flicker" : код фликера

,"hedgeid" : номер запроса

}

вверх

Авторизация AGSES-карт. Ответ.

Сессия, полученная в этом вызове точно такая же по своим свойствам как и сессия
от обычного вызова login

Храните её в надёжном, cухом, светлом месте вдали от детей.


{

 "action" : "login.agses.response" 

,"card" : номер карты, наличие лидирующих 0 не обязательно

,"hedgeid" : номер запроса

,"response" : ответ клиента

}

ответ


{

 <общие поля>

,"session" : "номер сессии" 

,"login" : "общий логин для которого выдана авторизация" 

,"sublogin" : "личный логин для которого выдана авторизация" 

}

вверх

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

Настоятельно не рекомендуется для вашей же безопасности не реализовывать автоматическую работу по API с использоватением этого вызова !
Не ленитесь реализовать нормальную работу с помощью сессий, вместо простой постоянной пересылки логина и пароля !

Единственный разумный повод использования разовой авторизации - смена пароля в ответ на ошибку force_change_password.

Указанный параметр передаётся во параметрах вызова вместо "session" и может использоваться с любым вызовом требующим авторизации.

Авторизация заканчивается в окончанием вызова и завершения вызовом "logout" не требует.

Процессы порождённые асинхронными вызовами продолжают работать-и-работать до их естественного завершения.


{

 "one_time_auth" : {

                    "login"  : "общий логин" 

                   ,"sublogin" : "личный логин" 

                   ,"passwd" : "пароль" 

                   }

}

вверх

Окончание работы

Текущая сессия становится не действительной


{

 "action" : "logout" 

}

ответ


{

 <общие поля>

}

вверх

Отслеживание асинхронных запросов

Список асинхронных запросов

Список всех или отобраных по фильтру запросов. Информация о запросах старше двух месяцев регулярно автоматически удаляется из системы.

{

 "action" : "track.list" 

,"filter" : { -- обязателен как минимум один параметр фильтра

             "action" : тип запроса

             -- или
            ,"status" : интересующее состояние запроса -- одно число или список чисел но в массиве
             -- или
            ,"!status" : состояние запроса которое исключить -- одно число или список чисел но в массиве

            ,"dt.from" : "YYYY-MM-DD hh:mm:ss" -- самая ранняя дата создания запроса на отслеживание

            ,"status.dt.from" : "YYYY-MM-DD hh:mm:ss" -- самая ранняя дата изменени статуса запроса на отслеживание
            }
}

ответ

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

 "list" : [
            {
             объект как возвращается track.get
            }

           .........
          ]
}

Описание асинхронного запроса

{

 "action" : "track.get" 

,"id"     : номер трекера

}

ответ

{
 obj : {
        "id" : номер запроса

       ,"dt" : дата-время создания (YYYY-MM-DD hh:mm:ss)

       ,"action" : -- тип запроса
                    "issue.send" 
                    "stat.uni" 
                    "member.list" 
                    "member.list.count" 
                    "member.import" 
                    "member.sendconfirm" 
                    "member.update" 
                    "member.delete" 
                    "stat.activity" 
                    "stat.issue" 
                    "stat.group.portrait" 
                    "stat.group.common" 
                    "issue.split.create" 
                    "stoplist.add" 
                    "stoplist.delete" 

       ,"status" : состояние запроса
                    -- -4 - отложенное действие (например отложенный выпуск рассылки)
                    -- -3 - отменён
                    -- -2 - закончился ошибкой
                    -- -1 - закончился успешно
                    --  0 - принят
                    --  1 - запущен
                    --  2 - начата обработка
                    --  3 - сортировка
                    --  4 - форматирование
                    --  5 - генерация отчёта
                    -- остальное - зависит от типа запроса

       ,"status.dt" : дата-время установки текущего состояния (YYYY-MM-DD hh:mm:ss)

       ,"error"  : код ошибки    -- не обязательно
                                 -- смысл зависит от типа запроса

   -- дополнительные параметры в зависимости от запроса и его состояния

      ,"param" : {

   -- === issue.send ===

                   ,"group.id" : код группы

                     -- характеристики выпуска, если они уже известны

                   ,"issue.id" : номер выпуска

                   ,"issue.dt" : время начала выпуска

                   ,"issue.size" : количество смсок или примерный размер письма в байтах

                   ,"issue.members" : количество получателей для которых уже сформированы письма

                   ,"issue.format" : "email" или "sms" 

                   -- в состоянии "начата обработка" примерная информация о достигнутом прогрессе

                   ,"eta" : {

                             "rest'        : примерное количество секунд до окончания

                            ,"perc"        : примерный процент обработки

                            ,"dt.finished" : примерное дата-время окончания

                            ,"dt.updated   : дата-время последнего обновления информации eta
                            }  

                   -- отчёт и статистика после завершения работы для Экспресс-Выпуска

                   ,"report_file" : "название файла" -- если были ошибки, то название файла с отчётом для вызова rfs.get

                   ,"statistic" : {
                                   "total"       : всего строк
                                  ,"taked"       : верных строк
                                  ,"erroneous"   : строк с ошибками
                                  ,"repeated"    : повторов адресов
                                  ,"only_unique" : 0|1 -- был ли выпуск запущен с запретом повторной
                                                       -- рассылки по дублирующимся адресам
                                  }

   -- === stat.uni ===

                    "report_file" : "название файла" -- пусто до окончания запроса
                                                     -- после окончания - название файла с отчётом для вызова rfs.get
                                                     -- или пусто если заказывалась высылка на почту
   -- === stat.activity ===
   -- === stat.issue    ===

                    "group.id" : "код группы" -- пусто если считалось без указания группы
                                              -- иначе код группы указанной для расчёта

                   ,"report_file" : "название файла" 

   -- === stat.group.portrait ===
   -- === stat.group.common   ===
   -- === member.list         ===

                    "group.id" : "код группы" -- пусто если считалось без указания группы
                                              -- иначе код группы указанной для расчёта

                   ,"report_file" : "название файла" 

                   ,"eta" : { в состоянии "начата обработка" примерная информация о достигнутом прогрессе }

   -- === member.import ===

                    "group.id" : "код группы" -- пусто если при импорте не указывалась автосоздание новой
                                              -- или заполнение существующей группы иначе код автосозданой
                                              -- существующей группы

                   ,"eta" : { в состоянии "начата обработка" примерная информация о достигнутом прогрессе }

                   -- отчёты и статистика после завершения работы

                   ,"report_file" : "название файла" -- файл в формате xlsx с отчётом в хранилище отчётов если были ошибки.
                                                     -- первые 1000 ошибок для просмотра человеком

                   ,"report_file.json" : "название файла" -- файл в формате json запакованый zip с отчётом в хранилище отчётов если были ошибки.
                                                          -- все ошибки в машиноразбираемом виде

                   ,"statistic" : {
                                   "inserted"    : добавлено новых адрес
                                  ,"updated"     : обновлено адресов
                                  ,"erroneous"   : строк с ошибками
                                  ,"unconfirmed" : новых адресов внесённых без подтверждения
                                  ,"repeated"    : повторов адресов
                                  }

   -- === member.list.count  ===
   -- === member.sendconfirm ===
   -- === member.update ===
   -- === member.delete ===
   -- === stoplist.add ===
   -- === stoplist.delete ===

                    "group.id" : "код группы" -- пусто если считается без указания группы
                                              -- иначе код группы указанной для расчёта

                   ,"eta" : { в состоянии "начата обработка" примерная информация о достигнутом прогрессе }

   -- === issue.split.create  === 

                    -- отслеживается не "создание" (как формально следует из названия)
                    -- а процесс формирования списка получателей и назначения им того или иного варианта
                    -- соответственно, трекер отслеживает тестирование в состояние "подготавливается к запуску" 

                    "split.id" : "код группы" -- номер сплит-тестирования

                   ,"eta" : { в состоянии "начата обработка" примерная информация о достигнутом прогрессе }

                  }
       }
}

вверх

Анкеты

Список анкет


{

 "action" : "anketa.list" 

}

ответ


{

 <общие поля>

,"list" : [

            {

             "system" : "системная, да, нет (1, 0)" -- системные анкеты не доступны для изменения

             ,"id" : "уникальный идентификатор" 

             ,"name" : "название",

            }

            ...

           ]

}

Чтение анкеты


{

 "action" : "anketa.get" 

,"id"     : код-анкеты

}

ответ


{

 <общие поля>

,"obj" : {

          "id" : код-анкеты,

          "param" : {

                     "name"     : название анкеты,

                     "system"   : анкета системная да/нет

                    },

          "order" : [ -- порядок вопросов

                     "код-вопроса3",

                     "код-вопроса8",

                     .....

                     "код-вопроса4" 

                    ]

          "quests" : { -- вопросы анкеты

             "код-вопроса" : {

                             "id" : код-вопроса,

                             "@"  : номер по порядку,

                             "name" : формулировка вопроса,

                             "type" : тип вопроса,

                             --  для типа "dt" 

                             "subtype" : под-тип вопроса
                                        --  yd  - Дата и время с точностью от года до дня
                                        --  yh  - Дата и время с точностью от года до часа
                                        --  ym  - Дата и время с точностью от года до минуты
                                        --  ys -  Дата и время с точностью от года до секунды

                             --  для типа "free" 

                             "width" : ширина поля в байтах для типа free

                             --  для типа "1m" или "nm" 

                             "answers" : { -- ответы вопроса

                                          "код ответа1" : "название ответа1",

                                          "код ответа2" : "название ответа2",

                                           .....

                                          "код ответа3" : "название ответа3",

                                         },

                             "order" : [ -- порядок ответов

                                        "код ответа3",

                                        "код ответа1",

                                        .....

                                        "код ответа2" 

                                       ],

                            },

             ............

         },

}

вверх

Удаление анкеты


{

    "action" : "anketa.delete" 

   ,"id" : "уникальный идентификатор анкеты" 

}

ответ


{

    <общие поля>

}

вверх

Создание анкеты


{

     "action" : "anketa.create" 

    ,"name" : "Название анкеты",

     необязательные:

    ,"copy_from" : "уникальный идентификатор анкеты (не нужен для создаваемой с нуля, а не копируемой анкеты)" 

    ,"id" : "уникальный код анкеты" 

    ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 )" 

    по умолчанию, считается "return_fresh_obj" : "0" 

}

ответ


{

  <общие поля>

,"id" : "код анкеты" 

}

если "return_fresh_obj" : "1" 

ответ -- как на запрос чтения "action" : "anketa.get" соответствующей анкеты

вверх

Сохранение анкеты


{

    "action" : "anketa.set" 

    ,"id" : "идентификатор анкеты" 

    ,"name" : "название анкеты" 

    необязательный

    ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 )" 

    по умолчанию, считается "return_fresh_obj" : "0" 

}

ответ


{

    <общие поля>

}

если "return_fresh_obj" : "1" 

ответ -- как на запрос чтения "action" : "anketa.get" соответствующей анкеты

вверх

Добавления нового вопроса анкеты


{

   "action" : "anketa.quest.add" 

  ,"anketa.id" : "уникальный идентификатор анкеты вопроса" 

  ,"return_fresh_obj" : 0|1  -- вернуть новый объект анкета -- да (1), нет (0, по умолчанию)

-- добавление одного вопроса

  ,obj : { -- описание вопроса

          "name" : "Текст вопроса" 

         ,"type" : "Тип вопроса" 
                 -- free - свободный ввод
                 -- dt   - дата и время
                 -- 1m   - выбор одного из списка
                 -- nm   - выбор нескольких из списк
                 -- int  - целое число

         -- параметры, определяемые типом вопроса:

         -- для type=free:

              ,"width" : "количество символов для свободного ввода" -- обязателен,  > 0

         -- для  type=dt:

              ,"dtsubtype" : "точность для даты и времени" -- обязателен
                              -- yd - от года до дня.     в данных записывается и выводится в формате "YYYY-MM-DD" 
                              -- yh - от года до часа.    в данных записывается и выводится в формате "YYYY-MM-DD hh" 
                              -- ym - от года до минуты.  в данных записывается и выводится в формате "YYYY-MM-DD hh:mm" 
                              -- ys - от года до секунды. в данных записывается и выводится в формате "YYYY-MM-DD hh:mm:ss" 

         -- для type=1m или nm (выбор из списка):

              ,"answers" : { -- ответы вопроса 

                            "код ответа 1" : "название ответа1" 

                           ,"код ответа 2" : "название ответа2" 

                           ..... 

                           ,"код ответа N" : "название ответаN" 

                          }

             ,"order" : [ -- порядок ответов

                           "код ответа 3" 

                          ,"код ответа 8" 

                          .....

                          ,"код ответа 4" 
                        ]

         -- необязательные для любого типа

         ,"id" : "уникальный идентификатор вопроса" 

        }

-- добавление нескольких вопросов. любая ошибка отменяет все изменения. вопросы буду или все добавлены или все не добавлены

  ,obj : [
          { описание вопроса-1 }
         ,{ описание вопроса-2 }
         ,...............
         ]
}

ответ


{

  <общие поля>

 ,"obj" : { объект анкета как из ответа anketa.get } -- если "return_fresh_obj" : "1" 

-- в зависимости от вида параметра obj в вызове

 ,"id" : "id-добавленного вопроса" 

-- или

 ,"id" : [ -- порядок id соответствует порядку объектов в параметре obj в вызове
          "id-добавленного вопроса-1" 
         ,"id-добавленного вопроса-2" 
         .......
         ]

}

вверх

Изменение вопроса анкеты


{

   "action" : "anketa.quest.set" 

  ,"anketa.id" : "уникальный идентификатор анкеты вопроса" 

  ,"return_fresh_obj" : 0|1  -- вернуть новый объект анкета -- да (1), нет (0, по умолчанию)

-- добавление одного вопроса

  ,"obj" : { -- описание вопроса

            "id" : "уникальный идентификатор вопроса" 

           ,"name" : "Текст вопроса" 

           -- параметры, определяемые типом вопроса:

           -- для type=free:

                ,"width" : "количество символов для свободного ввода, обязателен,  > 0" 

           -- для type=1m или nm:

                ,"answers" : { -- ответы вопроса 

                              "код ответа 1" : "название ответа 1" 

                             ,"код ответа 2" : "название ответа 2" 

                             ..... 

                             ,"код ответа N" : "название ответа N" 

                             }

               ,"order" : [ -- порядок ответов

                           "код ответа 3" 

                          ,"код ответа 8" 

                          .....

                          ,"код ответа 4" 
                          ]
        }

-- изменение нескольких вопросов. любая ошибка отменяет все изменения. вопросы буду или все изменены или все не изменены

  ,obj : [
          { описание вопроса-1 }
         ,{ описание вопроса-2 }
         ,...............
         ]
}

ответ


{

    <общие поля>

 ,"obj" : { объект анкета как из ответа anketa.get } -- если "return_fresh_obj" : "1" 

-- в зависимости от вида параметра obj в вызове

 ,"id" : "id-добавленного вопроса" 

-- или

 ,"id" : [ -- порядок id соответствует порядку объектов в параметре obj в вызове
          "id-добавленного вопроса-1" 
         ,"id-добавленного вопроса-2" 
         .......
         ]
}

вверх

Удаление вопроса анкеты

Удалении нескольких вопроса транхакционно - удаляются или все (не ошибок) или ни ничего (хоть одна ошибка).


{

     "action" : "anketa.quest.delete" 

    ,"anketa.id" : "уникальный идентификатор анкеты вопроса" 

    ,"id" : "уникальный идентификатор вопроса" 

     -- или

    ,"id" : [ "id1", "id2", ..... ]

--- необязательный

    ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 )" 

    по умолчанию, считается "return_fresh_obj" : "0" 

}

ответ


{

    <общие поля>

}

если "return_fresh_obj" : "1" 

ответ -- как на запрос чтения "action" : "anketa.get" соответствующей анкеты

вверх

Изменение позиции вопроса анкеты


{

     "action" : "anketa.quest.order" 

     ,"anketa.id" : "уникальный идентификатор анкеты вопроса" 

     ,"order" : [ -- новый порядок вопросов

          "код-вопроса3" 

         ,"код-вопроса8" 

          .....

         ,"код-вопроса4" 

     ]

    необязательный

    ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 )" 

    по умолчанию, считается "return_fresh_obj" : "0" 

}

ответ


{

    <общие поля>

}

если "return_fresh_obj" : "1" 

ответ -- как на запрос чтения "action" : "anketa.get" соответствующей анкеты

вверх

Удаление ответа вопроса анкеты

Для вопросов типа "1m" и "nm"


{

    "action" : "anketa.quest.response.delete" 

   ,"anketa.id" : "уникальный идентификатор анкеты вопроса" 

   ,"quest.id" : "уникальный идентификатор вопроса" 

   ,"id" : "уникальный идентификатор ответа" 

   необязательный

   ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 )" 

   по умолчанию, считается "return_fresh_obj" : "0" 

}

ответ


{

    <общие поля>

}

если "return_fresh_obj" : "1" 

ответ -- как на запрос чтения "action" : "anketa.get" соответствующей анкеты

вверх

Изменение позиции ответа вопроса

Для вопросов типа "1m" и "nm"


{

    "action" : "anketa.quest.response.order" 

    ,"anketa.id" : "уникальный идентификатор анкеты вопроса" 

    ,"id" : "уникальный идентификатор вопроса" 

    ,"order" : [ -- новый порядок ответов

         "код-ответа3" 

        ,"код-ответа8" 

        .....

        ,"код-ответа4" 

]

    необязательный

    ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 )" 

    по умолчанию, считается "return_fresh_obj" : "0" 

}

ответ


{

    <общие поля>

}

если "return_fresh_obj" : "1" 

ответ -- как на запрос чтения "action" : "anketa.get" соответствующей анкеты

Подписчики

Проверка адресов

Вызов проверяет список адресов на синтаксическую верность, даёт нормализованый вариант написания и (если указано) на то, что про этот адрес думает первичный MX обслуживающий домен.

В ответе ключами списка являются адреса из исходного списка в неизменном виде. Номарлизованый вид доступен в параметре email.

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

Стадии на которых проверка SMTP закончилась ошибкой (параметр status):

  resolver - определение MX

  nomxa    - у домена нет ни одной записи MX или А

  connref  - не удалось соединиться с MX

  banner   - ошибка в первичном банере

  helo     - ошибка в ответ на HELO

  mailfrom - ошибка в ответ на MAIL FROM

  rcptto   - ошибка в ответ на RCPT TO

Для принимания как работает SMTP и что значат все эти странные слова полезно изучить RFC 5321.


{

  "action" : "email.test" 

  "smtp.test" : "проверять доступность по smtp - 0|1" -- не обязательное поле

 ,"smtp.timeout" : "таймаут в секундах" -- не обязательное поле. по умолчанию 15.

 ,"list" :  [ -- список адресов для проверки

             " missing@CityCat.ru" 

            ,"     PRO@subscribe.ru   " 

            ,"123@test@test.ru  " 

            ......

            ]

}

ответ


{

    <общие поля>

   "list" : {

              " missing@CityCat.ru" : { "email" : "missing@citycat.ru" -- нормализованая форма адреса

                                       ,"syntax" : "ok" -- результат синтаксической проверки адреса

                                       ,"smtp"  : {

                                              "status"  : "rcptto" -- стадия возникновения ошибки

                                             ,"domain"  : "citycat.ru" -- домен для которого определялся первичный MX

                                             ,"mx"      : "smtp.citycat.ru" -- первичный MX используемый для теста

                                             ,"ip"      : "81.9.34.192" -- ip-адрес использованного MX

                                             ,"ptr"     : [             -- список имён соответствующих ip-адресу
                                                           "cat192.subscribe.ru" 
                                                          ]

                                             ,"code"    : "550" -- код SMTP-ошибки (000 - тайм-аут)

                                             ,"dsn"     : "5.1.1" -- DSN-код SMTP-ошибки (при наличии в ответе)

                                             ,"message" : "<missing@citycat.ru>... User unknown" 
                                                           -- текст SMTP-ошибки или ошибки DNS

                                                  }

                                       }
             ,"     PRO@subscribe.ru   " :  { "email" : "pro@subscribe.ru" 

                                             ,"syntax" : "ok" 

                                             ,"smtp" : {
                                                          "status" : "ok" 

                                                         ,"domain" : "subscribe.ru" 

                                                         ,"mx"     : "smtp.subscribe.ru" 

                                                         ,"ip"     : "81.9.34.192" 

                                                         ,"ptr"    : [
                                                                      "cat192.subscribe.ru" 
                                                                     ]
                                                       }

                                            }

             ,"123@test@test.ru  " :   { "email" : null

                                       ,"syntax" : "error/email/multydog" -- код ошибки

                                      }

         }

}

Получить ответы подписчика


{

  "action" : "member.get" 

 ,"email" : "адрес подписчика" 

}

ответ


{

    <общие поля>

-- ответы на вопросы анкет

  ,"obj" : { 

            -- ank   - код анкеты

            -- quest - код ответа

            -- val   - значение ответа или код ответа если вопрос с выбором из списка

            "ank1" : {

                       -- обычный вопрос

                       "quest" : "val" 

                       -- вопрос с множественным выбором

                      ,"quest" : [ "val", "val", "val"....]

                       -- вопрос с выбором одного из нескольких - всё равно массив

                      ,"quest" : [ "val" ]

                      }

          ,"ank2" : {

                      ..............

                     }

          -- псевдо-анкета описывающая участие в группах-списках

          ,"-group": { -- перечисляются кода только тех групп-списков, в которых адрес состоит

              "id1" : 1

             ,"id2" : 1

                .............

             ,"idN": 1

            }

          }

}

Cоздать подписчика / Установить ответы подписчика

При отсутствии адреса в базе он автоматически создаётся.

Все попытки изменения системной анкеты member игнорируются, за исключением ответа error.

Установка member.error в "0" удаляет запись о проблемах доставки и снимает блокировку адреса из-за ошибок доставки если таковая была.


{

  "action" : "member.set" 

 ,"email" : "адрес подписчика" 

 ,"addr_type": "тип адреса подписчика (email | msisdn)" 

 ,"source" : "ip-адрес оригинального запроса" 
              -- если оригинальный инициатор запроса вы сами - ваш ip, иначе ip-адрес откуда вам пришёл запрос

 ,"if_exists" : "error|update|overwrite" 
                 -- правило изменения ответов анкетных данных. не действует на псевдоанкету "-group" 
                 -- error     - при наличии адреса в базе возвращается ошибка
                 -- update    - если ответ на изменяемый вопрос уже есть то он остаётся неизменным
                 -- overwrite - ответ заменяется/создаётся в любом случае

  ,"newbie.confirm": "подписчик должен подтвердить внесение в базу (1|0)" 
                     -- используется только при внесении email-адресов
                     -- действует лимит внесения без подтверждения
                     -- подробнее описанный в вызове member.import

  ,"newbie.letter.confirm" : "номер шаблона информационного письма" 
                           -- высылается если новый адрес до этого отсутствовал в базе, и внесён с необходимостью подтверждения
                           -- если параметр пуст или отсутствует, то ничего выслано не будет
                           -- но необходимость подтвердить регистрацию никуда не денется и выслать письма вы сможете позже через member.sendconfirm
                           -- этот параметр НЕ зависит от newbie.confirm - если у вас кончился лимит внесения без подтверждения, то импорт
                           -- продолжит вносить уже с подтверждением и этот параметр будет использоваться 
                           -- при внесении номеров телефонов ни какие уведомления не высылаются
                           -- информационное письмо должно иметь заполненный адрес отправителя и не находиться на модерации

  ,"newbie.letter.no-confirm" : "номер шаблона информационного письма" 
                           -- высылается если новый адрес до этого отсутствовал в базе, и внесён с без необходимостью подтверждения
                           -- если параметр пуст или отсутствует, то ничего выслано не будет
                           -- при внесении номеров телефонов ни какие уведомления не высылаются
                           -- информационное письмо должно иметь заполненный адрес отправителя и не находиться на модерации

  ,"obj" : {

            -- управление анкетными данными подписчика

            -- ank   - код анкеты

            -- quest - код ответа

            -- val   - значение ответа или код ответа если вопрос с выбором из списка

            -- значение не указанных вопросов не меняется

            'ank1' : {

                       -- установка значения ответа обычного вопроса

                       'quest' :'val'

                       -- установка значения ответа для вопроса с типом "дата" в зависимости от заданной в анкете точности

                       'quest' : 'YYYY-MM-DD'
                       'quest' : 'YYYY-MM-DD hh'
                       'quest' : 'YYYY-MM-DD hh:mm'
                       'quest' : 'YYYY-MM-DD hh:mm:ss'

                       -- установка значения ответа у вопроса с множественным выбором

                      ,'quest' : [ 'val', 'val', 'val'....]

                       -- установка значения ответа у вопрос с выбором одного из нескольких - всё равно массив

                      ,'quest' : [ 'val' ]

                       -- удаление ответа

                      ,'quest' : null

                      }

          ,'ank2' : {

                      ..............

                     }

          -- управление членством в группах-списках с помощью псевдо-анкеты

          -- параметр 'if_exists' не влияет на эту анкету

          ,'-group': {

                  -- 0 - удалить из группы

                  -- 1 - добавить в группу

                  -- членство в неперечисленных группах не меняется

                  'id1' : "(0|1)" 

                  ,id2' : "(0|1)" 

                   .............

                  ,idN' : "(0|1)" 

                }

          }

-- необязательный

 ,"return_fresh_obj" : "нужно вернуть данные объекта -- да, нет ( 1 | 0 )" 

-- по умолчанию, считается "return_fresh_obj" : "0" 

}

ответ


{

 <общие поля>

,"newbie"  : 0|1 -- новый подписчик - 0 - уже был в базе, 1 - внесён в базу

-- если "return_fresh_obj" : "1" 

 данные от запроса "member.get" соответствующего адреса

}

вверх

Подтвердить регистрацию


{

  "action" : "member.confirm" 

 ,"email" : "адрес подписчика" 

 ,'cookie' : "код подтверждения" 

}

ответ


{

 <общие поля>

 ,'error' : 'error/member/wrongcookie' - не верный код. отсутстви ошибки - подтверждение выполнено или не требовалось

}

вверх

Удалить подписчика

Адреса реально удаляются из базы !

Запрос с указанием списка по умолчанию синхронный.

Запрос с указанием группы по умолчанию асинхронный. Используйте sync = 1 если вам реально нужен ответ с описанием какие адреса как были обработаны.

Асинхронные запросы возвращают номер трекера для отслеживания.


{

  "action" : "member.delete" 

-- указание подписчиков одним из способов

 ,"email" : "адрес подписчика" 

или

 ,"list" : [

            "адрес подписчика" 

           ,"адрес подписчика" 

            ........

           ]

 ,"sync" : 0|1 -- изменение синхронности запроса. по умолчанию 1 - синхронный

или

 ,"group" : код группы участники которой будут удалены

 ,"sync" : 0|1 -- изменение синхронности запроса. по умолчанию 0 - асинхронный

или

 ,"url" : ссылка на файл со списком адресов. по одному на строке. возможно сжатие zip.

 ,"sync" : 0|1 -- изменение синхронности запроса. по умолчанию 0 - асинхронный

или

 ,"stat.uni" : { -- адреса для обработки получаются из запроса Универсальной Cтатистики 

                 -- подразумевается unique = 1

               ,"filter" : [ условие выборки как у запроса в вызове stat.uni ]

               ,"cache"  : [ настройки кэширования как у запроса в вызове stat.uni ]

               ,"select" : [ ... ] -- используйте только если не подходит значение по умолчанию [ 'member.email' ]

               }

 ,"sync" : 0|1 -- изменение синхронности запроса. по умолчанию 0 - асинхронный

}

ответ


{

 <общие поля>

-- для асинхронного запроса

,"track.id" : номер -- номер асинхронного запроса для отслеживания с помощью track.*

-- для синхронного запроса

,"list" : {
           "адрес-1" : 0|1 -- результат удаления - 1 - удалён, 0 - нет (например адрес ошибочен или отсутствует)

          ,"адрес-2" : 0|1

           .................
          }

}

вверх

Участие подписчика в группах-фильтрах

Списков групп-фильтров в которых состоит подписчик

А участие в группах-списках и так известена через member.get

{
 "action" : "member.where" 

 ,"email" : "адрес подписчика" 
}

ответ

{

 <общие поля>

,"list" : {
           "адрес" : [ список кодов групп-фильтров в которые адрес входит ]

          -- или

          ,"адрес" : null -- адрес не существует или синтаксически не верен

          }

}

Список подписчиков


{

  "action" : "member.list" 

 ,"addr_type": "тип выбираемых адресов подписчиков (email | msisdn)" 
                -- при отсутвии определяется по типу адресов в указанной группе
                -- если указан, то должен совпадать с типом адресов в группе, если и та указана
                -- если не указан и не указана группа, то выбираются еmail-адреса

 ,"group" : "идентификатор группы" 
            -- задает фильтр по списку подписчиков группы-фильтра или группы-списка (необязателен)

 ,"member.haslock" : "код" -- учёт состояния блокировки подписчика
                   -- пусто или отсутствует - не учитывать состояние блокировки
                   -- -1 - есть хоть какая-то блокировка       - не может получать письма или sms
                   --  0 - нет блокировки                      - может получать письма или sms
                   --  1 - заблокирован так как отписался      - не может получать письма или sms
                   --  2 - заблокирован так как не подтверждён - не может получать письма или sms
                   --  4 - заблокирован так как имеет фатальные ошибки доставки - не может получать письма или sms
                   --  прочие значения ( 3,5,6,7 ) - одновременное наличие нескольких указаных блокировок
                   --
                   -- формально этот параметр можно заменить дополнительными условиями в фильтре группы
                   -- но его использование позволяет 
                   --  * не меняя фильтра групп узнавать сколько в ней всего участников и сколько из них могут получать письма
                   --  * не создавая группы узнавать это же для всех свои адресов вообще
                   --  * выполнять запрос быстрее по сравнению с таким же условием в фильтре группы

 ,"format" : "идентификатор формата вывода" 
             -- если формат вывода не используется, то выводится только адрес (member.email) или номер телефона (member.cellphone)

 ,"sort" : "коданкеты.кодвопроса" 
             -- сортировка по указаному полю (не важно будет оно в итоговых данных или нет)
             -- если параметр отсутствует или пуст, то выдача идёт в неком внутреннем порядке
             -- сортировка по произвольной анкете/полю может резко увеличить время выполнения запроса
             -- так как для его выполнения надо отсортировать всю выборку
             -- но сортировка по ниже перечисленным полям практически не влияет на скорость выполнения
             --   member.id
             --   member.email     - при выборке адресов (email)
             --   member.cellphone - при выборке телефонов (msisdn)
             --   member.domain    - сортировка по домену и внутри него по адресу
             --                    - при выборке телефонов эквивалентно просто member.cellphone

  ,"sort.order" : "asc|desc" -- направление сортировки asc - по возрастанию (по умолчанию), desc - по убыванию

  ,"result" : "response|email|save" 
               -- cпособ возврата результата: (по умолчанию - response) 
               -- response - сразу (в ответе), в этом случае вызов синхронен
               -- email    - выслать на почту, в этом случае вызов асинхронный и вместо результата возвращается track.id
               -- save     - сохранить на сервере, в этом случае вызов асинхронный и вместо результата возвращается track.id

---- дополнительные параметры запроса зависящие от result

-- для response

  ,"page" : "номер страницы" -- при отсутвии выдаётся всь список. должны быть указаны оба параметра или ни одного

  ,"pagesize": "размер страницы" 

-- email или save
  ,"result.format" : "csv|xlsx" -- формат файла с данными (необязательно, по умолчанию csv)

  ,"caption" :"id|name" 
              -- в первой строке выводить заголовок, содержащий для каждой колонки код анкеты и вопроса (id)
              -- или их названия (name)
              -- если параметр отсутствует или пуст, то такая строка не добавляется

-- email

 ,email : [ e@mail1 ,e@mail2 ... ]   -- адреса получателей списка подписчиков

--

}

ответ


{

    <общие поля>

-- для result = email или save

  ,"track.id" : номер -- номер асинхронного запроса для отслеживания с помощью track.*

-- для result = response

  ,order : [ -- описание порядка колонок выводимых результатов

              {

                anketa      : код анкеты

               ,anketa.name : название анкеты

               ,quest       : код вопроса

               ,quest.name  : названи вопроса

              }

             ....

           ]

   ,list : [ -- по одной записи для каждого адреса

              [ значения запрошеных полей в порядке указанном в параметре ответа order ]

             ,[ значения запрошеных полей в порядке указанном в параметре ответа order ]

             ,[ значения запрошеных полей в порядке указанном в параметре ответа order ]

             .....

           ]

}

вверх

Количество подписчиков

Расчёт быстр если ведётся по "всем", по группам-спискам, по группам-фильтрам состоящим из групп-списков.

Расчёт по группе-фильтру может быть долог - в зависимости от сложности фильтра и общего числа подписчиков.

По умолчанию вызов синхронный. Используйте sync = 0, что бы сделать его асинхронным.

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


{

  "action" : "member.list.count" 

 ,"group" : "идентификатор группы" 
            -- задает фильтр по списку подписчиков группы
            -- если пусто или отсутствует, то подсчёт идёт по всем подписчикам

 ,"cache" : { параметры кэширования }

 ,"sync" : 0|1 -- изменение синхронности запроса. по умолчанию 1 - синхронный

}

ответ


{

  <общие поля>

-- для асинхронного запроса

,"track.id" : номер -- номер асинхронного запроса для отслеживания с помощью track.*

-- для синхронного запроса

 ,"obj" : {
            "total" : всего подписчиков

           ,"active" : количество подписчиков которые могут участвовать в рассылках писем или смс

           ,"locked" : уникальное количество заблокированных - не могут быть в рассылке.

           -- подробности по причинам блокировок (у одного подписчика может быть больше одной блокировки)

           ,"locked.unsubscribed" : в том числе заблокированные так как отписались

           ,"locked.confirm" : в том числе заблокированные так как не подтвердили внесение в базу

           ,"locked.stoplist" : в том числе заблокированные так как находятся в стоп-листе

           ,"locked.hardbounced" : в том числе заблокированные так как имеют фатальные ошибки доставки

           -- при подсчёте по всем подписчикам, дополнительно появляются поля

           ,"total.email" : всего именно email

           ,"total.msisdn" : всего именно телефонов

           ,"active.email" : способных получать именно адреса

           ,"active.msisdn" : способных получать именно телефоны

           }

}

вверх

Внесение списка подписчиков

Вызов member.import.probe предназначет для проверки что думает система импорта о ваших входных данных.

В ответ будет сообщено как произошло авто-определение кодировки (сharset), разделителя столбцов, определилась ли первая строка данных как строка конфигурации (firstline), и каким ответом каких анкет приписаны столбцы данных

Если вы знаете зарание ответы на эти вопросы, то можете сразу указать эти данные при вызове тогда выше указание будет иметь приоритет над авто-определением системы.

Вызов member.import реально импортирует данные, описаные выше параметры будет определены автоматически если их не указать сразу при вызове.

Вызов member.import асинхронный - получение положительного ответа означает, что задание на импорт поставлено в очередь. Это не означает успешность импорта - он может не состоятся из ошибок возникших позже. Следить за его исходом можно по возвращаемому номеру асинхронного запроса.

Обратите внимание: Все возможные проблемы (фатальные или нет - смотрите по cannot_import) связанные именно с импортом (настройки, данные) при вызове member.import.probe сообщаются только через параметр warnings. А параметр errors служит только для сообщения о проблемах вызова только с точки зрения API. При вызове же самого импорта, не фатальные ошибки остаются по прежнему в warnings, а фатальные переносятся в errors. Это может выглядеть не логично, но это так из-за назначения вызова member.import.probe - предупредить вас о возможных ошибках, а не выполнять само импортирование.


{

  "action" : "member.import" 

или 

  "action" : "member.import.probe" 

** данные импортирования

  ,"addr_type": "тип вносимых адресов подписчиков (email | msisdn)" 

и одни из источников данных:

   ,"users.list": адреса и данные для импортирования -- непосредственно в JSON или в СSV или XLSX
                                                     -- подробнее в разделе "Форматы данных для импортирования и Экспресс-Выпуска" 

или

   ,"users.url": "URL по которому находятся список адресов и данных для импортирования ftp:// или http(s)://" 
                  -- данные забираются в момент вызова

или

  ,"uid": "идентификатор уже загруженных данных" 
          -- возвращается после первого вызова member.import.probe, используется далее вместо cамих данных

** параметры (не обязательны)

  ,"charset": "кодировка символов ( koi8-r | cp1251 | utf-8 | utf-7 | utf-16 | mac-cyrillic )",

  ,"separator": "разделитель символов ("," | ";" | "|" | "t")", -- t - табуляция

  ,"firstline": "использовать первую строку как строку конфигурации ( 1 | 0 )",

  ,"caption":  [ -- по порядку следования столбцов в данных. приписывают каждый столбец одному вопросу одной анкеты

                -- при addr_type = email  колонка с адресом это анкета member вопрос email

                -- при addr_type = msisdn колонка с номером телефона это анкета member вопрос cellphone

                {

                 "anketa": "id  анкеты",

                 "quest": "id вопроса в анкете",

                  или 

                 "ignore": "1" -- игнорировать столбец

                 },

                ......

              ]

** пробный импорт

  ,"action": "member.import.probe",

** импорт

  ,"action": "member.import",

  ,"if_exists": "overwrite|ignore|error" -- обязателен
                -- что делать если адрес уже существует 
                -- overwrite - перезаписывать данные
                -- ignore    - не вносить
                -- error     - не вносить и считать строку ошибочной

  ,"newbie.confirm" : "подписчик должен подтвердить внесение в базу (1|0)",

                      -- по умолчанию - внесение без необходимости подтверждения подписчиком (0)

                      -- внесение email без подтверждения ограничено величиной member.noconfirm.rest
                      -- (узнать можно через sys.settings.get) при её исчерпании остальные адреса
                      -- вносятся с подтверждением.

                      -- если количество адресов в базе превышает member.tarif.limit, то величина member.noconfirm.rest
                      -- равна нулю, иначе она равна member.noconfirm.limit за вычетом количества уникальных email
                      -- адресов внесённых в текущем месяце

                      -- на внесение номеров телефонов параметр newbie.confirm не влияет (они всегда вносятся без подтверждения)

  ,"newbie.letter.confirm" : "номер шаблона информационного письма" 
                           -- высылается если новый адрес до этого отсутствовал в базе, и внесён с необходимостью подтверждения
                           -- если параметр пуст или отсутствует, то ничего выслано не будет
                           -- но необходимость подтвердить регистрацию никуда не денется и выслать письма вы сможете позже через member.sendconfirm
                           -- этот параметр НЕ зависит от newbie.confirm - если у вас кончился лимит внесения без подтверждения, то импорт
                           -- продолжит вносить уже с подтверждением и этот параметр будет использоваться 
                           -- при внесении номеров телефонов ни какие уведомления не высылаются
                           -- информационное письмо должно иметь заполненный адрес отправителя и не находиться на модерации

  ,"auto_group" : {
                  -- автоматически создать группу-список для импортируемых адресов
                  -- или дополнить любую существующую группу-список импортируемыми адресами

                  -- отсутствие всего параметра auto_group означает ни создавать новую ни пополнять существующую группу
                  -- наличие auto_group но без id и без name означает создание группы со стандартным кодом
                  -- и со стандартным названием. код созданый группы можно узнать используя возвращаемый номер track.id
                  -- и вызов track.get

                  "id" : "идентификатор группы-списка для пополнения" 
                         -- при отсутствии такой группы она создаётся автоматически
                         -- если параметр пуст или отсутствует то используется стандартный
                         -- код вида importYYYYYMMDDhhmmss

                 ,"name" : "название группы" 
                            -- название для создаваемой группы.
                            -- если параметр пуст или отсутвует то используется стандартное "Внесены <дата-время импорта>" 

                 }, 

   ,"clean_group" : 0|1 -- очищать (1) или нет (0) группу-список указанную в auto_group перед началом импортирования
                      -- позволяет не дополнять группу, а полностью заменять её участников
                      --
                      -- сначал проверяются все возможные причины по которым импорт может не начаться. если они есть, то импорт заканчивается ошибкой и очистка не происходит
                      -- иначе импорт начинается с очистки списка участников, а внесение новых происходит только после этого
                      --
                      -- не путайте удаление адреса из списка участников с удалением адреса из базы - это разные вещи
                      --
                      -- при одновременном импортировании в одну группу несколькими вызовами у которых хоть у одного clean_group=1 результат
                      -- зависит от порядка выполнения и может быть неожиданным. для однозначного результата всегда выполняйте импорт с cleangroup=1
                      -- только по завершении всех предыдущих импортов в ту же самую группу и не запускайте новый импорт в такую группу до завершения текущего.

   ,"format" : " id-формата" -- дополнить данные каждого вносимого адреса данным из формата
                             -- отсутствие или пусто - не дополнять

   ,"sequence.event" : 0|1 -- будет ли внесение/изменение данных вызывать срабатывание событийных действий
                           -- отсутствие или пусто - вызова событий не будет
   ,"result": [
               -- один или несколько адресов для высылки отчёта с результатами импорта
               -- обратите внимание на обязательность mailto:

               "mailto:your@email.tld" 
               ,.....
              ]
}

ответ


{

   <общие поля>

-- только для member.import.probe

  ,"uid": "идентификатор уже загруженных данных" 

  ,"charset": "кодировка символов ( koi8-r | cp1251 | utf-8 | utf-7 | utf-16 | mac-cyrillic )" 

  ,"separator": "разделитель символов ("," | ";" | "|" | "t")" 

  ,"firstline": "использовать первую строку как строку конфигурации ( 1 | 0 )" 

  ,"caption":  [ -- поля конфигурации

                {

               -- для колонки которой найдено соответствие анкете/вопросу

                 "name": "отображаемое имя колонки",

                 "anketa": "id  анкеты",

                 "quest": "id вопроса в анкете",

               -- для колонки которой не найдено соответствие

                 "name": "неопределенное начение из элемента строки конфигурации или null",

               -- для колонки для которой конфигурации задано игнорирование

                  "ignore" : 1

                 }

              ]

  ,"rows" : [ -- данные первых 15 значащих строк разбитые по полям с учёном указанного разделителя

             [ "колонка-1-строки-1" ,"колонка-2-строки-1" ,"колонка-3-строки-1" ]

            ,[ "колонка-1-строки-2" ,"колонка-2-строки-2" ,"колонка-3-строки-3"]

            .......

           ]

  ,"cannot_import" : 0|1  -- может ли быть выполнен импорт с текущими параметрами запроса
                         -- 1 - может
                         -- 0 - нет

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

   ,"warnings" : [ -- предупреждения о проблемах при анализе данных
                   -- в первую очередь проверяйте параметр cannot_import
                   -- если он показывает, что импорт невозможен, то тут содержится описание почему

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

                  { "id":"код ошибки", "explain": "доп. информация (скаляр, массив или хэш)" }

                  ....

                ]

-- только для member.import

  ,"queue_position" : "номер в очереди импортирования" 

  ,"track.id" : номер -- номер асинхронного запроса для отслеживания с помощью track.*

}

вверх

Пригласить подписчиков

Запрос с указанием списка по умолчанию синхронный.

Запрос с указанием группы по умолчанию асинхронный. Используйте sync = 1 если вам реально нужен ответ с описанием какие адреса как были обработаны.

Асинхронные запросы возвращают номер трекера для отслеживания.


{

 "action" : "member.sendconfirm" 

 ,"letter" : "код информационного письма" -- обязательно
                                          -- информационное письмо должно иметь заполненный адрес отправителя и не находиться на модерации

-- указание подписчиков одним из способов

 ,"email" : "адрес подписчика" 

или

 ,"list" : [

            "адрес подписчика" 

           ,"адрес подписчика" 

            ........

           ]

 ,"sync" : 0|1 -- изменение синхронности запроса. по умолчанию 1 - синхронный

или

 ,"group" : код группы, участникам которой будут высланы повторные напоминания о подтверждении регистрации

 ,"sync" : 0|1 -- изменение синхронности запроса. по умолчанию 0 - асинхронный

или

 ,"url" : ссылка на файл со списком адресов. по одному на строке. возможно сжатие zip.

 ,"sync" : 0|1 -- изменение синхронности запроса. по умолчанию 0 - асинхронный

или

 ,"stat.uni" : { -- адреса для обработки получаются из запроса Универсальной Cтатистики 

                 -- подразумевается unique = 1

               ,"filter" : [ условие выборки как у запроса в вызове stat.uni ]

               ,"cache"  : [ настройки кэширования как у запроса в вызове stat.uni ]

               ,"select" : [ ... ] -- используйте только если не подходит значение по умолчанию [ 'member.email' ]

               }
 ,"sync" : 0|1 -- изменение синхронности запроса. по умолчанию 0 - асинхронный
}

ответ


{

  <общие поля>

-- для асинхронного запроса

,"track.id" : номер -- номер асинхронного запроса для отслеживания с помощью track.*

-- для синхронного запроса

,"list" : {
           "адрес-1" : 0|1 -- результат высылки - 1 - выслано, 0 - нет (например адрес ошибочен или отсутствует или не нуждается в подтверждении)

          ,"адрес-2" : 0|1

           .................
          }
}

вверх

Массовое изменение данных подписчиков

Запрос с указанием списка по умолчанию синхронный.

Запрос с указанием группы по умолчанию асинхронный. Используйте sync = 1 если вам реально нужен ответ с описанием какие адреса как были обработаны.

Асинхронные запросы возвращают номер трекера для отслеживания.

Все попытки изменения системной анкеты member игнорируются, за исключением ответа error.

Установка member.error в "0" удаляет запись о проблемах доставки и снимает блокировку адреса из-за ошибок доставки если таковая была.


{

 "action" : "member.update" 

-- указание подписчиков одним из способов

 ,"email" : "адрес подписчика" 

или

 ,"list" : [

            "адрес подписчика" 

           ,"адрес подписчика" 

            ........

           ]

 ,"sync" : 0|1 -- изменение синхронности запроса. по умолчанию 1 - синхронный

или

 ,"group" : код группы к участникам которой будут применены изменения

 ,"sync" : 0|1 -- изменение синхронности запроса. по умолчанию 0 - асинхронный

или

 ,"url" : ссылка на файл со списком адресов. по одному на строке. возможно сжатие zip.

 ,"sync" : 0|1 -- изменение синхронности запроса. по умолчанию 0 - асинхронный

или

 ,"stat.uni" : { -- адреса для обработки получаются из запроса Универсальной Cтатистики 

                 -- подразумевается unique = 1

               ,"filter" : [ условие выборки как у запроса в вызове stat.uni ]

               ,"cache"  : [ настройки кэширования как у запроса в вызове stat.uni ]

               ,"select" : [ ... ] -- используйте только если не подходит значение по умолчанию [ 'member.email' ]

               }

 ,"sync" : 0|1 -- изменение синхронности запроса. по умолчанию 0 - асинхронный

-- общее для все способов указания

 ,"format" : код формата заполнения или универсального, из данных которого будут применены изменения

 ,"if_has" : что делать, если изменяемый пункт анкеты уже заполнен

             -- "overwrite" - заменить старое значение новым из формата

             -- "merge"     - объединить старое и новое значения

             -- "skip"      - оставить старое значение

 ,"if_hasnt" : что делать, если изменяемый пункт анкеты ещё не заполнен

             -- "set"  - заполнить указанным в формате значением

             -- "skip" - оставить пункт незаполненным

}

ответ


{

  <общие поля>

-- для асинхронного запроса

,"track.id" : номер -- номер асинхронного запроса для отслеживания с помощью track.*

-- для синхронного запроса

,"list" : {
           "адрес-1" : 0|1 -- результат изменения - 1 - изменение применено, 0 - нет (например адрес ошибочен или отсутствует)

          ,"адрес-2" : 0|1

           .................
          }

}

вверх

Форма подписки для сайта


{

 "action" : "decor.siteform" 

 ,"format" : "html" (в данный момент только одно это значение)

 ,"fields" : [ -- список вопросов в порядке, в котором они должны быть в форме

              {

                "anketa" : "код анкеты" 

               ,"quest"  : "код вопроса" 

             }

             .....

           ]

 ,"letter" : "код информационного письма, высылаемого после регистрации" (не обязательно)

 ,"redirect_to" : "полный урл для перенаправления после регистрации нового пользователя" (не обязательно)
                  -- если не указан, то будет использована глобальная настройка
                  -- redirect.member.join, если она заполнена
                  -- укажите значение "-" для отмены действия глобальной настройки

 ,"redirect_exists_to" : "полный урл для перенаправления если пользователь уже существует" (не обязательно)
                         -- если не указан, то будет использована глобальная настройка
                         -- redirect.member.join.exists, если она заполнена
                         -- укажите значение "-" для отмены действия глобальной настройки

}

ответ


{

  <общие поля>

 , "siteform" : "html код формы" 

}

вверх

Файлы изображений и отчетов

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

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

К хранилищу отчётов публичного доступа нет. Его файлы доступны только через API.

Параметр path используемый в вызовах это абсолютный путь по хранилищу начинающийся со слэша.

Cписок файлов

Если path указывает на файл, то возвращается информация только о нём - можно использовать для проверки существования этого файла.

Если path указывает на каталог, то возвращается список его файлов и подкаталогов.

Параметр domain указывает на используемую область - хранилище картинок (image) или хранилище отчётов (report)


{

 "action" : "rfs.list" 

 ,"domain" : "image|report" 

 ,"path" : "полный путь - каталог или файл" 

}

ответ


{

 <общие поля>

 ,"file" : [

            {

             "name" : название

             ,"path" : полный путь c названием

             ,"size" : размер

             ,"date" : дата изменения (в формате yyyy-mm-dd hh:mm:ss)

             ,"url" : публичная веб-ссылка для доступа. только если domain=image

            }

            ......... 

           ]

 ,"dir" : [

           {

            "name" : название

           ,"path" : полный пусть с названием

           }

           ......... 

          ]

} 

вверх

Получить файл


{

 "action" : "rfs.file.get" 

 ,"domain" : "image|report" 

 ,"path" : "полный путь с названием файла" 

 ,"encoding" : "желаемая кодировка данных в ответе" -- пусто или не указана - обычная Unicode/UTF-8
                                                    -- base64 - содержимое будет возвращено в base64
}

ответ


{

 <общие поля>

 ,"datа" : "содержимое файла" -- предполагается что файл двоичный и :
                              -- без заказа кодировки значение октетов 0x00 - 0xFF отображено как U+0000-U+00FF
                              -- при заказе base64 данные будет возвращены в base64

} 

Записать файл

Только для domain = image


{

 "action" : "rfs.file.put" 

 ,"domain" : "image" 

 ,"path" : "полный путь с названием файла (отсутствующие попутные подкаталоги НЕ создаются автоматически)" 

 ,"datа" : "содержимое файла" -- предполагается что файл двоичный и:
                              -- без указания кодировки значение октетов 0x00 - 0xFF заданы как U+0000-U+00FF
                              -- при указании base64 данные считаются закодированы в base64

 ,"encoding" : "кодировка данных" -- пусто или не указана - обычная Unicode/UTF-8
                                  -- base64 - содержимое date закодировано в base64
}

ответ


{

 <общие поля>

 ,"url" : публичная веб-ссылка для доступа. только если domain=image

} 

вверх

Удалить файл


{

 "action" : "rfs.file.delete" 

 ,"domain" : "image|report" 

 ,"path" : "полный путь с названием файла" 

}

ответ


{

 <общие поля>

} 

вверх

Cоздать каталог

Только для domain = image


{

 "action" : "rfs.dir.make" 

 ,"domain" : "image" 

 ,"path" : полный путь с названием каталога (отсутствующие попутные подкаталоги создаются автоматически)" 

}

ответ


{

 <общие поля>

 ,"url" : публичная веб-ссылка для доступа. только если domain=image

} 

вверх

Удалить каталог

Только пустой каталог

Только для domain = image


{

 "action" : "rfs.dir.delete" 

 ,"domain" : "image" 

 ,"path" : "полный путь с названием каталога" 

}

ответ


{

 <общие поля>

} 

вверх

Выпуски рассылки

Варианты выпуска рассылок

Все рассылки (и email и sms) выпускаются через вызов issue.send

Есть четыре варианта его использования.

Группа-спискок Группа-фильтр Экспресс-Выпуск Транзакционное письмо
Адреса получателей Зарнее созданые в базе список Динамический поиск по всей (!) базе адресов данные которых удовлетворяют фильтру Указываются при выпуске или забираются по ссылке Указывается при выпуске
Данные для персонализации Из базы Из базы Указываются при выпуске Из базы если не указаны при выпуске
Количество переменных Не ограничено Не ограничено Не ограничено Не ограничено
Шаблонизатор Продвинутый Продвинутый Продвинутый Продвинутый
Собственные функции-плагины к шаблонизатору Да Да Да Да
Сложные вложенные структуры данных Нет Нет Да Да, если указаные сразу
Подтверждение от владельца адреса Требуется Требуется Требуется Не обязательно
Разбор жалоб на спам Согласно договора Согласно договора Согласно договора С особым пристрастием
Лимит в месяц Не ограничено Не ограничено Не ограничено Первоначально равен максимально допустимому количеству адресов в базе. Далее в зависимости от репутации

Отослать выпуск

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

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

Данные персонализации могут быть дополнены внешними данными в процессе выпуска подробнее в разделе "Внешние данные для персонализации"

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


{
    "action" : "issue.send",

* параметры содержимого выпуска. Для Транзакционных писем - только указание черновика и прикреплённых файлов.

    "letter" : {

                * текст письма

                "subject" : "Тема письма" -- игнорируется для SMS

               ,"from.name" : "Имя отправителя" -- по умолчанию название аккаунта
                                                -- для SMS обязителен и имя отправителя должно быть в списке уже промодерированных имён (вызовы issue.smssender.*)

               ,"from.email" : "Адрес отправителя (email)" -- обязателен для email,  игнорируется для SMS

               ,"reply.email" : "Обратный адрес для ответа (email)" -- игнорируется для SMS

               ,"reply.name" : "Имя для обратного адреса для ответа" -- игнорируется для SMS

               ,"to.name" : "Имя получателя" -- в этом поле уместна персонализация для подстановки имени и фамилии получателя. игнорируется для SMS

               ,"message": {

                           -- для выпуска по email-адресам одна или обе версии письма

                           "html" : "html-версия письма" 

                          ,"text" : "текстовая версия письма" 

                          -- для выпуска по SMS

                           "sms"  : "sms cообщение" 

                          }

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

                "draft.id": "номер черновика содержимое которого даст выпуск", -- тип черновика определит вид и формат рассылки (email или sms)
                                                                               -- черновик должен иметь заполненный адрес отправителя

                * прикреплённые файлы письма. тип файла определяется по расширению. (игнорируется для SMS)

                -- следующие имена файлов зарезервированы для внутренних нужд и не должны использоваться
                --  message.text
                --
                --  index.html
                --  index.htm
                --  index.shtml
                --  index.shtm
                --
                --  attach.html
                --  attach.htm
                --  attach.shtml
                --  attach.shtm
                --
                --  message.html
                --  message.htm
                --  message.shtml
                --  message.shtm

               ,"attaches": [ 

                             { "name": "имя файла", "content": "содержимое файла" },

                             { "name": "имя файла", "content": "содержимое файла" },

                             { "url": "url откуда заборать" },

                             { "url": "url откуда заборать" },

                             ...

                            ]
               }

* параметры способа выпуска. Тестовый выпуск не доступен для Экспресс-Выпуска, Транзакционных писем и SMS

    "group" : "id группы | masssending - если это Экспресс-Выпуск | personal - Транзакционное письмо",
              -- старый параметр gid переименован в group для совместимости с другими вызовами
              -- можно продолжать указывать gid - он будет использоваться при отсутствии group

    "mute": "не высылать уведомление об успешном постановке выпуска в очередь на обработку (1 - да|0 - нет)",

    "sendwhen": "Когда выпустить (now - сейчас | later - отложенный | delay - задержаные | save - отложить на хранение | test - тестовый)",

при отложеном выпуске (later) указывается дополнительно дата, час и минута не ранее которых выпускать 

    -- Если на дату выпуска уже запланирован выпуск по такой же группе, то НОВОЕ ЗАДАНИЕ ЗАМЕНИТ СОБОЙ СТАРОЕ.
    -- ИСКЛЮЧЕНИЕ - Экспресс-Выпуск и Транзакционные Письма.
    -- Выпуски по этим группам могут быть запланированы на одну и ту же дату в любом количестве.

    "later.time" : "YYYY-MM-DD hh:mm" 

при задержаном выпуске (delay) указывается дополнительно на сколько минут задержать выпуск

    -- Так как "задерка" реализуется созданием отложенного выпуска на "текущее время + delay.time",
    -- то действуют ТАКИЕ ЖЕ ОГРАНИЧЕНИЯ на одновременные выпуски одной рассылки, что и при использовании later.time

    "delay.time" : "mm" 

при выпуске теста (test) указываются адреса получателей. не более пяти.

    "mca": [

            "e@mail.1",

            "e@mail.2",

             ......

           ]

* параметры преобразования ссылок для учёта перехода по ним (игнорируются для SMS)

    "relink" : 0|1 -- Преобразовывать ссылки автоматически
                   -- 1 - да, 0 - нет

    "relink.param" : {

                      "link" : 0|1 -- преобразовывать ссылки тега <A>
                                   -- 1 - да, 0 - нет

                      "image": 0|1 -- преобразовывать ссылка на внешние картинки для тега <IMG>
                                   -- и фоновых изображений в <BODY> и <TABLE>
                                   -- 1 - да, 0 - нет

                      "test": 0|1 -- проверять существование адресов
                                  -- при включённой проверке выпуск не выйдет если ссылка
                                  -- не действительна (ответ не 200, не 301 и не 302)
                                  -- 1 - да, 0 - нет
                     }

если relink = 1, а какой то из параметров relink.param не указан, то считается что: link=1, image=0 и test=1 соответственно.

    "link.qsid": "Параметр для отслеживания переходов по ссылкам" 
                  -- добавляется ко всем ссылкам через query-string.
                  -- значение должно быть уже url-encoded

* список получателей для Экпресс-Выпуска

    "users.list": адреса и данные персонализации  -- непосредственно в JSON (два варианта) или в СSV или XLSX
                                                  -- подробнее в разделе "Форматы данных для импортирования и Экспресс-Выпуска" 
                                                  -- данные персонализации могут быть дополнены внешними данными в процессе выпуска
                                                  -- подробнее в разделе "Внешние данные для персонализации" 

или

    "users.url": "url"  -- по которому находятся список адресов с данными для персонализации ftp:// или http(s)://" 
                        -- данные персонализации могут быть дополнены внешними данными в процессе выпуска
                        -- подробнее в разделе "Внешние данные для персонализации" 

* параметры списка получателей для Экпресс-Выпуска

    "only_unique": 0|1 -- Следить за уникальностью адресов в списке
                       -- 1 - да,  повторно встреченные адреса исключаются из выпуска
                       -- 0 - нет, повторно встреченные адреса участвуют в рассылке

* получатель для Транзакционных писем

    "email" : "адрес получателя" -- данные для персонализации берутся из базы
                                 --
                                 -- данные персонализации могут быть дополнены внешними данными в процессе выпуска
                                 -- подробнее в разделе "Внешние данные для персонализации" 
                                 --
                                 -- при этом способе указания получателя он сразу проверяется на возможность отсылки ему письма
                                 -- (синтаксическая верность адреса, стоп-лист, ошибки доставки, отписался) и вы сразу получите в ответ описание ошибки.
                                 -- при  других способах задания получателя, проверка на возможность выпуска происходит
                                 -- только при формировании рассылки

или

    "users.list": один адрес и данные персонализации  -- непосредственно в JSON (два варианта) или в СSV или XLSX
                                                      -- подробнее в разделе "Форматы данных для импортирования и Экспресс-Выпуска" 
                                                      -- при указании только адреса данные персонализации берутся из базы
                                                      -- при нескольких адресах используется только первый
                                                      -- данные персонализации могут быть дополнены внешними данными в процессе выпуска
                                                      -- подробнее в разделе "Внешние данные для персонализации" 

* Для email ограничение на частоту отправки
  --
  -- все ограничения касаются только первой попытки отправки сообщения.
  -- если сообщение не принято сразу, то ограничения могут не быть соблюдены.

 ,"tz_limit" {

   "default" : {

      -- ограничение на частоту отправки сообщений
      -- за rate минут будет передано в доставку в не более number сообщений
      --
      -- если параметр отсутствует или пуст или обе величины равны 0,
      -- то ограничения нет

      "batch" : { -- или отсутствуют или указаны оба значения
                 "number" : "сколько cообщений" -- обязательно, >= 0
                ,"rate" : "за сколько минут"    -- обязательно, >= 0
                }

     }
  }

* Для sms ограничение на частоту и время отправки
  --
  -- все ограничения касаются только первой попытки отправки сообщения в sms-центр.
  -- если sms-центр не принял сообщение сразу или принял, но внутри себя
  -- по какой-то причине не пытался доставить сразу, то ограничения могут не быть соблюдены.

  -- допустимы временные зоны относительно GMT от -23 до +23 и default. знаки "+" и "-" обязательны.

  -- временная зона телефонного номера сотовой связи определяется по базе Федерального Агентства Связи.

 ,"tz_limit" {

   "default" : { -- настройки по умолчанию для не описанных зон или отсутствующих описаний с описанной зоне

      -- ограничение на частоту отправки сообщений
      -- за rate минут будет передано в доставку в sms-центр не более number сообщений
      --
      -- если параметр отсутствует или пуст или обе величины равны 0,
      -- то ограничения нет

      "batch" : { -- или отсутствуют или указаны оба значения
                 "number" : "сколько cообщений" -- обязательно, >= 0
                ,"rate" : "за сколько минут"    -- обязательно, >= 0
                }

      -- ограничение по времени отправки
      -- *все даты и часы по местному времени временной зоны !!!*

     ,"time" : {
                 "dt" : "YYYY-MM-DD" -- отправка начнётся не ранее указанного дня.
                                     -- если не указано или пусто или в прошлом,
                                     -- то используется текущая даты зоны MSK/MSD

                ,"hour" : [ час, час, час, ... ] -- разрешённые часы местного времени (0-23) в которые будет осуществлять отправка 
                                                 -- если не указано или пусто, то разрешены часы с 9 по 21
                                                 -- подумайте, стоит ли расширять этот диапазон на утреннее и ночное время !
                                                 --
                                                 -- порядок следования часов важен для первого дня отправки !
                                                 -- запись
                                                 --        {
                                                 --         "dt"   : "2012-09-17" 
                                                 --        ,"hour" : [ 20, 21, 22, 23, 06 08 07 ]
                                                 --        }
                                                 -- задаёт разрешённые часы с 6, 7, 8, 20, 22 и 23 и начало рассылки 17го декабря
                                                 -- но именно 17го она начнётся не в 6 часов, а в 20, так как 20 указано первым
                                                 -- а 18го и далее - начиная с 6
                                                 --
                                                 -- разрешается весь час - последняя отправка в нём может быть
                                                 -- даже в 59 минут 59 секунд, что может выглядеть как уже "не разрешённый" час
                                                 -- т.к. сообщение будет доставлено уже явно в следующем часе.
                }

   }

  ,"+4" : { -- ограничение для временной зоны GMT+4

       -- полностью отсутствующий или пустой компонент берётся из default

      "batch" : { .... } -- используйте 0 для rate и number что-бы снять ограничение из default

     ,"time"  : { .... } -- *дата и часы по местному времени временной зоны !!!*
                         --
                         -- если задано только dt, то действуют стандартные разрешённые часы с 9 по 21
                         -- подумайте, стоит ли расширять этот диапазон на утреннее и ночное время !
                         --
                         -- если задано только time, то то используется текущая даты зоны MSK/MSD
                         --
                         -- если не задано ни dt ни time, то действуют ограничения из default

   }

  ,"-2" : { -- ограничение для зоны GMT-2
    ..........
  }

  ..........

 }

* данные произвольной структуры одинаковые для всех подписчиков
 --
 -- эти данные будут доступны для шаблонизатора выпуска как переменные с именем совпадающим с именем ключа хэша

 -- эти данные могут быть дополнены внешними данными в процессе выпуска - подробнее в разделе "Внешние данные для персонализации" 

 -- в настоящий момент зарезервированы имена:
 --   anketa - данные о конкретном подписчике
 --   lenta  - данные для подстановки rss/atom каналов
 --   date   - интерфейс к функциям работы с датой и временем
 --   form   - форма опроса

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

,"extra" : {
            "Key1" : [ 12, 34 ,56 ] -- пример использования [% Key1[2] %]

           ,"City" : { -- пример использования [% City.rus.long %]
                      "rus" : {
                               "long"  : "Санкт-Петербург" 
                              ,"short" : "СПБ" 
                     ,"eng" : {
                               "long"  : "Leningrad" 
                              ,"short" : "LED" 
                     }

           }

}

ответ

{

    <общие поля>

   ,"track.id" : номер -- номер асинхронного запроса для отслеживания с помощью track.*

}

Список отложенных выпусков


{

  "action" : "issue.later.list",

 ,"from" : "YYYY-MM-DD" -- от даты (не обязательно)

 ,"upto" : "YYYY-MM-DD" -- до даты (не обязательно)

 ,"group" : [ -- фильтр по группам (не обязательно)

               код-группы-1

              ,код-группы-2

               ... 

             ] 

 ,"format" :  "email|sms|html|text"  -- фильтр по формату (не обязательно).
                                      -- email это "html или text" 
}

ответ


{

  <общие поля>

 ,"list" :  [

              {

                 "id" : "код выпуска" 

                ,"create.date" : "YYYY-MM-DD hh:mm:ss" -- дата постановки задания

                ,"status" : "статус задания" -- hold  - отложено на хранение
                                             -- later - отложено для выпуска
                                             -- error - ошибка выпуска

                -- если статус "отложено из-за ошибки" 

                ,"status.reason" : код ошибки

                -- если статус "отложено для выпуска" 

                ,"sendwhen" : "later" -- всегда later, так как вы смотрите отложенные выпуски
                ,"later.time" : "YYYY-MM-DD hh:mm" -- запланированная дата выпуска

-- базовые настроки выпуска

                ,"group" : код группы для которой отложен выпуск

                ,"letter" : {

                             ,"subject" : "тема выпуска" -- для sms совпадает с именем отправителя

                             ,"draft.id" : код черновика используемого для выпуска

                             ,"from.email" : адрес отправителя
                             ,"from.name" : имя отправителя
                            }

-- следующие параметры заменены новыми для совместимости с issue.send, но некоторое время ещё будут выдаваться
--  "format" - параметр не покрывает всех варинатов. определяйте форматы по message
--  "name"   - совпадает c letter::subject
--  "draft.id"  - совпадает с letter::draft.id
--  "issue.date" - cовпадает с later.time

              },

             ...

            ]

}

Прочитать отложенный выпуск


{
 "action" : "issue.later.get",

 "id" : "код выпуска",
}

ответ


{

  <общие поля>

 ,"obj" : {
                 "id" : "код выпуска" 

                ,"create.date" : "YYYY-MM-DD hh:mm:ss" -- дата постановки задания

                ,"status" : "статус задания" -- hold  - отложено на хранение
                                             -- later - отложено для выпуска
                                             -- error - ошибка выпуска

                -- если статус "отложено из-за ошибки" 

                ,"status.reason" : код ошибки

-- следующие параметры одноимённы c issue.send и значения obj можно использовать для создания нового выпуска
-- необходимо будет добавить action
-- уточнить значения sendwhen и later.time
-- и настоятельно рекомендуется удалить "лишнее" : id, create.date, status, status.reason, format, name, issue.date
-- но для просто смены даты выпуска или отсылки немедленно используйте специализированный выпуск issue.later.send

-- при использовании выпуска по черновику (указан draft.id) параметры message, subject, from.name, from.email, reply.email, reply.name и to.name
-- берутся из черновика и заполняются только для справки

-- при выпуске по черновику перед вызовом issue.send необходимо проверить параметры
-- subject, from.name и from.email черновика, так как они в черновике не обязательны

                ,"group" : код группы для которой отложен выпуск

                ,"letter" : {

                             "from.email" : адрес отправителя
                            ,"from.name" : имя отправителя

                            ,"reply.email" : обратный адрес для ответа
                            ,"reply.name" : имя для обратного адреса для ответа

                            ,"to.name" :  имя получателя

                            ,"subject" : тема выпуска -- для sms совпадает с именем отправителя

                            ,"message": { тексты пуска }

                            ,"attaches" : [ прикрепляемые файлы ] 

                            ,"draft.id" : код черновика используемого для выпуска
                            }

                ,"relink" : отслеживание переходов
                ,"relink.param" : параметры отслеживания

                ,"link.qsid" : трекинг ссылок на стороне клиента

                ,"users.list" : список получателей
                ,"users.url" : адрес списка получателей
                ,"email" : получатель
                ,"only_unique" : подавление дублей

                ,"tz_limit" : { параметры доставки по временным зонам }

                ,"extra" : { дополнительные параметры }

                ,"mute" : отчёт о выпуске

                -- если статус "отложено для выпуска" 

                ,"sendwhen" : "later" -- всегда later, так как вы смотрите отложенные выпуски
                ,"later.time" : "YYYY-MM-DD hh:mm" -- запланированная дата выпуска

-- следующие параметры заменены новыми для совместимости с issue.send, но некоторое время ещё будут выдаваться
--  "format" - параметр не покрывает всех варинатов. определяйте форматы по message
--  "name"   - совпадает c letter::subject
--  "draft.id"  - совпадает с letter::draft.id
--  "issue.date" - cовпадает с later.time

         }
}

Изменение даты выхода отложенного выпуска

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

Исключение - Экспресс-Выпуск и Транзакционные Письма. Выпуски по этим группам могут быть запланированы на одну и ту же дату в любом количестве.


{

 "action" : "issue.later.send",

 "id" : "код выпуска",

 "issue.date" : "YYYY-MM-DD hh:mm" -- Новая дата выпуска
                                   -- Если параметр отсутствует или указана дата в прошлом,
                                   -- то выпуск выйдет сразу

}

ответ


{

  <общие поля>

}

Удаление отложенного выпуска


{

 "action" : "issue.later.delete",

 "id" : "код выпуска" 

}

ответ


{

  <общие поля>

}

Список имён sms-отправителей

{

  "action" : "issue.smssender.list" 

}

ответ

{

 <общие поля>

,"list" : [

            {

             "id" : "уникальный идентификатор" 

            ,"name" : "отправитель" 

            ,"onmodera