SPINE API
API работает RUENAZ OpenAPI Консоль

SPINE Developer API

Единый API платформы для внешних проектов: почта, SSL-сертификаты, DNS, ящики, домены, вебхуки и Telegram-боты, по одному ключу.

BASE · https://api.spine.maxsystems.az

🔑 Аутентификация

Заголовок Authorization: Bearer spine_sk_… в каждом запросе. Ключ выдаётся в консоли, показывается один раз, отзывается в клик.

⚙️ Учёт и лимиты

Каждый ключ считает запросы; лимит по умолчанию 120/мин (настраивается). Превышение, 429. Суточные/месячные квоты, по желанию.

⚠️ Коды ошибок

401 ключ неверный · 403 нет права · 429 лимит · 400 ошибка запроса (поле error).

Права (скоупы)
СкоупЧто разрешает
mail:sendОтправка писем (одиночно и пакетом, статус и статистика отправленных)
mail:readЧтение писем своих ящиков
mail:suppressionsЗаблокированные адреса: смотреть, добавлять, снимать блок
mail:mailboxesСоздание и управление ящиками
mail:domainsПодключение и статус доменов
mail:templatesШаблоны писем проекта: смотреть, править, откатывать, слать тест
notify:sendУведомления одним запросом: почта, Telegram, вебхук, с запасными каналами
mail:subscriptionsСписки рассылок и подписки: подписать, отписать, посмотреть согласия
protection:verifyПроверка ответа Turnstile для форм вашего сайта
domains:readСроки регистрации своих доменов: когда истекает, у какого регистратора
mail:migrateПеренос почты с другого хостинга по IMAP (задания и прогресс)
cert:issueЗаказ и перевыпуск сертификатов
cert:readСтатус и скачивание сертификатов, SPKI-пины
cert:acmeACME-доступ: пары EAB для certbot, acme.sh, cert-manager, файрволов
cert:delegateCNAME-делегации для доменов, чей DNS не у нас
pki:issueВыпуск из приватного CA: длинные серты и клиентские mTLS
pki:readПриватный CA: корневые сертификаты, цепочки, CRL
dns:readЧтение DNS-записей зоны
dns:recordsУправление DNS-записями зоны
webhooksПодписка на вебхуки (входящие письма, события доставки)
telegram:botsУправление Telegram-ботами проекта
webmail:loginСсылки авто-входа сотрудника в вебмейл (SSO для встраивания)
brand:readБренд-кит проекта (логотипы, цвета, шрифты, файлы)
project:readСводка проекта: домены, серты, ключи, боты, бренд, биллинг (self-view)
audit:readЖурнал событий проекта (кто/что/когда менял)
siteconfig:readSEO / site-config проекта (raw-конфиг; артефакты отдаются публично)
secrets:readЧитать секреты проекта из сейфа (карта/.env/по имени)
secrets:writeЗаписывать/генерировать/удалять секреты проекта в сейфе
security:readОтчёт безопасности своих доменов: оценка и открытые находки
otp:sendВерификация: выдать и доставить одноразовый код (SMS, Telegram)
otp:verifyВерификация: проверить код, статус попытки и доставки
otp:totpВерификация: приложение-аутентификатор и резервные коды пользователей проекта

Почта

POST/v1/mail/sendmail:send

Отправить письмо. Домен отправителя должен быть подключён к почте SPINE и принадлежать проекту.

Параметры
fromадрес отправителя (обязателен)
toполучатель (обязателен)
subjectтема (или template)
text / htmlтело письма
categoryметка для статистики (необязательно)
cc / bcc / replyToсписки адресов (необязательно)
attachmentsвложения: [{ filename, content (base64), contentType }], до 15 МБ на письмо
headersсвои заголовки письма, объект (необязательно)
streamtransactional (по умолчанию) | marketing. Транзакционный поток не блокируется отписками и жалобами: платёжный документ получатель должен получить, даже если отказался от новостей. Жёсткий отказ (несуществующий ящик) действует в обоих потоках
scopeвладелец отношения с получателем (например slug мерчанта). Жалобы и отписки ложатся именно на него, а не на весь проект. Пусто = весь проект, как раньше
campaignметка кампании: по ней считается воронка в /v1/mail/stats
tracktrue, считать открытия и клики (события mail.opened / mail.clicked)
unsubscribetrue, добавить отписку в один клик (RFC 8058, требование Gmail и Yahoo)
sendAtдата ISO 8601, отложенная отправка (до 90 дней, отмена через DELETE)
idempotencyKeyзащита от дублей (необязательно)
Тело запроса
{
  "from": "noreply@ваш-домен",
  "to": "user@example.com",
  "subject": "Привет",
  "text": "Тело письма",
  "html": "<p>Тело письма со <a href=\"https://example.com\">ссылкой</a></p>",
  "campaign": "welcome-july",
  "track": true,
  "unsubscribe": true
}
Пример ответа
{ "id": "01K…", "status": "sent", "relay": "ses", "relayMessageId": "…" }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/mail/send" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"from":"noreply@ваш-домен","to":"user@example.com","subject":"Привет","text":"Тело письма","html":"<p>Тело письма со <a href=\"https://example.com\">ссылкой</a></p>","campaign":"welcome-july","track":true,"unsubscribe":true}'
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/send", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
  "from": "noreply@ваш-домен",
  "to": "user@example.com",
  "subject": "Привет",
  "text": "Тело письма",
  "html": "<p>Тело письма со <a href=\"https://example.com\">ссылкой</a></p>",
  "campaign": "welcome-july",
  "track": true,
  "unsubscribe": true
})
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/mail/send",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"from":"noreply@ваш-домен","to":"user@example.com","subject":"Привет","text":"Тело письма","html":"<p>Тело письма со <a href=\"https://example.com\">ссылкой</a></p>","campaign":"welcome-july","track":true,"unsubscribe":true}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mail/send");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"from":"noreply@ваш-домен","to":"user@example.com","subject":"Привет","text":"Тело письма","html":"<p>Тело письма со <a href=\"https://example.com\">ссылкой</a></p>","campaign":"welcome-july","track":true,"unsubscribe":true}',
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/mail/send-batchmail:send

Отправить письма пакетом: до 200 получателей за запрос, у каждого свои vars. Общие поля задаются на верхнем уровне.

Параметры
messagesсписок писем: [{ to, vars, subject?, html?, attachments? }] (обязателен)
from / subject / html / text / templateобщие поля для всех писем пакета
campaign / track / unsubscribe / sendAtкак в одиночной отправке, применяются ко всему пакету
Тело запроса
{
  "from": "noreply@ваш-домен",
  "subject": "Привет, {{name}}",
  "html": "<p>Здравствуйте, {{name}}</p>",
  "campaign": "july-news",
  "track": true,
  "messages": [
    { "to": "a@example.com", "vars": { "name": "Анна" } },
    { "to": "b@example.com", "vars": { "name": "Борис" } }
  ]
}
Пример ответа
{ "total": 2, "counts": { "sent": 2 }, "results": [ { "to": "a@example.com", "id": "01K…", "status": "sent" } ] }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/mail/send-batch" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"from":"noreply@ваш-домен","subject":"Привет, {{name}}","html":"<p>Здравствуйте, {{name}}</p>","campaign":"july-news","track":true,"messages":[{"to":"a@example.com","vars":{"name":"Анна"}},{"to":"b@example.com","vars":{"name":"Борис"}}]}'
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/send-batch", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
  "from": "noreply@ваш-домен",
  "subject": "Привет, {{name}}",
  "html": "<p>Здравствуйте, {{name}}</p>",
  "campaign": "july-news",
  "track": true,
  "messages": [
    { "to": "a@example.com", "vars": { "name": "Анна" } },
    { "to": "b@example.com", "vars": { "name": "Борис" } }
  ]
})
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/mail/send-batch",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"from":"noreply@ваш-домен","subject":"Привет, {{name}}","html":"<p>Здравствуйте, {{name}}</p>","campaign":"july-news","track":true,"messages":[{"to":"a@example.com","vars":{"name":"Анна"}},{"to":"b@example.com","vars":{"name":"Борис"}}]}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mail/send-batch");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"from":"noreply@ваш-домен","subject":"Привет, {{name}}","html":"<p>Здравствуйте, {{name}}</p>","campaign":"july-news","track":true,"messages":[{"to":"a@example.com","vars":{"name":"Анна"}},{"to":"b@example.com","vars":{"name":"Борис"}}]}',
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mailboxes/{id}/messagesmail:read

Письма ящика, новые сверху, с постраничным обходом. Возвращает total и hasMore, поэтому обойти весь ящик можно без догадок.

Параметры
limitсколько вернуть, 1..500 (по умолчанию 50)
offsetсколько пропустить: обход страницами offset += limit
sinceписьма СТРОГО позже этого момента, ISO 8601. ⚠️ Письмо той же секунды не вернётся
beforeписьма раньше этого момента, ISO 8601
hasAttachmenttrue, только письма с вложениями, false, только без
withпереписка с адресом: письма ОТ него или К нему
from / toточечный фильтр по отправителю или получателю
Пример ответа
{ "messages": [ { "id": "…", "subject": "…", "receivedAt": "2026-08-10T09:00:00Z", "hasAttachment": true, "rfcMessageId": "20260810020431.ade65@example.com" } ], "total": 1843, "offset": 0, "limit": 50, "hasMore": true }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mailboxes/{id}/messages/by-message-idmail:read

Письмо по его Message-ID: нужно, чтобы сопоставить письмо с уже сохранённым у себя, не перебирая списки. Значение принимается и в угловых скобках, и без них. Если письмо не найдено, в тексте ошибки написано, сколько писем просмотрено и до какой даты дошёл поиск: «не нашли в просмотренном» и «не существует» это разные ответы.

Параметры
valueMessage-ID письма (обязательно)
since / beforeсузить период поиска, ISO 8601: ускоряет ответ на больших ящиках
Пример ответа
{ "message": { "id": "…", "subject": "…", "rfcMessageId": "…" }, "lookup": { "source": "index", "scanned": 0 } }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/by-message-id" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/by-message-id", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/by-message-id",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/by-message-id");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mail/messagesmail:send

Список своих отправленных писем со статусами.

Параметры
limitсколько вернуть (до 200, по умолчанию 50)
statusфильтр: sent | delivered | bounced | complained | delayed | scheduled | suppressed | failed
campaignфильтр по метке кампании
Пример ответа
{ "messages": [ { "id": "01K…", "toAddr": "user@example.com", "status": "delivered", "opens": 1, "clicks": 0 } ] }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/mail/messages" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/messages", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mail/messages",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mail/messages");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mail/messages/{id}mail:send

Статус письма и вся его история: принято релеем, доставлено, открыто, клик по ссылке.

Пример ответа
{ "message": { "id": "01K…", "status": "delivered", "relay": "ses", "opens": 1, "clicks": 1 },
  "events": [ { "event": "sent" }, { "event": "delivered" }, { "event": "opened" }, { "event": "clicked" } ],
  "links": [ { "idx": 1, "url": "https://example.com", "clicks": 1 } ] }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/mail/messages/{id}" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/messages/{id}", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mail/messages/{id}",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mail/messages/{id}");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
DELETE/v1/mail/messages/{id}mail:send

Отменить отложенное письмо, пока оно не ушло.

Пример ответа
{ "id": "01K…", "status": "canceled" }
Примеры кода
curl -X DELETE "https://api.spine.maxsystems.az/v1/mail/messages/{id}" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/messages/{id}", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.delete(
  "https://api.spine.maxsystems.az/v1/mail/messages/{id}",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mail/messages/{id}");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "DELETE",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mail/statsmail:send

Воронка доставляемости за период: принято, доставлено, открыто, клики, отказы, жалобы. Проценты отказов и жалоб важно держать ниже порогов Amazon (5% и 0.1%).

Параметры
daysпериод в днях (1..365, по умолчанию 30)
campaignтолько по одной кампании
Пример ответа
{ "days": 30, "total": 120,
  "funnel": { "accepted": 118, "delivered": 115, "opened": 64, "clicked": 12, "bounced": 2, "complained": 0 },
  "rates": { "bounce": 1.69, "complaint": 0, "delivery": 97.46, "open": 55.65, "click": 10.43 } }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/mail/stats" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/stats", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mail/stats",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mail/stats");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/mail/validatemail:send

Проверить адрес ДО отправки: синтаксис, есть ли у домена почтовые серверы, одноразовая ли почта, нет ли опечатки в известном домене (gmial вместо gmail). Заведомо негодный адрес получает verdict invalid, сомнительный warn с объяснением. Помогает не набирать жёсткие отказы, которые бьют по репутации домена.

