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

Две стороны: приложение на телефоне забирает очередь и отчитывается, ваш сайт или CRM кладёт в неё сообщения через API.

Как это устроено

  1. Вы регистрируетесь, подтверждаете почту и в разделе «Телефон» подключаете телефон по QR-коду.
  2. На Android-телефоне с вашей SIM стоит приложение «СМС-шлюз» (ru.teamcraft.smsgate). Страница, открытая на телефоне по QR-коду, передаёт приложению адрес очереди и одноразовый код; приложение меняет код на токен, вы нажимаете «Сохранить» и включаете рассылку.
  3. Вы указываете номер SIM (его видят клиенты) и проверяете связь тестовым СМС: телефон отправляет его сам себе. Ушло — телефон умеет слать; вернулось входящим на тот же телефон — номер подтверждён (приложение узнаёт свою строку и сообщает серверу). Отправка открыта с момента подключения телефона: подтверждение — значок, не условие.
  4. Приложение 1.38+ забирает очередь по сигналу линии и дополнительно проверяет её раз в 5 минут, отправляет СМС и сообщает результат по каждому. Всё идёт по HTTPS, токен — только в заголовке.
  5. Ваша система кладёт сообщения в очередь через API с ключом из раздела «API», или вы делаете это вручную в разделе «Сообщения».

Когда телефон подключают заново или меняют номер SIM в кабинете, значок подтверждения снимается — проверка связи повторяется по желанию, приём сообщений не останавливается.

Лимиты и правила

  • Час. По тарифу: «Бесплатный» — до 15 СМС в час на номер, «Про» и «Максимальный» — до 28 (ограничение Android: 30 частей СМС за 30 минут на приложение; ночью на «Максимальный» — 60). Считает сервер — по числу передач сообщения телефону. Паузу «не чаще 1 СМС в минуту» держит само приложение, сервер сообщает её в поле min_gap_sec. Это защита номера от блокировки оператором.
  • Сутки по тарифу. «Бесплатный» — 10 сообщений, «Про» — 200, «Максимальный» — без квоты (до 3 номеров, с каждого — сколько позволяет темп). Сутки календарные, с 00:00 по московскому времени. Сообщение засчитывается, когда телефон впервые его забрал, и остаётся засчитанным при любом исходе. Автоповтор и «Повторить» после ошибки квоту не тратят. Тестовые СМС проверки номера не считаются.
  • Страховочный потолок. Передач телефону за 24 часа, считая повторные и неудачные: «Бесплатный» — 100, «Про» — 300, «Максимальный» — 1500 на номер; у приложения до 1.32 — 100 на любом тарифе. Если телефон несколько часов подряд не может отправить — нет денег на SIM, нет сети, — потолок исчерпается и без отправленных СМС.
  • Получатель. Одному номеру — не больше 3 сообщений за 24 часа. Номера — российские мобильные.
  • Текст. До 4 частей СМС: 268 знаков кириллицей или 612 латиницей.
  • Содержание. Только служебные уведомления людям, с которыми у вас есть отношения: заказы, записи, доставка. Реклама запрещена условиями.
  • Хранение. Тексты и номера получателей хранятся 30 дней после конечного статуса, потом затираются.

Сообщение, переданное телефону, но не подтверждённое сетью, получает статус «неизвестно, дошло ли»: сервер и телефон его повторно не шлют, решение за вами.

API для вашей системы

Базовый адрес — https://smsflake.snow3.ru/api/v1. Ключ передаётся в заголовке Authorization: Bearer fs_live_… или X-Api-Key; в адресе запроса ключа быть не должно. Тело и ответы — JSON в UTF-8. Время в ответах — московское.

Поставить сообщение в очередь

POST …/messages
curl -X POST "https://smsflake.snow3.ru/api/v1/messages" \
  -H "Authorization: Bearer fs_live_…" \
  -H "Content-Type: application/json" \
  -d '{"to": "+79990000000",
       "text": "Заказ 127001 прибыл в пункт выдачи",
       "ref": "order-127001"}'
PHP · curl
<?php
$url = 'https://smsflake.snow3.ru/api/v1/messages';
$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('FREESMS_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'to' => '+79990000000',
        'text' => 'Заказ 127001 прибыл в пункт выдачи',
        'ref' => 'order-127001',
    ], JSON_UNESCAPED_UNICODE),
]);
$answer = json_decode(curl_exec($ch), true);
Python · requests
import os, requests

