Skip to the content.

Грабли Beget API

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

Ошибка метода приезжает в success-конверте

api.beget.com отвечает HTTP 200 и {"status": "success", "answer": {...}}, а собственную ошибку метода кладёт внутрь answer:

{"status": "success",
 "answer": {"status": "error",
            "errors": [{"error_code": "METHOD_FAILED", "error_text": "Failed to get DNS records"}]}}

Проверять только конверт недостаточно. Пока BegetClient.call() возвращал такой answer как значение, ломались сразу две вещи:

Отсюда два инварианта, закрытых тестами:

Второй инвариант нужен отдельно от первого: фейковый клиент в тестах райзил корректно, поэтому тесты фолбэка проходили, пока живой клиент молчал. Дыра была ровно на границе клиента.

www-алиасы поддоменов не существуют для API

Beget автоматически заводит www.sub.site.ru к поддомену sub.site.ru, и в панели такой алиас виден. Для API его нет:

dns_get в этом случае уходит на родительскую зону и помечает ответ полями note, queried_fqdn, parent_zone. Тот же фолбэк закрывает DKIM-имена вида mail._domainkey.site.ru, которые тоже иногда живут на уровне родителя.

Фолбэк срабатывает только на METHOD_FAILED. INVALID_DATA, лимиты и транзиентные сбои отдаются как есть: иначе они подменялись бы записями родителя и пометкой «поддомена не существует», и настоящая причина терялась бы.

Setter-тулам зоны нет — они райзят SUBDOMAIN_NOT_CREATED, если родительская зона при этом читается (лечится domain_add_subdomain), и NO_ZONE_DATA в остальных случаях. Вслепую не пишут ни в том, ни в другом. Осознанно записать зону целиком, не читая её, можно через dns_set_records с replace_all=True — этот путь идёт мимо read-merge-write, и для несозданного поддомена он НЕ помогает: сущность так не появляется.

Зачем это разделение, чем опасны автозаписи нового поддомена, почему отсутствие записи в зоне не означает «имя не резолвится» и почему success от dns_set_* не равен «отдаётся миру» — docs/dns-facts-vs-intent.md.

Имена параметров, которые легко угадать неверно

Проверено живыми вызовами, ошибочные варианты возвращают INVALID_DATA:

Метод Верно Неверно
cron/delete row_number (поле так и называется в cron/getList) id
mysql/changeAccessPassword suffix + access + password; access обязателен без access
stat/getSiteLoad site_id — принимает и число, и строку

Пароль API: спецсимволы и query string

Пароль аккаунта Beget API может содержать # и прочие символы, ломающие URL. BegetClient отправляет login/passwd POST’ом в теле формы, поэтому не задет: кодирование берёт на себя requests, и пароль не попадает в access-логи по пути. В query остаётся только output_format.

Сторонние клиенты этим не защищены: dns_beget.sh из acme.sh кладёт креды прямо в query string без URL-кодирования (dnsapi/dns_beget.sh), и на # в пароле curl обрывает URL — наружу это выглядит как error code: 3 / «Can’t get domain list».