SPINE API
API işləyir RUENAZ OpenAPI Konsol

SPINE Developer API

Xarici layihələr üçün vahid platforma API-si: poçt, SSL sertifikatları, DNS, poçt qutuları, domenlər, webhook-lar və Telegram botları, hamısı tək açarla.

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

🔑 Autentifikasiya

Hər sorğuda Authorization: Bearer spine_sk_… başlığı. Açar konsolda verilir, bir dəfə göstərilir və bir kliklə ləğv olunur.

⚙️ Uçot və limitlər

Hər açar sorğuları sayır; defolt limit 120/dəq (tənzimlənir). Aşılma zamanı 429. Günlük/aylıq kvotalar istəyə görədir.

⚠️ Xəta kodları

401 yanlış açar · 403 icazə yoxdur · 429 limit · 400 yanlış sorğu (error sahəsi).

İcazələr (scope-lar)
ScopeNəyə icazə verir
mail:sendMəktub göndərmə (tək və paketlə, göndərilənlərin statusu və statistikası)
mail:readÖz qutularınızın məktublarını oxumaq
mail:suppressionsBloklanmış ünvanlar: baxmaq, əlavə etmək, blokdan çıxarmaq
mail:mailboxesQutuların yaradılması və idarəsi
mail:domainsDomenlərin qoşulması və statusu
mail:templatesШаблоны писем проекта: смотреть, править, откатывать, слать тест
notify:sendУведомления одним запросом: почта, Telegram, вебхук, с запасными каналами
mail:subscriptionsСписки рассылок и подписки: подписать, отписать, посмотреть согласия
protection:verifyПроверка ответа Turnstile для форм вашего сайта
domains:readСроки регистрации своих доменов: когда истекает, у какого регистратора
mail:migrateПеренос почты с другого хостинга по IMAP (задания и прогресс)
cert:issueSertifikatların sifarişi və yenidən buraxılışı
cert:readСтатус и скачивание сертификатов, SPKI-пины
cert:acmeACME-доступ: пары EAB для certbot, acme.sh, cert-manager, файрволов
cert:delegateCNAME-делегации для доменов, чей DNS не у нас
pki:issueВыпуск из приватного CA: длинные серты и клиентские mTLS
pki:readПриватный CA: корневые сертификаты, цепочки, CRL
dns:readZonanın DNS yazılarının oxunması
dns:recordsZonanın DNS yazılarının idarəsi
webhooksWebhook-lara abunə (gələn məktublar, çatdırılma hadisələri)
telegram:botsLayihənin Telegram botlarının idarəsi
webmail:loginİşçinin webmail-a avto-giriş linkləri (yerləşdirmə üçün SSO)
brand:readБренд-кит проекта (логотипы, цвета, шрифты, файлы)
project:readСводка проекта: домены, серты, ключи, боты, бренд, биллинг (self-view)
audit:readЖурнал событий проекта (кто/что/когда менял)
siteconfig:readSEO / site-config проекта (raw-конфиг; артефакты отдаются публично)
secrets:readЧитать секреты проекта из сейфа (карта/.env/по имени)
secrets:writeЗаписывать/генерировать/удалять секреты проекта в сейфе
security:readОтчёт безопасности своих доменов: оценка и открытые находки
otp:sendВерификация: выдать и доставить одноразовый код (SMS, Telegram)
otp:verifyВерификация: проверить код, статус попытки и доставки
otp:totpВерификация: приложение-аутентификатор и резервные коды пользователей проекта

Poçt

POST/v1/mail/sendmail:send

Məktub göndər. Göndərənin domeni SPINE poçtuna qoşulmalı və layihəyə aid olmalıdır.

Parametrlər
fromgöndərənin ünvanı (məcburidir)
toalan (məcburidir)
subjectmövzu (və ya template)
text / htmlməktubun mətni
categorystatistika üçün etiket (məcburi deyil)
cc / bcc / replyToünvan siyahıları (istəyə görə)
attachmentsqoşmalar: [{ filename, content (base64), contentType }], məktuba 15 MB-a qədər
headersöz məktub başlıqlarınız, obyekt (istəyə görə)
streamtransactional (по умолчанию) | marketing. Транзакционный поток не блокируется отписками и жалобами: платёжный документ получатель должен получить, даже если отказался от новостей. Жёсткий отказ (несуществующий ящик) действует в обоих потоках
scopeвладелец отношения с получателем (например slug мерчанта). Жалобы и отписки ложатся именно на него, а не на весь проект. Пусто = весь проект, как раньше
campaignkampaniya etiketi: /v1/mail/stats-dakı qıf ona görə hesablanır
tracktrue açılış və klikləri saymaq deməkdir (mail.opened / mail.clicked hadisələri)
unsubscribetrue bir kliklə abunəlikdən çıxma əlavə edir (RFC 8058, Gmail və Yahoo tələbi)
sendAtISO 8601 tarixi, təxirə salınmış göndərmə (90 günə qədər, DELETE ilə ləğv)
idempotencyKeydublikatdan qoruma (məcburi deyil)
Sorğu gövdəsi
{
  "from": "noreply@ваш-домен",
  "to": "user@example.com",
  "subject": "Привет",
  "text": "Тело письма",
  "html": "<p>Тело письма со <a href=\"https://example.com\">ссылкой</a></p>",
  "campaign": "welcome-july",
  "track": true,
  "unsubscribe": true
}
Cavab nümunəsi
{ "id": "01K…", "status": "sent", "relay": "ses", "relayMessageId": "…" }
Kod nümunələri
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

Paketlə göndər: bir sorğuda 200 alıcıya qədər, hər birinin öz vars-ı. Ümumi sahələr yuxarı səviyyədə verilir.

Parametrlər
messagesməktub siyahısı: [{ to, vars, subject?, html?, attachments? }] (mütləqdir)
from / subject / html / text / templatepaketdəki bütün məktublar üçün ümumi sahələr
campaign / track / unsubscribe / sendAttək göndərmə kimi, bütün paketə tətbiq olunur
Sorğu gövdəsi
{
  "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": "Борис" } }
  ]
}
Cavab nümunəsi
{ "total": 2, "counts": { "sent": 2 }, "results": [ { "to": "a@example.com", "id": "01K…", "status": "sent" } ] }
Kod nümunələri
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, поэтому обойти весь ящик можно без догадок.

Parametrlər
limitсколько вернуть, 1..500 (по умолчанию 50)
offsetсколько пропустить: обход страницами offset += limit
sinceписьма СТРОГО позже этого момента, ISO 8601. ⚠️ Письмо той же секунды не вернётся
beforeписьма раньше этого момента, ISO 8601
hasAttachmenttrue, только письма с вложениями, false, только без
withпереписка с адресом: письма ОТ него или К нему
from / toточечный фильтр по отправителю или получателю
Cavab nümunəsi
{ "messages": [ { "id": "…", "subject": "…", "receivedAt": "2026-08-10T09:00:00Z", "hasAttachment": true, "rfcMessageId": "20260810020431.ade65@example.com" } ], "total": 1843, "offset": 0, "limit": 50, "hasMore": true }
Kod nümunələri
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: нужно, чтобы сопоставить письмо с уже сохранённым у себя, не перебирая списки. Значение принимается и в угловых скобках, и без них. Если письмо не найдено, в тексте ошибки написано, сколько писем просмотрено и до какой даты дошёл поиск: «не нашли в просмотренном» и «не существует» это разные ответы.

