SPINE API
API is up RUENAZ OpenAPI Console

SPINE Developer API

One platform API for external projects: mail, SSL certificates, DNS, mailboxes, domains, webhooks and Telegram bots, all under a single key.

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

🔑 Authentication

The Authorization: Bearer spine_sk_… header in every request. The key is issued in the console, shown once, and revoked in one click.

⚙️ Metering and limits

Each key counts requests; the default limit is 120/min (configurable). On overflow, 429. Daily/monthly quotas are optional.

⚠️ Error codes

401 invalid key · 403 missing scope · 429 rate limit · 400 bad request (the error field).

Permissions (scopes)
ScopeWhat it allows
mail:sendSending emails (single and batch, status and stats of what was sent)
mail:readReading emails from your mailboxes
mail:suppressionsBlocked addresses: view, add, remove the block
mail:mailboxesCreating and managing mailboxes
mail:domainsConnecting domains and their status
mail:templatesШаблоны писем проекта: смотреть, править, откатывать, слать тест
notify:sendУведомления одним запросом: почта, Telegram, вебхук, с запасными каналами
mail:subscriptionsСписки рассылок и подписки: подписать, отписать, посмотреть согласия
protection:verifyПроверка ответа Turnstile для форм вашего сайта
domains:readСроки регистрации своих доменов: когда истекает, у какого регистратора
mail:migrateПеренос почты с другого хостинга по IMAP (задания и прогресс)
cert:issueOrdering and reissuing certificates
cert:readСтатус и скачивание сертификатов, SPKI-пины
cert:acmeACME-доступ: пары EAB для certbot, acme.sh, cert-manager, файрволов
cert:delegateCNAME-делегации для доменов, чей DNS не у нас
pki:issueВыпуск из приватного CA: длинные серты и клиентские mTLS
pki:readПриватный CA: корневые сертификаты, цепочки, CRL
dns:readReading the zone DNS records
dns:recordsManaging the zone DNS records
webhooksSubscribing to webhooks (incoming emails, delivery events)
telegram:botsManaging the project Telegram bots
webmail:loginEmployee auto-login links to webmail (SSO for embedding)
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Верификация: приложение-аутентификатор и резервные коды пользователей проекта

Mail

POST/v1/mail/sendmail:send

Send an email. The sender domain must be connected to SPINE mail and belong to the project.

Parameters
fromsender address (required)
torecipient (required)
subjectsubject (or template)
text / htmlemail body
categorylabel for statistics (optional)
cc / bcc / replyTolists of addresses (optional)
attachmentsattachments: [{ filename, content (base64), contentType }], up to 15 MB per message
headersyour own message headers, an object (optional)
streamtransactional (по умолчанию) | marketing. Транзакционный поток не блокируется отписками и жалобами: платёжный документ получатель должен получить, даже если отказался от новостей. Жёсткий отказ (несуществующий ящик) действует в обоих потоках
scopeвладелец отношения с получателем (например slug мерчанта). Жалобы и отписки ложатся именно на него, а не на весь проект. Пусто = весь проект, как раньше
campaigncampaign label: the funnel in /v1/mail/stats is grouped by it
tracktrue means count opens and clicks (events mail.opened / mail.clicked)
unsubscribetrue adds one-click unsubscribe (RFC 8058, required by Gmail and Yahoo)
sendAtan ISO 8601 date for scheduled sending (up to 90 days, cancel via DELETE)
idempotencyKeyduplicate protection (optional)
Request body
{
  "from": "noreply@ваш-домен",
  "to": "user@example.com",
  "subject": "Привет",
  "text": "Тело письма",
  "html": "<p>Тело письма со <a href=\"https://example.com\">ссылкой</a></p>",
  "campaign": "welcome-july",
  "track": true,
  "unsubscribe": true
}
Response example
{ "id": "01K…", "status": "sent", "relay": "ses", "relayMessageId": "…" }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/mail/send" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "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

Send a batch: up to 200 recipients per request, each with its own vars. Shared fields go at the top level.

Parameters
messageslist of messages: [{ to, vars, subject?, html?, attachments? }] (required)
from / subject / html / text / templateshared fields for every message in the batch
campaign / track / unsubscribe / sendAtsame as in a single send, applied to the whole batch
Request body
{
  "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": "Борис" } }
  ]
}
Response example
{ "total": 2, "counts": { "sent": 2 }, "results": [ { "to": "a@example.com", "id": "01K…", "status": "sent" } ] }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/mail/send-batch" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "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, поэтому обойти весь ящик можно без догадок.

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

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mailboxes/{id}/messages/by-message-idmail:read

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

Parameters
valueMessage-ID письма (обязательно)
since / beforeсузить период поиска, ISO 8601: ускоряет ответ на больших ящиках
Response example
{ "message": { "id": "…", "subject": "…", "rfcMessageId": "…" }, "lookup": { "source": "index", "scanned": 0 } }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/by-message-id" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/by-message-id", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
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_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mail/messagesmail:send

List of your sent emails with their statuses.

Parameters
limithow many to return (up to 200, 50 by default)
statusfilter: sent | delivered | bounced | complained | delayed | scheduled | suppressed | failed
campaignfilter by campaign label
Response example
{ "messages": [ { "id": "01K…", "toAddr": "user@example.com", "status": "delivered", "opens": 1, "clicks": 0 } ] }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/mail/messages" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/messages", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mail/messages",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mail/messages/{id}mail:send

Message status and its full history: accepted by the relay, delivered, opened, link clicked.

