Быстрый старт
Сквозной путь: компания → шаблон → версия → выпуск лицензии → интеграция SDK. Команды проверены против API на http://localhost:5100 с seed-оператором admin@certified.local / changeme.
Тарҷумаи тоҷикии ҳуҷҷатҳо дар ҳоли омодашавӣ аст. Матни зерин ба забони русӣ оварда шудааст.
0. Предпосылки
- CertifiEd API запущен и поднят с
CERTIFIED_MASTER_KEY(base64, 32 байта). - Установлены
curlи — для HMAC-подписи heartbeat —openssl.
BASE=http://localhost:5100
curl -s $BASE/health# {"status":"ok", ...}1. Логин оператора
Panel API работает по HttpOnly-cookie certified.auth; сохраняем её в cookie-jar cj.txt.
curl -s -c cj.txt -X POST $BASE/api/v1/panel/auth/login \ -H 'Content-Type: application/json' \ -d '{"email":"admin@certified.local","password":"changeme"}'{ "id": "019f69e4-2a5d-7c3c-bb39-93d6829d92a5", "email": "admin@certified.local", "displayName": "Operator", "isOperator": true}Дальше во все panel-запросы добавляем -b cj.txt.
2. Компания-лицензиат
Корневую компанию создаёт оператор (parentId: null).
curl -s -b cj.txt -X POST $BASE/api/v1/panel/companies \ -H 'Content-Type: application/json' \ -d '{"name":"Globex Inc","slug":"globex-inc","parentId":null, "contactEmail":"it@globex.example"}'{ "id": "019f6b45-927c-7c2b-978c-91e7689fe7f3", "name": "Globex Inc", "slug": "globex-inc", "parentId": null, "path": "c019f6b45927c7c2b978c91e7689fe7f3", "depth": 0, "status": "Active", "contactEmail": "it@globex.example", "createdAt": "2026-07-16T14:12:29.43+00:00"}Запомним id как COID. slug — 1–64 символа [a-z0-9-], не начинается и не заканчивается дефисом, уникален среди сиблингов.
3. Шаблон продукта
Создание шаблона автоматически генерирует первую активную Ed25519 signing-key.
curl -s -b cj.txt -X POST $BASE/api/v1/panel/templates \ -H 'Content-Type: application/json' \ -d '{"name":"Curl Demo","productCode":"curl-demo","description":"demo", "defaultOfflineDays":7,"defaultValidityDays":365}'{ "id": "019f6b45-92bf-758a-8955-131b7c9cc364", "name": "Curl Demo", "productCode": "curl-demo", "defaultOfflineDays": 7, "defaultValidityDays": 365, "status": "Active", "currentVersionId": null, "createdAt": "2026-07-16T14:12:29.50+00:00"}Запомним id как TPLID. currentVersionId пока null — версии ещё нет.
4. Версия шаблона
Версия несёт JSON-схему конфигурации и defaults. configSchema и defaults — строки JSON (внутренние кавычки экранируются). Созданная версия сразу становится текущей.
curl -s -b cj.txt -X POST $BASE/api/v1/panel/templates/$TPLID/versions \ -H 'Content-Type: application/json' \ -d '{"configSchema":"{\"type\":\"object\"}", "defaults":"{\"features\":[]}","changelog":"v1"}'{ "id": "019f6b45-92f8-71e5-bd7a-aa95cf5644a1", "templateId": "019f6b45-92bf-758a-8955-131b7c9cc364", "version": 1, "configSchema": "{\"type\":\"object\"}", "defaults": "{\"features\":[]}", "signingKeyId": "019f6b45-92c3-7a09-8bdf-24b8ef0b8ba5", "changelog": "v1", "createdAt": "2026-07-16T14:12:29.56+00:00"}5. Выпуск лицензии
Ключевой момент: поле config — это строка JSON, которая встраивается в подписанный токен. Именно её потом читают HasFeature() и GetConfig<T>(). Здесь кладём фичи export / api и лимит limits.maxSeats = 25.
curl -s -b cj.txt -X POST $BASE/api/v1/panel/licenses \ -H 'Content-Type: application/json' \ -d '{ "companyId": "'"$COID"'", "templateId": "'"$TPLID"'", "config": "{\"features\":[\"export\",\"api\"],\"limits\":{\"maxSeats\":25}}", "offlineDays": 7 }'{ "id": "019f6b45-9312-7c41-b1fb-2e2539eaac9d", "licenseKey": "CFED-3NCR-4ZER-BAEA-YREY", "token": "eyJhbGciOiJFZDI1NTE5Iiwia2lkIjoi...<three segments>...", "publicKey": "67619cd9aac91085cb5be3b93a9fbca97e5b06c21fcef6a7fdf3b182e16487d8"}expiresAt не задали → срок = now + defaultValidityDays шаблона. publicKey отдаётся в hex. Запомним licenseKey как KEY, id — как LICID.
6. Скачать .ced и публичный ключ
# License file: JSON envelope { licenseKey, token, publicKey }curl -s -b cj.txt $BASE/api/v1/panel/licenses/$LICID/download -o license.ced
# Public key on its own (hex)curl -s -b cj.txt $BASE/api/v1/panel/licenses/$LICID/public-key# {"publicKey":"67619cd9aac9...87d8"}license.ced можно отдать клиентскому приложению как есть — SDK читает и JSON-конверт, и «голый» токен одной строкой.
7. Клиентская сторона: SDK
Обычно проверкой занимается SDK — он делает всё из шагов 8–9 самостоятельно.
using CertifiEd.Client;
var options = new CertifiEdClientOptions{ ServerUrl = "http://localhost:5100", LicenseFilePath = "license.ced", PublicKey = Convert.FromHexString( "67619cd9aac91085cb5be3b93a9fbca97e5b06c21fcef6a7fdf3b182e16487d8"),};
await using var license = new CertifiEdLicenseClient(options);if (!await license.InitializeAsync()) return; // missing file / bad signature / expired
Console.WriteLine(license.Status); // ActiveConsole.WriteLine(license.HasFeature("export")); // TrueConsole.WriteLine(license.GetConfig<int>("limits.maxSeats")); // 25Детали — в разделе Клиентский SDK. Ниже — те же клиентские вызовы «руками», через client API.
8. Активация (client API, без HMAC)
activate использует сам license key как общий секрет — HMAC-подпись не нужна.
curl -s -X POST $BASE/api/v1/client/activate \ -H 'Content-Type: application/json' \ -d '{"licenseKey":"'"$KEY"'","hwFingerprint":"demo-fp-001", "machineName":"demo-host"}'{ "activationId": "019f6b45-9362-78f1-aec3-1e3e105a3bf8", "heartbeatToken": "eyJ0eXAiOiJjZXJ0aWZpZWQt...", "heartbeatIntervalSeconds": 3600, "maxOfflineDays": 7}heartbeatToken — подписанный offline-маркер; SDK кэширует его рядом с лицензией как license.ced.hb. Запомним activationId как ACTID.
9. Heartbeat (HMAC-подпись)
heartbeat и deactivate подписываются: X-CertifiEd-Signature: v1=hex(HMAC_SHA256(key = licenseKey, msg = timestamp)), где timestamp — Unix-секунды, окно ±5 минут.
TS=$(date +%s)SIG=$(printf '%s' "$TS" | openssl dgst -sha256 -hmac "$KEY" -hex | sed 's/^.*= //')
curl -s -X POST $BASE/api/v1/client/heartbeat \ -H 'Content-Type: application/json' \ -H "X-CertifiEd-Timestamp: $TS" \ -H "X-CertifiEd-Signature: v1=$SIG" \ -d '{"licenseKey":"'"$KEY"'","activationId":"'"$ACTID"'"}'{ "heartbeatToken": "eyJ0eXAiOiJjZXJ0aWZpZWQt..." }HTTP 200 и свежий маркер. Деактивация выполняется так же, но через POST /api/v1/client/deactivate — вернёт 204.
Проверка со стороны панели
curl -s -b cj.txt $BASE/api/v1/panel/licenses/$LICID/activationscurl -s -b cj.txt "$BASE/api/v1/panel/licenses/$LICID/heartbeats?limit=10"Что дальше
- Клиентский SDK — интеграция в своё приложение.
- Протокол лицензий — формат токена, маркер, grace-период.
- API Reference — полный справочник эндпоинтов.
- Webhooks — уведомления о событиях лицензий.