Тело запроса
{ "emails": ["user@gmial.com", "someone@example.com"] }
Пример ответа
{ "total": 2, "counts": { "warn": 1, "ok": 1 },
  "results": [ { "email": "user@gmial.com", "verdict": "warn", "suggestion": "user@gmail.com",
                 "reasons": ["похоже на опечатку в домене"], "hasMx": true, "disposable": false } ] }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/mail/validate" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"emails":["user@gmial.com","someone@example.com"]}'
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/validate", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "emails": ["user@gmial.com", "someone@example.com"] })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/mail/validate",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"emails":["user@gmial.com","someone@example.com"]}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mail/validate");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"emails":["user@gmial.com","someone@example.com"]}',
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/mail/checkmail:send

Чек-лист письма перед отправкой: нет текстовой части, картинка вместо текста, крик в теме, короткие ссылки, скрытый текст, массовое письмо без отписки. Это наш чек-лист причин попасть в спам, а не вердикт спам-фильтра.

Тело запроса
{ "subject": "СРОЧНО!!", "html": "<img src=…>", "category": "marketing" }
Пример ответа
{ "score": 8, "verdict": "bad",
  "issues": [ { "weight": 3, "text": "нет текстовой части: письмо только в HTML" } ] }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/mail/check" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"subject":"СРОЧНО!!","html":"<img src=…>","category":"marketing"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/check", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "subject": "СРОЧНО!!", "html": "<img src=…>", "category": "marketing" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/mail/check",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"subject":"СРОЧНО!!","html":"<img src=…>","category":"marketing"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mail/check");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"subject":"СРОЧНО!!","html":"<img src=…>","category":"marketing"}',
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mailboxes/{id}/messages/{msgId}/rawmail:read

Письмо целиком в исходном виде (RFC 5322, message/rfc822). Нужно, когда своего разбора хочется больше, чем нашего: вы получаете ровно то, что лежит на сервере. ⚠️ Политика проекта и антивирус применяются и здесь: письмо с запрещённым или заражённым вложением не отдаётся целиком, иначе через сырой файл проверку можно было бы обойти. Выдача пишется в журнал проекта: это доступ ко всей переписке письма.

Пример ответа
(поток message/rfc822, имя файла <id письма>.eml)
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/{msgId}/raw" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/{msgId}/raw", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/{msgId}/raw",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/{msgId}/raw");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mail/suppressionsmail:suppressions

Заблокированные адреса проекта: жёсткие отказы, жалобы на спам, отписки. Письма на них не отправляются.

Параметры
emailпроверить один адрес
scopeблокировки этого владельца плюс общепроектные (они действуют на всех)
streamtransactional, показать только то, что блокирует платёжные письма
limitсколько вернуть (до 500)
Пример ответа
{ "suppressions": [ { "email": "user@example.com", "reason": "hard_bounce", "scope": null, "ts": "2026-07-25T10:00:00Z" } ] }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/mail/suppressions" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/suppressions", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mail/suppressions",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mail/suppressions");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/mail/suppressionsmail:suppressions

Заблокировать адрес вручную (например, по просьбе получателя).

Параметры
emailадрес (обязателен)
reasonhard_bounce | complaint | manual | unsubscribe (по умолчанию manual)
scopeчья это блокировка: пусто = весь проект
Тело запроса
{ "email": "user@example.com", "reason": "manual", "scope": "мерчант" }
Пример ответа
{ "email": "user@example.com", "reason": "manual", "tenant": "ваш-проект", "scope": "мерчант" }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/mail/suppressions" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"email":"user@example.com","reason":"manual","scope":"мерчант"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/suppressions", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "email": "user@example.com", "reason": "manual", "scope": "мерчант" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/mail/suppressions",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"email":"user@example.com","reason":"manual","scope":"мерчант"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mail/suppressions");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"email":"user@example.com","reason":"manual","scope":"мерчант"}',
]);
$data = json_decode(curl_exec($ch), true);
DELETE/v1/mail/suppressions/{email}mail:suppressions

Снять блокировку с адреса (например, человек починил свой ящик и снова хочет письма). С параметром scope снимается блокировка ровно этого владельца, без него, все блокировки адреса в проекте.

Параметры
scopeснять блокировку только этого владельца
Пример ответа
{ "email": "user@example.com", "removed": 1, "scope": "мерчант" }
Примеры кода
curl -X DELETE "https://api.spine.maxsystems.az/v1/mail/suppressions/{email}" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/suppressions/{email}", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.delete(
  "https://api.spine.maxsystems.az/v1/mail/suppressions/{email}",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mail/suppressions/{email}");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "DELETE",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mail/policymail:send

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

Пример ответа
{ "project": "ваш-проект",
  "attachments": { "maxBytesOutbound": 15728640, "maxBytesInbound": 26214400,
    "blockedExtensions": ["exe", "js", "bat", "…"], "antivirus": "отклоняем заражённое" },
  "sending": { "maxRecipientsPerBatch": 200, "dailyCap": null } }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/mail/policy" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/policy", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mail/policy",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mail/policy");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);

Подписки

GET/v1/mail/listsmail:subscriptions

Списки рассылок проекта («счета», «новости»). Отписка идёт от списка, а не от всей почты сразу: человек, отказавшийся от новостей, продолжает получать счета.

Пример ответа
{ "lists": [ { "slug": "news", "name": "Новости", "doubleOptIn": true,
    "confirmed": 128, "pending": 4, "unsubscribed": 11 } ] }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/mail/lists" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/lists", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mail/lists",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mail/lists");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/mail/listsmail:subscriptions

Создать список рассылки. doubleOptIn (по умолчанию true) значит, что адрес считается подписанным только после подтверждения по ссылке из письма: так требуют Gmail и Yahoo, и так чужой адрес нельзя подписать за человека.

Тело запроса
{ "slug": "news", "name": "Новости", "description": "раз в месяц", "doubleOptIn": true }
Пример ответа
{ "id": "…", "slug": "news", "name": "Новости", "doubleOptIn": true }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/mail/lists" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"slug":"news","name":"Новости","description":"раз в месяц","doubleOptIn":true}'
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/lists", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "slug": "news", "name": "Новости", "description": "раз в месяц", "doubleOptIn": true })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/mail/lists",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"slug":"news","name":"Новости","description":"раз в месяц","doubleOptIn":true}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mail/lists");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"slug":"news","name":"Новости","description":"раз в месяц","doubleOptIn":true}',
]);
$data = json_decode(curl_exec($ch), true);
DELETE/v1/mail/lists/{slug}mail:subscriptions

Удалить список вместе со всеми подписками на него. Уже отправленные письма это не затрагивает.

Пример ответа
{ "deleted": "news" }
Примеры кода
curl -X DELETE "https://api.spine.maxsystems.az/v1/mail/lists/{slug}" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/lists/{slug}", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.delete(
  "https://api.spine.maxsystems.az/v1/mail/lists/{slug}",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mail/lists/{slug}");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "DELETE",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/mail/subscriptionsmail:subscriptions

Подписать адрес на список. При двойном подтверждении платформа сразу отправляет письмо со ссылкой и возвращает status pending: до подтверждения письма списка на этот адрес НЕ уходят. Если согласие уже получено у вас (галочка при заказе), можно передать confirmed: true.

Тело запроса
{ "email": "user@example.com", "list": "news", "source": "форма на сайте" }
Пример ответа
{ "email": "user@example.com", "list": "news", "status": "pending", "confirmationSent": true }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/mail/subscriptions" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"email":"user@example.com","list":"news","source":"форма на сайте"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/subscriptions", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "email": "user@example.com", "list": "news", "source": "форма на сайте" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/mail/subscriptions",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"email":"user@example.com","list":"news","source":"форма на сайте"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mail/subscriptions");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"email":"user@example.com","list":"news","source":"форма на сайте"}',
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mail/subscriptionsmail:subscriptions

Слаг списка во всех методах называется list; поле listSlug оставлено синонимом ради совместимости. Согласия адреса по всем спискам проекта и готовая ссылка на его центр подписок. Ссылку можно положить в подвал письма: на ней человек сам управляет подписками.

Параметры
emailадрес, который проверяем
Пример ответа
{ "email": "user@example.com",
  "subscriptions": [ { "list": "news", "status": "confirmed", "confirmedAt": "2026-08-01T10:00:00Z" } ],
  "prefsUrl": "https://t.example.com/s/…" }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/mail/subscriptions" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/subscriptions", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mail/subscriptions",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mail/subscriptions");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
DELETE/v1/mail/subscriptionsmail:subscriptions

Отписать адрес от списка. Без параметра list отпишет от всех списков проекта.

Параметры
emailадрес
listслаг списка (необязательно)
Пример ответа
{ "email": "user@example.com", "list": "news", "unsubscribed": 1 }
Примеры кода
curl -X DELETE "https://api.spine.maxsystems.az/v1/mail/subscriptions" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/subscriptions", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.delete(
  "https://api.spine.maxsystems.az/v1/mail/subscriptions",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mail/subscriptions");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "DELETE",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);

Шаблоны

GET/v1/mail/templatesmail:templates

Шаблоны писем проекта. Текст письма перестаёт жить в коде проекта: правится здесь, версии сохраняются, откат делается одним запросом.

Пример ответа
{ "templates": [ { "slug": "invoice", "name": "Счёт", "version": 3, "variables": ["name","number"] } ] }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/mail/templates" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/templates", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mail/templates",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mail/templates");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/mail/templatesmail:templates

Создать шаблон. Переменные вида {{name}} платформа находит сама и показывает списком.

Тело запроса
{ "slug": "invoice", "name": "Счёт", "subject": "Счёт {{number}}",
  "html": "<p>Здравствуйте, {{name}}. Счёт {{number}} на {{sum}}.</p>" }
Пример ответа
{ "slug": "invoice", "version": 1, "variables": ["name","number","sum"] }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/mail/templates" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"slug":"invoice","name":"Счёт","subject":"Счёт {{number}}","html":"<p>Здравствуйте, {{name}}. Счёт {{number}} на {{sum}}.</p>"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/templates", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "slug": "invoice", "name": "Счёт", "subject": "Счёт {{number}}",
  "html": "<p>Здравствуйте, {{name}}. Счёт {{number}} на {{sum}}.</p>" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/mail/templates",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"slug":"invoice","name":"Счёт","subject":"Счёт {{number}}","html":"<p>Здравствуйте, {{name}}. Счёт {{number}} на {{sum}}.</p>"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mail/templates");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"slug":"invoice","name":"Счёт","subject":"Счёт {{number}}","html":"<p>Здравствуйте, {{name}}. Счёт {{number}} на {{sum}}.</p>"}',
]);
$data = json_decode(curl_exec($ch), true);
PUT/v1/mail/templates/{slug}mail:templates

Изменить шаблон. Прежняя версия сохраняется в истории, номер версии растёт.

Тело запроса
{ "subject": "Счёт {{number}} к оплате" }
Пример ответа
{ "slug": "invoice", "version": 2 }
Примеры кода
curl -X PUT "https://api.spine.maxsystems.az/v1/mail/templates/{slug}" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"subject":"Счёт {{number}} к оплате"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/templates/{slug}", {
  method: "PUT",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "subject": "Счёт {{number}} к оплате" })
});
const data = await res.json();
import requests

r = requests.put(
  "https://api.spine.maxsystems.az/v1/mail/templates/{slug}",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"subject":"Счёт {{number}} к оплате"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mail/templates/{slug}");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "PUT",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"subject":"Счёт {{number}} к оплате"}',
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/mail/templates/{slug}/rollbackmail:templates

Вернуть содержимое прошлой версии. История не переписывается: откат создаёт новую версию с прежним текстом.

Тело запроса
{ "version": 1 }
Пример ответа
{ "slug": "invoice", "version": 3, "rolledBackFrom": 1 }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/mail/templates/{slug}/rollback" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"version":1}'
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/templates/{slug}/rollback", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "version": 1 })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/mail/templates/{slug}/rollback",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"version":1}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mail/templates/{slug}/rollback");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"version":1}',
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/mail/templates/{slug}/previewmail:templates

Предпросмотр с подстановкой переменных плюс чек-лист письма.

Тело запроса
{ "vars": { "name": "Михаил", "number": "SPINE-2026-0007", "sum": "59 AZN" } }
Пример ответа
{ "subject": "Счёт SPINE-2026-0007", "html": "…", "check": { "score": 0, "verdict": "ok" } }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/mail/templates/{slug}/preview" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"vars":{"name":"Михаил","number":"SPINE-2026-0007","sum":"59 AZN"}}'
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/templates/{slug}/preview", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "vars": { "name": "Михаил", "number": "SPINE-2026-0007", "sum": "59 AZN" } })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/mail/templates/{slug}/preview",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"vars":{"name":"Михаил","number":"SPINE-2026-0007","sum":"59 AZN"}}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mail/templates/{slug}/preview");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"vars":{"name":"Михаил","number":"SPINE-2026-0007","sum":"59 AZN"}}',
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/mail/templates/{slug}/testmail:templates