Response example
{ "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 } ] }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/mail/messages/{id}" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/messages/{id}", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mail/messages/{id}",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
DELETE/v1/mail/messages/{id}mail:send

Cancel a scheduled email while it has not been sent yet.

Response example
{ "id": "01K…", "status": "canceled" }
Code examples
curl -X DELETE "https://api.spine.maxsystems.az/v1/mail/messages/{id}" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/messages/{id}", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.delete(
  "https://api.spine.maxsystems.az/v1/mail/messages/{id}",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mail/statsmail:send

Deliverability funnel for a period: accepted, delivered, opened, clicks, bounces, complaints. Keep bounce and complaint rates below the Amazon thresholds (5% and 0.1%).

Parameters
daysperiod in days (1..365, 30 by default)
campaignfor a single campaign only
Response example
{ "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 } }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/mail/stats" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/stats", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mail/stats",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/mail/validatemail:send

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

Request body
{ "emails": ["user@gmial.com", "someone@example.com"] }
Response example
{ "total": 2, "counts": { "warn": 1, "ok": 1 },
  "results": [ { "email": "user@gmial.com", "verdict": "warn", "suggestion": "user@gmail.com",
                 "reasons": ["похоже на опечатку в домене"], "hasMx": true, "disposable": false } ] }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/mail/validate" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "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

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

Request body
{ "subject": "СРОЧНО!!", "html": "<img src=…>", "category": "marketing" }
Response example
{ "score": 8, "verdict": "bad",
  "issues": [ { "weight": 3, "text": "нет текстовой части: письмо только в HTML" } ] }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/mail/check" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "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). Нужно, когда своего разбора хочется больше, чем нашего: вы получаете ровно то, что лежит на сервере. ⚠️ Политика проекта и антивирус применяются и здесь: письмо с запрещённым или заражённым вложением не отдаётся целиком, иначе через сырой файл проверку можно было бы обойти. Выдача пишется в журнал проекта: это доступ ко всей переписке письма.

Response example
(поток message/rfc822, имя файла <id письма>.eml)
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/{msgId}/raw" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/{msgId}/raw", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
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_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mail/suppressionsmail:suppressions

Blocked addresses of the project: hard bounces, spam complaints, unsubscribes. Messages to them are not sent.

Parameters
emailcheck a single address
scopeблокировки этого владельца плюс общепроектные (они действуют на всех)
streamtransactional, показать только то, что блокирует платёжные письма
limithow many to return (up to 500)
Response example
{ "suppressions": [ { "email": "user@example.com", "reason": "hard_bounce", "scope": null, "ts": "2026-07-25T10:00:00Z" } ] }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/mail/suppressions" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/suppressions", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mail/suppressions",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/mail/suppressionsmail:suppressions

Block an address manually (for example, at the recipient's request).

Parameters
emailaddress (required)
reasonhard_bounce | complaint | manual | unsubscribe (manual by default)
scopeчья это блокировка: пусто = весь проект
Request body
{ "email": "user@example.com", "reason": "manual", "scope": "мерчант" }
Response example
{ "email": "user@example.com", "reason": "manual", "tenant": "ваш-проект", "scope": "мерчант" }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/mail/suppressions" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "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 снимается блокировка ровно этого владельца, без него, все блокировки адреса в проекте.

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

r = requests.delete(
  "https://api.spine.maxsystems.az/v1/mail/suppressions/{email}",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mail/policymail:send

Your own limits and rules: attachment sizes, blocked file types, the antivirus mode, sending caps. Handy so you do not have to guess why a message was rejected.

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

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mail/policy",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);

Подписки

GET/v1/mail/listsmail:subscriptions

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

Response example
{ "lists": [ { "slug": "news", "name": "Новости", "doubleOptIn": true,
    "confirmed": 128, "pending": 4, "unsubscribed": 11 } ] }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/mail/lists" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/lists", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mail/lists",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/mail/listsmail:subscriptions

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

Request body
{ "slug": "news", "name": "Новости", "description": "раз в месяц", "doubleOptIn": true }
Response example
{ "id": "…", "slug": "news", "name": "Новости", "doubleOptIn": true }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/mail/lists" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "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

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

Response example
{ "deleted": "news" }
Code examples
curl -X DELETE "https://api.spine.maxsystems.az/v1/mail/lists/{slug}" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/lists/{slug}", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.delete(
  "https://api.spine.maxsystems.az/v1/mail/lists/{slug}",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/mail/subscriptionsmail:subscriptions

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

Request body
{ "email": "user@example.com", "list": "news", "source": "форма на сайте" }
Response example
{ "email": "user@example.com", "list": "news", "status": "pending", "confirmationSent": true }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/mail/subscriptions" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "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 оставлено синонимом ради совместимости. Согласия адреса по всем спискам проекта и готовая ссылка на его центр подписок. Ссылку можно положить в подвал письма: на ней человек сам управляет подписками.

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

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mail/subscriptions",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
DELETE/v1/mail/subscriptionsmail:subscriptions

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

Parameters
emailадрес
listслаг списка (необязательно)
Response example
{ "email": "user@example.com", "list": "news", "unsubscribed": 1 }
Code examples
curl -X DELETE "https://api.spine.maxsystems.az/v1/mail/subscriptions" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/subscriptions", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.delete(
  "https://api.spine.maxsystems.az/v1/mail/subscriptions",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);

Шаблоны

GET/v1/mail/templatesmail:templates

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

Response example
{ "templates": [ { "slug": "invoice", "name": "Счёт", "version": 3, "variables": ["name","number"] } ] }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/mail/templates" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/mail/templates", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mail/templates",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/mail/templatesmail:templates

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

Request body
{ "slug": "invoice", "name": "Счёт", "subject": "Счёт {{number}}",
  "html": "<p>Здравствуйте, {{name}}. Счёт {{number}} на {{sum}}.</p>" }
Response example
{ "slug": "invoice", "version": 1, "variables": ["name","number","sum"] }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/mail/templates" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "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

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

Request body
{ "subject": "Счёт {{number}} к оплате" }
Response example
{ "slug": "invoice", "version": 2 }
Code examples
curl -X PUT "https://api.spine.maxsystems.az/v1/mail/templates/{slug}" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"subject":"Счёт {{number}} к оплате"}',
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/mail/templates/{slug}/rollbackmail:templates

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

Request body
{ "version": 1 }
Response example
{ "slug": "invoice", "version": 3, "rolledBackFrom": 1 }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/mail/templates/{slug}/rollback" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"version":1}',
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/mail/templates/{slug}/previewmail:templates

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

Request body
{ "vars": { "name": "Михаил", "number": "SPINE-2026-0007", "sum": "59 AZN" } }
Response example
{ "subject": "Счёт SPINE-2026-0007", "html": "…", "check": { "score": 0, "verdict": "ok" } }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/mail/templates/{slug}/preview" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "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.

Request body
{ "to": "me@example.com", "vars": { "name": "Михаил" } }
Response example
{ "id": "01K…", "status": "sent" }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/mail/templates/{slug}/test" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"to":"me@example.com","vars":{"name":"Михаил"}}',
]);
$data = json_decode(curl_exec($ch), true);

Уведомления

POST/v1/notifynotify:send

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

Request body
{ "title": "Платёж прошёл", "body": "Счёт SPINE-2026-0007 оплачен",
  "channels": [ { "channel": "telegram", "to": "12345678" }, { "channel": "email", "to": "user@example.com" } ] }
Response example
{ "channel": "email", "to": "user@example.com", "status": "sent",
  "tried": [ { "channel": "telegram", "error": "Telegram HTTP 400" } ] }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/notify" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "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);

