Skip to content

License Activation API

API endpoints for license activation, used by SentriKat instances to validate and activate licenses.

Online Activation

Exchange an activation code for a hardware-locked signed license key.

POST /api/v1/license/activate
Content-Type: application/json

Request Body

Field Type Required Description
activation_code string Yes Activation code (format: SK-XXXX-XXXX-XXXX-XXXX)
installation_id string Yes Installation ID from SentriKat (format: SK-INST-<64-char-hex>)
app_version string No SentriKat application version (e.g. 1.3.0)

Example Request

curl -X POST https://portal.sentrikat.com/api/v1/license/activate \
  -H "Content-Type: application/json" \
  -d '{
    "activation_code": "SK-A1B2-C3D4-E5F6-G7H8",
    "installation_id": "SK-INST-abc123def456...",
    "app_version": "1.3.0"
  }'

Response (200 OK)

{
  "license_key": "eyJsaWNlbnNlX2lkIjoi...base64payload.base64signature"
}

The license_key is a signed license string in the format base64(JSON_payload).base64(RSA_signature). The SentriKat instance verifies this signature using its embedded RSA public key.

Error Responses

Status Description
400 Invalid activation code format or installation ID format
404 Activation code not found
409 Activation code has already been used
410 Activation code has expired
429 Rate limit exceeded (10 attempts/hour/IP)
500 License signing unavailable

Rate Limiting

  • 10 activation attempts per hour per IP address
  • Applies to both successful and failed attempts
  • Returns 429 Too Many Requests when exceeded

Security Notes

  • Activation codes are validated against the pattern ^[A-Za-z0-9\-]+$ (8-128 characters)
  • Installation IDs must start with SK-INST-
  • All activation attempts are logged (code, installation_id, IP, success/failure)
  • One activation code = one installation (single use)
  • Codes expire 90 days after purchase (configurable)

Heartbeat

Periodic check-in from active SentriKat instances.

POST /api/v1/heartbeat
Content-Type: application/json

Request Body

Field Type Required Description
license_key string Yes The license key
installation_id string Yes Installation ID

Response (200 OK)

{
  "status": "ok",
  "timestamp": "2026-02-08T12:00:00"
}

Update Check

Check for available SentriKat updates. Called periodically by SentriKat installations.

GET /api/v1/releases/latest

No authentication required — just the installation ID header (same one used for heartbeats).

Request Headers

Header Required Description
X-Installation-ID Yes Installation ID (e.g. SK-INST-abc123...)
X-App-Version No Current SentriKat version (e.g. 1.0.0-beta.4)

Example Request

curl https://portal.sentrikat.com/api/v1/releases/latest \
  -H "X-Installation-ID: SK-INST-abc123def456..." \
  -H "X-App-Version: 1.0.0-beta.4"

Response (200 OK)

{
  "version": "1.0.0-beta.5",
  "download_url": "https://portal.sentrikat.com/api/v1/releases/1.0.0-beta.5/download",
  "image": "ghcr.io/sbr0nch/sentrikat:1.0.0-beta.5",
  "released_at": "2026-02-16T12:00:00+00:00",
  "release_notes": "Bug fixes and performance improvements",
  "update_available": true
}

Response (204 No Content)

Returned when no releases exist yet.

Notes

  • update_available is true when the latest version is newer than X-App-Version
  • If X-App-Version is not sent, update_available defaults to false
  • The portal logs each update check for analytics (installation, version, IP)
  • image is the Docker image URL — use this for Docker-based deployments
  • download_url is the tar.gz package URL — for offline/air-gapped deployments

Important: Private Repository Downloads

If the SentriKat GitHub repository is private:

  • Docker images (image field): Work if GHCR package visibility is set to public (independent of repo visibility)
  • Release assets (download_url): GitHub release download URLs require authentication for private repos. Either:
    • Host the tar.gz on the portal server (/opt/sentrikat/downloads/)
    • Or set the download URL to the portal's own download endpoint

Verify Signed License

Verify a signed license string from the portal (used by SentriKat instances).

POST /api/v1/verify-signed
Content-Type: application/json

Request Body

Field Type Required Description
signed_license string Yes The base64(payload).base64(signature) string
installation_id string Yes The requesting installation's ID

Response (200 OK)

{
  "valid": true,
  "message": "License is valid",
  "edition": "pro",
  "customer": "Acme Corp",
  "limits": {
    "max_users": null,
    "max_organizations": null,
    "max_products": null,
    "max_agents": 10
  },
  "features": ["all", "ldap", "sso", "webhooks", "api"],
  "expires_at": "2027-02-08T00:00:00",
  "issued_at": "2026-02-08T00:00:00"
}

Validation Rules

  1. Signature must be valid RSA-4096 (PKCS1v15 + SHA256)
  2. License must exist in the database (revoked licenses are rejected)
  3. License must be ACTIVE status
  4. License must not be expired
  5. Installation ID in the payload must match the requesting installation
  6. Max activation limit is enforced

License Payload Format

The signed license payload contains:

{
  "license_id": "uuid",
  "license_key": "SK-PRO-XXXX-XXXX",
  "customer": "Company Name",
  "email": "[email protected]",
  "edition": "pro",
  "installation_id": "SK-INST-...",
  "issued_at": "2026-02-08T12:00:00",
  "expires_at": "2027-02-08T12:00:00",
  "limits": {
    "max_users": null,
    "max_organizations": null,
    "max_products": null,
    "max_agents": 10,
    "max_agent_api_keys": 10
  },
  "features": ["all", "ldap", "sso", "webhooks", "api"]
}

The payload is JSON-encoded, base64url-encoded, then RSA-signed. The full license string is:

base64url(json_payload).base64url(rsa_signature)