Parametrlər
valueMessage-ID письма (обязательно)
since / beforeсузить период поиска, ISO 8601: ускоряет ответ на больших ящиках
Cavab nümunəsi
{ "message": { "id": "…", "subject": "…", "rfcMessageId": "…" }, "lookup": { "source": "index", "scanned": 0 } }
Kod nümunələri
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

Göndərdiyiniz məktubların statuslarla siyahısı.

Parametrlər
limitnə qədər qaytarılsın (200-ə qədər, susmaya görə 50)
statusfiltr: sent | delivered | bounced | complained | delayed | scheduled | suppressed | failed
campaignkampaniya etiketinə görə filtr
Cavab nümunəsi
{ "messages": [ { "id": "01K…", "toAddr": "user@example.com", "status": "delivered", "opens": 1, "clicks": 0 } ] }
Kod nümunələri
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

Məktubun statusu və bütün tarixi: reley qəbul etdi, çatdırıldı, açıldı, linkə klik.

Cavab nümunəsi
{ "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 } ] }
Kod nümunələri
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

Hələ göndərilməmiş təxirə salınmış məktubu ləğv et.

Cavab nümunəsi
{ "id": "01K…", "status": "canceled" }
Kod nümunələri
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

Dövr üzrə çatdırılma qıfı: qəbul edildi, çatdırıldı, açıldı, kliklər, rədd, şikayətlər. Rədd və şikayət faizlərini Amazon hədlərindən (5% və 0.1%) aşağı saxlamaq lazımdır.

Parametrlər
daysdövr günlərlə (1..365, susmaya görə 30)
campaignyalnız bir kampaniya üzrə
Cavab nümunəsi
{ "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 } }
Kod nümunələri
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 с объяснением. Помогает не набирать жёсткие отказы, которые бьют по репутации домена.

Sorğu gövdəsi
{ "emails": ["user@gmial.com", "someone@example.com"] }
Cavab nümunəsi
{ "total": 2, "counts": { "warn": 1, "ok": 1 },
  "results": [ { "email": "user@gmial.com", "verdict": "warn", "suggestion": "user@gmail.com",
                 "reasons": ["похоже на опечатку в домене"], "hasMx": true, "disposable": false } ] }
Kod nümunələri
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

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

Sorğu gövdəsi
{ "subject": "СРОЧНО!!", "html": "<img src=…>", "category": "marketing" }
Cavab nümunəsi
{ "score": 8, "verdict": "bad",
  "issues": [ { "weight": 3, "text": "нет текстовой части: письмо только в HTML" } ] }
Kod nümunələri
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). Нужно, когда своего разбора хочется больше, чем нашего: вы получаете ровно то, что лежит на сервере. ⚠️ Политика проекта и антивирус применяются и здесь: письмо с запрещённым или заражённым вложением не отдаётся целиком, иначе через сырой файл проверку можно было бы обойти. Выдача пишется в журнал проекта: это доступ ко всей переписке письма.

Cavab nümunəsi
(поток message/rfc822, имя файла <id письма>.eml)
Kod nümunələri
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

Layihənin bloklanmış ünvanları: sərt rəddlər, spam şikayətləri, abunəlikdən çıxmalar. Onlara məktub göndərilmir.

Parametrlər
emailbir ünvanı yoxla
scopeблокировки этого владельца плюс общепроектные (они действуют на всех)
streamtransactional, показать только то, что блокирует платёжные письма
limitnə qədər qaytarılsın (500-ə qədər)
Cavab nümunəsi
{ "suppressions": [ { "email": "user@example.com", "reason": "hard_bounce", "scope": null, "ts": "2026-07-25T10:00:00Z" } ] }
Kod nümunələri
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

Ünvanı əl ilə blokla (məsələn, alıcının xahişi ilə).

Parametrlər
emailünvan (mütləqdir)
reasonhard_bounce | complaint | manual | unsubscribe (susmaya görə manual)
scopeчья это блокировка: пусто = весь проект
Sorğu gövdəsi
{ "email": "user@example.com", "reason": "manual", "scope": "мерчант" }
Cavab nümunəsi
{ "email": "user@example.com", "reason": "manual", "tenant": "ваш-проект", "scope": "мерчант" }
Kod nümunələri
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 снимается блокировка ровно этого владельца, без него, все блокировки адреса в проекте.

Parametrlər
scopeснять блокировку только этого владельца
Cavab nümunəsi
{ "email": "user@example.com", "removed": 1, "scope": "мерчант" }
Kod nümunələri
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

Öz limitləriniz və qaydalarınız: qoşma ölçüləri, qadağan olunan fayl tipləri, antivirus rejimi, göndərmə hədləri. Məktubun nə üçün rədd edildiyini təxmin etməmək üçün faydalıdır.

Cavab nümunəsi
{ "project": "ваш-проект",
  "attachments": { "maxBytesOutbound": 15728640, "maxBytesInbound": 26214400,
    "blockedExtensions": ["exe", "js", "bat", "…"], "antivirus": "отклоняем заражённое" },
  "sending": { "maxRecipientsPerBatch": 200, "dailyCap": null } }
Kod nümunələri
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

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

Cavab nümunəsi
{ "lists": [ { "slug": "news", "name": "Новости", "doubleOptIn": true,
    "confirmed": 128, "pending": 4, "unsubscribed": 11 } ] }
Kod nümunələri
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, и так чужой адрес нельзя подписать за человека.

Sorğu gövdəsi
{ "slug": "news", "name": "Новости", "description": "раз в месяц", "doubleOptIn": true }
Cavab nümunəsi
{ "id": "…", "slug": "news", "name": "Новости", "doubleOptIn": true }
Kod nümunələri
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

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

Cavab nümunəsi
{ "deleted": "news" }
Kod nümunələri
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.

Sorğu gövdəsi
{ "email": "user@example.com", "list": "news", "source": "форма на сайте" }
Cavab nümunəsi
{ "email": "user@example.com", "list": "news", "status": "pending", "confirmationSent": true }
Kod nümunələri
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 оставлено синонимом ради совместимости. Согласия адреса по всем спискам проекта и готовая ссылка на его центр подписок. Ссылку можно положить в подвал письма: на ней человек сам управляет подписками.

Parametrlər
emailадрес, который проверяем
Cavab nümunəsi
{ "email": "user@example.com",
  "subscriptions": [ { "list": "news", "status": "confirmed", "confirmedAt": "2026-08-01T10:00:00Z" } ],
  "prefsUrl": "https://t.example.com/s/…" }
Kod nümunələri
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 отпишет от всех списков проекта.

Parametrlər
emailадрес
listслаг списка (необязательно)
Cavab nümunəsi
{ "email": "user@example.com", "list": "news", "unsubscribed": 1 }
Kod nümunələri
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

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

Cavab nümunəsi
{ "templates": [ { "slug": "invoice", "name": "Счёт", "version": 3, "variables": ["name","number"] } ] }
Kod nümunələri
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}} платформа находит сама и показывает списком.