Тестовая отправка шаблона на указанный адрес. Идёт тем же путём, что и настоящее письмо, с метками template-test.

Тело запроса
{ "to": "me@example.com", "vars": { "name": "Михаил" } }
Пример ответа
{ "id": "01K…", "status": "sent" }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/mail/templates/{slug}/test" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"to":"me@example.com","vars":{"name":"Михаил"}}'
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/templates/{slug}/test", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "to": "me@example.com", "vars": { "name": "Михаил" } })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/mail/templates/{slug}/test",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"to":"me@example.com","vars":{"name":"Михаил"}}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mail/templates/{slug}/test");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"to":"me@example.com","vars":{"name":"Михаил"}}',
]);
$data = json_decode(curl_exec($ch), true);

Уведомления

POST/v1/notifynotify:send

Уведомление одним запросом с запасными каналами: пробуем по очереди, пока один не сработает. Порядок и переход держим мы, вам не нужно городить «не дошло в Telegram, шлю письмо» у себя. В ответе видно, какой канал сработал и что пробовали до него.

Тело запроса
{ "title": "Платёж прошёл", "body": "Счёт SPINE-2026-0007 оплачен",
  "channels": [ { "channel": "telegram", "to": "12345678" }, { "channel": "email", "to": "user@example.com" } ] }
Пример ответа
{ "channel": "email", "to": "user@example.com", "status": "sent",
  "tried": [ { "channel": "telegram", "error": "Telegram HTTP 400" } ] }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/notify" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"title":"Платёж прошёл","body":"Счёт SPINE-2026-0007 оплачен","channels":[{"channel":"telegram","to":"12345678"},{"channel":"email","to":"user@example.com"}]}'
const res = await fetch("https://api.spine.maxsystems.az/v1/notify", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "title": "Платёж прошёл", "body": "Счёт SPINE-2026-0007 оплачен",
  "channels": [ { "channel": "telegram", "to": "12345678" }, { "channel": "email", "to": "user@example.com" } ] })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/notify",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"title":"Платёж прошёл","body":"Счёт SPINE-2026-0007 оплачен","channels":[{"channel":"telegram","to":"12345678"},{"channel":"email","to":"user@example.com"}]}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/notify");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"title":"Платёж прошёл","body":"Счёт SPINE-2026-0007 оплачен","channels":[{"channel":"telegram","to":"12345678"},{"channel":"email","to":"user@example.com"}]}',
]);
$data = json_decode(curl_exec($ch), true);

Сертификаты

POST/v1/certs/issuecert:issue

Заказать публичный SSL-сертификат. Домен должен быть в управляемой зоне либо иметь CNAME-делегацию, причём делегация нужна на КАЖДОЕ имя сертификата: владение проверяется по каждому имени отдельно, по своему _acme-challenge.<имя> (исключение, wildcard, он проверяется по записи домена). Выпуск 15-30 сек. Срок задаёт не клиент: по правилам CA/Browser Forum публичный сертификат живёт 199 дней (с 2027, 100, с 2029, 47). Нужен длинный срок, смотрите POST /v1/pki/issue. ⚠️ Поле pinWarning в ответе означает, что по этим доменам уже есть ДРУГОЙ сертификат с вшитыми пинами: у нового свой ключ, значит и пин другой. Нужен прежний пин, перевыпускайте существующий сертификат, а не заказывайте второй.

Параметры
domainsсписок доменов, можно wildcard *.домен (обязателен)
csrсвой CSR, тогда приватный ключ остаётся у вас (необязательно)
autoRenewавтопродление, по умолчанию true
Тело запроса
{ "domains": ["app.ваш-домен", "*.ваш-домен"], "autoRenew": true }
Пример ответа
{ "id": "…", "subject": "app.ваш-домен", "sans": [...], "status": "pending",
  "pinWarning": ["Банк X (Java-приложение)"] }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/certs/issue" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"domains":["app.ваш-домен","*.ваш-домен"],"autoRenew":true}'
const res = await fetch("https://api.spine.maxsystems.az/v1/certs/issue", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "domains": ["app.ваш-домен", "*.ваш-домен"], "autoRenew": true })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/certs/issue",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"domains":["app.ваш-домен","*.ваш-домен"],"autoRenew":true}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/certs/issue");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"domains":["app.ваш-домен","*.ваш-домен"],"autoRenew":true}',
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/certs/{id}/statuscert:read

Статус сертификата (pending → active | failed).

Пример ответа
{ "status": "active", "notAfter": "…", "daysLeft": 89 }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/certs/{id}/status" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/certs/{id}/status", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/certs/{id}/status",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/certs/{id}/status");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/certs/{id}/downloadcert:read

Скачать сертификат (fullchain + приватный ключ; для сертов по CSR privkey=null).

Пример ответа
{ "fullchain": "-----BEGIN CERTIFICATE-----…", "privkey": "…" }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/certs/{id}/download" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/certs/{id}/download", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/certs/{id}/download",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/certs/{id}/download");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/certs/{id}/pinscert:read

SPKI-пины сертификата. Это то, что вшивают у себя партнёры вместо самого сертификата: платформа сохраняет приватный ключ ЭТОГО сертификата, поэтому пин переживает и автопродление, и ручной перевыпуск. ⚠️ Пин сменится, если завести ВТОРОЙ сертификат на те же домены вместо перевыпуска первого, удалить и создать заново, выключить «Сохранять ключ» или запросить ротацию ключа. Отдаём текущий и запасной, плюс готовые строки для curl и OkHttp.

Пример ответа
{ "spkiPin": "…", "backupSpkiPin": "…", "keepKey": true,
  "formats": { "curl": "curl --pinnedpubkey \"sha256//…\" https://api.example.com/" } }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/certs/{id}/pins" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/certs/{id}/pins", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/certs/{id}/pins",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/certs/{id}/pins");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/certs/{id}/pins/registercert:read

Отметить, что пин где-то вшит. Запись попадает в реестр, и перед сменой ключа платформа предупредит, кого это заденет.

Тело запроса
{ "party": "Партнёр X", "system": "Java-приложение", "contact": "dev@partner.com" }
Пример ответа
{ "id": "…", "registered": true, "pin": "…" }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/certs/{id}/pins/register" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"party":"Партнёр X","system":"Java-приложение","contact":"dev@partner.com"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/certs/{id}/pins/register", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "party": "Партнёр X", "system": "Java-приложение", "contact": "dev@partner.com" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/certs/{id}/pins/register",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"party":"Партнёр X","system":"Java-приложение","contact":"dev@partner.com"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/certs/{id}/pins/register");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"party":"Партнёр X","system":"Java-приложение","contact":"dev@partner.com"}',
]);
$data = json_decode(curl_exec($ch), true);

PKI и ACME

POST/v1/acme/eabcert:acme

Выдать себе пару External Account Binding для нашего ACME-сервера. После этого с платформой работает любой стандартный клиент без нашего кода: certbot, acme.sh, cert-manager, Caddy, Traefik, FortiGate 7.6.3+, Cisco ASA 9.23.1+. HMAC-ключ возвращается ОДИН раз.

Параметры
labelметка для себя (необязательно)
allowedDomainsограничить домены (пусто = любые доступные)
issuerletsencrypt (по умолчанию) или private
policyauto (мы проходим проверку сами) или verify (проверяет клиент)
Тело запроса
{ "label": "prod-cluster", "allowedDomains": ["example.com"] }
Пример ответа
{ "kid": "…", "hmacKey": "…",
  "directoryUrl": "https://api.spine.maxsystems.az/acme/directory",
  "snippets": { "certbot": "certbot register --server … --eab-kid … --eab-hmac-key …" } }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/acme/eab" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"label":"prod-cluster","allowedDomains":["example.com"]}'
const res = await fetch("https://api.spine.maxsystems.az/v1/acme/eab", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "label": "prod-cluster", "allowedDomains": ["example.com"] })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/acme/eab",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"label":"prod-cluster","allowedDomains":["example.com"]}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/acme/eab");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"label":"prod-cluster","allowedDomains":["example.com"]}',
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/acme/delegationscert:delegate

Создать делегацию проверки владения по CNAME. Нужна для доменов, чей DNS ведём не мы: клиент один раз ставит одну запись, дальше все проверки выполняем мы у себя, доступ к его DNS не требуется.

Тело запроса
{ "domain": "client-domain.com" }
Пример ответа
{ "domain": "client-domain.com", "record": { "type": "CNAME",
  "name": "_acme-challenge.client-domain.com", "value": "<метка>.acme.spine.maxsystems.az", "ttl": 300 } }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/acme/delegations" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"domain":"client-domain.com"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/acme/delegations", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "domain": "client-domain.com" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/acme/delegations",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"domain":"client-domain.com"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/acme/delegations");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"domain":"client-domain.com"}',
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/acme/delegations/{id}/verifycert:delegate

Проверить, что клиент поставил CNAME. Только читает DNS, ничего не меняет.

Пример ответа
{ "ok": true, "expected": "…", "found": ["…"] }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/acme/delegations/{id}/verify" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/acme/delegations/{id}/verify", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/acme/delegations/{id}/verify",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/acme/delegations/{id}/verify");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/acme/delegationscert:delegate

Список делегаций проекта с их состоянием.

Пример ответа
{ "delegations": [{ "domain": "client-domain.com", "verified": true }] }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/acme/delegations" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/acme/delegations", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/acme/delegations",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/acme/delegations");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/pki/issuepki:issue

Выпустить сертификат приватным CA платформы. Правила CA/Browser Forum на приватный CA не распространяются, поэтому срок задаёте вы: до 3650 дней. Годится для партнёрских интеграций, внутренних сервисов и клиентских сертификатов mTLS.

Параметры
domainsдомены для серверного сертификата
commonNameимя владельца для клиентского сертификата mTLS
daysсрок в днях, по умолчанию 397, максимум 3650
certTypeserver (по умолчанию) или client
csrсвой CSR, приватный ключ остаётся у вас (необязательно)
Тело запроса
{ "domains": ["internal.example.com"], "days": 1095, "certType": "server" }
Пример ответа
{ "id": "…", "subject": "internal.example.com", "status": "pending", "lifetimeDays": 1095 }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/pki/issue" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"domains":["internal.example.com"],"days":1095,"certType":"server"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/pki/issue", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "domains": ["internal.example.com"], "days": 1095, "certType": "server" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/pki/issue",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"domains":["internal.example.com"],"days":1095,"certType":"server"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/pki/issue");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"domains":["internal.example.com"],"days":1095,"certType":"server"}',
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/pki/rootpki:read

Корневой сертификат приватного CA и цепочка. Это то, что партнёр один раз ставит себе в truststore и живёт с ним годы: перевыпуск конечных сертификатов его не затрагивает.

Пример ответа
{ "name": "SPINE Partner CA Root", "rootPem": "-----BEGIN CERTIFICATE-----…",
  "notAfter": "2036-07-29T…", "crlUrl": "https://api.spine.maxsystems.az/v1/pki/…/crl.pem" }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/pki/root" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/pki/root", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/pki/root",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/pki/root");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/pki/crlpki:read

Список отозванных сертификатов приватного CA (CRL) в формате PEM.

Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/pki/crl" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/pki/crl", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/pki/crl",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/pki/crl");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/pki/authoritiespki:read

Центры сертификации, доступные проекту: их имена, тип и срок действия.

Пример ответа
{ "cas": [{ "name": "SPINE Partner CA Issuing", "kind": "intermediate",
  "notAfter": "2031-07-28T…", "isDefault": true }] }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/pki/authorities" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/pki/authorities", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/pki/authorities",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/pki/authorities");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);

Ящики

POST/v1/mailboxesmail:mailboxes

Создать почтовый ящик на своём домене.

Параметры
nameимя ящика до @ (2-40, [a-z0-9._-])
domainдомен проекта (обязателен)
passwordпароль ящика (мин. 8)
descriptionописание (необязательно)
Тело запроса
{ "name": "info", "domain": "ваш-домен", "password": "…" }
Пример ответа
{ "email": "info@ваш-домен", "id": "…" }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/mailboxes" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"name":"info","domain":"ваш-домен","password":"…"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/mailboxes", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "name": "info", "domain": "ваш-домен", "password": "…" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/mailboxes",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"name":"info","domain":"ваш-домен","password":"…"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mailboxes");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"name":"info","domain":"ваш-домен","password":"…"}',
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mailboxesmail:mailboxes