Certificates

POST/v1/certs/issuecert:issue

Order a public SSL certificate. The domain must be in a managed zone or have a CNAME delegation, and the delegation is needed for EVERY name in the certificate: ownership is verified per name, each via its own _acme-challenge.<name> (the exception is a wildcard, verified via the domain record). Issuance takes 15-30 sec. The lifetime is not up to the client: under CA/Browser Forum rules a public certificate lives 199 days (100 from 2027, 47 from 2029). Need a longer one, see POST /v1/pki/issue. ⚠️ A pinWarning field in the response means these domains already have ANOTHER certificate with embedded pins: the new one has its own key, so its pin differs. If you need the previous pin, reissue the existing certificate instead of ordering a second one.

Parameters
domainslist of domains, wildcard *.domain allowed (required)
csryour own CSR, then the private key stays with you (optional)
autoRenewauto-renewal, true by default
Request body
{ "domains": ["app.ваш-домен", "*.ваш-домен"], "autoRenew": true }
Response example
{ "id": "…", "subject": "app.ваш-домен", "sans": [...], "status": "pending",
  "pinWarning": ["Банк X (Java-приложение)"] }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/certs/issue" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"domains":["app.ваш-домен","*.ваш-домен"],"autoRenew":true}',
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/certs/{id}/statuscert:read

Certificate status (pending → active | failed).

Response example
{ "status": "active", "notAfter": "…", "daysLeft": 89 }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/certs/{id}/status" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/certs/{id}/status", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/certs/{id}/status",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/certs/{id}/downloadcert:read

Download the certificate (fullchain + private key; for CSR-based certs privkey=null).

Response example
{ "fullchain": "-----BEGIN CERTIFICATE-----…", "privkey": "…" }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/certs/{id}/download" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/certs/{id}/download", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/certs/{id}/download",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/certs/{id}/pinscert:read

SPKI pins of the certificate. This is what partners embed instead of the certificate itself: the platform keeps the private key OF THIS certificate, so the pin survives both auto-renewal and a manual reissue. ⚠️ The pin changes if you create a SECOND certificate for the same domains instead of reissuing the first, delete and recreate it, turn off Keep key, or request a key rotation. We return the current and the backup pin, plus ready-made strings for curl and OkHttp.

Response example
{ "spkiPin": "…", "backupSpkiPin": "…", "keepKey": true,
  "formats": { "curl": "curl --pinnedpubkey \"sha256//…\" https://api.example.com/" } }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/certs/{id}/pins" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/certs/{id}/pins", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/certs/{id}/pins",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/certs/{id}/pins/registercert:read

Record that the pin is embedded somewhere. The entry goes into the registry, and before a key change the platform warns you who will be affected.

Request body
{ "party": "Партнёр X", "system": "Java-приложение", "contact": "dev@partner.com" }
Response example
{ "id": "…", "registered": true, "pin": "…" }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/certs/{id}/pins/register" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"party":"Партнёр X","system":"Java-приложение","contact":"dev@partner.com"}',
]);
$data = json_decode(curl_exec($ch), true);

PKI and ACME

POST/v1/acme/eabcert:acme

Issue yourself an External Account Binding pair for our ACME server. After that any standard client works with the platform without our code: certbot, acme.sh, cert-manager, Caddy, Traefik, FortiGate 7.6.3+, Cisco ASA 9.23.1+. The HMAC key is returned ONCE.

Parameters
labela label for yourself (optional)
allowedDomainsrestrict domains (empty = any available)
issuerletsencrypt (default) or private
policyauto (we pass the check ourselves) or verify (the client passes it)
Request body
{ "label": "prod-cluster", "allowedDomains": ["example.com"] }
Response example
{ "kid": "…", "hmacKey": "…",
  "directoryUrl": "https://api.spine.maxsystems.az/acme/directory",
  "snippets": { "certbot": "certbot register --server … --eab-kid … --eab-hmac-key …" } }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/acme/eab" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"label":"prod-cluster","allowedDomains":["example.com"]}',
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/acme/delegationscert:delegate

Create a CNAME ownership validation delegation. Needed for domains whose DNS we do not run: the client adds one record once, after that we run every check on our side and need no access to their DNS.

Request body
{ "domain": "client-domain.com" }
Response example
{ "domain": "client-domain.com", "record": { "type": "CNAME",
  "name": "_acme-challenge.client-domain.com", "value": "<метка>.acme.spine.maxsystems.az", "ttl": 300 } }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/acme/delegations" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"domain":"client-domain.com"}',
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/acme/delegations/{id}/verifycert:delegate

Check that the client added the CNAME. Read-only DNS lookup, changes nothing.

Response example
{ "ok": true, "expected": "…", "found": ["…"] }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/acme/delegations/{id}/verify" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/acme/delegations/{id}/verify", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
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_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/acme/delegationscert:delegate

List of the project delegations with their state.

Response example
{ "delegations": [{ "domain": "client-domain.com", "verified": true }] }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/acme/delegations" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/acme/delegations", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/acme/delegations",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/pki/issuepki:issue

Issue a certificate from the platform private CA. CA/Browser Forum rules do not apply to a private CA, so you set the lifetime: up to 3650 days. Suitable for partner integrations, internal services and mTLS client certificates.

Parameters
domainsdomains for a server certificate
commonNameholder name for an mTLS client certificate
dayslifetime in days, 397 by default, 3650 maximum
certTypeserver (default) or client
csryour own CSR: the private key stays with you (optional)
Request body
{ "domains": ["internal.example.com"], "days": 1095, "certType": "server" }
Response example
{ "id": "…", "subject": "internal.example.com", "status": "pending", "lifetimeDays": 1095 }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/pki/issue" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "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

The root certificate of the private CA and its chain. This is what a partner installs into their truststore once and lives with for years: reissuing end certificates does not affect it.

Response example
{ "name": "SPINE Partner CA Root", "rootPem": "-----BEGIN CERTIFICATE-----…",
  "notAfter": "2036-07-29T…", "crlUrl": "https://api.spine.maxsystems.az/v1/pki/…/crl.pem" }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/pki/root" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/pki/root", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/pki/root",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/pki/crlpki:read

The revoked certificate list (CRL) of the private CA in PEM format.

Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/pki/crl" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/pki/crl", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/pki/crl",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/pki/authoritiespki:read

Certificate authorities available to the project: names, kind and validity.