Sorğu gövdəsi
{ "slug": "invoice", "name": "Счёт", "subject": "Счёт {{number}}",
  "html": "<p>Здравствуйте, {{name}}. Счёт {{number}} на {{sum}}.</p>" }
Cavab nümunəsi
{ "slug": "invoice", "version": 1, "variables": ["name","number","sum"] }
Kod nümunələri
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

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

Sorğu gövdəsi
{ "subject": "Счёт {{number}} к оплате" }
Cavab nümunəsi
{ "slug": "invoice", "version": 2 }
Kod nümunələri
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

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

Sorğu gövdəsi
{ "version": 1 }
Cavab nümunəsi
{ "slug": "invoice", "version": 3, "rolledBackFrom": 1 }
Kod nümunələri
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

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

Sorğu gövdəsi
{ "vars": { "name": "Михаил", "number": "SPINE-2026-0007", "sum": "59 AZN" } }
Cavab nümunəsi
{ "subject": "Счёт SPINE-2026-0007", "html": "…", "check": { "score": 0, "verdict": "ok" } }
Kod nümunələri
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.

Sorğu gövdəsi
{ "to": "me@example.com", "vars": { "name": "Михаил" } }
Cavab nümunəsi
{ "id": "01K…", "status": "sent" }
Kod nümunələri
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, шлю письмо» у себя. В ответе видно, какой канал сработал и что пробовали до него.

Sorğu gövdəsi
{ "title": "Платёж прошёл", "body": "Счёт SPINE-2026-0007 оплачен",
  "channels": [ { "channel": "telegram", "to": "12345678" }, { "channel": "email", "to": "user@example.com" } ] }
Cavab nümunəsi
{ "channel": "email", "to": "user@example.com", "status": "sent",
  "tried": [ { "channel": "telegram", "error": "Telegram HTTP 400" } ] }
Kod nümunələri
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);

Sertifikatlar

POST/v1/certs/issuecert:issue

Publik SSL sertifikatı sifariş et. Domen idarə olunan zonada olmalı və ya CNAME deleqasiyası olmalıdır, özü də deleqasiya sertifikatdakı HƏR ad üçün lazımdır: sahiblik hər ad üzrə ayrıca, öz _acme-challenge.<ad> ilə yoxlanılır (istisna, wildcard, o, domen yazısı ilə yoxlanılır). Vermə 15-30 saniyə çəkir. Müddəti müştəri təyin etmir: CA/Browser Forum qaydalarına görə publik sertifikat 199 gün yaşayır (2027-dən 100, 2029-dan 47). Uzun müddət lazımdırsa, POST /v1/pki/issue-a baxın. ⚠️ Cavabdakı pinWarning sahəsi o deməkdir ki, bu domenlər üzrə artıq pinləri quraşdırılmış BAŞQA sertifikat var: yenisinin öz açarı var, deməli pini də fərqlidir. Əvvəlki pin lazımdırsa, ikinci sertifikat sifariş etmək əvəzinə mövcudu yenidən verin.

Parametrlər
domainsdomenlərin siyahısı, wildcard *.domain olar (məcburidir)
csröz CSR-iniz, onda şəxsi açar sizdə qalır (məcburi deyil)
autoRenewavto-yeniləmə, defolt olaraq true
Sorğu gövdəsi
{ "domains": ["app.ваш-домен", "*.ваш-домен"], "autoRenew": true }
Cavab nümunəsi
{ "id": "…", "subject": "app.ваш-домен", "sans": [...], "status": "pending",
  "pinWarning": ["Банк X (Java-приложение)"] }
Kod nümunələri
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

Sertifikatın statusu (pending → active | failed).

Cavab nümunəsi
{ "status": "active", "notAfter": "…", "daysLeft": 89 }
Kod nümunələri
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

Sertifikatı yüklə (fullchain + şəxsi açar; CSR ilə buraxılan sertifikatlar üçün privkey=null).

Cavab nümunəsi
{ "fullchain": "-----BEGIN CERTIFICATE-----…", "privkey": "…" }
Kod nümunələri
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

Sertifikatın SPKI pinləri. Tərəfdaşlar sertifikatın özü yerinə məhz bunu quraşdırır: platforma BU sertifikatın şəxsi açarını saxlayır, ona görə pin həm avtomatik yenilənməni, həm də əl ilə yenidən verməni yaşayır. ⚠️ Pin dəyişəcək, əgər birincini yenidən vermək əvəzinə eyni domenlərə İKİNCİ sertifikat yaratsanız, silib yenidən yaratsanız, «Açarı saxla» seçimini söndürsəniz və ya açarın rotasiyasını istəsəniz. Cari və ehtiyat pini, həmçinin curl və OkHttp üçün hazır sətirləri qaytarırıq.

Cavab nümunəsi
{ "spkiPin": "…", "backupSpkiPin": "…", "keepKey": true,
  "formats": { "curl": "curl --pinnedpubkey \"sha256//…\" https://api.example.com/" } }
Kod nümunələri
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

Pinin haradasa quraşdırıldığını qeyd et. Yazı reyestrə düşür və açar dəyişikliyindən əvvəl platforma kimin təsirlənəcəyini xəbərdar edir.

Sorğu gövdəsi
{ "party": "Партнёр X", "system": "Java-приложение", "contact": "dev@partner.com" }
Cavab nümunəsi
{ "id": "…", "registered": true, "pin": "…" }
Kod nümunələri
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 və ACME

POST/v1/acme/eabcert:acme

Bizim ACME serverimiz üçün özünüzə External Account Binding cütü verin. Bundan sonra istənilən standart müştəri bizim kodumuz olmadan platforma ilə işləyir: certbot, acme.sh, cert-manager, Caddy, Traefik, FortiGate 7.6.3+, Cisco ASA 9.23.1+. HMAC açarı BİR dəfə qaytarılır.

Parametrlər
labelözünüz üçün etiket (məcburi deyil)
allowedDomainsdomenləri məhdudlaşdır (boş = mövcud olan hər biri)
issuerletsencrypt (susmaya görə) və ya private
policyauto (yoxlamanı özümüz keçirik) və ya verify (müştəri keçir)
Sorğu gövdəsi
{ "label": "prod-cluster", "allowedDomains": ["example.com"] }
Cavab nümunəsi
{ "kid": "…", "hmacKey": "…",
  "directoryUrl": "https://api.spine.maxsystems.az/acme/directory",
  "snippets": { "certbot": "certbot register --server … --eab-kid … --eab-hmac-key …" } }
Kod nümunələri
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

CNAME ilə sahiblik yoxlaması deleqasiyası yarat. DNS-ini biz idarə etmədiyimiz domenlər üçün lazımdır: müştəri bir dəfə bir yazı əlavə edir, sonra bütün yoxlamaları biz öz tərəfimizdə edirik və onun DNS-inə giriş tələb olunmur.