Список ящиков и групп на доменах проекта. Поле type: personal (личный), shared (общий, доступ у нескольких людей), group (распределительная группа). У групп поле membersCount.

Пример ответа
{ "mailboxes": [ { "id": "…", "email": "ivan@ваш-домен", "type": "personal", "usedBytes": 0 }, { "id": "…", "email": "team@ваш-домен", "type": "group", "membersCount": 4 } ] }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/mailboxes" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/mailboxes", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mailboxes",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mailboxes");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
DELETE/v1/mailboxes/{id}mail:mailboxes

Удалить ящик (только на своём домене).

Пример ответа
{ "deleted": true }
Примеры кода
curl -X DELETE "https://api.spine.maxsystems.az/v1/mailboxes/{id}" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/mailboxes/{id}", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.delete(
  "https://api.spine.maxsystems.az/v1/mailboxes/{id}",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mailboxes/{id}");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "DELETE",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/mailboxes/{id}/sharedmail:mailboxes

Пометить ящик общим (shared) или снять пометку. Общий ящик, это личный ящик, к которому дан доступ нескольким людям (support@, info@). Меняет поле type ящика.

Тело запроса
{ "shared": true }
Пример ответа
{ "id": "…", "shared": true }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/mailboxes/{id}/shared" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"shared":true}'
const res = await fetch("https://api.spine.maxsystems.az/v1/mailboxes/{id}/shared", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "shared": true })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/mailboxes/{id}/shared",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"shared":true}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mailboxes/{id}/shared");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"shared":true}',
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mailboxes/{id}/accessmail:mailboxes

Кто из сотрудников работает с общим ящиком под своим логином. Уровни: read (только читать), write (читать и отвечать), full (плюс управление папками и удаление).

Пример ответа
{ "access": [ { "accountId": "u", "email": "tamara@example.com", "level": "write", "folders": 5 } ] }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/mailboxes/{id}/access" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/mailboxes/{id}/access", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mailboxes/{id}/access",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mailboxes/{id}/access");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/mailboxes/{id}/accessmail:mailboxes

Открыть сотруднику доступ к общему ящику. Отдельный пароль ему не нужен: ящик появится в его почтовой программе сам, в разделе «Shared Folders». Доступ выдаётся на все папки ящика; если позже появятся новые папки, вызвать повторно.

Тело запроса
{ "mailbox": "tamara@example.com", "level": "write" }
Пример ответа
{ "ok": true, "mailbox": "tamara@example.com", "level": "write", "folders": 5 }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/mailboxes/{id}/access" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"mailbox":"tamara@example.com","level":"write"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/mailboxes/{id}/access", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "mailbox": "tamara@example.com", "level": "write" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/mailboxes/{id}/access",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"mailbox":"tamara@example.com","level":"write"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mailboxes/{id}/access");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"mailbox":"tamara@example.com","level":"write"}',
]);
$data = json_decode(curl_exec($ch), true);
DELETE/v1/mailboxes/{id}/access/{granteeId}mail:mailboxes

Закрыть сотруднику доступ к общему ящику во всех папках. granteeId, это accountId из выдачи GET /v1/mailboxes/{id}/access.

Пример ответа
{ "ok": true, "folders": 5 }
Примеры кода
curl -X DELETE "https://api.spine.maxsystems.az/v1/mailboxes/{id}/access/{granteeId}" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/mailboxes/{id}/access/{granteeId}", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.delete(
  "https://api.spine.maxsystems.az/v1/mailboxes/{id}/access/{granteeId}",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mailboxes/{id}/access/{granteeId}");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "DELETE",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mailboxes/{id}/messagesmail:read

Список последних писем в ящике (свежие сверху). Можно фильтровать по адресу, удобно для панели «переписка с контактом».

Параметры
limitсколько писем вернуть, до 100 (по умолчанию 20)
withадрес контакта: письма ОТ или К нему (переписка с контрагентом)
fromфильтр по отправителю (подстрока адреса)
toфильтр по получателю (подстрока адреса)
Пример ответа
{ "messages": [ { "id": "eaaaaab", "messageId": ["<abc@bank.az>"], "subject": "…", "from": [{"email":"…"}], "receivedAt": "…", "preview": "…" } ] }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mailboxes/{id}/messages/{msgId}mail:read

Полное письмо: тело, RFC Message-ID/In-Reply-To/References (для тредирования), сырые заголовки и список вложений с id для скачивания. Внимание: id в пути, per-mailbox (не глобальный); глобальный ключ, messageId. Чужой ящик отвечает 404, а не 403.

Пример ответа
{ "id": "eaaaaab", "messageId": "<abc@bank.az>", "inReplyTo": ["<prev@you.az>"], "references": ["<root@…>"], "subject": "…", "from": […], "to": […], "receivedAt": "…", "text": "тело письма", "headers": [{"name":"Message-ID","value":"<abc@bank.az>"}, …] }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/{msgId}" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/{msgId}", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/{msgId}",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/{msgId}");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mailboxes/{id}/messages/{msgId}/attachmentsmail:read

Вложения письма: имя, тип, размер, id для скачивания и вердикт антивируса. Файл тянуть не нужно, чтобы узнать, что он заражён или запрещён политикой.

Пример ответа
{ "attachments": [ {
    "id": "cp73raw…", "name": "akt-sverki.pdf", "contentType": "application/pdf",
    "size": 1665, "disposition": "attachment", "cid": null,
    "blocked": false, "blockedReason": null,
    "av": { "status": "clean", "threat": null, "detail": null, "scannedAt": "2026-07-26T10:48:38Z" }
  } ] }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/{msgId}/attachments" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/{msgId}/attachments", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/{msgId}/attachments",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/{msgId}/attachments");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mailboxes/{id}/messages/{msgId}/attachments/{attId}mail:read

Скачать вложение. Отдаётся потоком самим файлом (не base64), с заголовками типа и имени. Перед отдачей файл проверяется антивирусом: заражённый и запрещённый политикой не отдаются.

Параметры
attIdid вложения из списка вложений этого же письма
Пример ответа
HTTP 200
Content-Type: application/pdf
Content-Disposition: attachment; filename*=UTF-8''akt-sverki.pdf
Content-Length: 1665

<байты файла>

# 409, вложение заражено или его тип запрещён политикой проекта
# 413, вложение больше лимита проекта
# 503, антивирус недоступен: файл не отдаём (fail-closed), пробуйте позже
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/{msgId}/attachments/{attId}" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/{msgId}/attachments/{attId}", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/{msgId}/attachments/{attId}",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/{msgId}/attachments/{attId}");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mailboxes/{id}/unreadmail:read

Число непрочитанных писем в ящике, для бейджа-счётчика в вашем приложении (напр. сайдбар CRM). В пути можно указать и id ящика, и полный адрес сотрудника, так не нужно заранее знать id. Поле unread, непрочитанные во «Входящих» (число для бейджа), total, по всем папкам, folders, разбивка по папкам, где есть непрочитанные.

Пример ответа
{ "unread": 3, "total": 5, "folders": [ { "name": "Inbox", "role": "inbox", "unread": 3 }, { "name": "Черновики", "role": null, "unread": 2 } ] }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/mailboxes/{id}/unread" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/mailboxes/{id}/unread", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mailboxes/{id}/unread",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mailboxes/{id}/unread");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);

Перенос почты

POST/v1/mail-migrationsmail:migrate

Завести перенос одного ящика с другого хостинга по IMAP. Ящик у нас должен быть создан заранее: письма кладём в него по IMAP, поэтому нужен его пароль. sourceHost принимает и готовый пресет (vk, yandex, google, outlook), и адрес сервера. Для VK WorkSpace и Mail.ru нужен пароль для внешних приложений: обычный пароль их сервер отклоняет.

Параметры
targetEmailящик у нас, куда переносим
targetPasswordпароль этого ящика
sourceHostпресет (vk|yandex|google|outlook) или адрес IMAP-сервера
sourcePortпорт источника (по умолчанию 993)
sourceSecuretrue = TLS сразу, false = STARTTLS
sourceUserлогин на источнике (по умолчанию равен targetEmail)
sourcePasswordпароль на источнике (для VK и Mail.ru, пароль приложения)
Тело запроса
{ "targetEmail": "info@ваш-домен", "targetPassword": "…", "sourceHost": "vk", "sourcePassword": "…" }
Пример ответа
{ "id": "…", "targetEmail": "info@ваш-домен", "status": "pending", "copiedMessages": 0 }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/mail-migrations" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"targetEmail":"info@ваш-домен","targetPassword":"…","sourceHost":"vk","sourcePassword":"…"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/mail-migrations", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "targetEmail": "info@ваш-домен", "targetPassword": "…", "sourceHost": "vk", "sourcePassword": "…" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/mail-migrations",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"targetEmail":"info@ваш-домен","targetPassword":"…","sourceHost":"vk","sourcePassword":"…"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mail-migrations");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"targetEmail":"info@ваш-домен","targetPassword":"…","sourceHost":"vk","sourcePassword":"…"}',
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mail-migrationsmail:migrate

Задания переноса проекта и готовые пресеты популярных хостингов.

Пример ответа
{ "presets": { "vk": { "host": "imap.mail.ru", "port": 993 } }, "migrations": [ { "id": "…", "status": "done", "copiedMessages": 4210 } ] }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/mail-migrations" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/mail-migrations", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mail-migrations",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mail-migrations");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mail-migrations/{id}mail:migrate

Подробности задания: счётчики по каждой папке и журнал прогона.

Пример ответа
{ "migration": { "status": "running" }, "folders": [ { "sourcePath": "Отправленные", "targetPath": "Sent", "copied": 812, "total": 812 } ], "log": ["…"] }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/mail-migrations/{id}" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/mail-migrations/{id}", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mail-migrations/{id}",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mail-migrations/{id}");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/mail-migrations/startmail:migrate

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

Параметры
idsсписок id заданий
includeTrashпереносить корзину (по умолчанию да)
includeJunkпереносить спам (по умолчанию нет)
Тело запроса
{ "ids": ["…"], "includeTrash": true, "includeJunk": false }
Пример ответа
{ "queued": 1 }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/mail-migrations/start" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"ids":["…"],"includeTrash":true,"includeJunk":false}'
const res = await fetch("https://api.spine.maxsystems.az/v1/mail-migrations/start", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "ids": ["…"], "includeTrash": true, "includeJunk": false })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/mail-migrations/start",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"ids":["…"],"includeTrash":true,"includeJunk":false}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mail-migrations/start");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"ids":["…"],"includeTrash":true,"includeJunk":false}',
]);
$data = json_decode(curl_exec($ch), true);
DELETE/v1/mail-migrations/{id}mail:migrate

Удалить задание переноса вместе с сохранёнными доступами.

Пример ответа
{ "deleted": true }
Примеры кода
curl -X DELETE "https://api.spine.maxsystems.az/v1/mail-migrations/{id}" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/mail-migrations/{id}", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.delete(
  "https://api.spine.maxsystems.az/v1/mail-migrations/{id}",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/mail-migrations/{id}");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "DELETE",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);

Группы

GET/v1/groupsmail:mailboxes

Список распределительных групп проекта. Группа, это адрес team@домен, письмо на который разлетается всем участникам в их личные ящики.

Пример ответа
{ "groups": [ { "id": "…", "email": "team@ваш-домен", "membersCount": 4 } ] }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/groups" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/groups", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/groups",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/groups");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/groupsmail:mailboxes

Создать распределительную группу name@домен. Домен обязан быть на вашем проекте.

Параметры
domainдомен проекта (обязателен)
nameимя адреса, 2-40 символов [a-z0-9._-] (обязателен)
descriptionописание группы (необязательно)
Тело запроса
{ "domain": "ваш-домен.az", "name": "team", "description": "Вся команда" }
Пример ответа
{ "id": "…", "email": "team@ваш-домен.az" }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/groups" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"domain":"ваш-домен.az","name":"team","description":"Вся команда"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/groups", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "domain": "ваш-домен.az", "name": "team", "description": "Вся команда" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/groups",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"domain":"ваш-домен.az","name":"team","description":"Вся команда"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/groups");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"domain":"ваш-домен.az","name":"team","description":"Вся команда"}',
]);
$data = json_decode(curl_exec($ch), true);
DELETE/v1/groups/{id}mail:mailboxes

Удалить группу.