Response example
{ "cas": [{ "name": "SPINE Partner CA Issuing", "kind": "intermediate",
  "notAfter": "2031-07-28T…", "isDefault": true }] }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/pki/authorities" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/pki/authorities", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/pki/authorities",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);

Mailboxes

POST/v1/mailboxesmail:mailboxes

Create a mailbox on your own domain.

Parameters
namemailbox name before @ (2-40, [a-z0-9._-])
domainproject domain (required)
passwordmailbox password (min. 8)
descriptiondescription (optional)
Request body
{ "name": "info", "domain": "ваш-домен", "password": "…" }
Response example
{ "email": "info@ваш-домен", "id": "…" }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/mailboxes" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"name":"info","domain":"ваш-домен","password":"…"}',
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mailboxesmail:mailboxes

List of mailboxes and groups on the project domains. The type field: personal, shared (several people have access), group (distribution group). Groups have a membersCount field.

Response example
{ "mailboxes": [ { "id": "…", "email": "ivan@ваш-домен", "type": "personal", "usedBytes": 0 }, { "id": "…", "email": "team@ваш-домен", "type": "group", "membersCount": 4 } ] }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/mailboxes" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/mailboxes", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mailboxes",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
DELETE/v1/mailboxes/{id}mail:mailboxes

Delete a mailbox (only on your own domain).

Response example
{ "deleted": true }
Code examples
curl -X DELETE "https://api.spine.maxsystems.az/v1/mailboxes/{id}" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/mailboxes/{id}", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.delete(
  "https://api.spine.maxsystems.az/v1/mailboxes/{id}",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/mailboxes/{id}/sharedmail:mailboxes

Mark a mailbox as shared or remove the mark. A shared mailbox is a personal mailbox that several people are given access to (support@, info@). Changes the mailbox type field.

Request body
{ "shared": true }
Response example
{ "id": "…", "shared": true }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/mailboxes/{id}/shared" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"shared":true}',
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mailboxes/{id}/accessmail:mailboxes

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

Response example
{ "access": [ { "accountId": "u", "email": "tamara@example.com", "level": "write", "folders": 5 } ] }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/mailboxes/{id}/access" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/mailboxes/{id}/access", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mailboxes/{id}/access",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/mailboxes/{id}/accessmail:mailboxes

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

Request body
{ "mailbox": "tamara@example.com", "level": "write" }
Response example
{ "ok": true, "mailbox": "tamara@example.com", "level": "write", "folders": 5 }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/mailboxes/{id}/access" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "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.

Response example
{ "ok": true, "folders": 5 }
Code examples
curl -X DELETE "https://api.spine.maxsystems.az/v1/mailboxes/{id}/access/{granteeId}" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/mailboxes/{id}/access/{granteeId}", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
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_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mailboxes/{id}/messagesmail:read

List of the latest emails in a mailbox (newest first). You can filter by address, handy for a contact conversation panel.

Parameters
limithow many emails to return, up to 100 (20 by default)
withcontact address: emails FROM or TO them (conversation with a counterparty)
fromfilter by sender (address substring)
tofilter by recipient (address substring)
Response example
{ "messages": [ { "id": "eaaaaab", "messageId": ["<abc@bank.az>"], "subject": "…", "from": [{"email":"…"}], "receivedAt": "…", "preview": "…" } ] }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mailboxes/{id}/messages/{msgId}mail:read

The full email: body, RFC Message-ID/In-Reply-To/References (for threading), raw headers and the list of attachments with ids to download. Note: the id in the path is per-mailbox (not global); the global key is messageId. A mailbox of another project answers 404, not 403.

Response example
{ "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>"}, …] }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/{msgId}" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/{msgId}", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
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_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mailboxes/{id}/messages/{msgId}/attachmentsmail:read

Message attachments: name, type, size, the id to download and the antivirus verdict. You do not need to fetch the file to learn that it is infected or blocked by policy.

Response example
{ "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" }
  } ] }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/{msgId}/attachments" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/{msgId}/attachments", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
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_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mailboxes/{id}/messages/{msgId}/attachments/{attId}mail:read

Download an attachment. It is streamed as the file itself (not base64), with type and name headers. Before serving, the file is scanned: infected files and types blocked by policy are not served.

Parameters
attIdthe attachment id from the attachment list of the same message
Response example
HTTP 200
Content-Type: application/pdf
Content-Disposition: attachment; filename*=UTF-8''akt-sverki.pdf
Content-Length: 1665

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

# 409, вложение заражено или его тип запрещён политикой проекта
# 413, вложение больше лимита проекта
# 503, антивирус недоступен: файл не отдаём (fail-closed), пробуйте позже
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/{msgId}/attachments/{attId}" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/{msgId}/attachments/{attId}", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
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_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mailboxes/{id}/unreadmail:read

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

Response example
{ "unread": 3, "total": 5, "folders": [ { "name": "Inbox", "role": "inbox", "unread": 3 }, { "name": "Черновики", "role": null, "unread": 2 } ] }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/mailboxes/{id}/unread" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/mailboxes/{id}/unread", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mailboxes/{id}/unread",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);

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

POST/v1/mail-migrationsmail:migrate

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

Parameters
targetEmailящик у нас, куда переносим
targetPasswordпароль этого ящика
sourceHostпресет (vk|yandex|google|outlook) или адрес IMAP-сервера
sourcePortпорт источника (по умолчанию 993)
sourceSecuretrue = TLS сразу, false = STARTTLS
sourceUserлогин на источнике (по умолчанию равен targetEmail)
sourcePasswordпароль на источнике (для VK и Mail.ru, пароль приложения)
Request body
{ "targetEmail": "info@ваш-домен", "targetPassword": "…", "sourceHost": "vk", "sourcePassword": "…" }
Response example
{ "id": "…", "targetEmail": "info@ваш-домен", "status": "pending", "copiedMessages": 0 }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/mail-migrations" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"targetEmail":"info@ваш-домен","targetPassword":"…","sourceHost":"vk","sourcePassword":"…"}',
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mail-migrationsmail:migrate

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

Response example
{ "presets": { "vk": { "host": "imap.mail.ru", "port": 993 } }, "migrations": [ { "id": "…", "status": "done", "copiedMessages": 4210 } ] }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/mail-migrations" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/mail-migrations", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mail-migrations",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/mail-migrations/{id}mail:migrate

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

Response example
{ "migration": { "status": "running" }, "folders": [ { "sourcePath": "Отправленные", "targetPath": "Sent", "copied": 812, "total": 812 } ], "log": ["…"] }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/mail-migrations/{id}" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/mail-migrations/{id}", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/mail-migrations/{id}",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/mail-migrations/startmail:migrate

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