Sorğu gövdəsi
{ "domain": "client-domain.com" }
Cavab nümunəsi
{ "domain": "client-domain.com", "record": { "type": "CNAME",
  "name": "_acme-challenge.client-domain.com", "value": "<метка>.acme.spine.maxsystems.az", "ttl": 300 } }
Kod nümunələri
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

Müştərinin CNAME əlavə etdiyini yoxla. Yalnız DNS oxuyur, heç nə dəyişmir.

Cavab nümunəsi
{ "ok": true, "expected": "…", "found": ["…"] }
Kod nümunələri
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

Layihənin deleqasiyalarının siyahısı və vəziyyəti.

Cavab nümunəsi
{ "delegations": [{ "domain": "client-domain.com", "verified": true }] }
Kod nümunələri
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

Platformanın şəxsi CA-sı ilə sertifikat ver. CA/Browser Forum qaydaları şəxsi CA-ya şamil olunmur, ona görə müddəti siz təyin edirsiniz: 3650 günə qədər. Tərəfdaş inteqrasiyaları, daxili servislər və mTLS müştəri sertifikatları üçün uyğundur.

Parametrlər
domainsserver sertifikatı üçün domenlər
commonNamemTLS müştəri sertifikatı üçün sahibin adı
daysgün ilə müddət, susmaya görə 397, maksimum 3650
certTypeserver (susmaya görə) və ya client
csröz CSR-iniz: şəxsi açar sizdə qalır (məcburi deyil)
Sorğu gövdəsi
{ "domains": ["internal.example.com"], "days": 1095, "certType": "server" }
Cavab nümunəsi
{ "id": "…", "subject": "internal.example.com", "status": "pending", "lifetimeDays": 1095 }
Kod nümunələri
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

Şəxsi CA-nın kök sertifikatı və zənciri. Tərəfdaş bunu bir dəfə truststore-a quraşdırır və illərlə istifadə edir: son sertifikatların yenidən verilməsi ona toxunmur.

Cavab nümunəsi
{ "name": "SPINE Partner CA Root", "rootPem": "-----BEGIN CERTIFICATE-----…",
  "notAfter": "2036-07-29T…", "crlUrl": "https://api.spine.maxsystems.az/v1/pki/…/crl.pem" }
Kod nümunələri
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

Şəxsi CA-nın ləğv edilmiş sertifikatlar siyahısı (CRL), PEM formatında.

Kod nümunələri
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

Layihə üçün əlçatan sertifikat mərkəzləri: adları, növü və etibarlılıq müddəti.

Cavab nümunəsi
{ "cas": [{ "name": "SPINE Partner CA Issuing", "kind": "intermediate",
  "notAfter": "2031-07-28T…", "isDefault": true }] }
Kod nümunələri
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);

Poçt qutuları

POST/v1/mailboxesmail:mailboxes

Öz domenində poçt qutusu yarat.

Parametrlər
name@ işarəsindən əvvəlki qutu adı (2-40, [a-z0-9._-])
domainlayihə domeni (məcburidir)
passwordqutu parolu (min. 8)
descriptiontəsvir (məcburi deyil)
Sorğu gövdəsi
{ "name": "info", "domain": "ваш-домен", "password": "…" }
Cavab nümunəsi
{ "email": "info@ваш-домен", "id": "…" }
Kod nümunələri
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

Layihə domenlərindəki poçt qutuları və qrupların siyahısı. type sahəsi: personal (şəxsi), shared (ortaq, bir neçə nəfərin girişi var), group (paylama qrupu). Qruplarda membersCount sahəsi olur.

Cavab nümunəsi
{ "mailboxes": [ { "id": "…", "email": "ivan@ваш-домен", "type": "personal", "usedBytes": 0 }, { "id": "…", "email": "team@ваш-домен", "type": "group", "membersCount": 4 } ] }
Kod nümunələri
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

Poçt qutusunu sil (yalnız öz domenində).

Cavab nümunəsi
{ "deleted": true }
Kod nümunələri
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

Poçt qutusunu ortaq (shared) kimi işarələ və ya işarəni götür. Ortaq qutu, bir neçə nəfərə giriş verilən şəxsi qutudur (support@, info@). Qutunun type sahəsini dəyişir.

Sorğu gövdəsi
{ "shared": true }
Cavab nümunəsi
{ "id": "…", "shared": true }
Kod nümunələri
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 (плюс управление папками и удаление).

Cavab nümunəsi
{ "access": [ { "accountId": "u", "email": "tamara@example.com", "level": "write", "folders": 5 } ] }
Kod nümunələri
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». Доступ выдаётся на все папки ящика; если позже появятся новые папки, вызвать повторно.

Sorğu gövdəsi
{ "mailbox": "tamara@example.com", "level": "write" }
Cavab nümunəsi
{ "ok": true, "mailbox": "tamara@example.com", "level": "write", "folders": 5 }
Kod nümunələri
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.

Cavab nümunəsi
{ "ok": true, "folders": 5 }
Kod nümunələri
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

Qutudakı son məktubların siyahısı (təzələr yuxarıda). Ünvana görə süzgəcləmək olar, kontaktla yazışma paneli üçün əlverişlidir.

Parametrlər
limitneçə məktub qaytarılsın, 100-ə qədər (defolt 20)
withkontaktın ünvanı: ondan və ya ona olan məktublar (qarşı tərəflə yazışma)
fromgöndərənə görə süzgəc (ünvanın alt sətri)
toalana görə süzgəc (ünvanın alt sətri)
Cavab nümunəsi
{ "messages": [ { "id": "eaaaaab", "messageId": ["<abc@bank.az>"], "subject": "…", "from": [{"email":"…"}], "receivedAt": "…", "preview": "…" } ] }
Kod nümunələri
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

Tam məktub: mətn, RFC Message-ID/In-Reply-To/References (thread üçün), xam başlıqlar və yükləmə id-ləri olan qoşma siyahısı. Diqqət: yoldakı id per-mailbox-dır (qlobal deyil); qlobal açar messageId-dir. Başqa layihənin qutusu 403 deyil, 404 qaytarır.

Cavab nümunəsi
{ "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>"}, …] }
Kod nümunələri
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

Məktubun qoşmaları: ad, tip, ölçü, yükləmə üçün id və antivirus qərarı. Faylın yoluxduğunu və ya siyasətlə qadağan olunduğunu bilmək üçün onu çəkmək lazım deyil.

Cavab nümunəsi
{ "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" }
  } ] }
Kod nümunələri
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

Qoşmanı yüklə. Fayl özü axınla verilir (base64 deyil), tip və ad başlıqları ilə. Verməkdən əvvəl fayl yoxlanılır: yoluxmuş və siyasətlə qadağan olunan verilmir.

Parametrlər
attIdhəmin məktubun qoşma siyahısındaki qoşma id-si
Cavab nümunəsi
HTTP 200
Content-Type: application/pdf
Content-Disposition: attachment; filename*=UTF-8''akt-sverki.pdf
Content-Length: 1665

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