r = requests.post(
    "https://smsflake.snow3.ru/api/v1/messages",
    headers={"Authorization":
             "Bearer " + os.environ["FREESMS_KEY"]},
    json={"to": "+79990000000",
          "text": "Заказ 127001 прибыл в пункт выдачи",
          "ref": "order-127001"},
    timeout=10,
)
print(r.status_code, r.json())
Ответ · HTTP 201
{
  "ok": true,
  "id": 42,
  "status": "queued",
  "parts": 1,
  "duplicate": false
}

Поля запроса: to — номер получателя в любом написании, text — текст, ref — ваша метка сообщения (необязательно, до 120 знаков), allow_repeat — true, если одинаковый текст тому же номеру отправляется намеренно, slot — с какого номера отправить (1, 2 или 3; на тарифе с несколькими номерами; без поля номер выбирает сервис: тот, с которого этому получателю писали в последний раз, иначе наименее загруженный из готовых).

Повторы. Запрос с той же меткой ref второго сообщения не создаёт: в ответ придёт первое, с кодом 200 и "duplicate": true. Одинаковый текст на тот же номер в течение 10 минут тоже считается повтором — с меткой или без неё; новая метка при этом не занимается. Сверку текста снимает "allow_repeat": true — так отправляют коды подтверждения.

Остальные запросы

GET  https://smsflake.snow3.ru/api/v1/messages/42
GET  https://smsflake.snow3.ru/api/v1/messages?status=queued&since=0&limit=50
POST https://smsflake.snow3.ru/api/v1/messages/42/cancel
GET  https://smsflake.snow3.ru/api/v1/status
GET  https://smsflake.snow3.ru/api/v1/replies?since=0&limit=50

Входящие принимаются на всех тарифах. На бесплатном тарифе text содержит первые 10 символов после маскировки цифр; на платных — полный маскированный текст. replies_enabled обозначает включённый приём на сервере; preview_chars — 10 на бесплатном тарифе, null на платных. То же ограничение действует для MO-уведомлений.

GET …/messages/42
Одно сообщение: id, status, to, text, parts, ref, source, error, attempts, created_at, pulled_at, acked_at.
GET …/messages
Список по возрастанию номера: status — фильтр, since — номер, после которого читать, limit — до 200.
POST …/cancel
Отмена, пока телефон не забрал сообщение, а также сообщения со статусом failed — до автоповтора.
GET …/replies
Ответы клиентов — входящие СМС от номеров, которым вы писали за последние 30 дней (все тарифы, приложение 1.32 и новее). По возрастанию номера: since — номер, после которого читать, limit — до 200. У ответа: id, from, text, received_at, message_id — ваше сообщение, на которое отвечают, slot. Текст хранится 30 дней, затем стирается (wiped: true). replies_enabled — включён ли приём ответов на сервере. Опрашивайте не чаще раза в минуту: лимит 60 запросов в минуту общий с отправкой. В GET …/messages/42 — replies_count.
GET …/status
Телефон, очередь и лимиты. В phone.sim_confirmed — вернулось ли тестовое СМС на телефон (номер подтверждён; на приём сообщений не влияет). В limits: plan — тариф, hour — передачи телефону за последний час, day — квота тарифа за сутки ("max": null — без квоты), attempts — страховочный потолок за 24 часа, can_enqueue — сколько сообщений очередь ещё примет. Потолок на получателя, повторы и неподтверждённый номер SIM в can_enqueue не учтены.

Статусы сообщения

queued
В очереди. Если в ответе заполнено pulled_at — сообщение уже передано телефону и отменить его нельзя.
sent
Сеть оператора приняла СМС. Отчёт о доставке на телефон получателя приложение не запрашивает.
failed
Не ушло: нет сети, нет денег на SIM. Сервис сам вернёт сообщение в очередь через 30 минут.
rejected
Негодное сообщение или 5 ошибок подряд. Само не повторится; вернуть в очередь можно в кабинете.
unknown
Могло уйти, но подтверждения нет. Сервис и телефон его не повторяют: решение за человеком, в кабинете.
cancelled
Отменено вами или не забрано телефоном за 3 суток. Так же помечается сообщение, вместо которого отправлен повтор.