Parameters
idsсписок id заданий
includeTrashпереносить корзину (по умолчанию да)
includeJunkпереносить спам (по умолчанию нет)
Request body
{ "ids": ["…"], "includeTrash": true, "includeJunk": false }
Response example
{ "queued": 1 }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/mail-migrations/start" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "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

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

Response example
{ "deleted": true }
Code examples
curl -X DELETE "https://api.spine.maxsystems.az/v1/mail-migrations/{id}" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/mail-migrations/{id}", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.delete(
  "https://api.spine.maxsystems.az/v1/mail-migrations/{id}",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);

Groups

GET/v1/groupsmail:mailboxes

List of the project distribution groups. A group is an address team@domain; an email sent to it fans out to all members in their personal mailboxes.

Response example
{ "groups": [ { "id": "…", "email": "team@ваш-домен", "membersCount": 4 } ] }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/groups" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/groups", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/groups",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/groupsmail:mailboxes

Create a distribution group name@domain. The domain must belong to your project.

Parameters
domainproject domain (required)
nameaddress name, 2-40 characters [a-z0-9._-] (required)
descriptiongroup description (optional)
Request body
{ "domain": "ваш-домен.az", "name": "team", "description": "Вся команда" }
Response example
{ "id": "…", "email": "team@ваш-домен.az" }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/groups" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"domain":"ваш-домен.az","name":"team","description":"Вся команда"}',
]);
$data = json_decode(curl_exec($ch), true);
DELETE/v1/groups/{id}mail:mailboxes

Delete a group.

Response example
{ "deleted": true }
Code examples
curl -X DELETE "https://api.spine.maxsystems.az/v1/groups/{id}" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/groups/{id}", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.delete(
  "https://api.spine.maxsystems.az/v1/groups/{id}",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/groups/{id}/membersmail:mailboxes

Group members (mailboxes the mail fans out to).

Response example
{ "members": [ { "id": "…", "email": "ivan@ваш-домен.az" } ] }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/groups/{id}/members" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/groups/{id}/members", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/groups/{id}/members",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/groups/{id}/membersmail:mailboxes

Add a mailbox to the group by its address.

Request body
{ "mailbox": "ivan@ваш-домен.az" }
Response example
{ "ok": true, "member": { "id": "…", "email": "ivan@ваш-домен.az" } }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/groups/{id}/members" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"mailbox":"ivan@ваш-домен.az"}',
]);
$data = json_decode(curl_exec($ch), true);
DELETE/v1/groups/{id}/members/{userId}mail:mailboxes

Remove a member from the group.

Response example
{ "ok": true }
Code examples
curl -X DELETE "https://api.spine.maxsystems.az/v1/groups/{id}/members/{userId}" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/groups/{id}/members/{userId}", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
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_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);

Webmail (SSO)

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

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

Request body
{ "email": "ivan@ваш-домен.az", "currentPassword": "…", "newPassword": "…" }
Response example
{ "ok": true }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/webmail/change-password" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "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@). Нужно потому, что почтовый сервер даёт читать общий ящик, но отправлять от его имени не даёт. Платформа проверяет, что у отправителя есть доступ на запись к этому ящику, и пишет каждую отправку в аудит.

Request body
{ "accountId": "id ящика", "emailId": "id черновика" }
Response example
{ "sent": true }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/webmail/submit-as" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "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} отменяет.

Request body
{ "accountId": "id ящика", "emailId": "id черновика", "sendAt": 1785400000000 }
Response example
{ "id": "…", "sendAt": "2026-07-30T06:00:00.000Z", "status": "pending" }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/webmail/scheduled" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "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

Issue an employee a one-time auto-login link to THEIR own webmail mailbox, so you can embed mail into your app (e.g. a CRM). The link lives 60 seconds and is single-use: on opening it (in a new tab or in an iframe) the employee lands in mail without a second login. The mailbox must be on your domain.

Parameters
mailboxthe employee full mailbox address on your domain (required)
Request body
{ "mailbox": "ivan@ваш-домен.az" }
Response example
{ "url": "https://webmail.ваш-домен.az/?login_token=…", "email": "ivan@ваш-домен.az", "expiresIn": 60 }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/webmail/login-link" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"mailbox":"ivan@ваш-домен.az"}',
]);
$data = json_decode(curl_exec($ch), true);

Domains

POST/v1/maildomainsmail:domains

Connect a domain to mail (creates the domain and, when possible, publishes DNS itself).

Request body
{ "name": "ваш-домен" }
Response example
{ "id": "…", "name": "ваш-домен", "autoDns": { "created": 6 }, "records": [...], "ready": false }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/maildomains" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"name":"ваш-домен"}',
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/maildomainsmail:domains

List of the project connected domains.

Response example
{ "domains": [ { "id": "…", "name": "ваш-домен" } ] }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/maildomains" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/maildomains", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/maildomains",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/maildomains/{domain}mail:domains

DNS records and the domain verification status.

Response example
{ "name": "ваш-домен", "records": [...], "ready": true }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/maildomains/{domain}" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/maildomains/{domain}", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/maildomains/{domain}",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/maildomains/{domain}/catch-allmail:domains

Intake of mail sent to addresses that do not exist on the domain: whether it is on, which mailbox it goes to, and whether we reply to the sender. A warning field appears when the intake points at a person's own mailbox.

Response example
{ "domain": "ваш-домен", "enabled": true, "mailbox": "catchall@ваш-домен", "autoReply": true }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/maildomains/{domain}/catch-all" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/maildomains/{domain}/catch-all", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
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_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
PUT/v1/maildomains/{domain}/catch-allmail:domains

Turn the intake on, change it or turn it off. The mailbox must exist and belong to this same domain. While the intake is on, the sender gets an automatic reply in English: there is no such address, answering it makes no sense, here is the support address of the domain. Only real people get that reply: mailing lists, robots and bounce reports get nothing, and one address hears from us at most once a day.

Parameters
mailboxthe intake mailbox on this domain, or null to turn it off (then mail to non-existent addresses is rejected outright)
autoReplywhether to tell the sender that the address does not exist (true by default)
Request body
{ "mailbox": "catchall@ваш-домен", "autoReply": true }
Response example
{ "domain": "ваш-домен", "enabled": true, "mailbox": "catchall@ваш-домен", "autoReply": true }
Code examples
curl -X PUT "https://api.spine.maxsystems.az/v1/maildomains/{domain}/catch-all" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "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.