# 409, вложение заражено или его тип запрещён политикой проекта
# 413, вложение больше лимита проекта
# 503, антивирус недоступен: файл не отдаём (fail-closed), пробуйте позже
Kod nümunələri
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, разбивка по папкам, где есть непрочитанные.

Cavab nümunəsi
{ "unread": 3, "total": 5, "folders": [ { "name": "Inbox", "role": "inbox", "unread": 3 }, { "name": "Черновики", "role": null, "unread": 2 } ] }
Kod nümunələri
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 нужен пароль для внешних приложений: обычный пароль их сервер отклоняет.

Parametrlər
targetEmailящик у нас, куда переносим
targetPasswordпароль этого ящика
sourceHostпресет (vk|yandex|google|outlook) или адрес IMAP-сервера
sourcePortпорт источника (по умолчанию 993)
sourceSecuretrue = TLS сразу, false = STARTTLS
sourceUserлогин на источнике (по умолчанию равен targetEmail)
sourcePasswordпароль на источнике (для VK и Mail.ru, пароль приложения)
Sorğu gövdəsi
{ "targetEmail": "info@ваш-домен", "targetPassword": "…", "sourceHost": "vk", "sourcePassword": "…" }
Cavab nümunəsi
{ "id": "…", "targetEmail": "info@ваш-домен", "status": "pending", "copiedMessages": 0 }
Kod nümunələri
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

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

Cavab nümunəsi
{ "presets": { "vk": { "host": "imap.mail.ru", "port": 993 } }, "migrations": [ { "id": "…", "status": "done", "copiedMessages": 4210 } ] }
Kod nümunələri
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

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

Cavab nümunəsi
{ "migration": { "status": "running" }, "folders": [ { "sourcePath": "Отправленные", "targetPath": "Sent", "copied": 812, "total": 812 } ], "log": ["…"] }
Kod nümunələri
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.

Parametrlər
idsсписок id заданий
includeTrashпереносить корзину (по умолчанию да)
includeJunkпереносить спам (по умолчанию нет)
Sorğu gövdəsi
{ "ids": ["…"], "includeTrash": true, "includeJunk": false }
Cavab nümunəsi
{ "queued": 1 }
Kod nümunələri
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

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

Cavab nümunəsi
{ "deleted": true }
Kod nümunələri
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);

Qruplar

GET/v1/groupsmail:mailboxes

Layihənin paylama qruplarının siyahısı. Qrup, team@domain ünvanıdır; ona gələn məktub bütün üzvlərin şəxsi qutularına paylanır.

Cavab nümunəsi
{ "groups": [ { "id": "…", "email": "team@ваш-домен", "membersCount": 4 } ] }
Kod nümunələri
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

name@domain paylama qrupu yarat. Domen sizin layihənizə aid olmalıdır.

Parametrlər
domainlayihə domeni (məcburidir)
nameünvan adı, 2-40 simvol [a-z0-9._-] (məcburidir)
descriptionqrupun təsviri (məcburi deyil)
Sorğu gövdəsi
{ "domain": "ваш-домен.az", "name": "team", "description": "Вся команда" }
Cavab nümunəsi
{ "id": "…", "email": "team@ваш-домен.az" }
Kod nümunələri
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

Qrupu sil.

Cavab nümunəsi
{ "deleted": true }
Kod nümunələri
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

Qrupun üzvləri (məktubun paylandığı qutular).

Cavab nümunəsi
{ "members": [ { "id": "…", "email": "ivan@ваш-домен.az" } ] }
Kod nümunələri
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

Poçt qutusunu ünvanına görə qrupa əlavə et.

Sorğu gövdəsi
{ "mailbox": "ivan@ваш-домен.az" }
Cavab nümunəsi
{ "ok": true, "member": { "id": "…", "email": "ivan@ваш-домен.az" } }
Kod nümunələri
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

Üzvü qrupdan çıxar.

Cavab nümunəsi
{ "ok": true }
Kod nümunələri
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 минут.

Sorğu gövdəsi
{ "email": "ivan@ваш-домен.az", "currentPassword": "…", "newPassword": "…" }
Cavab nümunəsi
{ "ok": true }
Kod nümunələri
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@). Нужно потому, что почтовый сервер даёт читать общий ящик, но отправлять от его имени не даёт. Платформа проверяет, что у отправителя есть доступ на запись к этому ящику, и пишет каждую отправку в аудит.

Sorğu gövdəsi
{ "accountId": "id ящика", "emailId": "id черновика" }
Cavab nümunəsi
{ "sent": true }
Kod nümunələri
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} отменяет.

Sorğu gövdəsi
{ "accountId": "id ящика", "emailId": "id черновика", "sendAt": 1785400000000 }
Cavab nümunəsi
{ "id": "…", "sendAt": "2026-07-30T06:00:00.000Z", "status": "pending" }
Kod nümunələri
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

İşçiyə ÖZ webmail qutusuna birdəfəlik avto-giriş linki ver, beləcə poçtu tətbiqinizə (məs. CRM) yerləşdirə bilərsiniz. Link 60 saniyə yaşayır və birdəfəlikdir: onu açanda (yeni tabda və ya iframe-də) işçi ikinci girişsiz poçta düşür. Qutu sizin domendə olmalıdır.

Parametrlər
mailboxişçinin sizin domendəki tam qutu ünvanı (məcburidir)
Sorğu gövdəsi
{ "mailbox": "ivan@ваш-домен.az" }
Cavab nümunəsi
{ "url": "https://webmail.ваш-домен.az/?login_token=…", "email": "ivan@ваш-домен.az", "expiresIn": 60 }
Kod nümunələri
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);

Domenlər

POST/v1/maildomainsmail:domains

Domeni poçta qoş (domeni yaradır və mümkün olduqda DNS-i özü dərc edir).

Sorğu gövdəsi
{ "name": "ваш-домен" }
Cavab nümunəsi
{ "id": "…", "name": "ваш-домен", "autoDns": { "created": 6 }, "records": [...], "ready": false }
Kod nümunələri
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

Layihənin qoşulmuş domenlərinin siyahısı.

Cavab nümunəsi
{ "domains": [ { "id": "…", "name": "ваш-домен" } ] }
Kod nümunələri
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 yazıları və domenin doğrulama statusu.

Cavab nümunəsi
{ "name": "ваш-домен", "records": [...], "ready": true }
Kod nümunələri
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

Domendə mövcud olmayan ünvanlara gələn məktubların qəbulu: aktivdirmi, hansı qutuya gedir və göndərənə cavab veririkmi. Qəbul insanın şəxsi qutusuna yönəldilibsə, warning sahəsi görünür.

Cavab nümunəsi
{ "domain": "ваш-домен", "enabled": true, "mailbox": "catchall@ваш-домен", "autoReply": true }
Kod nümunələri
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

Mövcud olmayan ünvanlara qəbulu aktivləşdir, dəyiş və ya söndür. Qutu mövcud olmalı və elə həmin domenə aid olmalıdır. Qəbul aktiv olduqca göndərənə ingiliscə avtocavab gedir: belə ünvan yoxdur, buraya cavab yazmağın mənası yoxdur, domenin dəstək ünvanı budur. Cavab yalnız canlı insanlara gedir: paylanmalara, robotlara və çatdırılma hesabatlarına cavab verilmir, bir ünvana sutkada bir dəfədən çox yazılmır.

