SentriKat Early Access is open: Cloud is free on the plan you choose, no credit card. Get started

What SentriKat sends home

A licensed SentriKat installation checks in with our license server every 12 hours. A Community Edition installation does not send it. Knowledge-base synchronization, described below, is a separate channel and applies to every edition that is not air-gapped. The license service answers on three names, license.sentrikat.com, api.sentrikat.com and portal.sentrikat.com: allow all three in your firewall. This page lists every field that check-in can carry, what each one is for, and how to read back exactly what we hold about your installation.

Last updated: 2026-10-09

The short version

From the license check-in: counters, versions and timestamps. From knowledge-base synchronization, unless you switch it off: the vendor and product names of software your installation could not identify, and the mappings you have confirmed. Never hosts, users, addresses or scan results. On-premises, scan results never leave your network. In the Cloud they are stored on our servers in the EU.

Every field, listed

This table is the contract, not a summary of it. What the server does with a field that is not on it depends on where the field is.

  • In the kb block, an unlisted field makes the server refuse the whole heartbeat with an error (HTTP 422). Inside a quality-signal entry, an unlisted key is discarded and logged instead.
  • At the top level of the heartbeat, and among the usage counters, an unlisted key is ignored: it is not read, it is not stored, and the heartbeat is still accepted.
  • In offline_bundle, an unlisted key is discarded and logged.
  • In quality, a block in any other schema is discarded, a metric the server cannot read is dropped and counted, and the metrics it can read are stored as they arrived.
  • Dataset names follow a separate rule, below.