Response example
{ "domains": [ { "domain": "ваш-домен", "registrar": "…", "expiresAt": "2027-03-01T00:00:00.000Z", "daysLeft": 208, "state": "ok", "uses": ["почта"], "source": "rdap" } ], "alertDays": [40, 30, 15, 5, 1] }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/domains/mine" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/domains/mine", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/domains/mine",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);

Защита

POST/v1/protection/turnstile/verifyprotection:verify

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

Parameters
tokenзначение поля cf-turnstile-response из вашей формы (обязательно)
domainдомен, для которого настроены ключи Turnstile
Request body
{ "token": "0.abc…", "domain": "ваш-домен" }
Response example
{ "ok": true, "errors": [] }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/protection/turnstile/verify" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "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) на оценку не влияют: не проверено это не «чисто».

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

r = requests.get(
  "https://api.spine.maxsystems.az/v1/security/report",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);

DNS

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

List of the zone DNS records (the zone must be linked to the project).

Response example
{ "zone": "ваш-домен", "records": [ { "id": "…", "type": "A", "name": "@", "data": "1.2.3.4" } ] }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/dns/{zone}/records" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/dns/{zone}/records", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/dns/{zone}/records",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/dns/{zone}/recordsdns:records

Create a DNS record in the project zone.

Parameters
typeA | AAAA | TXT | MX | CNAME | NS | SRV | CAA
namerecord name ('@' for the root)
datavalue
priority / ttlfor MX / TTL (optional)
Request body
{ "type": "CNAME", "name": "app", "data": "target.example.com" }
Response example
{ "created": true }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/dns/{zone}/records" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "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

Delete a DNS record. For GoDaddy add ?data=<value>.

Response example
{ "deleted": true }
Code examples
curl -X DELETE "https://api.spine.maxsystems.az/v1/dns/{zone}/records/{id}" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/dns/{zone}/records/{id}", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
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_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);

Webhooks

POST/v1/webhookswebhooks

Subscribe to events: the platform will send a POST to your URL. The secret in the response is shown once.

Parameters
urlyour https receiving address (required)
eventsmail in a mailbox: mail.received (received), mail.sent_folder (an employee sent it from the mailbox) · sending via API: mail.sent, mail.delivered, mail.bounced, mail.complained, mail.delayed, mail.rejected · engagement (with track:true): mail.opened, mail.clicked, mail.unsubscribed
Request body
{ "url": "https://ваш-сервер/webhook", "events": ["mail.received", "mail.bounced"] }
Response example
{ "id": "…", "url": "…", "events": [...], "secret": "whsec_…" }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/webhooks" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"url":"https://ваш-сервер/webhook","events":["mail.received","mail.bounced"]}',
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/webhookswebhooks

List of your webhooks.

Response example
{ "webhooks": [ { "id": "…", "url": "…", "events": [...], "active": true } ] }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/webhooks" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/webhooks", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/webhooks",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
PATCH/v1/webhooks/{id}webhooks

Update the subscription: event list, receiving URL, enable or disable. THE SECRET DOES NOT CHANGE, so your signature verifier stays as is. Send only the fields you are changing.

Parameters
urlnew https receiving URL (optional)
eventsthe full new event list (optional)
activefalse pauses delivery without losing the subscription and its log (optional)
Request body
{ "events": ["mail.received", "mail.sent_folder", "mail.bounced"] }
Response example
{ "id": "…", "url": "…", "events": [...], "active": true }
Code examples
curl -X PATCH "https://api.spine.maxsystems.az/v1/webhooks/{id}" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "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

Rotate the signing secret (leak, staff change). The new one is shown ONCE. The old one stops working immediately: events are signed with the new secret, and until you replace it on your side you will reject them.

Response example
{ "id": "…", "secret": "whsec_…" }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/webhooks/{id}/rotate-secret" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/webhooks/{id}/rotate-secret", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
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_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
DELETE/v1/webhooks/{id}webhooks

Delete a webhook. To simply change events or the URL use PATCH: re-creating issues a new secret.

Response example
{ "deleted": true }
Code examples
curl -X DELETE "https://api.spine.maxsystems.az/v1/webhooks/{id}" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/webhooks/{id}", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.delete(
  "https://api.spine.maxsystems.az/v1/webhooks/{id}",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/webhooks/{id}/deliverieswebhooks

Webhook delivery log: status (pending/delivered/failed), codes, attempts and the exact signed body, handy for debugging the signature.

Response example
{ "deliveries": [ { "id": 42, "event": "mail.received", "status": "failed", "httpCode": 401, "attempts": 7, "payload": "{…}" } ] }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/webhooks/{id}/deliveries" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/webhooks/{id}/deliveries", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/webhooks/{id}/deliveries",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/webhooks/{id}/deliveries/{deliveryId}/redeliverwebhooks

Redeliver a specific delivery now (for example, after you have fixed the signature verifier).

Response example
{ "status": "delivered", "httpCode": 200 }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/webhooks/{id}/deliveries/{deliveryId}/redeliver" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/webhooks/{id}/deliveries/{deliveryId}/redeliver", {
  method: "POST",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
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_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);

Telegram bots

POST/v1/telegram/mybotstelegram:bots

Connect an existing bot to the platform by its token (from BotFather).

Request body
{ "token": "123456:ABC-…", "note": "бот проекта" }
Response example
{ "id": "…", "botId": 123456, "username": "MyBot", "name": "My Bot" }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/telegram/mybots" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"token":"123456:ABC-…","note":"бот проекта"}',
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/telegram/mybots/bulktelegram:bots

Bulk import: connect many bots at once from a list of tokens (from BotFather). tokens is an array or text (one token per line).

Request body
{ "tokens": ["123456:AAA…", "234567:BBB…"] }
Response example
{ "total": 2, "added": 2, "failed": 0, "results": [ { "ok": true, "username": "MyBot", "botId": 123456 } ] }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/telegram/mybots/bulk" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"tokens":["123456:AAA…","234567:BBB…"]}',
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/telegram/mybotstelegram:bots

List of the project bots.

Response example
{ "bots": [ { "id": "…", "username": "MyBot", "webhookSet": true } ] }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/telegram/mybots" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/telegram/mybots", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/telegram/mybots",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/telegram/mybots/{id}telegram:bots

Live bot status (getMe + webhook + commands).

Response example
{ "username": "MyBot", "me": {...}, "webhook": {...}, "commands": [...] }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/telegram/mybots/{id}" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/telegram/mybots/{id}", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/telegram/mybots/{id}",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/telegram/mybots/{id}/webhooktelegram:bots