Parametrlər
mailboxbu domendəki qəbul qutusu və ya söndürmək üçün null (onda mövcud olmayan ünvanlara gələn məktublar imtina ilə geri qaytarılır)
autoReplygöndərənə ünvanın mövcud olmadığını bildirmək (defolt olaraq true)
Sorğu gövdəsi
{ "mailbox": "catchall@ваш-домен", "autoReply": true }
Cavab nümunəsi
{ "domain": "ваш-домен", "enabled": true, "mailbox": "catchall@ваш-домен", "autoReply": true }
Kod nümunələri
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.

Cavab nümunəsi
{ "domains": [ { "domain": "ваш-домен", "registrar": "…", "expiresAt": "2027-03-01T00:00:00.000Z", "daysLeft": 208, "state": "ok", "uses": ["почта"], "source": "rdap" } ], "alertDays": [40, 30, 15, 5, 1] }
Kod nümunələri
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 из формы вашего сайта. Секрет хранится у нас и наружу не отдаётся: класть его в браузер нельзя, иначе проверку обойдут за минуту. Вы присылаете только токен, который вернул виджет, и домен, для которого он выдан.

Parametrlər
tokenзначение поля cf-turnstile-response из вашей формы (обязательно)
domainдомен, для которого настроены ключи Turnstile
Sorğu gövdəsi
{ "token": "0.abc…", "domain": "ваш-домен" }
Cavab nümunəsi
{ "ok": true, "errors": [] }
Kod nümunələri
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) на оценку не влияют: не проверено это не «чисто».

Cavab nümunəsi
{ "project": "ваш-проект", "grade": "B", "score": 82, "open": 3, "findings": [ { "target": "ваш-домен", "category": "mail", "severity": "medium", "title": "DMARC в режиме наблюдения (p=none)", "detail": "…" } ] }
Kod nümunələri
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

Zonanın DNS yazılarının siyahısı (zona layihəyə bağlı olmalıdır).

Cavab nümunəsi
{ "zone": "ваш-домен", "records": [ { "id": "…", "type": "A", "name": "@", "data": "1.2.3.4" } ] }
Kod nümunələri
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

Layihə zonasında DNS yazısı yarat.

Parametrlər
typeA | AAAA | TXT | MX | CNAME | NS | SRV | CAA
nameyazının adı (kök üçün '@')
datadəyər
priority / ttlMX / TTL üçün (məcburi deyil)
Sorğu gövdəsi
{ "type": "CNAME", "name": "app", "data": "target.example.com" }
Cavab nümunəsi
{ "created": true }
Kod nümunələri
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

DNS yazısını sil. GoDaddy üçün ?data=<dəyər> əlavə et.

Cavab nümunəsi
{ "deleted": true }
Kod nümunələri
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);

Webhook-lar

POST/v1/webhookswebhooks

Hadisələrə abunə ol: platforma sizin URL-ə POST göndərəcək. Cavabdakı secret bir dəfə göstərilir.

Parametrlər
urlqəbul üçün sizin https ünvanınız (məcburidir)
eventsqutudakı məktub: mail.received (qəbul edilib), mail.sent_folder (işçi qutudan göndərib) · API ilə göndərmə: mail.sent, mail.delivered, mail.bounced, mail.complained, mail.delayed, mail.rejected · maraq (track:true ilə): mail.opened, mail.clicked, mail.unsubscribed
Sorğu gövdəsi
{ "url": "https://ваш-сервер/webhook", "events": ["mail.received", "mail.bounced"] }
Cavab nümunəsi
{ "id": "…", "url": "…", "events": [...], "secret": "whsec_…" }
Kod nümunələri
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

Webhook-larınızın siyahısı.

Cavab nümunəsi
{ "webhooks": [ { "id": "…", "url": "…", "events": [...], "active": true } ] }
Kod nümunələri
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

Abunəni dəyiş: hadisə siyahısı, qəbul ünvanı, yandır və ya söndür. GİZLİ AÇAR DƏYİŞMİR, ona görə imza yoxlayıcınıza toxunmaq lazım deyil. Yalnız dəyişdiyiniz sahələri göndərin.

Parametrlər
urlyeni https qəbul ünvanı (istəyə bağlı)
eventstam yeni hadisə siyahısı (istəyə bağlı)
activefalse çatdırılmanı dayandırır, abunə və jurnal itmir (istəyə bağlı)
Sorğu gövdəsi
{ "events": ["mail.received", "mail.sent_folder", "mail.bounced"] }
Cavab nümunəsi
{ "id": "…", "url": "…", "events": [...], "active": true }
Kod nümunələri
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

İmza açarını dəyiş (sızma, işçinin getməsi). Yenisi BİR dəfə göstərilir. Köhnəsi dərhal işləməyi dayandırır: hadisələr yeni imza ilə gəlir və siz açarı öz tərəfinizdə dəyişənə qədər onları rədd edəcəksiniz.

Cavab nümunəsi
{ "id": "…", "secret": "whsec_…" }
Kod nümunələri
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

Webhook-u sil. Sadəcə hadisələri və ya ünvanı dəyişmək üçün PATCH istifadə edin: yenidən yaratmaq yeni gizli açar verir.

Cavab nümunəsi
{ "deleted": true }
Kod nümunələri
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 çatdırılma jurnalı: status (pending/delivered/failed), kodlar, cəhdlər və dəqiq imzalanmış gövdə, imzanın sazlanması üçün əlverişlidir.

Cavab nümunəsi
{ "deliveries": [ { "id": 42, "event": "mail.received", "status": "failed", "httpCode": 401, "attempts": 7, "payload": "{…}" } ] }
Kod nümunələri
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

Konkret çatdırılmanı indi yenidən göndər (məsələn, imza yoxlayıcısını düzəltdikdən sonra).

Cavab nümunəsi
{ "status": "delivered", "httpCode": 200 }
Kod nümunələri
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 botları

POST/v1/telegram/mybotstelegram:bots

Mövcud botu tokeni ilə (BotFather-dan) platformaya qoş.

Sorğu gövdəsi
{ "token": "123456:ABC-…", "note": "бот проекта" }
Cavab nümunəsi
{ "id": "…", "botId": 123456, "username": "MyBot", "name": "My Bot" }
Kod nümunələri
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

Kütləvi idxal: token siyahısı ilə (BotFather-dan) bir anda çoxlu bot qoş. tokens massiv və ya mətndir (hər sətirdə bir token).

Sorğu gövdəsi
{ "tokens": ["123456:AAA…", "234567:BBB…"] }
Cavab nümunəsi
{ "total": 2, "added": 2, "failed": 0, "results": [ { "ok": true, "username": "MyBot", "botId": 123456 } ] }
Kod nümunələri
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

Layihənin botlarının siyahısı.