Пример ответа
{ "deleted": true }
Примеры кода
curl -X DELETE "https://api.spine.maxsystems.az/v1/groups/{id}" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/groups/{id}", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.delete(
  "https://api.spine.maxsystems.az/v1/groups/{id}",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/groups/{id}");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "DELETE",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/groups/{id}/membersmail:mailboxes

Участники группы (ящики, в которые разлетается почта).

Пример ответа
{ "members": [ { "id": "…", "email": "ivan@ваш-домен.az" } ] }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/groups/{id}/members" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/groups/{id}/members", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/groups/{id}/members",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/groups/{id}/members");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/groups/{id}/membersmail:mailboxes

Добавить ящик в группу по его адресу.

Тело запроса
{ "mailbox": "ivan@ваш-домен.az" }
Пример ответа
{ "ok": true, "member": { "id": "…", "email": "ivan@ваш-домен.az" } }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/groups/{id}/members" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"mailbox":"ivan@ваш-домен.az"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/groups/{id}/members", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "mailbox": "ivan@ваш-домен.az" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/groups/{id}/members",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"mailbox":"ivan@ваш-домен.az"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/groups/{id}/members");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"mailbox":"ivan@ваш-домен.az"}',
]);
$data = json_decode(curl_exec($ch), true);
DELETE/v1/groups/{id}/members/{userId}mail:mailboxes

Убрать участника из группы.

Пример ответа
{ "ok": true }
Примеры кода
curl -X DELETE "https://api.spine.maxsystems.az/v1/groups/{id}/members/{userId}" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/groups/{id}/members/{userId}", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.delete(
  "https://api.spine.maxsystems.az/v1/groups/{id}/members/{userId}",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/groups/{id}/members/{userId}");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "DELETE",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);

Вебмейл (SSO)

POST/v1/webmail/change-passwordпароль ящика

Сотрудник меняет пароль СВОЕГО ящика сам. Ключ API не нужен: запрос подтверждается текущим паролем, который проверяется попыткой входа. Минимум 8 символов, словарные пароли почтовый сервер отклоняет. Защита от перебора: 5 попыток на ящик и 20 на адрес источника за 15 минут.

Тело запроса
{ "email": "ivan@ваш-домен.az", "currentPassword": "…", "newPassword": "…" }
Пример ответа
{ "ok": true }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/webmail/change-password" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"email":"ivan@ваш-домен.az","currentPassword":"…","newPassword":"…"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/webmail/change-password", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "email": "ivan@ваш-домен.az", "currentPassword": "…", "newPassword": "…" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/webmail/change-password",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"email":"ivan@ваш-домен.az","currentPassword":"…","newPassword":"…"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/webmail/change-password");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"email":"ivan@ваш-домен.az","currentPassword":"…","newPassword":"…"}',
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/webmail/submit-asвход ящика (Basic/SSO)

Отправить подготовленный черновик ОТ ИМЕНИ общего ящика (support@, info@). Нужно потому, что почтовый сервер даёт читать общий ящик, но отправлять от его имени не даёт. Платформа проверяет, что у отправителя есть доступ на запись к этому ящику, и пишет каждую отправку в аудит.

Тело запроса
{ "accountId": "id ящика", "emailId": "id черновика" }
Пример ответа
{ "sent": true }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/webmail/submit-as" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"accountId":"id ящика","emailId":"id черновика"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/webmail/submit-as", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "accountId": "id ящика", "emailId": "id черновика" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/webmail/submit-as",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"accountId":"id ящика","emailId":"id черновика"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/webmail/submit-as");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"accountId":"id ящика","emailId":"id черновика"}',
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/webmail/scheduledвход ящика (Basic/SSO)

Отправить письмо позже. Письмо лежит обычным черновиком в ящике, платформа отправляет его в назначенное время (проверка раз в 30 секунд). Не раньше чем через минуту и не дальше чем на год. GET возвращает свои запланированные, DELETE /v1/webmail/scheduled/{id} отменяет.

Тело запроса
{ "accountId": "id ящика", "emailId": "id черновика", "sendAt": 1785400000000 }
Пример ответа
{ "id": "…", "sendAt": "2026-07-30T06:00:00.000Z", "status": "pending" }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/webmail/scheduled" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"accountId":"id ящика","emailId":"id черновика","sendAt":1785400000000}'
const res = await fetch("https://api.spine.maxsystems.az/v1/webmail/scheduled", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "accountId": "id ящика", "emailId": "id черновика", "sendAt": 1785400000000 })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/webmail/scheduled",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"accountId":"id ящика","emailId":"id черновика","sendAt":1785400000000}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/webmail/scheduled");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"accountId":"id ящика","emailId":"id черновика","sendAt":1785400000000}',
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/webmail/login-linkwebmail:login

Выдать сотруднику одноразовую ссылку авто-входа в ЕГО ящик в вебмейле, чтобы встроить почту в ваше приложение (напр. CRM). Ссылка живёт 60 секунд и одноразовая: открыв её (в новой вкладке или в iframe), сотрудник попадает в почту без второго логина. Ящик обязан быть на вашем домене.

Параметры
mailboxполный адрес ящика сотрудника на вашем домене (обязателен)
Тело запроса
{ "mailbox": "ivan@ваш-домен.az" }
Пример ответа
{ "url": "https://webmail.ваш-домен.az/?login_token=…", "email": "ivan@ваш-домен.az", "expiresIn": 60 }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/webmail/login-link" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"mailbox":"ivan@ваш-домен.az"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/webmail/login-link", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "mailbox": "ivan@ваш-домен.az" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/webmail/login-link",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"mailbox":"ivan@ваш-домен.az"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/webmail/login-link");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"mailbox":"ivan@ваш-домен.az"}',
]);
$data = json_decode(curl_exec($ch), true);

Домены

POST/v1/maildomainsmail:domains

Подключить домен к почте (создаёт домен, при возможности сам публикует DNS).

Тело запроса
{ "name": "ваш-домен" }
Пример ответа
{ "id": "…", "name": "ваш-домен", "autoDns": { "created": 6 }, "records": [...], "ready": false }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/maildomains" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"name":"ваш-домен"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/maildomains", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "name": "ваш-домен" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/maildomains",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"name":"ваш-домен"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/maildomains");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"name":"ваш-домен"}',
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/maildomainsmail:domains

Список подключённых доменов проекта.

Пример ответа
{ "domains": [ { "id": "…", "name": "ваш-домен" } ] }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/maildomains" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/maildomains", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/maildomains",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/maildomains");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/maildomains/{domain}mail:domains

DNS-записи и статус верификации домена.

Пример ответа
{ "name": "ваш-домен", "records": [...], "ready": true }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/maildomains/{domain}" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/maildomains/{domain}", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/maildomains/{domain}",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/maildomains/{domain}");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/maildomains/{domain}/catch-allmail:domains

Приём писем на несуществующие адреса домена: включён ли он, в какой ящик идёт и отвечаем ли отправителю. Поле warning появляется, когда приём настроен на личный ящик человека.

Пример ответа
{ "domain": "ваш-домен", "enabled": true, "mailbox": "catchall@ваш-домен", "autoReply": true }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/maildomains/{domain}/catch-all" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/maildomains/{domain}/catch-all", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/maildomains/{domain}/catch-all",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/maildomains/{domain}/catch-all");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
PUT/v1/maildomains/{domain}/catch-allmail:domains

Включить, изменить или выключить приём на несуществующие адреса. Ящик обязан существовать и быть на этом же домене. Пока приём включён, отправителю уходит автоответ на английском: такого адреса нет, отвечать сюда бессмысленно, вот адрес поддержки домена. Автоответ уходит только живым людям: рассылкам, роботам и отчётам о недоставке не отвечаем, одному адресу пишем не чаще раза в сутки.

Параметры
mailboxящик приёма на этом домене, либо null чтобы выключить (тогда письма на несуществующие адреса отбиваются с отказом)
autoReplyотвечать ли отправителю, что адреса нет (по умолчанию true)
Тело запроса
{ "mailbox": "catchall@ваш-домен", "autoReply": true }
Пример ответа
{ "domain": "ваш-домен", "enabled": true, "mailbox": "catchall@ваш-домен", "autoReply": true }
Примеры кода
curl -X PUT "https://api.spine.maxsystems.az/v1/maildomains/{domain}/catch-all" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"mailbox":"catchall@ваш-домен","autoReply":true}'
const res = await fetch("https://api.spine.maxsystems.az/v1/maildomains/{domain}/catch-all", {
  method: "PUT",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "mailbox": "catchall@ваш-домен", "autoReply": true })
});
const data = await res.json();
import requests

r = requests.put(
  "https://api.spine.maxsystems.az/v1/maildomains/{domain}/catch-all",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"mailbox":"catchall@ваш-домен","autoReply":true}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/maildomains/{domain}/catch-all");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "PUT",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"mailbox":"catchall@ваш-домен","autoReply":true}',
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/domains/minedomains:read

Сроки регистрации доменов проекта: сколько дней осталось, у какого регистратора, где домен используется. Поле source говорит, откуда дата: rdap или whois, из реестра, manual, вбита в консоли (у части зон, например .az, машинного источника нет вовсе). Если даты нет, expiresAt = null и state = unknown.

Пример ответа
{ "domains": [ { "domain": "ваш-домен", "registrar": "…", "expiresAt": "2027-03-01T00:00:00.000Z", "daysLeft": 208, "state": "ok", "uses": ["почта"], "source": "rdap" } ], "alertDays": [40, 30, 15, 5, 1] }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/domains/mine" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/domains/mine", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/domains/mine",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/domains/mine");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);

Защита

POST/v1/protection/turnstile/verifyprotection:verify

Проверить ответ Turnstile из формы вашего сайта. Секрет хранится у нас и наружу не отдаётся: класть его в браузер нельзя, иначе проверку обойдут за минуту. Вы присылаете только токен, который вернул виджет, и домен, для которого он выдан.

Параметры
tokenзначение поля cf-turnstile-response из вашей формы (обязательно)
domainдомен, для которого настроены ключи Turnstile
Тело запроса
{ "token": "0.abc…", "domain": "ваш-домен" }
Пример ответа
{ "ok": true, "errors": [] }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/protection/turnstile/verify" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"token":"0.abc…","domain":"ваш-домен"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/protection/turnstile/verify", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "token": "0.abc…", "domain": "ваш-домен" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/protection/turnstile/verify",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"token":"0.abc…","domain":"ваш-домен"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/protection/turnstile/verify");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"token":"0.abc…","domain":"ваш-домен"}',
]);
$data = json_decode(curl_exec($ch), true);

Безопасность

GET/v1/security/reportsecurity:read

Отчёт безопасности своих доменов: оценка A-F и открытые находки (TLS, DNS, почтовая аутентификация, HTTP-заголовки). Только чтение, сканы отсюда не запускаются. Находки со статусом «не проверено» (unknown) на оценку не влияют: не проверено это не «чисто».

Пример ответа
{ "project": "ваш-проект", "grade": "B", "score": 82, "open": 3, "findings": [ { "target": "ваш-домен", "category": "mail", "severity": "medium", "title": "DMARC в режиме наблюдения (p=none)", "detail": "…" } ] }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/security/report" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/security/report", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/security/report",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/security/report");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);

DNS

GET/v1/dns/{zone}/recordsdns:read

Список DNS-записей зоны (зона должна быть привязана к проекту).

Пример ответа
{ "zone": "ваш-домен", "records": [ { "id": "…", "type": "A", "name": "@", "data": "1.2.3.4" } ] }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/dns/{zone}/records" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/dns/{zone}/records", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/dns/{zone}/records",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/dns/{zone}/records");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/dns/{zone}/recordsdns:records

Создать DNS-запись в зоне проекта.

Параметры
typeA | AAAA | TXT | MX | CNAME | NS | SRV | CAA
nameимя записи ('@' для корня)
dataзначение
priority / ttlдля MX / TTL (необязательно)
Тело запроса
{ "type": "CNAME", "name": "app", "data": "target.example.com" }
Пример ответа
{ "created": true }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/dns/{zone}/records" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"type":"CNAME","name":"app","data":"target.example.com"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/dns/{zone}/records", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "type": "CNAME", "name": "app", "data": "target.example.com" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/dns/{zone}/records",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"type":"CNAME","name":"app","data":"target.example.com"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/dns/{zone}/records");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"type":"CNAME","name":"app","data":"target.example.com"}',
]);
$data = json_decode(curl_exec($ch), true);
DELETE/v1/dns/{zone}/records/{id}dns:records

Удалить DNS-запись. Для GoDaddy добавь ?data=<значение>.

Пример ответа
{ "deleted": true }
Примеры кода
curl -X DELETE "https://api.spine.maxsystems.az/v1/dns/{zone}/records/{id}" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/dns/{zone}/records/{id}", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.delete(
  "https://api.spine.maxsystems.az/v1/dns/{zone}/records/{id}",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/dns/{zone}/records/{id}");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "DELETE",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);