Route the bot updates to your server (SPINE receives them from Telegram and forwards them to you).

Request body
{ "forwardUrl": "https://ваш-сервер/tg" }
Response example
{ "hookUrl": "https://api.spine.maxsystems.az/v1/tg/hook/123456", "forwardUrl": "…" }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/telegram/mybots/{id}/webhook" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"forwardUrl":"https://ваш-сервер/tg"}',
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/telegram/mypresetstelegram:bots

Turnkey factory presets available to the project (shared + your own): commands, webhook, profile.

Response example
{ "presets": [ { "id": "…", "name": "Поддержка", "forwardUrl": "https://…" } ] }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/telegram/mypresets" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/telegram/mypresets", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/telegram/mypresets",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/telegram/mybots/create-linktelegram:bots

Factory: a deep link to create a new bot. The user confirms in Telegram and the bot connects to the project by itself. With presetId the bot is deployed turnkey right away (commands + webhook + profile).

Request body
{ "suggestedUsername": "myproject_bot", "presetId": "…(необязательно)" }
Response example
{ "link": "https://t.me/newbot/spinemsbot/myproject_bot" }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/telegram/mybots/create-link" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"suggestedUsername":"myproject_bot","presetId":"…(необязательно)"}',
]);
$data = json_decode(curl_exec($ch), true);

Brand

GET/v1/brandbrand:read

Your project brand kit: name, tagline, color palette, fonts, links and logos/files (with direct URLs to embed). Handy to auto-apply the brand style to emails, websites and apps.

Response example
{
  "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/…" }]
}
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/brand" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/brand", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/brand",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);

Проект

GET/v1/projectproject:read

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

Response example
{
  "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 дн." }]
}
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/project" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/project", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/project",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/project/auditaudit:read

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

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

r = requests.get(
  "https://api.spine.maxsystems.az/v1/project/audit",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);

Сайт / SEO

GET/v1/site-configsiteconfig:read

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

Response example
{ "tenant": "acme", "meta": { "en": { "title": "Acme", "description": "…" } }, "og": { "siteName": "Acme" }, "analytics": { "ga4": "G-XXXX" }, "robots": { "disallow": ["/admin"] } }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/site-config" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/site-config", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/site-config",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/site/{tenant}/robots.txt

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

Response example
User-agent: *
Disallow: /admin

Sitemap: https://acme.az/sitemap.xml
Code examples
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 сюда.

Response example
<?xml version="1.0" encoding="UTF-8"?><urlset>…</urlset>
Code examples
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).

Parameters
pathпуть страницы для canonical и og:url (например /events/123)
langязык meta-тегов (ключ из секции meta, например ru/en/az)
Response example
<title>Acme</title>
<meta name="description" content="…">
<meta property="og:image" content="…/og.png">
<script>…GA4…</script>
Code examples
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 автоматически, отдельно нужна, если хотите свою подпись на превью страницы.

Parameters
titleзаголовок на картинке (по умолчанию название бренда)
subtitleподзаголовок (необязательно)
Response example
(image/png, картинка 1200×630)
Code examples
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 у проекта обновляется.

Response example
(image/png, иконка)
Code examples
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).

Parameters
envокружение: prod|staging|dev|любое (по умолчанию prod)
Response example
{ "env": "prod", "secrets": { "DB_PASSWORD": "…", "STRIPE_KEY": "…" } }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/secrets" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/secrets", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/secrets",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/secrets.envsecrets:read

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

Parameters
envокружение (по умолчанию prod)
Response example
DB_PASSWORD="…"
STRIPE_KEY="…"
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/secrets.env" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/secrets.env", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/secrets.env",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/secrets/getsecrets:read

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

Request body
{ "env": "prod", "name": "db/password" }
Response example
{ "name": "db/password", "env": "prod", "value": "…" }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/secrets/get" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"env":"prod","name":"db/password"}',
]);
$data = json_decode(curl_exec($ch), true);
PUT/v1/secretssecrets:write

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

Request body
{ "env": "prod", "name": "stripe/key", "value": "sk_live_…", "description": "ключ Stripe" }
Response example
{ "name": "stripe/key", "env": "prod", "version": 2 }
Code examples
curl -X PUT "https://api.spine.maxsystems.az/v1/secrets" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "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. Значение возвращается один раз.

Request body
{ "env": "prod", "name": "jwt/secret", "kind": "base64", "length": 48 }
Response example
{ "name": "jwt/secret", "env": "prod", "version": 1, "value": "…сгенерировано…" }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/secrets/generate" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "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.

Request body
{ "env": "prod", "name": "db/password", "ttl": 120 }
Response example
{ "token": "spine_lease_…", "url": "https://api.spine.maxsystems.az/v1/secrets/lease/spine_lease_…", "expiresAt": "…", "singleUse": true }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/secrets/lease" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "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 на всю).

Response example
{ "env": "prod", "name": "db/password", "value": "…" }
Code examples
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.

Parameters
channelsms (по умолчанию) | telegram
toномер телефона в международном виде (994501234567) или ваш идентификатор пользователя для Telegram
purposeзачем код: login, payment, reset. Попадает в журнал (необязательно)
ttlSecondsсрок жизни кода, 60..3600 (по умолчанию из настроек проекта)
codeLengthдлина кода, 4..8 (по умолчанию 6)
Request body
{ "channel": "sms", "to": "994501234567", "purpose": "login" }
Response example
{ "verificationId": "3f2a…", "channel": "sms", "to": "994501234567", "expiresAt": "…", "resendAfter": "…", "attemptsLeft": 5, "delivery": "sent", "sandbox": false }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/otp/send" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "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.

Parameters
verificationIdидентификатор из ответа на send (рекомендуется)
toполучатель, если verificationId не сохраняли
channelнужен вместе с to, чтобы одинаково нормализовать номер
codeкод от пользователя (обязателен)
Request body
{ "verificationId": "3f2a…", "code": "123456" }
Response example
{ "valid": true, "verificationId": "3f2a…", "channel": "sms", "to": "994501234567", "purpose": "login" }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/otp/verify" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"verificationId":"3f2a…","code":"123456"}',
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/otp/resendotp:send

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

Request body
{ "verificationId": "3f2a…" }
Response example
{ "verificationId": "3f2a…", "expiresAt": "…", "resendAfter": "…", "delivery": "sent" }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/otp/resend" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"verificationId":"3f2a…"}',
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/otp/verifications/{id}otp:verify

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