Cavab nümunəsi
{ "bots": [ { "id": "…", "username": "MyBot", "webhookSet": true } ] }
Kod nümunələri
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

Botun canlı statusu (getMe + webhook + əmrlər).

Cavab nümunəsi
{ "username": "MyBot", "me": {...}, "webhook": {...}, "commands": [...] }
Kod nümunələri
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

Botun update-lərini serverinizə yönləndir (SPINE onları Telegram-dan qəbul edib sizə ötürür).

Sorğu gövdəsi
{ "forwardUrl": "https://ваш-сервер/tg" }
Cavab nümunəsi
{ "hookUrl": "https://api.spine.maxsystems.az/v1/tg/hook/123456", "forwardUrl": "…" }
Kod nümunələri
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

Layihəyə əlçatan hazır fabrik presetləri (ortaq + öz): əmrlər, webhook, profil.

Cavab nümunəsi
{ "presets": [ { "id": "…", "name": "Поддержка", "forwardUrl": "https://…" } ] }
Kod nümunələri
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

Fabrik: yeni bot yaratmaq üçün dip-link. İstifadəçi Telegram-da təsdiqləyir və bot layihəyə özü qoşulur. presetId ilə bot dərhal hazır şəkildə qurulur (əmrlər + webhook + profil).

Sorğu gövdəsi
{ "suggestedUsername": "myproject_bot", "presetId": "…(необязательно)" }
Cavab nümunəsi
{ "link": "https://t.me/newbot/spinemsbot/myproject_bot" }
Kod nümunələri
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);

Brend

GET/v1/brandbrand:read

Layihənizin brend dəsti: ad, şüar, rəng palitrası, şriftlər, keçidlər və loqolar/fayllar (birbaşa URL-lərlə). Brend üslubunu məktublara, saytlara və tətbiqlərə avtomatik tətbiq etmək üçün əlverişlidir.

Cavab nümunəsi
{
  "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/…" }]
}
Kod nümunələri
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: ключ видит только СВОЙ проект.

Cavab nümunəsi
{
  "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 дн." }]
}
Kod nümunələri
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.

Parametrlər
limitсколько событий вернуть, до 500 (по умолчанию 100)
Cavab nümunəsi
{ "events": [ { "ts": "2026-07-22T18:40:00Z", "action": "cert.issue", "target": "*.acme.az", "actor": "apikey:spine_sk_ab12cd", "source": "api" } ] }
Kod nümunələri
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, ключ не нужен).

Cavab nümunəsi
{ "tenant": "acme", "meta": { "en": { "title": "Acme", "description": "…" } }, "og": { "siteName": "Acme" }, "analytics": { "ga4": "G-XXXX" }, "robots": { "disallow": ["/admin"] } }
Kod nümunələri
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 вашего сайта сюда, правим в консоли, у вас применяется без редеплоя.

Cavab nümunəsi
User-agent: *
Disallow: /admin

Sitemap: https://acme.az/sitemap.xml
Kod nümunələri
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 сюда.

Cavab nümunəsi
<?xml version="1.0" encoding="UTF-8"?><urlset>…</urlset>
Kod nümunələri
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).

Parametrlər
pathпуть страницы для canonical и og:url (например /events/123)
langязык meta-тегов (ключ из секции meta, например ru/en/az)
Cavab nümunəsi
<title>Acme</title>
<meta name="description" content="…">
<meta property="og:image" content="…/og.png">
<script>…GA4…</script>
Kod nümunələri
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 автоматически, отдельно нужна, если хотите свою подпись на превью страницы.

Parametrlər
titleзаголовок на картинке (по умолчанию название бренда)
subtitleподзаголовок (необязательно)
Cavab nümunəsi
(image/png, картинка 1200×630)
Kod nümunələri
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 у проекта обновляется.

Cavab nümunəsi
(image/png, иконка)
Kod nümunələri
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).

Parametrlər
envокружение: prod|staging|dev|любое (по умолчанию prod)
Cavab nümunəsi
{ "env": "prod", "secrets": { "DB_PASSWORD": "…", "STRIPE_KEY": "…" } }
Kod nümunələri
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"). Проект пишет его к себе одним запросом.

Parametrlər
envокружение (по умолчанию prod)
Cavab nümunəsi
DB_PASSWORD="…"
STRIPE_KEY="…"
Kod nümunələri
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).

Sorğu gövdəsi
{ "env": "prod", "name": "db/password" }
Cavab nümunəsi
{ "name": "db/password", "env": "prod", "value": "…" }
Kod nümunələri
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

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

Sorğu gövdəsi
{ "env": "prod", "name": "stripe/key", "value": "sk_live_…", "description": "ключ Stripe" }
Cavab nümunəsi
{ "name": "stripe/key", "env": "prod", "version": 2 }
Kod nümunələri
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. Значение возвращается один раз.

Sorğu gövdəsi
{ "env": "prod", "name": "jwt/secret", "kind": "base64", "length": 48 }
Cavab nümunəsi
{ "name": "jwt/secret", "env": "prod", "version": 1, "value": "…сгенерировано…" }
Kod nümunələri
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.

Sorğu gövdəsi
{ "env": "prod", "name": "db/password", "ttl": 120 }
Cavab nümunəsi
{ "token": "spine_lease_…", "url": "https://api.spine.maxsystems.az/v1/secrets/lease/spine_lease_…", "expiresAt": "…", "singleUse": true }
Kod nümunələri
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 на всю).

Cavab nümunəsi
{ "env": "prod", "name": "db/password", "value": "…" }
Kod nümunələri
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.

Parametrlər
channelsms (по умолчанию) | telegram
toномер телефона в международном виде (994501234567) или ваш идентификатор пользователя для Telegram
purposeзачем код: login, payment, reset. Попадает в журнал (необязательно)
ttlSecondsсрок жизни кода, 60..3600 (по умолчанию из настроек проекта)
codeLengthдлина кода, 4..8 (по умолчанию 6)
Sorğu gövdəsi
{ "channel": "sms", "to": "994501234567", "purpose": "login" }
Cavab nümunəsi
{ "verificationId": "3f2a…", "channel": "sms", "to": "994501234567", "expiresAt": "…", "resendAfter": "…", "attemptsLeft": 5, "delivery": "sent", "sandbox": false }
Kod nümunələri
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.

Parametrlər
verificationIdидентификатор из ответа на send (рекомендуется)
toполучатель, если verificationId не сохраняли
channelнужен вместе с to, чтобы одинаково нормализовать номер
codeкод от пользователя (обязателен)
Sorğu gövdəsi
{ "verificationId": "3f2a…", "code": "123456" }
Cavab nümunəsi
{ "valid": true, "verificationId": "3f2a…", "channel": "sms", "to": "994501234567", "purpose": "login" }
Kod nümunələri
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.

Sorğu gövdəsi
{ "verificationId": "3f2a…" }
Cavab nümunəsi
{ "verificationId": "3f2a…", "expiresAt": "…", "resendAfter": "…", "delivery": "sent" }
Kod nümunələri
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

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

Cavab nümunəsi
{ "verificationId": "3f2a…", "verified": false, "attemptsLeft": 4, "delivery": "sent", "sandbox": false }
Kod nümunələri
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.

