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).
| Scope | Nəyə icazə verir |
|---|---|
mail:send | Mə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:suppressions | Bloklanmış ünvanlar: baxmaq, əlavə etmək, blokdan çıxarmaq |
mail:mailboxes | Qutuların yaradılması və idarəsi |
mail:domains | Domenlərin qoşulması və statusu |
mail:templates | Шаблоны писем проекта: смотреть, править, откатывать, слать тест |
notify:send | Уведомления одним запросом: почта, Telegram, вебхук, с запасными каналами |
mail:subscriptions | Списки рассылок и подписки: подписать, отписать, посмотреть согласия |
protection:verify | Проверка ответа Turnstile для форм вашего сайта |
domains:read | Сроки регистрации своих доменов: когда истекает, у какого регистратора |
mail:migrate | Перенос почты с другого хостинга по IMAP (задания и прогресс) |
cert:issue | Sertifikatların sifarişi və yenidən buraxılışı |
cert:read | Статус и скачивание сертификатов, SPKI-пины |
cert:acme | ACME-доступ: пары EAB для certbot, acme.sh, cert-manager, файрволов |
cert:delegate | CNAME-делегации для доменов, чей DNS не у нас |
pki:issue | Выпуск из приватного CA: длинные серты и клиентские mTLS |
pki:read | Приватный CA: корневые сертификаты, цепочки, CRL |
dns:read | Zonanın DNS yazılarının oxunması |
dns:records | Zonanın DNS yazılarının idarəsi |
webhooks | Webhook-lara abunə (gələn məktublar, çatdırılma hadisələri) |
telegram:bots | Layihə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:read | SEO / site-config проекта (raw-конфиг; артефакты отдаются публично) |
secrets:read | Читать секреты проекта из сейфа (карта/.env/по имени) |
secrets:write | Записывать/генерировать/удалять секреты проекта в сейфе |
security:read | Отчёт безопасности своих доменов: оценка и открытые находки |
otp:send | Верификация: выдать и доставить одноразовый код (SMS, Telegram) |
otp:verify | Верификация: проверить код, статус попытки и доставки |
otp:totp | Верификация: приложение-аутентификатор и резервные коды пользователей проекта |
Poçt
/v1/mail/sendmail:sendMəktub göndər. Göndərənin domeni SPINE poçtuna qoşulmalı və layihəyə aid olmalıdır.
from | göndərənin ünvanı (məcburidir) |
to | alan (məcburidir) |
subject | mövzu (və ya template) |
text / html | məktubun mətni |
category | statistika üçün etiket (məcburi deyil) |
cc / bcc / replyTo | ünvan siyahıları (istəyə görə) |
attachments | qoş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ə) |
stream | transactional (по умолчанию) | marketing. Транзакционный поток не блокируется отписками и жалобами: платёжный документ получатель должен получить, даже если отказался от новостей. Жёсткий отказ (несуществующий ящик) действует в обоих потоках |
scope | владелец отношения с получателем (например slug мерчанта). Жалобы и отписки ложатся именно на него, а не на весь проект. Пусто = весь проект, как раньше |
campaign | kampaniya etiketi: /v1/mail/stats-dakı qıf ona görə hesablanır |
track | true açılış və klikləri saymaq deməkdir (mail.opened / mail.clicked hadisələri) |
unsubscribe | true bir kliklə abunəlikdən çıxma əlavə edir (RFC 8058, Gmail və Yahoo tələbi) |
sendAt | ISO 8601 tarixi, təxirə salınmış göndərmə (90 günə qədər, DELETE ilə ləğv) |
idempotencyKey | dublikatdan qoruma (məcburi deyil) |
{
"from": "noreply@ваш-домен",
"to": "user@example.com",
"subject": "Привет",
"text": "Тело письма",
"html": "<p>Тело письма со <a href=\"https://example.com\">ссылкой</a></p>",
"campaign": "welcome-july",
"track": true,
"unsubscribe": true
}{ "id": "01K…", "status": "sent", "relay": "ses", "relayMessageId": "…" }
curl -X POST "https://api.spine.maxsystems.az/v1/mail/send" \
-H "Authorization: Bearer spine_sk_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);
/v1/mail/send-batchmail:sendPaketlə 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.
messages | məktub siyahısı: [{ to, vars, subject?, html?, attachments? }] (mütləqdir) |
from / subject / html / text / template | paketdəki bütün məktublar üçün ümumi sahələr |
campaign / track / unsubscribe / sendAt | tək göndərmə kimi, bütün paketə tətbiq olunur |
{
"from": "noreply@ваш-домен",
"subject": "Привет, {{name}}",
"html": "<p>Здравствуйте, {{name}}</p>",
"campaign": "july-news",
"track": true,
"messages": [
{ "to": "a@example.com", "vars": { "name": "Анна" } },
{ "to": "b@example.com", "vars": { "name": "Борис" } }
]
}{ "total": 2, "counts": { "sent": 2 }, "results": [ { "to": "a@example.com", "id": "01K…", "status": "sent" } ] }
curl -X POST "https://api.spine.maxsystems.az/v1/mail/send-batch" \
-H "Authorization: Bearer spine_sk_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);
/v1/mailboxes/{id}/messagesmail:readПисьма ящика, новые сверху, с постраничным обходом. Возвращает total и hasMore, поэтому обойти весь ящик можно без догадок.
limit | сколько вернуть, 1..500 (по умолчанию 50) |
offset | сколько пропустить: обход страницами offset += limit |
since | письма СТРОГО позже этого момента, ISO 8601. ⚠️ Письмо той же секунды не вернётся |
before | письма раньше этого момента, ISO 8601 |
hasAttachment | true, только письма с вложениями, false, только без |
with | переписка с адресом: письма ОТ него или К нему |
from / to | точечный фильтр по отправителю или получателю |
{ "messages": [ { "id": "…", "subject": "…", "receivedAt": "2026-08-10T09:00:00Z", "hasAttachment": true, "rfcMessageId": "20260810020431.ade65@example.com" } ], "total": 1843, "offset": 0, "limit": 50, "hasMore": true }
curl -X GET "https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages" \
-H "Authorization: Bearer spine_sk_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);
/v1/mailboxes/{id}/messages/by-message-idmail:readПисьмо по его Message-ID: нужно, чтобы сопоставить письмо с уже сохранённым у себя, не перебирая списки. Значение принимается и в угловых скобках, и без них. Если письмо не найдено, в тексте ошибки написано, сколько писем просмотрено и до какой даты дошёл поиск: «не нашли в просмотренном» и «не существует» это разные ответы.
value | Message-ID письма (обязательно) |
since / before | сузить период поиска, ISO 8601: ускоряет ответ на больших ящиках |
{ "message": { "id": "…", "subject": "…", "rfcMessageId": "…" }, "lookup": { "source": "index", "scanned": 0 } }
curl -X GET "https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/by-message-id" \
-H "Authorization: Bearer spine_sk_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);
/v1/mail/messagesmail:sendGöndərdiyiniz məktubların statuslarla siyahısı.
limit | nə qədər qaytarılsın (200-ə qədər, susmaya görə 50) |
status | filtr: sent | delivered | bounced | complained | delayed | scheduled | suppressed | failed |
campaign | kampaniya etiketinə görə filtr |
{ "messages": [ { "id": "01K…", "toAddr": "user@example.com", "status": "delivered", "opens": 1, "clicks": 0 } ] }
curl -X GET "https://api.spine.maxsystems.az/v1/mail/messages" \ -H "Authorization: Bearer spine_sk_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);
/v1/mail/messages/{id}mail:sendMəktubun statusu və bütün tarixi: reley qəbul etdi, çatdırıldı, açıldı, linkə klik.
{ "message": { "id": "01K…", "status": "delivered", "relay": "ses", "opens": 1, "clicks": 1 },
"events": [ { "event": "sent" }, { "event": "delivered" }, { "event": "opened" }, { "event": "clicked" } ],
"links": [ { "idx": 1, "url": "https://example.com", "clicks": 1 } ] }
curl -X GET "https://api.spine.maxsystems.az/v1/mail/messages/{id}" \
-H "Authorization: Bearer spine_sk_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);
/v1/mail/messages/{id}mail:sendHələ göndərilməmiş təxirə salınmış məktubu ləğv et.
{ "id": "01K…", "status": "canceled" }
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);
/v1/mail/statsmail:sendDö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.
days | dövr günlərlə (1..365, susmaya görə 30) |
campaign | yalnız bir kampaniya üzrə |
{ "days": 30, "total": 120,
"funnel": { "accepted": 118, "delivered": 115, "opened": 64, "clicked": 12, "bounced": 2, "complained": 0 },
"rates": { "bounce": 1.69, "complaint": 0, "delivery": 97.46, "open": 55.65, "click": 10.43 } }
curl -X GET "https://api.spine.maxsystems.az/v1/mail/stats" \ -H "Authorization: Bearer spine_sk_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);
/v1/mail/validatemail:sendПроверить адрес ДО отправки: синтаксис, есть ли у домена почтовые серверы, одноразовая ли почта, нет ли опечатки в известном домене (gmial вместо gmail). Заведомо негодный адрес получает verdict invalid, сомнительный warn с объяснением. Помогает не набирать жёсткие отказы, которые бьют по репутации домена.
{ "emails": ["user@gmial.com", "someone@example.com"] }{ "total": 2, "counts": { "warn": 1, "ok": 1 },
"results": [ { "email": "user@gmial.com", "verdict": "warn", "suggestion": "user@gmail.com",
"reasons": ["похоже на опечатку в домене"], "hasMx": true, "disposable": false } ] }
curl -X POST "https://api.spine.maxsystems.az/v1/mail/validate" \
-H "Authorization: Bearer spine_sk_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);
/v1/mail/checkmail:sendЧек-лист письма перед отправкой: нет текстовой части, картинка вместо текста, крик в теме, короткие ссылки, скрытый текст, массовое письмо без отписки. Это наш чек-лист причин попасть в спам, а не вердикт спам-фильтра.
{ "subject": "СРОЧНО!!", "html": "<img src=…>", "category": "marketing" }{ "score": 8, "verdict": "bad",
"issues": [ { "weight": 3, "text": "нет текстовой части: письмо только в HTML" } ] }
curl -X POST "https://api.spine.maxsystems.az/v1/mail/check" \
-H "Authorization: Bearer spine_sk_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);
/v1/mailboxes/{id}/messages/{msgId}/rawmail:readПисьмо целиком в исходном виде (RFC 5322, message/rfc822). Нужно, когда своего разбора хочется больше, чем нашего: вы получаете ровно то, что лежит на сервере. ⚠️ Политика проекта и антивирус применяются и здесь: письмо с запрещённым или заражённым вложением не отдаётся целиком, иначе через сырой файл проверку можно было бы обойти. Выдача пишется в журнал проекта: это доступ ко всей переписке письма.
(поток message/rfc822, имя файла <id письма>.eml)
curl -X GET "https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/{msgId}/raw" \
-H "Authorization: Bearer spine_sk_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);
/v1/mail/suppressionsmail:suppressionsLayihənin bloklanmış ünvanları: sərt rəddlər, spam şikayətləri, abunəlikdən çıxmalar. Onlara məktub göndərilmir.
email | bir ünvanı yoxla |
scope | блокировки этого владельца плюс общепроектные (они действуют на всех) |
stream | transactional, показать только то, что блокирует платёжные письма |
limit | nə qədər qaytarılsın (500-ə qədər) |
{ "suppressions": [ { "email": "user@example.com", "reason": "hard_bounce", "scope": null, "ts": "2026-07-25T10:00:00Z" } ] }
curl -X GET "https://api.spine.maxsystems.az/v1/mail/suppressions" \ -H "Authorization: Bearer spine_sk_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);
/v1/mail/suppressionsmail:suppressionsÜnvanı əl ilə blokla (məsələn, alıcının xahişi ilə).
email | ünvan (mütləqdir) |
reason | hard_bounce | complaint | manual | unsubscribe (susmaya görə manual) |
scope | чья это блокировка: пусто = весь проект |
{ "email": "user@example.com", "reason": "manual", "scope": "мерчант" }{ "email": "user@example.com", "reason": "manual", "tenant": "ваш-проект", "scope": "мерчант" }
curl -X POST "https://api.spine.maxsystems.az/v1/mail/suppressions" \
-H "Authorization: Bearer spine_sk_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);
/v1/mail/suppressions/{email}mail:suppressionsСнять блокировку с адреса (например, человек починил свой ящик и снова хочет письма). С параметром scope снимается блокировка ровно этого владельца, без него, все блокировки адреса в проекте.
scope | снять блокировку только этого владельца |
{ "email": "user@example.com", "removed": 1, "scope": "мерчант" }
curl -X DELETE "https://api.spine.maxsystems.az/v1/mail/suppressions/{email}" \
-H "Authorization: Bearer spine_sk_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);
/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.
{ "project": "ваш-проект",
"attachments": { "maxBytesOutbound": 15728640, "maxBytesInbound": 26214400,
"blockedExtensions": ["exe", "js", "bat", "…"], "antivirus": "отклоняем заражённое" },
"sending": { "maxRecipientsPerBatch": 200, "dailyCap": null } }
curl -X GET "https://api.spine.maxsystems.az/v1/mail/policy" \ -H "Authorization: Bearer spine_sk_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);
Подписки
/v1/mail/listsmail:subscriptionsСписки рассылок проекта («счета», «новости»). Отписка идёт от списка, а не от всей почты сразу: человек, отказавшийся от новостей, продолжает получать счета.
{ "lists": [ { "slug": "news", "name": "Новости", "doubleOptIn": true,
"confirmed": 128, "pending": 4, "unsubscribed": 11 } ] }
curl -X GET "https://api.spine.maxsystems.az/v1/mail/lists" \ -H "Authorization: Bearer spine_sk_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);
/v1/mail/listsmail:subscriptionsСоздать список рассылки. doubleOptIn (по умолчанию true) значит, что адрес считается подписанным только после подтверждения по ссылке из письма: так требуют Gmail и Yahoo, и так чужой адрес нельзя подписать за человека.
{ "slug": "news", "name": "Новости", "description": "раз в месяц", "doubleOptIn": true }{ "id": "…", "slug": "news", "name": "Новости", "doubleOptIn": true }
curl -X POST "https://api.spine.maxsystems.az/v1/mail/lists" \
-H "Authorization: Bearer spine_sk_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);
/v1/mail/lists/{slug}mail:subscriptionsУдалить список вместе со всеми подписками на него. Уже отправленные письма это не затрагивает.
{ "deleted": "news" }
curl -X DELETE "https://api.spine.maxsystems.az/v1/mail/lists/{slug}" \
-H "Authorization: Bearer spine_sk_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);
/v1/mail/subscriptionsmail:subscriptionsПодписать адрес на список. При двойном подтверждении платформа сразу отправляет письмо со ссылкой и возвращает status pending: до подтверждения письма списка на этот адрес НЕ уходят. Если согласие уже получено у вас (галочка при заказе), можно передать confirmed: true.
{ "email": "user@example.com", "list": "news", "source": "форма на сайте" }{ "email": "user@example.com", "list": "news", "status": "pending", "confirmationSent": true }
curl -X POST "https://api.spine.maxsystems.az/v1/mail/subscriptions" \
-H "Authorization: Bearer spine_sk_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);
/v1/mail/subscriptionsmail:subscriptionsСлаг списка во всех методах называется list; поле listSlug оставлено синонимом ради совместимости. Согласия адреса по всем спискам проекта и готовая ссылка на его центр подписок. Ссылку можно положить в подвал письма: на ней человек сам управляет подписками.
email | адрес, который проверяем |
{ "email": "user@example.com",
"subscriptions": [ { "list": "news", "status": "confirmed", "confirmedAt": "2026-08-01T10:00:00Z" } ],
"prefsUrl": "https://t.example.com/s/…" }
curl -X GET "https://api.spine.maxsystems.az/v1/mail/subscriptions" \ -H "Authorization: Bearer spine_sk_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);
/v1/mail/subscriptionsmail:subscriptionsОтписать адрес от списка. Без параметра list отпишет от всех списков проекта.
email | адрес |
list | слаг списка (необязательно) |
{ "email": "user@example.com", "list": "news", "unsubscribed": 1 }
curl -X DELETE "https://api.spine.maxsystems.az/v1/mail/subscriptions" \ -H "Authorization: Bearer spine_sk_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);
Шаблоны
/v1/mail/templatesmail:templatesШаблоны писем проекта. Текст письма перестаёт жить в коде проекта: правится здесь, версии сохраняются, откат делается одним запросом.
{ "templates": [ { "slug": "invoice", "name": "Счёт", "version": 3, "variables": ["name","number"] } ] }
curl -X GET "https://api.spine.maxsystems.az/v1/mail/templates" \ -H "Authorization: Bearer spine_sk_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);
/v1/mail/templatesmail:templatesСоздать шаблон. Переменные вида {{name}} платформа находит сама и показывает списком.
{ "slug": "invoice", "name": "Счёт", "subject": "Счёт {{number}}",
"html": "<p>Здравствуйте, {{name}}. Счёт {{number}} на {{sum}}.</p>" }{ "slug": "invoice", "version": 1, "variables": ["name","number","sum"] }
curl -X POST "https://api.spine.maxsystems.az/v1/mail/templates" \
-H "Authorization: Bearer spine_sk_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);
/v1/mail/templates/{slug}mail:templatesИзменить шаблон. Прежняя версия сохраняется в истории, номер версии растёт.
{ "subject": "Счёт {{number}} к оплате" }{ "slug": "invoice", "version": 2 }
curl -X PUT "https://api.spine.maxsystems.az/v1/mail/templates/{slug}" \
-H "Authorization: Bearer spine_sk_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);
/v1/mail/templates/{slug}/rollbackmail:templatesВернуть содержимое прошлой версии. История не переписывается: откат создаёт новую версию с прежним текстом.
{ "version": 1 }{ "slug": "invoice", "version": 3, "rolledBackFrom": 1 }
curl -X POST "https://api.spine.maxsystems.az/v1/mail/templates/{slug}/rollback" \
-H "Authorization: Bearer spine_sk_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);
/v1/mail/templates/{slug}/previewmail:templatesПредпросмотр с подстановкой переменных плюс чек-лист письма.
{ "vars": { "name": "Михаил", "number": "SPINE-2026-0007", "sum": "59 AZN" } }{ "subject": "Счёт SPINE-2026-0007", "html": "…", "check": { "score": 0, "verdict": "ok" } }
curl -X POST "https://api.spine.maxsystems.az/v1/mail/templates/{slug}/preview" \
-H "Authorization: Bearer spine_sk_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);
/v1/mail/templates/{slug}/testmail:templatesТестовая отправка шаблона на указанный адрес. Идёт тем же путём, что и настоящее письмо, с метками template-test.
{ "to": "me@example.com", "vars": { "name": "Михаил" } }{ "id": "01K…", "status": "sent" }
curl -X POST "https://api.spine.maxsystems.az/v1/mail/templates/{slug}/test" \
-H "Authorization: Bearer spine_sk_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);
Уведомления
/v1/notifynotify:sendУведомление одним запросом с запасными каналами: пробуем по очереди, пока один не сработает. Порядок и переход держим мы, вам не нужно городить «не дошло в Telegram, шлю письмо» у себя. В ответе видно, какой канал сработал и что пробовали до него.
{ "title": "Платёж прошёл", "body": "Счёт SPINE-2026-0007 оплачен",
"channels": [ { "channel": "telegram", "to": "12345678" }, { "channel": "email", "to": "user@example.com" } ] }{ "channel": "email", "to": "user@example.com", "status": "sent",
"tried": [ { "channel": "telegram", "error": "Telegram HTTP 400" } ] }
curl -X POST "https://api.spine.maxsystems.az/v1/notify" \
-H "Authorization: Bearer spine_sk_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
/v1/certs/issuecert:issuePublik 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.
domains | domenlərin siyahısı, wildcard *.domain olar (məcburidir) |
csr | öz CSR-iniz, onda şəxsi açar sizdə qalır (məcburi deyil) |
autoRenew | avto-yeniləmə, defolt olaraq true |
{ "domains": ["app.ваш-домен", "*.ваш-домен"], "autoRenew": true }{ "id": "…", "subject": "app.ваш-домен", "sans": [...], "status": "pending",
"pinWarning": ["Банк X (Java-приложение)"] }
curl -X POST "https://api.spine.maxsystems.az/v1/certs/issue" \
-H "Authorization: Bearer spine_sk_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);
/v1/certs/{id}/statuscert:readSertifikatın statusu (pending → active | failed).
{ "status": "active", "notAfter": "…", "daysLeft": 89 }
curl -X GET "https://api.spine.maxsystems.az/v1/certs/{id}/status" \
-H "Authorization: Bearer spine_sk_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);
/v1/certs/{id}/downloadcert:readSertifikatı yüklə (fullchain + şəxsi açar; CSR ilə buraxılan sertifikatlar üçün privkey=null).
{ "fullchain": "-----BEGIN CERTIFICATE-----…", "privkey": "…" }
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);
/v1/certs/{id}/pinscert:readSertifikatı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.
{ "spkiPin": "…", "backupSpkiPin": "…", "keepKey": true,
"formats": { "curl": "curl --pinnedpubkey \"sha256//…\" https://api.example.com/" } }
curl -X GET "https://api.spine.maxsystems.az/v1/certs/{id}/pins" \
-H "Authorization: Bearer spine_sk_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);
/v1/certs/{id}/pins/registercert:readPinin 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.
{ "party": "Партнёр X", "system": "Java-приложение", "contact": "dev@partner.com" }{ "id": "…", "registered": true, "pin": "…" }
curl -X POST "https://api.spine.maxsystems.az/v1/certs/{id}/pins/register" \
-H "Authorization: Bearer spine_sk_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
/v1/acme/eabcert:acmeBizim 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.
label | özünüz üçün etiket (məcburi deyil) |
allowedDomains | domenləri məhdudlaşdır (boş = mövcud olan hər biri) |
issuer | letsencrypt (susmaya görə) və ya private |
policy | auto (yoxlamanı özümüz keçirik) və ya verify (müştəri keçir) |
{ "label": "prod-cluster", "allowedDomains": ["example.com"] }{ "kid": "…", "hmacKey": "…",
"directoryUrl": "https://api.spine.maxsystems.az/acme/directory",
"snippets": { "certbot": "certbot register --server … --eab-kid … --eab-hmac-key …" } }
curl -X POST "https://api.spine.maxsystems.az/v1/acme/eab" \
-H "Authorization: Bearer spine_sk_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);
/v1/acme/delegationscert:delegateCNAME 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.
{ "domain": "client-domain.com" }{ "domain": "client-domain.com", "record": { "type": "CNAME",
"name": "_acme-challenge.client-domain.com", "value": "<метка>.acme.spine.maxsystems.az", "ttl": 300 } }
curl -X POST "https://api.spine.maxsystems.az/v1/acme/delegations" \
-H "Authorization: Bearer spine_sk_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);
/v1/acme/delegations/{id}/verifycert:delegateMüştərinin CNAME əlavə etdiyini yoxla. Yalnız DNS oxuyur, heç nə dəyişmir.
{ "ok": true, "expected": "…", "found": ["…"] }
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);
/v1/acme/delegationscert:delegateLayihənin deleqasiyalarının siyahısı və vəziyyəti.
{ "delegations": [{ "domain": "client-domain.com", "verified": true }] }
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);
/v1/pki/issuepki:issuePlatformanı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.
domains | server sertifikatı üçün domenlər |
commonName | mTLS müştəri sertifikatı üçün sahibin adı |
days | gün ilə müddət, susmaya görə 397, maksimum 3650 |
certType | server (susmaya görə) və ya client |
csr | öz CSR-iniz: şəxsi açar sizdə qalır (məcburi deyil) |
{ "domains": ["internal.example.com"], "days": 1095, "certType": "server" }{ "id": "…", "subject": "internal.example.com", "status": "pending", "lifetimeDays": 1095 }
curl -X POST "https://api.spine.maxsystems.az/v1/pki/issue" \
-H "Authorization: Bearer spine_sk_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);
/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.
{ "name": "SPINE Partner CA Root", "rootPem": "-----BEGIN CERTIFICATE-----…",
"notAfter": "2036-07-29T…", "crlUrl": "https://api.spine.maxsystems.az/v1/pki/…/crl.pem" }
curl -X GET "https://api.spine.maxsystems.az/v1/pki/root" \ -H "Authorization: Bearer spine_sk_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);
/v1/pki/crlpki:readŞəxsi CA-nın ləğv edilmiş sertifikatlar siyahısı (CRL), PEM formatında.
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);
/v1/pki/authoritiespki:readLayihə üçün əlçatan sertifikat mərkəzləri: adları, növü və etibarlılıq müddəti.
{ "cas": [{ "name": "SPINE Partner CA Issuing", "kind": "intermediate",
"notAfter": "2031-07-28T…", "isDefault": true }] }
curl -X GET "https://api.spine.maxsystems.az/v1/pki/authorities" \ -H "Authorization: Bearer spine_sk_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ı
/v1/mailboxesmail:mailboxesÖz domenində poçt qutusu yarat.
name | @ işarəsindən əvvəlki qutu adı (2-40, [a-z0-9._-]) |
domain | layihə domeni (məcburidir) |
password | qutu parolu (min. 8) |
description | təsvir (məcburi deyil) |
{ "name": "info", "domain": "ваш-домен", "password": "…" }{ "email": "info@ваш-домен", "id": "…" }
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);
/v1/mailboxesmail:mailboxesLayihə 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.
{ "mailboxes": [ { "id": "…", "email": "ivan@ваш-домен", "type": "personal", "usedBytes": 0 }, { "id": "…", "email": "team@ваш-домен", "type": "group", "membersCount": 4 } ] }
curl -X GET "https://api.spine.maxsystems.az/v1/mailboxes" \ -H "Authorization: Bearer spine_sk_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);
/v1/mailboxes/{id}mail:mailboxesPoçt qutusunu sil (yalnız öz domenində).
{ "deleted": true }
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);
/v1/mailboxes/{id}/accessmail:mailboxesКто из сотрудников работает с общим ящиком под своим логином. Уровни: read (только читать), write (читать и отвечать), full (плюс управление папками и удаление).
{ "access": [ { "accountId": "u", "email": "tamara@example.com", "level": "write", "folders": 5 } ] }
curl -X GET "https://api.spine.maxsystems.az/v1/mailboxes/{id}/access" \
-H "Authorization: Bearer spine_sk_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);
/v1/mailboxes/{id}/accessmail:mailboxesОткрыть сотруднику доступ к общему ящику. Отдельный пароль ему не нужен: ящик появится в его почтовой программе сам, в разделе «Shared Folders». Доступ выдаётся на все папки ящика; если позже появятся новые папки, вызвать повторно.
{ "mailbox": "tamara@example.com", "level": "write" }{ "ok": true, "mailbox": "tamara@example.com", "level": "write", "folders": 5 }
curl -X POST "https://api.spine.maxsystems.az/v1/mailboxes/{id}/access" \
-H "Authorization: Bearer spine_sk_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);
/v1/mailboxes/{id}/access/{granteeId}mail:mailboxesЗакрыть сотруднику доступ к общему ящику во всех папках. granteeId, это accountId из выдачи GET /v1/mailboxes/{id}/access.
{ "ok": true, "folders": 5 }
curl -X DELETE "https://api.spine.maxsystems.az/v1/mailboxes/{id}/access/{granteeId}" \
-H "Authorization: Bearer spine_sk_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);
/v1/mailboxes/{id}/messagesmail:readQutudakı 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.
limit | neçə məktub qaytarılsın, 100-ə qədər (defolt 20) |
with | kontaktın ünvanı: ondan və ya ona olan məktublar (qarşı tərəflə yazışma) |
from | göndərənə görə süzgəc (ünvanın alt sətri) |
to | alana görə süzgəc (ünvanın alt sətri) |
{ "messages": [ { "id": "eaaaaab", "messageId": ["<abc@bank.az>"], "subject": "…", "from": [{"email":"…"}], "receivedAt": "…", "preview": "…" } ] }
curl -X GET "https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages" \
-H "Authorization: Bearer spine_sk_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);
/v1/mailboxes/{id}/messages/{msgId}mail:readTam 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.
{ "id": "eaaaaab", "messageId": "<abc@bank.az>", "inReplyTo": ["<prev@you.az>"], "references": ["<root@…>"], "subject": "…", "from": […], "to": […], "receivedAt": "…", "text": "тело письма", "headers": [{"name":"Message-ID","value":"<abc@bank.az>"}, …] }
curl -X GET "https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/{msgId}" \
-H "Authorization: Bearer spine_sk_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);
/v1/mailboxes/{id}/messages/{msgId}/attachmentsmail:readMə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.
{ "attachments": [ {
"id": "cp73raw…", "name": "akt-sverki.pdf", "contentType": "application/pdf",
"size": 1665, "disposition": "attachment", "cid": null,
"blocked": false, "blockedReason": null,
"av": { "status": "clean", "threat": null, "detail": null, "scannedAt": "2026-07-26T10:48:38Z" }
} ] }
curl -X GET "https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/{msgId}/attachments" \
-H "Authorization: Bearer spine_sk_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);
/v1/mailboxes/{id}/messages/{msgId}/attachments/{attId}mail:readQoş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.
attId | həmin məktubun qoşma siyahısındaki qoşma id-si |
HTTP 200 Content-Type: application/pdf Content-Disposition: attachment; filename*=UTF-8''akt-sverki.pdf Content-Length: 1665 <байты файла> # 409, вложение заражено или его тип запрещён политикой проекта # 413, вложение больше лимита проекта # 503, антивирус недоступен: файл не отдаём (fail-closed), пробуйте позже
curl -X GET "https://api.spine.maxsystems.az/v1/mailboxes/{id}/messages/{msgId}/attachments/{attId}" \
-H "Authorization: Bearer spine_sk_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);
/v1/mailboxes/{id}/unreadmail:readЧисло непрочитанных писем в ящике, для бейджа-счётчика в вашем приложении (напр. сайдбар CRM). В пути можно указать и id ящика, и полный адрес сотрудника, так не нужно заранее знать id. Поле unread, непрочитанные во «Входящих» (число для бейджа), total, по всем папкам, folders, разбивка по папкам, где есть непрочитанные.
{ "unread": 3, "total": 5, "folders": [ { "name": "Inbox", "role": "inbox", "unread": 3 }, { "name": "Черновики", "role": null, "unread": 2 } ] }
curl -X GET "https://api.spine.maxsystems.az/v1/mailboxes/{id}/unread" \
-H "Authorization: Bearer spine_sk_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);
Перенос почты
/v1/mail-migrationsmail:migrateЗавести перенос одного ящика с другого хостинга по IMAP. Ящик у нас должен быть создан заранее: письма кладём в него по IMAP, поэтому нужен его пароль. sourceHost принимает и готовый пресет (vk, yandex, google, outlook), и адрес сервера. Для VK WorkSpace и Mail.ru нужен пароль для внешних приложений: обычный пароль их сервер отклоняет.
targetEmail | ящик у нас, куда переносим |
targetPassword | пароль этого ящика |
sourceHost | пресет (vk|yandex|google|outlook) или адрес IMAP-сервера |
sourcePort | порт источника (по умолчанию 993) |
sourceSecure | true = TLS сразу, false = STARTTLS |
sourceUser | логин на источнике (по умолчанию равен targetEmail) |
sourcePassword | пароль на источнике (для VK и Mail.ru, пароль приложения) |
{ "targetEmail": "info@ваш-домен", "targetPassword": "…", "sourceHost": "vk", "sourcePassword": "…" }{ "id": "…", "targetEmail": "info@ваш-домен", "status": "pending", "copiedMessages": 0 }
curl -X POST "https://api.spine.maxsystems.az/v1/mail-migrations" \
-H "Authorization: Bearer spine_sk_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);
/v1/mail-migrationsmail:migrateЗадания переноса проекта и готовые пресеты популярных хостингов.
{ "presets": { "vk": { "host": "imap.mail.ru", "port": 993 } }, "migrations": [ { "id": "…", "status": "done", "copiedMessages": 4210 } ] }
curl -X GET "https://api.spine.maxsystems.az/v1/mail-migrations" \ -H "Authorization: Bearer spine_sk_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);
/v1/mail-migrations/{id}mail:migrateПодробности задания: счётчики по каждой папке и журнал прогона.
{ "migration": { "status": "running" }, "folders": [ { "sourcePath": "Отправленные", "targetPath": "Sent", "copied": 812, "total": 812 } ], "log": ["…"] }
curl -X GET "https://api.spine.maxsystems.az/v1/mail-migrations/{id}" \
-H "Authorization: Bearer spine_sk_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);
/v1/mail-migrations/startmail:migrateЗапустить перенос по списку заданий. Повторный запуск догоняет только новые письма (помним последний перенесённый UID в каждой папке), поэтому его безопасно гонять после переключения MX.
ids | список id заданий |
includeTrash | переносить корзину (по умолчанию да) |
includeJunk | переносить спам (по умолчанию нет) |
{ "ids": ["…"], "includeTrash": true, "includeJunk": false }{ "queued": 1 }
curl -X POST "https://api.spine.maxsystems.az/v1/mail-migrations/start" \
-H "Authorization: Bearer spine_sk_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);
/v1/mail-migrations/{id}mail:migrateУдалить задание переноса вместе с сохранёнными доступами.
{ "deleted": true }
curl -X DELETE "https://api.spine.maxsystems.az/v1/mail-migrations/{id}" \
-H "Authorization: Bearer spine_sk_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
/v1/groupsmail:mailboxesLayihə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.
{ "groups": [ { "id": "…", "email": "team@ваш-домен", "membersCount": 4 } ] }
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);
/v1/groupsmail:mailboxesname@domain paylama qrupu yarat. Domen sizin layihənizə aid olmalıdır.
domain | layihə domeni (məcburidir) |
name | ünvan adı, 2-40 simvol [a-z0-9._-] (məcburidir) |
description | qrupun təsviri (məcburi deyil) |
{ "domain": "ваш-домен.az", "name": "team", "description": "Вся команда" }{ "id": "…", "email": "team@ваш-домен.az" }
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);
/v1/groups/{id}mail:mailboxesQrupu sil.
{ "deleted": true }
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);
/v1/groups/{id}/membersmail:mailboxesQrupun üzvləri (məktubun paylandığı qutular).
{ "members": [ { "id": "…", "email": "ivan@ваш-домен.az" } ] }
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);
/v1/groups/{id}/membersmail:mailboxesPoçt qutusunu ünvanına görə qrupa əlavə et.
{ "mailbox": "ivan@ваш-домен.az" }{ "ok": true, "member": { "id": "…", "email": "ivan@ваш-домен.az" } }
curl -X POST "https://api.spine.maxsystems.az/v1/groups/{id}/members" \
-H "Authorization: Bearer spine_sk_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);
/v1/groups/{id}/members/{userId}mail:mailboxesÜzvü qrupdan çıxar.
{ "ok": true }
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)
/v1/webmail/change-passwordпароль ящикаСотрудник меняет пароль СВОЕГО ящика сам. Ключ API не нужен: запрос подтверждается текущим паролем, который проверяется попыткой входа. Минимум 8 символов, словарные пароли почтовый сервер отклоняет. Защита от перебора: 5 попыток на ящик и 20 на адрес источника за 15 минут.
{ "email": "ivan@ваш-домен.az", "currentPassword": "…", "newPassword": "…" }{ "ok": true }
curl -X POST "https://api.spine.maxsystems.az/v1/webmail/change-password" \
-H "Authorization: Bearer spine_sk_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);
/v1/webmail/submit-asвход ящика (Basic/SSO)Отправить подготовленный черновик ОТ ИМЕНИ общего ящика (support@, info@). Нужно потому, что почтовый сервер даёт читать общий ящик, но отправлять от его имени не даёт. Платформа проверяет, что у отправителя есть доступ на запись к этому ящику, и пишет каждую отправку в аудит.
{ "accountId": "id ящика", "emailId": "id черновика" }{ "sent": true }
curl -X POST "https://api.spine.maxsystems.az/v1/webmail/submit-as" \
-H "Authorization: Bearer spine_sk_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);
/v1/webmail/scheduledвход ящика (Basic/SSO)Отправить письмо позже. Письмо лежит обычным черновиком в ящике, платформа отправляет его в назначенное время (проверка раз в 30 секунд). Не раньше чем через минуту и не дальше чем на год. GET возвращает свои запланированные, DELETE /v1/webmail/scheduled/{id} отменяет.
{ "accountId": "id ящика", "emailId": "id черновика", "sendAt": 1785400000000 }{ "id": "…", "sendAt": "2026-07-30T06:00:00.000Z", "status": "pending" }
curl -X POST "https://api.spine.maxsystems.az/v1/webmail/scheduled" \
-H "Authorization: Bearer spine_sk_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);
/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.
mailbox | işçinin sizin domendəki tam qutu ünvanı (məcburidir) |
{ "mailbox": "ivan@ваш-домен.az" }{ "url": "https://webmail.ваш-домен.az/?login_token=…", "email": "ivan@ваш-домен.az", "expiresIn": 60 }
curl -X POST "https://api.spine.maxsystems.az/v1/webmail/login-link" \
-H "Authorization: Bearer spine_sk_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
/v1/maildomainsmail:domainsDomeni poçta qoş (domeni yaradır və mümkün olduqda DNS-i özü dərc edir).
{ "name": "ваш-домен" }{ "id": "…", "name": "ваш-домен", "autoDns": { "created": 6 }, "records": [...], "ready": false }
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);
/v1/maildomainsmail:domainsLayihənin qoşulmuş domenlərinin siyahısı.
{ "domains": [ { "id": "…", "name": "ваш-домен" } ] }
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);
/v1/maildomains/{domain}mail:domainsDNS yazıları və domenin doğrulama statusu.
{ "name": "ваш-домен", "records": [...], "ready": true }
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);
/v1/maildomains/{domain}/catch-allmail:domainsDomendə 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.
{ "domain": "ваш-домен", "enabled": true, "mailbox": "catchall@ваш-домен", "autoReply": true }
curl -X GET "https://api.spine.maxsystems.az/v1/maildomains/{domain}/catch-all" \
-H "Authorization: Bearer spine_sk_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);
/v1/maildomains/{domain}/catch-allmail:domainsMö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.
mailbox | bu 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) |
autoReply | göndərənə ünvanın mövcud olmadığını bildirmək (defolt olaraq true) |
{ "mailbox": "catchall@ваш-домен", "autoReply": true }{ "domain": "ваш-домен", "enabled": true, "mailbox": "catchall@ваш-домен", "autoReply": true }
curl -X PUT "https://api.spine.maxsystems.az/v1/maildomains/{domain}/catch-all" \
-H "Authorization: Bearer spine_sk_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);
/v1/domains/minedomains:readСроки регистрации доменов проекта: сколько дней осталось, у какого регистратора, где домен используется. Поле source говорит, откуда дата: rdap или whois, из реестра, manual, вбита в консоли (у части зон, например .az, машинного источника нет вовсе). Если даты нет, expiresAt = null и state = unknown.
{ "domains": [ { "domain": "ваш-домен", "registrar": "…", "expiresAt": "2027-03-01T00:00:00.000Z", "daysLeft": 208, "state": "ok", "uses": ["почта"], "source": "rdap" } ], "alertDays": [40, 30, 15, 5, 1] }
curl -X GET "https://api.spine.maxsystems.az/v1/domains/mine" \ -H "Authorization: Bearer spine_sk_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);
Защита
/v1/protection/turnstile/verifyprotection:verifyПроверить ответ Turnstile из формы вашего сайта. Секрет хранится у нас и наружу не отдаётся: класть его в браузер нельзя, иначе проверку обойдут за минуту. Вы присылаете только токен, который вернул виджет, и домен, для которого он выдан.
token | значение поля cf-turnstile-response из вашей формы (обязательно) |
domain | домен, для которого настроены ключи Turnstile |
{ "token": "0.abc…", "domain": "ваш-домен" }{ "ok": true, "errors": [] }
curl -X POST "https://api.spine.maxsystems.az/v1/protection/turnstile/verify" \
-H "Authorization: Bearer spine_sk_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);
Безопасность
/v1/security/reportsecurity:readОтчёт безопасности своих доменов: оценка A-F и открытые находки (TLS, DNS, почтовая аутентификация, HTTP-заголовки). Только чтение, сканы отсюда не запускаются. Находки со статусом «не проверено» (unknown) на оценку не влияют: не проверено это не «чисто».
{ "project": "ваш-проект", "grade": "B", "score": 82, "open": 3, "findings": [ { "target": "ваш-домен", "category": "mail", "severity": "medium", "title": "DMARC в режиме наблюдения (p=none)", "detail": "…" } ] }
curl -X GET "https://api.spine.maxsystems.az/v1/security/report" \ -H "Authorization: Bearer spine_sk_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
/v1/dns/{zone}/recordsdns:readZonanın DNS yazılarının siyahısı (zona layihəyə bağlı olmalıdır).
{ "zone": "ваш-домен", "records": [ { "id": "…", "type": "A", "name": "@", "data": "1.2.3.4" } ] }
curl -X GET "https://api.spine.maxsystems.az/v1/dns/{zone}/records" \
-H "Authorization: Bearer spine_sk_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);
/v1/dns/{zone}/recordsdns:recordsLayihə zonasında DNS yazısı yarat.
type | A | AAAA | TXT | MX | CNAME | NS | SRV | CAA |
name | yazının adı (kök üçün '@') |
data | dəyər |
priority / ttl | MX / TTL üçün (məcburi deyil) |
{ "type": "CNAME", "name": "app", "data": "target.example.com" }{ "created": true }
curl -X POST "https://api.spine.maxsystems.az/v1/dns/{zone}/records" \
-H "Authorization: Bearer spine_sk_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);
/v1/dns/{zone}/records/{id}dns:recordsDNS yazısını sil. GoDaddy üçün ?data=<dəyər> əlavə et.
{ "deleted": true }
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
/v1/webhookswebhooksHadisələrə abunə ol: platforma sizin URL-ə POST göndərəcək. Cavabdakı secret bir dəfə göstərilir.
url | qəbul üçün sizin https ünvanınız (məcburidir) |
events | qutudakı 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 |
{ "url": "https://ваш-сервер/webhook", "events": ["mail.received", "mail.bounced"] }{ "id": "…", "url": "…", "events": [...], "secret": "whsec_…" }
curl -X POST "https://api.spine.maxsystems.az/v1/webhooks" \
-H "Authorization: Bearer spine_sk_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);
/v1/webhookswebhooksWebhook-larınızın siyahısı.
{ "webhooks": [ { "id": "…", "url": "…", "events": [...], "active": true } ] }
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);
/v1/webhooks/{id}webhooksAbunə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.
url | yeni https qəbul ünvanı (istəyə bağlı) |
events | tam yeni hadisə siyahısı (istəyə bağlı) |
active | false çatdırılmanı dayandırır, abunə və jurnal itmir (istəyə bağlı) |
{ "events": ["mail.received", "mail.sent_folder", "mail.bounced"] }{ "id": "…", "url": "…", "events": [...], "active": true }
curl -X PATCH "https://api.spine.maxsystems.az/v1/webhooks/{id}" \
-H "Authorization: Bearer spine_sk_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);
/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.
{ "id": "…", "secret": "whsec_…" }
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);
/v1/webhooks/{id}webhooksWebhook-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.
{ "deleted": true }
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);
/v1/webhooks/{id}/deliverieswebhooksWebhook çatdırılma jurnalı: status (pending/delivered/failed), kodlar, cəhdlər və dəqiq imzalanmış gövdə, imzanın sazlanması üçün əlverişlidir.
{ "deliveries": [ { "id": 42, "event": "mail.received", "status": "failed", "httpCode": 401, "attempts": 7, "payload": "{…}" } ] }
curl -X GET "https://api.spine.maxsystems.az/v1/webhooks/{id}/deliveries" \
-H "Authorization: Bearer spine_sk_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);
/v1/webhooks/{id}/deliveries/{deliveryId}/redeliverwebhooksKonkret çatdırılmanı indi yenidən göndər (məsələn, imza yoxlayıcısını düzəltdikdən sonra).
{ "status": "delivered", "httpCode": 200 }
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ı
/v1/telegram/mybotstelegram:botsMövcud botu tokeni ilə (BotFather-dan) platformaya qoş.
{ "token": "123456:ABC-…", "note": "бот проекта" }{ "id": "…", "botId": 123456, "username": "MyBot", "name": "My Bot" }
curl -X POST "https://api.spine.maxsystems.az/v1/telegram/mybots" \
-H "Authorization: Bearer spine_sk_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);
/v1/telegram/mybots/bulktelegram:botsKü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).
{ "tokens": ["123456:AAA…", "234567:BBB…"] }{ "total": 2, "added": 2, "failed": 0, "results": [ { "ok": true, "username": "MyBot", "botId": 123456 } ] }
curl -X POST "https://api.spine.maxsystems.az/v1/telegram/mybots/bulk" \
-H "Authorization: Bearer spine_sk_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);
/v1/telegram/mybotstelegram:botsLayihənin botlarının siyahısı.
{ "bots": [ { "id": "…", "username": "MyBot", "webhookSet": true } ] }
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);
/v1/telegram/mybots/{id}telegram:botsBotun canlı statusu (getMe + webhook + əmrlər).
{ "username": "MyBot", "me": {...}, "webhook": {...}, "commands": [...] }
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);
/v1/telegram/mybots/{id}/webhooktelegram:botsBotun update-lərini serverinizə yönləndir (SPINE onları Telegram-dan qəbul edib sizə ötürür).
{ "forwardUrl": "https://ваш-сервер/tg" }{ "hookUrl": "https://api.spine.maxsystems.az/v1/tg/hook/123456", "forwardUrl": "…" }
curl -X POST "https://api.spine.maxsystems.az/v1/telegram/mybots/{id}/webhook" \
-H "Authorization: Bearer spine_sk_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);
/v1/telegram/mypresetstelegram:botsLayihəyə əlçatan hazır fabrik presetləri (ortaq + öz): əmrlər, webhook, profil.
{ "presets": [ { "id": "…", "name": "Поддержка", "forwardUrl": "https://…" } ] }
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);
/v1/telegram/mybots/create-linktelegram:botsFabrik: 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).
{ "suggestedUsername": "myproject_bot", "presetId": "…(необязательно)" }{ "link": "https://t.me/newbot/spinemsbot/myproject_bot" }
curl -X POST "https://api.spine.maxsystems.az/v1/telegram/mybots/create-link" \
-H "Authorization: Bearer spine_sk_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
/v1/brandbrand:readLayihə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.
{
"tenant": "acme",
"displayName": "Acme Inc.",
"tagline": "Строим будущее",
"colors": [{ "name": "Основной", "hex": "#5b8def", "role": "primary" }],
"fonts": [{ "name": "Inter", "role": "текст" }],
"links": [{ "label": "Сайт", "url": "https://acme.example" }],
"logos": [{ "variant": "primary", "format": "svg", "downloadUrl": "https://api.spine.maxsystems.az/v1/public/brand/…" }],
"files": [{ "label": "Брендбук", "format": "pdf", "downloadUrl": "https://api.spine.maxsystems.az/v1/public/brand/…" }]
}
curl -X GET "https://api.spine.maxsystems.az/v1/brand" \ -H "Authorization: Bearer spine_sk_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);
Проект
/v1/projectproject:readКарточка вашего проекта одним запросом: что подключено и активно (домены, ящики, сертификаты со сроками, API-ключи и скоупы, вебхуки, Telegram-боты, бренд-кит, тарифы и счета) и что требует внимания (истекающие серты, просроченные счета). Поле client это ваш клиент-владелец: у одного клиента может быть несколько проектов. Self-view: ключ видит только СВОЙ проект.
{
"tenant": { "slug": "acme", "name": "Acme Inc.", "status": "active" },
"client": { "slug": "acme-group", "name": "Acme Group", "status": "active" },
"domains": [{ "domain": "acme.az", "whitelabel": true, "branding": true }],
"mail": { "domains": 1, "mailboxes": 8 },
"certificates": { "total": 2, "list": [{ "subject": "*.acme.az", "status": "active", "notAfter": "2026-10-14", "autoRenew": true }] },
"dns": { "managedZones": 1, "monitors": 0 },
"api": { "clients": 1, "keys": 2, "scopes": ["mail:send","mail:read"], "requests": 1240, "webhooks": 1 },
"telegram": { "bots": 1 },
"brand": { "hasProfile": true, "assets": 5 },
"billing": { "customers": 1, "activeSubscriptions": 2, "openInvoices": 1, "dueMinor": 5900, "currency": "AZN" },
"attention": [{ "kind": "cert-expiring", "text": "Сертификат *.acme.az истекает через 12 дн." }]
}
curl -X GET "https://api.spine.maxsystems.az/v1/project" \ -H "Authorization: Bearer spine_sk_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);
/v1/project/auditaudit:readЖурнал событий вашего проекта: кто, что и когда менял (сертификаты, DNS, API-ключи, бренд, счета). Свежие сверху. Параметр limit, до 500.
limit | сколько событий вернуть, до 500 (по умолчанию 100) |
{ "events": [ { "ts": "2026-07-22T18:40:00Z", "action": "cert.issue", "target": "*.acme.az", "actor": "apikey:spine_sk_ab12cd", "source": "api" } ] }
curl -X GET "https://api.spine.maxsystems.az/v1/project/audit" \ -H "Authorization: Bearer spine_sk_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
/v1/site-configsiteconfig:readСырой SEO/site-конфиг вашего проекта (meta по языкам, OG, аналитика, robots, sitemap, JSON-LD, verification). Нужен, только если рендерите <head> сами. Проще, брать готовые артефакты ниже (их отдаёт SPINE, ключ не нужен).
{ "tenant": "acme", "meta": { "en": { "title": "Acme", "description": "…" } }, "og": { "siteName": "Acme" }, "analytics": { "ga4": "G-XXXX" }, "robots": { "disallow": ["/admin"] } }
curl -X GET "https://api.spine.maxsystems.az/v1/site-config" \ -H "Authorization: Bearer spine_sk_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);
/v1/site/{tenant}/robots.txtГотовый robots.txt проекта (публично, ключ не нужен). Проксируйте /robots.txt вашего сайта сюда, правим в консоли, у вас применяется без редеплоя.
User-agent: * Disallow: /admin Sitemap: https://acme.az/sitemap.xml
curl -X GET "https://api.spine.maxsystems.az/v1/site/{tenant}/robots.txt"
const res = await fetch("https://api.spine.maxsystems.az/v1/site/{tenant}/robots.txt", {
method: "GET"
});
const data = await res.json();
import requests
r = requests.get(
"https://api.spine.maxsystems.az/v1/site/{tenant}/robots.txt"
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/site/{tenant}/robots.txt");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);
/v1/site/{tenant}/sitemap.xmlГотовый sitemap.xml из URL проекта (публично). Проксируйте /sitemap.xml сюда.
<?xml version="1.0" encoding="UTF-8"?><urlset>…</urlset>
curl -X GET "https://api.spine.maxsystems.az/v1/site/{tenant}/sitemap.xml"
const res = await fetch("https://api.spine.maxsystems.az/v1/site/{tenant}/sitemap.xml", {
method: "GET"
});
const data = await res.json();
import requests
r = requests.get(
"https://api.spine.maxsystems.az/v1/site/{tenant}/sitemap.xml"
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/site/{tenant}/sitemap.xml");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);
/v1/site/{tenant}/headГотовый <head>-фрагмент: meta, canonical, Open Graph, Twitter, JSON-LD, verification-метки и сниппеты аналитики (GA4/Метрика/Pixel). Вставьте один include в <head>, SPINE рендерит всё сам. Параметры: path (текущий путь для canonical/og:url), lang (язык meta).
path | путь страницы для canonical и og:url (например /events/123) |
lang | язык meta-тегов (ключ из секции meta, например ru/en/az) |
<title>Acme</title> <meta name="description" content="…"> <meta property="og:image" content="…/og.png"> <script>…GA4…</script>
curl -X GET "https://api.spine.maxsystems.az/v1/site/{tenant}/head"
const res = await fetch("https://api.spine.maxsystems.az/v1/site/{tenant}/head", {
method: "GET"
});
const data = await res.json();
import requests
r = requests.get(
"https://api.spine.maxsystems.az/v1/site/{tenant}/head"
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/site/{tenant}/head");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);
/v1/site/{tenant}/og.pngБрендированная OG-картинка 1200×630, сгенерированная из бренд-кита проекта (лого, цвета). Параметры title/subtitle. Ссылку выдаёт /head автоматически, отдельно нужна, если хотите свою подпись на превью страницы.
title | заголовок на картинке (по умолчанию название бренда) |
subtitle | подзаголовок (необязательно) |
(image/png, картинка 1200×630)
curl -X GET "https://api.spine.maxsystems.az/v1/site/{tenant}/og.png"
const res = await fetch("https://api.spine.maxsystems.az/v1/site/{tenant}/og.png", {
method: "GET"
});
const data = await res.json();
import requests
r = requests.get(
"https://api.spine.maxsystems.az/v1/site/{tenant}/og.png"
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/site/{tenant}/og.png");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);
/v1/site/{tenant}/favicon.pngFavicon-набор из логотипа проекта: favicon.png/ico (32), apple-touch-icon.png (180), icon-192/512.png и site.webmanifest. Ссылки на них /head тоже проставляет сам. Меняете лого в бренд-ките, favicon у проекта обновляется.
(image/png, иконка)
curl -X GET "https://api.spine.maxsystems.az/v1/site/{tenant}/favicon.png"
const res = await fetch("https://api.spine.maxsystems.az/v1/site/{tenant}/favicon.png", {
method: "GET"
});
const data = await res.json();
import requests
r = requests.get(
"https://api.spine.maxsystems.az/v1/site/{tenant}/favicon.png"
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/site/{tenant}/favicon.png");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);
Секреты
/v1/secretssecrets:readВсе секреты проекта в окружении одной картой, грузите как переменные окружения при старте. Значения шифруются у нас (AES-256-GCM), отдаются только вашему ключу по TLS. Параметр env (по умолчанию prod).
env | окружение: prod|staging|dev|любое (по умолчанию prod) |
{ "env": "prod", "secrets": { "DB_PASSWORD": "…", "STRIPE_KEY": "…" } }
curl -X GET "https://api.spine.maxsystems.az/v1/secrets" \ -H "Authorization: Bearer spine_sk_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);
/v1/secrets.envsecrets:readГотовый .env-файл со всеми секретами окружения (NAME="value"). Проект пишет его к себе одним запросом.
env | окружение (по умолчанию prod) |
DB_PASSWORD="…" STRIPE_KEY="…"
curl -X GET "https://api.spine.maxsystems.az/v1/secrets.env" \ -H "Authorization: Bearer spine_sk_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);
/v1/secrets/getsecrets:readОдно значение по имени (имя в теле, имена могут быть путём вида db/password).
{ "env": "prod", "name": "db/password" }{ "name": "db/password", "env": "prod", "value": "…" }
curl -X POST "https://api.spine.maxsystems.az/v1/secrets/get" \
-H "Authorization: Bearer spine_sk_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);
/v1/secretssecrets:writeЗаписать свой секрет (создаёт новую версию, старые хранятся для отката).
{ "env": "prod", "name": "stripe/key", "value": "sk_live_…", "description": "ключ Stripe" }{ "name": "stripe/key", "env": "prod", "version": 2 }
curl -X PUT "https://api.spine.maxsystems.az/v1/secrets" \
-H "Authorization: Bearer spine_sk_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);
/v1/secrets/generatesecrets:writeСгенерировать стойкий секрет и сохранить его. kind: base64|hex|alnum|uuid, length до 512. Значение возвращается один раз.
{ "env": "prod", "name": "jwt/secret", "kind": "base64", "length": 48 }{ "name": "jwt/secret", "env": "prod", "version": 1, "value": "…сгенерировано…" }
curl -X POST "https://api.spine.maxsystems.az/v1/secrets/generate" \
-H "Authorization: Bearer spine_sk_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);
/v1/secrets/leasesecrets:readКороткоживущая выдача: одноразовый токен-ссылка на один секрет (или всю карту), чтобы передать значение процессу/человеку, не светя долгоживущий ключ. ttl в секундах (30..86400, по умолчанию 300), singleUse по умолчанию true.
{ "env": "prod", "name": "db/password", "ttl": 120 }{ "token": "spine_lease_…", "url": "https://api.spine.maxsystems.az/v1/secrets/lease/spine_lease_…", "expiresAt": "…", "singleUse": true }
curl -X POST "https://api.spine.maxsystems.az/v1/secrets/lease" \
-H "Authorization: Bearer spine_sk_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);
/v1/secrets/lease/{token}Погасить lease по токену (сам токен, доступ, ключ не нужен). Работает один раз до истечения TTL. Возвращает значение секрета (или карту, если lease на всю).
{ "env": "prod", "name": "db/password", "value": "…" }
curl -X GET "https://api.spine.maxsystems.az/v1/secrets/lease/{token}"
const res = await fetch("https://api.spine.maxsystems.az/v1/secrets/lease/{token}", {
method: "GET"
});
const data = await res.json();
import requests
r = requests.get(
"https://api.spine.maxsystems.az/v1/secrets/lease/{token}"
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/secrets/lease/{token}");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);
Верификация
/v1/otp/sendotp:sendВыдать одноразовый код и доставить его: SMS или Telegram. Код у нас хранится только в виде отпечатка и не отдаётся никому, кроме получателя. Ключ с пометкой «песочница» ничего никуда не шлёт и возвращает код прямо в ответе, чтобы интеграцию можно было собрать без расхода денег. Условия канала при этом проверяются как в бою: нет учётки SMS - 503, пользователь не привязал Telegram - 409.
channel | sms (по умолчанию) | telegram |
to | номер телефона в международном виде (994501234567) или ваш идентификатор пользователя для Telegram |
purpose | зачем код: login, payment, reset. Попадает в журнал (необязательно) |
ttlSeconds | срок жизни кода, 60..3600 (по умолчанию из настроек проекта) |
codeLength | длина кода, 4..8 (по умолчанию 6) |
{ "channel": "sms", "to": "994501234567", "purpose": "login" }{ "verificationId": "3f2a…", "channel": "sms", "to": "994501234567", "expiresAt": "…", "resendAfter": "…", "attemptsLeft": 5, "delivery": "sent", "sandbox": false }
curl -X POST "https://api.spine.maxsystems.az/v1/otp/send" \
-H "Authorization: Bearer spine_sk_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);
/v1/otp/verifyotp:verifyПроверить код. Правильный путь, по verificationId; можно и по получателю, тогда берётся его последний активный код. Успех гасит код. Ответ всегда объясняет причину отказа: no_active_code, expired, wrong_code, too_many_attempts.
verificationId | идентификатор из ответа на send (рекомендуется) |
to | получатель, если verificationId не сохраняли |
channel | нужен вместе с to, чтобы одинаково нормализовать номер |
code | код от пользователя (обязателен) |
{ "verificationId": "3f2a…", "code": "123456" }{ "valid": true, "verificationId": "3f2a…", "channel": "sms", "to": "994501234567", "purpose": "login" }
curl -X POST "https://api.spine.maxsystems.az/v1/otp/verify" \
-H "Authorization: Bearer spine_sk_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);
/v1/otp/resendotp:sendПовторная отправка в рамках той же верификации: выдаётся новый код, счётчик неверных попыток сохраняется. Пауза между повторами своя (по умолчанию 60 секунд), раньше срока приходит 429.
{ "verificationId": "3f2a…" }{ "verificationId": "3f2a…", "expiresAt": "…", "resendAfter": "…", "delivery": "sent" }
curl -X POST "https://api.spine.maxsystems.az/v1/otp/resend" \
-H "Authorization: Bearer spine_sk_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);
/v1/otp/verifications/{id}otp:verifyСостояние попытки: подтверждена или нет, сколько попыток осталось, что с доставкой. Кода здесь нет.
{ "verificationId": "3f2a…", "verified": false, "attemptsLeft": 4, "delivery": "sent", "sandbox": false }
curl -X GET "https://api.spine.maxsystems.az/v1/otp/verifications/{id}" \
-H "Authorization: Bearer spine_sk_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);
/v1/otp/verifications/{id}/deliveryotp:verifyСпросить оператора, что стало с сообщением. «Приняли в очередь» и «доставлено» это разные вещи, поэтому статус доставки отдельный: delivered, pending, failed или unknown.
{ "delivery": "delivered", "note": "доставлено" }
curl -X GET "https://api.spine.maxsystems.az/v1/otp/verifications/{id}/delivery" \
-H "Authorization: Bearer spine_sk_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);
/v1/otp/settingsotp:verifyЧто доступно вашему проекту: включённые каналы, длина кода, срок жизни, пауза между повторами и действующие лимиты. Полезно показать пользователю честный таймер вместо отказа наугад.
{ "project": "ваш-проект", "channels": ["sms","telegram","totp"], "codeLength": 6, "ttlSeconds": 300, "limits": { "perIdentifierHour": 5, "perIdentifierDay": 15, "perProjectDay": 500, "perIpHour": 20 } }
curl -X GET "https://api.spine.maxsystems.az/v1/otp/settings" \ -H "Authorization: Bearer spine_sk_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);
/v1/otp/telegram/linkotp:sendСсылка привязки Telegram. Бот не может написать человеку первым, поэтому сначала пользователь открывает эту ссылку и нажимает «Старт» у бота вашего проекта, и только после этого коды доставляются в Telegram. Ссылка одноразовая и живёт 15 минут. Если пользователь уже привязан, вернётся linked: true.
{ "identifier": "user-42" }{ "url": "https://t.me/вашбот?start=otp_…", "bot": "вашбот", "expiresAt": "…", "linked": false }
curl -X POST "https://api.spine.maxsystems.az/v1/otp/telegram/link" \
-H "Authorization: Bearer spine_sk_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);
/v1/otp/telegram/linkotp:verifyПривязан ли пользователь к Telegram (параметр identifier). Спрашивайте до отправки, чтобы показать человеку кнопку привязки, а не ошибку.
{ "linked": true, "username": "ivan", "linkedAt": "…", "revoked": false }
curl -X GET "https://api.spine.maxsystems.az/v1/otp/telegram/link" \ -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/otp/telegram/link", {
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/telegram/link",
headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/otp/telegram/link");
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);
/v1/otp/telegram/linkotp:sendОтвязать Telegram у пользователя (identifier в теле или в query).
{ "removed": true }
curl -X DELETE "https://api.spine.maxsystems.az/v1/otp/telegram/link" \ -H "Authorization: Bearer spine_sk_YOUR_KEY"
const res = await fetch("https://api.spine.maxsystems.az/v1/otp/telegram/link", {
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/telegram/link",
headers={"Authorization": "Bearer spine_sk_YOUR_KEY"}
)
print(r.status_code, r.json())
$ch = curl_init("https://api.spine.maxsystems.az/v1/otp/telegram/link");
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);
/v1/otp/totp/enrollotp:totpНачать подключение приложения-аутентификатора (Google Authenticator, 1Password и любое другое по RFC 6238). Возвращает секрет и otpauth-ссылку для QR-кода. До подтверждения кодом TOTP выключен: иначе человек запишет секрет с ошибкой и потеряет доступ, думая, что всё настроил.
{ "identifier": "user-42", "issuer": "Ваш проект" }{ "secret": "JBSWY3DP…", "otpauthUrl": "otpauth://totp/…", "enabled": false }
curl -X POST "https://api.spine.maxsystems.az/v1/otp/totp/enroll" \
-H "Authorization: Bearer spine_sk_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);
/v1/otp/totp/confirmotp:totpПодтвердить подключение кодом из приложения и включить TOTP.
{ "identifier": "user-42", "code": "123456" }{ "enabled": true }
curl -X POST "https://api.spine.maxsystems.az/v1/otp/totp/confirm" \
-H "Authorization: Bearer spine_sk_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);
/v1/otp/totp/verifyotp:totpПроверить код из приложения либо резервный код. Один и тот же код второй раз не принимается (reason: code_already_used), резервный код после использования вычёркивается.
{ "identifier": "user-42", "code": "123456" }{ "valid": true }
curl -X POST "https://api.spine.maxsystems.az/v1/otp/totp/verify" \
-H "Authorization: Bearer spine_sk_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);
/v1/otp/totp/backup-codesotp:totpВыпустить резервные коды (10 штук). Показываются один раз, у нас хранятся только отпечатки. Прежний список при этом перестаёт работать.
{ "identifier": "user-42" }{ "codes": ["A1B2C3D4E5", "…"] }
curl -X POST "https://api.spine.maxsystems.az/v1/otp/totp/backup-codes" \
-H "Authorization: Bearer spine_sk_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);
/v1/otp/totpotp:totpСостояние TOTP пользователя (параметр identifier): подключён, включён, сколько резервных кодов осталось.
{ "enrolled": true, "enabled": true, "backupLeft": 8, "backupUsed": 2 }
curl -X GET "https://api.spine.maxsystems.az/v1/otp/totp" \ -H "Authorization: Bearer spine_sk_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);
/v1/otp/totpotp:totpОтключить TOTP у пользователя и удалить его секрет.
{ "removed": true }
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);
İmzanın yoxlanması və çatdırılmaHə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.
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);
}
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).
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.
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, поэтому квитанция об оплате уйдёт даже тому, кто отказался от новостей.
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.
https://api.spine.maxsystems.az · maşın təsviri /v1/openapi.json