REST API Bulko позволяет импортировать получателей, запускать managed email-кампании, получать статистику, читать классифицированные ответы и проверять баланс кредитов. В этом руководстве используются production endpoint’ы v1 с примерами на Python и cURL.
API предназначен для серверных интеграций. Не помещайте токен в браузерный код, мобильное приложение, публичный репозиторий или логи. Отправка зависит от баланса, suppression-проверок, content safety, Acceptable Use Policy и применимого законодательства.
Что поддерживает API
| Метод | Endpoint | Назначение |
|---|---|---|
| POST | /api/v1/subscribers/import | Создать или дополнить список |
| POST | /api/v1/campaigns/launch | Создать и поставить кампанию в очередь |
| GET | /api/v1/campaigns/{id}/stats | Получить статистику |
| GET | /api/v1/replies | Получить ответы и классификацию |
| GET | /api/v1/account/balance | Проверить кредиты |
| GET | /api/v1/smtp/accesses | Проверить отдельно купленный SMTP Access |
Исходящие webhooks пока не доступны в production. Для автоматизации используйте polling статистики и ответов. Канонический контракт опубликован как OpenAPI 3.1.
1. Создайте API-токен
Войдите в аккаунт и откройте API keys. Назовите ключ и сразу скопируйте его: полный токен показывается только один раз. Формат заголовка — Authorization: Bearer bulko_live_.... Скомпрометированный ключ можно отозвать там же.
export BULKO_API_TOKEN='bulko_live_replace_me'
2. Проверьте авторизацию и баланс
cURL
curl --fail-with-body \
-H "Authorization: Bearer $BULKO_API_TOKEN" \
https://bulko.io/api/v1/account/balance
Python
import os
import requests
BASE_URL = "https://bulko.io/api/v1"
HEADERS = {
"Authorization": f"Bearer {os.environ['BULKO_API_TOKEN']}",
"Content-Type": "application/json",
}
response = requests.get(f"{BASE_URL}/account/balance", headers=HEADERS, timeout=30)
response.raise_for_status()
print(response.json()["balance"])
3. Импортируйте проверенный список
Один запрос принимает до 10 000 записей. Элемент может быть строкой либо объектом с email и name. Невалидные, повторные и находящиеся в глобальном suppression list адреса пропускаются и считаются отдельно.
payload = {
"list_name": "Product demo requests",
"emails": [
{"email": "[email protected]", "name": "Alex"},
"[email protected]",
],
}
response = requests.post(
f"{BASE_URL}/subscribers/import",
headers=HEADERS,
json=payload,
timeout=30,
)
response.raise_for_status()
list_id = response.json()["list"]["id"]
Техническая валидация не означает разрешение на контакт. Законно собирайте адреса, фиксируйте основание обработки и предоставляйте понятный opt-out.
4. Запустите кампанию
Передайте subscriber_list_id или массив recipients. Обязательные поля: name, subject и body. Успешный запрос возвращает HTTP 202, поскольку отправка выполняется асинхронно.
campaign_payload = {
"name": "Requested product update",
"subject": "A useful update for your team",
"body": "<p>Hello, here is the update you requested.</p>",
"subscriber_list_id": list_id,
"reply_to": "[email protected]",
"language": "en",
"enable_tracking": True,
}
response = requests.post(
f"{BASE_URL}/campaigns/launch",
headers=HEADERS,
json=campaign_payload,
timeout=30,
)
response.raise_for_status()
campaign_id = response.json()["id"]
Ответ также содержит число допущенных и подавленных получателей и URL статистики. В API-кампаниях обработка отписки всегда остаётся включённой.
5. Проверяйте статус через polling
import time
for attempt in range(20):
response = requests.get(
f"{BASE_URL}/campaigns/{campaign_id}/stats",
headers=HEADERS,
timeout=30,
)
response.raise_for_status()
stats = response.json()["campaign"]
print(stats["status"], stats["sent"], stats["failed"])
if stats["status"] in {"completed", "failed"}:
break
time.sleep(min(60, 5 * (attempt + 1)))
Используйте постепенное увеличение интервала. Текущий лимит — 60 запросов в минуту на токен; ответ HTTP 429 содержит заголовок Retry-After.
6. Получите ответы
params = {"status": "positive", "limit": 50, "offset": 0}
response = requests.get(
f"{BASE_URL}/replies",
headers=HEADERS,
params=params,
timeout=30,
)
response.raise_for_status()
for reply in response.json()["items"]:
print(reply["campaign_id"], reply["from"], reply["status"])
Допустимые фильтры перечислены в OpenAPI. Токен получает ответы только собственных кампаний пользователя.
Обработка ошибок
| Код | Причина | Действие |
|---|---|---|
| 400 | Ошибка JSON или поля | Исправить запрос, не повторять без изменений |
| 401 | Нет токена либо он отозван | Проверить Bearer или заменить ключ |
| 402 | Недостаточно email-кредитов | Проверить и пополнить баланс |
| 403 | Контент заблокирован | Проверить сообщение и правила |
| 404 | Ресурс пользователя не найден | Проверить ID и аккаунт |
| 429 | Превышен rate limit | Подождать Retry-After |
Для сетевых ошибок и временных 5xx используйте ограниченные повторы с exponential backoff. Не повторяйте запуск кампании автоматически, пока система не подтвердила, что первый запрос не был принят.
Чек-лист production-интеграции
- Храните токен в secret manager или защищённой переменной окружения.
- Создавайте отдельные ключи для разных систем и отзывайте неиспользуемые.
- Задавайте connection и read timeout для каждого запроса.
- Логируйте ID кампаний, но не токены и полные базы получателей.
- Учитывайте suppression и не возвращайте отписавшихся пользователей в базу.
- Используйте OpenAPI для генерации типизированного клиента и проверки payload.
На странице API и интеграций есть примеры JavaScript и PHP, информация о SMTP Access, текущих ограничениях и roadmap webhooks.