Field Type What it is for Example
installation_id string Identifies which of your installations is checking in. Generated by your instance on first start. "SK-INST-7F3A9C21B4E05D8A6C13F27B90D4E5A8"
license_key string Authenticates the check-in. Your license, not your data. "SK-XXXX-XXXX-XXXX-XXXX"
license_id string (a UUID) Identifies the license in place of license_key. A check-in carries one or the other. Your license, not your data. "3f2b8c1e-5a7d-4c0e-9b1a-6d2f4e8a7c10"
edition string Which edition your instance believes it is running, so an upgrade applies without a re-activation. "professional"
app_version string Which version you are on. Support's first question, and how we know a fix has reached you. "1.4.2"
usage.agents integer How many agents are enrolled, checked against your seat count. A number, never a list. 42
usage.products integer How many products are tracked, checked against your plan. A number, never a list. 137
usage.organizations integer How many organizations the installation holds, checked against your plan. A number, never a list. The license service stores the agent and product counters. 3
usage.api_keys integer How many API keys exist, as a number, never the keys. The license service stores the agent and product counters. 5
kb.row_counts map of dataset to integer How many rows of each dataset your knowledge base holds. Lets us see that your instance has 40,000 vulnerabilities where it should have 300,000, without you running queries for us. Keys come from the fixed list below. { "vulnerabilities": 312904, "osv_advisories": 296118 }
kb.cursors map of dataset to timestamp How far each incremental sync has reached. A cursor that has not moved in a week is the single most common cause of a stale knowledge base, and it is invisible from our side without this. { "vulnerabilities": "2026-08-22T04:15:00Z" }
kb.last_sync_at timestamp When your instance last completed a sync. "2026-08-22T04:15:00Z"
kb.last_sync_status one of: ok, error, partial, never How that sync ended. A fixed set of words, never a message: an error message is free text, and free text is where a URL with a token in it ends up. "ok"
kb.last_sync_failed_dataset dataset name Which dataset failed, when one did. A name from the fixed list below, so support knows where to look. "osv_advisories"
kb.bundle_version string For air-gapped instances that load offline bundles instead of syncing. Asking them when they last synced is the wrong question; this is the right one. "2026-08-22"
kb.bundle_loaded_at timestamp When that bundle was loaded. "2026-08-22T06:02:00Z"
kb.oracle_signals one entry per internal quality check: {oracle_id, status, transition, at} Whether our own internal quality checks pass on your data. status is one of ok/warn/fail/stale/unknown; transition is one of none/became_bad/recovered, computed by your installation, never re-derived here; at is when the check measured, not when this heartbeat was sent, and null means it has never run. Never a check result's content, only its status. An entry can also carry age_days, next_due and overdue, declared by your installation: how old the last measurement is, when the next one is due, and whether it is late. { "oracle_id": "recall", "status": "ok", "transition": "none", "at": "2026-09-05T08:00:00Z" }
kb.oracle_signals_version integer Which version of the quality-signal format the entries above are in, so the shape can change later without either side guessing which one it is looking at. 1
offline_bundle.sequence integer The sequence number of the last offline bundle your installation imported. Only an installation that imports bundles reports it. 12
offline_bundle.outcome string How that import ended, as one of four words chosen by your installation: applicato (the bundle's sequence number was read and checked, so an older bundle would have been refused), assente (a bundle with no sequence number, normally an older one, so that check could not be made), illeggibile (the sequence number or the expiry date in the bundle could not be read: a defect on SentriKat's side, not in your installation, and the check was not made), inatteso (a recent bundle that should carry a sequence number and does not: a defect on SentriKat's side, and the check was not made). Stored as sent, cut at 64 characters. The last import only, replaced each time, never a history. An import that is refused is not reported here. "applicato"
offline_bundle.generated_at timestamp When that bundle was generated, as written in the bundle. Stored as sent, cut at 64 characters. "2026-10-07T06:00:00Z"
offline_bundle.imported_at timestamp When your installation imported it. Stored as sent, cut at 64 characters. "2026-10-08T09:12:00Z"
quality.schema string Which version of the data-quality format the block is in. The only value read is sentrikat.quality-index.v1: a block with any other value is discarded and nothing from it is stored. "sentrikat.quality-index.v1"
quality.misurato_a timestamp When the data quality was measured, not when this heartbeat was sent. "2026-09-23T08:45:00Z"
quality.metriche list of metrics One entry per measured dimension of the quality of your knowledge base, made of the keys listed next. Counts and names only, nothing about your machines or your software. An entry the server cannot read is dropped and counted; an entry it can read is stored as it arrived. [ { "nome": "copertura_identita", ... } ]
quality.metriche[].nome string The name of the dimension that was measured. "copertura_identita"
quality.metriche[].num number The numerator of the measurement. 680
quality.metriche[].den number The denominator. The ratio is computed on our side and a finished score is never sent. 1627
quality.metriche[].metodo one of: misurato, proxy, non_misurabile How the value was obtained: measured, estimated through a proxy, or not measurable. "misurato"
quality.metriche[].coperto_num number The numerator of the coverage: how much of the data the measurement could see. 1627
quality.metriche[].coperto_den number The denominator of the coverage: how much there was to see. 1627
quality.metriche[].critico boolean True when the sender marks this dimension as critical. false
quality.metriche[].ferme list of names The names of what has stopped or is late within this dimension, when anything has. []
quality.coda object of two integers The size of the review queue: righe is how many rows are in it, etichette_umane is how many of them a person has already decided. { "etichette_umane": 0, "righe": 732 }

The fixed list of datasets

row_counts, cursors and last_sync_failed_dataset accept these names and no others. Without a closed list, "a map of dataset names to timestamps" would be a map of anything to anything, and the sentence above it would mean nothing.

A name outside this list is discarded. It is never stored, and it does not cost the rest of the heartbeat: the fields around it are kept. Your instance is told in the response which names were discarded, so a partly accepted report cannot be mistaken for a complete one, and we record alongside your state how many were discarded, as a number and never as the names themselves.

Name What it holds
vulnerabilities CVE records
cpe_dictionary Software identities used to match your inventory
exploit_intel Known-exploited flags
epss Exploitation probability scores
distro_advisories Linux distribution security advisories
osv_advisories Open-source package advisories
curated_mappings Reviewed software-identity mappings
cna_ranges Vendor-declared affected version ranges

Knowledge-base synchronization

Every 12 hours, an installation of any edition that is not air-gapped synchronizes its knowledge base with ours. Each request carries the installation ID and the edition and, with a license, the first 64 characters of the license key. Our license service stores the mappings (first row) only from Professional licenses and refuses them from other editions; it stores the software identities (second row) from any valid license.

What Contents Default Switch off
Mappings you have learned Up to 500 per run: vendor_pattern, product_pattern, cpe_vendor, cpe_product, confidence, source, usage_count. On SENTRIKAT_KB_SHARE_MAPPINGS=false
Software your installation could not identify Up to 200 per run, only if the name has a CVE history: vendor, name, installs (a count) and cve_history (yes or no). No version, host or user. On The switch in the installation's interface, or SENTRIKAT_MAPPING_CONTRIBUTIONS=false
Mappings from the community and the curated ones Downloaded to your installation. Nothing about you is in the download. On SENTRIKAT_KB_SYNC_ENABLED=false (this also turns off the first row; it does not turn off the second)

What is never sent

Listed explicitly, because "we only collect what we need" is a sentence every vendor writes and none of them define.

  • Any CVE found on one of your machines
  • Any hostname, IP, or identifier of a machine you scanned
  • Any file path, configuration, or SBOM
  • Any user of your instance: no names, no email addresses, no credentials
  • Any log line, stack trace, or free-text error message
  • Any scan result, finding, ticket, or report

An example heartbeat

Nothing in this example is redacted: the values are made up and the field names are the real ones. It does not show these optional fields: license_id, usage.organizations, usage.api_keys, kb.last_sync_failed_dataset, kb.bundle_loaded_at, kb.oracle_signals, kb.oracle_signals_version, offline_bundle and quality. The table above lists every field.

POST https://api.sentrikat.com/api/v1/license/heartbeat

{
  "installation_id": "SK-INST-7F3A9C21B4E05D8A6C13F27B90D4E5A8",
  "license_key": "SK-XXXX-XXXX-XXXX-XXXX",
  "edition": "professional",
  "app_version": "1.4.2",
  "usage": {
    "agents": 42,
    "products": 137
  },
  "kb": {
    "row_counts": {
      "vulnerabilities": 312904,
      "cpe_dictionary": 1402883,
      "osv_advisories": 296118,
      "distro_advisories": 1469177
    },
    "cursors": {
      "vulnerabilities": "2026-08-22T04:15:00Z",
      "osv_advisories": "2026-08-22T03:25:00Z"
    },
    "last_sync_at": "2026-08-22T04:15:00Z",
    "last_sync_status": "ok",
    "bundle_version": null
  }
}

Read back what we hold

The table above says what we collect. This command says what we actually have, for your installation, right now. Run it whenever you like; it needs no new credential beyond your license key and your installation ID.

curl -sS https://api.sentrikat.com/api/v1/license/telemetry \
  -H 'Content-Type: application/json' \
  -d '{"license_key":"YOUR-LICENSE-KEY","installation_id":"YOUR-INSTALLATION-ID"}'

Besides what the payload carried, the response shows what the license service records about the installation itself: its edition and version, when it was activated and last seen, the address its requests came from, and a hostname and operating system description only if a client has provided them (the current SentriKat release provides neither), the report of the last offline bundle, and the times at which the knowledge-base state and that report were received. The data-quality reports are not in this response: they are kept apart, under the installation ID alone. They are deleted 180 days after we receive them, and when the account is deleted (unless the same installation ID also appears on another customer's account); freeing a seat does not delete them. To see them, write to [email protected].

If the response contains something this page does not list, this page is what is wrong. Tell us at [email protected] and we will correct it.

Turning it off

The license check-in itself cannot be disabled: it is how a license stays valid, and it is processing necessary to perform our contract with you (Art. 6(1)(b) GDPR). Knowledge-base synchronization can be switched off, with the settings in the table above. Air-gapped instances send nothing at all, by construction, and are licensed offline.

Retention, your rights, and who processes what are covered in the Privacy Policy and the DPA.