Ошибки и лимиты
Это центральная страница со всеми кодами и лимитами Public API.
Формат ответа
Все ошибки приходят с JSON-телом единого формата:
{
"error": {
"code": "domain_not_verified",
"message": "Domain example.ru is not verified for this project",
"details": {}
}
}
Ошибки валидации (Pydantic, 422) кладут массив проблем в details:
{
"error": {
"code": "validation_error",
"message": "Request body is invalid",
"details": [
{
"loc": ["body", "to", 0],
"msg": "value is not a valid email address",
"type": "value_error.email"
}
]
}
}
HTTP-коды
| Код | Значение | Когда |
|---|---|---|
400 | Bad Request | Битый JSON или неверный формат запроса |
401 | Unauthorized | API-ключ отсутствует, невалиден, отозван или истёк |
402 | Payment Required | Превышен лимит плана |
403 | Forbidden | Прав ключа недостаточно (например, READ_ONLY пытается отправлять) |
404 | Not Found | Ресурс не найден или не принадлежит проекту/организации |
409 | Conflict | Дублирующийся ресурс (slug шаблона, имя ключа, домен и т.д.) |
422 | Unprocessable Entity | Ошибка валидации, домен не верифицирован, шаблон не найден, и т.д. |
429 | Too Many Requests | Превышен rate limit |
5xx | Server Error | Внутренняя ошибка — повторите запрос с backoff |
Rate Limits
В каждый ответ добавляются заголовки:
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 7
X-RateLimit-Reset: 1715461260
| Заголовок | Описание |
|---|---|
X-RateLimit-Limit | Максимум запросов в секунду для текущего плана |
X-RateLimit-Remaining | Осталось в текущем окне |
X-RateLimit-Reset | Unix timestamp сброса счётчика |
При 429 ответ дополнительно содержит Retry-After (секунды).
Лимиты по плану
| Параметр | FREE | PAID | PREMIUM |
|---|---|---|---|
| Писем в день | 100 | 10 000 | 100 000 |
| Запросов в секунду | 1 | 10 | 100 |
| Доменов | 1 | 5 | 50 |
Sandbox (mi_test_…) — 50 писем в день, не зависит от плана.
Лимиты на письмо
| Параметр | Значение |
|---|---|
Получателей (to + cc + bcc) | 50 |
reply_to адресов | 10 |
| Тегов | 10 |
| Длина тега | 32 символа |
Длина subject | 998 символов (RFC 5321) |
| Размер вложения | 10 МиБ |
| Вложений в письме | 10 |
| Размер письма целиком | 25 МиБ |
Писем в одном POST /v1/emails/batch | 100 |
Retry с экспоненциальным backoff
Корректная стратегия для интеграции:
import time
import httpx
def send_with_retry(payload: dict, api_key: str, max_retries: int = 3) -> dict:
"""Отправляет письмо с retry на 429 и 5xx.
Args:
payload: Тело запроса POST /v1/emails.
api_key: Plain API-ключ (mi_live_... или mi_test_...).
max_retries: Максимум повторных попыток.
Returns:
Поле `data` успешного ответа.
Raises:
RuntimeError: Если все попытки исчерпаны.
"""
delay = 1.0
for _ in range(max_retries):
r = httpx.post(
"https://api.mailinfra.ru/v1/emails",
headers={"Authorization": f"Bearer {api_key}"},
json=payload,
)
if r.status_code == 429:
wait = int(r.headers.get("Retry-After", delay))
time.sleep(wait)
delay *= 2
continue
if r.status_code >= 500:
time.sleep(delay)
delay *= 2
continue
r.raise_for_status()
return r.json()["data"]
raise RuntimeError("Max retries exceeded")
Для безопасного retry на сетевых таймаутах (ConnectError, чтение прервалось) добавляйте заголовок Idempotency-Key — повтор не приведёт к дубликату письма. См. Идемпотентность.