Документация для разработчиков

<p><strong>Kalkhan API</strong> позволяет выполнять проверку лицензий, активацию и дилерскую интеграцию из ваших собственных систем. Этот документ описывает структуру запросов и ответов, способ аутентификации и коды ошибок.</p> <h2 id="genel-bakis" style="scroll-margin-top:120px;">Обзор</h2> <p>Kalkhan API — это ресурсно-ориентированный <strong>REST</strong>-интерфейс. Тела всех запросов и ответов передаются в формате <strong>JSON</strong> в кодировке <strong>UTF-8</strong>. Соединения обязательно шифруются по <strong>TLS 1.2 и выше</strong>; незашифрованные (HTTP) запросы не принимаются.</p> <div class="table-responsive"> <table class="table" style="background:#ffffff;"> <tbody> <tr><th scope="row" style="width:32%;">Base URL</th><td><code>https://api.kalkhan.com.tr/v1</code></td></tr> <tr><th scope="row">Протокол</th><td>HTTPS / TLS 1.2+ (обязательно)</td></tr> <tr><th scope="row">Формат данных</th><td>JSON · <code>Content-Type: application/json; charset=utf-8</code></td></tr> <tr><th scope="row">Кодировка</th><td>UTF-8</td></tr> <tr><th scope="row">Формат даты</th><td>ISO 8601 (UTC), например <code>2027-07-23T00:00:00Z</code></td></tr> <tr><th scope="row">Ограничение частоты</th><td>120 запросов в минуту (на каждый ключ)</td></tr> </tbody> </table> </div> <p>Адреса всех эндпоинтов используются вместе с base URL. Например, полный адрес эндпоинта проверки лицензии — <code>https://api.kalkhan.com.tr/v1/licenses/verify</code>.</p> <h2 id="kurulum" style="scroll-margin-top:120px;">Установка и начало работы</h2> <p>Если вы начинаете интеграцию с нуля, выполните шаги ниже по порядку. Весь процесс занимает в среднем <strong>15 минут</strong> и не требует устанавливать на сервере какое-либо ПО Kalkhan — достаточно среды, способной отправлять HTTPS-запросы.</p> <h3 style="margin-top:26px;">Предварительные требования</h3> <ul> <li>Подтверждённый <strong>дилерский аккаунт Kalkhan</strong> (заявка: <a href="/iletisim">форма обратной связи</a>)</li> <li>Серверная среда с поддержкой TLS 1.2 или выше (PHP 7.4+, Node.js 16+, .NET 6+, Python 3.8+ и т. п.)</li> <li>Способ хранить API-ключ <em>на стороне сервера</em> (рекомендуются переменные окружения)</li> </ul> <h3 style="margin-top:26px;">Пошаговая настройка</h3> <ol> <li style="margin-bottom:10px;"><strong>Войдите в дилерскую панель.</strong> Доступ к панели открывается после того, как аккаунт подтвердит администратор.</li> <li style="margin-bottom:10px;"><strong>Создайте API-ключ.</strong> В разделе «API-ключи» сначала выпустите тестовый ключ с префиксом <code>klk_test_</code>.</li> <li style="margin-bottom:10px;"><strong>Задайте переменные окружения.</strong> Добавьте ключ и base URL в конфигурацию приложения (пример ниже).</li> <li style="margin-bottom:10px;"><strong>Отправьте первый запрос.</strong> Чтобы проверить соединение, вызовите эндпоинт проверки доступности.</li> <li style="margin-bottom:10px;"><strong>Зарегистрируйте адрес вебхука.</strong> Чтобы получать события лицензий мгновенно, укажите в панели свой HTTPS-адрес для вебхуков.</li> <li><strong>Переходите в продакшен.</strong> После тестов замените ключ на <code>klk_live_</code>; в коде меняется только ключ.</li> </ol> <h4 style="margin-top:22px;">1) Конфигурация окружения</h4> <pre><code style="background:#0F2453;color:#e6edff;padding:18px;border-radius:10px;overflow-x:auto;font-size:14px;line-height:1.6;display:block;"># .env KALKHAN_API_URL=https://api.kalkhan.com.tr/v1 KALKHAN_API_KEY=klk_test_xxxxxxxxxxxxxxxx KALKHAN_WEBHOOK_SECRET=whsec_xxxxxxxxxxxx</code></pre> <h4 style="margin-top:22px;">2) Проверка соединения</h4> <pre><code style="background:#0F2453;color:#e6edff;padding:18px;border-radius:10px;overflow-x:auto;font-size:14px;line-height:1.6;display:block;">curl -s https://api.kalkhan.com.tr/v1/ping \ -H "Authorization: Bearer $KALKHAN_API_KEY" # Успешный ответ { "ok": true, "env": "test", "dealer": "BYI-1001" }</code></pre> <div style="background:#eef3ff;border-left:4px solid #3F82FB;padding:16px 18px;border-radius:8px;margin:22px 0;"> <strong>Совет:</strong> лицензии, созданные в тестовой среде, не попадают в реальную тарификацию и могут быть удалены в любой момент. Перед запуском интеграции в продакшен прогоните тестовым ключом все сценарии: создание, проверку и отмену лицензии. </div> <h2 id="kimlik-dogrulama" style="scroll-margin-top:120px;">Аутентификация</h2> <p>Запросы к API авторизуются по схеме <strong>Bearer</strong> с использованием вашего API-ключа. Ключ нужно передавать в заголовке <code>Authorization</code> каждого запроса.</p> <pre><code style="background:#0F2453;color:#e6edff;padding:18px;border-radius:10px;overflow-x:auto;font-size:14px;line-height:1.6;display:block;">Authorization: Bearer klk_live_xxx Content-Type: application/json; charset=utf-8 Accept: application/json</code></pre> <p>Создать и отозвать API-ключ можно в разделе «API-ключи» <strong>вашей дилерской панели</strong>. Для тестовой среды используются ключи с префиксом <code>klk_test_</code>, для боевой — <code>klk_live_</code>.</p> <p><strong>Важно о безопасности:</strong> API-ключ — это секретные учётные данные, храните его только на стороне сервера. Не встраивайте ключ в браузерный код, в пакет мобильного приложения или в публичный репозиторий. При малейшем подозрении на утечку немедленно отзовите ключ в панели и выпустите новый.</p> <h2 id="lisans-dogrulama" style="scroll-margin-top:120px;">Проверка лицензии</h2> <p>Используется, чтобы узнать статус лицензионного ключа, его продукт и оставшийся срок. Лицензионные ключи имеют вид <code>KLKN-XXXX-XXXX-XXXX-XXXX</code>.</p> <div class="table-responsive"> <table class="table" style="background:#ffffff;"> <thead> <tr><th>Метод</th><th>Эндпоинт</th><th>Права</th></tr> </thead> <tbody> <tr><td><strong>POST</strong></td><td><code>/licenses/verify</code></td><td>Bearer API-ключ</td></tr> </tbody> </table> </div> <h3>Запрос</h3> <pre><code style="background:#0F2453;color:#e6edff;padding:18px;border-radius:10px;overflow-x:auto;font-size:14px;line-height:1.6;display:block;">POST /v1/licenses/verify HTTP/1.1 Host: api.kalkhan.com.tr Authorization: Bearer klk_live_xxx Content-Type: application/json; charset=utf-8 { "key": "KLKN-4F2A-9C71-B3D8-A15E", "deviceId": "WIN-8f2c41ab9d", "productId": "kalkhan-av-windows" }</code></pre> <h3>Ответ (200 OK)</h3> <pre><code style="background:#0F2453;color:#e6edff;padding:18px;border-radius:10px;overflow-x:auto;font-size:14px;line-height:1.6;display:block;">{ "key": "KLKN-4F2A-9C71-B3D8-A15E", "status": "active", "product": "Kalkhan Anti-Virüs (Windows)", "edition": "business", "seats": 25, "seatsUsed": 11, "expiresAt": "2027-03-14T23:59:59Z" }</code></pre> <div class="table-responsive"> <table class="table" style="background:#ffffff;"> <thead> <tr><th style="width:24%;">Поле</th><th style="width:18%;">Тип</th><th>Описание</th></tr> </thead> <tbody> <tr><td><code>status</code></td><td>string</td><td><code>active</code>, <code>expired</code>, <code>suspended</code> или <code>cancelled</code></td></tr> <tr><td><code>product</code></td><td>string</td><td>Отображаемое имя продукта, к которому относится лицензия</td></tr> <tr><td><code>edition</code></td><td>string</td><td><code>home</code>, <code>business</code> или <code>enterprise</code></td></tr> <tr><td><code>seats</code></td><td>integer</td><td>Общее число устройств, покрываемых лицензией</td></tr> <tr><td><code>expiresAt</code></td><td>string</td><td>Дата окончания в формате ISO 8601 (UTC)</td></tr> </tbody> </table> </div> <p>Для несуществующего или недействительного ключа возвращается <code>404 not_found</code>, для ключа с неверным форматом — <code>400 invalid_request</code>.</p> <h2 id="lisans-olusturma" style="scroll-margin-top:120px;">Создание лицензии (дилер)</h2> <p>Используется авторизованными дилерами для выпуска новой лицензии на имя конечного пользователя. Этот эндпоинт доступен только API-ключам с дилерскими правами.</p> <div class="table-responsive"> <table class="table" style="background:#ffffff;"> <thead> <tr><th>Метод</th><th>Эндпоинт</th><th>Права</th></tr> </thead> <tbody> <tr><td><strong>POST</strong></td><td><code>/licenses</code></td><td>Дилерский API-ключ</td></tr> </tbody> </table> </div> <h3>Запрос</h3> <pre><code style="background:#0F2453;color:#e6edff;padding:18px;border-radius:10px;overflow-x:auto;font-size:14px;line-height:1.6;display:block;">POST /v1/licenses HTTP/1.1 Host: api.kalkhan.com.tr Authorization: Bearer klk_live_xxx Content-Type: application/json; charset=utf-8 { "productId": "kalkhan-av-windows", "edition": "business", "seats": 25, "customer": { "name": "Örnek Bilişim A.Ş.", "email": "muhasebe@ornekbilisim.com.tr", "taxId": "1234567890", "taxOffice": "Ümraniye", "address": "Mithatpaşa Cd. No:116, Ümraniye/İstanbul" } }</code></pre> <h3>Ответ (201 Created)</h3> <pre><code style="background:#0F2453;color:#e6edff;padding:18px;border-radius:10px;overflow-x:auto;font-size:14px;line-height:1.6;display:block;">{ "key": "KLKN-7B10-CE45-2F93-D8A6", "status": "active", "product": "Kalkhan Anti-Virüs (Windows)", "edition": "business", "seats": 25, "amount": 34750.00, "currency": "TRY", "expiresAt": "2027-07-23T23:59:59Z" }</code></pre> <p><strong>При корпоративных продажах</strong> (когда <code>edition</code> равен <code>business</code> или <code>enterprise</code>) для выставления счёта <strong>обязательно</strong> передавать поля <code>customer.taxId</code> (налоговый номер), <code>customer.taxOffice</code> (налоговая инспекция) и <code>customer.address</code>. Если этих полей нет, запрос отклоняется с ошибкой <code>400 invalid_request</code>. Для частных (<code>home</code>) лицензий налоговые поля необязательны.</p> <p>Повторные запросы по одному и тому же клиенту и продукту за короткий промежуток времени получают ответ <code>409 duplicate</code>; чтобы исключить выпуск дублирующих лицензий, рекомендуем добавлять к запросу заголовок <code>Idempotency-Key</code>.</p> <h2 id="bayi-api" style="scroll-margin-top:120px;">Дилерский API</h2> <p>Для просмотра собственного портфеля лицензий и сводки по продажам дилерам доступны два эндпоинта.</p> <div class="table-responsive"> <table class="table" style="background:#ffffff;"> <thead> <tr><th style="width:14%;">Метод</th><th style="width:32%;">Эндпоинт</th><th>Описание</th></tr> </thead> <tbody> <tr><td><strong>GET</strong></td><td><code>/dealer/licenses</code></td><td>Постраничный список лицензий дилера</td></tr> <tr><td><strong>GET</strong></td><td><code>/dealer/stats</code></td><td>Сводка по числу продаж, обороту и ближайшим продлениям</td></tr> </tbody> </table> </div> <h3>Параметры постраничного вывода</h3> <div class="table-responsive"> <table class="table" style="background:#ffffff;"> <thead> <tr><th style="width:18%;">Параметр</th><th style="width:16%;">По умолчанию</th><th>Описание</th></tr> </thead> <tbody> <tr><td><code>page</code></td><td>1</td><td>Номер отображаемой страницы (нумерация с 1)</td></tr> <tr><td><code>limit</code></td><td>25</td><td>Количество записей на страницу (не более 100)</td></tr> <tr><td><code>status</code></td><td>&mdash;</td><td>Необязательный фильтр: <code>active</code>, <code>expired</code>, <code>cancelled</code></td></tr> </tbody> </table> </div> <h3>Запрос и ответ</h3> <pre><code style="background:#0F2453;color:#e6edff;padding:18px;border-radius:10px;overflow-x:auto;font-size:14px;line-height:1.6;display:block;">GET /v1/dealer/licenses?page=2&amp;limit=25&amp;status=active HTTP/1.1 Host: api.kalkhan.com.tr Authorization: Bearer klk_live_xxx { "data": [ { "key": "KLKN-4F2A-9C71-B3D8-A15E", "status": "active", "product": "Kalkhan Anti-Virüs (Windows)", "edition": "business", "seats": 25, "customer": "Örnek Bilişim A.Ş.", "expiresAt": "2027-03-14T23:59:59Z" } ], "pagination": { "page": 2, "limit": 25, "total": 143, "totalPages": 6 } }</code></pre> <p>Эндпоинт <code>GET /dealer/stats</code> возвращает сводку за период: общее число активных лицензий, количество продаж за месяц, суммарный оборот и число лицензий, истекающих в ближайшие 30 дней.</p> <h2 id="webhooks" style="scroll-margin-top:120px;">Webhooks</h2> <p>Чтобы мгновенно получать в свои системы изменения жизненного цикла лицензии, используйте вебхуки. Адрес назначения задаётся в дилерской панели; Kalkhan отправляет на него JSON-тело запросом <code>POST</code>.</p> <div class="table-responsive"> <table class="table" style="background:#ffffff;"> <thead> <tr><th style="width:34%;">Событие</th><th>Когда срабатывает</th></tr> </thead> <tbody> <tr><td><code>license.created</code></td><td>При создании новой лицензии</td></tr> <tr><td><code>license.expired</code></td><td>По истечении срока действия лицензии</td></tr> <tr><td><code>license.cancelled</code></td><td>При отмене лицензии или оформлении возврата</td></tr> </tbody> </table> </div> <h3>Пример тела события</h3> <pre><code style="background:#0F2453;color:#e6edff;padding:18px;border-radius:10px;overflow-x:auto;font-size:14px;line-height:1.6;display:block;">POST /webhooks/kalkhan HTTP/1.1 X-Kalkhan-Signature: t=1784937600,v1=9f8c1d2b7e4a63f05c8b1a9d4e7f2c30b6a5d8e1f4c7b0a3d6e9f2c5b8a1d4e7 Content-Type: application/json; charset=utf-8 { "id": "evt_01J9X2K7QF", "event": "license.created", "createdAt": "2026-07-23T09:41:02Z", "data": { "key": "KLKN-7B10-CE45-2F93-D8A6", "product": "Kalkhan Anti-Virüs (Windows)", "edition": "business", "seats": 25, "expiresAt": "2027-07-23T23:59:59Z" } }</code></pre> <h3>Проверка подписи</h3> <p>Каждый запрос вебхука подписывается заголовком <code>X-Kalkhan-Signature</code>. Подпись — это <strong>HMAC-SHA256</strong> от метки времени и сырого тела запроса, вычисленный на вашем секретном ключе вебхука. Обязательно проверяйте подпись до обработки запроса и отклоняйте метки времени старше 5 минут.</p> <pre><code style="background:#0F2453;color:#e6edff;padding:18px;border-radius:10px;overflow-x:auto;font-size:14px;line-height:1.6;display:block;">// Node.js — проверка подписи const crypto = require('crypto'); function dogrula(rawBody, header, secret) { const parts = Object.fromEntries( header.split(',').map(p =&gt; p.split('=')) ); const beklenen = crypto .createHmac('sha256', secret) .update(parts.t + '.' + rawBody) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(beklenen), Buffer.from(parts.v1) ); }</code></pre> <p>Если ваш сервер не вернёт ответ <code>2xx</code>, событие будет повторно отправлено не более 5 раз с нарастающими интервалами (1 мин, 5 мин, 30 мин, 2 ч, 12 ч). Исходите из того, что одно и то же событие может прийти несколько раз, и проверяйте повторы по полю <code>id</code>.</p> <h2 id="hata-kodlari" style="scroll-margin-top:120px;">Коды ошибок</h2> <p>Ошибки возвращаются со стандартными HTTP-статусами, а тело ответа содержит машиночитаемый <code>code</code> и поясняющий <code>message</code>.</p> <div class="table-responsive"> <table class="table" style="background:#ffffff;"> <thead> <tr><th style="width:14%;">HTTP</th><th style="width:26%;">Код</th><th>Описание</th></tr> </thead> <tbody> <tr><td><strong>400</strong></td><td><code>invalid_request</code></td><td>Параметр отсутствует или имеет неверный формат</td></tr> <tr><td><strong>401</strong></td><td><code>unauthorized</code></td><td>API-ключ не передан, недействителен или отозван</td></tr> <tr><td><strong>403</strong></td><td><code>forbidden</code></td><td>У ключа нет прав на этот эндпоинт</td></tr> <tr><td><strong>404</strong></td><td><code>not_found</code></td><td>Запрошенная лицензия или ресурс не найдены</td></tr> <tr><td><strong>409</strong></td><td><code>duplicate</code></td><td>Запись уже существует; дублирующий запрос отклонён</td></tr> <tr><td><strong>429</strong></td><td><code>rate_limited</code></td><td>Превышено ограничение частоты; дождитесь времени из заголовка <code>Retry-After</code></td></tr> <tr><td><strong>500</strong></td><td><code>server_error</code></td><td>Непредвиденная ошибка на стороне сервера</td></tr> </tbody> </table> </div> <h3>Пример ответа с ошибкой</h3> <pre><code style="background:#0F2453;color:#e6edff;padding:18px;border-radius:10px;overflow-x:auto;font-size:14px;line-height:1.6;display:block;">HTTP/1.1 400 Bad Request Content-Type: application/json; charset=utf-8 { "error": { "code": "invalid_request", "message": "Поле customer.taxId обязательно для корпоративных лицензий.", "field": "customer.taxId", "requestId": "req_01J9X2K7QF" } }</code></pre> <p>Указывайте значение <code>requestId</code> из ответа при обращении в поддержку — так проблему разберут быстрее.</p> <h2 id="sdk" style="scroll-margin-top:120px;">SDK и примеры</h2> <p>Официальные SDK находятся в разработке. Пока вы можете обращаться к API напрямую по HTTP. Ниже приведены варианты запроса на проверку лицензии для cURL и JavaScript.</p> <h3>cURL</h3> <pre><code style="background:#0F2453;color:#e6edff;padding:18px;border-radius:10px;overflow-x:auto;font-size:14px;line-height:1.6;display:block;">curl -X POST https://api.kalkhan.com.tr/v1/licenses/verify \ -H "Authorization: Bearer klk_live_xxx" \ -H "Content-Type: application/json; charset=utf-8" \ -d '{ "key": "KLKN-4F2A-9C71-B3D8-A15E", "deviceId": "WIN-8f2c41ab9d", "productId": "kalkhan-av-windows" }'</code></pre> <h3>JavaScript (fetch)</h3> <pre><code style="background:#0F2453;color:#e6edff;padding:18px;border-radius:10px;overflow-x:auto;font-size:14px;line-height:1.6;display:block;">const API_KEY = process.env.KALKHAN_API_KEY; // храните на стороне сервера async function lisansDogrula(key, deviceId) { const res = await fetch( 'https://api.kalkhan.com.tr/v1/licenses/verify', { method: 'POST', headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json; charset=utf-8' }, body: JSON.stringify({ key, deviceId, productId: 'kalkhan-av-windows' }) } ); if (!res.ok) { const { error } = await res.json(); throw new Error(`${error.code}: ${error.message}`); } return res.json(); // { status, product, edition, seats, expiresAt } } lisansDogrula('KLKN-4F2A-9C71-B3D8-A15E', 'WIN-8f2c41ab9d') .then(lisans =&gt; console.log(lisans.status, lisans.expiresAt)) .catch(err =&gt; console.error('Проверка не удалась:', err.message));</code></pre> <p>Эти примеры предназначены только для серверной части. Не размещайте API-ключ в коде, который выполняется в браузере; проводите запросы проверки от клиентских приложений через собственный бэкенд.</p> <!-- Uyarı Kutusu Start --> <div role="note" style="margin-top:40px;background:#FFF8E6;border:1px solid #F0D28A;border-left:5px solid #C9A227;border-radius:12px;padding:22px 24px;display:flex;gap:16px;align-items:flex-start;"> <span style="flex:0 0 auto;color:#C9A227;font-size:22px;line-height:1.2;"><i class="fa-solid fa-triangle-exclamation" aria-hidden="true"></i></span> <span style="color:#4a3d12;font-size:15px;line-height:1.7;"> <strong style="display:block;color:#0F2453;font-size:16px;margin-bottom:4px;">Закрытая бета</strong> API сейчас находится в стадии закрытой беты; чтобы получить доступ, напишите на <a href="mailto:bilgi@kalkhan.com.tr" style="color:#0F2453;font-weight:600;">bilgi@kalkhan.com.tr</a>. Во время беты в эндпоинтах возможны обратно несовместимые изменения. </span> </div> <!-- Uyarı Kutusu End -->