Коды отказов

Отказ — это JSON с полями ok: false, code и error. В code — причина для программы, в error — она же словами для человека. Отказ со статусом 422 значит: запрос понят, но отклонён, в очередь ничего не записано.

→ 422 {"ok":false,"code":"recipient_cap","error":"потолок на номер: 3 в сутки"}
→ 422 {"ok":false,"code":"daily_cap","error":"…","reason":"plan_quota","retry_at":"2026-10-01T00:00:00+03:00"}
400bad_json
Тело запроса — не JSON-объект.
401unauthorized
Ключа нет в заголовке, он не принят или отозван.
403email_unconfirmed
Почта аккаунта не подтверждена.
403blocked
Аккаунт заблокирован или удаляется.
404not_found
Нет такого адреса или такого сообщения.
405method_not_allowed
Адрес есть, но метод запроса не тот.
409already_pulled
Отмена не прошла: сообщение уже передано телефону.
409already_final
Отмена не прошла: сообщение уже в конечном статусе.
422bad_phone
Номер получателя не распознан: нужен российский мобильный, +7 и 10 цифр. Поле должно быть строкой.
422bad_text
Текст пустой, длиннее 612 знаков или не укладывается в 4 части СМС.
422bad_ref
Метка ref длиннее 120 знаков или совпала со служебной меткой сервиса.
422bad_slot
Поле slot: такого номера в кабинете нет, или он сейчас не готов к отправке (не на связи, упёрся в потолок).
422bad_status
В запросе списка указан статус, которого нет.
422no_phone
Телефон не подключён.
422phone_revoked
Телефон отвязан от кабинета.
422recipient_cap
Этому номеру за последние 24 часа уже поставлено 3 сообщения.
422daily_cap
Очередь больше не принимает сообщения. Причина — в поле reason, срок — в retry_at, о них ниже.
422stopword
Текст похож на рекламу.
422busy
Очередь занята другим запросом этого аккаунта — повторите через секунду.
429rate_limited
Больше 60 запросов в минуту на ключ или слишком много отказов по ключу. Срок — в заголовке Retry-After.
500internal
Ошибка на стороне сервиса. Запрос можно повторить.

Отказ daily_cap: причина и срок

У отказа daily_cap в ответе ещё два поля: reason — что именно упёрлось, и retry_at — когда место начнёт освобождаться (ISO-8601 с часовым поясом; null — срок назвать нельзя).

plan_quota
Выбрана квота тарифа за сутки. retry_at — ближайшая полночь по московскому времени.
queue_full
Всё место — квоту тарифа или страховочный потолок — занимают сообщения, которые ещё ждут отправки. Время место не освободит: проверьте, что телефон на связи, или отмените часть ждущих. retry_at — null.
safety_cap
Страховочный потолок передач телефону за 24 часа (по тарифу: 100 / 300 / 1500). retry_at — когда истечёт самая старая передача.
app_outdated
Тот же потолок, но у приложения на телефоне версия до 1.32: оно держит свои 15 в час и 100 за 24 часа на любом тарифе. Обновите приложение. retry_at — когда истечёт самая старая передача.
create_cap
За 24 часа создано 300 сообщений, считая отменённые. retry_at — через сутки после самого старого из них.

Контракт телефона

Для тех, кто разбирается, как приложение говорит с сервисом. Адрес очереди — https://smsflake.snow3.ru/gate.php, токен телефона — в заголовке Authorization: Bearer или X-Auth-Token.

GET  https://smsflake.snow3.ru/gate.php?action=pull&limit=10
→ {"ok":true,"messages":[{"id":42,"phone":"+79990000000","text":"…"}],"limits":{"hour":{"used":3,"max":28},"day":{"used":3,"max":200},"attempts":{"used":4,"max":300},"next_at":null,
    "gap":{"day_sec":60,"night_sec":60,"night_from":23,"night_to":8,"tz":"Europe/Moscow"},"plan":"pro","replies":true,"app_outdated":false}}
→ {"ok":true,"messages":[],"throttled":true,"queued_left":4,"limits":{…,"next_at":"2026-09-05T12:30:00+03:00"}}