Response example
{ "verificationId": "3f2a…", "verified": false, "attemptsLeft": 4, "delivery": "sent", "sandbox": false }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/otp/verifications/{id}" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/otp/verifications/{id}", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/otp/verifications/{id}",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/otp/verifications/{id}/deliveryotp:verify

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

Response example
{ "delivery": "delivered", "note": "доставлено" }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/otp/verifications/{id}/delivery" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/otp/verifications/{id}/delivery", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
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_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/otp/settingsotp:verify

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

Response example
{ "project": "ваш-проект", "channels": ["sms","telegram","totp"], "codeLength": 6, "ttlSeconds": 300, "limits": { "perIdentifierHour": 5, "perIdentifierDay": 15, "perProjectDay": 500, "perIpHour": 20 } }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/otp/settings" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/otp/settings", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/otp/settings",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/otp/telegram/linkotp:send

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

Request body
{ "identifier": "user-42" }
Response example
{ "url": "https://t.me/вашбот?start=otp_…", "bot": "вашбот", "expiresAt": "…", "linked": false }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/otp/telegram/link" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "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 выключен: иначе человек запишет секрет с ошибкой и потеряет доступ, думая, что всё настроил.

Request body
{ "identifier": "user-42", "issuer": "Ваш проект" }
Response example
{ "secret": "JBSWY3DP…", "otpauthUrl": "otpauth://totp/…", "enabled": false }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/otp/totp/enroll" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"identifier":"user-42","issuer":"Ваш проект"}',
]);
$data = json_decode(curl_exec($ch), true);
POST/v1/otp/totp/confirmotp:totp

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

Request body
{ "identifier": "user-42", "code": "123456" }
Response example
{ "enabled": true }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/otp/totp/confirm" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "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), резервный код после использования вычёркивается.

Request body
{ "identifier": "user-42", "code": "123456" }
Response example
{ "valid": true }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/otp/totp/verify" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "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 штук). Показываются один раз, у нас хранятся только отпечатки. Прежний список при этом перестаёт работать.

Request body
{ "identifier": "user-42" }
Response example
{ "codes": ["A1B2C3D4E5", "…"] }
Code examples
curl -X POST "https://api.spine.maxsystems.az/v1/otp/totp/backup-codes" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY" \
  -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_YOUR_KEY",
    "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_YOUR_KEY", "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_YOUR_KEY", "Content-Type: application/json"],
  CURLOPT_POSTFIELDS => '{"identifier":"user-42"}',
]);
$data = json_decode(curl_exec($ch), true);
GET/v1/otp/totpotp:totp

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

Response example
{ "enrolled": true, "enabled": true, "backupLeft": 8, "backupUsed": 2 }
Code examples
curl -X GET "https://api.spine.maxsystems.az/v1/otp/totp" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/otp/totp", {
  method: "GET",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.get(
  "https://api.spine.maxsystems.az/v1/otp/totp",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
DELETE/v1/otp/totpotp:totp

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

Response example
{ "removed": true }
Code examples
curl -X DELETE "https://api.spine.maxsystems.az/v1/otp/totp" \
  -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/otp/totp", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer spine_sk_YOUR_KEY"
  }
});
const data = await res.json();
import requests

r = requests.delete(
  "https://api.spine.maxsystems.az/v1/otp/totp",
  headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
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_YOUR_KEY"],
]);
$data = json_decode(curl_exec($ch), true);
WEBHOOKSignature verification and delivery

Every delivery is a POST with the headers X-Spine-Event (event type) and X-Spine-Signature: sha256=<hex>. Body: {"event":"…","ts":"…","data":{…}}.

The signature = HMAC-SHA256 of the raw request body (bytes as received, before parsing JSON). The key is the whsec_… secret as is, the whole string: do NOT base64-decode it and do NOT strip the prefix (this is not Svix). The result is hex, with the sha256= prefix. Compare in constant time.

Verifier (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);
}
Delivery and retries

One immediate attempt; on failure, durable retries with exponential backoff (≈ after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, up to 7 attempts over ~21 hours); the queue survives a restart. Success = your 2xx response. Delivery log: GET /v1/webhooks/{id}/deliveries (shows status, codes and the exact signed body); redeliver a specific one: POST /v1/webhooks/{id}/deliveries/{deliveryId}/redeliver. Respond quickly and push heavy work to the background (delivery is at-least-once, so make your handler idempotent).

The mail.received event: what is inside data

For an incoming email we return its metadata (message), the thread RFC headers (rfc) and fraud signals (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" }
  }
}

Folders. mail.received fires only for RECEIVED mail: Inbox, Junk, Archive and the employee's own folders. Drafts and Trash produce no events under any subscription (a draft your mail client saves every 25 seconds is not a message, and mail reaches Trash only after it was already reported). Mail an employee sent from the mailbox arrives as a separate event mail.sent_folder with mailboxRole: "sent": subscribe to it if you keep the whole conversation, skip it if you only need incoming mail.

delivery. Mail between mailboxes of the SAME project has no SPF, DKIM or DMARC: it never left the platform, so there was nothing to check and security.auth will be empty. To tell such mail apart from someone spoofing your domain from outside, use delivery.internal. The flag is derived from the message PATH, not from the sender address: anything arriving from outside gets a received line from our node, and the sender cannot forge it. imported means the message was brought in by a migration from a previous host rather than received as mail.

Identifiers: message.id is a short per-mailbox id (to read the same email via the API), NOT global. As a globally unique key (idempotency, threading) use rfc.messageId; link replies into a thread by rfc.inReplyTo/rfc.references.

security: score is the spam-filter score (rspamd style; higher = more suspicious), isSpam is whether it crossed the threshold; spf/dkim/dmarc are the sender authenticity (pass/fail/none…). If the email has no such headers, the field may be null.

Sending events: what is inside data

The life of a message: mail.sent (relay accepted) → mail.delivered (recipient server accepted) or mail.bounced / mail.complained / mail.delayed. The key of a message is messageId, the same id that the send call returned.

"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, поэтому квитанция об оплате уйдёт даже тому, кто отказался от новостей.

Engagement events (require track: true)

Opens and clicks are counted by the platform itself: a transparent pixel is inserted into the HTML and links are replaced with our redirect (the tracking domain is yours when t.your-domain is enabled). The field automated: true means the open came from a proxy or a mailbox provider scanner rather than a person, and such opens are usually not counted.

"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 arrives when the recipient unsubscribed, including through the Gmail or Yahoo button itself (one-click unsubscribe per RFC 8058, enabled by the field unsubscribe: true). The address goes to the blocklist right away.

SPINE API · base address https://api.spine.maxsystems.az · machine description /v1/openapi.json