Вебхуки

POST/v1/webhookswebhooks

Подписаться на события: платформа будет слать POST на ваш URL. secret в ответе, один раз.

Параметры
urlваш https-адрес приёма (обязателен)
eventsписьмо в ящике: mail.received (получено), mail.sent_folder (сотрудник отправил из ящика) · отправка через API: mail.sent, mail.delivered, mail.bounced, mail.complained, mail.delayed, mail.rejected · вовлечённость (при track:true): mail.opened, mail.clicked, mail.unsubscribed
Тело запроса
{ "url": "https://ваш-сервер/webhook", "events": ["mail.received", "mail.bounced"] }
Пример ответа
{ "id": "…", "url": "…", "events": [...], "secret": "whsec_…" }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/webhooks" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://ваш-сервер/webhook","events":["mail.received","mail.bounced"]}'
const res = await fetch("https://api.spine.maxsystems.az/v1/webhooks", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "url": "https://ваш-сервер/webhook", "events": ["mail.received", "mail.bounced"] })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/webhooks",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"url":"https://ваш-сервер/webhook","events":["mail.received","mail.bounced"]}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/webhooks");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"url":"https://ваш-сервер/webhook","events":["mail.received","mail.bounced"]}',
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/webhookswebhooks

Список ваших вебхуков.

Пример ответа
{ "webhooks": [ { "id": "…", "url": "…", "events": [...], "active": true } ] }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/webhooks" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/webhooks", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/webhooks",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/webhooks");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
PATCH/v1/webhooks/{id}webhooks

Изменить подписку: список событий, адрес приёма, включить или выключить. СЕКРЕТ НЕ МЕНЯЕТСЯ, поэтому ваш верификатор подписи трогать не нужно. Передавайте только те поля, которые меняете.

Параметры
urlновый https-адрес приёма (необязательно)
eventsновый список событий целиком (необязательно)
activefalse, приостановить доставку, не теряя подписку и лог (необязательно)
Тело запроса
{ "events": ["mail.received", "mail.sent_folder", "mail.bounced"] }
Пример ответа
{ "id": "…", "url": "…", "events": [...], "active": true }
Примеры кода
curl -X PATCH "https://api.spine.maxsystems.az/v1/webhooks/{id}" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"events":["mail.received","mail.sent_folder","mail.bounced"]}'
const res = await fetch("https://api.spine.maxsystems.az/v1/webhooks/{id}", {
  method: "PATCH",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "events": ["mail.received", "mail.sent_folder", "mail.bounced"] })
});
const data = await res.json();
import requests

r = requests.patch(
  "https://api.spine.maxsystems.az/v1/webhooks/{id}",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"events":["mail.received","mail.sent_folder","mail.bounced"]}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/webhooks/{id}");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "PATCH",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"events":["mail.received","mail.sent_folder","mail.bounced"]}',
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/webhooks/{id}/rotate-secretwebhooks

Сменить секрет подписи (утечка, уход сотрудника). Новый показывается ОДИН раз. Старый перестаёт работать сразу: события пойдут с новой подписью, и до подмены секрета у себя вы будете их отбивать.

Пример ответа
{ "id": "…", "secret": "whsec_…" }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/webhooks/{id}/rotate-secret" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/webhooks/{id}/rotate-secret", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/webhooks/{id}/rotate-secret",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/webhooks/{id}/rotate-secret");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
DELETE/v1/webhooks/{id}webhooks

Удалить вебхук. Чтобы просто поменять события или адрес, используйте PATCH: пересоздание выдаст новый секрет.

Пример ответа
{ "deleted": true }
Примеры кода
curl -X DELETE "https://api.spine.maxsystems.az/v1/webhooks/{id}" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/webhooks/{id}", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.delete(
  "https://api.spine.maxsystems.az/v1/webhooks/{id}",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/webhooks/{id}");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "DELETE",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/webhooks/{id}/deliverieswebhooks

Лог доставок вебхука: статус (pending/delivered/failed), коды, попытки и точное подписанное тело, удобно для отладки подписи.

Пример ответа
{ "deliveries": [ { "id": 42, "event": "mail.received", "status": "failed", "httpCode": 401, "attempts": 7, "payload": "{…}" } ] }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/webhooks/{id}/deliveries" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/webhooks/{id}/deliveries", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/webhooks/{id}/deliveries",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/webhooks/{id}/deliveries");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/webhooks/{id}/deliveries/{deliveryId}/redeliverwebhooks

Переотправить конкретную доставку сейчас (например, после того как починили верификатор подписи).

Пример ответа
{ "status": "delivered", "httpCode": 200 }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/webhooks/{id}/deliveries/{deliveryId}/redeliver" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/webhooks/{id}/deliveries/{deliveryId}/redeliver", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/webhooks/{id}/deliveries/{deliveryId}/redeliver",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/webhooks/{id}/deliveries/{deliveryId}/redeliver");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);

Telegram-боты

POST/v1/telegram/mybotstelegram:bots

Подключить существующего бота по токену (из BotFather) к платформе.

Тело запроса
{ "token": "123456:ABC-…", "note": "бот проекта" }
Пример ответа
{ "id": "…", "botId": 123456, "username": "MyBot", "name": "My Bot" }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/telegram/mybots" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"token":"123456:ABC-…","note":"бот проекта"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/telegram/mybots", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "token": "123456:ABC-…", "note": "бот проекта" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/telegram/mybots",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"token":"123456:ABC-…","note":"бот проекта"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/telegram/mybots");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"token":"123456:ABC-…","note":"бот проекта"}',
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/telegram/mybots/bulktelegram:bots

Массовый импорт: подключить сразу много ботов по списку токенов (из BotFather). tokens, массив или текст (по токену на строку).

Тело запроса
{ "tokens": ["123456:AAA…", "234567:BBB…"] }
Пример ответа
{ "total": 2, "added": 2, "failed": 0, "results": [ { "ok": true, "username": "MyBot", "botId": 123456 } ] }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/telegram/mybots/bulk" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"tokens":["123456:AAA…","234567:BBB…"]}'
const res = await fetch("https://api.spine.maxsystems.az/v1/telegram/mybots/bulk", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "tokens": ["123456:AAA…", "234567:BBB…"] })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/telegram/mybots/bulk",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"tokens":["123456:AAA…","234567:BBB…"]}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/telegram/mybots/bulk");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"tokens":["123456:AAA…","234567:BBB…"]}',
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/telegram/mybotstelegram:bots

Список ботов проекта.

Пример ответа
{ "bots": [ { "id": "…", "username": "MyBot", "webhookSet": true } ] }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/telegram/mybots" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/telegram/mybots", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/telegram/mybots",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/telegram/mybots");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/telegram/mybots/{id}telegram:bots

Живой статус бота (getMe + вебхук + команды).

Пример ответа
{ "username": "MyBot", "me": {...}, "webhook": {...}, "commands": [...] }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/telegram/mybots/{id}" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/telegram/mybots/{id}", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/telegram/mybots/{id}",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/telegram/mybots/{id}");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/telegram/mybots/{id}/webhooktelegram:bots

Направить апдейты бота на ваш сервер (SPINE принимает от Telegram и пересылает вам).

Тело запроса
{ "forwardUrl": "https://ваш-сервер/tg" }
Пример ответа
{ "hookUrl": "https://api.spine.maxsystems.az/v1/tg/hook/123456", "forwardUrl": "…" }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/telegram/mybots/{id}/webhook" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"forwardUrl":"https://ваш-сервер/tg"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/telegram/mybots/{id}/webhook", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "forwardUrl": "https://ваш-сервер/tg" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/telegram/mybots/{id}/webhook",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"forwardUrl":"https://ваш-сервер/tg"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/telegram/mybots/{id}/webhook");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"forwardUrl":"https://ваш-сервер/tg"}',
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/telegram/mypresetstelegram:bots

Пресеты «фабрика под ключ», доступные проекту (общие + свои): команды, вебхук, профиль.

Пример ответа
{ "presets": [ { "id": "…", "name": "Поддержка", "forwardUrl": "https://…" } ] }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/telegram/mypresets" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/telegram/mypresets", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/telegram/mypresets",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/telegram/mypresets");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/telegram/mybots/create-linktelegram:bots

Фабрика: дип-линк создания нового бота. Пользователь подтверждает в Telegram, бот сам подключится к проекту. С presetId бот сразу разворачивается «под ключ» (команды + вебхук + профиль).

Тело запроса
{ "suggestedUsername": "myproject_bot", "presetId": "…(необязательно)" }
Пример ответа
{ "link": "https://t.me/newbot/spinemsbot/myproject_bot" }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/telegram/mybots/create-link" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"suggestedUsername":"myproject_bot","presetId":"…(необязательно)"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/telegram/mybots/create-link", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "suggestedUsername": "myproject_bot", "presetId": "…(необязательно)" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/telegram/mybots/create-link",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"suggestedUsername":"myproject_bot","presetId":"…(необязательно)"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/telegram/mybots/create-link");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"suggestedUsername":"myproject_bot","presetId":"…(необязательно)"}',
]);
$data = json_decode(curl_exec($ch), true);

Бренд

GET/v1/brandbrand:read

Бренд-кит вашего проекта: название, слоган, палитра цветов, шрифты, ссылки и логотипы/файлы (с прямыми URL для встраивания). Полезно для авто-подстановки фирменного стиля в письма, сайты и приложения.

Пример ответа
{
  "tenant": "acme",
  "displayName": "Acme Inc.",
  "tagline": "Строим будущее",
  "colors": [{ "name": "Основной", "hex": "#5b8def", "role": "primary" }],
  "fonts": [{ "name": "Inter", "role": "текст" }],
  "links": [{ "label": "Сайт", "url": "https://acme.example" }],
  "logos": [{ "variant": "primary", "format": "svg", "downloadUrl": "https://api.spine.maxsystems.az/v1/public/brand/…" }],
  "files": [{ "label": "Брендбук", "format": "pdf", "downloadUrl": "https://api.spine.maxsystems.az/v1/public/brand/…" }]
}
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/brand" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/brand", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/brand",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/brand");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);

Проект

GET/v1/projectproject:read

Карточка вашего проекта одним запросом: что подключено и активно (домены, ящики, сертификаты со сроками, API-ключи и скоупы, вебхуки, Telegram-боты, бренд-кит, тарифы и счета) и что требует внимания (истекающие серты, просроченные счета). Поле client это ваш клиент-владелец: у одного клиента может быть несколько проектов. Self-view: ключ видит только СВОЙ проект.

Пример ответа
{
  "tenant": { "slug": "acme", "name": "Acme Inc.", "status": "active" },
  "client": { "slug": "acme-group", "name": "Acme Group", "status": "active" },
  "domains": [{ "domain": "acme.az", "whitelabel": true, "branding": true }],
  "mail": { "domains": 1, "mailboxes": 8 },
  "certificates": { "total": 2, "list": [{ "subject": "*.acme.az", "status": "active", "notAfter": "2026-10-14", "autoRenew": true }] },
  "dns": { "managedZones": 1, "monitors": 0 },
  "api": { "clients": 1, "keys": 2, "scopes": ["mail:send","mail:read"], "requests": 1240, "webhooks": 1 },
  "telegram": { "bots": 1 },
  "brand": { "hasProfile": true, "assets": 5 },
  "billing": { "customers": 1, "activeSubscriptions": 2, "openInvoices": 1, "dueMinor": 5900, "currency": "AZN" },
  "attention": [{ "kind": "cert-expiring", "text": "Сертификат *.acme.az истекает через 12 дн." }]
}
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/project" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/project", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/project",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/project");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/project/auditaudit:read

Журнал событий вашего проекта: кто, что и когда менял (сертификаты, DNS, API-ключи, бренд, счета). Свежие сверху. Параметр limit, до 500.

Параметры
limitсколько событий вернуть, до 500 (по умолчанию 100)
Пример ответа
{ "events": [ { "ts": "2026-07-22T18:40:00Z", "action": "cert.issue", "target": "*.acme.az", "actor": "apikey:spine_sk_ab12cd", "source": "api" } ] }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/project/audit" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/project/audit", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/project/audit",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/project/audit");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);

Сайт / SEO

GET/v1/site-configsiteconfig:read

Сырой SEO/site-конфиг вашего проекта (meta по языкам, OG, аналитика, robots, sitemap, JSON-LD, verification). Нужен, только если рендерите <head> сами. Проще, брать готовые артефакты ниже (их отдаёт SPINE, ключ не нужен).