POST https://smsflake.snow3.ru/gate.php?action=ack     {"results":[{"id":42,"status":"sent"},{"id":43,"status":"failed","error":"нет сети"}]}
POST https://smsflake.snow3.ru/gate.php?action=log     text/plain — отчёт приложения для разбора
GET  https://smsflake.snow3.ru/gate.php?action=wait&timeout=45  линия ожидания: сервер держит запрос и отвечает, как только есть что забрать
→ {"ok":true,"ready":true}  ·  по таймауту {"ok":true,"ready":false}  ·  линий нет {"ok":true,"ready":false,"busy":true,"retry_sec":60}
POST https://smsflake.snow3.ru/gate.php?action=sim_seen {"id":42} — своё тестовое СМС (строка выдачи с "sim_test":true) пришло входящим на этот телефон → {"ok":true,"confirmed":true}
POST https://smsflake.snow3.ru/gate.php?action=claim   {"code":"…"} без токена — обмен одноразового кода подключения на токен
POST https://smsflake.snow3.ru/gate.php?action=inbound {"messages":[{"key":"sha1","from":"+7…","text":"…","at":1700000000000}]} — ответы клиентов (тарифы с ответами)
→ {"ok":true,"accepted":["…"],"duplicates":[],"bad":[],"replies":true}

Сообщение, за которое телефон не отчитался, выдаётся снова, но не раньше чем через 30 минут после выдачи: пачку из 10 сообщений телефон отправляет до четверти часа, и выдавать её заново раньше нельзя. Повторную отправку предотвращает журнал приложения.

С версии 1.38 телефон дополнительно проверяет очередь раз в 5 минут службой переднего плана. Системная задача раз в 15 минут остаётся страховкой восстановления службы. С версии 1.33 приложение также держит линию ожидания: запрос action=wait висит до 45 секунд, сервер отвечает, как только появилось сообщение, и телефон забирает его сразу — в кабинете это выглядит как отправка через несколько секунд после постановки. Линий одновременно — 1; когда все заняты, телефон опрашивает очередь сам раз в минуту. Ответ pull несёт блок wait, пока линии включены; приложение 1.38+ без него продолжает проверять очередь раз в 5 минут. Для работы службы нужны включённая рассылка, интернет и исключение из энергосбережения; если Android остановил службу, проверка может задержаться.

Когда очередь непуста, но выдавать нечего — из-за лимита или из-за этой паузы, — ответ приходит с "throttled": true, queued_left и сроком в limits.next_at. Пустой ответ без throttled означает, что очередь действительно пуста. Ошибки — JSON {"ok":false,"error":"…"} с кодами 400, 403 и 429.

Приложение

Цифры во входящих скрыты. Приложение 1.39+ заменяет каждую группу цифр, включая цифры других алфавитов, на [xxxxxx] до сохранения и передачи. Сервер также маскирует новые ответы; ранее сохранённые ответы маскируются при показе в кабинете, выдаче API и уведомлениях о входящих. Например, Ответ 123456 выглядит как Ответ [xxxxxx]. Для полного текста смотрите обычные «Входящие» телефона. Проверка своего тестового СМС для подтверждения номера выполняется до маскировки; это СМС не попадает в ответы клиентов. Запрет банковских сообщений сохраняется.

Скачать «СМС-шлюз»: файл APK. Нужен Android 8.0 или новее.

Приложение не читает папку «Входящие», контакты и другие данные. Из разрешений ему нужны отправка СМС, уведомления и исключение из энергосбережения; приём СМС — только если вы включили передачу ответов клиентов в его настройках: тогда с версии 1.32 оно передаёт в кабинет входящие только от номеров, которым вы писали за последние 30 дней, остальные не сохраняет.

Запрет на чтение. Раньше всех проверок — и на телефоне, и на сервере (action=inbound отвечает на такое bad) — отбрасываются: СМС с коротких номеров (любой короче 10 цифр), от буквенных имён отправителей (так подписаны банки, платёжные и государственные сервисы, мессенджеры и соцсети, операторы связи, магазины и доставка — база из 455 имён, 2026-09-30), с городских, 8-800 и зарубежных номеров, а также тексты, похожие на код подтверждения (число из 4–8 цифр рядом со словом «код», «пароль», «PIN», «CVC», «никому не сообщайте»), даже от знакомого номера. Такое СМС телефон не покидает и следа не оставляет — растёт только счётчик в отчёте приложения. Читаются только ответы с обычных российских мобильных номеров, которым вы писали.