Cavab nümunəsi
{ "delivery": "delivered", "note": "доставлено" }
Kod nümunələri
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

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

Cavab nümunəsi
{ "project": "ваш-проект", "channels": ["sms","telegram","totp"], "codeLength": 6, "ttlSeconds": 300, "limits": { "perIdentifierHour": 5, "perIdentifierDay": 15, "perProjectDay": 500, "perIpHour": 20 } }
Kod nümunələri
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.

Sorğu gövdəsi
{ "identifier": "user-42" }
Cavab nümunəsi
{ "url": "https://t.me/вашбот?start=otp_…", "bot": "вашбот", "expiresAt": "…", "linked": false }
Kod nümunələri
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 выключен: иначе человек запишет секрет с ошибкой и потеряет доступ, думая, что всё настроил.

Sorğu gövdəsi
{ "identifier": "user-42", "issuer": "Ваш проект" }
Cavab nümunəsi
{ "secret": "JBSWY3DP…", "otpauthUrl": "otpauth://totp/…", "enabled": false }
Kod nümunələri
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.

Sorğu gövdəsi
{ "identifier": "user-42", "code": "123456" }
Cavab nümunəsi
{ "enabled": true }
Kod nümunələri
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), резервный код после использования вычёркивается.

Sorğu gövdəsi
{ "identifier": "user-42", "code": "123456" }
Cavab nümunəsi
{ "valid": true }
Kod nümunələri
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 штук). Показываются один раз, у нас хранятся только отпечатки. Прежний список при этом перестаёт работать.

Sorğu gövdəsi
{ "identifier": "user-42" }
Cavab nümunəsi
{ "codes": ["A1B2C3D4E5", "…"] }
Kod nümunələri
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): подключён, включён, сколько резервных кодов осталось.

Cavab nümunəsi
{ "enrolled": true, "enabled": true, "backupLeft": 8, "backupUsed": 2 }
Kod nümunələri
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 у пользователя и удалить его секрет.

Cavab nümunəsi
{ "removed": true }
Kod nümunələri
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);
WEBHOOKİmzanın yoxlanması və çatdırılma

Hər çatdırılma X-Spine-Event (hadisə növü) və X-Spine-Signature: sha256=<hex> başlıqları olan POST-dur. Gövdə: {"event":"…","ts":"…","data":{…}}.

İmza = sorğunun xam gövdəsindən HMAC-SHA256 (baytlar gəldiyi kimi, JSON parse edilməzdən əvvəl). Açar whsec_… sirridir, olduğu kimi, tam sətir: onu base64-dən açmayın və prefiksi kəsməyin (bu Svix deyil). Nəticə hex-dir, sha256= prefiksi ilə. Sabit vaxtda müqayisə edin.

Yoxlayıcı (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);
}
Çatdırılma və təkrarlar

Bir dərhal cəhd; uğursuzluqda eksponensial backoff ilə durable təkrarlar (≈ 1 dəq, 5 dəq, 30 dəq, 2 saat, 6 saat, 12 saat sonra, ~21 saat ərzində 7 cəhdə qədər); növbə yenidən başlamağa tab gətirir. Uğur = sizin 2xx cavabınız. Çatdırılma jurnalı: GET /v1/webhooks/{id}/deliveries (status, kodlar və dəqiq imzalanmış gövdə görünür); konkret olanı yenidən göndər: POST /v1/webhooks/{id}/deliveries/{deliveryId}/redeliver. Tez cavab verin, ağır emalı fona atın (çatdırılma ən azı bir dəfədir, ona görə handler-i idempotent edin).

mail.received hadisəsi: data-nın içində nə var

Gələn məktub üçün onun metadatasını (message), thread-in RFC başlıqlarını (rfc) və fırıldaq siqnallarını (security) qaytarırıq:

"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" }
  }
}

Qovluqlar. mail.received yalnız QƏBUL EDİLMİŞ məktublar üçün gəlir: «Gələnlər», «Spam», «Arxiv» və işçinin öz qovluqları. «Qaralamalar» və «Səbət» üzrə heç bir abunə ilə hadisə yoxdur (poçt proqramının hər 25 saniyədə saxladığı qaralama məktub deyil, səbətə isə məktub artıq bildirildikdən sonra düşür). İşçinin qutudan göndərdiyi məktub ayrıca mail.sent_folder hadisəsi ilə gəlir, mailboxRole: "sent" ilə: bütün yazışmanı aparırsınızsa abunə olun, yalnız gələnlər lazımdırsa abunə olmayın.

delivery. EYNİ layihənin qutuları arasındakı məktubda nə SPF, nə DKIM, nə də DMARC var: məktub heç yerə getməyib, yoxlanacaq bir şey olmayıb və security.auth boş olacaq. Belə məktubu domeninizin kənardan saxtalaşdırılmasından ayırmaq üçün delivery.internal sahəsinə baxın. Əlamət göndərənin ünvanından deyil, məktubun YOLUNDAN qurulur: kənardan gələn hər məktub bizim qovşağımızdan qəbul sətri alır və göndərən onu saxtalaşdıra bilmir. imported məktubun poçtla qəbul edilmədiyini, əvvəlki hostinqdən köçürmə ilə gətirildiyini bildirir.

İdentifikatorlar: message.id qısa per-mailbox id-dir (eyni məktubu API ilə oxumaq üçün), qlobal DEYİL. Qlobal unikal açar kimi (idempotentlik, thread) rfc.messageId istifadə edin; cavabları thread-ə rfc.inReplyTo/rfc.references üzrə bağlayın.

security: score spam-filtrin qiymətidir (rspamd üslubu; çox = daha şübhəli), isSpam həddi keçib-keçmədiyidir; spf/dkim/dmarc göndərənin həqiqiliyidir (pass/fail/none…). Əgər məktubda bu başlıqlar yoxdursa, sahə null ola bilər.

Göndərmə hadisələri: data-nın içində nə var

Məktubun həyat yolu: mail.sent (reley qəbul etdi) → mail.delivered (alıcının serveri qəbul etdi) və ya mail.bounced / mail.complained / mail.delayed. Məktubun açarı messageId-dir, göndərmənin qaytardığı həmin id.

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

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

Maraq hadisələri (track: true lazımdır)

Açılış və klikləri platforma özü sayır: HTML-ə şəffaf piksel əlavə olunur, linklər bizim yönləndirməyə dəyişdirilir (t.sizin-domen açıqdırsa, treking domeni sizinkidir). automated: true sahəsi açılışı insanın deyil, proksi və ya poçt provayderinin skanerinin etdiyini bildirir, belələri adətən sayılmır.

"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 alıcı abunəlikdən çıxdıqda gəlir, o cümlədən Gmail və ya Yahoo-nun öz düyməsi ilə (RFC 8058 üzrə bir kliklə çıxma, unsubscribe: true sahəsi ilə açılır). Ünvan dərhal blok siyahısına düşür.

SPINE API · baza ünvanı https://api.spine.maxsystems.az · maşın təsviri /v1/openapi.json