Пример ответа
{ "tenant": "acme", "meta": { "en": { "title": "Acme", "description": "…" } }, "og": { "siteName": "Acme" }, "analytics": { "ga4": "G-XXXX" }, "robots": { "disallow": ["/admin"] } }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/site-config" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/site-config", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/site-config",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/site-config");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/site/{tenant}/robots.txt

Готовый robots.txt проекта (публично, ключ не нужен). Проксируйте /robots.txt вашего сайта сюда, правим в консоли, у вас применяется без редеплоя.

Пример ответа
User-agent: *
Disallow: /admin

Sitemap: https://acme.az/sitemap.xml
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/site/{tenant}/robots.txt"
const res = await fetch("https://api.spine.maxsystems.az/v1/site/{tenant}/robots.txt", {
  method: "GET"
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/site/{tenant}/robots.txt"
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/site/{tenant}/robots.txt");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/site/{tenant}/sitemap.xml

Готовый sitemap.xml из URL проекта (публично). Проксируйте /sitemap.xml сюда.

Пример ответа
<?xml version="1.0" encoding="UTF-8"?><urlset>…</urlset>
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/site/{tenant}/sitemap.xml"
const res = await fetch("https://api.spine.maxsystems.az/v1/site/{tenant}/sitemap.xml", {
  method: "GET"
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/site/{tenant}/sitemap.xml"
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/site/{tenant}/sitemap.xml");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/site/{tenant}/head

Готовый <head>-фрагмент: meta, canonical, Open Graph, Twitter, JSON-LD, verification-метки и сниппеты аналитики (GA4/Метрика/Pixel). Вставьте один include в <head>, SPINE рендерит всё сам. Параметры: path (текущий путь для canonical/og:url), lang (язык meta).

Параметры
pathпуть страницы для canonical и og:url (например /events/123)
langязык meta-тегов (ключ из секции meta, например ru/en/az)
Пример ответа
<title>Acme</title>
<meta name="description" content="…">
<meta property="og:image" content="…/og.png">
<script>…GA4…</script>
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/site/{tenant}/head"
const res = await fetch("https://api.spine.maxsystems.az/v1/site/{tenant}/head", {
  method: "GET"
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/site/{tenant}/head"
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/site/{tenant}/head");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/site/{tenant}/og.png

Брендированная OG-картинка 1200×630, сгенерированная из бренд-кита проекта (лого, цвета). Параметры title/subtitle. Ссылку выдаёт /head автоматически, отдельно нужна, если хотите свою подпись на превью страницы.

Параметры
titleзаголовок на картинке (по умолчанию название бренда)
subtitleподзаголовок (необязательно)
Пример ответа
(image/png, картинка 1200×630)
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/site/{tenant}/og.png"
const res = await fetch("https://api.spine.maxsystems.az/v1/site/{tenant}/og.png", {
  method: "GET"
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/site/{tenant}/og.png"
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/site/{tenant}/og.png");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/site/{tenant}/favicon.png

Favicon-набор из логотипа проекта: favicon.png/ico (32), apple-touch-icon.png (180), icon-192/512.png и site.webmanifest. Ссылки на них /head тоже проставляет сам. Меняете лого в бренд-ките, favicon у проекта обновляется.

Пример ответа
(image/png, иконка)
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/site/{tenant}/favicon.png"
const res = await fetch("https://api.spine.maxsystems.az/v1/site/{tenant}/favicon.png", {
  method: "GET"
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/site/{tenant}/favicon.png"
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/site/{tenant}/favicon.png");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);

Секреты

GET/v1/secretssecrets:read

Все секреты проекта в окружении одной картой, грузите как переменные окружения при старте. Значения шифруются у нас (AES-256-GCM), отдаются только вашему ключу по TLS. Параметр env (по умолчанию prod).

Параметры
envокружение: prod|staging|dev|любое (по умолчанию prod)
Пример ответа
{ "env": "prod", "secrets": { "DB_PASSWORD": "…", "STRIPE_KEY": "…" } }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/secrets" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/secrets", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/secrets",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/secrets");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/secrets.envsecrets:read

Готовый .env-файл со всеми секретами окружения (NAME="value"). Проект пишет его к себе одним запросом.

Параметры
envокружение (по умолчанию prod)
Пример ответа
DB_PASSWORD="…"
STRIPE_KEY="…"
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/secrets.env" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/secrets.env", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/secrets.env",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/secrets.env");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/secrets/getsecrets:read

Одно значение по имени (имя в теле, имена могут быть путём вида db/password).

Тело запроса
{ "env": "prod", "name": "db/password" }
Пример ответа
{ "name": "db/password", "env": "prod", "value": "…" }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/secrets/get" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"env":"prod","name":"db/password"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/secrets/get", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "env": "prod", "name": "db/password" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/secrets/get",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"env":"prod","name":"db/password"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/secrets/get");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"env":"prod","name":"db/password"}',
]);
$data = json_decode(curl_exec($ch), true);
PUT/v1/secretssecrets:write

Записать свой секрет (создаёт новую версию, старые хранятся для отката).

Тело запроса
{ "env": "prod", "name": "stripe/key", "value": "sk_live_…", "description": "ключ Stripe" }
Пример ответа
{ "name": "stripe/key", "env": "prod", "version": 2 }
Примеры кода
curl -X PUT "https://api.spine.maxsystems.az/v1/secrets" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"env":"prod","name":"stripe/key","value":"sk_live_…","description":"ключ Stripe"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/secrets", {
  method: "PUT",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "env": "prod", "name": "stripe/key", "value": "sk_live_…", "description": "ключ Stripe" })
});
const data = await res.json();
import requests

r = requests.put(
  "https://api.spine.maxsystems.az/v1/secrets",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"env":"prod","name":"stripe/key","value":"sk_live_…","description":"ключ Stripe"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/secrets");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "PUT",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"env":"prod","name":"stripe/key","value":"sk_live_…","description":"ключ Stripe"}',
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/secrets/generatesecrets:write

Сгенерировать стойкий секрет и сохранить его. kind: base64|hex|alnum|uuid, length до 512. Значение возвращается один раз.

Тело запроса
{ "env": "prod", "name": "jwt/secret", "kind": "base64", "length": 48 }
Пример ответа
{ "name": "jwt/secret", "env": "prod", "version": 1, "value": "…сгенерировано…" }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/secrets/generate" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"env":"prod","name":"jwt/secret","kind":"base64","length":48}'
const res = await fetch("https://api.spine.maxsystems.az/v1/secrets/generate", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "env": "prod", "name": "jwt/secret", "kind": "base64", "length": 48 })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/secrets/generate",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"env":"prod","name":"jwt/secret","kind":"base64","length":48}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/secrets/generate");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"env":"prod","name":"jwt/secret","kind":"base64","length":48}',
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/secrets/leasesecrets:read

Короткоживущая выдача: одноразовый токен-ссылка на один секрет (или всю карту), чтобы передать значение процессу/человеку, не светя долгоживущий ключ. ttl в секундах (30..86400, по умолчанию 300), singleUse по умолчанию true.

Тело запроса
{ "env": "prod", "name": "db/password", "ttl": 120 }
Пример ответа
{ "token": "spine_lease_…", "url": "https://api.spine.maxsystems.az/v1/secrets/lease/spine_lease_…", "expiresAt": "…", "singleUse": true }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/secrets/lease" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"env":"prod","name":"db/password","ttl":120}'
const res = await fetch("https://api.spine.maxsystems.az/v1/secrets/lease", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "env": "prod", "name": "db/password", "ttl": 120 })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/secrets/lease",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"env":"prod","name":"db/password","ttl":120}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/secrets/lease");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"env":"prod","name":"db/password","ttl":120}',
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/secrets/lease/{token}

Погасить lease по токену (сам токен, доступ, ключ не нужен). Работает один раз до истечения TTL. Возвращает значение секрета (или карту, если lease на всю).

Пример ответа
{ "env": "prod", "name": "db/password", "value": "…" }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/secrets/lease/{token}"
const res = await fetch("https://api.spine.maxsystems.az/v1/secrets/lease/{token}", {
  method: "GET"
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/secrets/lease/{token}"
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/secrets/lease/{token}");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);

Верификация

POST/v1/otp/sendotp:send

Выдать одноразовый код и доставить его: SMS или Telegram. Код у нас хранится только в виде отпечатка и не отдаётся никому, кроме получателя. Ключ с пометкой «песочница» ничего никуда не шлёт и возвращает код прямо в ответе, чтобы интеграцию можно было собрать без расхода денег. Условия канала при этом проверяются как в бою: нет учётки SMS - 503, пользователь не привязал Telegram - 409.

Параметры
channelsms (по умолчанию) | telegram
toномер телефона в международном виде (994501234567) или ваш идентификатор пользователя для Telegram
purposeзачем код: login, payment, reset. Попадает в журнал (необязательно)
ttlSecondsсрок жизни кода, 60..3600 (по умолчанию из настроек проекта)
codeLengthдлина кода, 4..8 (по умолчанию 6)
Тело запроса
{ "channel": "sms", "to": "994501234567", "purpose": "login" }
Пример ответа
{ "verificationId": "3f2a…", "channel": "sms", "to": "994501234567", "expiresAt": "…", "resendAfter": "…", "attemptsLeft": 5, "delivery": "sent", "sandbox": false }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/otp/send" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"channel":"sms","to":"994501234567","purpose":"login"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/otp/send", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "channel": "sms", "to": "994501234567", "purpose": "login" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/otp/send",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"channel":"sms","to":"994501234567","purpose":"login"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/otp/send");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"channel":"sms","to":"994501234567","purpose":"login"}',
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/otp/verifyotp:verify

Проверить код. Правильный путь, по verificationId; можно и по получателю, тогда берётся его последний активный код. Успех гасит код. Ответ всегда объясняет причину отказа: no_active_code, expired, wrong_code, too_many_attempts.

Параметры
verificationIdидентификатор из ответа на send (рекомендуется)
toполучатель, если verificationId не сохраняли
channelнужен вместе с to, чтобы одинаково нормализовать номер
codeкод от пользователя (обязателен)
Тело запроса
{ "verificationId": "3f2a…", "code": "123456" }
Пример ответа
{ "valid": true, "verificationId": "3f2a…", "channel": "sms", "to": "994501234567", "purpose": "login" }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/otp/verify" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"verificationId":"3f2a…","code":"123456"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/otp/verify", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "verificationId": "3f2a…", "code": "123456" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/otp/verify",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"verificationId":"3f2a…","code":"123456"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/otp/verify");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"verificationId":"3f2a…","code":"123456"}',
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/otp/resendotp:send

Повторная отправка в рамках той же верификации: выдаётся новый код, счётчик неверных попыток сохраняется. Пауза между повторами своя (по умолчанию 60 секунд), раньше срока приходит 429.

Тело запроса
{ "verificationId": "3f2a…" }
Пример ответа
{ "verificationId": "3f2a…", "expiresAt": "…", "resendAfter": "…", "delivery": "sent" }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/otp/resend" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"verificationId":"3f2a…"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/otp/resend", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "verificationId": "3f2a…" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/otp/resend",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"verificationId":"3f2a…"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/otp/resend");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"verificationId":"3f2a…"}',
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/otp/verifications/{id}otp:verify

Состояние попытки: подтверждена или нет, сколько попыток осталось, что с доставкой. Кода здесь нет.

Пример ответа
{ "verificationId": "3f2a…", "verified": false, "attemptsLeft": 4, "delivery": "sent", "sandbox": false }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/otp/verifications/{id}" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/otp/verifications/{id}", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/otp/verifications/{id}",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/otp/verifications/{id}");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/otp/verifications/{id}/deliveryotp:verify

Спросить оператора, что стало с сообщением. «Приняли в очередь» и «доставлено» это разные вещи, поэтому статус доставки отдельный: delivered, pending, failed или unknown.

Пример ответа
{ "delivery": "delivered", "note": "доставлено" }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/otp/verifications/{id}/delivery" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/otp/verifications/{id}/delivery", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/otp/verifications/{id}/delivery",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/otp/verifications/{id}/delivery");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/otp/settingsotp:verify

Что доступно вашему проекту: включённые каналы, длина кода, срок жизни, пауза между повторами и действующие лимиты. Полезно показать пользователю честный таймер вместо отказа наугад.

Пример ответа
{ "project": "ваш-проект", "channels": ["sms","telegram","totp"], "codeLength": 6, "ttlSeconds": 300, "limits": { "perIdentifierHour": 5, "perIdentifierDay": 15, "perProjectDay": 500, "perIpHour": 20 } }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/otp/settings" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/otp/settings", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/otp/settings",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/otp/settings");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/otp/telegram/linkotp:send