Обновления ставятся поверх. Удалять приложение не нужно: в нём журнал отправленных, по нему телефон не отправляет одно сообщение дважды.

Отчёт для разбора: в приложении нажмите «Отчёт», затем «Отправить на сервер» — он появится в разделе «Отчёты приложения», его увидит и поддержка. Как подключить телефон и отключить энергосбережение — в помощи.

Совместимость с API МегаФона

Если ваша система уже использует HTTP API МегаФона, замените адрес на https://smsflake.snow3.ru/sms/v1/sms. Авторизация — HTTP Basic: логин — почта аккаунта, пароль — действующий ключ fs_live_…. Пароль от кабинета для API не подходит.

СМС отправляется с номера вашей SIM. Поле from проверяется, но имя отправителя не подменяется. Статус delivered означает «отправлено с телефона», без подтверждения доставки от оператора.

Сообщения из обоих API идут в автоматическую очередь: с 10:00 включительно до 19:00 исключительно по часовому поясу отправителя, с интервалами 60–180 секунд. Пояс задаётся в настройках аккаунта. Ручная очередь кабинета имеет приоритет и отправляется круглосуточно с минутным интервалом. Лимиты тарифа и Android сохраняются; для автоматической очереди обновите приложение до 1.37 или новее.

Для статусов заказов рекомендуем конструктор сообщений: равнозначные фразы выбираются случайно без повторов до завершения цикла. Доступны PHP-код, инструкция и скилл.

POST /sms/v1/sms
Authorization: Basic base64(email:fs_live_…)
Content-Type: application/json

{"from":"MyShop","to":79991234567,"message":"Ваш заказ готов","msg_id":"order-1","callback_url":"https://example.com/sms-status"}

{"result":{"status":{"code":0,"description":"ok"},"msg_id":"order-1"}}

from, to и message обязательны. Получатель — число или строка из 11–15 цифр, без плюса; поддерживаются российские мобильные 79XXXXXXXXX. Имя отправителя — до 11 символов из латиницы, цифр и разрешённых знаков, без пробелов по краям. Текст — 1–1000 символов, но действует общий предел 4 частей СМС.

msg_id необязателен: 1–16 символов A–Z a–z 0–9 _ : -. Повтор того же ID в аккаунте — код 20102, срок хранения — 30 дней. Одинаковый текст тому же номеру за 10 минут возвращает успешный ответ и ID первого сообщения; новая метка при этом не занимается. При таком повторе адрес уведомления первого сообщения сохраняется.

Ошибки полей — HTTP 200, result.status.code=20101 и payload.wrong_field. Остальные коды: 1 — длинный текст, 88 — лимит или занятая очередь, 1281 — отправка запрещена, 1284 — запрещённые слова, 1288 — неподдерживаемый регион. Неверные реквизиты — HTTP 401, код 14; заблокированный аккаунт или неподтверждённая почта — HTTP 403; внутренняя ошибка — HTTP 500.

callback_url необязателен. Для sent отправляем delivered; для rejected и просроченного/отменённого — delivery_failed. Промежуточные failed и unknown уведомлений не дают. Тело: msg_id, числовой receipted_message_id, status, строка short_message в формате deliver_sm.

Для входящих задайте адрес уведомлений в разделе API кабинета. Тело: from — номер клиента, to — номер SIM шлюза, message и short_message — одинаковый текст с заменой каждой группы цифр на [xxxxxx], msg_id — ID ответа. Принимаются только разрешённые ответы клиентов, на тарифе с входящими. Новый адрес действует для ещё не отправленных уведомлений; отключение адреса прекращает их отправку.

Уведомления — POST JSON, без авторизации. Клиент должен ответить HTTP 200. При отсутствии ответа или 5xx повторяем через 1, 3 и 5 минут; другой HTTP-ответ прекращает повторы. Обработка запускается после запроса телефона и таймером; указанные интервалы — минимальные. Адрес — только публичный HTTP/HTTPS, порт 80 или 443, без логина и перенаправлений. Внутренние адреса и адрес самого SMSFlake запрещены. У клиента нужно разрешить IP вашего сервера SMSFlake, а не IP МегаФона.

Существующий API /api/v1/… с Bearer-ключом продолжает работать. У обоих API общая очередь, квоты, ключи и ограничение 60 запросов в минуту на ключ.