Ссылка привязки Telegram. Бот не может написать человеку первым, поэтому сначала пользователь открывает эту ссылку и нажимает «Старт» у бота вашего проекта, и только после этого коды доставляются в Telegram. Ссылка одноразовая и живёт 15 минут. Если пользователь уже привязан, вернётся linked: true.

Тело запроса
{ "identifier": "user-42" }
Пример ответа
{ "url": "https://t.me/вашбот?start=otp_…", "bot": "вашбот", "expiresAt": "…", "linked": false }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/otp/telegram/link" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"identifier":"user-42"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/otp/telegram/link", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "identifier": "user-42" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/otp/telegram/link",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"identifier":"user-42"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/otp/telegram/link");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"identifier":"user-42"}',
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/otp/totp/enrollotp:totp

Начать подключение приложения-аутентификатора (Google Authenticator, 1Password и любое другое по RFC 6238). Возвращает секрет и otpauth-ссылку для QR-кода. До подтверждения кодом TOTP выключен: иначе человек запишет секрет с ошибкой и потеряет доступ, думая, что всё настроил.

Тело запроса
{ "identifier": "user-42", "issuer": "Ваш проект" }
Пример ответа
{ "secret": "JBSWY3DP…", "otpauthUrl": "otpauth://totp/…", "enabled": false }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/otp/totp/enroll" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"identifier":"user-42","issuer":"Ваш проект"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/otp/totp/enroll", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "identifier": "user-42", "issuer": "Ваш проект" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/otp/totp/enroll",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"identifier":"user-42","issuer":"Ваш проект"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/otp/totp/enroll");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"identifier":"user-42","issuer":"Ваш проект"}',
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/otp/totp/confirmotp:totp

Подтвердить подключение кодом из приложения и включить TOTP.

Тело запроса
{ "identifier": "user-42", "code": "123456" }
Пример ответа
{ "enabled": true }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/otp/totp/confirm" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"identifier":"user-42","code":"123456"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/otp/totp/confirm", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "identifier": "user-42", "code": "123456" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/otp/totp/confirm",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"identifier":"user-42","code":"123456"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/otp/totp/confirm");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"identifier":"user-42","code":"123456"}',
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/otp/totp/verifyotp:totp

Проверить код из приложения либо резервный код. Один и тот же код второй раз не принимается (reason: code_already_used), резервный код после использования вычёркивается.

Тело запроса
{ "identifier": "user-42", "code": "123456" }
Пример ответа
{ "valid": true }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/otp/totp/verify" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"identifier":"user-42","code":"123456"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/otp/totp/verify", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "identifier": "user-42", "code": "123456" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/otp/totp/verify",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"identifier":"user-42","code":"123456"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/otp/totp/verify");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"identifier":"user-42","code":"123456"}',
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/otp/totp/backup-codesotp:totp

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

Тело запроса
{ "identifier": "user-42" }
Пример ответа
{ "codes": ["A1B2C3D4E5", "…"] }
Примеры кода
curl -X POST "https://api.spine.maxsystems.az/v1/otp/totp/backup-codes" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"identifier":"user-42"}'
const res = await fetch("https://api.spine.maxsystems.az/v1/otp/totp/backup-codes", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ "identifier": "user-42" })
});
const data = await res.json();
import requests

r = requests.post(
  "https://api.spine.maxsystems.az/v1/otp/totp/backup-codes",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type": "application/json"},
  data='''{"identifier":"user-42"}'''
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/otp/totp/backup-codes");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"identifier":"user-42"}',
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/otp/totpotp:totp

Состояние TOTP пользователя (параметр identifier): подключён, включён, сколько резервных кодов осталось.

Пример ответа
{ "enrolled": true, "enabled": true, "backupLeft": 8, "backupUsed": 2 }
Примеры кода
curl -X GET "https://api.spine.maxsystems.az/v1/otp/totp" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/otp/totp", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/otp/totp",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/otp/totp");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
DELETE/v1/otp/totpotp:totp

Отключить TOTP у пользователя и удалить его секрет.

Пример ответа
{ "removed": true }
Примеры кода
curl -X DELETE "https://api.spine.maxsystems.az/v1/otp/totp" \
  -H "Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"
const res = await fetch("https://api.spine.maxsystems.az/v1/otp/totp", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"
  }
});
const data = await res.json();
import requests

r = requests.delete(
  "https://api.spine.maxsystems.az/v1/otp/totp",
  headers={"Authorization": "Bearer spine_sk_ВАШ_КЛЮЧ"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/otp/totp");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "DELETE",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer spine_sk_ВАШ_КЛЮЧ"],
]);
$data = json_decode(curl_exec($ch), true);
WEBHOOKПроверка подписи и доставка

Каждая доставка, POST с заголовками X-Spine-Event (тип события) и X-Spine-Signature: sha256=<hex>. Тело: {"event":"…","ts":"…","data":{…}}.

Подпись = HMAC-SHA256 от сырого тела запроса (байты как пришли, до разбора JSON). Ключ, секрет whsec_… как есть, строкой целиком: НЕ декодировать из base64 и НЕ срезать префикс (это не Svix). Результат, hex, с префиксом sha256=. Сравнивайте в константное время.

Верификатор (Node.js)
const crypto = require("crypto");
function verifySpineWebhook(secret, rawBody, sigHeader) {
  // secret, строка ЦЕЛИКОМ, включая префикс whsec_ (не декодировать/не обрезать)
  // rawBody, СЫРОЕ тело запроса (Buffer/строка), до JSON.parse
  // sigHeader- значение заголовка X-Spine-Signature ("sha256=…")
  const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(sigHeader || ""), b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Доставка и повторы

Одна немедленная попытка; при неуспехе, durable-ретраи с экспоненциальным бэкоффом (≈ через 1 мин, 5 мин, 30 мин, 2 ч, 6 ч, 12 ч, до 7 попыток за ~21 час), очередь переживает перезапуск. Успех = ваш ответ 2xx. Лог доставок, GET /v1/webhooks/{id}/deliveries (видно статус, коды и точное подписанное тело); переотправить конкретную, POST /v1/webhooks/{id}/deliveries/{deliveryId}/redeliver. Отвечайте быстро, тяжёлую обработку, в фон (доставка «хотя бы один раз», делайте обработчик идемпотентным).

Событие mail.received, что внутри data

Для входящего письма отдаём его метаданные (message), RFC-заголовки треда (rfc) и сигналы фрода (security):

"data": {
  "mailbox": "you@ваш-домен.az", "mailboxId": "c",
  "folder": "Inbox", "mailboxRole": "inbox",   // где лежит письмо (роль: inbox|junk|trash|archive|null)
  "message": {
    "id": "eaaaaab",                          // per-mailbox id (НЕ глобальный, см. ниже)
    "from": [{ "email": "sender@bank.az" }], "to": [{ "email": "you@ваш-домен.az" }],
    "subject": "…", "receivedAt": "…", "preview": "…"
  },
  "rfc": {
    "messageId": "abc123@bank.az",             // строка или null: ключ письма, БЕЗ угловых скобок
    "inReplyTo": ["prev@you.az"],              // ВСЕГДА массив (пустой = письмо не ответ)
    "references": ["root@…", "prev@you.az"]    // ВСЕГДА массив: цепочка треда от корня
  },
  "delivery": {
    "type": "inbound",                         // local | inbound | imported
    "internal": false,                         // true = письмо не выходило за пределы платформы
    "receivedBy": "mail.spine.maxsystems.az"   // наш узел, принявший письмо (только у inbound)
  },
  "security": {
    "spam": { "isSpam": false, "score": 2.9, "status": "No", "label": "ham", "symbols": "DKIM_VALID(-0.5), …" },
    "auth": { "spf": "pass", "dkim": "pass", "dmarc": "pass" }
  }
}

Папки. mail.received приходит только на ПОЛУЧЕННЫЕ письма: «Входящие», «Спам», «Архив» и свои папки сотрудника. По «Черновикам» и «Корзине» событий нет ни при какой подписке (черновик, который почтовый клиент сохраняет каждые 25 секунд, это не письмо, а в корзину письмо попадает уже после того, как о нём сообщили). Письмо, отправленное сотрудником из ящика, приходит отдельным событием mail.sent_folder с mailboxRole: "sent": подпишитесь на него, если ведёте переписку целиком, и не подписывайтесь, если нужны только входящие.

delivery. У письма между ящиками ОДНОГО проекта нет ни SPF, ни DKIM, ни DMARC: письмо никуда не уходило, проверять было нечего, и security.auth будет пустым. Чтобы такое письмо не путать с подделкой вашего домена извне, смотрите delivery.internal. Признак строится на ПУТИ письма, а не на адресе отправителя: любое письмо снаружи получает от нашего узла строку приёма, подделать её отправитель не может. imported, письмо заведено переносом с прежнего хостинга, а не получено почтой.

Идентификаторы: message.id, короткий per-mailbox id (для чтения того же письма через API), НЕ глобальный. Как глобально-уникальный ключ (идемпотентность, тредирование) используйте rfc.messageId; связывайте ответы в тред по rfc.inReplyTo/rfc.references.

security: score, оценка спам-фильтра (стиль rspamd; больше = подозрительнее), isSpam, превысил ли порог; spf/dkim/dmarc, подлинность отправителя (pass/fail/none…). Если письмо без этих заголовков, поле может быть null.

События отправки: что внутри data

Жизненный путь письма: mail.sent (релей принял) → mail.delivered (сервер получателя принял) либо mail.bounced / mail.complained / mail.delayed. Ключ письма, messageId (тот же id, что вернула отправка).

"data": {
  "messageId": "01KYCGV14SS0FPTFD4VRK7TF0S",   // id письма в SPINE (вернулся при отправке)
  "to": "user@example.com", "from": "noreply@ваш-домен",
  "subject": "…", "campaign": "welcome-july",
  "relay": "ses",                                // каким релеем ушло
  "relayMessageId": "0107019f…",                 // id письма у релея
  "bounce": { "type": "Permanent", "subType": "General", "diagnostic": "smtp; 550 5.1.1 …" }
}

Жёсткий отказ и жалоба блокируют адрес: он попадает в блок-лист проекта (GET /v1/mail/suppressions), и следующие письма на него возвращают статус suppressed вместо отправки, с полями reason и scope. Это защищает репутацию домена: у Amazon отказы должны быть ниже 5%, жалобы ниже 0.1%. Область блокировки зависит от причины: несуществующий ящик закрывается для всего проекта и обоих потоков, а жалоба и отписка, только для marketing и только у своего scope, поэтому квитанция об оплате уйдёт даже тому, кто отказался от новостей.

События вовлечённости (нужен track: true)

Открытия и клики считает сама платформа: в HTML подставляется прозрачный пиксель, ссылки заменяются на наш редирект (домен трекинга ваш, если включён t.ваш-домен). Поле automated: true означает, что открытие сделал не человек, а прокси или сканер почтового провайдера, такие обычно не считают.

"data": {
  "messageId": "01KYCGV14SS0FPTFD4VRK7TF0S",
  "to": "user@example.com", "campaign": "welcome-july",
  "url": "https://example.com/страница",        // только у mail.clicked
  "count": 1,                                    // какое это по счёту открытие/клик
  "automated": false,                            // true = прокси или сканер, не человек
  "userAgent": "…", "ip": "85.132.72.134",     // адрес открывшего; null, если определить не вышло
  "geo": { "country": "AZ", "countryName": "Azerbaijan", "continent": "AS",
            "city": "Baku", "region": "Baki", "lat": 40.37, "lon": 50.08 }
}

Про ip и geo: страну определяет сама платформа, вам не нужна своя база: в событии сразу приходит geo с кодом страны, названием и континентом (null, если определить не вышло). Готовый разрез по странам есть и в GET /v1/mail/stats, поле byCountry. Есть и город с регионом, и приблизительные координаты для карты: это центр населённого пункта, а не точка человека, и на бесплатной базе точность на уровне города или области. Для географии считайте в первую очередь клики. У открытий адрес часто принадлежит прокси почтового провайдера (Gmail грузит картинки через свои серверы), и по нему вы увидите дата-центр, а не город получателя, такие открытия помечены automated: true и в byCountry они не попадают. Данные о странах: IP Geolocation by DB-IP (CC BY 4.0).

mail.unsubscribed приходит, когда получатель нажал отписку (в том числе кнопкой самого Gmail или Yahoo, отписка в один клик по RFC 8058, включается полем unsubscribe: true). Адрес сразу попадает в блок-лист.

SPINE API · базовый адрес https://api.spine.maxsystems.az · машинное описание /v1/openapi.json