# Introduction

Make Kernel responsible for data quality. Grow confidently with data you trust.

{% embed url="<https://pub-8e1d08f439ec43bdbb79f1055a273a02.r2.dev/kernel-introduction-kernel-ai-4a4ca76a.mp4>" %}

Data accuracy is the foundation for growth. Territory planning, AI automations, and cross-functional workflows between go-to-market and finance all depend on one thing: knowing exactly which entities are in your market, how they relate to each other, and whether your systems reflect that accurately.&#x20;

Before Kernel, our customers used to buy enrichment data, hire humans to fix it, and maybe try to plug the gaps with AI. The result was inaccurate data, stressed humans, and missed opportunities. Kernel solves that.

<figure><img src="/files/JnkPnYGzLtAyWXo5AgDC" alt="A Salesforce account record with five data quality issues flagged: wrong account identity, malformed website, wrong LinkedIn, missing parent, and a duplicate account"><figcaption><p>Most Account records have one or more data quality issues</p></figcaption></figure>

## What Kernel is

#### How it works:

Kernel is entity data guaranteed to be accurate. Kernel resolves every record in your system of record to a unique and persistent KERN ID. It then enriches the records with hierarchical linkages and firmographic data - sourced and verified by agents trained on hard edge cases. If you find an error, Kernel handles errors end to end, ensuring your systems are accurate at all times.

### What that means in practice:

1. Hierarchies and firmographic data that reflects how you think about your market, not overly granular legal entities or overly simplified domain-based accounts.
2. Duplicates resolved and parent-child relationships corrected at scale.
3. Every change is risk-scored, auditable, and applied on your terms.

### Why Kernel is different:

Unlike traditional data providers, Kernel combines the consistency and coverage of a database with the data accuracy from agentic research. Delivered in the systems where you work with enterprise-grade APIs, change management tools, and accuracy SLAs.\
\
Imagine you had expert humans continuously fix and enrich every record. That's Kernel.


# KERN ID

Kernel's entity database is anchored on a unique, persistent ID that covers every entity of every kind.

{% embed url="<https://pub-8e1d08f439ec43bdbb79f1055a273a02.r2.dev/kern-id-cbfe1b3e.mp4>" %}

The Kernel Entity Reference Number (KERN) is a proprietary 10-digit ID that uniquely and persistently identifies a real-world entity.

A KERN ID answers a simple question: **which entity does this CRM record actually mean?** It is not just a cleaned website, company name, or parent account. Kernel uses the available account evidence to decide the correct entity boundary, then assigns or reuses the KERN ID for that exact entity.

Two records should share a KERN ID when they refer to the same entity at the same level. Two records should have different KERN IDs when they refer to different levels of the same family, such as a holding company, an operating subsidiary, a brand, or a physical branch.

<figure><img src="/files/GE9SRRfEbuQ86vnf4xya" alt="Five inconsistent CRM records for the same company - entered as Google, GOOGLE, google, and the domain itself, with varied or missing websites - all resolve through Kernel to a single KERN ID: 9237577900, the operating company Google LLC in the United States. A different level, such as the parent Alphabet Inc., gets its own KERN ID."><figcaption><p>The same company arrives in your CRM many ways; Kernel resolves every version to one persistent KERN ID. A different level - the parent Alphabet Inc., a brand, or a branch - gets its own.</p></figcaption></figure>

## How Kernel decides the entity

Before assigning a KERN ID, Kernel reviews the signals available on and around the CRM record, such as the company name, website, legal name, country, LinkedIn URL, address, related domains, parent relationship, and opportunity context.

Kernel then uses [entity resolution](/concepts/entity-resolution) to resolve the record into a consistent identity:

| Dimension                                | What it answers                                      | Example                                                    |
| ---------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------------- |
| Legal anchor                             | Which legal entity is responsible for this identity? | Kraft Heinz Company                                        |
| Country of incorporation or registration | Where is that legal entity registered?               | US                                                         |
| Trading identity                         | What name or website does the market recognize?      | Jell-O                                                     |
| Canonical website                        | What is the URL?                                     | [kraftheinz.com/jell-o](https://www.kraftheinz.com/jell-o) |
| Entity classification                    | What kind of entity is it?                           | Company / Business Unit                                    |

The KERN ID itself is just the persistent identifier. [Core entity data](/data/entity-data/core-entity-data) holds the fields that explain what the identifier represents.

## Legal and trading identities

A legal entity is an incorporated or registered organization that can contract in its own name. Examples include **Google LLC**, **Salesforce, Inc.**, or **Kraft Heinz Company**.

A trading identity is a real market-facing identity that operates through a legal entity. This can be a brand, division, business unit, academic unit, or physical establishment. These can receive their own KERN IDs when the CRM record clearly refers to them as the account being tracked.

Examples:

| CRM record means...                | KERN ID represents...    | Why                                                         |
| ---------------------------------- | ------------------------ | ----------------------------------------------------------- |
| Alphabet Inc.                      | Alphabet Inc.            | The holding company itself                                  |
| Google                             | Google                   | The operating company or main operating brand, not Alphabet |
| Jell-O                             | Jell-O                   | A brand/business unit under Kraft Heinz                     |
| Lloyds Bank - London Bridge branch | The London Bridge branch | A specific establishment, not the whole bank                |

Hierarchy connects these entities together. It does not collapse them into one ID. A parent, subsidiary, brand, and branch can each have their own KERN ID when they represent distinct account identities.

Entity type and role live in [Entity categories](/data/entity-data/entity-categories#categories-and-sub-categories), including company sub-categories such as Operating, HoldCo / Investment, Business unit, and Establishment.

## Reuse and persistence

If Kernel has already seen the same entity, it reuses the existing KERN ID. If the record represents a new entity, Kernel creates a new KERN ID for that entity.

The KERN ID should stay stable when superficial details change, such as punctuation, formatting, a redirected website, or a corrected company name. If the record is later found to refer to a different entity, it should be linked to that other entity's KERN ID instead.

{% hint style="warning" %}
**Limitations**

Every KERN ID must ultimately be tied to a legal entity. Kernel can represent sub-legal entities such as brands, business units, branches, sites, hotels, plants, divisions, and academic units, but each is still connected to its closest responsible legal entity.

Records with no credible organization or legal-entity association, such as a personal blog with only an individual's email address, would not receive a KERN ID.
{% endhint %}

<br>


# Entity resolution

Kernel's proprietary entity resolution mechanism intelligently matches records in your system record with Kernel's database similarly to how a human would do it - with full context

{% embed url="<https://pub-8e1d08f439ec43bdbb79f1055a273a02.r2.dev/entity-resolution-7c94d46f.mp4>" %}

## Problem: Account identity mismatches

All systems of record suffer from master entity data that is inconsistent, incorrect, or stale, all of which may lead to ambiguity when matching to a canonical entity database like Kernel's.

Traditional vendors rely on simple look-up fields against a static database. These lookup fields are usually the company's website or name.

<table><thead><tr><th width="118.4375">Name</th><th width="128.7578125">Website</th><th>Ambiguity</th></tr></thead><tbody><tr><td>Pepsi</td><td>fritolay.com</td><td>Which company is this? Pepsi or Fritolay? Fritolay is a subsidiary of Pepsi, but is operating on its own.</td></tr><tr><td>Dove</td><td>unilever.com</td><td>Dove is a brand within Unilever, not a legal entity on its own. But some GTM teams may still want this to resolve to Dove.</td></tr><tr><td>Oracle</td><td></td><td>This is likely the Oracle Corporation, but it may also refer to a Vancouver-based investment company, which is also called Oracle. Or it may refer any of the Oracle Corporation's subsidiaries.</td></tr><tr><td>Kernel</td><td></td><td>This could refer both to Kernel, the UK-based startup building an entity database of all companies in the world, or to the US-based neuroscience startup founded by Bryan Johnson</td></tr></tbody></table>

As a human, to resolve this you would look at peripheral data in the system:

* Are there alternative names and websites associated with the record? e.g., if the Kernel record has 'kernel.ai' written somewhere, this helps narrow the confusion
* Is there an address? Pepsi is headquartered in New York, whereas Fritolay is in Texas
* What contacts are associated with the record? If all contact emails end in "oracle.com", this is likely the Oracle Corporation. But if all contacts are located in Spain, it is more likely to be Oracle Ibérica, S.R.L., the Spanish regional subsidiary of Oracle
* Which notes have previous users left behind? Previous opportunity and deal names may show the user's intent ("Dove | Pilot - Closed Lost") indicates the rep thought of the record as a business unit rather than the legal entity itself

## Reasoning-based entity resolution

A customer record may have a mix of information in its core fields, describing it as both PepsiCo and Frito-Lay. This means that a simple name/website lookup does not guarantee that we match it to the KERN ID that the customer intended.

To resolve this, Kernel's entity resolution reviews internal and external data related to the account - from the name and website, to the address, alternative domains, existing parent relationship, related contact domains, associated opportunity names, and more.

**Internal data (from your CRM)**

This is the data Kernel reads from your system of record. The table groups the inputs by type and shows where each one usually lives in Salesforce as an example.

If you store a value somewhere else (for example, a custom "Website" field), point Kernel at that field instead. Only the account name and website are required; every other field improves accuracy where it is available.

| Field type                    | Salesforce field (example)                              | Required? |
| ----------------------------- | ------------------------------------------------------- | --------- |
| Account name                  | `Account.Name`                                          | Required  |
| Website / domain              | `Account.Website`                                       | Required  |
| Legal name                    | Custom `Account` field                                  | Optional  |
| Alternative names & websites  | Custom `Account` field                                  | Optional  |
| LinkedIn company URL          | Custom `Account` field                                  | Optional  |
| Account / billing emails      | Custom `Account` field                                  | Optional  |
| Billing / shipping address    | `Account.BillingAddress` (street, city, state, country) | Optional  |
| Related contact email domains | `Contact.Email` on related contacts                     | Optional  |
| Related opportunity names     | `Opportunity.Name` on related opportunities             | Optional  |

{% hint style="info" %}
This is a conceptual summary of the inputs used for identity resolution. The full field-by-field Salesforce requirements - access, related objects, and write-back - are covered separately in the [Salesforce integration](/integrations/salesforce-integration) guides.
{% endhint %}

**External data (gathered by Kernel)**

Kernel does not rely only on what is already in your CRM. It also gathers corroborating evidence on its own to confirm or challenge the internal data.

| Source                                     | What it provides                                                                                        |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------- |
| Company website                            | Kernel crawls the website listed on the record to confirm the entity and pull supporting detail.        |
| Corporate registries & third-party sources | Kernel researches official registries and other external sources to disambiguate and verify the entity. |

Kernel weighs all of these inputs together on the assumption that not all of them are correct or internally consistent, which is why it resolves conflicts through reasoning rather than trusting any single field.

{% hint style="warning" %}
More input data usually helps with difficult or ambiguous records - but only when it is trustworthy. A field that is confidently wrong can make resolution harder, not easier. For example, an address populated by another data vendor may point to the wrong location and pull the match toward the wrong entity. When a field is known to be unreliable, it is better to leave it out than to feed it in.
{% endhint %}

## Bias modes

When a record's name and website point to different companies and nothing else is decisive (see example below), which should Kernel lean on? That usually depends on how the CRM was built and maintained - where the records came from, and which field the team kept clean.

Kernel treats this as a bias, not a hard rule.

<figure><img src="/files/WpcPK43DdbnxUdNzh8kQ" alt="A CRM record where the name is Pepsi but the website is fritolay.com, with no other decisive evidence. Under URL bias (the default) Kernel leans on the website and resolves to Frito-Lay; under Name bias it leans on the name and resolves to PepsiCo. A bias only breaks the tie and is not a hard rule."><figcaption><p>The same ambiguous record resolves differently depending on the bias - but a bias only breaks the tie.</p></figcaption></figure>

* **URL bias** (default) - lean on the website. Best when records came from web forms, enrichment, or email domains, where the domain is the reliable key, or when a previous vendor has been used that keys on domain.
* **Name bias** - lean on the company name as the strongest statement of what the record was meant to represent. Best when reps typed account names by hand or when a previous vendor has been used that keys on company name.

A bias only breaks a tie. When the address, legal name, LinkedIn, or contact domains clearly corroborate one entity, that evidence wins.

## Entity resolution output

The output of the analysis is a KERN ID, all its component fields, as well as a human-readable reasoning justifying the decision and a confidence level, which is either High, Medium, or Low.

"Low" confidence means there was latent ambiguity, i.e., not enough information to singularly establish the correct identity without doubt. Resolving "Oracle" to the Oracle Corporation is an example of this; most humans would agree that this is sensible, but strictly speaking there are more companies in the world named Oracle.

## Identity viewer (app)

The identity viewer in the Kernel app gives you direct visibility into the resolution process. For each account, you can inspect what Kernel has resolved the identity to and the reasoning behind it - helping you verify the match before taking any action.


# Hierarchy relation taxonomy

How Kernel labels each account's position in its corporate tree.

Every account in your CRM sits somewhere in a corporate tree. The **Hierarchy Relation** field tells you where. It's a single label, picked from eight positions, that reps can sort on, route with, and roll up against.

The taxonomy works the same way across Company, Government, and Education trees. You don't need to learn a separate model for each.

## Hierarchy positions

Every account gets exactly one label. When more than one could apply, the highest priority wins.

| Priority | Label                               | Company tree                                  | Government tree                       | Education tree                                                 |
| -------- | ----------------------------------- | --------------------------------------------- | ------------------------------------- | -------------------------------------------------------------- |
| 1        | **GUP** · Global Ultimate Parent    | Topmost HoldCo or Operating entity            | National Government, IGO              | Education System, standalone HEI                               |
| 2        | **GOP** · Global Operating Parent   | Topmost Operating entity (when GUP is HoldCo) | -                                     | -                                                              |
| 3        | **DUP** · Domestic Ultimate Parent  | Highest entity in its country                 | National Government below an IGO      | -                                                              |
| 4        | **DOP** · Domestic Operating Parent | Highest Operating entity in its country       | -                                     | -                                                              |
| 5        | **Subsidiary**                      | Operating subsidiary                          | Agency, Subnational, Local, Judiciary | Higher-Ed Institution, Pre-tertiary school, Research Institute |
| 6        | **Business Unit**                   | Brand or division inside an Operating entity  | -                                     | Academic Unit                                                  |
| 7        | **Establishment**                   | Branch, office, or site                       | -                                     | -                                                              |
| 8        | **Standalone**                      | Independent company                           | Independent agency                    | Independent institution                                        |

{% hint style="info" %}
**Government and Education don't split Operating vs HoldCo.** That distinction only exists for companies, so `GOP` and `DOP` are skipped for those trees. The classifier jumps straight from `GUP` to `DUP` to `Subsidiary`.
{% endhint %}

## Precedence rules

* Highest priority label wins. An account that qualifies for both `GUP` and `GOP` gets `GUP`.
* `GUP` is always the topmost entity in the tree, even when that entity is Operating (in which case `GOP` doesn't fire).
* `GOP` only exists when the `GUP` is a HoldCo and the tree is a company tree.
* `DUP` only appears when the highest entity in its country differs from the `GUP` or `GOP`.
* `DOP` only exists when the `DUP` is a HoldCo and the tree is a company tree.
* For Government and Education, the classifier skips `GOP` and `DOP` entirely.
* Ties are broken deterministically so the same tree always produces the same labels.

## How classification works

For every account, Kernel walks down this checklist and stops at the first match.

1. Is this the top of the tree? If yes, it's the **GUP**.
2. Is this an Operating company sitting below a HoldCo `GUP`? **GOP**.
3. Is this the highest account in its country, and that country is different from the `GUP`'s country? **DUP**.
4. Is this an Operating company sitting below a HoldCo `DUP`? **DOP**.
5. Is this a physical site (branch, office, plant)? **Establishment**.
6. Is this a brand or academic unit that isn't a legal entity? **Business Unit**.
7. Otherwise, **Subsidiary**.

If the account has no parent in the tree at all, it's a **Standalone**.

## Edge cases

| Case                                                            | What happens                                                   |
| --------------------------------------------------------------- | -------------------------------------------------------------- |
| No parent account anywhere in the tree                          | Labelled `Standalone`                                          |
| The account is the top of its own tree                          | Immediate `GUP`, no further walk needed                        |
| No country on the account                                       | Can't be `DUP` or `DOP`, falls through to `Subsidiary`         |
| Two accounts tied for "highest in country"                      | Deterministic tiebreak, same result every run                  |
| A Government or Education account is tagged HoldCo or Operating | Treated as Operating equivalent, `GOP` and `DOP` still skipped |
| Sub-category we don't recognize                                 | Defaults to `Subsidiary`                                       |

## Examples

Each node shows the entity's **sub-category** and **country**, then the label the classifier assigns. Shading by position: darker sage for the `GUP`, lighter sage for other parent positions, white for leaf positions.

### PE-backed insurance group

The canonical complex case. Every commercial position fires somewhere in this tree.

```mermaid
flowchart TD
  PS["<b>Pollen Street Capital</b><br/>HoldCo · GB<br/>GUP"]:::gup
  MG["<b>Markerstudy Group</b><br/>Operating · GB<br/>GOP"]:::gop
  MIS["<b>Markerstudy Insurance Svcs</b><br/>Operating · GB<br/>Subsidiary"]:::leaf
  MDE["<b>Markerstudy Deutschland</b><br/>HoldCo · DE<br/>DUP"]:::dup
  MDO["<b>Markerstudy DE Operations</b><br/>Operating · DE<br/>DOP"]:::dop
  MBE["<b>Markerstudy Berlin</b><br/>Establishment · DE<br/>Establishment"]:::leaf
  MMU["<b>Markerstudy Munich</b><br/>Establishment · DE<br/>Establishment"]:::leaf
  MES["<b>Markerstudy Spain</b><br/>Operating · ES<br/>DUP"]:::dup

  PS --> MG
  MG --> MIS
  MG --> MDE
  MG --> MES
  MDE --> MDO
  MDE --> MMU
  MDO --> MBE

  classDef gup fill:#b5c4b6,stroke:#343539,stroke-width:1.5px,color:#343539
  classDef gop fill:#d5e2d6,stroke:#98a59c,stroke-width:1px,color:#343539
  classDef dup fill:#e2ede3,stroke:#98a59c,stroke-width:1px,color:#343539
  classDef dop fill:#f0f6f1,stroke:#98a59c,stroke-width:1px,color:#343539
  classDef leaf fill:#ffffff,stroke:#c8d5c9,stroke-width:1px,color:#343539
  linkStyle default stroke:#98a59c,stroke-width:1.5px
```

Markerstudy Insurance Services is a `Subsidiary`, not a `DUP`, because the `GOP` is already in GB. `DUP` requires a different country from the `GUP` or `GOP`. Markerstudy Spain has a single Operating entity in its country, so it takes `DUP` (priority 3) ahead of `DOP` (priority 4).

### Swiss multinational with Operating at the top

When the topmost entity is already Operating, `GUP` and `GOP` collapse into one.

```mermaid
flowchart TD
  N["<b>Nestlé S.A.</b><br/>Operating · CH<br/>GUP"]:::gup
  NUK["<b>Nestlé UK</b><br/>Operating · GB<br/>DUP"]:::dup
  NPU["<b>Nestlé Purina UK</b><br/>Operating · GB<br/>Subsidiary"]:::leaf
  NHS["<b>Nestlé Health Science</b><br/>Operating · CH<br/>Subsidiary"]:::leaf
  NES["<b>Nespresso</b><br/>Operating · CH<br/>Subsidiary"]:::leaf

  N --> NUK
  N --> NHS
  N --> NES
  NUK --> NPU

  classDef gup fill:#b5c4b6,stroke:#343539,stroke-width:1.5px,color:#343539
  classDef dup fill:#e2ede3,stroke:#98a59c,stroke-width:1px,color:#343539
  classDef leaf fill:#ffffff,stroke:#c8d5c9,stroke-width:1px,color:#343539
  linkStyle default stroke:#98a59c,stroke-width:1.5px
```

`GOP` doesn't fire because the `GUP` is already Operating. Swiss children fall to `Subsidiary` because they share a country with the `GUP`.

### Global bank with HoldCo and cross-border operations

Every commercial position in one tree.

```mermaid
flowchart TD
  HH["<b>HSBC Holdings</b><br/>HoldCo · GB<br/>GUP"]:::gup
  HB["<b>HSBC Bank plc</b><br/>Operating · GB<br/>GOP"]:::gop
  HUK["<b>HSBC UK Bank</b><br/>Operating · GB<br/>Subsidiary"]:::leaf
  HUS["<b>HSBC USA Inc.</b><br/>HoldCo · US<br/>DUP"]:::dup
  HBUS["<b>HSBC Bank USA, N.A.</b><br/>Operating · US<br/>DOP"]:::dop
  HHK["<b>HSBC Hong Kong Branch</b><br/>Establishment · HK<br/>Establishment"]:::leaf

  HH --> HB
  HB --> HUK
  HB --> HUS
  HB --> HHK
  HUS --> HBUS

  classDef gup fill:#b5c4b6,stroke:#343539,stroke-width:1.5px,color:#343539
  classDef gop fill:#d5e2d6,stroke:#98a59c,stroke-width:1px,color:#343539
  classDef dup fill:#e2ede3,stroke:#98a59c,stroke-width:1px,color:#343539
  classDef dop fill:#f0f6f1,stroke:#98a59c,stroke-width:1px,color:#343539
  classDef leaf fill:#ffffff,stroke:#c8d5c9,stroke-width:1px,color:#343539
  linkStyle default stroke:#98a59c,stroke-width:1.5px
```

### Brand inside an operating company

Business Units aren't legal entities. They contract through a parent.

```mermaid
flowchart TD
  KH["<b>Kraft Heinz</b><br/>Operating · US<br/>GUP"]:::gup
  KUK["<b>Kraft Heinz UK</b><br/>Operating · GB<br/>DUP"]:::dup
  JO["<b>Jell-O</b><br/>Business unit · US<br/>Business Unit"]:::leaf

  KH --> KUK
  KH --> JO

  classDef gup fill:#b5c4b6,stroke:#343539,stroke-width:1.5px,color:#343539
  classDef dup fill:#e2ede3,stroke:#98a59c,stroke-width:1px,color:#343539
  classDef leaf fill:#ffffff,stroke:#c8d5c9,stroke-width:1px,color:#343539
  linkStyle default stroke:#98a59c,stroke-width:1.5px
```

Jell-O is a brand, not a separate company. It takes the `Business Unit` position.

### US Federal Government

Multi-level government tree. Everything below the `GUP` is flat.

```mermaid
flowchart TD
  USG["<b>United States Government</b><br/>National Government · US<br/>GUP"]:::gup
  HHS["<b>HHS</b><br/>Agency · US<br/>Subsidiary"]:::leaf
  FDA["<b>FDA</b><br/>Agency · US<br/>Subsidiary"]:::leaf
  CDC["<b>CDC</b><br/>Agency · US<br/>Subsidiary"]:::leaf
  NIH["<b>NIH</b><br/>Agency · US<br/>Subsidiary"]:::leaf
  DOD["<b>Department of Defense</b><br/>Agency · US<br/>Subsidiary"]:::leaf
  ARMY["<b>US Army</b><br/>Agency · US<br/>Subsidiary"]:::leaf
  DARPA["<b>DARPA</b><br/>Agency · US<br/>Subsidiary"]:::leaf
  FED["<b>Federal Reserve</b><br/>Agency · US<br/>Subsidiary"]:::leaf

  USG --> HHS
  USG --> DOD
  USG --> FED
  HHS --> FDA
  HHS --> CDC
  HHS --> NIH
  DOD --> ARMY
  DOD --> DARPA

  classDef gup fill:#b5c4b6,stroke:#343539,stroke-width:1.5px,color:#343539
  classDef leaf fill:#ffffff,stroke:#c8d5c9,stroke-width:1px,color:#343539
  linkStyle default stroke:#98a59c,stroke-width:1.5px
```

{% hint style="info" %}
**Government trees are flat below the `GUP`.** Every descendant resolves to `Subsidiary`. To tell FDA apart from HHS from USG, walk the parent relationship rather than relying on the label alone.
{% endhint %}

### EU, member state, subnational, local

`DUP` can fire for a government tree when an IGO sits on top.

```mermaid
flowchart TD
  EU["<b>European Union</b><br/>IGO · no country<br/>GUP"]:::gup
  FR["<b>République française</b><br/>National Government · FR<br/>DUP"]:::dup
  MIN["<b>Ministère de l'Économie</b><br/>Agency · FR<br/>Subsidiary"]:::leaf
  IDF["<b>Région Île-de-France</b><br/>Subnational · FR<br/>Subsidiary"]:::leaf
  PAR["<b>Ville de Paris</b><br/>Local Government · FR<br/>Subsidiary"]:::leaf

  EU --> FR
  FR --> MIN
  FR --> IDF
  IDF --> PAR

  classDef gup fill:#b5c4b6,stroke:#343539,stroke-width:1.5px,color:#343539
  classDef dup fill:#e2ede3,stroke:#98a59c,stroke-width:1px,color:#343539
  classDef leaf fill:#ffffff,stroke:#c8d5c9,stroke-width:1px,color:#343539
  linkStyle default stroke:#98a59c,stroke-width:1.5px
```

The EU has no single country, so it's the `GUP` by default. France is the highest entity in FR, and FR is different from the `GUP`'s country, so France takes `DUP`.

### UK Government with devolved administrations

```mermaid
flowchart TD
  UKG["<b>UK Government</b><br/>National Government · GB<br/>GUP"]:::gup
  HMT["<b>HM Treasury</b><br/>Agency · GB<br/>Subsidiary"]:::leaf
  DFE["<b>Department for Education</b><br/>Agency · GB<br/>Subsidiary"]:::leaf
  OFS["<b>Ofsted</b><br/>Agency · GB<br/>Subsidiary"]:::leaf
  SCOT["<b>Scottish Government</b><br/>Subnational · GB<br/>Subsidiary"]:::leaf
  SE["<b>Scottish Enterprise</b><br/>Agency · GB<br/>Subsidiary"]:::leaf

  UKG --> HMT
  UKG --> DFE
  UKG --> SCOT
  DFE --> OFS
  SCOT --> SE

  classDef gup fill:#b5c4b6,stroke:#343539,stroke-width:1.5px,color:#343539
  classDef leaf fill:#ffffff,stroke:#c8d5c9,stroke-width:1px,color:#343539
  linkStyle default stroke:#98a59c,stroke-width:1.5px
```

Scottish Government is subnational, but country is recorded at the sovereign level (GB), so `DUP` doesn't fire. Same for Welsh and NI governments.

### University of California System

Education tree where `Business Unit` fires via Academic Unit.

```mermaid
flowchart TD
  UC["<b>University of California System</b><br/>Education System · US<br/>GUP"]:::gup
  UCB["<b>UC Berkeley</b><br/>Higher-Ed Institution · US<br/>Subsidiary"]:::leaf
  HAAS["<b>Haas School of Business</b><br/>Academic Unit · US<br/>Business Unit"]:::leaf
  ENG["<b>College of Engineering</b><br/>Academic Unit · US<br/>Business Unit"]:::leaf
  LBL["<b>Lawrence Berkeley Lab</b><br/>Research Institute · US<br/>Subsidiary"]:::leaf
  UCLA["<b>UCLA</b><br/>Higher-Ed Institution · US<br/>Subsidiary"]:::leaf
  AND["<b>Anderson School of Management</b><br/>Academic Unit · US<br/>Business Unit"]:::leaf
  UCSF["<b>UC San Francisco</b><br/>Higher-Ed Institution · US<br/>Subsidiary"]:::leaf

  UC --> UCB
  UC --> UCLA
  UC --> UCSF
  UCB --> HAAS
  UCB --> ENG
  UCB --> LBL
  UCLA --> AND

  classDef gup fill:#b5c4b6,stroke:#343539,stroke-width:1.5px,color:#343539
  classDef leaf fill:#ffffff,stroke:#c8d5c9,stroke-width:1px,color:#343539
  linkStyle default stroke:#98a59c,stroke-width:1.5px
```

### K-12 district

```mermaid
flowchart TD
  LAU["<b>LA Unified School District</b><br/>Education System · US<br/>GUP"]:::gup
  LH["<b>Lincoln High</b><br/>Pre-tertiary · US<br/>Subsidiary"]:::leaf
  RH["<b>Roosevelt High</b><br/>Pre-tertiary · US<br/>Subsidiary"]:::leaf
  GH["<b>Garfield High</b><br/>Pre-tertiary · US<br/>Subsidiary"]:::leaf

  LAU --> LH
  LAU --> RH
  LAU --> GH

  classDef gup fill:#b5c4b6,stroke:#343539,stroke-width:1.5px,color:#343539
  classDef leaf fill:#ffffff,stroke:#c8d5c9,stroke-width:1px,color:#343539
  linkStyle default stroke:#98a59c,stroke-width:1.5px
```

### Standalones

An account with no parent and no children is a `Standalone`.

| Account                                             | Classification | Why                                                                                           |
| --------------------------------------------------- | -------------- | --------------------------------------------------------------------------------------------- |
| **Shopify Inc.** (Operating · CA)                   | Standalone     | No parent, tree of one                                                                        |
| **Harvard University** (Higher-Ed Institution · US) | Standalone     | Independent, no system parent                                                                 |
| **Federal Reserve** (Agency · US)                   | Standalone     | Only when imported without a USG parent. Otherwise `Subsidiary` as in the US Federal example. |


# Feedback loop

Kernel's feedback loop lets your team flag data issues from Salesforce or the Kernel app and track them through to resolution by the Kernel data team.

The feedback loop is how you tell Kernel when something about an account looks wrong - the wrong company was matched, a headcount is off, a parent is missing - and how you follow that issue through to resolution.

You raise an issue, the Kernel data team triages it, and you see the outcome in the same tracked workflow without having to chase updates across channels.

{% hint style="info" %}
The full feedback experience lives in **Salesforce** for now - the Send Kernel Feedback button captures the account context automatically and files a tracked ticket. You can also raise feedback from the **Kernel app**, and both routes create the same kind of ticket.
{% endhint %}

## How to raise feedback

You can raise feedback in two places. Both create the same kind of tracked support ticket for the Kernel data team.

#### From Salesforce - "Send Kernel Feedback"

On an **Account** or **Kernel Account** record, click the **Send Kernel Feedback** button. A short form opens where you choose the field you disagree with, tell us the value you expected, and add any evidence or context.

<figure><img src="/files/doAssCzrto1Mgg4jB5Zm" alt="The Send Kernel Feedback button in the action bar of a Salesforce record."><figcaption><p>Send Kernel Feedback sits in the record action bar, on both Account and Kernel Account records.</p></figcaption></figure>

The current Kernel value is captured for you automatically, so you only need to describe what is wrong.

The current experience requires Kernel Salesforce package version 3.11.4 or later, installed for all users. Each intended user also needs **Kernel Readonly PermissionSet**. See [Send Kernel Feedback](/integrations/salesforce-integration/report-data-issues) for how to upgrade, assign access, and add it to your Account pages.

<figure><img src="/files/N40UVzu57w262OIlygXP" alt="The Send Kernel Feedback form in Salesforce, with fields for the disputed field, category, expected value, priority, evidence URL, and comment."><figcaption><p>The Send Kernel Feedback form on a Kernel Account record.</p></figcaption></figure>

#### From the Kernel app

In the Kernel app you can reject an identity match or leave a note directly on an account. Kernel turns that note into a feedback ticket in the same way.

## What happens next

Every piece of feedback becomes a support ticket for the Kernel data team.

* We triage the issue and investigate the account.
* Progress and resolution follow the support terms agreed for your workspace.
* You can read the outcome and the reasoning behind it once the issue is resolved.

## Tracking feedback and outcomes

You can follow the status of every issue you have raised on the **Data feedback loop** page of the Kernel app, at [app.kernel.ai/feedback](https://app.kernel.ai/feedback).

The page lists each piece of feedback as a row, showing:

* the **account** the feedback was raised against,
* the **disputed field** and the value you expected,
* the **status** as it moves from submitted, through in review, to resolved, and
* a **link back** to the account in your CRM.

Click into a row to see the full detail: what was submitted, the resolution the data team reached, and the reasoning behind it - so you can see not just that an issue was fixed, but why. Feedback from every rep in your organisation appears here, giving your team one shared view of what has been raised and where it stands.

<figure><img src="/files/hPZ8rsRlyZM4jPYMAVzO" alt="The Data feedback loop page in the Kernel app, listing each account&#x27;s feedback with its disputed field category, status, outcome, close date, and a link back to the CRM record."><figcaption><p>The Data feedback loop page in the Kernel app, where you can track every issue raised and its outcome.</p></figcaption></figure>

When you raise feedback from Salesforce, the issue is stored as a **Kernel Feedback** record against the standard Account. If your organisation uses linked Kernel Account records, the same issue appears in the **Kernel Feedback** related list there, so your team can see the history without leaving Salesforce.

<figure><img src="/files/s60DJuUHu96Qd82Nmacc" alt="The Kernel Feedback related list on a Kernel Account record, showing a submitted feedback item with its disputed field, category, and status."><figcaption><p>Submitted feedback appears in the Kernel Feedback related list on the Kernel Account, with its status.</p></figcaption></figure>

{% hint style="info" %}
**Using the legacy feedback field?** Some Kernel implementations still collect feedback through a Salesforce custom field (`kernel_feedback__c`, labeled "Kernel - Feedback") that Kernel reads on a schedule. This method is still supported. When an issue raised through the field is resolved, the field value is updated with a green tick (✅) and the resolution note. New implementations use the **Send Kernel Feedback** form described above.
{% endhint %}


# Entity data

An entity is any real-world organization that can appear as an account in your CRM. This includes commercial businesses, government bodies, educational institutions, and the sub-units within them, from holding companies and operating subsidiaries down to individual business units and physical establishments.

Every account in your CRM should map to exactly one entity. In practice, this rarely holds. The same entity appears multiple times (duplicates), different entities share the same record (conflated accounts), and some records don't map to any real entity at all (defunct or junk accounts).

Kernel's job is to resolve each CRM account to the correct real-world entity, assign it a unique [KERN ID](/concepts/kern-id), classify it by type, and place it in the right position within a [hierarchy](/data/hierarchies).

## What's included

| Page                                                     | What it covers                                                                                   | Use it for                                                             |
| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------- |
| [Core entity data](/data/entity-data/core-entity-data)   | The resolved entity identity: name, legal name, website, country of incorporation, and KERN ID.  | Understanding the basic identity fields Kernel returns for an account. |
| [Entity categories](/data/entity-data/entity-categories) | Entity category and sub-category, including Company, Government, Education, and their sub-types. | Understanding what kind of organization an account represents.         |
| [LinkedIn URL](/data/entity-data/linkedin-url)           | How Kernel matches an entity to the appropriate LinkedIn company page.                           | Understanding LinkedIn URL matching, match type, and reasoning.        |

{% hint style="info" %}
Entity data is the identity layer. Firmographics such as headcount, revenue, location, and operational status live under [Firmographics](/data/firmographics). Parent and subsidiary relationships live under [Hierarchies](/data/hierarchies).
{% endhint %}

## Why entity definitions matter

Consistent entity definitions are the foundation for every downstream operation:

* **Deduplication** - You can only identify duplicates if you know what constitutes "the same entity."
* **Hierarchy mapping** - Parent-child relationships depend on clear entity boundaries.
* **Enrichment** - Firmographic data must be attributed to the right entity, not a parent when you mean a subsidiary.
* **Territory assignment** - Reps need to know whether an account represents the right real-world organization for their book of business.

Start with [Core entity data](/data/entity-data/core-entity-data), then use [Entity categories](/data/entity-data/entity-categories), [LinkedIn URL](/data/entity-data/linkedin-url), and [Hierarchies](/data/hierarchies) to understand the surrounding context.


# Core entity data

The identity fields Kernel resolves for each real-world entity

Core entity data describes the real-world organization Kernel has resolved from a CRM account. These fields answer the basic identity question before enrichment, hierarchy mapping, or LinkedIn matching are applied.

| Field                    | What it means                                                       | Example              |
| ------------------------ | ------------------------------------------------------------------- | -------------------- |
| KERN ID                  | Kernel's persistent identifier for the resolved entity.             | `6347422643`         |
| Name                     | The common or trading name teams use to recognize the entity.       | Stripe               |
| Legal name               | The registered legal name, when Kernel can determine it.            | Stripe, Inc.         |
| Website                  | The primary website Kernel associates with the resolved entity.     | `https://stripe.com` |
| Country of incorporation | The country where the entity is legally registered, when available. | United States        |

## How Kernel resolves core data

Kernel starts with the CRM record and checks the surrounding evidence: account name, website, legal name fields, country, address, LinkedIn URL, related account data, and public web evidence. The goal is to decide which real-world entity the CRM account represents, then return a consistent identity for that entity.

{% stepper %}
{% step %}
**Resolve the entity**

Kernel determines whether the CRM account maps to a real organization and assigns the appropriate [KERN ID](/concepts/kern-id).
{% endstep %}

{% step %}
**Separate trading and legal identity**

Kernel distinguishes the name teams sell to from the registered legal entity where that distinction matters.
{% endstep %}

{% step %}
**Attach stable core fields**

Kernel returns the website, legal name, country of incorporation, and other core identity fields that belong to the resolved entity.
{% endstep %}
{% endstepper %}

See [Kernel Account object reference](/integrations/salesforce-integration/kernel-account-object-reference) for the full field list.


# Entity categories

How Kernel classifies the type and role of each resolved entity

Kernel classifies each resolved entity with two fields:

| Dimension           | What it answers                    | Examples                                        |
| ------------------- | ---------------------------------- | ----------------------------------------------- |
| Entity category     | What kind of organization is this? | Company, Government, Education                  |
| Entity sub-category | What specific role does it play?   | Operating, HoldCo, Business unit, Establishment |

These classifications are determined through Kernel's [entity resolution](/concepts/entity-resolution) process. They are not static labels copied from a vendor table.

## Categories and sub-categories

{% tabs %}
{% tab title="Company" %}
Used for commercial businesses, non-profits, and trade associations.

Includes corporations, partnerships, startups, NGOs, charities, unions, and state-owned enterprises operating as commercial entities.

| Sub-category        | Definition                                                                                                        | Examples                                            |
| ------------------- | ----------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| Operating           | Companies that sell goods or services directly to external customers under their own brand.                       | Salesforce, Frito-Lay, Heineken                     |
| HoldCo / Investment | Entities that primarily exist to own or control other companies or assets. They rarely sell products directly.    | Berkshire Hathaway, Alphabet Inc., KKR & Co.        |
| Business unit       | A unit, brand, portfolio, line, or division inside a company that is not a separate legal entity.                 | Jell-O under Kraft Heinz; Amazon Music under Amazon |
| Establishment       | A specific physical site tied to a company, not a separate corporation and not independently legally responsible. | McDonald's Store #1234; Lloyds London Bridge branch |
| {% endtab %}        |                                                                                                                   |                                                     |

{% tab title="Government" %}
Used for public administration bodies.

Includes national, regional, and local government bodies, ministries, departments, agencies, regulators, and armed forces.

| Sub-category                   | Definition                                                                                    |
| ------------------------------ | --------------------------------------------------------------------------------------------- |
| Agency / Department            | Executive bodies, ministries, regulators, and authorities.                                    |
| National Government            | The central or federal government of a sovereign state.                                       |
| Subnational Government         | First administrative level below the country, such as states, provinces, regions, or cantons. |
| Local Government               | Municipal tiers such as cities, towns, councils, and boroughs.                                |
| Intergovernmental Organization | Polities formed by sovereign states, such as the EU, ASEAN, or UN.                            |
| Judiciary                      | Courts and judicial councils at any level.                                                    |
| {% endtab %}                   |                                                                                               |

{% tab title="Education" %}
Used for teaching and academic research institutions.

Includes universities, colleges, business schools, K-12 schools, and research institutes.

| Sub-category                 | Definition                                                                            |
| ---------------------------- | ------------------------------------------------------------------------------------- |
| Higher-Education Institution | Degree-awarding tertiary providers, such as universities, colleges, and polytechnics. |
| Academic Unit                | A department, faculty, or school inside a higher-education institution.               |
| Pre-tertiary school          | K-12, high schools, primary schools, and international schools.                       |
| Education System             | A governing body that controls multiple schools or institutions.                      |
| Research Institute           | Organizations focused on research or vocational training that do not award degrees.   |
| {% endtab %}                 |                                                                                       |
| {% endtabs %}                |                                                                                       |

{% hint style="info" %}
A corporate tree can span multiple entity categories. For example, a government body may own a holding company, which owns an operating company.
{% endhint %}

## Operating vs HoldCo / Investment

Within the Company category, the split that drives corporate-tree behavior is **Operating** vs **HoldCo / Investment**.

|                  | Operating                                                            | HoldCo / Investment                                                                                                                                                                      |
| ---------------- | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Primary function | Sells goods or services to external customers and runs its own P\&L. | Owns or controls other companies or financial assets. Revenue comes from what it owns, not from direct operations.                                                                       |
| Typical signals  | An independently established business trading under its own brand.   | Name often contains "Group", "Holdings", or "Partners"; owns subsidiaries or portfolio companies; appears as a parent/holding entity in filings; funds, trusts, and investment vehicles. |
| Examples         | Salesforce, Heineken                                                 | Berkshire Hathaway, Alphabet Inc., KKR                                                                                                                                                   |

Operating is the default. Kernel classifies an entity as HoldCo / Investment only when the evidence shows its main role is ownership rather than operations.

### Why this split matters

<figure><img src="/files/VIrESnWb2hQtyCWfGvL0" alt="Berkshire Hathaway, a HoldCo / Investment owner, sits above three unrelated operating businesses - GEICO, See&#x27;s Candies, and Duracell. Berkshire is the top parent for all three, but each operating company is its own top operating parent because the investment owner is skipped."><figcaption><p>Berkshire Hathaway is the top parent for GEICO, See's Candies, and Duracell alike - but each operating business is its own top operating parent. Ownership federates down to the operating company, not up to the investment owner.</p></figcaption></figure>

This is the classification Kernel uses to separate the [top parent from the top operating parent](/data/hierarchies/parent-relationships):

* **Top parent** is the ultimate owner at the very top of the tree - even if that owner is a holding company or an equity investor.
* **Top operating parent** is the highest **Operating** company. Kernel climbs the tree and skips every HoldCo / Investment entity, stopping at the highest operating business.

The main use is **federating ownership down to the operating business.** For most GTM, routing, and reporting workflows you do not want an account to roll up to a passive holding company or a private-equity / investment owner - you want the operating company that actually runs the business. Top operating parent gives you that operating anchor, while top parent still records the full legal top of the tree.

Kernel also exposes a **Skip holdco parents** cleaning rule, so hierarchy actions attach a child to its operating parent instead of a holding-company parent.

See [Kernel Account object reference](/integrations/salesforce-integration/kernel-account-object-reference) for the full field list.


# LinkedIn URL

How Kernel matches a resolved entity to the right LinkedIn company page

{% embed url="<https://pub-8e1d08f439ec43bdbb79f1055a273a02.r2.dev/linkedin-url-ee3f61be.mp4>" %}

LinkedIn company pages are useful evidence, but they are not a clean entity identifier on their own. Kernel matches LinkedIn URLs after resolving the underlying entity, so the URL is tied to the company, subsidiary, brand, or operating unit the CRM account is meant to represent.

This matters because the relationship between CRM accounts, real-world entities, and LinkedIn pages is often many-to-many:

* Multiple CRM accounts can share the same LinkedIn URL because the accounts are duplicates, regional records, branches, or records that all point at a global parent profile.
* One account can have several plausible LinkedIn URLs, such as a global company page, a regional subsidiary page, an acquired brand page, or an older duplicate profile.
* A LinkedIn page can list a website that redirects, uses a short link, points to a careers page, or belongs to the broader group rather than the specific entity.

{% hint style="info" %}
Examples:

* Starbucks alone has 100+ LinkedIn profiles.
* Google's [LinkedIn profile](https://www.linkedin.com/company/google) is associated with [`goo.gle/3DLEokh`](https://goo.gle/3DLEokh), a short link to their careers site.
* In your CRM, Frito-Lay may be incorrectly associated with `linkedin.com/company/frito-lay-inc/`, which looks correct, but isn't.
  {% endhint %}

Kernel therefore treats LinkedIn as evidence about the entity, not as the entity itself.

## How Kernel chooses a LinkedIn URL

Kernel starts from the resolved entity and the evidence around the CRM account: name, website, legal name, country, hierarchy context, address, existing CRM LinkedIn URL, and related account signals. It does not pick the first LinkedIn search result or assume that matching a website domain is enough.

The matching flow has three stages:

{% stepper %}
{% step %}
**Candidate generation**

Kernel builds a candidate set from its LinkedIn profile index, the account website, existing CRM fields, web evidence, and related entity data.
{% endstep %}

{% step %}
**Candidate ranking**

Kernel ranks those candidates using signals such as name and slug similarity, website alignment, geography, profile completeness, headcount range, follower count, and parent or regional context.
{% endstep %}

{% step %}
**Candidate selection**

Kernel compares the strongest candidates against the resolved entity boundary and selects the profile whose scope best fits the account.
{% endstep %}
{% endstepper %}

The output can be:

| Output       | Meaning                                                          |
| ------------ | ---------------------------------------------------------------- |
| LinkedIn URL | The profile Kernel believes best represents the resolved entity. |
| Match type   | Whether the profile is an `actual` or `indicative` match.        |
| Reasoning    | A plain-language explanation of why that profile was selected.   |

## Match types

Kernel uses match type to separate direct matches from useful but less definitive profile links:

| Match type   | Meaning                                                                                                                                                                                                        |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `actual`     | The LinkedIn profile is a verified direct match for the resolved entity. The profile identity, website, branding, geography, and supporting evidence line up strongly enough to treat it as the entity's page. |
| `indicative` | The LinkedIn profile is the best available signal, but it may represent a parent, regional operation, brand, or broader group rather than a clean one-to-one entity match.                                     |

This distinction is important for review workflows. Two accounts can legitimately share an indicative group profile, while another account may need a more specific actual profile before the CRM field should be updated.

In Salesforce, the standard Kernel Account custom object (`Kernel_Account__c`) exposes the matched URL as `Kernel_Linkedin_Url__c`.


# Hierarchies

How Kernel links entities into corporate trees

Hierarchy data explains how one resolved entity relates to another. Kernel keeps each entity distinct, assigns each one its own KERN ID, and links related entities into a tree.

This matters because a CRM account can mean different things. It might be the global group, a country subsidiary, a holding company, a product brand, or a standalone business. Kernel keeps those boundaries visible instead of folding every related record into one company.

## What hierarchy data answers

Use hierarchy data when you need to know:

| Question                                                       | Use                                                                   |
| -------------------------------------------------------------- | --------------------------------------------------------------------- |
| Who is directly above this account?                            | CRM parent fields, association actions, and account ownership cleanup |
| What is the top of the corporate tree?                         | Routing, reporting, account-family lists, and rollups                 |
| Is this the highest operating company below a holding company? | GTM views that should skip passive holding companies                  |
| Is this account a regional subsidiary?                         | Territory planning, regional reporting, and duplicate policy          |
| Which scope should a number use?                               | Entity-level vs. consolidated headcount and revenue                   |

## Pages in this section

* [Parent relationships](/data/hierarchies/parent-relationships) explains immediate parent, top parent, top operating parent, and standalone companies.
* [Regional subsidiaries](/data/hierarchies/regional-subsidiaries) explains how Kernel distinguishes a regional subsidiary from a duplicate account.


# Parent relationships

Immediate parent, top parent, top operating parent, and standalone entities

Kernel resolves parent relationships after it has resolved the account to a specific entity. The goal is to show where that entity sits in the corporate tree without changing the entity itself.

The same company family can have several useful parent answers. A CRM hierarchy usually needs the direct parent. Reporting and routing often need the top of the tree. GTM workflows sometimes need the highest operating company rather than a passive holding company.

## Types of parent

Each parent relationship includes data about the parent entity, not just a relationship label. That can include the parent name, legal name, KERN ID, website, and other core entity details Kernel has for that parent. This lets teams map parent records into CRM fields, routing rules, and reporting tables without doing a separate lookup first.

| Relationship         | What it means                                                                                                                                    | Use it for                                                                        |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------- |
| Immediate parent     | The parent entity one level above the account. It is empty when the entity is standalone.                                                        | CRM parent fields, association actions, and hierarchy cleanup                     |
| Top parent           | The ultimate parent at the top of the corporate tree. For a company at the top of its tree, this points to the company itself.                   | Account-family rollups, global routing, group-level reporting, and dedupe context |
| Top operating parent | The highest operating parent in the tree, excluding holding or investment entities. Empty when no operating company sits at or above the entity. | GTM reporting and routing that should skip holding companies                      |

## Example

<figure><img src="/files/zPYPjvuBSOxrNYI7BaMo" alt="Corporate tree for YouTube: the immediate parent and top operating parent both resolve to Google LLC, while the top parent resolves to Alphabet Inc. at the top of the tree"><figcaption><p>Alphabet is a holding company, so the top operating parent stops at Google LLC, the highest operating company, while the top parent continues to the top of the tree.</p></figcaption></figure>

For YouTube:

| Relationship         | Entity        |
| -------------------- | ------------- |
| Account              | YouTube       |
| Immediate parent     | Google LLC    |
| Top parent           | Alphabet Inc. |
| Top operating parent | Google LLC    |

Here the immediate parent and the top operating parent match, because Google is already the highest operating company beneath the holding company. That is not always the case. When an account sits under a mid-tier operating company, the immediate parent and the top operating parent differ:

<figure><img src="/files/ZdCcLsGfmsDRd2lXcbb3" alt="Corporate tree for Blizzard Entertainment: the immediate parent resolves to Activision Blizzard, while the top operating parent and top parent both resolve to Microsoft"><figcaption><p>There is no holding company to skip, so the top operating parent equals the top parent (Microsoft). The immediate parent is the mid-tier operating company, Activision Blizzard.</p></figcaption></figure>

For Blizzard Entertainment:

| Relationship         | Entity                 |
| -------------------- | ---------------------- |
| Account              | Blizzard Entertainment |
| Immediate parent     | Activision Blizzard    |
| Top parent           | Microsoft              |
| Top operating parent | Microsoft              |

## When a company has no parent

Some companies sit at the top of their tree. Nothing owns them, so they have no immediate parent. This applies both to a genuinely standalone company and to the ultimate parent of a large group.

These companies still have a top parent. The top parent is the company at the very top of the tree, so when a company is already at the top, its top parent is the company itself. Every resolved company has a top parent, and it is never empty.

<figure><img src="/files/u89qGhP93WYKvwXIM41S" alt="Two companies at the top of their trees. Microsoft, an operating company, is its own top parent and its own top operating parent. Alphabet Inc., a holding company, is its own top parent but has no top operating parent."><figcaption><p>A company at the top of its tree is its own top parent. It is its own top operating parent too, unless it is a holding company, in which case no operating company sits at or above it.</p></figcaption></figure>

The top operating parent works the same way, with one exception. It is the highest operating company in the tree:

* When the company at the top is an operating company, it is its own top operating parent, just as it is its own top parent. Microsoft is an example.
* When the company at the top is a holding company, there is no operating company at or above it, so the top operating parent is empty. The operating companies sit further down the tree, not above it. Alphabet is an example.

When you read these values back, a top parent that equals the account is not a data gap. It tells you the account is already the top of its tree. An empty top operating parent tells you either the account has no parent chain, or the top of that chain is a holding company.

## Which parent should I use?

Use the immediate parent when you want to write or repair the CRM hierarchy itself. This is the relationship Kernel uses for association actions, because CRM parent fields usually represent a direct parent-child link.

Use the top parent when the question is about the whole account family. This is usually the right value for group reporting, global account lists, territory rules that roll up to the full corporate group, and duplicate review context.

Use the top operating parent when the legal top parent is a holding company but the useful GTM anchor is the operating business below it. This keeps ownership structure visible without forcing every sales or routing workflow to anchor on a passive holding company.

## What Kernel does with parent relationships

Kernel keeps parent relationships separate from merge decisions. A subsidiary, business unit, or regional arm can be related to a parent without becoming the same entity as the parent.

In the app, parent recommendations appear in [Associate hierarchies](/app/actioning/associate). Kernel can suggest linking a child to an existing parent, reparenting a child, creating a missing parent, or removing an incorrect parent relationship.


# Regional subsidiaries

How Kernel distinguishes regional subsidiaries from duplicate accounts

{% embed url="<https://pub-8e1d08f439ec43bdbb79f1055a273a02.r2.dev/regional-subsidiaries-and-scope-f1b0a747.mp4>" %}

A global company can appear in a CRM as a parent company, a country subsidiary, and several local branches. Some of those records are true duplicates. Others are real entities that should stay separate.

Kernel treats regional subsidiaries as distinct entities when the evidence supports it. A regional subsidiary gets its own KERN ID, its own entity-level data, and a parent relationship back to the wider company family.

## What counts as a regional subsidiary

A record must meet all four criteria:

1. **Verified existence** - it is a real legal entity, not a loose regional label.
2. **Shared brand** - it operates under the same core brand as the parent.
3. **Explicit geography** - its name or evidence points to a specific country, region, or market.
4. **Regional scope** - it serves a market that is smaller than the parent's global scope.

<figure><img src="/files/SRuD7PKkqVk068qOptdy" alt="Salesforce has two subsidiaries. Salesforce Japan meets all four criteria (verified existence, shared brand, explicit geography in Japan, regional scope) and is a regional subsidiary. Slack is a real entity but shares no brand, is in the same country as Salesforce, and serves the global market, so it is a subsidiary but not regional."><figcaption><p>Both are real subsidiaries of Salesforce. Salesforce Japan shares the brand and serves a smaller market, so it is regional; Slack is a separate brand in the same market, so it is a subsidiary but not a regional one.</p></figcaption></figure>

For example, Google Japan Inc. can be a regional subsidiary when it is the legal entity for Google's Japanese operations. A product division such as Google Cloud Japan is not automatically a regional subsidiary unless it is also the legal entity representing that region.

## Regional scope

Kernel provides three data points about an entity's regional scope:

| Data point     | What it means                                                       |
| -------------- | ------------------------------------------------------------------- |
| Is regional    | Whether the entity operates as a regional subsidiary of its parent. |
| Regional scope | The country or market served by the regional operation.             |
| Reasoning      | Why Kernel classified the entity as regional or not regional.       |

The regional scope is filled in only when the entity is a regional subsidiary. If the account is not regional, or Kernel has not determined the classification yet, it can be empty.

See the [Kernel Account object reference](/integrations/salesforce-integration/kernel-account-object-reference) for the Salesforce and API field names.

## Duplicate or subsidiary?

The right CRM action depends on how your team wants to model regional records.

| Policy                               | What happens                                                                         |
| ------------------------------------ | ------------------------------------------------------------------------------------ |
| Treat regional sites as subsidiaries | Kernel keeps the regional entity separate and links it to the parent.                |
| Treat regional sites as duplicates   | Kernel can merge regional versions into the global company when your rules allow it. |

Exact duplicates still merge regardless of this policy. The policy applies to records that could reasonably represent either a regional entity or another copy of the global company.

## Scope across regions

Regional subsidiaries matter because firmographics have scope. The regional entity has its own headcount, revenue, location, and status. The parent or top parent can also have consolidated values for the larger family.

Use the regional entity when the account is meant to represent local operations. Use the parent or top parent when the question is about the full group.


# Firmographics

High-level guide to Kernel firmographic data and field-level explainers

Firmographics are the company facts Kernel returns after it has resolved a CRM record to the right entity. They cover where the entity operates, whether it is still in business, how many people it employs, and what revenue figure belongs at the right scope.

Kernel researches these fields instead of copying one static vendor value. Each result is tied to the resolved entity, checked against multiple sources, and returned with enough context to understand how the value was chosen.

## Core firmographics

| Data point         | What it answers                                                                                              | Learn more                                                   |
| ------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------ |
| Headcount          | How many people are on the entity's payroll, at entity or consolidated scope.                                | [Headcount](/data/firmographics/headcount)                   |
| Revenue            | What annual top-line revenue belongs to the entity, with currency, fiscal year, source, and scope preserved. | [Revenue](/data/firmographics/revenue)                       |
| Location           | Where the entity operates, and where it is legally registered.                                               | [Location](/data/firmographics/location)                     |
| Operational status | Whether the entity is active, absorbed into another brand, out of business, or unclear.                      | [Operational status](/data/firmographics/operational-status) |

## Reasoning and confidence

Firmographic values are meant to be inspectable, not just usable as a final number. The exact fields vary by integration, but the same ideas show up across the product:

* **Reasoning** explains how the value was determined, including sources, scope decisions, and important caveats.
* **Confidence** appears on headcount and revenue, and reflects how strong the evidence is.
* **Source** appears on headcount and revenue, and records whether the figure was identified directly from evidence or estimated by Kernel.

Operational status does not use a separate confidence field. `Undetermined` is the uncertainty value when the evidence is incomplete or conflicting.

If you disagree with a finding, you can flag it for review from your CRM with the **Send Kernel Feedback** button - see the [feedback loop](/concepts/feedback-loop).

## **Entity/Consolidated/Recommended**

Kernel keeps the value and its scope separate before choosing a CRM-ready recommendation.

| Term         | Meaning                                                                                                                                                                                                                                                                          |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Entity       | The value for the resolved legal entity itself. For headcount, this means direct payroll headcount for that entity. For revenue, this means annual revenue attributable to that entity.                                                                                          |
| Consolidated | The value for the entity plus its subsidiaries, when a reliable group-level value is available. This is useful when the account is a parent or holding company and the standalone entity does not reflect the scale of the operating group below it.                             |
| Recommended  | <p>Kernel chooses either Entity or Consolidated depending on the entity.</p><p>For most operating companies, this is the entity value. For holding companies or group parents, Kernel may recommend the consolidated value because it better represents the account's scale.</p> |


# Headcount

How Kernel determines payroll-based headcount at entity and consolidated scope

{% embed url="<https://pub-8e1d08f439ec43bdbb79f1055a273a02.r2.dev/headcount-87274624.mp4>" %}

Headcount looks simple until you define the entity and the scope. Kernel treats it as a researched firmographic field: <mark style="background-color:$success;">the number of people directly employed by the company and on its payroll.</mark>

It is headcount, not FTE. Kernel does not count contractors, consultants, agency workers, franchise employees, gig workers, or everyone connected to a brand. If a subsidiary has its own payroll, those employees belong to that subsidiary. They are not automatically counted as direct employees of the parent legal entity.

## How Kernel determines headcount

Kernel starts from the resolved entity: legal name, trading name, website, LinkedIn profile, entity category, parent structure, and regional scope.

It then checks available evidence across official filings, annual reports, company websites, investor pages, press releases, credible news, public registry data, LinkedIn company data, and other supporting sources.

Each source has tradeoffs.

* Filings are strong when they report the right scope and are current, but often report a consolidated group.
* Company websites can be useful but may be rounded or stale.
* LinkedIn can help, but associated profile counts and size ranges are not treated as truth by default.

Kernel weighs the evidence by source quality, recency, entity scope, geography, industry, age, and growth profile. When no reliable direct figure exists, Kernel estimates the value and marks it as estimated.

## Key data points

| Label                  | Description                                                                                                |
| ---------------------- | ---------------------------------------------------------------------------------------------------------- |
| Entity headcount       | Payroll headcount for the resolved legal entity.                                                           |
| Consolidated headcount | Headcount for the entity plus its subsidiaries, when available.                                            |
| Recommended headcount  | The recommended value guides you on whether to use entity- or consolidated headcount based on the account. |
| Confidence             | How strong the supporting evidence is.                                                                     |
| Reasoning              | Explanation of the source, date, scope, method, and caveats.                                               |

### **Entity/Consolidated/Recommended**

Kernel keeps the value and its scope separate before choosing a CRM-ready recommendation.

<figure><img src="/files/UkMnKAqhNvioTnxQDc81" alt="Diagram of entity-level, consolidated, and Kernel&#x27;s recommended headcount scope, shown on the Alphabet and Google LLC hierarchy"><figcaption><p>Entity-level and consolidated headcount describe different scopes. Recommended is the value Kernel maps to the primary CRM field.</p></figcaption></figure>

| Term         | Meaning                                                                                                                                                                                                                                                                          |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Entity       | The value for the resolved legal entity itself. For headcount, this means direct payroll headcount for that entity. For revenue, this means annual revenue attributable to that entity.                                                                                          |
| Consolidated | The value for the entity plus its subsidiaries, when a reliable group-level value is available. This is useful when the account is a parent or holding company and the standalone entity does not reflect the scale of the operating group below it.                             |
| Recommended  | <p>Kernel chooses either Entity or Consolidated depending on the entity.</p><p>For most operating companies, this is the entity value. For holding companies or group parents, Kernel may recommend the consolidated value because it better represents the account's scale.</p> |

The reasoning field explains which scope Kernel selected and why.

See the [Kernel Account object reference](/integrations/salesforce-integration/kernel-account-object-reference#headcount) for field types and help text.


# Revenue

How Kernel determines annual revenue, currency, source, and scope

{% embed url="<https://pub-8e1d08f439ec43bdbb79f1055a273a02.r2.dev/revenue-89ddc3fb.mp4>" %}

Revenue is the company's annual top-line figure. Kernel looks for total revenue, net revenue, net sales, turnover, or operating revenue. It does not use EBITDA, net income, operating income, balance-sheet items, or a single quarter as annual revenue.

<figure><img src="/files/zZho4UkivuGMJO4TldSO" alt="Diagram of what counts as revenue: an income statement with the top line highlighted as the value Kernel takes, next to exclusion cards for profit metrics, single quarters, forecasts, and balance-sheet items"><figcaption><p>Revenue is the annual top line, as reported. Profit metrics, single quarters, forecasts, and balance-sheet items do not qualify.</p></figcaption></figure>

Most private companies do not disclose a clean number, and even public figures can be hard to compare. One source may report pounds for a fiscal year ending in March. Another may report euros for a calendar year. A bare number without currency, period, source, or scope is not very useful.

## How Kernel determines revenue

Kernel searches for reported revenue across public filings, registries, annual reports, earnings releases, investor materials, credible news, and official company disclosures. When a company reports revenue in a local currency, Kernel keeps the local figure and converts it to USD using the verified annual average exchange rate for the relevant fiscal year.

Scope is handled separately. A segment is not the whole company, and a holding company's direct revenue is not the same as the revenue of the group below it. Kernel returns entity-level and consolidated values, then provides a primary value for CRM and export workflows.

<figure><img src="/files/HweYz0kXEfuCRpZRox3q" alt="Illustrative corporate hierarchy with A above B and subsidiaries C and D below B. The consolidated revenue of B is shown as B plus C plus D, while parent A is excluded."><figcaption><p>For entity B, consolidated revenue includes B and every subsidiary beneath it. Parent A is not part of B's consolidated total.</p></figcaption></figure>

When no public revenue figure can be found, Kernel estimates from signals such as headcount, industry, revenue model, operating model, geography, company maturity, and funding profile. Estimated values are labeled as estimates and do not carry the highest confidence.

## Key data points

| Label                | Description                                                                      |
| -------------------- | -------------------------------------------------------------------------------- |
| Entity revenue       | Annual revenue for the resolved legal entity.                                    |
| Consolidated revenue | Annual revenue for the entity plus its subsidiaries, when available.             |
| Recommended revenue  | The CRM-ready revenue value Kernel recommends for the account.                   |
| Currency             | The source currency Kernel found before converting revenue values.               |
| USD value            | Revenue converted to USD when the source figure and conversion are reliable.     |
| Confidence           | How strong the supporting evidence is.                                           |
| Reasoning            | Explanation of the source, currency handling, fiscal period, scope, and caveats. |

Kernel returns `null` rather than inventing a value when it cannot determine a safe figure or cannot perform a reliable currency conversion.

### **Entity/Consolidated/Recommended**

Kernel keeps the value and its scope separate before choosing a CRM-ready recommendation.

| Term         | Meaning                                                                                                                                                                                                                                                                          |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Entity       | The value for the resolved legal entity itself. For headcount, this means direct payroll headcount for that entity. For revenue, this means annual revenue attributable to that entity.                                                                                          |
| Consolidated | The value for the entity plus its subsidiaries, when a reliable group-level value is available. This is useful when the account is a parent or holding company and the standalone entity does not reflect the scale of the operating group below it.                             |
| Recommended  | <p>Kernel chooses either Entity or Consolidated depending on the entity.</p><p>For most operating companies, this is the entity value. For holding companies or group parents, Kernel may recommend the consolidated value because it better represents the account's scale.</p> |

See the [Kernel Account object reference](/integrations/salesforce-integration/kernel-account-object-reference#revenue) for field types and help text.


# Location

Why Kernel separates operating and registered location

{% embed url="<https://pub-8e1d08f439ec43bdbb79f1055a273a02.r2.dev/location-operating-registered-53a294c8.mp4>" %}

Location sounds like one field. In Kernel, it is deliberately two: where the entity operates, and where it is legally registered.

The operating address is where the entity actually works. Think headquarters, head office, main office, or the physical site your CRM account is meant to track. The registered address is the legal address from a corporate registry or another authoritative filing.

For most companies, these addresses are the same. Kernel mirrors them unless the evidence says there is a real split.

## How Kernel determines location

Kernel researches location from company websites, regulatory filings, news reports, business registrations, and other evidence tied to the resolved entity.

The split matters in common cases. A company may be registered at an agent address but operate from another city. A branch, store, plant, or office may operate at one street address while the legal entity is registered somewhere else. A remote-first company may have no operating street address but still have a registered office.

Use operating location for territory assignment, routing, and understanding where work actually happens. Use registered location for legal identity, compliance, and incorporation context.

## Key data points

Kernel returns each of these fields for both <mark style="background-color:$warning;">operating and registered locations</mark>.

| Label     | Description                                                                            |
| --------- | -------------------------------------------------------------------------------------- |
| Address   | Street address for the operating or registered location.                               |
| City      | City or locality for the address.                                                      |
| State     | State, province, region, or other administrative area for the address, when available. |
| Country   | Country for the address.                                                               |
| Reasoning | Why Kernel selected the location and which evidence supports it.                       |

The useful part is not just having two addresses. It is knowing which question each address answers.

See the [Kernel Account object reference](/integrations/salesforce-integration/kernel-account-object-reference#location) for field types and help text.


# Operational status

How Kernel determines whether an entity is active, absorbed, out of business, or undetermined

{% embed url="<https://pub-8e1d08f439ec43bdbb79f1055a273a02.r2.dev/operational-status-6245ffcc.mp4>" %}

Operational status answers a practical question: is this company still trading in the market your team cares about?

Kernel does not treat website liveness as a simple active-or-inactive flag. Dead companies can keep websites online, and active companies can have stale, broken, or redirected domains. Acquisitions also need care: one acquired brand may continue selling under its own name, another may be folded into the parent, and another may be shut down.

## How Kernel determines status

Kernel judges the go-to-market reality, not just the legal shell. It checks whether the company sells or supports products today under its own brand, whether the brand has been retired into a parent, and whether operations have stopped.

The research uses company websites, redirects, parked-domain signals, closure news, business registries, insolvency records, and local-language evidence where relevant. When signals conflict or the evidence is not strong enough, Kernel returns `Undetermined` instead of guessing.

Operational status is resolved before headcount and revenue. If an entity is out of business, downstream values should not make it look like a healthy operating company. When configured, an out-of-business account can also route to the [Delete inoperational](/app/actioning/delete) review queue.

## Key data points

| Label              | Description                                                               |
| ------------------ | ------------------------------------------------------------------------- |
| Operational status | Whether the entity is active, absorbed, out of business, or undetermined. |
| Reasoning          | Explanation of the evidence behind the status.                            |

There is no separate confidence field for operational status. `Undetermined` is the uncertainty value when the evidence is incomplete, stale, or contradictory.

## Status values

| Status            | Meaning                                                                                                    |
| ----------------- | ---------------------------------------------------------------------------------------------------------- |
| `Active`          | The company operates today under its own brand, even if it is owned by another company.                    |
| `Absorbed`        | The original brand is retired, but its operations or capabilities continue under a parent brand.           |
| `Out of business` | Operations have ceased and there is no meaningful continuation under the original brand or a parent brand. |
| `Undetermined`    | Kernel cannot classify the status confidently from the available evidence.                                 |

See the [Kernel Account object reference](/integrations/salesforce-integration/kernel-account-object-reference#operational-status) for field types and help text.


# Industry (custom)

Custom industry classification grounded in Kernel entity data and customer context

A custom industry classification is a customer-specific way to categorize accounts.

Kernel can classify accounts into the industry categories your team uses for routing, reporting, territory planning, or workflow automation. You define the categories, subcategories, inclusion rules, exclusion rules, and examples. Kernel applies that schema to account evidence such as websites, LinkedIn profiles, business descriptions, and other entity context, then returns the assigned category with reasoning.

## How it works

{% stepper %}
{% step %}

### Define schema

Define the categories, subcategories, and rules in the Kernel platform.
{% endstep %}

{% step %}

### Test and iterate

Run the schema across a test set of accounts and refine it for edge cases.
{% endstep %}

{% step %}

### Deploy

Classify the CRM and write the result into account-level fields.
{% endstep %}
{% endstepper %}

Schemas can use broad categories and subcategories. For example, a top-level category such as *Healthcare*, *Financial Services*, or *Education* can include more specific subcategories such as *Digital Health Platforms*, *WealthTech Advisories*, or *EdTech SaaS*.

Each category and subcategory can include definitions, inclusion rules, exclusion criteria, and examples. Kernel uses those rules to map account evidence to the best matching category. When an account could fit multiple categories, Kernel follows the schema hierarchy and returns reasoning for the assignment.

The output is a structured industry value that can be reviewed, synced to your CRM, and used in downstream workflows.

<figure><img src="/files/hImOSqPEFeRZMGOLcxaT" alt="A classified account showing the assigned vertical, confidence, and reasoning"><figcaption><p>A classified account: the assigned vertical, confidence, and the reasoning behind it</p></figcaption></figure>

## Key data points

| Label                | Description                                                                  |
| -------------------- | ---------------------------------------------------------------------------- |
| Custom industry      | Customer-defined category assigned by Kernel.                                |
| Custom sub-industry  | More specific customer-defined subcategory, when the schema uses one.        |
| Industry reasoning   | Explanation of why Kernel assigned the category or subcategory.              |
| NAICS classification | Standard NAICS classification when available or configured for the workflow. |

## Configuration

Custom industry classifications can be configured within the Kernel app. You can review the schema, configure conditional subcategories, and define what does or does not belong in each category.

Your Solutions Engineer can help test the schema during implementation.

### Pre implementation

Before implementation, define the category structure you want Kernel to apply. For each category and subcategory, list what should be included, what should be excluded, and examples of edge cases. This helps Kernel distinguish similar accounts and keep assignments consistent. Your Solutions Engineer can also help make the schema MECE (Mutually Exclusive, Collectively Exhaustive) where possible.

### Demonstration

Use the video below as a guide to configure custom industry classifications in the Kernel platform. For additional support, work with your dedicated Solutions Engineer.

{% embed url="<https://www.loom.com/share/98414ac5815749ad8ac6a1c1b41f8b5f>" %}

See the [Kernel Account object reference](/integrations/salesforce-integration/kernel-account-object-reference#industry) for field types and help text.


# Data dictionary

Every field Kernel resolves and enriches for a company, independent of any CRM

The data dictionary lists every field Kernel resolves and enriches for a company.

Kernel builds a company record in three steps, which map to the three sections below: resolve the identity, map the hierarchy, then enrich firmographics. For the Salesforce field names and types, see the [Kernel Account object reference](/integrations/salesforce-integration/kernel-account-object-reference).

## Entity resolution

Matching a record to the real-world company behind it: its canonical identity, classification, and how confidently Kernel resolved it. See the [Entity resolution](/concepts/entity-resolution) concept and [Core entity data](/data/entity-data/core-entity-data).

### Entity

| Field                     | Type       | Description                                                                                                                                                                                     | Worked example                                                                                                                                                  |
| ------------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| KERN ID                   | Identifier | Kernel's stable, unique identifier for the resolved company. Ten digits; the same company always resolves to the same KERN ID.                                                                  | `4329828950`                                                                                                                                                    |
| Legal name                | Text       | Registered legal name of the entity.                                                                                                                                                            | SEPHORA SAS                                                                                                                                                     |
| Trading name              | Text       | Primary name the company goes to market under.                                                                                                                                                  | Sephora                                                                                                                                                         |
| Website                   | URL        | Primary website for the entity.                                                                                                                                                                 | `https://www.sephora.fr`                                                                                                                                        |
| Country of incorporation  | Text       | Country where the entity is legally registered.                                                                                                                                                 | France                                                                                                                                                          |
| Entity category           | Enum       | Top-level type of organisation. Values: Company, Government, Education.                                                                                                                         | Company                                                                                                                                                         |
| Entity sub-category       | Enum       | Sub-type within the category, showing how the entity sits in a corporate structure. Values: Operating, HoldCo / Investment, Business unit, Establishment (plus Government and Education types). | Operating                                                                                                                                                       |
| Legal name (Reasoning)    | Long text  | Why Kernel chose this legal name, with the supporting evidence.                                                                                                                                 | "The legal notice on sephora.fr names SEPHORA SAS (SIREN 393 712 286) at its Neuilly-sur-Seine head office; the French company register lists the same entity." |
| Entity match (Reasoning)  | Long text  | Why Kernel matched the record to this company.                                                                                                                                                  | "Domain sephora.fr, the LinkedIn company page, and the French company register (SIREN 393 712 286) all resolve to the same entity."                             |
| Entity match (Confidence) | Enum       | How strong the evidence is for the identity match. Values: HIGH, MEDIUM, LOW.                                                                                                                   | HIGH                                                                                                                                                            |

See [KERN ID](/concepts/kern-id) for the identifier, and [Entity categories](/data/entity-data/entity-categories) for the category and sub-category values.

## Hierarchies

Placing the company in its corporate tree: the immediate parent one level up, the top parent at the apex, the top operating parent (the highest operating company, skipping holding entities), and regional-subsidiary flags.

### Immediate parent

The direct parent one level up. Empty when the company is standalone. In the worked chain the focal company is Sephora, whose immediate parent is LVMH. See [Parent relationships](/data/hierarchies/parent-relationships).

| Field                           | Type       | Description                                                                                             | Worked example                                                                 |
| ------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| Parent KERN ID                  | Identifier | KERN ID of the immediate parent.                                                                        | `4648473239`                                                                   |
| Parent legal name               | Text       | Legal name of the immediate parent.                                                                     | LVMH Moët Hennessy Louis Vuitton                                               |
| Parent trading name             | Text       | Trading name of the immediate parent.                                                                   | LVMH                                                                           |
| Parent website                  | URL        | Website of the immediate parent.                                                                        | `https://www.lvmh.com`                                                         |
| Parent country of incorporation | Text       | Country where the immediate parent is legally registered.                                               | France                                                                         |
| Parent entity category          | Enum       | Category of the immediate parent. Values: Company, Government, Education.                               | Company                                                                        |
| Parent entity sub-category      | Enum       | Sub-type of the immediate parent. Values: Operating, HoldCo / Investment, Business unit, Establishment. | Operating                                                                      |
| Parent (Reasoning)              | Long text  | Why Kernel identified this parent.                                                                      | "Sephora is a wholly-owned maison within LVMH's Selective Retailing division." |
| Parent (Confidence)             | Enum       | Confidence in the parent relationship. Values: HIGH, MEDIUM, LOW.                                       | HIGH                                                                           |

### Top parent

The ultimate parent at the top of the corporate tree, and often not the famous brand. For a standalone company this points to the company itself. In the worked chain the top parent is Agache Commandité SAS, the Arnault family holding that sits above LVMH. See [Parent relationships](/data/hierarchies/parent-relationships).

| Field                               | Type       | Description                                                                                       | Worked example                                                                  |
| ----------------------------------- | ---------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| Top parent KERN ID                  | Identifier | KERN ID of the top parent.                                                                        | `8626035538`                                                                    |
| Top parent legal name               | Text       | Legal name of the top parent.                                                                     | Agache Commandité SAS                                                           |
| Top parent trading name             | Text       | Trading name of the top parent.                                                                   | Agache                                                                          |
| Top parent website                  | URL        | Website of the top parent.                                                                        | No public website; resolved via the French company register (SIREN 921 583 266) |
| Top parent country of incorporation | Text       | Country where the top parent is legally registered.                                               | France                                                                          |
| Top parent entity category          | Enum       | Category of the top parent. Values: Company, Government, Education.                               | Company                                                                         |
| Top parent entity sub-category      | Enum       | Sub-type of the top parent. Values: Operating, HoldCo / Investment, Business unit, Establishment. | HoldCo / Investment                                                             |

### Top operating parent

The highest operating company in the tree, skipping holding and investment entities. Use it when the legal top parent is a holding company but the useful GTM anchor is the operating business below it. Empty when the entity is standalone or has no operating parent above it. For Sephora it resolves to Christian Dior SE, the highest operating company above LVMH, skipping the Financière Agache and Agache Commandité SAS holdings. See [Parent relationships](/data/hierarchies/parent-relationships).

| Field                                         | Type       | Description                                                                                                 | Worked example                 |
| --------------------------------------------- | ---------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------ |
| Top operating parent KERN ID                  | Identifier | KERN ID of the highest operating company above this entity, skipping holding and investment entities.       | `5743308307`                   |
| Top operating parent legal name               | Text       | Legal name of the top operating parent.                                                                     | Christian Dior SE              |
| Top operating parent trading name             | Text       | Trading name of the top operating parent.                                                                   | Christian Dior                 |
| Top operating parent website                  | URL        | Website of the top operating parent.                                                                        | `https://www.dior-finance.com` |
| Top operating parent country of incorporation | Text       | Country where the top operating parent is legally registered.                                               | France                         |
| Top operating parent entity category          | Enum       | Category of the top operating parent. Values: Company, Government, Education.                               | Company                        |
| Top operating parent entity sub-category      | Enum       | Sub-type of the top operating parent. Values: Operating, HoldCo / Investment, Business unit, Establishment. | Operating                      |

### Regional subsidiary

Flags a company that operates under the parent's brand for a specific country or region. Sephora itself is the global brand, so it is not regional; a national arm such as Sephora USA, Inc. would be flagged, with scope United States. See [Regional subsidiaries](/data/hierarchies/regional-subsidiaries).

| Field                      | Type      | Description                                                                                 | Worked example                                                                                           |
| -------------------------- | --------- | ------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| Is regional subsidiary     | Boolean   | Whether the company is a regional subsidiary of its parent. Values: true, false.            | false (Sephora is the global brand)                                                                      |
| Regional scope             | Text      | The country or region a regional subsidiary covers. Empty when the company is not regional. | empty for Sephora; e.g. "United States" for Sephora USA                                                  |
| Regional scope (Reasoning) | Long text | Why Kernel flagged the company as regional or not.                                          | "Sephora is a global brand, not a legal entity tied to one country, so its national arms roll up to it." |

## Firmographics

Enriching the resolved company with commercial attributes. Values are given at entity scope (this company only) and consolidated scope (the company plus its subsidiaries) where relevant.

### Headcount

See [Headcount](/data/firmographics/headcount) for how Kernel researches employee counts.

| Field                    | Type      | Description                                                                                                            | Worked example                                                                                                                                      |
| ------------------------ | --------- | ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| Headcount (Entity)       | Number    | Employees at this specific entity, excluding subsidiaries.                                                             | 5,595 (SEPHORA, France)                                                                                                                             |
| Headcount (Consolidated) | Number    | Employees at this entity plus its subsidiaries, where available.                                                       | 33,770 (Sephora worldwide)                                                                                                                          |
| Headcount (Recommended)  | Number    | Kernel's recommended headcount to use, chosen between entity and consolidated based on the company's role in its tree. | 33,770 (consolidated view)                                                                                                                          |
| Headcount (Reasoning)    | Long text | Rationale and evidence for the headcount, including the scope chosen.                                                  | "French entity headcount 5,595; consolidated 33,770 across Sephora's worldwide operations. Consolidated recommended as the brand's effective size." |
| Headcount (Confidence)   | Enum      | Evidence strength for the headcount figure. Values: High, Medium, Low.                                                 | High                                                                                                                                                |

### Revenue

See [Revenue](/data/firmographics/revenue) for scope and currency handling.

| Field                            | Type      | Description                                                           | Worked example                                                                                                                                                                          |
| -------------------------------- | --------- | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Revenue (Entity, USD)            | Currency  | Annual revenue for this specific entity, converted to USD.            | $2,866,219,200                                                                                                                                                                          |
| Revenue (Entity, local)          | Currency  | Annual revenue for this specific entity, in its reporting currency.   | €2,510,000,000                                                                                                                                                                          |
| Revenue (Entity, currency)       | Text      | Reporting currency for the entity revenue (ISO 4217).                 | EUR                                                                                                                                                                                     |
| Revenue (Consolidated, USD)      | Currency  | Revenue for this entity plus its subsidiaries, converted to USD.      | $20,951,948,160                                                                                                                                                                         |
| Revenue (Consolidated, local)    | Currency  | Revenue for this entity plus its subsidiaries, in reporting currency. | €18,348,000,000                                                                                                                                                                         |
| Revenue (Consolidated, currency) | Text      | Reporting currency for the consolidated revenue.                      | EUR                                                                                                                                                                                     |
| Revenue (Recommended, USD)       | Currency  | Kernel's recommended revenue to use, converted to USD.                | $20,951,948,160 (consolidated)                                                                                                                                                          |
| Revenue (Recommended, local)     | Currency  | Recommended revenue in reporting currency.                            | €18,348,000,000                                                                                                                                                                         |
| Revenue (Recommended, currency)  | Text      | Reporting currency for the recommended revenue.                       | EUR                                                                                                                                                                                     |
| Revenue (Reasoning)              | Long text | Explanation of the revenue figure, currency, and scope.               | "French entity revenue €2.51B from FY2024 filed accounts; consolidated ≈ €18.3B across Sephora worldwide, converted to USD. Consolidated recommended, since Sephora is a global brand." |
| Revenue (Confidence)             | Enum      | Evidence strength for the revenue figure. Values: High, Medium, Low.  | Medium                                                                                                                                                                                  |

### Industry

A custom industry vertical mapped to your configured schema, plus full six-level NAICS. See [Industry (custom)](/data/firmographics/custom-verticals).

| Field                           | Type      | Description                                                       | Worked example                                                                                   |
| ------------------------------- | --------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| Custom industry                 | Long text | Industry vertical assigned using your configured industry schema. | Consumer Retail                                                                                  |
| Custom industry (Reasoning)     | Long text | Why Kernel assigned this vertical.                                | "Core business is prestige beauty and cosmetics retail; mapped to the 'Consumer Retail' schema." |
| Custom sub-industry             | Long text | Sub-vertical assigned using your configured schema.               | Beauty & Personal Care                                                                           |
| Custom sub-industry (Reasoning) | Long text | Why Kernel assigned this sub-vertical.                            | "Specialty beauty retailer spanning fragrance, make-up and skincare."                            |
| NAICS sector                    | Text      | NAICS classification, sector (2-digit).                           | `44-45` Retail Trade                                                                             |
| NAICS subsector                 | Text      | NAICS classification, subsector (3-digit).                        | `456` Health & Personal Care Retailers                                                           |
| NAICS industry group            | Text      | NAICS classification, industry group (4-digit).                   | `4561` Health & Personal Care Retailers                                                          |
| NAICS industry                  | Text      | NAICS classification, industry (5-digit).                         | `45612` Cosmetics, Beauty Supplies & Perfume Retailers                                           |
| NAICS national industry         | Text      | NAICS classification, national industry (6-digit).                | `456120` Cosmetics, Beauty Supplies & Perfume Retailers                                          |

### Location

Operating location (where the company actually works) versus registered address (its legal or compliance address). For Sephora the two match at 41 rue Ybry. They diverge for a specific site: the Sephora Neuilly-sur-Seine store (KERN `6816281905`) operates at 68 Avenue Charles de Gaulle, while its registered seat stays 41 rue Ybry. See [Location](/data/firmographics/location).

| Field                          | Type      | Description                                   | Worked example                                                                        |
| ------------------------------ | --------- | --------------------------------------------- | ------------------------------------------------------------------------------------- |
| Operating address              | Text      | Full operating address.                       | 41 rue Ybry, 92200 Neuilly-sur-Seine, France                                          |
| Operating street               | Text      | Operating street address.                     | 41 rue Ybry                                                                           |
| Operating city                 | Text      | Operating city.                               | Neuilly-sur-Seine                                                                     |
| Operating state                | Text      | Operating state or province.                  | Île-de-France                                                                         |
| Operating country              | Text      | Operating country.                            | France                                                                                |
| Operating country code         | Text      | Operating country code (ISO 3166-1 alpha-2).  | FR                                                                                    |
| Operating postcode             | Text      | Operating postcode.                           | 92200                                                                                 |
| Operating address (Reasoning)  | Long text | Why Kernel selected this operating location.  | "Head office per the company's legal notice and the French company register."         |
| Registered address             | Text      | Full legal registered address.                | 41 rue Ybry, 92200 Neuilly-sur-Seine, France                                          |
| Registered street              | Text      | Registered street address.                    | 41 rue Ybry                                                                           |
| Registered city                | Text      | Registered city.                              | Neuilly-sur-Seine                                                                     |
| Registered state               | Text      | Registered state or province.                 | Île-de-France                                                                         |
| Registered country             | Text      | Registered country.                           | France                                                                                |
| Registered country code        | Text      | Registered country code (ISO 3166-1 alpha-2). | FR                                                                                    |
| Registered postcode            | Text      | Registered postcode.                          | 92200                                                                                 |
| Registered address (Reasoning) | Long text | Why Kernel chose this registered address.     | "Registered seat (siège social) from the French company register, SIREN 393 712 286." |

### Operational status

See [Operational status](/data/firmographics/operational-status).

| Field                          | Type      | Description                                                                                                                                                                                     | Worked example                                                                                         |
| ------------------------------ | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| Operational status             | Enum      | Whether the company still operates under its own brand. Values: Active, Absorbed, Out of business, Undetermined. There is no separate confidence field; Undetermined is the uncertainty marker. | Active                                                                                                 |
| Operational status (Reasoning) | Long text | Why Kernel assigned this operational status.                                                                                                                                                    | "Live e-commerce site (sephora.fr), current legal filings, and active hiring under the Sephora brand." |

### LinkedIn

Attributes read from the matched LinkedIn company page. Kernel treats LinkedIn as one signal among many, and always cross-checks it against filings and first-party sources. See [LinkedIn URL](/data/entity-data/linkedin-url).

| Field                           | Type      | Description                                         | Worked example                                                                                                       |
| ------------------------------- | --------- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| LinkedIn - URL                  | URL       | LinkedIn company page URL.                          | `https://www.linkedin.com/company/sephora`                                                                           |
| LinkedIn - description          | Long text | Company description shown on LinkedIn.              | "Sephora is the world's leading global prestige beauty retail brand…"                                                |
| LinkedIn - industry             | Text      | Self-declared industry shown on LinkedIn.           | Retail                                                                                                               |
| LinkedIn - company type         | Text      | Company type shown on LinkedIn.                     | Privately Held                                                                                                       |
| LinkedIn - founded year         | Number    | Founded year shown on the matched LinkedIn page.    | 1969                                                                                                                 |
| LinkedIn - company size range   | Text      | Company size band shown on LinkedIn.                | 10,001+ employees                                                                                                    |
| LinkedIn - headcount            | Number    | Member headcount shown on LinkedIn.                 | 51,289                                                                                                               |
| LinkedIn - headcount growth 12m | Number    | 12-month member-headcount growth shown on LinkedIn. | +11%                                                                                                                 |
| LinkedIn - headcount growth 24m | Number    | 24-month member-headcount growth shown on LinkedIn. | +25%                                                                                                                 |
| LinkedIn - country              | Text      | Country shown on LinkedIn.                          | France                                                                                                               |
| LinkedIn - state                | Text      | State shown on LinkedIn.                            | Île-de-France                                                                                                        |
| LinkedIn - city                 | Text      | City shown on LinkedIn.                             | Neuilly-sur-Seine                                                                                                    |
| LinkedIn - all locations        | Long text | All office locations listed on LinkedIn.            | France · United States · Germany · Italy · Spain · UAE · China · South Korea · Singapore · Brazil · Canada · Mexico… |


# Overview

Find your way around the Kernel app and understand what the Dashboard shows.

The Kernel app is where you review your data, check what Kernel has processed, and manage changes to your CRM. The [Dashboard](https://app.kernel.ai/) is the best place to start.

<figure><img src="/files/Fk04PIiru8XgNY5gfxwU" alt="Kernel Dashboard Overview tab showing open actions, records corrected, and the data accuracy map"><figcaption><p>The Overview tab shows the work waiting for review and how recommendations are distributed by action and risk.</p></figcaption></figure>

{% hint style="info" %}
The numbers and sections you see depend on your CRM connection and the features enabled for your workspace.
{% endhint %}

## Dashboard

The Dashboard has three tabs: Overview, Usage, and Events.

| If you want to know\...                              | Start here                                              |
| ---------------------------------------------------- | ------------------------------------------------------- |
| What needs attention?                                | **Overview** for open actions and the data accuracy map |
| What has completed or failed in a period?            | **Usage** for totals, trends, and status                |
| What is running, or what happened in a specific run? | **Events** for recent processing activity               |

### Overview

The [Overview tab](https://app.kernel.ai/) gives you a quick read on the state of your data:

* **Open actions** shows how many recommendations are waiting.
* **Records corrected** shows recent completed work.
* **Data accuracy map** groups pending records by recommended action and risk tier. Select a cell to open the matching action queue.
* **Explore** takes you to [Accounts](/app/accounts), where you can inspect individual records.

### Usage

The [Usage tab](https://app.kernel.ai/?tab=usage) shows how much work Kernel has processed over a selected period. Use the date controls and, when available, the inbound flow filter to narrow the results.

You can see total results, completed results, errors, usage over time, and the status breakdown. Some workspaces also show request speed and let you open the records behind a result.

### Events

The [Events tab](https://app.kernel.ai/?tab=events) is a history of processing activity. It shows when each event started, how long it ran, its status, and who or what triggered it. Open an event to see its progress and results.

## Find your way around the app

The main navigation follows the way data moves through Kernel.

| Area                           | What it is for                                                                                                        |
| ------------------------------ | --------------------------------------------------------------------------------------------------------------------- |
| [CRM](/app/crm)                | Connect your CRM, choose which CRM objects Kernel can use, configure updates, and review syncs.                       |
| [Data setup](/app/data-setup)  | Map CRM fields, set risk controls, configure entity resolution, and monitor inbound flows.                            |
| [Accounts](/app/accounts)      | Search imported accounts, choose the fields you want to see, inspect identity and hierarchy data, and export results. |
| [Data actions](/app/actioning) | Set rules and work through associate, delete, and merge recommendations.                                              |
| [Enrichment](/app/enrichment)  | See the data Kernel provides, define custom verticals, and run bulk enrichment when it is enabled.                    |

{% hint style="info" %}
If an area is not visible in your navigation, it may not be enabled for your workspace. Your Kernel contact can confirm what is available.
{% endhint %}


# CRM

Connect Salesforce, choose where Kernel writes data, and review each CRM sync.

The CRM area controls how Kernel connects to Salesforce, which objects it can read, where results are written, and what happened during each sync.

Start by deciding where Kernel data should go. Kernel recommends the Kernel Account custom object, `Kernel_Account__c`, so you can review Kernel data without replacing customer-owned fields on the Salesforce Account. A mapping can also write to Account or move data between two CRM objects when that is part of your setup.

{% hint style="info" %}
The [Kernel Account custom object](/integrations/salesforce-integration/custom-object) is the recommended destination for most setups. See the [object reference](/integrations/salesforce-integration/kernel-account-object-reference) for its standard fields.
{% endhint %}

## Connection

[Open Connection in Kernel](https://app.kernel.ai/settings/crm-connection)

A Salesforce admin connects the org with an integration user. Salesforce permissions decide what Kernel can read and write.

The integration user normally needs:

* Read access to the source objects and fields that Kernel imports
* Read, Create, and Edit access to the destination object, such as `Kernel_Account__c`
* Read and Edit access to each destination field included in a sync mapping

Connection shows the active Salesforce environment and connection status. It also contains the nightly CRM import setting and the automatic refresh frequency for updated Kernel data.

{% hint style="info" %}
Before a larger sync, test one known account. Check that the value reaches the expected Salesforce object and field, then use the same mapping for the wider set.
{% endhint %}

For installation and permission details, see [Salesforce integration](/integrations/salesforce-integration).

## CRM objects

[Open CRM objects in Kernel](https://app.kernel.ai/crm/crm-objects)

CRM objects has two tabs:

* **Ingested objects** is where you bring in extra Salesforce data, such as Opportunity data.
* **New accounts** maps Kernel fields or fixed values to the Salesforce fields used when a workflow creates a record.

Use this page to confirm that Kernel can see the right object before you configure field mappings or run a sync.

## Configuration

[Open Configuration in Kernel](https://app.kernel.ai/crm-sync/configuration)

Configuration defines where Kernel writes data and which fields are enabled.

* **CRM push** maps Kernel fields to a Salesforce object, such as Kernel Account or Account.
* **In-CRM sync** maps data from one Salesforce object to another inside your CRM.

Open a mapping to choose the destination fields. If your team uses impact sets, you can also preview whether a sync would move accounts into or out of an important segment.

<figure><img src="/files/FTlN4AlLZIJYPMMaKVC5" alt="CRM Configuration page showing mappings from Kernel to the Kernel Account custom object and Salesforce Account"><figcaption><p>Each configuration names the destination object and shows how many fields are enabled.</p></figcaption></figure>

{% hint style="info" %}
Field mapping is directional. Check the source, destination object, and destination field before saving. A field with a similar name may still belong to a different Salesforce object.
{% endhint %}

## Syncs

[Open Syncs in Kernel](https://app.kernel.ai/crm-sync/syncs)

Syncs is the run history for CRM updates. Select a mapping to start a sync, then use the table to follow its progress.

Each run shows the destination object, number of accounts, successes, errors, and current status. Open **Monitor** for record-level details and generated files. This is the first place to check whether a run finished and whether every selected account reached Salesforce.

For scheduled and on-demand refresh behavior, see [Automatic refresh](/integrations/salesforce-integration/enrich-with-kernel).


# Data setup

Connect your data, map CRM fields, set risk rules, and approve entity resolution.

Data setup prepares your CRM data for cleaning, hierarchy, and enrichment work. The setup path follows the same order as the app:

[Open Data setup in Kernel](https://app.kernel.ai/setup)

**Connect → Map fields → Risk scoring → Entity resolution**

Complete the steps in order. A later step relies on the connection and field choices made earlier.

{% hint style="info" %}
The exact setup path depends on your integration. Salesforce workspaces use all four steps. Other integrations, including S3, can follow a shorter path.
{% endhint %}

## 1. Connect

[Open Connection in Kernel](https://app.kernel.ai/settings/crm-connection)

Connect the Salesforce environment that Kernel should use. Confirm that the integration user can read the source account data and edit the Kernel destination fields.

The [Salesforce integration](/integrations/salesforce-integration) guide covers installation, authorization, and permissions. CRM destinations and sync history are covered on [CRM](/app/crm).

## 2. Map fields

[Open Field mapping in Kernel](https://app.kernel.ai/settings/field-mapping)

Field mapping tells Kernel where to find each input in your CRM. For example, map the Kernel **Account URL** input to the Salesforce Website field and **Parent Account ID** to the Salesforce Parent ID field.

The **Relevant for** labels show where each mapping is used, such as account identity resolution, cleaning actions, or risk tiering. Map the fields your team trusts. A reliable name, website, legal name, address, or parent can improve a difficult match. An unreliable field can make it worse.

<figure><img src="/files/Ic5fqa12SL6XIozXiDHG" alt="The Field mapping page matching Kernel inputs to Salesforce Account fields and showing what each mapping is used for."><figcaption><p>Each row maps one Kernel input to a CRM field. The labels on the right show which workflows use it.</p></figcaption></figure>

{% hint style="info" %}
Field mapping defines what Kernel reads. It does not choose where enrichment is written back. Configure write-back separately under [CRM configuration](https://app.kernel.ai/crm-sync/configuration).
{% endhint %}

## 3. Risk scoring

[Open Risk scoring in Kernel](https://app.kernel.ai/cleaning/risk-management)

Risk scoring controls how cautiously Kernel treats an account before recommending a cleaning action. You can set risk tiers, score CRM activity and related records, and add safeguards for accounts that should not be changed automatically.

Test the configuration on a sample before saving it for the full CRM. See [Risk management](/app/data-setup/configuration) for safeguards, scores, and tiers, and [Active users](/app/data-setup/active-users) for how account ownership affects risk.

## 4. Entity resolution

[Open Entity resolution in Kernel](https://app.kernel.ai/cleaning/master-data)

Entity resolution matches each CRM record to the real company behind it and assigns a [KERN ID](/concepts/kern-id). Review a sample before approval. Look at the matched entity, reasoning, confidence, and any fields that changed.

The identity mode tells Kernel which input to lean on when the record is ambiguous. URL bias leans on the website. Name bias leans on the account name. Other reliable evidence, such as a legal name, address, or contact domain, can still decide the match.

When the sample looks right, approve and lock the configuration. See [Entity resolution](/concepts/entity-resolution) for the full matching model.

{% hint style="info" %}
Approval locks the identity configuration used for future runs. Review the sample and selected identity mode before you approve it.
{% endhint %}

## Monitor inbound flows

[Open Inbound flows in Kernel](https://app.kernel.ai/cleaning/inbound-flow)

Inbound flows is a monitoring view today. It lists existing scheduled and API flows with their schedule, number of field mappings, status, and last update.

You cannot create a new inbound flow from this page yet. Use it to check whether an existing flow is active and when it last changed. Work with the Kernel team when a new flow or configuration change is needed.


# Risk management

Set risk tiers and safeguards before running data actions

[Open Risk management in Kernel](https://app.kernel.ai/cleaning/risk-management)

Risk management tells Kernel how cautious to be with each account. It combines safeguards, a risk score, and account ownership signals to decide which changes can run in bulk and which ones need review.

{% embed url="<https://pub-8e1d08f439ec43bdbb79f1055a273a02.r2.dev/configure-risk-change-management-04926186.mp4>" %}

## Risk scores and tiers

Every account receives a score from 0 to 100 and a corresponding risk tier. A higher score means the account needs more care before it is changed.

<figure><img src="/files/UrRVSZgqzmzgl01Z2FCB" alt="Four Kernel risk tiers, from very low risk to high risk"><figcaption><p>Risk tiers turn the underlying score into a practical review threshold.</p></figcaption></figure>

The default tiers are:

* **Very low risk:** safe to include in bulk actions
* **Low risk:** likely safe to include in bulk actions
* **Medium risk:** review case by case
* **High risk:** review or exclude

Your Kernel team can adjust the score thresholds for your workspace.

## Safeguards

Safeguards take priority over the risk score. If an account matches a safeguard, Kernel gives it a score of 100 and protects it from destructive actions.

Common safeguards include:

* accounts with open opportunities
* accounts where `Type` is Customer
* accounts with associated ARR

You can build safeguards with standard or custom fields on the Account object.

## Risk scoring

For accounts that do not match a safeguard, Kernel calculates a score from CRM activity and context. The standard inputs include:

* recent activity, using `LastActivityDate`
* record age, using `CreatedDate`
* open and closed opportunities
* tasks from the last three years
* associated contacts
* owner type
* completion of important CRM fields

Each input has a weight from Very low to Very high. You can change those weights or add fields that matter to your team.

Higher-scoring records are also more likely to remain as the primary account when duplicates are merged.

### Custom fields

Any CRM field can contribute to the score. Choose the field, set the conditions that matter, and give it a weight that reflects its importance.

<figure><img src="/files/z2VftX0Bl0TaAjjqHGFn" alt="Custom CRM fields added to Kernel risk scoring"><figcaption><p>Custom fields let the risk model reflect the way your team manages accounts.</p></figcaption></figure>

## Account ownership

Account ownership is part of the risk model. An account may be a duplicate or belong to a closed business, but changing it can still disrupt a rep who is actively working it.

Use [Active users](/app/data-setup/active-users) to define active, inactive, and integration users. Kernel uses those definitions when it evaluates account ownership.

## How risk management is used

Start bulk cleaning with very low and low-risk accounts. Review higher-risk accounts separately or exclude them from the action. This lets most of the CRM move forward while sensitive records get more attention.

See [Data actions](/app/actioning) for how to review and run account changes with these controls.


# Active users

Kernel's customizable algorithm detects which accounts are owned by active sales reps.

## Account ownership

The purpose of Kernel's AI Cleaning & Hierarchies module is to bring the CRM into a functional state *without* risking any existing business processes.

When making structural changes to a live CRM, one of the most important considerations is whether an account is actively owned and actively managed by a sales rep.

An account may be out of business or an exact duplicate of another account, but deleting or merging this account may still be disruptive to a sales rep who was actively managing this account.

Account ownership is an important part of [risk management](/app/data-setup/configuration). Kernel uses these user categories to understand whether an account is actively managed.

## User definitions

Kernel categorizes all users as either Active, Inactive, or Integration

### How categorization works

Kernel applies user rules in a specific order of priority:

***

1. **Your custom rules (highest priority)**
   * **User overrides**: Any user you’ve manually assigned to a category will be placed there first.
   * **Custom conditions**: If you’ve defined custom conditions for Active, Inactive, or Integration, these rules are applied before Kernel’s defaults.

This means your rules always take precedence over Kernel’s logic.

***

2. **Kernel’s default logic**\
   If a user doesn't match any of your custom rules, we apply our default logic in the following order:
   * **Integration users**:

     A user is marked as Integration if they meet any of these conditions. If they do, no further rules are checked.

     * Keyword Match: Their Name, Username, or Profile Name contains terms like "integration," "salesforce," or "zoominfo" (case-insensitive).
     * API Permission: Profile has API-only permissions.
     * User Type: Their user type field is set to 'Integration' or 'Api Only User'.
   * **Inactive users**:

     If the user is not an Integration user, we'll mark them as Inactive if they meet any of these conditions:

     * Inactive Flag: Their `IsActive` field is set to false.
     * Last Login: Their `LastLoginDate` was more than 90 days ago.
   * **Active users:**

     Finally, a user is marked Active if they are not categorized as Integration or Inactive by any of the rules above.

***

### Summary table

<table><thead><tr><th width="157.0390625">Category</th><th>Definition</th></tr></thead><tbody><tr><td><h4>Active</h4></td><td>Active human users, e.g., SDRs and Account Executives.</td></tr><tr><td><h4>Inactive</h4></td><td>Inactive human users, including users who have not been active <a data-footnote-ref href="#user-content-fn-1">for at least 3 months</a>, as well as RevOps and System Administrators.</td></tr><tr><td><h4>Integration</h4></td><td>Integration (robot) users, e.g., users called “Integration User” or users with a Profile permission set that gives API access only.</td></tr></tbody></table>

## User fields

Kernel uses the following standard fields in Salesforce:

* `Name`
* `Username`
* `Title`
* `ProfileId` (--> `Profile.Name`)
* `LastLoginDate`
* `IsActive`
* `UserType`

Kernel's algorithm uses a combination of static rules, keyword matching, and AI analysis to determine which type of user it is.

<table><thead><tr><th width="196.2890625">Name</th><th width="119.67578125">Title</th><th width="110.11328125">IsActive</th><th width="151.56640625">LastLoginDate</th><th>User type<select><option value="85tv6bFe8TSM" label="Active" color="blue"></option><option value="gKx7pNwpjghg" label="Inactive" color="blue"></option><option value="vx4qRZwn4CKu" label="Integration" color="blue"></option></select></th></tr></thead><tbody><tr><td>Gary Smith</td><td>Account Executive</td><td>false</td><td>[6 months ago]</td><td><span data-option="gKx7pNwpjghg">Inactive</span></td></tr><tr><td>Ringlead Integration</td><td>null</td><td>true</td><td>[1 day ago]</td><td><span data-option="vx4qRZwn4CKu">Integration</span></td></tr><tr><td>Betty Smith</td><td>SDR</td><td>true</td><td>[3 days ago]</td><td><span data-option="85tv6bFe8TSM">Active</span></td></tr><tr><td>Zak Benney</td><td>RevOps</td><td>true</td><td>[1 day ago]</td><td><span data-option="gKx7pNwpjghg">Inactive</span></td></tr><tr><td>Chad Smith</td><td>Account Executive</td><td>true</td><td>[6 months ago]</td><td><span data-option="gKx7pNwpjghg">Inactive</span></td></tr></tbody></table>

## Custom logic

Kernel's standard logic can be modified to your needs, and also augmented with custom logic, such as using custom fields on the User object in the logic. For example, these may be fields related to cost centers, segments, managers, or title variations.

If you don't use the native `OwnerId` field to track account ownership, e.g., using a custom field, Kernel can also accommodate this.

<figure><img src="/files/XXhCF4UfwRgYJy50dv60" alt=""><figcaption></figcaption></figure>

[^1]: Kernel's default period is 3 months, but this is fully configurable by the user


# Accounts

Browse imported accounts, inspect identity and hierarchy data, and export the view you need.

Accounts is where you inspect the records Kernel has imported from your CRM. Use [Explore](https://app.kernel.ai/accounts) to find an account, review what Kernel matched it to, and choose which CRM and Kernel fields you want to see.

<figure><img src="/files/uG23XWikcGA6cgbFZMpq" alt="Kernel Accounts page with account rows, Kernel fields, filters, export, view settings, and search"><figcaption><p>Explore brings CRM records and Kernel fields into one view, so you can review the result without switching between tools.</p></figcaption></figure>

## Explore accounts

The toolbar above the table contains the main controls:

* **Search** finds accounts by name or URL.
* **Filter** narrows the list by field values. Salesforce workspaces can use field rules or SOQL, and can include records that have been deleted from the CRM.
* **View settings** lets you choose which Kernel and CRM fields appear as columns.
* **Export CSV** downloads the accounts in your current scope.

Each row starts with the account name and website. The remaining columns come from your current view settings. Select a row to open its details.

{% hint style="info" %}
Start with a small set of fields that answer the question you are working on. You can add more CRM or Kernel columns at any time.
{% endhint %}

## Understand the identity

The table brings together a CRM record and Kernel's resolved identity. These terms describe different things:

| Term               | What it means                                                                                                                                                                |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **CRM record**     | The account row stored in your CRM, with its own CRM ID and fields.                                                                                                          |
| **Matched entity** | The real organization Kernel determined that the CRM record represents. Learn more about [entities](/data/entity-data) and [entity resolution](/concepts/entity-resolution). |
| **KERN ID**        | Kernel's persistent identifier for that matched entity at that exact level. Read [What is a KERN ID?](/concepts/kern-id).                                                    |

{% hint style="info" %}
A KERN ID belongs to the matched entity, not the CRM record. Two CRM records that resolve to the same organization can share a KERN ID. A parent and subsidiary have different KERN IDs because they are different entities.
{% endhint %}

If you use the Kernel Account custom object in Salesforce, **Linked Account** connects that Kernel Account record to its Salesforce Account. It is a Salesforce relationship field. KERN ID identifies the company Kernel resolved the account to.

### A domain is one input, not the whole match

Kernel does not rely on the website alone. When the data is available, entity resolution can also use the account name, legal name, country, address, LinkedIn URL, related and contact domains, parent relationships, and opportunity context.

Map the relevant source fields in [Data setup](https://app.kernel.ai/settings/field-mapping) so Kernel has the context it needs. The [entity resolution guide](/concepts/entity-resolution) explains how those signals are combined.

### Top operating parent is not the full hierarchy

The **top operating parent** is the highest active operating company in an ownership chain. It is often the most useful anchor for territories, account planning, and go-to-market work.

It is still one point in the hierarchy. Use the hierarchy fields when you need the immediate parent, legal top parent, top operating parent, or the full chain between them. Read [Hierarchies](/data/hierarchies), [Parent relationships](/data/hierarchies/parent-relationships), and the [Data dictionary](/data/data-dictionary) for field definitions.

{% hint style="info" %}
Use top operating parent for a practical operating group. Use the full hierarchy when legal ownership or every level in the chain matters.
{% endhint %}

## Open account details

Select an account row to open the detail drawer. It puts the main review information in one place:

* KERN ID and CRM ID
* identity confidence
* risk tier and recommended cleaning action
* a comparison of the original CRM values and Kernel's resolved values
* the CRM sync difference, when sync is configured
* all available fields and data freshness information

Controls such as **Change identity**, **Reject identity**, and **Sync to CRM** only appear when they are enabled for your workspace.

## Add accounts

[Add accounts](https://app.kernel.ai/add-accounts) lets eligible Salesforce users select accounts from the CRM and review them before import. The flow covers account filters, field mapping, risk scoring, duplicate checks, and a final review.

{% hint style="info" %}
**Availability:** Add accounts appears when your workspace has an eligible Salesforce connection and plan allowance. If you do not see it, ask your Kernel contact whether it is enabled.
{% endhint %}


# Data actions

Review and run the account changes Kernel recommends for your CRM

Data actions is where you review the account changes Kernel recommends for your CRM. There are three action queues: associate accounts, delete accounts, and merge accounts. A recommendation does not change your CRM on its own.

## Set the rules

The Rules page controls which recommendations Kernel creates and which records it protects. It covers parent associations, deletion criteria, duplicate routing, and deliberate duplicates.

[Open Rules in Kernel](https://app.kernel.ai/cleaning/cleaning-rules)

<figure><img src="/files/RTbxR6r2plu4HQW2PW2C" alt="Rules page in app.kernel.ai showing association safeguards"><figcaption><p>The Rules page controls which association, deletion, and merge recommendations are eligible for review.</p></figcaption></figure>

{% hint style="info" %}
Saving a rule changes the configuration only. Rerun the cleaning action after a rule change, or after the same change was made directly in your CRM, so the queue reflects the current records. Reject a recommendation when you do not want it carried out.
{% endhint %}

## Choose an action

| Page                                                               | What it is for                                                                                               |
| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
| [**Associate accounts**](https://app.kernel.ai/cleaning/associate) | Link a child account to an existing parent, correct its parent, or create a missing parent.                  |
| [**Delete accounts**](https://app.kernel.ai/cleaning/delete)       | Review records Kernel identified as no longer operating or otherwise eligible for removal under your rules.  |
| [**Merge accounts**](https://app.kernel.ai/cleaning/merge)         | Review duplicate groups, confirm the primary record that will remain, and combine the other records into it. |

## Actionable and actioned

Each action page has two views:

| View           | What it contains                                                      |
| -------------- | --------------------------------------------------------------------- |
| **Actionable** | Recommendations that still need a decision or need to be carried out. |
| **Actioned**   | Recommendations that were completed or rejected.                      |

Rejecting a recommendation moves it out of the active queue without changing the CRM. It remains in Actioned as part of the history.

## Review and execute

Open a recommendation and check the proposed change, Kernel's reasoning, risk tier, and relevant CRM fields. For an association, confirm the proposed parent. For a merge, confirm the primary record. For a deletion, make sure the record should be removed.

To accept a recommendation, choose the action shown on the page, such as **Associate**, **Delete**, or **Merge**. The app shows a confirmation before it queues the CRM change. Choose **Reject** when the recommendation should not be carried out. You can make either decision for one item or a selected group.

{% hint style="info" %}
Safeguarded accounts are protected by default. Bulk confirmations show how many records will be acted on and call out protected records that will be skipped.
{% endhint %}

An association updates a parent relationship. Deletion and merging change CRM records more broadly, so review their confirmations carefully before continuing.

## Learn about each action

* [Associate accounts](/app/actioning/associate)
* [Delete accounts](/app/actioning/delete)
* [Merge accounts](/app/actioning/merge)


# Associate accounts

Link child accounts to their parent within your CRM

[Open Associate accounts in Kernel](https://app.kernel.ai/cleaning/associate?view=cards)

The associate action links child accounts to their parent using your CRM's native hierarchy. It does not delete either record, and you can reverse the link in your CRM.

<figure><img src="/files/tzF3xtjYrVtBQRHcDCCf" alt="Associate accounts queue showing a proposed parent and its child records"><figcaption><p>The Associate accounts queue groups child accounts under their proposed parent and shows the evidence and controls used to review the change.</p></figcaption></figure>

## Association types

Kernel identifies three scenarios:

* **Link to existing parent** - the parent account already exists in your CRM. Kernel populates the native Parent Account field to establish the relationship.
* **Reparent** - the account has an existing parent that is incorrect. Kernel updates the link to the correct parent.
* **Create missing parent** - the parent does not exist in your CRM. Kernel creates the parent account, then links the child to it.

## Creating missing parents

When Kernel identifies a parent that is missing from your CRM, it groups all child accounts that share the same parent together. The parent account is created with the relevant firmographic data, and all children are linked in a single operation. Once complete, the cleaning action for these accounts updates to `None`.

## Taking action

The associate action pane in the Kernel app displays all accounts with identified parents. Completed associations can be reviewed, and rejected associations are flagged for review by the Kernel team.

{% embed url="<https://www.loom.com/share/19433041f93140db92bbad9609e1c3f5>" %}


# Delete accounts

Remove dead and dormant accounts from your CRM

[Open Delete accounts in Kernel](https://app.kernel.ai/cleaning/delete?view=cards)

The delete action removes dead and dormant accounts from your CRM. These accounts may belong to closed businesses, have non-functional websites, or use suspicious URLs. Removing them reduces clutter and avoids spending enrichment credits on records you cannot use.

<figure><img src="/files/hOwIfW0mZMZpvWI8lHCb" alt="Delete accounts queue showing risk tiers, reasons, and review controls"><figcaption><p>The Delete accounts queue shows why each record was marked for deletion, its risk tier, and the available review actions.</p></figcaption></figure>

## How deletion works

Kernel evaluates each account against your configured deletion rules, which control parameters such as:

* **Website functionality** - whether the site is working, parked, or offline
* [**Company operational status**](/data/firmographics/operational-status) - whether the business is still active
* **Generic URLs** - whether the account carries a non-corporate URL (e.g., gmail.com)
* [**Active ownership**](/app/data-setup/active-users) - whether the account is actively managed by a sales rep

Safeguarded accounts are always excluded from deletion, regardless of other factors.

## Risk controls

Deletion recommendations respect your [risk tier thresholds](/app/data-setup/configuration). By default, only accounts at or below Medium risk are eligible for deletion. Accounts above your configured threshold are excluded automatically.

## Taking action

Within the Kernel app, you can review all accounts recommended for deletion alongside the reasoning behind each decision. Accounts can be approved or rejected individually or in bulk, filtered by risk tier and CRM fields.

{% hint style="danger" %}
Note: Deleting accounts is a destructive action. Ensure you have appropriately reviewed the data.
{% endhint %}

## Related pages

* [Operational status](/data/firmographics/operational-status)
* [Active users](/app/data-setup/active-users)
* [Risk management](/app/data-setup/configuration)
* [Actions](/app/actioning)


# Merge accounts

How Kernel merges duplicate accounts in Salesforce

[Open Merge accounts in Kernel](https://app.kernel.ai/cleaning/merge?view=cards)

Kernel can merge duplicate Salesforce accounts in bulk without blindly collapsing records that should stay separate.

<figure><img src="/files/GEoPFvtjluQI3v7qwIOH" alt="Merge accounts queue showing primary accounts and proposed duplicates"><figcaption><p>The Merge accounts queue shows the primary account that will remain alongside the duplicate records proposed for merging.</p></figcaption></figure>

The workflow is simple:

1. Kernel finds likely duplicate accounts.
2. You define which duplicates are safe to merge.
3. Kernel groups those records into merge groups.
4. Kernel merges them using Salesforce's native merge API.
5. Kernel logs what happened.

A duplicate group is the full set of records that look related. A merge group is the subset Kernel considers safe to act on after applying your rules.

## What a merge group contains

Each merge group has:

* one primary account, which survives the merge
* one or more duplicate accounts, which are merged into the primary
* a status, such as queued, processing, completed, or rejected

## How Kernel finds duplicates

Kernel scans your CRM and builds candidate duplicate groups using account data and other signals.

For each group, Kernel assigns:

* a group ID
* a confidence score
* a duplicate type
* a recommended primary record

## Duplicate types

Kernel uses the following duplicate types to explain why accounts were grouped together:

| Type     | When Kernel uses it                                                                                                                                                              | Example                                                                                          |
| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| Primary  | The record Kernel recommends keeping. Other accounts in the duplicate group merge into this record.                                                                              | If three CRM records represent Acme, the Primary record is the one that keeps its CRM ID.        |
| Exact    | Accounts share the same Kernel ID, or their legal name, legal country, record name, and trading country all match. Kernel can also use aligned URL, name, and legal name values. | Two CRM records both resolve to the same Acme entity and Kernel ID.                              |
| Location | A physical establishment and its operating company represent the same legal entity and share the same root domain. One account must be classified as an Establishment.           | Marriott London Bridge and Marriott International.                                               |
| Regional | Accounts share the same legal name but represent trading activity in different countries.                                                                                        | Acme France represents the French trading presence of Acme GmbH, which is registered in Germany. |
| Trading  | A trading identity and its legal entity share the same legal name within one country. The legal entity is the Primary record.                                                    | Dove as the trading identity and Unilever as the legal entity.                                   |
| Website  | Accounts share the same URL and name, but their legal names do not need to match. This type is optional and off by default.                                                      | Two Acme accounts both use `acme.com` and the name `Acme`.                                       |

## Choosing the primary record

Each duplicate group has one primary account.

The primary is the record that keeps its Salesforce ID after the merge. The other records are merged into it.

Kernel selects the Primary record using its own algorithm. Risk score is a major input to that decision.

## How field values are merged

By default, Kernel uses a "fill in the blanks" approach.

That means if the primary record has an empty field and a duplicate record has a value, Kernel copies that value to the primary.

You can override this with field-level rules.

Common options include:

* always keep the primary value
* always use the newest value
* use the longest value
* prefer values from a trusted source
* apply custom logic for specific fields

## Related records

### Standard Salesforce objects

Salesforce automatically reparents standard related records during a merge. This includes objects like contacts, tasks, opportunities, and cases.

### Custom objects

For custom objects, Kernel lets you define:

* which objects should be reparented
* which lookup field should be updated
* any special rules for handling those links

## How merge execution works

Kernel uses Salesforce's native merge API.

Important Salesforce limit:

* each merge request can include at most 3 records: 1 primary and 2 duplicates

If a merge group has more than 3 records, Kernel processes it in batches.

During execution:

* the primary record keeps its Salesforce ID
* duplicate records are deleted by Salesforce as part of the merge
* standard related records are reparented automatically
* custom related records are reparented based on your configuration
* field values are updated using your survivorship rules

## Deliberate duplicates

Not every duplicate should be merged.

Many companies keep similar-looking account records on purpose. These records may support billing, channel sales, product ownership, regional operations, or integrations.

Examples:

* separate billing entities
* channel-specific account records
* different business units under the same parent
* regional account records for global companies

Kernel lets you define rules to preserve these deliberate duplicates.

If a record matches those rules, Kernel excludes it from merge actions.

You can define exclusions using:

* custom fields
* owner or team
* account type
* product line
* region or territory
* naming patterns
* source system or external IDs

### Example exclusion rules

Billing entities:

```
IF Account.Billing_Entity__c = TRUE
THEN Exclude from Deduplication
```

Channel accounts:

```
IF Account.Owner.Role CONTAINS "Partner"
OR Account.Type = "Channel"
THEN Exclude from Deduplication
```

Product-line splits:

```
IF Account.Product_Line__c IS NOT NULL
AND Account.Parent_Account__c = <same brand>
THEN Treat as deliberate duplicates
```

## Audit trail and verification

After each merge, Kernel records:

* when the merge ran
* who initiated it
* which records were merged
* which fields changed
* whether the merge succeeded or failed

Kernel also verifies that the merge completed as expected.

## What you control

You control two things:

1. which duplicate groups are allowed to merge
2. which data survives the merge

That gives you a way to clean up CRM duplicates without breaking account ownership, billing structure, reporting, or downstream integrations.


# Enrichment

Understand enrichment data and the tools available in Kernel

Kernel enrichment adds researched company data after matching a CRM record to the right entity. Depending on your setup, this can include entity details, hierarchies, firmographics, and your own industry classifications.

Results may include reasoning and confidence so you can understand how Kernel reached a value. See [Firmographics](/data/firmographics) for an overview or use the [Data dictionary](/data/data-dictionary) to look up a field.

## Enrichment tools

* [Custom vertical modifications](/app/enrichment/custom-verticals) explains how to edit or import the industry structure your team uses.
* [Bulk enrichment](/app/enrichment/bulk-enrichment) explains how to enrich a CSV and export the results.

{% hint style="info" %}
The tools and fields available in your workspace depend on its configuration and field mapping.
{% endhint %}


# Custom vertical modifications

Edit the custom industry structure Kernel uses for your workspace

[Open Custom verticals in Kernel](https://app.kernel.ai/enrichment/custom-verticals)

Custom verticals are the industry categories and subcategories your team uses. Each vertical has a name and an optional definition. You can also add notes for the full schema.

<figure><img src="/files/Hs3Rq0chx85nPAKkK9lJ" alt="Custom vertical schema in Kernel with nested categories and CSV controls"><figcaption><p>Edit the nested structure, import or export it as CSV, and save the version Kernel uses for classification.</p></figcaption></figure>

## Make a change

1. Select a vertical name to edit its name or definition, then select **Done**.
2. Use **Add Main Vertical** to add a top-level category or **Add sub-vertical** to add a category beneath another one.
3. Use the X beside a vertical to remove it.
4. Select **Save** at the bottom of the page to apply your changes.

{% hint style="info" %}
Selecting **Done** closes the inline editor. Your changes are not applied until you select **Save**. The number of category levels allowed is set for your workspace.
{% endhint %}

## Import or export a schema

Use **Import CSV** to add several verticals at once. The CSV uses these columns:

| Column            | What to enter                                                                                    |
| ----------------- | ------------------------------------------------------------------------------------------------ |
| `name`            | The vertical name. This is required for every row.                                               |
| `definition`      | An optional description of what belongs in the vertical.                                         |
| `dependsOn.value` | Leave this blank for a top-level vertical. For a sub-vertical, enter its parent vertical's name. |

The import window includes a template and checks the file before adding it to your draft. Select **Save** after the import to apply it. Use **Export CSV** when you want a copy of the current schema.

See [Industry (custom)](/data/firmographics/custom-verticals) for how Kernel uses the saved structure to classify accounts.


# Bulk enrichment

Enrich a CSV and export the results

[Open Bulk enrichment in Kernel](https://app.kernel.ai/enrichment/bulk-enrichment)

Bulk enrichment lets you enrich a CSV without first adding every row to your CRM.

{% hint style="info" %}
If Bulk enrichment is not available in your workspace, ask Kernel to enable it.
{% endhint %}

## Prepare your CSV

Include a header row and at least one identifier for each company. The identifier can be a company name, website, LinkedIn URL, or CRM ID. You can also map legal name and address fields when they are available.

## Start a batch

1. Select **Choose CSV** and upload the file.
2. Review the detected field mappings and change any that are incorrect.
3. Select **Start batch**.
4. Follow the run until it finishes, then select **Export CSV**.

<figure><img src="/files/7qA8T9M6x9p9ZYAkLOLf" alt="Bulk enrichment page in app.kernel.ai showing CSV upload and field mapping"><figcaption><p>Check the identifying columns before starting the enrichment batch.</p></figcaption></figure>

## Follow a batch

The batch page shows how many rows are pending, processing, completed, or failed. Open **Run history** to return to a recent batch. You can retry a failed row, cancel a batch that is still running, or export the available results.

Use the [Data dictionary](/data/data-dictionary) to understand the fields in your results.


# Salesforce integration

Kernel connects to Salesforce via secure OAuth, reading standard objects and writing only to Kernel fields with a minimal permission set.

## What It Is

Kernel’s Salesforce integration lets Kernel read core CRM objects and write back Kernel-specific fields. The connection uses a standard OAuth 2.0 flow via the **Kernel SF Connected App**, and access is scoped to an **integration user** with a minimal Permission Set.

## **At a glance**

* A Salesforce admin installs the Kernel package and opens the **Kernel SF Connected App**.
* In that app, they **create an Integration User** and **authorize** Kernel via OAuth.
* Kernel stores a secure refresh token and uses short-lived access tokens for API calls.
* Kernel reads standard objects and **only writes** to Kernel-designated fields.

***

## **Data fields**

* Kernel either (a) provides a CSV of fields to add, or (b) installs an unlocked package that adds them.
* Kernel **only writes** to those Kernel fields; your existing data is not altered.

## What gets installed

* **Kernel Lightning App** with a **Kernel Setup** tab
  * `Authorize Kernel` (OAuth)
  * `Create Integration User` (one click, admin only)
  * Status panels
* **Connected App** (OAuth 2.0 Authorization Code + Refresh Token)
* **Permission Set** (baseline read + optional Kernel-field write)
* **LWC + Apex** to drive setup and status

## Data access

| Object            | Read                     | View-All                 | Edit              |
| ----------------- | ------------------------ | ------------------------ | ----------------- |
| Account           | ✓ (All or select fields) | ✓ (All or select fields) | ✓ (Kernel fields) |
| Lead\*            | ✓ (All or select fields) | ✓ (All or select fields) | ✓ (Kernel fields) |
| Contact           | ✓ (All or select fields) | ✓ (All or select fields) | <p><br></p>       |
| Opportunity       | ✓ (All or select fields) | ✓ (All or select fields) | <p><br></p>       |
| **Task/Activity** | ✓ (All or select fields) | ✓ (All or select fields) | <p><br></p>       |
| SystemUser        | ✓ (Limited fields)       |                          |                   |

\*Required only if you don’t use Accounts.

{% hint style="warning" %}
Account merges and deletions require more extensive permissions:

* **Delete on Account.**
* **Modify All** on Account (recommended) - bypasses record ownership and sharing restrictions.
* **View and Edit** on related objects (e.g. Contacts, Opportunities, Cases) - required to re-parent child records during merge.
* **Modify All Data** (optional) - only if merging across restrictive sharing models or record owners.
  {% endhint %}

### Security model

* **Auth**: Salesforce OAuth 2.0 (Authorization Code with Refresh Token).
* **Tokens**: Refresh token is stored in Kernel’s secrets manager; access tokens are short-lived (≤ 15 min).
* **Scopes**: `api`, `refresh_token`, plus OpenID basic profile scopes for user identity.
* **IP & session**: Uses Salesforce’s standard session security. You may keep your org’s default IP restrictions.
* **Revocation**: Revoke by deactivating the Integration User or revoking the Connected App token in Salesforce. Kernel also supports revocation via the portal.

### Runtime behavior

* Reads are batched; writes (to Kernel fields) use **Bulk API 2.0** (up to 500 records/batch).
* A 100k-Account org typically consumes **< 1%** of daily API quota for nightly updates.
* Fail-safe design: Kernel retries transient API errors and surfaces status in the portal.

## FAQs

**How does Kernel authenticate?**\
Through a Salesforce Connected App using OAuth 2.0 (Authorization Code + Refresh Token). A one-time authorization under the Integration User issues a refresh token; Kernel exchanges it for short-lived access tokens.

**Who creates the Integration User?**\
An admin creates it directly **inside the Kernel Lightning App** (one click). No separate Kernel admin login is required.

**Can we restrict permissions?**\
Yes. Use the Permission Set editor in the Kernel portal to save a **custom policy per customer**. If no custom policy is saved, Kernel assigns the **default** policy.

**How do we revoke access?**\
Deactivate the Integration User or revoke the app’s token in Salesforce Setup → Connected Apps → OAuth Usage. You can also click **Revoke** in the Kernel portal.

**Is data encrypted?**\
Yes. OAuth transport uses TLS 1.2+. Secrets are stored in a secrets manager; access tokens are ephemeral.

**How do we avoid row locks during writes?**\
Kernel batches updates and can schedule sync windows (e.g., nights/weekends). Coordinate heavy internal jobs to avoid overlap.

**What’s the frequency of the Refresh Token?**\
Permanent, long-lived.

**If batched updates fail do they retry at the next interval or at a specific time?**\
Kernel automatically retries transient API failures with backoff. If a failure is not transient or continues after retries, Kernel surfaces the status in the portal for follow-up.

**Which system administrator permissions are required?**\
System Admin privileges are required to authorize the integration user and create a handshake between Kernel and your Salesforce instance. Once this has been done you can downgrade privileges. Regardless of privileges, the user only has access to what is defined in its permission set.

**Where are our Kernel API keys stored in SFDC?**\
API Key is stored in your SF Database and also within Kernel.

## See also

* Salesforce Connected App - Setup Guide


# Package Installation Guide

Guide to install the Salesforce integration package and sync with Kernel

## Kernel Salesforce integration installation and setup guide

<figure><img src="/files/tUmN6tC7DMvLJLRJbPhH" alt=""><figcaption></figcaption></figure>

The Kernel team will create an environment and logins for you. You can access this via [app.kernel.ai](https://app.kernel.ai).

## Upgrade an existing installation

Use this path when the **Kernel SF Connected App** package is already installed and you want the latest features.

1. In Salesforce, go to **Setup → Installed Packages** and note the installed Kernel SF Connected App version.
2. In the Kernel app, open your Salesforce integration and select the current package install link.
3. Salesforce recognises the existing package and opens the upgrade flow. Review the package details and continue.
4. Choose **Install for All Users**. This makes the packaged Kernel components, including the structured feedback form, available to end users.
5. Wait for the **Install Complete** confirmation.
6. Return to **Setup → Installed Packages** and confirm the new version.
7. Assign **Kernel Readonly PermissionSet** (`Kernel_Readonly_PermissionSet`) to every user who should access Kernel features in Salesforce, then review any feature-specific page-layout steps in the relevant guide.

For structured feedback, upgrade to **version 3.11.4 or later**, choose **Install for All Users**, assign **Kernel Readonly PermissionSet** to every intended user, and add the **Send Kernel Feedback** action to the Account page. See [Send Kernel Feedback](/integrations/salesforce-integration/report-data-issues) for the complete setup and test flow.

{% hint style="info" %}
Package upgrades preserve your existing Kernel connection. You do not need to recreate the integration user or repeat the authorization flow unless Salesforce or the Kernel app prompts you to reconnect.
{% endhint %}

### Step 1: Your instance

You will need to select your CRM and then input your instance URL. This can be a production or sandbox environment. Then press continue.

<figure><img src="/files/9Ck74yg3MStxMNRcKK0l" alt=""><figcaption></figcaption></figure>

This will provide you with the install link for the Kernel package in your CRM. Click to open the install.

<figure><img src="/files/CxLOytnUTCxfmOCuw2tI" alt=""><figcaption></figcaption></figure>

### Step 2: Package configuration

### Prerequisites

* **Salesforce System Administrator** access in your target org
* **Package version**: Use the install link generated in Kernel. The link points to the current released package for your org.

{% embed url="<https://www.loom.com/share/9e212693e9174468b7d7f26707a1878c?sid=cb71e5bc-1aa6-4508-b634-d83ff2113feb>" %}

**Select installation options:**

* Install for: **All Users**

{% hint style="warning" %}
Choose **Install for All Users**, even if only a selected group will use Kernel. This makes the packaged forms and components visible to end users. You will control which users can access them by assigning the appropriate Kernel permission set after installation.
{% endhint %}

* You will be prompted to grant the Kernel API access to the user & permission set you create. (This allows us to sync your CRM to Kernel and we only have access based on Permission Sets)

<figure><img src="/files/DwGtxvNSgFl4GhHCPRp0" alt=""><figcaption></figcaption></figure>

* Click **Install**

<figure><img src="/files/dbUwyZGpvz6zO4wOyQXZ" alt=""><figcaption></figcaption></figure>

* Wait for "Install Complete" confirmation

<figure><img src="/files/WFrmXzmEzjKGY0Z8e6fT" alt=""><figcaption></figcaption></figure>

**Verify installation:**

* Navigate to **Setup → Installed Packages**

<figure><img src="/files/hSJRSQugepxnpXKF1qlG" alt=""><figcaption></figcaption></figure>

* Confirm "Kernel SF Connected App" appears with status "Installed"

<figure><img src="/files/zQ7lp3eelTMoXXejn1E7" alt=""><figcaption></figcaption></figure>

#### Assign end-user access

Assign **Kernel Readonly PermissionSet** to every user who should access Kernel features, including the structured feedback form.

1. In Salesforce Setup, go to **Permission Sets**.
2. Open **Kernel Readonly PermissionSet** (`Kernel_Readonly_PermissionSet`).
3. Select **Manage Assignments**, then **Add Assignments**.
4. Select the users who should have access and click **Assign**.

The permission set includes the **Kernel: Submit Feedback** permission and the access required to open the feedback form. Installing for all users makes the packaged components available; the permission set controls who can use them.

**Open App Launcher** (9-dot grid icon)

<figure><img src="/files/K5W1EAszsTjlm2PxfFPR" alt=""><figcaption></figcaption></figure>

1. **Select "Kernel Integration"** app
2. **Click the "Kernel Setup"** tab

### Step 3: Create integration user

The integration user is a dedicated System Administrator account that Kernel uses to access your Salesforce data securely.

#### Creating the user

1. **In the Kernel Setup tab**, locate the **Integration User** panel
2. **Review default settings:**
   * Email: `integrations@kernel.ai`
   * First Name: `Kernel`
   * Last Name: `Integration`
   * Username Prefix: `kernel-integration`
3. **Click "Create Integration User"**

   * The system creates a unique username: `kernel-integration@{OrgId}.kernel.ai`
   * A password reset email is sent to the specified email address
   * The user is created with the System Administrator profile. This is for authorizing the app/user and can be changed to minimum access after a successful connection.

   <figure><img src="/files/kdxrTLJmwlpKTfh1DNkv" alt=""><figcaption></figcaption></figure>
4. **Verify user creation:**

   * You'll see a success message with the user details

   <figure><img src="/files/ItBusdPtZ6LEVfJnjdr8" alt=""><figcaption></figcaption></figure>

#### Permission set assignment

| Object            | Read                     | View-All                 | Edit              |
| ----------------- | ------------------------ | ------------------------ | ----------------- |
| Account           | ✓ (All or select fields) | ✓ (All or select fields) | ✓ (Kernel fields) |
| Lead\*            | ✓ (All or select fields) | ✓ (All or select fields) | ✓ (Kernel fields) |
| Contact           | ✓ (All or select fields) | ✓ (All or select fields) | <p><br></p>       |
| Opportunity       | ✓ (All or select fields) | ✓ (All or select fields) | <p><br></p>       |
| **Task/Activity** | ✓ (All or select fields) | ✓ (All or select fields) | <p><br></p>       |
| SystemUser        | ✓ (Limited fields)       |                          |                   |

The system automatically assigns the `Kernel_SF_Connected_App_PermissionSet` to the integration user, which provides:

<figure><img src="/files/gkh2gtK83RVzRhG8j6we" alt=""><figcaption></figcaption></figure>

* API access
* Read access to standard and custom objects
* Access to Kernel-specific settings

You can also choose to assign your own custom permission set

<figure><img src="/files/Kv2x6pPTSYsIHOxTtm7z" alt=""><figcaption></figcaption></figure>

### Step 4: Authorize Kernel connection

Before authorizing the connection, you need your Kernel API credentials. These will now be shown in the Kernel App via the original page:

* **Tenant ID**: Your unique organization identifier (e.g., `tenant_abc123xyz`)
* **API Key**: Your secure API key for authentication

1. **In the Kernel Setup tab**, locate the **Authorize Access** panel
2. **Enter your Kernel credentials:**

   <figure><img src="/files/LJIpmEkuoMjzuUFjQU50" alt=""><figcaption></figcaption></figure>
3. **Click "Sync with Kernel"**

   <figure><img src="/files/oe4oN6qEvx9Ys5DlNHUU" alt=""><figcaption></figcaption></figure>

Finally, back in the Kernel app you will need to log in to the Salesforce instance to authorize the connection. On completion you will see our default fields to be mapped to your CRM. This means the installation is complete.

You can now proceed to set up the Kernel [Custom Object](/integrations/salesforce-integration/custom-object).

### Configuration options

#### Custom permission sets

By default, Kernel uses its standard permission set. For custom requirements:

1. **In the Permission Sets panel:**
   * Select **"Choose Custom Permission Sets"**
   * Select your organization's permission sets
   * Click **"Save"**
2. **The selected permission sets will be assigned** to the integration user

For full compatibility we recommend using the default Kernel permission set.

#### Non-admin sync access

Admins can enable **Allow Non-Admin Sync** to let users with `Kernel_Readonly_PermissionSet` run, configure, and schedule syncs through `Kernel_Sync_User_PermissionSet`, without granting the broader `Kernel_Admin_PermissionSet`. Sync schedules can run all active sync configs or only selected configs.

Because sync configuration requires Salesforce Metadata API permissions, only enable this for users who should be allowed to manage sync configuration.

### Security and compliance

#### OAuth scopes

The Kernel Connected App requests these OAuth scopes:

* `api` - Access and manage your data
* `refresh_token` - Perform requests while you're offline
* `openid` - Access unique user identifier
* `profile` - Access basic profile information
* `email` - Access email address

#### Data access

* Access is logged and auditable
* All API calls are tracked in Setup Audit Trail
* Data transmission is encrypted via TLS 1.2+

### Troubleshooting

#### Common issues and solutions

| Issue                                | Solution                                                                         |
| ------------------------------------ | -------------------------------------------------------------------------------- |
| **App not visible in App Launcher**  | Assign the Kernel Integration app to your user profile via Setup → App Manager   |
| **"Authorize" button disabled**      | Ensure Tenant ID and API Key are entered correctly                               |
| **Permission Set assignment failed** | Check if integration user is active; manually assign via Setup → Permission Sets |

### Managing the integration

#### Revoking access

To temporarily or permanently disconnect:

1. **Revoke OAuth Token:**
   * Setup → Connected Apps OAuth Usage
   * Find "Kernel SF Connected App"
   * Click **"Revoke"**
2. **Deactivate Integration User:**
   * Setup → Users
   * Find the kernel-integration user
   * Uncheck **"Active"**
   * Click **Save**

#### Re-establishing connection

1. **Reactivate the integration user** (if deactivated)
2. **Return to Kernel Setup tab**
3. **Click "Authorize Kernel"** again
4. **Complete OAuth flow**

#### Updating credentials

If your Kernel API credentials change:

1. **Obtain new credentials** from Kernel
2. **In Salesforce Kernel Setup:**
   * Click **"Disconnect"** (if connected)
   * Enter new Tenant ID and API Key
   * Click **"Authorize Kernel"**
   * Complete OAuth flow

### Support

#### Getting help

* **Email**: <support@kernel.ai>
* **Include in your support request:**
  * Organization ID
  * Environment type (Sandbox/Production)
  * Integration user username
  * Error messages or screenshots
  * Sync job IDs (from Kernel Portal)

### Package components reference

#### What gets installed

| Component            | Purpose                                  |
| -------------------- | ---------------------------------------- |
| **Connected App**    | OAuth 2.0 authentication with Kernel     |
| **Lightning App**    | Kernel Integration application container |
| **Custom Tab**       | Kernel Setup configuration interface     |
| **Apex Classes**     | Integration logic and API handlers       |
| **LWC Components**   | User interface for setup and management  |
| **Permission Set**   | Default access configuration             |
| **Custom Settings**  | Store integration configuration          |
| **Static Resources** | Application icons and assets             |

#### Recent package changes

* **v3.11.4**
  * Standardises the structured feedback action as **Send Kernel Feedback**
  * Lets users submit structured feedback from a standard Account without a linked Kernel Account
* **v3.9.0**
  * Adds **Allow Non-Admin Sync** for granting sync access via `Kernel_Sync_User_PermissionSet`
  * Adds per-config sync scheduler selection
* **v3.8.0**
  * Improves package install and test reliability by isolating package tests from subscriber Account DML
* **v3.7.1**
  * Adds the Standard sync direction toggle
  * Grants readonly users external credential access needed for enrichment callouts
* **v3.7.0**
  * Adds role-based permission sets and access gating
  * Adds the Account backfill setup step
* **v3.6.0**
  * Adds `KERN ID` on Account
  * Adds Kernel Search Accounts
  * Improves enrichment freshness and logging permissions

***


# Custom Object

{% embed url="<https://pub-8e1d08f439ec43bdbb79f1055a273a02.r2.dev/salesforce-object-model-and-field-ownership-40fb93c7.mp4>" %}

Kernel integrates with Salesforce to keep your account data clean, enriched, and up-to-date. You can choose whether Kernel syncs directly into your Salesforce Account records or into a dedicated custom object (Kernel\_Account\_\_c) that Kernel manages.

By default, we recommend the custom object approach, as it ensures Kernel owns the enrichment and cleaning process while maintaining a 1:1 mapping between your Accounts and Kernel-managed records. This provides maximum flexibility and minimizes risk to your production Salesforce data.

For hierarchies we would also be creating `Kernel_Account__c` records that you can turn into `Account` and link together. This way, you can have full hierarchy coverage while ensuring your CRM is clean.

<figure><img src="/files/i4b3qgKBb2sDtaBP9u41" alt=""><figcaption></figcaption></figure>

The `Security conscious` route limits the fields that Kernel will read from in your CRM. In this set up Kernel will only pull and push to the Kernel Account Object. Therefore we will only be able to interact with data that shared with the Account Object. These fields are very minimal covering basic account details. For a successful cleaning process we recommend Kernel pulling directly from the Account object.

## 1. Reading from Salesforce

* Kernel connects to your Salesforce instance via an integration as configured per the Salesforce Integration documentation.
* Account records (with selected fields and filters) are read into Kernel’s platform.
* This data is then prepared for enrichment, cleaning, and deduplication.

## 2. Processing in Kernel

* Kernel applies enrichment (e.g., adding missing firmographic data, hierarchy modeling).
* Cleaning rules are applied to the standard fields.
* Duplicate records are flagged and grouped for review.

## 3. Writing Back to Salesforce

* Kernel writes updates back into Salesforce at a specified cadence.
* By default, the updates flow into the `Kernel_Account__c`.
* Optionally, you can configure Kernel to write directly into the Account records if that better fits your workflow.

## Setting up the object

### 1. Navigate to the Record Sync tab

Within the Kernel application (search "Kernel" in the app launcher) you need to go to the **Record Sync** tab. The **Configuration** tab will have been complete while doing the [Package Installation Guide](/integrations/salesforce-integration/package-installation-guide).

### 2. Configure the sync

You need to configure your account object and the Kernel object. The **Source Object** needs to be the object that you use in Salesforce to represent an account. The **Target Object** should be the `Kernel Account` object.

You then need to link the objects using the Salesforce ID. The fields will be as shown in the screenshot.

Finally label the configuration.

<figure><img src="/files/bKIKkygWpJHOEg3Av0Qn" alt=""><figcaption></figcaption></figure>

### 3. Set up field sync (security conscious route)

This allows Kernel to pull from your source object via the custom object. By default Kernel recommends pulling fields directly from the Account object to allow maximum flexibility in the fields used.

For the standard Account to Kernel Account sync direction, the package shows these locked mappings:

| Source field | Kernel Account field | Notes                                                 |
| ------------ | -------------------- | ----------------------------------------------------- |
| `Name`       | `Name`               | Auto-populated when Kernel Account stubs are created. |
| `Website`    | `Kernel_Website__c`  | Auto-populated when Kernel Account stubs are created. |

Add only the extra source fields your security model allows Kernel to read. If Kernel is pulling directly from the Account object, this step can be left blank.

<figure><img src="/files/2nSH7WuL80E5bL43yN3C" alt=""><figcaption></figcaption></figure>

### 4. Filter Records

Finally you can filter out records that you don't want data populated for. We recommend leaving this blank. The primary use case is for any accounts with confidential/sensitive information that you don't want Kernel having access to.

Once complete you can sync the records to enact your latest changes.

{% embed url="<https://www.loom.com/share/c68463bebe13411cb7fd1e77293f08a7>" %}

## Custom object reference

The canonical field list now lives on [Kernel Account object reference](/integrations/salesforce-integration/kernel-account-object-reference). It covers the current `Kernel_Account__c` object fields, including identity, headcount, revenue, NAICS, location, hierarchy, LinkedIn, and account-linking fields.


# Kernel Account object reference

Current Salesforce fields on the Kernel Account custom object

When the Kernel Salesforce custom object is enabled, Kernel creates and maintains `Kernel_Account__c` records in Salesforce. Each record links to a Salesforce Account, stores Kernel-enriched values, and supports enrichment, cleaning, and hierarchy workflows without requiring Kernel to overwrite customer-owned Account fields directly.

## Object

| Item               | Value                     |
| ------------------ | ------------------------- |
| Salesforce object  | `Kernel_Account__c`       |
| Display name       | Kernel Account            |
| Account link field | `Linked_Account_Field__c` |
| Kernel identifier  | `Kernel_ID__c`            |

## Field reference

The standard Kernel Account object includes the following fields. Salesforce system fields such as `Id`, `OwnerId`, `CreatedDate`, and `LastModifiedDate` are not included.

#### Account link

<table><thead><tr><th width="380">API name</th><th width="280">Label</th><th width="120">Type</th><th>Help text</th></tr></thead><tbody><tr><td><code>Linked_Account_Field__c</code></td><td>Linked Account</td><td>Lookup</td><td>Lookup to the linked standard Account record</td></tr></tbody></table>

#### Entity resolution

<table><thead><tr><th width="380">API name</th><th width="280">Label</th><th width="120">Type</th><th>Help text</th></tr></thead><tbody><tr><td><code>Kernel_ID__c</code></td><td>KERN ID</td><td>Text</td><td>Unique Kernel identifier for this account</td></tr><tr><td><code>Kernel_Legal_Name__c</code></td><td>Kernel - Legal name</td><td>Text</td><td>Registered legal name of the entity</td></tr><tr><td><code>Name</code></td><td>Kernel - Trading name</td><td>Text</td><td>Primary name for this Kernel Account record</td></tr><tr><td><code>Kernel_Website__c</code></td><td>Kernel - Website</td><td>Url</td><td>Primary website URL for the entity</td></tr><tr><td><code>Kernel_Country_Of_Incorporation__c</code></td><td>Kernel - Country of incorporation</td><td>Text</td><td>Country where the entity is legally registered</td></tr><tr><td><code>Kernel_Entity_Category__c</code></td><td>Kernel - Entity category</td><td>Text</td><td>Entity type: Company, Government, Education, etc.</td></tr><tr><td><code>Kernel_Entity_Sub_Category__c</code></td><td>Kernel - Entity sub-category</td><td>Text</td><td>Entity sub-type within the main category</td></tr><tr><td><code>Kernel_Legal_Name_Reasoning__c</code></td><td>Kernel - Legal name (Reasoning)</td><td>LongTextArea</td><td>Why Kernel chose this legal name</td></tr><tr><td><code>Kernel_Entity_Match_Reasoning__c</code></td><td>Kernel - Entity match (Reasoning)</td><td>LongTextArea</td><td>Why Kernel matched this entity to this identity</td></tr><tr><td><code>Kernel_Entity_Match_Confidence__c</code></td><td>Kernel - Entity match (Confidence)</td><td>Text</td><td>Entity match confidence: HIGH, MEDIUM, or LOW</td></tr></tbody></table>

#### Headcount

<table><thead><tr><th width="380">API name</th><th width="280">Label</th><th width="120">Type</th><th>Help text</th></tr></thead><tbody><tr><td><code>Kernel_Headcount__c</code></td><td>Kernel - Headcount (Entity)</td><td>Number</td><td>Employee count for this specific entity, excluding subsidiaries.</td></tr><tr><td><code>Kernel_Headcount_Consolidated__c</code></td><td>Kernel - Headcount (Consolidated)</td><td>Number</td><td>Employee count for this entity and its subsidiaries, when a group-level value is available.</td></tr><tr><td><code>Kernel_Headcount_Effective__c</code></td><td>Kernel - Headcount (Recommended)</td><td>Number</td><td>Kernel's recommended headcount for CRM use. Chooses the entity or consolidated value based on which best represents this account.</td></tr><tr><td><code>Kernel_Headcount_Reasoning__c</code></td><td>Kernel - Headcount (Reasoning)</td><td>LongTextArea</td><td>Why Kernel selected this headcount, including the evidence used and the recommended scope.</td></tr><tr><td><code>Kernel_Headcount_Confidence__c</code></td><td>Kernel - Headcount (Confidence)</td><td>Text</td><td>Kernel's confidence in the recommended headcount: High, Medium, or Low.</td></tr></tbody></table>

#### Revenue

<table><thead><tr><th width="380">API name</th><th width="280">Label</th><th width="120">Type</th><th>Help text</th></tr></thead><tbody><tr><td><code>Kernel_Revenue__c</code></td><td>Kernel - Revenue (Entity, USD)</td><td>Number</td><td>Annual revenue for this specific entity, converted to USD.</td></tr><tr><td><code>Kernel_Revenue_Local__c</code></td><td>Kernel - Revenue (Entity, local)</td><td>Number</td><td>Annual revenue for this specific entity, in its local currency.</td></tr><tr><td><code>Kernel_Revenue_Local_Currency__c</code></td><td>Kernel - Revenue (Entity, currency)</td><td>Text</td><td>Currency used for the entity-level revenue value.</td></tr><tr><td><code>Kernel_Revenue_Consolidated_USD__c</code></td><td>Kernel - Revenue (Consolidated, USD)</td><td>Number</td><td>Annual revenue for this entity and its subsidiaries, converted to USD.</td></tr><tr><td><code>Kernel_Revenue_Consolidated_Local__c</code></td><td>Kernel - Revenue (Consolidated, local)</td><td>Number</td><td>Annual revenue for this entity and its subsidiaries, in the consolidated reporting currency.</td></tr><tr><td><code>Kernel_Revenue_Consolidated_Currency__c</code></td><td>Kernel - Revenue (Cons., currency)</td><td>Text</td><td>Currency used for the consolidated revenue value.</td></tr><tr><td><code>Kernel_Revenue_Effective_USD__c</code></td><td>Kernel - Revenue (Recommended, USD)</td><td>Number</td><td>Kernel's recommended revenue for CRM use, converted to USD.</td></tr><tr><td><code>Kernel_Revenue_Effective_Local__c</code></td><td>Kernel - Revenue (Recommended, local)</td><td>Number</td><td>Kernel's recommended revenue for CRM use, in the selected local or reporting currency.</td></tr><tr><td><code>Kernel_Revenue_Effective_Currency__c</code></td><td>Kernel - Revenue (Recommended, currency)</td><td>Text</td><td>Currency used for Kernel's recommended revenue value.</td></tr><tr><td><code>Kernel_Revenue_Reasoning__c</code></td><td>Kernel - Revenue (Reasoning)</td><td>LongTextArea</td><td>Why Kernel selected this revenue, including the evidence used and the recommended scope.</td></tr><tr><td><code>Kernel_Revenue_Confidence__c</code></td><td>Kernel - Revenue (Confidence)</td><td>Text</td><td>Kernel's confidence in the recommended revenue: High, Medium, or Low.</td></tr></tbody></table>

#### LinkedIn

<table><thead><tr><th width="380">API name</th><th width="280">Label</th><th width="120">Type</th><th>Help text</th></tr></thead><tbody><tr><td><code>Kernel_Linkedin_Url__c</code></td><td>Kernel - LinkedIn URL</td><td>Url</td><td>LinkedIn company page URL</td></tr><tr><td><code>Kernel_Linkedin_Description__c</code></td><td>Kernel - LinkedIn description</td><td>LongTextArea</td><td>Company description as displayed on LinkedIn</td></tr><tr><td><code>Kernel_Linkedin_Industry__c</code></td><td>Kernel - LinkedIn industry</td><td>Text</td><td>Company declared industry as displayed on LinkedIn</td></tr><tr><td><code>Kernel_Linkedin_Company_Type__c</code></td><td>Kernel - LinkedIn company type</td><td>Text</td><td>Company type as displayed on LinkedIn</td></tr><tr><td><code>Kernel_Linkedin_Founded_Year__c</code></td><td>Kernel - LinkedIn - Founded year</td><td>Number</td><td>Founded year shown on the matched LinkedIn company page.</td></tr><tr><td><code>Kernel_Linkedin_Company_Size__c</code></td><td>Kernel - LinkedIn company size range</td><td>Text</td><td>Company size as displayed on LinkedIn</td></tr><tr><td><code>Kernel_Linkedin_Headcount__c</code></td><td>Kernel - LinkedIn headcount</td><td>Number</td><td>Headcount as displayed on LinkedIn</td></tr><tr><td><code>Kernel_Linkedin_Headcount_Growth_12m__c</code></td><td>Kernel - LinkedIn headcount growth 12m</td><td>Number</td><td>Headcount growth over 12 months as displayed on LinkedIn</td></tr><tr><td><code>Kernel_Linkedin_Headcount_Growth_24m__c</code></td><td>Kernel - LinkedIn headcount growth 24m</td><td>Number</td><td>Headcount growth over 24 months as displayed on LinkedIn</td></tr><tr><td><code>Kernel_Linkedin_Country__c</code></td><td>Kernel - LinkedIn country</td><td>Text</td><td>Country as displayed on LinkedIn</td></tr><tr><td><code>Kernel_Linkedin_State__c</code></td><td>Kernel - LinkedIn state</td><td>Text</td><td>State location as displayed on LinkedIn</td></tr><tr><td><code>Kernel_Linkedin_City__c</code></td><td>Kernel - LinkedIn city</td><td>Text</td><td>City as displayed on LinkedIn</td></tr><tr><td><code>Kernel_Linkedin_All_Locations__c</code></td><td>Kernel - LinkedIn all locations</td><td>LongTextArea</td><td>All address locations as available on LinkedIn</td></tr></tbody></table>

#### Top parent

<table><thead><tr><th width="380">API name</th><th width="280">Label</th><th width="120">Type</th><th>Help text</th></tr></thead><tbody><tr><td><code>Kernel_Ultimate_Parent_Kern_Id__c</code></td><td>Kernel - Top parent KERN ID</td><td>Text</td><td>Kernel ID of the top parent in the corporate hierarchy.</td></tr><tr><td><code>Kernel_Ultimate_Parent_Legal_Name__c</code></td><td>Kernel - Top parent legal name</td><td>Text</td><td>Legal name of the top parent.</td></tr><tr><td><code>Kernel_Ultimate_Parent_Trading_Name__c</code></td><td>Kernel - Top parent trading name</td><td>Text</td><td>Trading name of the top parent.</td></tr><tr><td><code>Kernel_Ultimate_Parent_Url__c</code></td><td>Kernel - Top parent URL</td><td>Url</td><td>Website of the top parent.</td></tr><tr><td><code>Kernel_Ult_Parent_Country_Incorp__c</code></td><td>Kernel - Top parent incorp. country</td><td>Text</td><td>Country where the top parent is legally registered.</td></tr><tr><td><code>Kernel_Ultimate_Parent_Account__c</code></td><td>Kernel - Top parent account</td><td>Lookup</td><td>Lookup to the top parent Account record.</td></tr></tbody></table>

#### Immediate parent

<table><thead><tr><th width="380">API name</th><th width="280">Label</th><th width="120">Type</th><th>Help text</th></tr></thead><tbody><tr><td><code>Kernel_Parent_Kern_Id__c</code></td><td>Kernel - Parent KERN ID</td><td>Text</td><td>Kernel ID of the immediate parent entity</td></tr><tr><td><code>Kernel_Parent_Legal_Name__c</code></td><td>Kernel - Parent legal name</td><td>Text</td><td>Legal name of the immediate parent</td></tr><tr><td><code>Kernel_Parent_Trading_Name__c</code></td><td>Kernel - Parent trading name</td><td>Text</td><td>Trading name of the immediate parent</td></tr><tr><td><code>Kernel_Parent_Website__c</code></td><td>Kernel - Parent website</td><td>Url</td><td>Website of the immediate parent</td></tr><tr><td><code>Kernel_Parent_Country_Incorp__c</code></td><td>Kernel - Parent country of incorporation</td><td>Text</td><td>Country where the parent is legally registered</td></tr><tr><td><code>Kernel_Parent_Entity_Category__c</code></td><td>Kernel - Parent entity category</td><td>Text</td><td>Parent entity type: Company, Education, Government, etc.</td></tr><tr><td><code>Kernel_Parent_Entity_Sub_Category__c</code></td><td>Kernel - Parent entity sub-category</td><td>Text</td><td>Parent entity sub-type</td></tr><tr><td><code>Kernel_Parent_Reasoning__c</code></td><td>Kernel - Parent (Reasoning)</td><td>LongTextArea</td><td>Why Kernel identified this parent</td></tr><tr><td><code>Kernel_Parent_Confidence__c</code></td><td>Kernel - Parent (Confidence)</td><td>Text</td><td>Parent identification confidence: HIGH, MEDIUM, or LOW</td></tr></tbody></table>

#### Industry

<table><thead><tr><th width="380">API name</th><th width="280">Label</th><th width="120">Type</th><th>Help text</th></tr></thead><tbody><tr><td><code>Kernel_Main_Vertical__c</code></td><td>Kernel - Custom industry</td><td>TextArea</td><td>Custom industry vertical assigned by Kernel using your configured industry schema.</td></tr><tr><td><code>Kernel_Sub_Vertical__c</code></td><td>Kernel - Custom sub-industry</td><td>TextArea</td><td>Custom industry sub-vertical assigned by Kernel using your configured industry schema.</td></tr><tr><td><code>Kernel_NAICS_Sector__c</code></td><td>Kernel - NAICS sector</td><td>Text</td><td>NAICS classification assigned by Kernel (sector; 2-digit level).</td></tr><tr><td><code>Kernel_NAICS_Subsector__c</code></td><td>Kernel - NAICS subsector</td><td>Text</td><td>NAICS classification assigned by Kernel (subsector; 3-digit level).</td></tr><tr><td><code>Kernel_NAICS_Industry_Group__c</code></td><td>Kernel - NAICS industry group</td><td>Text</td><td>NAICS classification assigned by Kernel (industry group; 4-digit level).</td></tr><tr><td><code>Kernel_NAICS_Industry__c</code></td><td>Kernel - NAICS industry</td><td>Text</td><td>NAICS classification assigned by Kernel (industry; 5-digit level).</td></tr><tr><td><code>Kernel_NAICS_National_Industry__c</code></td><td>Kernel - NAICS national industry</td><td>Text</td><td>NAICS classification assigned by Kernel (national industry; 6-digit level).</td></tr><tr><td><code>Kernel_Main_Vertical_Reasoning__c</code></td><td>Kernel - Main vertical (Reasoning)</td><td>LongTextArea</td><td>Why Kernel assigned this custom industry vertical using your configured industry schema.</td></tr><tr><td><code>Kernel_Sub_Vertical_Reasoning__c</code></td><td>Kernel - Sub-vertical (Reasoning)</td><td>LongTextArea</td><td>Why Kernel assigned this custom industry sub-vertical using your configured industry schema.</td></tr></tbody></table>

#### Location

<table><thead><tr><th width="380">API name</th><th width="280">Label</th><th width="120">Type</th><th>Help text</th></tr></thead><tbody><tr><td><code>Kernel_Address__c</code></td><td>Kernel - Address</td><td>Text</td><td>Operating address selected by Kernel for this entity.</td></tr><tr><td><code>Kernel_City__c</code></td><td>Kernel - City</td><td>Text</td><td>Operating city selected by Kernel for this entity.</td></tr><tr><td><code>Kernel_State__c</code></td><td>Kernel - State</td><td>Text</td><td>Operating state or province selected by Kernel for this entity.</td></tr><tr><td><code>Kernel_Country__c</code></td><td>Kernel - Country</td><td>Text</td><td>Operating country selected by Kernel for this entity.</td></tr><tr><td><code>Kernel_Location_Reasoning__c</code></td><td>Kernel - Location (Reasoning)</td><td>LongTextArea</td><td>Why Kernel selected this operating location.</td></tr><tr><td><code>Kernel_Registered_Address__c</code></td><td>Kernel - Registered address</td><td>Text</td><td>Registered address selected by Kernel</td></tr><tr><td><code>Kernel_Registered_Address_Street__c</code></td><td>Kernel - Registered address street</td><td>Text</td><td>Registered street address selected by Kernel</td></tr><tr><td><code>Kernel_Registered_City__c</code></td><td>Kernel - Registered city</td><td>Text</td><td>Registered city selected by Kernel</td></tr><tr><td><code>Kernel_Registered_State__c</code></td><td>Kernel - Registered state</td><td>Text</td><td>Registered state selected by Kernel</td></tr><tr><td><code>Kernel_Registered_Country__c</code></td><td>Kernel - Registered country</td><td>Text</td><td>Registered country selected by Kernel</td></tr><tr><td><code>Kernel_Registered_Country_Code__c</code></td><td>Kernel - Registered country code</td><td>Text</td><td>Registered country code selected by Kernel</td></tr><tr><td><code>Kernel_Registered_Postcode__c</code></td><td>Kernel - Registered postcode</td><td>Text</td><td>Registered postcode selected by Kernel</td></tr><tr><td><code>Kernel_Registered_Address_Reasoning__c</code></td><td>Kernel - Registered address (Reasoning)</td><td>LongTextArea</td><td>Why Kernel chose this registered address</td></tr></tbody></table>

#### Operational status

<table><thead><tr><th width="380">API name</th><th width="280">Label</th><th width="120">Type</th><th>Help text</th></tr></thead><tbody><tr><td><code>Kernel_Operational_Status__c</code></td><td>Kernel - Operational status</td><td>Text</td><td>Operating status: Active, Out of Business, or Absorbed</td></tr><tr><td><code>Kernel_Operational_Status_Reasoning__c</code></td><td>Kernel - Operational status (Reasoning)</td><td>LongTextArea</td><td>Why Kernel assigned this operational status</td></tr></tbody></table>

#### Regional subsidiaries

<table><thead><tr><th width="380">API name</th><th width="280">Label</th><th width="120">Type</th><th>Help text</th></tr></thead><tbody><tr><td><code>Kernel_Entity_Regional_Scope__c</code></td><td>Kernel - Is regional subsidiary</td><td>Checkbox</td><td>Whether this entity is a regional subsidiary</td></tr><tr><td><code>Kernel_Regional_Scope__c</code></td><td>Kernel - Regional scope</td><td>Text</td><td>Region or country this subsidiary operates in</td></tr><tr><td><code>Kernel_Entity_Regional_Scope_Reason__c</code></td><td>Kernel - Regional scope (Reasoning)</td><td>LongTextArea</td><td>Why Kernel flagged this as regional or not</td></tr></tbody></table>


# Automatic refresh

How Kernel keeps Salesforce account data current through scheduled refreshes and the Enrich with Kernel action.

{% embed url="<https://pub-8e1d08f439ec43bdbb79f1055a273a02.r2.dev/automatic-refresh-frequency-e4010970.mp4>" %}

Automatic refresh keeps Salesforce close to Kernel's latest view of each account. The video explains automatic refresh frequency: the setting that controls how often Kernel writes changed values back to the Kernel Account object without overwriting customer-owned Account fields.

Use this page when you want to understand two related Salesforce refresh paths: scheduled refreshes that keep Kernel Account current, and the **Enrich with Kernel** action that lets a rep refresh one Account on demand.

## Automatic refresh frequency

Kernel continues to re-clean and re-enrich accounts in the background. Salesforce only sees the values Kernel last wrote. Automatic refresh frequency closes that gap by sending changed values to the Kernel Account object on the cadence you choose: Daily, Weekly, Monthly, or Never.

Each run updates only accounts whose Kernel values changed since the previous run. It does not rewrite every Account.

## Enrich with Kernel

Enrich with Kernel is a native Salesforce action that lets reps refresh an Account with the latest company data from Kernel without leaving Salesforce.

A rep clicks **Enrich with Kernel** on the Account record, confirms the refresh, and Kernel updates the account in the background with fresh firmographic data like industry, employee count, revenue, and related company signals.

The record updates automatically once enrichment completes.

## Why it matters

* Reps can refresh account data from the Salesforce record they are already using.
* Refreshes can happen on demand, not only during scheduled syncs.
* The standard unlocked package works in sandbox and production without custom development.
* The action respects Salesforce object and field permissions.
* Built-in caching prevents duplicate enrichments for 14 days
* Requests are rate-limited and concurrency-capped automatically


# Search and create accounts

Search Kernel's company database from inside Salesforce and create matched companies as Accounts, without leaving your CRM.

Search Accounts lets a rep look up any company in Kernel's database from inside Salesforce and turn the match into an Account in a couple of clicks. It is the fastest way to add a net-new company to Salesforce with clean company details already filled in, and it flags companies you already have so you do not create duplicates.

It is a full-page tab, not a record action, so reps use it whenever they need to find or add a company, not only when they are on an existing record.

## Where to find it

1. Open the **App Launcher** (the nine-dot grid, top left).
2. Select the **Kernel** app.
3. Open the **Kernel Search Accounts** tab.

The tab is available to every user once the package is installed for all users. See [Giving your team access](#giving-your-team-access) below.

<figure><img src="/files/cj3nnv3NTdPXYgHYwQBW" alt="The Kernel Search Accounts tab, with search filters on the left and an empty results panel on the right"><figcaption><p>The Kernel Search Accounts tab</p></figcaption></figure>

## Running a search

The search panel on the left has four filters. You can use any one of them on its own or combine them to narrow the results. At least one filter is required.

* **Business Name**: the company name to look for.
* **Website**: the company's domain or website.
* **National ID**: a company registration number (for example a Companies House number or an EIN).
* **Country Codes**: one or more two-letter country codes to restrict results by country, for example `US, GB, DE`.

Enter your criteria and click **Search**. **Reset Filters** clears the form and results so you can start again.

Kernel searches its global company database and ranks the matches by how closely they fit your criteria, so a search still finds the right company even when the name or website is entered slightly differently.

## Reading the results

Matches appear in a table on the right, with the total number of matches shown above it. Each row shows the company's **Business Name**, **Country**, **City**, **Address**, **Website**, and **National ID**.

<figure><img src="/files/fZLrNJfOBUPpyIE5GGk7" alt="Search results showing matching companies, with a Create button on new companies and a View button on companies already in Salesforce"><figcaption><p>Search results, with Create on new companies and View on ones already in your Salesforce</p></figcaption></figure>

Results are returned 100 at a time. When there are more matches, a **Load more** button appears at the bottom with the number of remaining results.

Every row has a button on the right whose label tells you the company's status in your Salesforce:

* **Create**: this company is not yet in your Salesforce. Click to create it as a new Account.
* **View**: this company is already an Account in your Salesforce. Click to open the existing record in a new browser tab, so you keep your search results open.
* **Exists**: this company is already in your Salesforce but is not linked to a single record Kernel can open directly. It is shown so you know not to create a duplicate.

Kernel decides whether a company is already one of your accounts by checking it against the companies Kernel has already linked to your Salesforce. This means reps see a clear duplicate warning before they create anything.

## Creating an account

When you click **Create** on a result, Salesforce opens the standard **New Account** page with the company's details already filled in:

* Business name
* Website
* Billing street, city, postal code, and country

You review the details, add anything else your team captures, and save as you would with any Account. Because it uses the standard New Account page, creation follows your normal Salesforce page layouts, validation rules, and permissions.

A couple of things to know:

* **National ID is not carried over.** Salesforce Accounts have no standard field for a registration number, so it is left off the new record.
* **None of the results fit?** Use the **Create "\<your search>"** button above the results (or on the empty-results screen) to create an Account from exactly what you typed, rather than from one of the matches. Kernel fills in the business name, website, and country where it can.

{% hint style="info" %}
Search results carry a company's identity and address. Once the Account is created, if you have an inbound process set up with Kernel, its data is automatically enriched, cleaned, and pushed back to both the new Account and the Kernel app.
{% endhint %}

## Giving your team access

Search Accounts is built for reps, not only administrators. Making it available to your whole team happens at install time and comes down to two things:

* **Install the latest version of the Kernel package.** Search Accounts is only present in recent versions, so an older installation will not show the tab. Use the current install link from Kernel to install or upgrade.
* **Install the package for all users, not just administrators.** During installation, choose **Install for All Users** rather than **Install for Admins Only**. This gives every rep the Kernel app and the **Kernel Search Accounts** tab. If the package is installed for admins only, reps will not see it.

See the [Package installation guide](/integrations/salesforce-integration/package-installation-guide) for the full install walkthrough.

A note on creating records: creating an Account from a result uses the standard New Account page, so it follows each user's own Salesforce permission to create Accounts, which most sales users already have. If a user can search but cannot save a new Account, check that their profile allows Account creation.

## Why it matters

* Reps add clean, correctly identified companies to Salesforce in seconds, without copying data between tabs.
* Duplicate matches are flagged before a record is created, so the same company does not get added twice.
* The feature is safe to roll out to the whole team: searching is read-only, and creating a record still respects each user's existing Salesforce permissions.
* Searching is free to run. It does not consume enrichment credits.


# Send Kernel Feedback

Turn on the Send Kernel Feedback button and add it to your Account and Kernel Account pages so reps can send data feedback to Kernel from Salesforce.

**Send Kernel Feedback** is the Salesforce feedback form for Kernel. It lets a rep flag a data issue - a wrong match, an off headcount, a missing parent - directly from an Account or Kernel Account record, and sends it to the Kernel data team as a tracked ticket. It replaces the older method of typing feedback into a Salesforce field.

This page covers how to switch the button on and show it on your records. For what happens after an issue is raised and where to track outcomes, see the [Feedback loop](/concepts/feedback-loop) concept page.

{% hint style="info" %}
**Requires Kernel Salesforce package version 3.11.4 or later.** Version 3.11.4 includes the **Send Kernel Feedback** label and lets reps submit from a standard Account without first creating a Kernel Account. Check your installed version under **Setup → Installed Packages** and [upgrade the package](/integrations/salesforce-integration/package-installation-guide#upgrade-an-existing-installation) if needed.
{% endhint %}

## Prerequisites

* The **Kernel SF Connected App** package is installed and authorized, on **version 3.11.4 or later**. ("Kernel SF Connected App" is the package name as it appears in Salesforce.) See the [Package installation guide](/integrations/salesforce-integration/package-installation-guide).
* The package was installed or upgraded using **Install for All Users**. This is required for end users to access the packaged feedback form.
* Feedback ticketing is enabled for your Kernel environment. Kernel configures this - if the button returns an error saying feedback is not enabled, contact your Kernel team.
* Every user who will submit feedback has **Kernel Readonly PermissionSet** (`Kernel_Readonly_PermissionSet`). This permission set includes the **Kernel: Submit Feedback** permission (`Kernel_Submit_Feedback`) and access to the feedback form.

## Give users access

Both the installation audience and the user permission set are required.

1. Confirm the package was installed using **Install for All Users**. If it was installed for administrators only, open the current package link in the Kernel app and complete the upgrade flow using **Install for All Users**.
2. In Salesforce Setup, go to **Permission Sets**.
3. Open **Kernel Readonly PermissionSet**.
4. Select **Manage Assignments**, then **Add Assignments**.
5. Select every user who should access the feedback form and click **Assign**.

You can now add the action to the relevant Account pages.

## How it works

Send Kernel Feedback is a Lightning quick action, so it appears as a button in the action row at the top of a record. It is available on two objects:

* the standard **Account**, and
* the **Kernel Account** (`Kernel_Account__c`) that stores Kernel's resolved data for that account.

When a rep clicks it, a short form opens. The rep chooses the field they disagree with and describes the correct value; Kernel captures the current value automatically. On submit, Kernel creates a support ticket for the data team and stores a tracking record in Salesforce. If the Account has a linked Kernel Account, the tracking record is associated with both records.

{% hint style="info" %}
A standard Account does not need a linked Kernel Account to submit feedback in package version 3.11.4 and later. A Kernel Account does need to be linked to its Salesforce Account so Kernel can identify the CRM record.
{% endhint %}

## Show the button on the Kernel Account

The package ships a ready-made Kernel Account record page that already includes the button and a feedback history list.

1. Go to **Setup → Object Manager → Kernel Account → Lightning Record Pages**.
2. Open **Kernel Account Record Page** and click **Activation**.
3. Set it as the **org default** (or assign it by app or profile as you prefer), then **Save**.

This page shows the **Send Kernel Feedback** button in the highlights panel and a **Kernel Feedback** related list, so your team can see every issue reported for that account in one place.

<figure><img src="/files/fIzFsnj2nkCvHjdVgOwv" alt="The Send Kernel Feedback button in the header of a Kernel Account record, with the Kernel Feedback related list alongside it."><figcaption><p>Send Kernel Feedback sits in the record header; submitted issues appear in the Kernel Feedback related list.</p></figcaption></figure>

## Show the button on the standard Account

To let reps raise feedback from the standard Account record, add the action to the Account page layout.

1. Go to **Setup → Object Manager → Account → Page Layouts**.
2. Open the layout your reps use and find the **Salesforce Mobile and Lightning Experience Actions** section.
3. If the section still shows the default actions, click **override the predefined actions**.
4. Drag **Send Kernel Feedback** into the action list where you want it to appear.
5. **Save**.

The button now appears in the action row at the top of Account records for users assigned **Kernel Readonly PermissionSet**.

Reps can submit from a standard Account whether or not your organisation uses the Kernel Account custom object.

## Using the form

When a rep clicks **Send Kernel Feedback**, the form asks for:

* **Disputed field** - the piece of Kernel data that looks wrong (for example, Kernel Headcount).
* **Current value** - filled in automatically from the record, so the rep does not type it.
* **Category** - the area the issue falls under, such as identity, headcount, revenue, or locations.
* **Expected value** - what the rep believes the value should be.
* **Priority** - optional triage guidance for the Kernel team. Leave it as **None** for routine corrections. Use **Low** or **Medium** for work that is not blocking a live process, **High** for an important issue affecting current work, and **Urgent** only when the issue is blocking an active business process.
* **Evidence URL** - an optional link that backs up the claim.
* **Comment** - any extra context.

The rep clicks **Send to Kernel** to submit. The feedback is sent to Kernel and stored as a **Kernel Feedback** record in Salesforce. When the Account has a linked Kernel Account, it also appears in the **Kernel Feedback** related list on that record.

<figure><img src="/files/N40UVzu57w262OIlygXP" alt="The Send Kernel Feedback form with fields for disputed field, category, expected value, priority, evidence URL, and comment, and a Send to Kernel button."><figcaption><p>The Send Kernel Feedback form.</p></figcaption></figure>

## What makes useful feedback

The more specific the submission, the faster the data team can act on it. A good report names the exact field, states the value you expected, and links to something we can verify against.

* **Wrong headcount.** Disputed field: Kernel Headcount. Expected value: "\~1,200". Evidence URL: the company's LinkedIn or careers page. Comment: "Their careers page lists 1,200+ staff; Kernel shows 300."
* **Wrong company matched.** Category: identity. Expected value: the correct legal name and website. Evidence URL: the correct company homepage. Comment: "This resolved to the US parent, but our account is the UK trading entity."
* **Missing or wrong parent.** Category: hierarchy. Expected value: the parent company you expect to see. Evidence URL: a news article or the parent's site confirming ownership. Comment: "Acquired by Acme in 2025 - should roll up to Acme Group."

A short note with a link beats a long description with none.

## Tracking outcomes

Submitted issues are always available on the **Data feedback loop** page in the Kernel app at [app.kernel.ai/feedback](https://app.kernel.ai/feedback), where you can read the resolution and reasoning once the data team has responded.

In Salesforce, each submission is stored as a **Kernel Feedback** record against the standard Account. If your organisation uses linked Kernel Account records, the same submission also appears in the **Kernel Feedback** related list on the Kernel Account.

The tracking record captures the disputed field, the expected value, the category, and its status, with a link to track the issue in the Kernel app.

<figure><img src="/files/PF0hSMlay6fwKcj1COhe" alt="A Kernel Feedback record in Salesforce showing the disputed field, category, expected value, status of Submitted, and a link to track the issue in the Kernel app."><figcaption><p>A submitted Kernel Feedback record on the account.</p></figcaption></figure>

See [Feedback loop](/concepts/feedback-loop) for the full picture.


# Okta SSO Setup Guide

This guide outlines the steps required to configure Single Sign-On (SSO) between your Okta instance and the Kernel platform using SAML.

## Step 1 - Kernel sends you the ACS URL

Kernel will provide you with the ACS (Assertion Consumer Service) URL, SP Entity ID and SP Metadata. This is the endpoint Okta will send the SAML assertion to. You will need this to create the Kernel application in Okta.

Action for client: Wait to receive the ACS URL from your Kernel contact before proceeding.

## Step 2 - Create the Kernel app in Okta and share the IdP metadata URL

Once you have the ACS URL from Kernel, create a new SAML application in Okta:

1. Log in to your Okta Admin Console.
2. Go to Applications → Applications → Create App Integration.
3. Select SAML 2.0 and click Next.
4. Fill in the app name (e.g. "Kernel") and proceed.
5. In the SAML Settings, paste the ACS URL provided by Kernel in the Single sign-on URL field.
6. Complete the setup and save.
7. Navigate to the app's Sign On tab → scroll to the Metadata section.
8. Copy the Identity Provider Metadata URL.

Action for client: Share the Okta IdP Metadata URL with Kernel so they can complete the configuration on their end.

## Step 3 - Configure attribute mappings

In the Okta app, go to the Sign On tab and edit the Attribute Statements. Add the following three mappings exactly as shown - pay close attention to the attribute names, as incorrect naming (e.g. userLast instead of lastName) will cause login errors.

| Name        | Value                                                                                         |
| ----------- | --------------------------------------------------------------------------------------------- |
| email       | user.email                                                                                    |
| firstName   | user.firstName                                                                                |
| lastName    | user.lastName                                                                                 |
| User IdP ID | user.email\* (\*usually it is email - please check if this is the case for your organization) |

Important: The NameID should be mapped to the user’s email address, as that is what Kernel uses for identification.

Action for client: Save the attribute statements and notify Kernel once done.

## Step 4 - Test the connection

Once Kernel confirms the configuration is complete on their end:

1. Ensure all users who will test SSO already have active accounts in the Kernel platform (users must have signed up / been provisioned in Kernel before SSO will work).
2. Have a test user attempt to log in via Okta SSO.
3. If login fails, double-check the attribute mappings in Step 3 - a common mistake is using userLast instead of lastName for the last name field.


# Salesforce Integration User Permission Matrix

This page defines the least-privilege access required by the Kernel Salesforce integration user.

Permissions depend on the workflows enabled in your Kernel configuration. The integration user requires:

1. Object and field permissions for each enabled workflow.
2. Record-level access to the records included in that workflow.

These are separate requirements. Object permissions do not automatically give the integration user access to every record.

### Permission sources

| Permission source          | Description                                                                                            |
| -------------------------- | ------------------------------------------------------------------------------------------------------ |
| **Kernel baseline**        | Fixed access required by Kernel for standard workflows and packaged fields.                            |
| **Standard conditional**   | Standard Salesforce access required only when the corresponding workflow or feature is enabled.        |
| **Customer extension**     | Customer-selected standard fields, custom fields, managed-package fields and related objects.          |
| **Customer record access** | Sharing rules, ownership, role hierarchy or object-specific `View All Records` / `Modify All Records`. |

Object permissions are abbreviated as:

* **R** — Read
* **C** — Create
* **E** — Edit
* **D** — Delete

Field permissions are shown as **Read** or **Edit**.

### Connect Kernel to Salesforce

| Workflow                                           | Object or resource                                  | Fields | Permissions needed                                          | Permission source                                                  |
| -------------------------------------------------- | --------------------------------------------------- | ------ | ----------------------------------------------------------- | ------------------------------------------------------------------ |
| Connect through the Salesforce API                 | Salesforce API                                      | —      | `API Enabled`                                               | Kernel baseline                                                    |
| Use a Kernel REST endpoint                         | Relevant Kernel Apex REST class                     | —      | Apex Class Access for the endpoint being used               | Kernel baseline                                                    |
| Authorize the connected application                | Kernel Connected App                                | —      | Connected App authorization and the configured OAuth scopes | Salesforce connection configuration                                |
| Make outbound requests from the Salesforce package | Kernel external credential                          | —      | External Credential Principal Access                        | Standard conditional                                               |
| Configure Kernel in Salesforce                     | Kernel setup controllers and configuration metadata | —      | Separate administrator permission set                       | Human administrator; not part of the headless integration baseline |

### Pull and clean CRM records

| Workflow                        | Object      | Fields                                                                                         | Permissions needed                                 | Permission source                                                          |
| ------------------------------- | ----------- | ---------------------------------------------------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------------------------------- |
| Pull and clean Accounts         | Account     | `Id`, `Name`; `Website` where used; every configured pull, filter, matching and cleaning field | Account R; listed fields Read                      | Kernel baseline for fixed fields; Customer extension for configured fields |
| Calculate Account-level rollups | Account     | `Id`, `OwnerId`, `LastActivityDate`, `CreatedDate` and enabled rollup fields                   | Account R; listed fields Read                      | Standard conditional                                                       |
| Pull Leads                      | Lead        | Every field selected for pull, matching, filtering or output                                   | Lead R; selected fields Read                       | Standard conditional and Customer extension                                |
| Read related Contacts           | Contact     | `Id`, `AccountId` and configured Contact fields                                                | Contact R; listed fields Read                      | Standard conditional and Customer extension                                |
| Read related Opportunities      | Opportunity | `Id`, `AccountId` and configured Opportunity fields                                            | Opportunity R; listed fields Read                  | Standard conditional and Customer extension                                |
| Read related Tasks              | Task        | `Id`, `AccountId` and configured Task fields                                                   | Task R; listed fields Read                         | Standard conditional and Customer extension                                |
| Read Account owners             | User        | `Id`, `Name`, `Email`, `IsActive` and configured owner fields                                  | Visibility of the required User records and fields | Standard conditional                                                       |

The integration user must be able to see every record included in a pull or rollup. Use customer sharing rules where possible. If sharing cannot provide complete visibility, grant `View All Records` only on the relevant object.

### Create and manage Account hierarchies

| Workflow                                               | Object  | Fields                                                            | Permissions needed                        | Permission source                  |
| ------------------------------------------------------ | ------- | ----------------------------------------------------------------- | ----------------------------------------- | ---------------------------------- |
| Find an existing parent                                | Account | `Id`, `Name`, `Website`                                           | Account R; listed fields Read             | Kernel baseline                    |
| Create a minimum parent Account                        | Account | `Name`, `Website`                                                 | Account C; populated fields Edit          | Kernel baseline                    |
| Populate a standard billing address                    | Account | `BillingStreet`, `BillingCity`, `BillingPostalCode`               | Account C; populated fields Edit          | Standard conditional               |
| Populate geography without State and Country Picklists | Account | `BillingState`, `BillingCountry`                                  | Account C; populated fields Edit          | Standard conditional               |
| Populate geography with State and Country Picklists    | Account | `BillingCountryCode`                                              | Account C; field Edit                     | Standard conditional               |
| Populate configured parent fields                      | Account | Every customer-selected standard, custom or managed-package field | Account C; populated fields Edit          | Customer extension                 |
| Set, change or remove the standard parent              | Account | `ParentId`                                                        | Account R/E; `ParentId` Read/Edit         | Kernel baseline                    |
| Use a custom hierarchy relationship                    | Account | Configured relationship field                                     | Account R/E; relationship field Read/Edit | Customer extension                 |
| Roll back a reparent                                   | Account | The same relationship field used by the original action           | Account E; relationship field Edit        | Same source as the original action |

The integration user also needs edit access to every Account being reparented. For cross-owner mass actions, this can be supplied through sharing or through Account `Modify All Records`.

### Merge Accounts

| Workflow                           | Object  | Fields                                                       | Permissions needed                                                                              | Permission source                                                          |
| ---------------------------------- | ------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| Read master and duplicate Accounts | Account | `Id`, `Name` and every matching or survivorship source field | Account R; listed fields Read                                                                   | Kernel baseline and Customer extension                                     |
| Back up Accounts before merge      | Account | Every field expected in the backup                           | Account R; expected backup fields Read                                                          | Kernel baseline for fixed fields; Customer extension for additional fields |
| Apply survivorship values          | Account | Every configured survivorship field                          | Account E; source fields Read; target fields Edit                                               | Customer extension                                                         |
| Preserve or repair the hierarchy   | Account | `ParentId`                                                   | Account R/E; `ParentId` Read/Edit                                                               | Standard conditional                                                       |
| Apply owner survivorship           | Account | `OwnerId`                                                    | Account E; ability to assign the selected owner; additional transfer permissions where required | Standard conditional                                                       |
| Merge the Accounts                 | Account | Record IDs                                                   | Account R/E/D; edit access to the master; delete access to losing Accounts                      | Kernel baseline plus Customer record access                                |

Salesforce requires Delete permission on Account to merge Accounts. It can also require edit access to related records such as Contacts and Opportunities.

`Modify All Records` on Account is not mandatory when ownership and sharing provide sufficient access. It can, however, be the most practical customer-controlled option for mass merges spanning many owners.

#### Account backup field access

Kernel’s Account backup includes fields available to the integration user. `View All Fields` is not required to run a merge, but Kernel cannot back up a field that the integration user cannot read.

Grant Read access to every Account field that you expect Kernel to preserve in the backup.

### Related Object Safeguards

A Related Object Safeguard counts records connected to an Account through a configured relationship and uses that count to determine whether the Account should be safeguarded.

| Workflow                                       | Object                     | Fields                                | Permissions needed                                                            | Permission source                                |
| ---------------------------------------------- | -------------------------- | ------------------------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------ |
| Evaluate a standard related-object safeguard   | Configured standard object | Configured Account relationship field | Object R; relationship field Read; visibility of records that must be counted | Standard conditional plus Customer record access |
| Evaluate a custom or managed-package safeguard | Configured object          | Configured Account relationship field | Object R; relationship field Read; visibility of records that must be counted | Customer extension plus Customer record access   |

Safeguard counts cover only objects and records visible to the integration user. If normal sharing does not provide complete visibility, grant `View All Records` on that specific related object.

A safeguard is separate from backup and delete-before-merge configuration:

* **Safeguards** count related records and protect Accounts from an action by default.
* **Backup Objects** determine which related records Kernel captures.
* **Delete-before-merge** determines which related records Kernel explicitly deletes.
* Objects not handled explicitly remain subject to Salesforce’s native merge behaviour.

Before enabling Account merges, review the relevant standard, custom and managed-package Account relationships with your Salesforce administrator.

### Back up related records

Contact, Opportunity and Task are included in the default Backup Objects configuration. Customers can change this list.

| Workflow                                   | Object                               | Fields                                                                                                                                              | Permissions needed                                                                                         | Permission source             |
| ------------------------------------------ | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ----------------------------- |
| Back up Contacts                           | Contact                              | `AccountId` and every field expected in the backup                                                                                                  | Contact R; listed fields Read                                                                              | Standard conditional          |
| Back up Opportunities                      | Opportunity                          | `AccountId` and every field expected in the backup                                                                                                  | Opportunity R; listed fields Read                                                                          | Standard conditional          |
| Back up Tasks                              | Task                                 | `AccountId` and every field expected in the backup                                                                                                  | Task R; listed fields Read                                                                                 | Standard conditional          |
| Back up another standard object            | Configured standard object           | Account relationship field and every field expected in the backup                                                                                   | Object R; listed fields Read                                                                               | Customer-selected conditional |
| Back up a custom or managed-package object | Configured object                    | Account relationship field and every field expected in the backup                                                                                   | Object R; listed fields Read                                                                               | Customer extension            |
| Back up Account history                    | `AccountHistory`, only when selected | Fields visible to the integration user, including applicable history details such as `AccountId`, `CreatedDate`, `Field`, `OldValue` and `NewValue` | Account R; visibility of affected Accounts; access to the tracked Account fields whose history is required | Customer-selected conditional |

Kernel’s current backup process requests the fields available to the connected user. Fields hidden by field-level security are not included.

`AccountHistory` is not a baseline requirement. It is read only when it has been added to the Backup Objects configuration.

### Handle related records during merge

| Workflow                                                             | Object                                      | Fields                                                          | Permissions needed                                                    | Permission source                          |
| -------------------------------------------------------------------- | ------------------------------------------- | --------------------------------------------------------------- | --------------------------------------------------------------------- | ------------------------------------------ |
| Allow Salesforce to handle related Contacts during native merge      | Contact                                     | `AccountId`                                                     | Contact R/E; `AccountId` Read/Edit where Salesforce requires it       | Standard conditional                       |
| Allow Salesforce to handle related Opportunities during native merge | Opportunity                                 | `AccountId`                                                     | Opportunity R/E; `AccountId` Read/Edit where Salesforce requires it   | Standard conditional                       |
| Allow Salesforce to handle another standard child relationship       | Case, Contract or another applicable object | Account relationship field                                      | Object R/E and relationship Read/Edit where required by Salesforce    | Standard conditional                       |
| Clean duplicate Account–Contact relationships                        | `AccountContactRelation`                    | `Id`, `AccountId`, `ContactId`, `IsDirect`                      | AccountContactRelation R/D; listed fields Read                        | Standard conditional                       |
| Reparent a direct Contact during relationship cleanup                | Contact                                     | `AccountId`; `OwnerId` where an inactive owner must be replaced | Contact R/E; `AccountId` Edit; ownership permissions where applicable | Standard conditional                       |
| Delete configured related records before merge                       | Any configured deletable object             | `Id`, Account relationship field and backup fields              | Object R/D; listed fields Read; delete access to affected records     | Standard conditional or Customer extension |

Kernel does not explicitly reparent every related-object type. Salesforce’s native merge processing may move or retain related records according to Salesforce’s own merge rules.

### Roll back failed actions

| Workflow                                           | Object                    | Fields            | Permissions needed                                                       | Permission source                           |
| -------------------------------------------------- | ------------------------- | ----------------- | ------------------------------------------------------------------------ | ------------------------------------------- |
| Revert an Account hierarchy repair                 | Account                   | `ParentId`        | Account E; `ParentId` Edit                                               | Standard conditional                        |
| Restore a losing Account deleted during merge      | Account                   | Record ID         | Account D and access required to restore the record from the Recycle Bin | Kernel baseline plus Customer record access |
| Restore a related record Kernel explicitly deleted | Configured related object | Record ID         | Object D and access required to restore the record from the Recycle Bin  | Standard conditional or Customer extension  |
| Restore a field Kernel explicitly changed          | Account or related object | The changed field | Object E; field Edit                                                     | Same source as the original change          |

Rollback is best effort and depends on the affected records remaining available in the Salesforce Recycle Bin. It does not reconstruct every side effect performed by Salesforce’s native merge process.

### Link and sync records

| Workflow                            | Object                          | Fields                                                                     | Permissions needed                                                                              | Permission source                      |
| ----------------------------------- | ------------------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | -------------------------------------- |
| Link an Account to a Kernel Account | Account                         | `Kernel_Account__c`; `Kern_Id__c` where used                               | Account R/E; fields Read/Edit                                                                   | Kernel baseline                        |
| Store the reverse Account link      | `Kernel_Account__c`             | `Linked_Account_Field__c`                                                  | Kernel Account R/E; field Read/Edit                                                             | Kernel baseline                        |
| Sync Account to Kernel Account      | Account                         | `Id`, `Name`, `Kernel_Account__c` and every mapped source field            | Account R; source fields Read; Account E and `Kernel_Account__c` Edit when writing the backlink | Kernel baseline and Customer extension |
| Create a missing Kernel Account     | `Kernel_Account__c`             | `Name`, `Kernel_ID__c`, `Linked_Account_Field__c` and mapped target fields | Kernel Account C; target fields Edit                                                            | Kernel baseline and Customer extension |
| Update a Kernel Account             | `Kernel_Account__c`             | `Linked_Account_Field__c` and mapped target fields                         | Kernel Account R/E; match field Read; target fields Edit                                        | Kernel baseline and Customer extension |
| Sync Kernel Account to Account      | `Kernel_Account__c` and Account | Configured source and target fields                                        | Kernel Account R; Account R/E; source fields Read; target fields Edit                           | Kernel baseline and Customer extension |

Delete permission on `Kernel_Account__c` is not a normal integration-user requirement.

### Record-level access for mass actions

CRUD and field permissions do not bypass Salesforce sharing.

| Operation                          | Minimum record access                                                                    | Practical mass-action option                                      |
| ---------------------------------- | ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| Pull, safeguard or back up records | Read access to every record in scope                                                     | Customer sharing rules or object-specific `View All Records`      |
| Reparent or update Accounts        | Edit access to every affected Account                                                    | Customer sharing/ownership or Account `Modify All Records`        |
| Merge Accounts                     | Edit access to the master and delete access to losing Accounts                           | Customer sharing/ownership or Account `Modify All Records`        |
| Reparent related records           | Edit access to every affected related record                                             | Customer sharing or `Modify All Records` on that object           |
| Delete related records             | Delete access to every affected related record                                           | Customer sharing/ownership or `Modify All Records` on that object |
| Change ownership                   | Access to the source record and selected owner, plus any Salesforce transfer permissions | Conditional ownership-transfer permissions                        |

For broad cross-owner mass actions, object-specific `View All Records` or `Modify All Records` can be an appropriate least-privilege choice.

These permissions should be granted only on the objects involved. They do not require the integration user to receive:

* System Administrator
* `View All Data`
* `Modify All Data`
* `View All Fields`
* `Modify Metadata`
* `View Setup and Configuration`
* `View Roles and Role Hierarchy`

### Administration and end-user features

The following capabilities should be assigned separately from the headless integration-user baseline:

| Capability                                             | Intended user                  |
| ------------------------------------------------------ | ------------------------------ |
| Install or upgrade the Kernel package                  | Salesforce administrator       |
| Configure Connected App policies and OAuth             | Salesforce administrator       |
| Create or change sync configuration metadata           | Salesforce administrator       |
| Manage schedules and setup settings                    | Salesforce administrator       |
| Access Kernel setup tabs or configuration applications | Salesforce administrator       |
| Submit feedback from the Salesforce UI                 | Human Salesforce user          |
| View or administer application logs                    | Support or administrative user |

### Setup checklist

Before enabling a Kernel workflow:

1. Grant the fixed object and field access required by the workflow.
2. Add configured standard, custom and managed-package fields to a customer-managed extension permission set.
3. Review the workflow’s Account and related-object relationships.
4. Confirm that Salesforce sharing gives the integration user access to the complete record population.
5. Add object-specific `View All Records` or `Modify All Records` only where sharing is insufficient.
6. Test the workflow with the integration user.
7. Repeat the review after changing survivorship rules, backup objects, safeguards, sync mappings, Salesforce sharing or schema.

### Permissions not required by current workflows

`SetupAuditTrail` is not queried by Kernel’s current integration workflows and is not a runtime permission requirement.

If Salesforce attributes `SetupAuditTrail` activity to the Kernel integration user, provide Kernel Support with:

* The timestamp and timezone
* The integration-user ID
* The Connected App or OAuth client ID
* The observed query or request URI, where available

This information allows the source of the activity to be traced without granting additional administrative permissions.


# S3 file exchange

Exchange complete account datasets with Kernel through CSV files in customer-owned Amazon S3 buckets.

<details>

<summary><strong>Required setup and information to send Kernel</strong></summary>

Complete these three steps before Kernel configures the connection.

**1. Get these environment-specific values from Kernel**

* Kernel customer integrations IAM role ARN
* Tenant-specific External ID
* IAM trust policy template containing both values

**2. Set up these resources in your AWS account**

* One customer-owned S3 input bucket containing one complete CSV at a fixed object key
* One customer-owned S3 output destination, either in the same bucket or a different bucket
* One IAM role for both endpoints, or separate input and output roles
* A trust relationship on each role that uses the Kernel role ARN and External ID
* `s3:ListBucket` and `s3:GetObject` access for the input location
* `s3:PutObject` access for the output location
* KMS permissions and key policy access when either location uses a customer-managed KMS key
* A representative input CSV uploaded at the exact object key Kernel will read

**3. Send Kernel this completed configuration**

```
Environment: production | sandbox

Input IAM role ARN:
Input bucket name:
Input AWS region:
Exact input object key, including filename:
Input prefix, if used:
Input KMS key ARN: none | <key ARN>
Source account ID column header:
CSV header to Kernel field mapping:

Output IAM role ARN: same as input | <role ARN>
Output bucket name:
Output AWS region:
Output prefix or path:
Output base filename:
Delivery mode: overwrite | timestamped
Output KMS key ARN: none | <key ARN>
Output join key:
Output fields to include:

Representative input CSV uploaded at the exact key: yes
```

Do not send AWS access keys, secret access keys, session tokens, or KMS key material.

</details>

## What it is

The S3 file exchange lets your team send account data to Kernel and receive enriched results as CSV files. The files remain in Amazon S3 buckets that your team owns, and no CRM installation is required.

The connection has two logical endpoints:

* **Input:** one bucket and one exact CSV object that Kernel reads.
* **Output:** one bucket and one configured location where Kernel writes a CSV object.

The input and output endpoints can use the same physical bucket or different buckets. They can also use one IAM role for both directions or separate read and write roles.

{% hint style="info" %}
Each pull and export is started manually in Kernel. Use S3 for bulk, complete-file exchanges. Use the [Kernel API](/developer/api-getting-started) for record-level or near-real-time workflows.
{% endhint %}

## At a glance

* Customer-hosted S3 buckets only.
* Cross-account IAM role assumption with a Kernel-provided External ID.
* No AWS access keys or other long-lived credentials are shared with Kernel.
* Kernel reads one exact input object on every pull.
* Rows are created or updated using your configured source account ID.
* A row missing from a later file remains unchanged.
* Every export writes one complete CSV object.
* Output can overwrite one stable object or create a timestamped object for each export.

## How it works

```mermaid
sequenceDiagram
    autonumber
    participant You as Your team
    participant Input as Input S3 object
    participant K as Kernel
    participant Output as Output S3 location

    You->>Input: Write complete account CSV
    You->>K: Click Pull file now
    K->>Input: Read the exact configured object
    Note over K: Resolve identity, hierarchy,<br/>cleaning, and enrichment
    You->>K: Click Export to S3
    K->>Output: Write one complete results CSV
    Output->>You: Ingest using your source account ID
```

## Information exchange

### Kernel provides

Kernel provides these values separately for each environment:

* The Kernel customer integrations IAM role ARN.
* A tenant-specific External ID.
* A trust policy template containing both values.

Add the Kernel role ARN as the trusted AWS principal. The External ID must match exactly.

### Your team provides

For the input endpoint:

* Environment: production or sandbox.
* Input IAM role ARN.
* Input bucket name and AWS region.
* Exact input object key, including its prefixes and `.csv` filename.
* Input prefix, when the setup screen is used to browse for the object.
* Customer-managed KMS key ARN, when the object uses SSE-KMS.
* Source account ID column header.
* Mapping from your CSV headers to Kernel fields.
* A representative CSV at the exact input object key.

For the output endpoint:

* Output IAM role ARN, or confirmation that the input role is also used for output.
* Output bucket name and AWS region. The bucket may be the same as the input bucket.
* Output prefix or path.
* Base filename.
* Delivery mode: **Overwrite** or **Timestamped**.
* Customer-managed KMS key ARN, when the destination uses SSE-KMS.
* Output join key and selected output fields.

{% hint style="warning" %}
Do not send AWS access keys, secret access keys, session tokens, or KMS key material.
{% endhint %}

## IAM setup

### Role trust policy

Use the trust policy generated in Kernel. It allows the Kernel customer integrations role to assume your role using the tenant-specific External ID.

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "<KERNEL_CUSTOMER_INTEGRATIONS_ROLE_ARN>"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "<KERNEL_EXTERNAL_ID>"
        }
      }
    },
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "<KERNEL_CUSTOMER_INTEGRATIONS_ROLE_ARN>"
      },
      "Action": "sts:TagSession"
    }
  ]
}
```

### Input role permissions

The input role must be able to list the configured prefix during setup and read the exact configured object.

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ListInputPrefix",
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::<INPUT_BUCKET>",
      "Condition": {
        "StringLike": {
          "s3:prefix": [
            "<INPUT_PREFIX>",
            "<INPUT_PREFIX>/*"
          ]
        }
      }
    },
    {
      "Sid": "ReadExactInputObject",
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::<INPUT_BUCKET>/<EXACT_INPUT_KEY>"
    }
  ]
}
```

When the input object uses a customer-managed KMS key, the input role also needs `kms:Decrypt` on that key. The KMS key policy must allow the role.

If the input object is at the bucket root, omit the `s3:prefix` condition from the list statement.

### Output role permissions

The output role must be able to write beneath the configured output prefix.

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "WriteOutputObjects",
      "Effect": "Allow",
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::<OUTPUT_BUCKET>/<OUTPUT_PREFIX>/*"
    }
  ]
}
```

The output role does not need `s3:ListBucket`, `s3:GetObject`, or `s3:DeleteObject`.

When the output uses a customer-managed KMS key, the output role also needs `kms:GenerateDataKey` on that key. The KMS key policy must allow the role.

If the output is written at the bucket root, scope `s3:PutObject` to `arn:aws:s3:::<OUTPUT_BUCKET>/*`.

If one role is used for both directions, combine the input and output permission statements. Existing bucket policies, service control policies, permission boundaries, and KMS key policies must not deny these operations.

## Input object

Kernel reads exactly one configured object:

```
s3://<INPUT_BUCKET>/<EXACT_INPUT_KEY>
```

For example:

```
s3://acme-data-exchange/kernel/input/accounts.csv
```

Write each new dataset to the same object key. Start **Pull file now** only after the upload has completed. You may enable S3 versioning for your own audit and recovery requirements, but Kernel reads the current object version.

Kernel does not scan the bucket, combine files, select the newest file or partition, read a manifest, calculate a diff, or poll for changes.

## Input CSV contract

The input file must meet these requirements:

* UTF-8 encoded CSV with a `.csv` filename.
* One header row followed by one row per source account.
* Comma-delimited with standard double-quote escaping.
* Unique, non-empty headers that remain stable between pulls.
* One configured source account ID column.
* A non-empty, unique, stable source account ID on every row.
* Identifiers represented as strings so leading zeroes are preserved.
* ISO 8601 dates, preferably `YYYY-MM-DD` for date-only values.
* Empty cells for unavailable values.

Mapped column names must continue to exist under the same header. If the schema changes, refresh the available columns and correct renamed or removed mappings before pulling.

Kernel validates the complete file before applying the pull. An unreadable object, malformed CSV, missing mapped column, or invalid source account ID blocks the pull and leaves the previous successful state unchanged.

### Input fields

Your physical CSV header names are configurable. During setup, map each header to its logical Kernel field.

| Logical field            | Requirement                                                               |
| ------------------------ | ------------------------------------------------------------------------- |
| Source account ID        | Required and non-empty on every row. Must be unique and stable.           |
| Account name             | Required column. Provide a value wherever available.                      |
| Website                  | Required column. May be empty where unavailable.                          |
| Parent source account ID | Required column. Leave empty for top-level accounts or an unknown parent. |
| Owner ID                 | Required column. Leave empty where no owner is applicable.                |
| Last activity date       | Required column. Use ISO 8601 or leave empty.                             |
| Company legal name       | Recommended for identity resolution.                                      |
| Country and address      | Recommended for identity resolution and hierarchy decisions.              |
| LinkedIn company URL     | Recommended identity signal.                                              |
| Related domains          | Recommended identity signal.                                              |
| Alternative website      | Recommended identity signal.                                              |

Additional supported columns can be mapped in Kernel.

Example:

```csv
source_account_id,account_name,website,parent_source_account_id,owner_id,last_activity_date,legal_name,country,linkedin_url
A001,Acme Ltd,https://acme.com,,OWNER-17,2026-08-10,Acme Holdings Limited,GB,https://www.linkedin.com/company/acme
A002,Beta GmbH,https://beta.example,A001,OWNER-22,2026-08-09,Beta GmbH,DE,https://www.linkedin.com/company/beta
```

## Pull behavior

Each click of **Pull file now** performs one whole-file upsert:

* Kernel reads the exact configured object.
* The CSV and configured field mappings are validated.
* A new source account ID creates an account.
* An existing source account ID updates its mapped fields.
* An account absent from a later file remains unchanged.
* Row absence never deletes, deactivates, tags, or otherwise changes an account.
* Only one pull can run for the connection at a time.

The completed pull shows its object key, completion time, and counts for total, created, updated, unchanged, skipped, and failed rows.

## Output configuration

Configure the output endpoint separately from input:

* Output bucket and prefix.
* Base filename.
* Join key.
* Selected output fields.
* Delivery mode.

The configured join key is always included in the output so that you can join Kernel results back to your source data.

### Output data

Each export contains:

* One row for every account imported through this S3 connection.
* The configured source join key.
* The selected supported public account API fields.
* Selected cleaning fields and the current `cleaning_action`, when configured.

All supported public account API fields are selected by default. The join key appears first, followed by fields in the saved configuration order. Null values are written as empty cells.

Cleaning actions are informational data. Exporting them does not mutate your systems. Accepted or rejected operational review status is not included in the file exchange.

### Output delivery modes

**Overwrite** replaces one stable object on every successful export:

```
s3://<OUTPUT_BUCKET>/<OUTPUT_PREFIX>/<BASE_FILENAME>.csv
```

For example:

```
s3://acme-data-exchange/kernel/output/accounts.csv
```

**Timestamped** creates one new object on every successful export:

```
s3://<OUTPUT_BUCKET>/<OUTPUT_PREFIX>/<BASE_FILENAME>_<YYYYMMDDTHHmmssSSSZ>.csv
```

For example:

```
s3://acme-data-exchange/kernel/output/accounts_20260812T145530123Z.csv
```

The timestamp is UTC and includes milliseconds. Each export writes exactly one complete CSV object. An export is successful only after CSV generation and `PutObject` both complete.

## Connection verification

Kernel verifies input and output independently.

* **Input verification** assumes the input role and performs `GetObject` against the exact configured input key.
* **Output verification** assumes the output role and performs `PutObject` beneath the configured output prefix.

Output verification supports an intentionally write-only role and does not require list, read, or delete permissions.

The connection displays one of four states:

| State            | Meaning                                          |
| ---------------- | ------------------------------------------------ |
| **Disconnected** | Neither input nor output is verified and usable. |
| **Input only**   | Input is verified and usable; output is not.     |
| **Output only**  | Output is verified and usable; input is not.     |
| **Live**         | Both input and output are verified and usable.   |

Changing a role ARN, bucket, region, input key, output prefix, or KMS configuration requires the affected capability to be verified again.

## Operating cycle

1. Produce the new account dataset.
2. Write it to the exact configured input object key.
3. Confirm that the upload has completed.
4. In Kernel, click **Pull file now**.
5. Review the object key, status, and row counts.
6. Complete the required processing and review in Kernel.
7. Click **Export to S3**.
8. Review the output object key, status, row count, selected field count, and delivery mode.
9. Ingest the complete output file using your source account ID.

## File exchange boundaries

The S3 file exchange uses manual, complete-file operations. It does not use:

* Scheduled pulls or automatic polling.
* Automatic exports.
* Multiple input files or newest-partition discovery.
* Diffs, incremental files, manifests, or completion markers.
* Deletion or deactivation inferred from a missing row.
* Kernel-hosted exchange buckets.
* Accept or reject actions through S3.

Use the [Kernel API](/developer/api-getting-started) for event-driven integrations, record-level writes, or near-real-time retrieval.

## Troubleshooting

### AssumeRole fails

Check the trusted Kernel role ARN, External ID, `sts:AssumeRole`, `sts:TagSession`, permission boundary, and organization service control policies.

### Input access is denied

Check `s3:GetObject` on the exact object ARN, `s3:ListBucket` on the configured prefix, bucket policy denies, and `kms:Decrypt` when SSE-KMS is used.

### The object cannot be found

Check the bucket, region, exact key, filename case, and prefix. S3 object keys are case-sensitive.

### The CSV is rejected

Check UTF-8 encoding, the header row, delimiter, quote escaping, mapped columns, and source account IDs. Confirm that the upload completed before starting the pull.

### Output access is denied

Check `s3:PutObject` on the configured prefix, bucket policy denies, the selected output role, and `kms:GenerateDataKey` when SSE-KMS is used.

## Customer handoff checklist

Provide the following information to your Kernel contact.

### Input

* Environment: production or sandbox
* Input IAM role ARN
* Input bucket
* Input region
* Input prefix
* Exact input object key
* Customer-managed KMS key ARN, if applicable
* Source account ID header
* Confirmation that a representative sample is available at the exact key
* Field mapping owner
* Notes about date formats or custom fields

### Output

* Whether the input role is also used for output
* Output IAM role ARN, if separate
* Output bucket
* Output region
* Output prefix
* Base filename
* Delivery mode: Overwrite or Timestamped
* Customer-managed KMS key ARN, if applicable
* Output join key
* Required output fields
* Downstream ingestion owner


# API

Getting started with the API

<a href="https://dev.kernel.ai" class="button primary" data-icon="brackets-curly">Open API reference</a>

<a href="https://app.kernel.ai/" class="button primary" data-icon="key">Kernel customers - create API key</a>

<a href="https://kernel.ai/kernel-api" class="button primary" data-icon="key-skeleton">Non-Kernel customers - request API key</a>

## Prerequisities

* A Kernel API key
  * Copy the key value and the webhook signing secret
  * Store the key, secret and base URL in your .env file
    * `KERNEL_API_KEY="your-key-here"`
    * `KERNEL_WEBHOOK_SECRET="your-webhook-secret-here"`
    * `KERNEL_API_BASE_URL=`[`https://api.kernel.ai/rest`](https://api.kernel.ai/rest)
* A connection to your system of record (i.e., CRM or data warehouse)

## Before you start

* The Kernel APIs retrieve and reconcile company information from a range of public sources, and return it with a confidence level and the reasoning behind each value. They run asynchronously: a request returns a job you poll, so results are not immediate. Most jobs take a few minutes, with firmographics and combined jobs being the slowest

## Getting started prompts

Use these to run the three main Kernel APIs, step by step.

{% tabs %}
{% tab title="Entity resolution" %}
Turn a messy or partial account record into a verified, canonical identity you can trust.

<a href="https://dev.kernel.ai/api-reference/endpoint/create-entity-resolution" class="button primary" data-icon="brackets-curly">Entity resolution API reference</a>

{% prompt description=" Example entity resolution prompt" icon="screwdriver-wrench" %}

````markdown
# Resolve a record to its KERN ID

You have access to the user's system of record (for example their CRM) and to the Kernel API
(`KERNEL_API_KEY` is already configured). When the user points you at a record, resolve it to one
canonical **KERN ID** and return its core identity. The KERN ID is the foundation you build on for
enrichment, hierarchy, and other Kernel use cases.

## When to use it

The user references a record by whatever identifier they have (a CRM ID, for example) and wants it
resolved to a single verified company identity with a stable KERN ID.

## What to do

1. Look up the referenced record in the connected system of record and read its identifying fields:
   legal or company name, website, country, plus any city, state, postal code, address, email, or
   LinkedIn URL.
2. Call Kernel entity resolution with those fields. Set `external_id` to the record's own id (its CRM
   id) so the result maps straight back to it.
3. Return the KERN ID and the resolved identity, and store the KERN ID on the record.

## Inputs

Send whatever the record gives you; `website` is the strongest signal.

`legal_name`, `trading_name`, `website`, `country`, `city`, `state`, `postal_code`, `address`,
`email`, `linkedin_url`, `match_to_linkedin` (bool), `identity_bias` (`URL_BIAS` default, or
`NAME_BIAS`), `external_id` (the record's own id, echoed back).

## Call it (curl)

```bash
curl -s -X POST https://api.kernel.ai/rest/v1/entity-resolution \
  -H "x-api-key: $KERNEL_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "legal_name": "Stripe, Inc.",
    "trading_name": "Stripe",
    "website": "https://stripe.com",
    "country": "US",
    "city": "South San Francisco",
    "state": "CA",
    "postal_code": "94070",
    "email": "info@stripe.com",
    "match_to_linkedin": true,
    "identity_bias": "NAME_BIAS",
    "external_id": "stripe-001"
  }'
# returns {"id":"<job_id>","status":"processing"}

curl -s https://api.kernel.ai/rest/v1/entity-resolution/<job_id> -H "x-api-key: $KERNEL_API_KEY"
# poll until status is "completed"
```

## Call it (Python)

```python
import os, time, requests

BASE = "https://api.kernel.ai/rest"
HEADERS = {"x-api-key": os.environ["KERNEL_API_KEY"], "Content-Type": "application/json"}

def resolve(**signals):
    job_id = requests.post(f"{BASE}/v1/entity-resolution", headers=HEADERS, json=signals).json()["id"]
    delay = 2
    while True:                                    # resolution takes about 1 to 2 minutes
        time.sleep(delay)
        data = requests.get(f"{BASE}/v1/entity-resolution/{job_id}", headers=HEADERS).json()
        if data["status"] in ("completed", "failed"):
            return data
        delay = min(delay + 2, 30)

# 1) Read the record from your connected system of record by its id.
record = get_record("stripe-001")                  # your system's own lookup

# 2) Resolve it; pass the record's id as external_id so the result maps back.
result = resolve(
    legal_name=record["legal_name"],
    website=record["website"],
    country=record["country"],
    external_id=record["id"],
)
print(result["record"]["kernel_id"])               # the KERN ID, your foundation
```

## What you get back

In `record`:
- `kernel_id`: the canonical KERN ID. Store it on the record; it is the key for enrichment, hierarchy,
  and other use cases.
- `legal_info`: `legal_name`, `country`, `website`, `confidence`, `reasoning`.
- `trading_info`: `trading_name`.
- `entity_classification`: `type` and `subtype` (for example Company / Operating).

## Notes

- Pass as many of the record's fields as you have. `website` is the strongest signal; add `country` to
  separate same named companies in different regions.
- `identity_bias` defaults to `URL_BIAS` (trust the website most). Use `NAME_BIAS` when the name is
  more reliable than the URL.
- Resolution usually takes 1 to 2 minutes. For long jobs, pass `webhook_url` and Kernel calls you back
  instead of polling.
- Errors: 403 invalid or missing key, 429 rate limited, 500 server error.

## Setting up your API key (do this first, or nothing above will run)

Every call needs a Kernel API key in `KERNEL_API_KEY`. Until it is set, the prompt above fails with a
403 and returns nothing useful.

**Get a key:**
- Already a Kernel customer: create one in the app at app.kernel.ai (Settings > API keys).
- Not a customer yet: request one at https://kernel.ai/kernel-api.
- Kernel already sent you a key: you are ready for the next step.

**Set it up:** save the key to a `.env` file in your project (never commit `.env`):

```
KERNEL_API_KEY=your-key-here
```

Load it with `from dotenv import load_dotenv; load_dotenv()` in Python, or
`export KERNEL_API_KEY=your-key-here` in your shell for the curl examples.

**Test the connection before running the prompt.** A `202` means the key works; a `403` means it is
missing or wrong, so fix it before continuing:

```bash
curl -s -o /dev/null -w "%{http_code}\n" -X POST https://api.kernel.ai/rest/v1/entity-resolution \
  -H "x-api-key: $KERNEL_API_KEY" -H "Content-Type: application/json" -d '{"legal_name":"Stripe"}'
```

## Other Kernel use cases

- **Resolve**: company signals to a KERN ID and identity. Use when you need a canonical ID.
- **Enrich firmographics**: KERN ID to revenue, headcount, location, status. Use when you need company attributes.
- **Enrich hierarchy**: KERN ID to parent and ultimate parent. Use when you need the corporate tree.

````

{% endprompt %}
{% endtab %}

{% tab title="Enrich with firmographics" %}
Get up-to-date revenue, headcount, and location for a company, each backed up by reasoning.

<a href="https://dev.kernel.ai/api-reference/endpoint/get-firmographics" class="button primary" data-icon="brackets-curly">Enrich with firmographics API reference</a>

{% prompt description="Example enrich with firmographics prompt" icon="dollar-sign" %}

````markdown
# Get a record's firmographics

You have access to the user's system of record and a record that already carries a Kernel `kernel_id`
(the Kernel API key is already configured). Return the firmographic data the user asks for.

## When to use it

The user wants firmographic facts about a company they have already resolved: revenue, headcount,
location, or operating status.

**Requires entity resolution first.** Kernel is keyed on the KERN ID: this accepts only a `kernel_id`,
never a company name or domain. The only way to get a `kernel_id` is to resolve the company (see the
Resolve prompt), so resolve first. Without a `kernel_id`, this does not run.

## First, ask what they want

Kernel returns four firmographic data points. Ask the user which they want, and offer the choice of
one, a combination, or all of them:

- **revenue** (entity and consolidated group figures)
- **headcount**
- **location** (operating and registered address)
- **operating status** (active, absorbed, and so on)

Then return only the data points they picked.

## Inputs

- `kernel_id` (required): the KERN ID stored on the record.
- `webhook_url` (optional): get a callback instead of polling.

## Call it (curl)

```bash
curl -s -X POST https://api.kernel.ai/rest/v1/firmographics \
  -H "x-api-key: $KERNEL_API_KEY" -H "Content-Type: application/json" \
  -d '{"kernel_id":"<KERNEL_ID>"}'
# returns {"id":"<job_id>","status":"processing"}

curl -s https://api.kernel.ai/rest/v1/firmographics/<job_id> -H "x-api-key: $KERNEL_API_KEY"
# poll until status is "completed"
```

## Call it (Python)

```python
import os, time, requests

BASE = "https://api.kernel.ai/rest"
HEADERS = {"x-api-key": os.environ["KERNEL_API_KEY"], "Content-Type": "application/json"}

# Maps the choices you offer the user to the fields in the response.
FIELDS = {"revenue": "revenue", "headcount": "headcount",
          "location": "location", "operating status": "op_status"}

def firmographics(kernel_id, wanted="all"):
    job_id = requests.post(f"{BASE}/v1/firmographics", headers=HEADERS,
                           json={"kernel_id": kernel_id}).json()["id"]
    delay = 2
    while True:                                     # firmographics can take 3 to 4 minutes
        time.sleep(delay)
        data = requests.get(f"{BASE}/v1/firmographics/{job_id}", headers=HEADERS).json()
        if data["status"] in ("completed", "failed"):
            break
        delay = min(delay + 5, 30)

    record = data.get("record", {})
    if wanted == "all":
        return record
    # wanted is a list of the user's choices, e.g. ["revenue", "headcount"]
    return {choice: record.get(FIELDS[choice]) for choice in wanted}

# Ask the user first, then pass their choice:
print(firmographics("<KERNEL_ID>", wanted=["revenue", "headcount"]))   # a combination
print(firmographics("<KERNEL_ID>", wanted="all"))                       # everything
```

## What you get back

The full firmographics record holds all four data points; return only the ones the user chose. Every
data point carries `reasoning`, and headcount and revenue carry a `confidence`.

- `op_status`: `operational_status` (for example Active or Absorbed), `reasoning`.
- `location`: `operating` and `registered`, each with `street`, `city`, `state`, `postcode`,
  `country`, `reasoning`.
- `headcount`: `count`, plus `count_entity` (this entity alone) and `count_consolidated` (whole group),
  `confidence`, `reasoning`.
- `revenue`: `usd` and `local` (with `local_currency`), each split into `*_entity` (this entity) and
  `consolidated_*` (whole group), plus `confidence`, `source`, `reasoning`.

### Example response

```json
{
  "kernel_id": "7944166432",
  "op_status": {
    "operational_status": "Active",
    "reasoning": "Entity is actively operating with no signs of cessation or absorption."
  },
  "location": {
    "operating": {
      "street": null, "city": "Singrauli", "state": "Madhya Pradesh",
      "country": "India", "postcode": "486889",
      "reasoning": "Address derived from company registration records."
    },
    "registered": {
      "street": "P.O. Singrauli Colliery", "city": "Singrauli", "state": "Madhya Pradesh",
      "country": "India", "postcode": "486889",
      "reasoning": "Address from official registry filing."
    }
  },
  "headcount": {
    "count": 13307, "count_entity": 13307, "count_consolidated": 13307,
    "confidence": "HIGH", "reasoning": "Headcount derived from annual report figures."
  },
  "revenue": {
    "usd": 2613846573, "usd_entity": 2613846573, "consolidated_usd": 2613846573,
    "local": 217820547750, "local_entity": 217820547750, "consolidated_local": 217820547750,
    "local_currency": "INR", "local_currency_entity": "INR", "consolidated_currency": "INR",
    "confidence": "HIGH", "source": "identified",
    "reasoning": "Revenue sourced from audited annual financial statements."
  }
}
```

## Notes

- Firmographics is slow (often 3 to 4 minutes). Use a longer poll interval, or pass `webhook_url` and
  skip polling.
- Revenue and headcount each come at two scopes: this entity alone (`usd_entity` / `count_entity`) and
  the whole group (`consolidated_usd` / `count_consolidated`). For "how big is this company," use the
  consolidated figure. For a single-entity company the two are equal.
- Each field carries `reasoning` that cites its sources. Surface it when the user wants provenance.
- Errors: 403 invalid or missing key, 429 rate limited, 500 server error.

## Setting up your API key (do this first, or nothing above will run)

Every call needs a Kernel API key in `KERNEL_API_KEY`. Until it is set, the prompt above fails with a
403 and returns nothing useful.

**Get a key:**
- Already a Kernel customer: create one in the app at app.kernel.ai (Settings > API keys).
- Not a customer yet: request one at https://kernel.ai/kernel-api.
- Kernel already sent you a key: you are ready for the next step.

**Set it up:** save the key to a `.env` file in your project (never commit `.env`):

```
KERNEL_API_KEY=your-key-here
```

Load it with `from dotenv import load_dotenv; load_dotenv()` in Python, or
`export KERNEL_API_KEY=your-key-here` in your shell for the curl examples.

**Test the connection before running the prompt.** A `202` means the key works; a `403` means it is
missing or wrong, so fix it before continuing:

```bash
curl -s -o /dev/null -w "%{http_code}\n" -X POST https://api.kernel.ai/rest/v1/entity-resolution \
  -H "x-api-key: $KERNEL_API_KEY" -H "Content-Type: application/json" -d '{"legal_name":"Stripe"}'
```

## Other Kernel use cases

- **Resolve**: company signals to a KERN ID and identity. Use when you need a canonical ID.
- **Enrich hierarchy**: KERN ID to parent and ultimate parent. Use when you need the corporate tree.
- **Enrich (both)**: KERN ID to hierarchy and firmographics together. Use when you want to fill a whole record.

````

{% endprompt %}
{% endtab %}

{% tab title="Enrich with parent hierarchies" %}
See the current corporate ownership of an account, from its direct parent, all the way up the hierarchy.

<a href="https://dev.kernel.ai/api-reference/endpoint/resolve-parent" class="button primary" data-icon="brackets-curly">Enrich with parent hierarchy API reference</a>

{% prompt description="ExampleEnrich with parent hierarchies prompt" icon="family-dress" %}

````markdown
# Get a record's corporate hierarchy

You have access to the user's system of record and a record that already carries a Kernel `kernel_id`
(the Kernel API key is already configured). Return the company's parent and ultimate parent.

## When to use it

The user wants to know how a company they have already resolved sits in its corporate tree: who owns
it, what the ultimate (top) parent is, and whether it is a regional subsidiary.

**Requires entity resolution first.** Kernel is keyed on the KERN ID: this accepts only a `kernel_id`,
never a company name or domain. The only way to get a `kernel_id` is to resolve the company (see the
Resolve prompt), so resolve first. Without a `kernel_id`, this does not run.

## Inputs

- `kernel_id` (required): the KERN ID stored on the record.
- `webhook_url` (optional): get a callback instead of polling.

## Call it (curl)

```bash
curl -s -X POST https://api.kernel.ai/rest/v1/resolve-parent \
  -H "x-api-key: $KERNEL_API_KEY" -H "Content-Type: application/json" \
  -d '{"kernel_id":"<KERNEL_ID>"}'
# returns {"id":"<job_id>","status":"processing"}

curl -s https://api.kernel.ai/rest/v1/resolve-parent/<job_id> -H "x-api-key: $KERNEL_API_KEY"
# poll until status is "completed"
```

## Call it (Python)

```python
import os, time, requests

BASE = "https://api.kernel.ai/rest"
HEADERS = {"x-api-key": os.environ["KERNEL_API_KEY"], "Content-Type": "application/json"}

def hierarchy(kernel_id):
    job_id = requests.post(f"{BASE}/v1/resolve-parent", headers=HEADERS,
                           json={"kernel_id": kernel_id}).json()["id"]
    delay = 2
    while True:                                     # usually a minute or two
        time.sleep(delay)
        data = requests.get(f"{BASE}/v1/resolve-parent/{job_id}", headers=HEADERS).json()
        if data["status"] in ("completed", "failed"):
            return data.get("record", {})
        delay = min(delay + 2, 30)

rec = hierarchy("<KERNEL_ID>")
print(rec.get("parent"))                 # immediate parent, or None
print(rec.get("top_parent"))             # ultimate parent
print(rec.get("top_operating_parent"))   # highest operating company (skips holding companies)
print(rec.get("regional_subsidiary"))    # is_regional, regional_scope
```

## What you get back

In `record`:
- `parent`: the immediate parent, or `null` if it has none.
- `top_parent`: the ultimate parent at the top of the tree (its own `kernel_id`, legal name, country).
- `top_operating_parent`: the highest operating company in the tree, skipping pure holding companies
  and investment vehicles. Conditionally present, and usually the best entity to roll accounts up to.
- `regional_subsidiary`: `is_regional` and `regional_scope`.

These three parent fields can point to the same entity (as in the example below, where LinkedIn's
parent Microsoft is also the top and the top operating company) or differ: for a company under a
holding company, `top_parent` may be the holding company while `top_operating_parent` is the operating
company beneath it.

### Example response

Resolving LinkedIn, then `resolve-parent` on its `kernel_id`:

```json
{
  "kernel_id": "9331102168",
  "parent": {
    "kernel_id": "5034871910",
    "legal_name": "Microsoft Corporation",
    "trading_name": "Microsoft",
    "website": "microsoft.com",
    "country": "US",
    "entity_category": "Company",
    "entity_sub_category": "Operating",
    "confidence": "HIGH",
    "reasoning": "Microsoft Corporation is the acquirer and parent of LinkedIn Corporation (Microsoft acquisition announcement; Reuters)."
  },
  "top_parent": {
    "kernel_id": "5034871910",
    "legal_name": "Microsoft Corporation",
    "trading_name": "Microsoft",
    "website": "microsoft.com",
    "country": "US",
    "entity_category": "Company",
    "entity_sub_category": "Operating",
    "confidence": "HIGH"
  },
  "top_operating_parent": {
    "kernel_id": "5034871910",
    "legal_name": "Microsoft Corporation",
    "trading_name": "Microsoft",
    "website": "microsoft.com",
    "country": "US",
    "entity_category": "Company",
    "entity_sub_category": "Operating",
    "confidence": "HIGH"
  },
  "regional_subsidiary": {
    "is_regional": false,
    "regional_scope": null,
    "reasoning": "LinkedIn operates under its own global brand, not as a single-country arm of Microsoft."
  }
}
```

## Notes

- A company can be its own `top_parent`, in which case `parent` is `null`. That is expected.
- Hierarchy is async; poll until completed (usually a minute or two), or pass `webhook_url`.
- Errors: 403 invalid or missing key, 429 rate limited, 500 server error.

## Setting up your API key (do this first, or nothing above will run)

Every call needs a Kernel API key in `KERNEL_API_KEY`. Until it is set, the prompt above fails with a
403 and returns nothing useful.

**Get a key:**
- Already a Kernel customer: create one in the app at app.kernel.ai (Settings > API keys).
- Not a customer yet: request one at https://kernel.ai/kernel-api.
- Kernel already sent you a key: you are ready for the next step.

**Set it up:** save the key to a `.env` file in your project (never commit `.env`):

```
KERNEL_API_KEY=your-key-here
```

Load it with `from dotenv import load_dotenv; load_dotenv()` in Python, or
`export KERNEL_API_KEY=your-key-here` in your shell for the curl examples.

**Test the connection before running the prompt.** A `202` means the key works; a `403` means it is
missing or wrong, so fix it before continuing:

```bash
curl -s -o /dev/null -w "%{http_code}\n" -X POST https://api.kernel.ai/rest/v1/entity-resolution \
  -H "x-api-key: $KERNEL_API_KEY" -H "Content-Type: application/json" -d '{"legal_name":"Stripe"}'
```

## Other Kernel use cases

- **Resolve**: company signals to a KERN ID and identity. Use when you need a canonical ID.
- **Enrich firmographics**: KERN ID to revenue, headcount, location, status. Use when you need company attributes.
- **Enrich (both)**: KERN ID to hierarchy and firmographics together. Use when you want to fill a whole record.

````

{% endprompt %}
{% endtab %}

{% tab title="Resolve & Enrich" %}
Resolve first, then enrich with firmographics or hierarchies (or both) in a single API call.

<a href="https://dev.kernel.ai/api-reference/endpoint/combined#resolve-then-enrich-firmographics" class="button primary" data-icon="brackets-curly">Resolve and enrich API reference</a>

{% prompt description="Resolve and enrich with firmographics" icon="1" %}

````markdown
# Resolve a record and get its firmographics in one call

You have access to the user's system of record (for example their CRM) and the Kernel API
(`KERNEL_API_KEY` is already configured). Given a company's details, Kernel's combined endpoint resolves
the company and returns its firmographics (revenue, headcount, location, operating status) in a single
call. Use it when you want a company's identity and its core facts, without chaining Resolve then
Firmographics.

This resolves the company first, then runs the requested job; everything is keyed on the KERN ID that
resolution produces.

## When to use it

You have a record (or just a company name and website) and want its canonical identity plus revenue,
headcount, location, and operating status. Resolution is included, so you do not need a `kernel_id`
first.

## Inputs

- `jobs` (required): `["firmographics"]` for this prompt. Without `jobs` the call is accepted but never
  processes.
- Company signals (same as Resolve): `legal_name`, `trading_name`, `website`, `country`, `city`,
  `state`, `postal_code`, `address`, `email`, `linkedin_url`, `match_to_linkedin`, `external_id`.
- `webhook_url` (optional, recommended here since firmographics is the slow step).

## Call it (curl)

```bash
curl -s -X POST https://api.kernel.ai/rest/v1/combined \
  -H "x-api-key: $KERNEL_API_KEY" -H "Content-Type: application/json" \
  -d '{"jobs":["firmographics"],"legal_name":"Stripe","website":"stripe.com","country":"US"}'
# -> {"id":"<job_id>","status":"processing"}

curl -s https://api.kernel.ai/rest/v1/combined/<job_id> -H "x-api-key: $KERNEL_API_KEY"
# poll until status is "completed"
```

## Call it (Python)

```python
import os, time, requests

BASE = "https://api.kernel.ai/rest"
HEADERS = {"x-api-key": os.environ["KERNEL_API_KEY"], "Content-Type": "application/json"}

def combined(jobs, **signals):
    job_id = requests.post(f"{BASE}/v1/combined", headers=HEADERS, json={"jobs": jobs, **signals}).json()["id"]
    delay = 5
    while True:                                    # exponential backoff; firmographics can take ~10 min
        time.sleep(delay)
        data = requests.get(f"{BASE}/v1/combined/{job_id}", headers=HEADERS).json()
        if data["status"] in ("completed", "failed"):
            return data.get("record", {})
        delay = min(delay * 2, 30)

rec = combined(["firmographics"], legal_name="Stripe", website="stripe.com", country="US")
print(rec["kernel_id"])
print(rec["firmographics"]["revenue"])        # firmographics, nested under "firmographics"
print(rec["firmographics"]["headcount"])
```

## What you get back

One `record`:
- **resolve identity:** `kernel_id`, `legal_info`, `trading_info`, `entity_classification`, `identity_*` fields
- **`firmographics`:** `revenue`, `headcount`, `location`, `op_status`

Firmographics is nested under `firmographics`. No hierarchy is returned (this prompt does not request it).

## Notes

- `jobs` is required. Omit it and the job is accepted but never leaves `pending`.
- This includes firmographics, the slow step (about 10 minutes in testing). Pass `webhook_url` rather
  than holding a long poll.
- Revenue and headcount come at two scopes: this entity alone (`usd_entity` / `count_entity`) and the
  whole group (`consolidated_usd` / `count_consolidated`). For "how big is this company," use the
  consolidated figure.
- Resolution is included, so no `kernel_id` is needed up front. If you already hold a `kernel_id`, use
  the single-purpose Firmographics prompt.
- Errors: 403 invalid or missing key, 429 rate limited, 500 server error.

## Setting up your API key (do this first, or nothing above will run)

Every call needs a Kernel API key in `KERNEL_API_KEY`. Until it is set, the prompt above fails with a
403 and returns nothing useful.

**Get a key:**
- Already a Kernel customer: create one in the app at app.kernel.ai (Settings > API keys).
- Not a customer yet: request one at https://kernel.ai/kernel-api.
- Kernel already sent you a key: you are ready for the next step.

**Set it up:** save the key to a `.env` file in your project (never commit `.env`):

```
KERNEL_API_KEY=your-key-here
```

Load it with `from dotenv import load_dotenv; load_dotenv()` in Python, or
`export KERNEL_API_KEY=your-key-here` in your shell for the curl examples.

**Test the connection before running the prompt.** A `202` means the key works; a `403` means it is
missing or wrong, so fix it before continuing:

```bash
curl -s -o /dev/null -w "%{http_code}\n" -X POST https://api.kernel.ai/rest/v1/entity-resolution \
  -H "x-api-key: $KERNEL_API_KEY" -H "Content-Type: application/json" -d '{"legal_name":"Stripe"}'
```

## Other Kernel use cases

- **Resolve**: company signals to a KERN ID and identity.
- **Combined (full)** / **(resolve + hierarchy)**: the other one-call variants.
- **Enrich / Firmographics / Hierarchy**: the same data via separate calls when you already have a `kernel_id`.

````

{% endprompt %}

{% prompt description="Resolve and enrich with parent hierarchies" icon="2" %}

````markdown
# Resolve a record and get its hierarchy in one call

You have access to the user's system of record (for example their CRM) and the Kernel API
(`KERNEL_API_KEY` is already configured). Given a company's details, Kernel's combined endpoint resolves
the company and returns its corporate hierarchy (parent and ultimate parent) in a single call. Use it
when you want a company's identity and who owns it, without chaining Resolve then Hierarchy.

This resolves the company first, then runs the requested job; everything is keyed on the KERN ID that
resolution produces.

## When to use it

You have a record (or just a company name and website) and want its canonical identity plus its parent
and ultimate parent. Resolution is included, so you do not need a `kernel_id` first.

## Inputs

- `jobs` (required): `["resolve-parent"]` for this prompt. Without `jobs` the call is accepted but
  never processes.
- Company signals (same as Resolve): `legal_name`, `trading_name`, `website`, `country`, `city`,
  `state`, `postal_code`, `address`, `email`, `linkedin_url`, `match_to_linkedin`, `external_id`.
- `webhook_url` (optional).

## Call it (curl)

```bash
curl -s -X POST https://api.kernel.ai/rest/v1/combined \
  -H "x-api-key: $KERNEL_API_KEY" -H "Content-Type: application/json" \
  -d '{"jobs":["resolve-parent"],"legal_name":"Stripe","website":"stripe.com","country":"US"}'
# -> {"id":"<job_id>","status":"processing"}

curl -s https://api.kernel.ai/rest/v1/combined/<job_id> -H "x-api-key: $KERNEL_API_KEY"
# poll until status is "completed"
```

## Call it (Python)

```python
import os, time, requests

BASE = "https://api.kernel.ai/rest"
HEADERS = {"x-api-key": os.environ["KERNEL_API_KEY"], "Content-Type": "application/json"}

def combined(jobs, **signals):
    job_id = requests.post(f"{BASE}/v1/combined", headers=HEADERS, json={"jobs": jobs, **signals}).json()["id"]
    delay = 5
    while True:                                    # exponential backoff
        time.sleep(delay)
        data = requests.get(f"{BASE}/v1/combined/{job_id}", headers=HEADERS).json()
        if data["status"] in ("completed", "failed"):
            return data.get("record", {})
        delay = min(delay * 2, 30)

rec = combined(["resolve-parent"], legal_name="Stripe", website="stripe.com", country="US")
print(rec["kernel_id"])
print(rec["parentage"]["parent"])             # immediate parent, or None
print(rec["parentage"]["top_parent"])         # ultimate parent
```

## What you get back

One `record`:
- **resolve identity:** `kernel_id`, `legal_info`, `trading_info`, `entity_classification`, `identity_*` fields
- **`parentage`:** `parent`, `top_parent`, `top_operating_parent`, `regional_subsidiary`

Hierarchy is nested under `parentage`. No firmographics are returned (this prompt does not request them).

## Notes

- `jobs` is required. Omit it and the job is accepted but never leaves `pending`.
- This omits firmographics, so it is faster than the full combined call (no slow firmographics step).
  Poll with backoff, or pass `webhook_url`.
- Resolution is included, so no `kernel_id` is needed up front. If you already hold a `kernel_id`, use
  the single-purpose Hierarchy prompt.
- Sanity-check hierarchy: a confident `parent` / `top_parent` can still be debatable (in testing, Bose
  resolved to MIT, a majority but non-voting owner). Review before trusting.
- Errors: 403 invalid or missing key, 429 rate limited, 500 server error.

## Setting up your API key (do this first, or nothing above will run)

Every call needs a Kernel API key in `KERNEL_API_KEY`. Until it is set, the prompt above fails with a
403 and returns nothing useful.

**Get a key:**
- Already a Kernel customer: create one in the app at app.kernel.ai (Settings > API keys).
- Not a customer yet: request one at https://kernel.ai/kernel-api.
- Kernel already sent you a key: you are ready for the next step.

**Set it up:** save the key to a `.env` file in your project (never commit `.env`):

```
KERNEL_API_KEY=your-key-here
```

Load it with `from dotenv import load_dotenv; load_dotenv()` in Python, or
`export KERNEL_API_KEY=your-key-here` in your shell for the curl examples.

**Test the connection before running the prompt.** A `202` means the key works; a `403` means it is
missing or wrong, so fix it before continuing:

```bash
curl -s -o /dev/null -w "%{http_code}\n" -X POST https://api.kernel.ai/rest/v1/entity-resolution \
  -H "x-api-key: $KERNEL_API_KEY" -H "Content-Type: application/json" -d '{"legal_name":"Stripe"}'
```

## Other Kernel use cases

- **Resolve**: company signals to a KERN ID and identity.
- **Combined (full)** / **(resolve + firmographics)**: the other one-call variants.
- **Enrich / Firmographics / Hierarchy**: the same data via separate calls when you already have a `kernel_id`.

````

{% endprompt %}

{% prompt description="Resolve and enrich with firmographics and hierarchies" icon="3" %}

````markdown
# Resolve and fully enrich a record in one call

You have access to the user's system of record (for example their CRM) and the Kernel API
(`KERNEL_API_KEY` is already configured). Given a company's details, Kernel's combined endpoint resolves
the company and enriches it with corporate hierarchy and firmographics in a single call, returning one
record. Use it when you want the whole picture and prefer one call over chaining Resolve, Hierarchy,
and Firmographics separately.

This resolves the company first, then runs the requested jobs; everything is keyed on the KERN ID that
resolution produces.

## When to use it

You have a record (or just a company name and website) and want its canonical identity, corporate
hierarchy, and firmographics together. Resolution is included, so you do not need a `kernel_id` first.

## Inputs

- `jobs` (required): `["resolve-parent", "firmographics"]` for this prompt. Without `jobs` the call is
  accepted but never processes.
- Company signals (same as Resolve): `legal_name`, `trading_name`, `website`, `country`, `city`,
  `state`, `postal_code`, `address`, `email`, `linkedin_url`, `match_to_linkedin`, `external_id`.
- `webhook_url` (optional, recommended here since this includes the slow firmographics step).

## Call it (curl)

```bash
curl -s -X POST https://api.kernel.ai/rest/v1/combined \
  -H "x-api-key: $KERNEL_API_KEY" -H "Content-Type: application/json" \
  -d '{"jobs":["resolve-parent","firmographics"],"legal_name":"Stripe","website":"stripe.com","country":"US"}'
# -> {"id":"<job_id>","status":"processing"}

curl -s https://api.kernel.ai/rest/v1/combined/<job_id> -H "x-api-key: $KERNEL_API_KEY"
# poll until status is "completed"
```

## Call it (Python)

```python
import os, time, requests

BASE = "https://api.kernel.ai/rest"
HEADERS = {"x-api-key": os.environ["KERNEL_API_KEY"], "Content-Type": "application/json"}

def combined(jobs, **signals):
    job_id = requests.post(f"{BASE}/v1/combined", headers=HEADERS, json={"jobs": jobs, **signals}).json()["id"]
    delay = 5
    while True:                                    # exponential backoff; this can take ~10 min
        time.sleep(delay)
        data = requests.get(f"{BASE}/v1/combined/{job_id}", headers=HEADERS).json()
        if data["status"] in ("completed", "failed"):
            return data.get("record", {})
        delay = min(delay * 2, 30)

rec = combined(["resolve-parent", "firmographics"],
               legal_name="Stripe", website="stripe.com", country="US")
print(rec["kernel_id"])
print(rec["parentage"]["top_parent"])         # hierarchy, nested under "parentage"
print(rec["firmographics"]["headcount"])      # firmographics, nested under "firmographics"
```

## What you get back

One `record`:
- **resolve identity:** `kernel_id`, `legal_info`, `trading_info`, `entity_classification`, `identity_*` fields
- **`parentage`:** `parent`, `top_parent`, `top_operating_parent`, `regional_subsidiary`
- **`firmographics`:** `revenue`, `headcount`, `location`, `op_status`

Hierarchy is nested under `parentage` and firmographics under `firmographics`.

## Notes

- `jobs` is required. Omit it and the job is accepted but never leaves `pending`.
- This includes firmographics, the slow step (about 10 minutes in testing). Pass `webhook_url` rather
  than holding a long poll.
- Resolution is included, so no `kernel_id` is needed up front. If you already hold a `kernel_id`, use
  the single-purpose Firmographics or Hierarchy prompts.
- Sanity-check hierarchy: a confident `parent` / `top_parent` can still be debatable (in testing, Bose
  resolved to MIT, a majority but non-voting owner). Review before trusting.
- Errors: 403 invalid or missing key, 429 rate limited, 500 server error.

## Setting up your API key (do this first, or nothing above will run)

Every call needs a Kernel API key in `KERNEL_API_KEY`. Until it is set, the prompt above fails with a
403 and returns nothing useful.

**Get a key:**
- Already a Kernel customer: create one in the app at app.kernel.ai (Settings > API keys).
- Not a customer yet: request one at https://kernel.ai/kernel-api.
- Kernel already sent you a key: you are ready for the next step.

**Set it up:** save the key to a `.env` file in your project (never commit `.env`):

```
KERNEL_API_KEY=your-key-here
```

Load it with `from dotenv import load_dotenv; load_dotenv()` in Python, or
`export KERNEL_API_KEY=your-key-here` in your shell for the curl examples.

**Test the connection before running the prompt.** A `202` means the key works; a `403` means it is
missing or wrong, so fix it before continuing:

```bash
curl -s -o /dev/null -w "%{http_code}\n" -X POST https://api.kernel.ai/rest/v1/entity-resolution \
  -H "x-api-key: $KERNEL_API_KEY" -H "Content-Type: application/json" -d '{"legal_name":"Stripe"}'
```

## Other Kernel use cases

- **Resolve**: company signals to a KERN ID and identity.
- **Combined (resolve + hierarchy)** / **(resolve + firmographics)**: the lighter one-call variants.
- **Enrich / Firmographics / Hierarchy**: the same data via separate calls when you already have a `kernel_id`.
````

{% endprompt %}
{% endtab %}
{% endtabs %}

## Advanced use-case prompts

These prompts help you solve common problems with Kernel APIs.

* Resolve RevOps tickets
* Batch fix trouble accounts
* Boost system match rates
* Plug data gaps
* Collapse hierarchies
* Climb to the ultimate parent
* Find the top operating parent

{% tabs %}
{% tab title="Resolve RevOps tickets" %}
Clear your account data quality ticket backlog, with a ready-to-approve fix proposed for every flagged account.

{% prompt description="Resolve RevOps tickets" icon="exclamation" %}

````markdown
# Clear a RevOps data-quality ticket queue

You have access to the user's ticket queue and their system of record (CRM), plus the Kernel API
(`KERNEL_API_KEY` is already configured). Reps file tickets when a company Account record looks wrong:
bad firmographics (headcount, revenue, location, status), a wrong or missing parent, or the record
pointing at the wrong company. For each in-scope ticket, use Kernel to verify the Account and propose a
correction for a human to approve.

Resolution always runs first: it produces the KERN ID the enrichment is keyed on. For a firmographics
or hierarchy ticket, the resolve and that enrichment happen in one `/combined` call; identity tickets
only resolve.

## When to use it

You have a queue of data-quality tickets against company Account records and want Kernel to check each
one and propose fixes, instead of a rep researching and editing by hand.

## What this can and cannot do

Kernel is a company entity API. This workflow only handles company data-quality tickets on Account
records: the right company (identity), its firmographics (revenue, headcount, location, status), and
its hierarchy (parent, ultimate parent).

It cannot help with these, and you should route them to a human instead:
- Contact or Lead records, and any person-level data (names, emails, job titles, phone numbers)
- Deals or opportunities
- Lead or account routing and ownership assignment
- Field formatting, picklist values, or data hygiene unrelated to company facts
- Permissions or access
- Anything that is not company data on an Account record

Out-of-scope tickets are flagged and skipped, not guessed at.

## How the queue is supplied

Read the queue from wherever the user keeps it: CRM cases, a ticketing tool (Jira, Zendesk, Linear), or
an exported list. Kernel does not ingest tickets; you orchestrate, calling Kernel once per record. Each
ticket should identify its record by whatever id the source uses (ideally a CRM record id; it may
instead be a company name or URL).

## What to do, per ticket

1. Read the ticket and identify the record it refers to. Pull that record's current fields from the
   system of record (name, website, country, address, and the disputed values).
2. Resolve and run the flagged enrichment in one `/combined` call (set `jobs` from the ticket):
   - firmographics issue: `jobs: ["firmographics"]`
   - hierarchy issue: `jobs: ["resolve-parent"]`
   - wrong-company or identity issue: a plain resolve (no enrichment job; `/combined` needs at least one)
3. Use the resolved identity returned in the same response to confirm the company. If it does not match
   the record, that mismatch is the finding: a bad field is often a symptom of a mis-resolved record.
4. Build a field-by-field before/after: the record's current value vs Kernel's, with Kernel's
   confidence and reasoning.
5. Propose the change for review. Do not write to the record and do not close the ticket. Output a
   proposal a reviewer can accept or reject.

## Example (Python)

```python
import os, time, requests

BASE = "https://api.kernel.ai/rest"
HEADERS = {"x-api-key": os.environ["KERNEL_API_KEY"], "Content-Type": "application/json"}

def run_job(path, body):
    job_id = requests.post(f"{BASE}/{path}", headers=HEADERS, json=body).json()["id"]
    delay = 2
    while True:
        time.sleep(delay)
        data = requests.get(f"{BASE}/{path}/{job_id}", headers=HEADERS).json()
        if data["status"] in ("completed", "failed"):
            return data.get("record", {})
        delay = min(delay + 5, 30)

def classify(ticket, record):
    # Account records only. Anything on a Contact, Lead, Opportunity, etc. is out of scope.
    obj = (record.get("object") or "Account").lower()
    if obj not in ("account", "company"):
        return "out_of_scope"
    t = (ticket.get("issue") or "").lower()
    # Out-of-scope topics even on an Account ticket: people, deals, routing, formatting, access.
    if any(w in t for w in ("contact", "person", "email", "phone", "job title",
                            "routing", "assignment", "deal", "opportunity",
                            "permission", "access", "format")):
        return "out_of_scope"
    if any(w in t for w in ("parent", "hierarchy", "ultimate", "subsidiary", "owns")):
        return "hierarchy"
    if any(w in t for w in ("revenue", "headcount", "employees", "size", "location",
                            "address", "hq", "industry", "status", "active", "defunct")):
        return "firmographics"
    # In-scope Account ticket with no clear enrichment: resolve confirms the right company.
    return "identity"

def process_ticket(ticket, record):
    issue = classify(ticket, record)
    if issue == "out_of_scope":
        return {"ticket": ticket["id"], "record": record["id"], "status": "out_of_scope",
                "reason": "Kernel handles company identity, firmographics, and hierarchy on Account "
                          "records only. Route this ticket to a human."}

    signals = {"legal_name": record.get("name"), "website": record.get("website"),
               "country": record.get("country"), "external_id": record["id"]}

    # Resolve and run the flagged enrichment in ONE /combined call. Identity tickets only need
    # resolution, and /combined needs at least one job, so those use plain entity-resolution.
    if issue == "firmographics":
        rec = run_job("v1/combined", {"jobs": ["firmographics"], **signals})
    elif issue == "hierarchy":
        rec = run_job("v1/combined", {"jobs": ["resolve-parent"], **signals})
    else:  # identity
        rec = run_job("v1/entity-resolution", signals)

    resolved_name = (rec.get("legal_info") or {}).get("legal_name")
    proposal = {"ticket": ticket["id"], "record": record["id"], "kernel_id": rec.get("kernel_id"),
                "resolved_name": resolved_name, "identity_ok": None, "changes": []}
    # Compare the resolved identity to the record (name / website) to flag a mis-resolved record.
    proposal["identity_ok"] = bool(resolved_name) and resolved_name.lower().startswith(
        (record.get("name") or "").lower()[:4])

    if issue == "identity":
        return proposal                                  # the resolve is the answer

    if issue == "firmographics":
        f = rec.get("firmographics") or {}
        hc, rev = (f.get("headcount") or {}), (f.get("revenue") or {})
        proposal["changes"] = [
            {"field": "headcount", "current": record.get("headcount"),
             "proposed": hc.get("count"), "confidence": hc.get("confidence"),
             "reasoning": hc.get("reasoning")},
            {"field": "revenue_usd", "current": record.get("revenue_usd"),
             "proposed": rev.get("consolidated_usd"), "confidence": rev.get("confidence"),
             "reasoning": rev.get("reasoning")},
        ]
    elif issue == "hierarchy":
        top = (rec.get("parentage") or {}).get("top_parent") or {}
        proposal["changes"] = [
            {"field": "ultimate_parent", "current": record.get("ultimate_parent"),
             "proposed": top.get("legal_name"), "confidence": top.get("confidence")},
        ]
    return proposal

# Read the queue from wherever it lives, then collect proposals for a human to review.
proposals = [process_ticket(t, get_record(t["record_id"])) for t in get_ticket_queue()]
for p in proposals:
    print(p)            # render as a review list; nothing is written back
```

## What you produce, per ticket

A proposal, never an edit:
- the ticket id and record id
- the resolved `kernel_id` and identity, and whether it matches the record
- a before/after for the disputed fields, with Kernel confidence and reasoning
- a recommendation: apply, reject, or needs a human look
- out-of-scope tickets, flagged and skipped with a reason, for a human to route

## Notes

- This only touches Account records. Contact, lead, deal, routing, formatting, and permission tickets
  are flagged out of scope and skipped, not attempted.
- Always resolve first, even for a firmographics ticket. If Kernel resolves the record to a different
  company or to a closed shell, fixing the number is pointless until the identity is right.
- For a hierarchy or firmographics ticket on a closed entity (`op_status` not Active), enrich the
  operating parent instead (`top_operating_parent`). See the Hierarchy prompt.
- Propose, do not auto-apply. Surface Kernel's reasoning so the reviewer can judge.
- Firmographics can take a few minutes; resolve and hierarchy are usually faster. Poll or use `webhook_url`.
- `/combined` requires at least one job. An empty `jobs` array is accepted but never processes, which
  is why the resolve-only (identity) branch uses plain entity resolution instead.
- Errors: 403 invalid or missing key, 429 rate limited, 500 server error.

## Setting up your API key (do this first, or nothing above will run)

Every call needs a Kernel API key in `KERNEL_API_KEY`. Until it is set, the prompt above fails with a
403 and returns nothing useful.

**Get a key:**
- Already a Kernel customer: create one in the app at app.kernel.ai (Settings > API keys).
- Not a customer yet: request one at https://kernel.ai/kernel-api.
- Kernel already sent you a key: you are ready for the next step.

**Set it up:** save the key to a `.env` file in your project (never commit `.env`):

```
KERNEL_API_KEY=your-key-here
```

Load it with `from dotenv import load_dotenv; load_dotenv()` in Python, or
`export KERNEL_API_KEY=your-key-here` in your shell for the curl examples.

**Test the connection before running the prompt.** A `202` means the key works; a `403` means it is
missing or wrong, so fix it before continuing:

```bash
curl -s -o /dev/null -w "%{http_code}\n" -X POST https://api.kernel.ai/rest/v1/entity-resolution \
  -H "x-api-key: $KERNEL_API_KEY" -H "Content-Type: application/json" -d '{"legal_name":"Stripe"}'
```

This workflow also needs your system of record (CRM) connected to the agent. The API key alone is not
enough.

## Other Kernel use cases

- **Resolve**: company signals to a KERN ID and identity.
- **Enrich firmographics**: KERN ID to revenue, headcount, location, status.
- **Enrich hierarchy**: KERN ID to parent and ultimate parent.
- **Enrich (both)**: KERN ID to hierarchy and firmographics together.

````

{% endprompt %}
{% endtab %}

{% tab title="Batch fix trouble accounts" %}
Check and correct a whole list of your most important or messiest accounts in a single pass.

{% prompt description="Batch fix trouble accounts" icon="face-anguished" %}

````markdown
# Resolve a batch of problem accounts

You have access to the user's system of record (CRM) and the Kernel API (`KERNEL_API_KEY` is already
configured). The user hands you a batch of accounts they know have data issues, or that matter enough
to get exactly right. Run the Kernel checks they ask for across the whole batch and propose
corrections for review.

Each account is resolved and enriched in one `/combined` call (resolution is included). It still
resolves first inside that call; everything is keyed on the KERN ID it produces.

## When to use it

The user has a curated list of accounts (known-bad data, or strategically important) and wants Kernel
to verify and correct them in one pass, rather than fixing them one at a time.

## First, agree the scope

Ask the user which dimensions to refresh across the batch. They can pick one, some, or all:
- identity (the correct company and a KERN ID)
- firmographics (revenue, headcount, location, operating status)
- hierarchy (parent and ultimate parent)

Firmographics and hierarchy are bundled with resolution in one `/combined` call, which doubles as the
identity check. If you pick neither enrichment, it runs a plain resolve.

## How the list is supplied

Take the batch from wherever the user provides it: a pasted list, a CSV, a CRM segment, a saved view.
Each row should identify its account by whatever id the source uses (ideally a CRM record id; it may
instead be a company name or URL). For a large batch, pass `webhook_url` so Kernel calls you back
instead of holding open a long poll.

## What to do, per account

1. Pull the account's current fields from the system of record (name, website, country, address, and
   the values you will compare against).
2. Resolve the account and run the selected enrichment in one `/combined` call (`jobs` from the chosen
   dimensions; a plain resolve if none selected). Flag any account whose resolved company does not match
   the record: that is a likely root cause of its data issues.
3. Read the selected dimensions from the response (`firmographics`, `parentage`).
4. Build a before/after for each selected dimension: current value vs Kernel's, with confidence and
   reasoning. The accounts where Kernel disagrees are where the value is.
5. Collect a proposal per account. Do not write to any record.

## Example (Python)

```python
import os, time, requests

BASE = "https://api.kernel.ai/rest"
HEADERS = {"x-api-key": os.environ["KERNEL_API_KEY"], "Content-Type": "application/json"}

# Dimensions the user chose for this batch. These become the /combined jobs; resolution is included.
DIMENSIONS = {"firmographics": True, "hierarchy": True}

def run_job(path, body):
    job_id = requests.post(f"{BASE}/{path}", headers=HEADERS, json=body).json()["id"]
    delay = 2
    while True:
        time.sleep(delay)
        data = requests.get(f"{BASE}/{path}/{job_id}", headers=HEADERS).json()
        if data["status"] in ("completed", "failed"):
            return data.get("record", {})
        delay = min(delay + 5, 30)

def review_account(record):
    signals = {"legal_name": record.get("name"), "website": record.get("website"),
               "country": record.get("country"), "external_id": record["id"]}
    jobs = (["resolve-parent"] if DIMENSIONS.get("hierarchy") else []) + \
           (["firmographics"] if DIMENSIONS.get("firmographics") else [])
    # One /combined call when enriching; plain resolve if only identity was chosen (/combined needs a job).
    rec = run_job("v1/combined", {"jobs": jobs, **signals}) if jobs else run_job("v1/entity-resolution", signals)

    resolved_name = (rec.get("legal_info") or {}).get("legal_name")
    out = {"record": record["id"], "kernel_id": rec.get("kernel_id"), "resolved_name": resolved_name,
           "identity_ok": bool(resolved_name) and resolved_name.lower().startswith(
               (record.get("name") or "").lower()[:4]),
           "changes": []}

    if DIMENSIONS.get("firmographics"):
        f = rec.get("firmographics") or {}
        hc, rev = (f.get("headcount") or {}), (f.get("revenue") or {})
        out["changes"] += [
            {"field": "headcount", "current": record.get("headcount"),
             "proposed": hc.get("count"), "confidence": hc.get("confidence")},
            {"field": "revenue_usd", "current": record.get("revenue_usd"),
             "proposed": rev.get("consolidated_usd"), "confidence": rev.get("confidence")},
        ]
    if DIMENSIONS.get("hierarchy"):
        top = (rec.get("parentage") or {}).get("top_parent") or {}
        out["changes"].append(
            {"field": "ultimate_parent", "current": record.get("ultimate_parent"),
             "proposed": top.get("legal_name"), "confidence": top.get("confidence")})
    return out

# Read the batch from wherever the user provides it.
proposals = [review_account(get_record(a)) for a in get_account_batch()]

# Surface the biggest problems first: identity mismatches, then most-changed records.
def changed(p): return sum(1 for c in p["changes"] if c["current"] != c["proposed"])
proposals.sort(key=lambda p: (p["identity_ok"], -changed(p)))
for p in proposals:
    print(p)            # render as a review list; nothing is written back
```

## What you produce

A review list, never edits:
- one row per account: record id, resolved KERN ID, and whether the identity matches the record
- for each selected dimension, a before/after with confidence and reasoning
- the batch ordered so identity mismatches and the biggest discrepancies come first

## Notes

- Resolve first whenever you enrich. A wrong number is often a symptom of a mis-resolved account, so
  the identity check is the first thing to surface.
- For closed entities (`op_status` not Active), enrich the operating parent instead
  (`top_operating_parent`). See the Hierarchy prompt.
- Firmographics is the slow step (a few minutes each). For large batches pass `webhook_url` and process
  in the background; log progress so the user can watch the queue drain.
- Propose, do not auto-apply. The user reviews the list and decides what to write back.
- `/combined` requires at least one job. An empty `jobs` array is accepted but never processes, which
  is why the identity-only selection uses plain entity resolution instead.
- Errors: 403 invalid or missing key, 429 rate limited, 500 server error.

## Setting up your API key (do this first, or nothing above will run)

Every call needs a Kernel API key in `KERNEL_API_KEY`. Until it is set, the prompt above fails with a
403 and returns nothing useful.

**Get a key:**
- Already a Kernel customer: create one in the app at app.kernel.ai (Settings > API keys).
- Not a customer yet: request one at https://kernel.ai/kernel-api.
- Kernel already sent you a key: you are ready for the next step.

**Set it up:** save the key to a `.env` file in your project (never commit `.env`):

```
KERNEL_API_KEY=your-key-here
```

Load it with `from dotenv import load_dotenv; load_dotenv()` in Python, or
`export KERNEL_API_KEY=your-key-here` in your shell for the curl examples.

**Test the connection before running the prompt.** A `202` means the key works; a `403` means it is
missing or wrong, so fix it before continuing:

```bash
curl -s -o /dev/null -w "%{http_code}\n" -X POST https://api.kernel.ai/rest/v1/entity-resolution \
  -H "x-api-key: $KERNEL_API_KEY" -H "Content-Type: application/json" -d '{"legal_name":"Stripe"}'
```

This workflow also needs your system of record (CRM) connected to the agent. The API key alone is not
enough.

## Other Kernel use cases

- **Resolve**: company signals to a KERN ID and identity.
- **Enrich firmographics**: KERN ID to revenue, headcount, location, status.
- **Enrich hierarchy**: KERN ID to parent and ultimate parent.
- **Clear a RevOps ticket queue**: the same checks driven by reps' data-quality tickets.


````

{% endprompt %}
{% endtab %}

{% tab title="Boost system match rates" %}
Match more of your records to your other data vendors by sending them clean, current company details.

{% prompt description="Boost match rates between your systems" icon="link" %}

````markdown
# Lift match rates to your other data vendors

You have access to the user's system of record (CRM) and the Kernel API (`KERNEL_API_KEY` is already
configured). Poor match rates against other vendors (enrichment, intent, ad platforms, D&B) usually
come from messy or stale identifiers, above all a wrong or outdated domain. Resolve each record with
Kernel to get a clean canonical identity, then use those canonical keys as the inputs you send your
other vendors. This uses entity resolution only.

Entity resolution is the whole of this use case: the canonical match keys come from resolving each
record to its KERN ID.

## When to use it

You query or enrich against another vendor and too many records come back unmatched. Kernel
canonicalizes the company identity (verified legal name, the current canonical domain, LinkedIn URL,
KERN ID), so the keys you send the vendor are the ones most likely to match.

## What it produces, per record (one resolve call)

- `kernel_id`, `legal_name`, `trading_name`
- `canonical_domain` (the current verified website, often corrected vs the record: this is the big lever)
- `linkedin_url` (set `match_to_linkedin` so resolution also returns the LinkedIn company URL)
- `country` and the resolution confidence

## What to do

1. Pull the record's current signals from the system of record (name, website, country).
2. Resolve with Kernel, setting `match_to_linkedin` to true, to get the canonical identity.
3. Build the canonical match keys. Where Kernel's canonical domain differs from the record's, that
   correction is the main match-rate lever; flag it as a proposed source fix for review.
4. Output a match-ready table (record id to canonical keys) to feed your vendor.
5. Optional: pass the canonical keys to your vendor's match or enrich API and record whether it matched,
   so you can measure the lift.

## Example (Python)

```python
import os, time, requests

BASE = "https://api.kernel.ai/rest"
HEADERS = {"x-api-key": os.environ["KERNEL_API_KEY"], "Content-Type": "application/json"}

def resolve(**signals):
    job_id = requests.post(f"{BASE}/v1/entity-resolution", headers=HEADERS, json=signals).json()["id"]
    delay = 2
    while True:
        time.sleep(delay)
        data = requests.get(f"{BASE}/v1/entity-resolution/{job_id}", headers=HEADERS).json()
        if data["status"] in ("completed", "failed"):
            return data.get("record", {})
        delay = min(delay + 2, 30)

def match_keys(record):
    r = resolve(legal_name=record.get("name"), website=record.get("website"),
                country=record.get("country"), external_id=record["id"],
                match_to_linkedin=True)
    legal, trading = (r.get("legal_info") or {}), (r.get("trading_info") or {})
    linkedin = r.get("linkedin") or {}
    canonical_domain = legal.get("website") or trading.get("website")
    keys = {
        "record_id": record["id"],
        "kernel_id": r.get("kernel_id"),
        "legal_name": legal.get("legal_name"),
        "trading_name": trading.get("trading_name"),
        "canonical_domain": canonical_domain,
        "linkedin_url": linkedin.get("url"),
        "country": legal.get("country") or trading.get("country"),
        "confidence": r.get("identity_resolution_confidence"),
    }
    # The main match-rate lever: a corrected domain. Flag it as a proposed source fix.
    if canonical_domain and canonical_domain != record.get("website"):
        keys["proposed_domain_fix"] = {"current": record.get("website"), "canonical": canonical_domain}
    return keys

def match_to_vendor(keys):
    # Optional: pass the canonical keys to your vendor's match or enrich API.
    # return vendor_client.match(domain=keys["canonical_domain"],
    #                            name=keys["legal_name"], linkedin=keys["linkedin_url"])
    ...

# Build canonical match keys for the batch, then export (and optionally match against the vendor).
rows = [match_keys(get_record(a)) for a in get_account_batch()]
for row in rows:
    print(row)            # match-ready: send canonical_domain / linkedin_url / legal_name to your vendor
```

## Output

- a match-ready row per record: record id, `kernel_id`, `legal_name`, `canonical_domain`, `linkedin_url`, `country`
- proposed source fixes: records where the canonical domain differs from the stored one (review before writing)
- if you ran the optional vendor step: matched yes or no per record, so you can measure the lift

## Notes

- The canonical domain is the biggest lever. Vendors key heavily on domain; sending the current,
  verified one (not a stale or wrong domain) is what moves match rates.
- `match_to_linkedin` adds `record.linkedin.url` (and a slug), a strong key for vendors that match on LinkedIn.
- Propose source-domain fixes, do not auto-apply. Fixing the domain at the source lifts every future
  vendor sync, not just this one.
- Resolution only; firmographics and hierarchy are not needed for matching.
- Errors: 403 invalid or missing key, 429 rate limited, 500 server error.

## Setting up your API key (do this first, or nothing above will run)

Every call needs a Kernel API key in `KERNEL_API_KEY`. Until it is set, the prompt above fails with a
403 and returns nothing useful.

**Get a key:**
- Already a Kernel customer: create one in the app at app.kernel.ai (Settings > API keys).
- Not a customer yet: request one at https://kernel.ai/kernel-api.
- Kernel already sent you a key: you are ready for the next step.

**Set it up:** save the key to a `.env` file in your project (never commit `.env`):

```
KERNEL_API_KEY=your-key-here
```

Load it with `from dotenv import load_dotenv; load_dotenv()` in Python, or
`export KERNEL_API_KEY=your-key-here` in your shell for the curl examples.

**Test the connection before running the prompt.** A `202` means the key works; a `403` means it is
missing or wrong, so fix it before continuing:

```bash
curl -s -o /dev/null -w "%{http_code}\n" -X POST https://api.kernel.ai/rest/v1/entity-resolution \
  -H "x-api-key: $KERNEL_API_KEY" -H "Content-Type: application/json" -d '{"legal_name":"Stripe"}'
```

This workflow also needs your system of record (CRM) connected to the agent. The API key alone is not
enough.

## Other Kernel use cases

- **Resolve**: company signals to a KERN ID and identity.
- **Enrich firmographics**: KERN ID to revenue, headcount, location, status.
- **Enrich hierarchy**: KERN ID to parent and ultimate parent.
- **Clear a RevOps ticket queue / Resolve a batch of problem accounts**: full workflows over many records.

````

{% endprompt %}
{% endtab %}

{% tab title="Plug data gaps" %}
Fill the missing fields on your accounts automatically, leaving the data you already have untouched.

{% prompt description="Plug identity, firmographic or hierarchy gaps" icon="puzzle-piece" %}

````markdown
# Plug data gaps on your accounts

You have access to the user's system of record (CRM) and the Kernel API (`KERNEL_API_KEY` is already
configured). Many accounts are missing fields: no LinkedIn URL, no revenue, no headcount, no parent.
For each account, resolve it with Kernel, see which fields are blank, and fill only those. Never
overwrite a field that already has a value.

Each account is resolved and enriched in one `/combined` call (resolution is included). It still
resolves first inside that call; everything is keyed on the KERN ID it produces.

## When to use it

Your CRM has accounts with empty company fields and you want Kernel to complete them without touching
data that is already there.

## What it can fill

Kernel can fill these fields when they are blank on the account:
- From resolution (always included): LinkedIn URL, canonical domain (website), country.
- From firmographics: revenue, headcount, location, operating status.
- From hierarchy: parent and ultimate parent.

One `/combined` call resolves the account and runs whichever of firmographics and hierarchy a gap
needs, so the resolve-derived fields (LinkedIn URL, domain, country) come for free. If only those are
missing, it is a plain resolve with no enrichment.

## Which accounts

Either works: take a list or saved view the user supplies, or scan the system of record for accounts
missing target fields (revenue, headcount, or parent is blank).

## Fill or review (your call)

Pick how to handle each filled value:
- review: propose every fill for a human to accept (write nothing).
- fill: write the value automatically when Kernel's confidence meets your threshold (e.g. HIGH), and
  route anything below the threshold to review.

Either way, only blank fields are touched; populated fields are left alone.

## What to do, per account

1. Pull the account's current fields from the system of record.
2. Find which fields are blank: LinkedIn URL, canonical domain, country (from resolution), plus
   revenue, headcount, location, operating status (firmographics) and parent (hierarchy).
3. Resolve and enrich in one `/combined` call: set `jobs` to firmographics and/or resolve-parent based
   on which gaps exist (a plain resolve if only resolve-derived fields are missing). If the account
   resolves to a different company, the gaps may be a symptom of a mis-resolved record, so surface that.
4. For each blank field, take Kernel's value and its confidence, then fill or route to review per your mode.

## Example (Python)

```python
import os, time, requests

BASE = "https://api.kernel.ai/rest"
HEADERS = {"x-api-key": os.environ["KERNEL_API_KEY"], "Content-Type": "application/json"}

MODE = "review"           # "review" proposes every fill; "fill" writes high-confidence ones
MIN_CONFIDENCE = "HIGH"   # in fill mode, auto-fill only at or above this; the rest go to review
RANK = {"LOW": 0, "MEDIUM": 1, "HIGH": 2}

def run_job(path, body):
    job_id = requests.post(f"{BASE}/{path}", headers=HEADERS, json=body).json()["id"]
    delay = 2
    while True:
        time.sleep(delay)
        data = requests.get(f"{BASE}/{path}/{job_id}", headers=HEADERS).json()
        if data["status"] in ("completed", "failed"):
            return data.get("record", {})
        delay = min(delay + 5, 30)

def is_blank(v):
    return v is None or v == ""        # adjust if your schema uses 0 or a placeholder for "missing"

def fill_gaps(record):
    # 1) Which fields are blank? (location maps to your address fields; here we use city.)
    gaps = []
    if is_blank(record.get("linkedin_url")):       gaps.append("linkedin_url")    # free, from resolution
    if is_blank(record.get("website")):            gaps.append("website")         # free, from resolution
    if is_blank(record.get("country")):            gaps.append("country")         # free, from resolution
    if is_blank(record.get("revenue_usd")):        gaps.append("revenue_usd")
    if is_blank(record.get("headcount")):          gaps.append("headcount")
    if is_blank(record.get("operational_status")): gaps.append("operational_status")
    if is_blank(record.get("city")):               gaps.append("location")
    if is_blank(record.get("ultimate_parent")):    gaps.append("ultimate_parent")

    # 2) Bundle the jobs the gaps need into one /combined call (resolution is always included).
    jobs = []
    if "ultimate_parent" in gaps:
        jobs.append("resolve-parent")
    if any(g in ("revenue_usd", "headcount", "operational_status", "location") for g in gaps):
        jobs.append("firmographics")
    signals = {"legal_name": record.get("name"), "website": record.get("website"),
               "country": record.get("country"), "external_id": record["id"],
               "match_to_linkedin": True}
    rec = run_job("v1/combined", {"jobs": jobs, **signals}) if jobs else run_job("v1/entity-resolution", signals)

    # 3) Read candidate values from the one response.
    legal    = rec.get("legal_info") or {}
    linkedin = rec.get("linkedin") or {}
    firmo    = rec.get("firmographics") or {}
    rev, hc  = (firmo.get("revenue") or {}), (firmo.get("headcount") or {})
    loc      = (firmo.get("location") or {}).get("operating") or {}
    top      = (rec.get("parentage") or {}).get("top_parent") or {}
    candidate = {   # field -> (value, confidence)
        "linkedin_url":       (linkedin.get("url"), None),
        "website":            (legal.get("website"), legal.get("confidence")),   # canonical domain
        "country":            (legal.get("country"), legal.get("confidence")),
        "revenue_usd":        (rev.get("consolidated_usd"), rev.get("confidence")),
        "headcount":          (hc.get("count"), hc.get("confidence")),
        "operational_status": ((firmo.get("op_status") or {}).get("operational_status"), None),
        "location":           (loc or None, None),   # {street, city, state, postcode, country}
        "ultimate_parent":    (top.get("legal_name"), top.get("confidence")),
    }

    fills = []
    for field in gaps:
        value, conf = candidate[field]
        if value is None:
            continue                                       # Kernel had nothing to offer
        auto = MODE == "fill" and RANK.get(conf, -1) >= RANK[MIN_CONFIDENCE]
        fills.append({"field": field, "value": value, "confidence": conf,
                      "action": "fill" if auto else "review"})
    return {"record": record["id"], "kernel_id": rec.get("kernel_id"), "fills": fills}

# Either: a supplied list, or scan the system of record for incomplete accounts.
records = SUPPLIED_LIST or get_incomplete_accounts()
results = [fill_gaps(get_record(a)) for a in records]
for res in results:
    print(res)            # write the "fill" items; route "review" items to a human
```

## What you produce

- per account: the resolved `kernel_id`, and a list of fills (field, value, confidence, action)
- only blank fields ever appear; populated fields are never proposed
- in fill mode, high-confidence values are written and the rest are queued for review

## Notes

- Resolve first. A record missing data is sometimes missing it because it points at the wrong company;
  the identity check catches that before you fill.
- For closed entities (`op_status` not Active), enrich the operating parent (`top_operating_parent`).
  See the Hierarchy prompt.
- Only blanks. Adjust `is_blank` if your schema uses 0 or a placeholder to mean missing.
- Operating status comes without a confidence score, so it routes to review by default in fill mode.
- LinkedIn URL comes from the resolve match and also has no confidence score, so it routes to review by
  default in fill mode.
- Location is multi-field: it fills your address fields (street, city, state, postal code, country) from
  `location.operating`, and likewise has no single confidence score, so it routes to review by default.
- Firmographics is the slow step; for large scans pass `webhook_url` and log progress.
- `/combined` requires at least one job. An empty `jobs` array is accepted but never processes, which
  is why a record with only resolve-derived gaps uses plain entity resolution instead.
- Errors: 403 invalid or missing key, 429 rate limited, 500 server error.

## Setting up your API key (do this first, or nothing above will run)

Every call needs a Kernel API key in `KERNEL_API_KEY`. Until it is set, the prompt above fails with a
403 and returns nothing useful.

**Get a key:**
- Already a Kernel customer: create one in the app at app.kernel.ai (Settings > API keys).
- Not a customer yet: request one at https://kernel.ai/kernel-api.
- Kernel already sent you a key: you are ready for the next step.

**Set it up:** save the key to a `.env` file in your project (never commit `.env`):

```
KERNEL_API_KEY=your-key-here
```

Load it with `from dotenv import load_dotenv; load_dotenv()` in Python, or
`export KERNEL_API_KEY=your-key-here` in your shell for the curl examples.

**Test the connection before running the prompt.** A `202` means the key works; a `403` means it is
missing or wrong, so fix it before continuing:

```bash
curl -s -o /dev/null -w "%{http_code}\n" -X POST https://api.kernel.ai/rest/v1/entity-resolution \
  -H "x-api-key: $KERNEL_API_KEY" -H "Content-Type: application/json" -d '{"legal_name":"Stripe"}'
```

This workflow also needs your system of record (CRM) connected to the agent. The API key alone is not
enough.

## Other Kernel use cases

- **Resolve**: company signals to a KERN ID and identity.
- **Enrich firmographics**: KERN ID to revenue, headcount, location, status.
- **Enrich hierarchy**: KERN ID to parent and ultimate parent.
- **RevOps ticket queue / batch problem accounts / vendor match rates**: other practical workflows.


````

{% endprompt %}
{% endtab %}

{% tab title="Collapse hierarchies" %}
Use Kernel entity definitions to consolidate entities into a simple hierarchy, according to your preferences and logic.

{% prompt description="Collapse hierarchies" icon="list-tree" %}

````markdown
# Collapse account hierarchies

You have access to the user's system of record (CRM) and the Kernel API (`KERNEL_API_KEY` is already
configured). A CRM often holds several distinct entities that belong to one organization: the
operating company, its brands, its regional offices, a holding company. These are real, separate
entities, not duplicate records of one thing. Use Kernel to work out what each account really is and
how it rolls up, then group the entities your policy says should consolidate into one survivor.
Nothing is merged automatically.

Each account is resolved and its hierarchy fetched in one `/combined` call. Resolution still runs first
inside that call; everything is keyed on the KERN ID it produces.

## When to use it

Your CRM holds multiple entities of the same organization (operating company, brands, regional arms,
holding company) and you want to consolidate them down to the accounts you actually want to keep, on
your own terms.

## How Kernel informs the decision

For each account, one `/combined` call (`jobs: ["resolve-parent"]`) gives you what you need:
- the resolve identity: `entity_classification.subtype`, one of `Operating`, `HoldCo/Investment`,
  `Business Unit`, or `Establishment`, plus the `kernel_id` and resolution confidence. (A brand is a
  `Business Unit`; its brand name is in `trading_name`.)
- `parentage`, the targets to roll up into: `parent` (one level), `top_operating_parent` (the highest
  operating company, skipping holding companies), `top_parent` (the apex), and whether the account is a
  regional subsidiary.

## Define your policy (required, no default)

You declare, for each subtype, whether an account of that type survives as its own account or
collapses, and the level it collapses into. Nothing is assumed: any subtype you leave unset is flagged
for you, not guessed. Whether `Business Unit` (brands) and `Establishment` (regional offices) persist
is the heart of your definition. See `POLICY` in the code.

## What to do

1. Take the set of accounts to consolidate (a supplied list, or scan the system of record).
2. For each account: one `/combined` call (`jobs: ["resolve-parent"]`) returns its identity, subtype,
   `kernel_id`, and roll-up targets (under `parentage`).
3. Apply your policy to decide survive vs collapse, and compute the survivor `kernel_id` for collapses.
4. Group accounts by the survivor they collapse into.
5. Propose each group: the survivor, the members to merge in, and any flags. Merge nothing; the user
   reviews and executes merges in the CRM.

## Example (Python)

```python
import os, time, requests

BASE = "https://api.kernel.ai/rest"
HEADERS = {"x-api-key": os.environ["KERNEL_API_KEY"], "Content-Type": "application/json"}

# ---- YOUR POLICY (required; there is no default) -----------------------------
# For each subtype set "action" to "survive" or "collapse".
# For "collapse" set "into" to one of: "top_operating_parent" | "top_parent" | "parent".
# Any row you leave as None is flagged for you, not guessed.
POLICY = {
    "Operating":         {"action": None, "into": None},
    "HoldCo/Investment": {"action": None, "into": None},
    "Business Unit":     {"action": None, "into": None},   # this row answers "do brands persist?"
    "Establishment":     {"action": None, "into": None},
}
REGIONAL_RULE = None   # optional override for regional subsidiaries, same shape as a POLICY row
# ------------------------------------------------------------------------------

def run_job(path, body):
    job_id = requests.post(f"{BASE}/{path}", headers=HEADERS, json=body).json()["id"]
    delay = 2
    while True:
        time.sleep(delay)
        data = requests.get(f"{BASE}/{path}/{job_id}", headers=HEADERS).json()
        if data["status"] in ("completed", "failed"):
            return data.get("record", {})
        delay = min(delay + 5, 30)

def classify(record):
    """Resolve an account and read its roll-up targets, in one /combined call."""
    rec = run_job("v1/combined", {
        "jobs": ["resolve-parent"],
        "legal_name": record.get("name"), "website": record.get("website"),
        "country": record.get("country"), "external_id": record["id"]})
    parentage = rec.get("parentage") or {}
    return {
        "record_id": record["id"],
        "kernel_id": rec.get("kernel_id"),
        "resolved_name": (rec.get("legal_info") or {}).get("legal_name"),
        "subtype": (rec.get("entity_classification") or {}).get("subtype"),
        "confidence": rec.get("identity_resolution_confidence"),
        "is_regional": (parentage.get("regional_subsidiary") or {}).get("is_regional"),
        "targets": {
            "parent": parentage.get("parent") or {},
            "top_operating_parent": parentage.get("top_operating_parent") or {},
            "top_parent": parentage.get("top_parent") or {},
        },
    }

def decide(info):
    """Apply YOUR policy. Returns ('survive', None), ('collapse', survivor), or ('flag', reason)."""
    rule = REGIONAL_RULE if (info["is_regional"] and REGIONAL_RULE) else POLICY.get(info["subtype"])
    if not rule or rule.get("action") not in ("survive", "collapse"):
        return ("flag", f"set a policy for subtype {info['subtype']}")
    if rule["action"] == "survive":
        return ("survive", None)
    return ("collapse", info["targets"].get(rule["into"]) or {})

# Resolve the whole set, then group accounts by the survivor they collapse into.
infos = [classify(get_record(a)) for a in get_account_set()]
all_kids = {i["kernel_id"] for i in infos}
groups, flags = {}, []
for info in infos:
    action, detail = decide(info)
    if action == "survive":
        continue                                      # this account persists; not in any group
    if action == "flag":
        flags.append({"record_id": info["record_id"], "issue": detail})
        continue
    survivor = detail
    skid = survivor.get("kernel_id")
    if not skid:
        flags.append({"record_id": info["record_id"], "issue": "no roll-up target from Kernel"})
        continue
    groups.setdefault(skid, {"survivor": survivor, "members": []})["members"].append(info)

# Propose each merge group. Nothing is merged here.
for skid, g in groups.items():
    print({
        "survivor_kernel_id": skid,
        "survivor_name": g["survivor"].get("legal_name"),
        "survivor_is_an_account": skid in all_kids,    # if False, you decide: create it or promote a member
        "merge_in": [m["record_id"] for m in g["members"]],
        "low_confidence_members": [m["record_id"] for m in g["members"] if m["confidence"] != "HIGH"],
    })
print("flags:", flags)
# Review each group, then execute the merges in your CRM. Nothing was changed.
```

## What you produce

- one group per survivor: survivor `kernel_id` and name, plus the member accounts to merge in
- `survivor_is_an_account`: whether the survivor is already one of your accounts (if not, you decide
  whether to create it or promote a member to survivor)
- flags: members with no roll-up target, low-confidence resolutions, and subtypes with no policy set
- accounts your policy keeps simply do not appear in any group

## Notes

- Merging is destructive and irreversible. This proposes only; a human reviews every group and runs the
  merge in the CRM.
- Collapsing to `top_operating_parent` flattens a multi-level tree in one pass and skips holding
  companies. Collapsing to `parent` rolls up only one level.
- `top_parent` caveat: it is meant to be the apex but can stop short of the true ultimate parent (a data
  gap). If your policy rolls up to `top_parent`, verify the survivor, or climb: call resolve-parent on
  the survivor's `kernel_id` until `parent` is null.
- Subtypes you do not set in POLICY, and Government or Education entities that may have no subtype, are
  flagged rather than guessed.
- Review low-confidence resolutions and any identity mismatch before merging; a wrong resolve would
  merge the wrong accounts.
- `/combined` requires at least one job. An empty `jobs` array is accepted but never processes; this
  workflow always passes `resolve-parent`.
- Errors: 403 invalid or missing key, 429 rate limited, 500 server error.

## Setting up your API key (do this first, or nothing above will run)

Every call needs a Kernel API key in `KERNEL_API_KEY`. Until it is set, the prompt above fails with a
403 and returns nothing useful.

**Get a key:**
- Already a Kernel customer: create one in the app at app.kernel.ai (Settings > API keys).
- Not a customer yet: request one at https://kernel.ai/kernel-api.
- Kernel already sent you a key: you are ready for the next step.

**Set it up:** save the key to a `.env` file in your project (never commit `.env`):

```
KERNEL_API_KEY=your-key-here
```

Load it with `from dotenv import load_dotenv; load_dotenv()` in Python, or
`export KERNEL_API_KEY=your-key-here` in your shell for the curl examples.

**Test the connection before running the prompt.** A `202` means the key works; a `403` means it is
missing or wrong, so fix it before continuing:

```bash
curl -s -o /dev/null -w "%{http_code}\n" -X POST https://api.kernel.ai/rest/v1/entity-resolution \
  -H "x-api-key: $KERNEL_API_KEY" -H "Content-Type: application/json" -d '{"legal_name":"Stripe"}'
```

This workflow also needs your system of record (CRM) connected to the agent. The API key alone is not
enough.

## Other Kernel use cases

- **Resolve**: company signals to a KERN ID and identity (the subtype lives here).
- **Enrich hierarchy**: KERN ID to parent, ultimate parent, and top operating parent.
- **Plug data gaps / batch problem accounts / vendor match rates**: other practical workflows.

````

{% endprompt %}
{% endtab %}

{% tab title="Climb to the ultimate parent" %}
Loop up the ownership chain hop by hop until there is no parent left — the verified apex of the tree, with every intermediate entity on the way.

{% prompt description="Climb to the ultimate parent" icon="sitemap" %}

````markdown
# Climb to the ultimate parent

You have access to the Kernel API (`KERNEL_API_KEY` is already configured). For any account, walk the
corporate ownership chain one hop at a time — company, parent, parent's parent — until there is no
parent left. The entity you stop at is the verified ultimate parent (the apex of the tree), and the
loop gives you every intermediate entity on the way up, each with its classification, confidence, and
reasoning.

A single `/resolve-parent` call already returns a `top_parent` shortcut, but it can stop short of the
true apex (a data gap), and it tells you nothing about the levels in between. Climbing hop by hop
fixes both: you land on the entity that genuinely has no parent, and you get the full chain.

## When to use it

- You need the complete ownership chain for an account, not just the endpoints — every holding
  company, intermediate parent, and regional arm between the account and the top.
- You are rolling up accounts by ultimate parent and want the apex verified, not shortcut.
- You are auditing hierarchy data and want to see exactly where a tree's levels sit.

## How Kernel informs it

Each `POST /v1/resolve-parent` call takes a `kernel_id` and returns:
- `parent` — the immediate parent (one hop), with its own `kernel_id`, names, website, country,
  `entity_category` / `entity_sub_category`, confidence, and reasoning
- `top_parent` — the apex shortcut (used as a cross-check here, not as the answer)
- `top_operating_parent` and `regional_subsidiary` — extra context, not needed for the climb

The loop is: call resolve-parent, take `parent.kernel_id`, call again, repeat until `parent` is null.

If you start from company details instead of a Kernel ID, run one `/v1/entity-resolution` call first
to get the `kernel_id`, then climb.

## What to do

1. Take the account (a Kernel ID, or company details to resolve first).
2. Climb: call `/v1/resolve-parent` on the current `kernel_id`, append the returned `parent` to the
   chain, move up. Stop when `parent` is null — the current entity is the ultimate parent.
3. Guard against cycles (fragmented data can loop) and cap the depth as a safety net.
4. Report the full chain bottom → top, and cross-check the climbed apex against the `top_parent`
   shortcut from the first call; disagreement means the shortcut stopped short.

## Example (Python)

```python
import os, time, requests

BASE = "https://api.kernel.ai/rest"
HEADERS = {"x-api-key": os.environ["KERNEL_API_KEY"], "Content-Type": "application/json"}
MAX_DEPTH = 25          # safety cap; real trees converge well before this

def run_job(path, body):
    """Kernel jobs are async: submit, then poll until the job completes."""
    job_id = requests.post(f"{BASE}/{path}", headers=HEADERS, json=body).json()["id"]
    delay = 2
    while True:
        time.sleep(delay)
        data = requests.get(f"{BASE}/{path}/{job_id}", headers=HEADERS).json()
        if data["status"] in ("completed", "failed"):
            return data.get("record", {})
        delay = min(delay + 5, 30)

def resolve_kernel_id(name=None, website=None, country=None):
    """Company details -> kernel_id. Skip this if you already have one."""
    rec = run_job("v1/entity-resolution",
                  {k: v for k, v in {"legal_name": name, "website": website,
                                     "country": country}.items() if v})
    return rec.get("kernel_id")

def climb(kernel_id):
    """Walk parent links up until there is no parent left."""
    chain, seen, cur = [], {str(kernel_id)}, str(kernel_id)
    shortcut = None
    for _ in range(MAX_DEPTH):
        rec = run_job("v1/resolve-parent", {"kernel_id": cur})
        if shortcut is None:
            shortcut = rec.get("top_parent")          # the one-call answer, kept as a cross-check
        parent = rec.get("parent")
        if not parent:                                # no parent -> cur is the ultimate parent
            return {"ultimate_parent_kernel_id": cur, "chain": chain,
                    "top_parent_shortcut": shortcut, "warning": None}
        pid = str(parent["kernel_id"])
        if pid in seen:                               # cycle guard (fragmented data)
            return {"ultimate_parent_kernel_id": cur, "chain": chain,
                    "top_parent_shortcut": shortcut, "warning": "cycle detected"}
        seen.add(pid)
        chain.append(parent)
        cur = pid
    return {"ultimate_parent_kernel_id": cur, "chain": chain,
            "top_parent_shortcut": shortcut, "warning": "max depth reached"}

def name_of(node):
    return node.get("trading_name") or node.get("legal_name") or node.get("kernel_id") if node else None

# --- run it -------------------------------------------------------------------
kid = resolve_kernel_id(name="Ben & Jerry's", country="United States")   # or a known Kernel ID
result = climb(kid)

print(f"Chain (bottom -> top), starting from {kid}:")
for node in result["chain"]:
    print(f"  ^ {name_of(node)}  ({node.get('entity_sub_category')}, "
          f"{node.get('website') or '-'}, kernel_id {node['kernel_id']})")
apex = result["chain"][-1] if result["chain"] else None
print(f"Ultimate parent: {name_of(apex) or kid}  (kernel_id {result['ultimate_parent_kernel_id']})")

shortcut = result["top_parent_shortcut"]
if shortcut and str(shortcut.get("kernel_id")) != result["ultimate_parent_kernel_id"]:
    print(f"NOTE: top_parent shortcut said {name_of(shortcut)} - the climb went higher; trust the climb.")
if result["warning"]:
    print(f"WARNING: {result['warning']}")
```

## What you produce

- the full ownership chain, bottom → top, one entity per hop, each with name, `kernel_id`, website,
  country, and classification
- the verified ultimate parent: the entity the climb ends on because it has no parent
- a cross-check against the single-call `top_parent` shortcut, flagged when they disagree
- warnings for cycles or depth-capped climbs, so nothing fails silently

## Notes

- Each hop is one async job that can take ~30–60 seconds; a deep tree takes a few minutes. For a batch
  of accounts, run the climbs concurrently and cache results per `kernel_id` — trees overlap heavily,
  so siblings share almost their whole chain.
- If the climbed apex disagrees with the `top_parent` shortcut, trust the climb: it is grounded hop by
  hop, while the shortcut can stop short.
- If you want the highest *operating* company instead of the absolute apex (which is often a holding
  company), use the "Find the top operating parent" use case instead.
- Errors: 403 invalid or missing key, 429 rate limited, 500 server error.

## Setting up your API key (do this first, or nothing above will run)

Every call needs a Kernel API key in `KERNEL_API_KEY`. Until it is set, the prompt above fails with a
403 and returns nothing useful.

**Get a key:**
- Already a Kernel customer: create one in the app at app.kernel.ai (Settings > API keys).
- Not a customer yet: request one at https://kernel.ai/kernel-api.
- Kernel already sent you a key: you are ready for the next step.

**Set it up:** save the key to a `.env` file in your project (never commit `.env`):

```
KERNEL_API_KEY=your-key-here
```

Load it with `from dotenv import load_dotenv; load_dotenv()` in Python, or
`export KERNEL_API_KEY=your-key-here` in your shell for the curl examples.

**Test the connection before running the prompt.** A `202` means the key works; a `403` means it is
missing or wrong, so fix it before continuing:

```bash
curl -s -o /dev/null -w "%{http_code}\n" -X POST https://api.kernel.ai/rest/v1/entity-resolution \
  -H "x-api-key: $KERNEL_API_KEY" -H "Content-Type: application/json" -d '{"legal_name":"Stripe"}'
```

## Other Kernel use cases

- **Resolve**: company signals to a KERN ID and identity.
- **Find the top operating parent**: the same climb, stopped at the highest operating company.
- **Collapse hierarchies / plug data gaps / batch problem accounts**: other practical workflows.

````

{% endprompt %}

#### Just the script

The same logic as a ready-to-run CLI — copy it into `climb_to_ultimate_parent.py` and run it directly:

```bash
export KERNEL_API_KEY=your-key-here
pip install requests

python3 climb_to_ultimate_parent.py 7913528487                                   # by Kernel ID
python3 climb_to_ultimate_parent.py "Ben & Jerry's" --country "United States"   # by name
```

<details>

<summary>climb_to_ultimate_parent.py — expand for the full script</summary>

```python
#!/usr/bin/env python3
"""Climb the Kernel ownership tree from any account to its ultimate parent.

Walks /v1/resolve-parent hop by hop until there is no parent left. The entity
it stops at is the verified apex of the tree; the chain holds every entity in
between. Takes a Kernel ID, or a company name (resolved first).
"""
import argparse
import os
import re
import sys
import time

import requests

BASE = os.environ.get("KERNEL_API_BASE_URL", "https://api.kernel.ai/rest")
MAX_DEPTH = 25                        # safety cap; real trees converge well before this
KERN_RE = re.compile(r"^\d{6,12}$")   # Kernel IDs are numeric


def run_job(path, body, headers):
    """Kernel jobs are async: submit, then poll until the job completes."""
    job_id = requests.post(f"{BASE}/{path}", headers=headers, json=body).json()["id"]
    delay = 2
    while True:
        time.sleep(delay)
        data = requests.get(f"{BASE}/{path}/{job_id}", headers=headers).json()
        if data["status"] == "failed":
            sys.exit(f"{path} job failed: {data}")
        if data["status"] == "completed":
            return data.get("record", {})
        delay = min(delay + 5, 30)


def name_of(node):
    if not node:
        return None
    return node.get("trading_name") or node.get("legal_name") or node.get("kernel_id")


def climb(kernel_id, headers):
    """Walk parent links up until there is no parent left."""
    chain, seen, cur = [], {str(kernel_id)}, str(kernel_id)
    shortcut, warning = None, None
    for _ in range(MAX_DEPTH):
        rec = run_job("v1/resolve-parent", {"kernel_id": cur}, headers)
        if shortcut is None:
            shortcut = rec.get("top_parent")     # the one-call answer, kept as a cross-check
        parent = rec.get("parent")
        if not parent:                           # no parent -> cur is the ultimate parent
            break
        pid = str(parent["kernel_id"])
        if pid in seen:                          # cycle guard (fragmented data)
            warning = "cycle detected"
            break
        seen.add(pid)
        chain.append(parent)
        cur = pid
    else:
        warning = "max depth reached"
    return {"ultimate_parent_kernel_id": cur, "chain": chain,
            "top_parent_shortcut": shortcut, "warning": warning}


def main():
    ap = argparse.ArgumentParser(description="Climb the Kernel tree to the ultimate parent.")
    ap.add_argument("query", help="A Kernel ID, or a company name to resolve first")
    ap.add_argument("--website", help="Sharpens the match when resolving by name")
    ap.add_argument("--country", help="Sharpens the match when resolving by name")
    args = ap.parse_args()

    key = os.environ.get("KERNEL_API_KEY")
    if not key:
        sys.exit("Set KERNEL_API_KEY first (see 'Setting up your API key')")
    headers = {"x-api-key": key, "Content-Type": "application/json"}

    if KERN_RE.match(args.query.strip()):
        kid = args.query.strip()
    else:
        body = {k: v for k, v in {"legal_name": args.query, "website": args.website,
                                  "country": args.country}.items() if v}
        kid = run_job("v1/entity-resolution", body, headers).get("kernel_id")
        if not kid:
            sys.exit(f"Could not resolve '{args.query}' to a Kernel entity")
        print(f"Resolved to kernel_id {kid}")

    result = climb(kid, headers)
    print(f"Chain (bottom -> top), starting from {kid}:")
    for node in result["chain"]:
        print(f"  ^ {name_of(node)}  ({node.get('entity_sub_category')}, "
              f"{node.get('website') or '-'}, kernel_id {node['kernel_id']})")
    apex = result["chain"][-1] if result["chain"] else None
    print(f"Ultimate parent: {name_of(apex) or kid}  "
          f"(kernel_id {result['ultimate_parent_kernel_id']})")

    shortcut = result["top_parent_shortcut"]
    if shortcut and str(shortcut.get("kernel_id")) != result["ultimate_parent_kernel_id"]:
        print(f"NOTE: top_parent shortcut said {name_of(shortcut)} - "
              f"the climb went higher; trust the climb.")
    if result["warning"]:
        print(f"WARNING: {result['warning']}")


if __name__ == "__main__":
    main()
```

</details>
{% endtab %}

{% tab title="Find the top operating parent" %}
Get the account every subsidiary should roll up to: the highest operating company in the tree, skipping holding companies and investment vehicles.

{% prompt description="Find the top operating parent" icon="crown" %}

````markdown
# Find the top operating parent

You have access to the Kernel API (`KERNEL_API_KEY` is already configured). For any account, find its
top operating parent — the highest revenue-generating company in its ownership tree, skipping holding
companies, investment vehicles, and other non-operating entities — and build the operating-only chain
from the account up to it.

This is the account that subsidiaries roll up to for territory assignment, deduplication, and
reporting. The absolute apex of a tree is often a HoldCo or investment vehicle nobody sells to; the
top operating parent is the real business above it all.

## When to use it

- You are assigning accounts to owners or territories by "who is the real parent company".
- You are rolling up revenue, headcount, or opportunities and holding companies would distort the
  rollup.
- A rep asks "who actually owns this account?" and the answer needs to skip PE funds and shell
  entities.
- You have a CSV of accounts (Kernel IDs or names) and want the top operating parent for each.

## How Kernel informs it

Each `POST /v1/resolve-parent` call takes a `kernel_id` and returns:
- `top_operating_parent` — the answer: the highest operating company in the tree
- `parent` — the immediate parent, with `entity_category` / `entity_sub_category`, so you can tell
  operating companies from non-operating ones
- `top_parent` — the absolute apex (often a HoldCo above the top operating parent)

An entity is operating when `entity_category == "Company"` and `entity_sub_category` is not one of
`HoldCo/Investment`, `Business Unit`, `Academic Unit`, `Establishment`.

One call gives you the top operating parent. The loop in the example additionally walks the immediate
parents up to it, so you also get the chain of operating companies in between and a list of the
non-operating entities that were skipped — useful for showing *why* the rollup lands where it does.

If you start from company details instead of a Kernel ID, run one `/v1/entity-resolution` call first
to get the `kernel_id`.

## What to do

1. Take the account (a Kernel ID, or company details to resolve first).
2. Call `/v1/resolve-parent` on it and read `top_operating_parent`. If it is the account itself (or
   missing), the account is already the top of its operating tree — stop.
3. Otherwise walk up hop by hop: keep operating ancestors in the chain, set non-operating ones aside,
   and stop when you reach the top operating parent's `kernel_id`.
4. Report per account: the top operating parent, the operating chain (bottom → top), and the
   non-operating entities skipped along the way.

## Example (Python)

```python
import os, time, requests

BASE = "https://api.kernel.ai/rest"
HEADERS = {"x-api-key": os.environ["KERNEL_API_KEY"], "Content-Type": "application/json"}
MAX_DEPTH = 25
NON_OPERATING = {"HoldCo/Investment", "Business Unit", "Academic Unit", "Establishment"}

def run_job(path, body):
    """Kernel jobs are async: submit, then poll until the job completes."""
    job_id = requests.post(f"{BASE}/{path}", headers=HEADERS, json=body).json()["id"]
    delay = 2
    while True:
        time.sleep(delay)
        data = requests.get(f"{BASE}/{path}/{job_id}", headers=HEADERS).json()
        if data["status"] in ("completed", "failed"):
            return data.get("record", {})
        delay = min(delay + 5, 30)

def resolve_kernel_id(name=None, website=None, country=None):
    """Company details -> kernel_id. Skip this if you already have one."""
    rec = run_job("v1/entity-resolution",
                  {k: v for k, v in {"legal_name": name, "website": website,
                                     "country": country}.items() if v})
    return rec.get("kernel_id")

def is_operating(node):
    return bool(node) and node.get("entity_category") == "Company" \
        and node.get("entity_sub_category") not in NON_OPERATING

def name_of(node):
    return node.get("trading_name") or node.get("legal_name") or node.get("kernel_id") if node else None

def top_operating_parent(kernel_id):
    """One call answers it; the walk up fills in the operating chain."""
    kid = str(kernel_id)
    root = run_job("v1/resolve-parent", {"kernel_id": kid})
    top_op = root.get("top_operating_parent")
    top_op_id = str(top_op["kernel_id"]) if top_op else None

    if top_op_id in (None, kid):        # the account IS the top of its operating tree
        return {"kernel_id": kid, "is_own_top_operating": top_op_id == kid,
                "top_operating_parent": top_op, "operating_chain": [], "skipped": [],
                "ultimate_parent": root.get("top_parent")}

    chain, skipped, seen, cur = [], [], {kid}, kid
    for _ in range(MAX_DEPTH):
        rec = root if cur == kid else run_job("v1/resolve-parent", {"kernel_id": cur})
        parent = rec.get("parent")
        if not parent:
            break
        pid = str(parent["kernel_id"])
        if pid in seen:                 # cycle guard (fragmented data)
            break
        seen.add(pid)
        (chain if is_operating(parent) else skipped).append(parent)
        cur = pid
        if pid == top_op_id:            # reached the answer - stop climbing
            break
    if not chain or str(chain[-1]["kernel_id"]) != top_op_id:
        chain.append(top_op)            # make sure the apex is present and last

    return {"kernel_id": kid, "is_own_top_operating": False,
            "top_operating_parent": top_op, "operating_chain": chain,
            "skipped": skipped, "ultimate_parent": root.get("top_parent")}

# --- run it -------------------------------------------------------------------
kid = resolve_kernel_id(name="Ben & Jerry's", country="United States")   # or a known Kernel ID
r = top_operating_parent(kid)

if r["is_own_top_operating"]:
    print(f"{kid} is its own top operating parent - it IS the top of its operating tree")
else:
    top = r["top_operating_parent"]
    print(f"Top operating parent: {name_of(top)}  "
          f"(kernel_id {top['kernel_id']}, {top.get('website') or '-'})")
    print("Operating chain (bottom -> top):")
    for node in r["operating_chain"]:
        print(f"  ^ {name_of(node)}  ({node.get('entity_sub_category')}, kernel_id {node['kernel_id']})")
    if r["skipped"]:
        print("Skipped (non-operating): "
              + ", ".join(f"{name_of(n)} [{n.get('entity_sub_category')}]" for n in r["skipped"]))
if r["ultimate_parent"] and r["top_operating_parent"] and \
   str(r["ultimate_parent"].get("kernel_id")) != str(r["top_operating_parent"].get("kernel_id")):
    print(f"(Ultimate parent above it: {name_of(r['ultimate_parent'])} - non-operating apex)")
```

## What you produce

- the top operating parent per account: name, `kernel_id`, website — the rollup / assignment target
- `is_own_top_operating`: whether the account is already the top of its operating tree
- the operating chain, bottom → top: every operating company between the account and the answer
- the non-operating entities skipped (HoldCos, investment vehicles, business units), so the rollup is
  explainable
- the ultimate parent above it, when a non-operating apex sits on top of the tree

## Notes

- If you only need the answer and not the chain, the first `/resolve-parent` call is enough — read
  `top_operating_parent` and stop. The walk exists to make the result explainable.
- Batch over a CSV by running lookups concurrently and caching `/resolve-parent` responses per
  `kernel_id`: accounts in the same tree share most of their chain, so the cache saves most calls.
- Government and Education trees work differently: entities there may have no subtype, so treat the
  tree root itself as the rollup target rather than forcing an "operating" pick.
- Beware fragmentation: the same company occasionally appears as two adjacent Kernel entities in a
  chain. Collapse consecutive nodes with an identical name or `kernel_id`; never merge on shared
  website alone — regional subsidiaries legitimately share the parent's domain.
- Errors: 403 invalid or missing key, 429 rate limited, 500 server error.

## Setting up your API key (do this first, or nothing above will run)

Every call needs a Kernel API key in `KERNEL_API_KEY`. Until it is set, the prompt above fails with a
403 and returns nothing useful.

**Get a key:**
- Already a Kernel customer: create one in the app at app.kernel.ai (Settings > API keys).
- Not a customer yet: request one at https://kernel.ai/kernel-api.
- Kernel already sent you a key: you are ready for the next step.

**Set it up:** save the key to a `.env` file in your project (never commit `.env`):

```
KERNEL_API_KEY=your-key-here
```

Load it with `from dotenv import load_dotenv; load_dotenv()` in Python, or
`export KERNEL_API_KEY=your-key-here` in your shell for the curl examples.

**Test the connection before running the prompt.** A `202` means the key works; a `403` means it is
missing or wrong, so fix it before continuing:

```bash
curl -s -o /dev/null -w "%{http_code}\n" -X POST https://api.kernel.ai/rest/v1/entity-resolution \
  -H "x-api-key: $KERNEL_API_KEY" -H "Content-Type: application/json" -d '{"legal_name":"Stripe"}'
```

## Other Kernel use cases

- **Resolve**: company signals to a KERN ID and identity.
- **Climb to the ultimate parent**: the same climb, continued past operating companies to the absolute
  apex of the tree.
- **Collapse hierarchies / plug data gaps / batch problem accounts**: other practical workflows.

````

{% endprompt %}

#### Just the script

The same logic as a ready-to-run CLI — copy it into `top_operating_parent.py` and run it directly:

```bash
export KERNEL_API_KEY=your-key-here
pip install requests

python3 top_operating_parent.py 7913528487                                   # by Kernel ID
python3 top_operating_parent.py "Ben & Jerry's" --country "United States"   # by name
```

<details>

<summary>top_operating_parent.py — expand for the full script</summary>

```python
#!/usr/bin/env python3
"""Find the top operating parent for any account — the highest operating company
in its ownership tree, skipping holding companies and investment vehicles.

One /v1/resolve-parent call answers it; the walk up fills in the operating-only
chain and the non-operating entities skipped along the way. Takes a Kernel ID,
or a company name (resolved first).
"""
import argparse
import os
import re
import sys
import time

import requests

BASE = os.environ.get("KERNEL_API_BASE_URL", "https://api.kernel.ai/rest")
MAX_DEPTH = 25                        # safety cap; real trees converge well before this
KERN_RE = re.compile(r"^\d{6,12}$")   # Kernel IDs are numeric
NON_OPERATING = {"HoldCo/Investment", "Business Unit", "Academic Unit", "Establishment"}


def run_job(path, body, headers):
    """Kernel jobs are async: submit, then poll until the job completes."""
    job_id = requests.post(f"{BASE}/{path}", headers=headers, json=body).json()["id"]
    delay = 2
    while True:
        time.sleep(delay)
        data = requests.get(f"{BASE}/{path}/{job_id}", headers=headers).json()
        if data["status"] == "failed":
            sys.exit(f"{path} job failed: {data}")
        if data["status"] == "completed":
            return data.get("record", {})
        delay = min(delay + 5, 30)


def is_operating(node):
    return bool(node) and node.get("entity_category") == "Company" \
        and node.get("entity_sub_category") not in NON_OPERATING


def name_of(node):
    if not node:
        return None
    return node.get("trading_name") or node.get("legal_name") or node.get("kernel_id")


def top_operating_parent(kernel_id, headers):
    """One call answers it; the walk up fills in the operating chain."""
    kid = str(kernel_id)
    root = run_job("v1/resolve-parent", {"kernel_id": kid}, headers)
    top_op = root.get("top_operating_parent")
    top_op_id = str(top_op["kernel_id"]) if top_op else None

    if top_op_id in (None, kid):        # the account IS the top of its operating tree
        return {"kernel_id": kid, "is_own_top_operating": top_op_id == kid,
                "top_operating_parent": top_op, "operating_chain": [], "skipped": [],
                "ultimate_parent": root.get("top_parent")}

    chain, skipped, seen, cur = [], [], {kid}, kid
    for _ in range(MAX_DEPTH):
        rec = root if cur == kid else run_job("v1/resolve-parent", {"kernel_id": cur}, headers)
        parent = rec.get("parent")
        if not parent:
            break
        pid = str(parent["kernel_id"])
        if pid in seen:                 # cycle guard (fragmented data)
            break
        seen.add(pid)
        (chain if is_operating(parent) else skipped).append(parent)
        cur = pid
        if pid == top_op_id:            # reached the answer - stop climbing
            break
    if not chain or str(chain[-1]["kernel_id"]) != top_op_id:
        chain.append(top_op)            # make sure the apex is present and last

    return {"kernel_id": kid, "is_own_top_operating": False,
            "top_operating_parent": top_op, "operating_chain": chain,
            "skipped": skipped, "ultimate_parent": root.get("top_parent")}


def main():
    ap = argparse.ArgumentParser(description="Find the top operating parent for an account.")
    ap.add_argument("query", help="A Kernel ID, or a company name to resolve first")
    ap.add_argument("--website", help="Sharpens the match when resolving by name")
    ap.add_argument("--country", help="Sharpens the match when resolving by name")
    args = ap.parse_args()

    key = os.environ.get("KERNEL_API_KEY")
    if not key:
        sys.exit("Set KERNEL_API_KEY first (see 'Setting up your API key')")
    headers = {"x-api-key": key, "Content-Type": "application/json"}

    if KERN_RE.match(args.query.strip()):
        kid = args.query.strip()
    else:
        body = {k: v for k, v in {"legal_name": args.query, "website": args.website,
                                  "country": args.country}.items() if v}
        kid = run_job("v1/entity-resolution", body, headers).get("kernel_id")
        if not kid:
            sys.exit(f"Could not resolve '{args.query}' to a Kernel entity")
        print(f"Resolved to kernel_id {kid}")

    r = top_operating_parent(kid, headers)
    if r["is_own_top_operating"]:
        print(f"{kid} is its own top operating parent - it IS the top of its operating tree")
    else:
        top = r["top_operating_parent"]
        print(f"Top operating parent: {name_of(top)}  "
              f"(kernel_id {top['kernel_id']}, {top.get('website') or '-'})")
        print("Operating chain (bottom -> top):")
        for node in r["operating_chain"]:
            print(f"  ^ {name_of(node)}  "
                  f"({node.get('entity_sub_category')}, kernel_id {node['kernel_id']})")
        if r["skipped"]:
            print("Skipped (non-operating): "
                  + ", ".join(f"{name_of(n)} [{n.get('entity_sub_category')}]"
                              for n in r["skipped"]))
    up, top = r["ultimate_parent"], r["top_operating_parent"]
    if up and top and str(up.get("kernel_id")) != str(top.get("kernel_id")):
        print(f"(Ultimate parent above it: {name_of(up)} - non-operating apex)")


if __name__ == "__main__":
    main()
```

</details>
{% endtab %}
{% endtabs %}


# Inbound

## Scheduled inbound from Salesforce

Most inbound enrichment should start where the record already lives: Salesforce. With scheduled inbound, Kernel polls your CRM for qualifying changes and runs the normal inbound workflow in the background.

This is the low-maintenance path when you want enriched Salesforce records without building your own orchestration layer around the Inbound API.

### How scheduled inbound works

* **Salesforce stays the operating surface.** Your team keeps creating and updating records in Salesforce.
* **Kernel polls on a configured cadence.** Scheduled inbound can run every minute, every five minutes, hourly, or daily.
* **A CRM filter decides what qualifies.** Each flow points at a configured Salesforce object, field map, and SOQL `WHERE` clause, such as lifecycle stage, region, record type, or another agreed criterion.
* **Matching records are claimed before work starts.** Kernel checks active capacity, cooldowns, prior attempts, and whether another run already owns the same Salesforce ID before dispatching work.
* **The normal inbound worker takes over.** Kernel links identity, runs the configured enrichment workflow, and tracks the record through queued, linking, processing, complete, or error.
* **Writeback uses the standard CRM integration.** When writeback is configured, the workflow pushes mapped enriched fields back to the Salesforce record.

Scheduled inbound works for Accounts, Leads, and configured custom objects included in your Kernel Salesforce setup.

{% hint style="info" %}
Scheduled inbound reuses the same Connected App, integration user, and permission set described in the [Salesforce integration](/integrations/salesforce-integration), including any standard or custom objects you have enabled for Kernel.
{% endhint %}

### When to use it

Use scheduled inbound when Salesforce is the source of records that need enrichment. It is especially useful when the trigger is already expressible as a CRM filter: records entering a lifecycle stage, accounts in a region, leads with a specific status, or custom objects that are ready for Kernel to process.

## API-triggered inbound

The Inbound API is still available when your own system needs to trigger just-in-time enrichment for a specific record and then poll for results.

Kernel collects and classifies data in real time, so API-triggered inbound is asynchronous. You initiate an enrichment task, then poll until the payload is complete or the workflow reaches an error state.

### Understanding data availability

* **Asynchronous population:** The output JSON payload populates gradually. When you poll, fields that have not been found or processed yet will usually be `null`.
* **Fast data points:** Some firmographic fields are often available within minutes, such as country, state or region, industry, headcount, and headcount growth.
* **Slower data points:** Other data points can require more extensive analysis or different data sources and may take longer.
* **Polling strategy:** Continue polling until `status` is `complete` or `error`. The exact fast and slow fields depend on your configured workflow.

### Error handling strategy

When interacting with the API, use this default retry strategy:

1. **HTTP 502/503/504 server errors**
   * **Meaning:** These indicate a temporary server-side issue.
   * **Default action:** Retry the request after some time.
2. **GET response** `"status": "error"`
   * **Meaning:** The enrichment workflow identified by the `executionId` encountered an error during processing.
   * **Default action:** Retry the request after some time. If the error persists, contact Kernel support.

### Rate limits

The API enforces rate limiting to ensure fair usage:

* **Rate limit:** 5 requests per second per API key
* **Concurrency limit:** Maximum of 50 accounts can be processing simultaneously. This can be increased upon request.
* **429 response:** When rate limit is exceeded, you will receive a 429 status code. Please wait before retrying.

### Base path

```
api.kernel.ai
```

## Initiate asynchronous enrichment for an account

> \### Input Data Requirements:\
> \* \_\_salesforceId\_\_ (string): This is the Salesforce record ID (e.g., Lead IDs often start with 00Q, Account IDs with 001).\
> \* You must provide either:\
> &#x20; \* \_\_linkedinUrl\_\_ (string): The LinkedIn profile URL (e.g., <https://www.linkedin.com/company/example).\\>
> &#x20; \* OR both \_\_name\_\_ (string) and \_\_website\_\_ (string).\
> \* \_\_force\_\_ (boolean, optional): When true, bypasses the previously enriched check and starts a fresh enrichment.\
> \* Contextual Data (Optional but helpful): While the core requirement is above, providing additional context like emailDomain (e.g., kernel.ai from <marcus@kernel.ai>) will be beneficial depending on the specific workflow configuration, even if it is not strictly required by the current endpoint setup. Check with your Kernel contact to see if these are utilized.\
> \* Data Privacy: Do not send Personally Identifiable Information (PII) such as first name or last name, unless explicitly part of the agreed schema. You should send email\_domain if available and relevant to the configuration.\
> &#x20;

```json
{"openapi":"3.1.0","info":{"title":"Kernel Inbound API","version":"1.0.0"},"paths":{"/api/v1/inbound/enrichment":{"post":{"tags":["Enrichment"],"summary":"Initiate asynchronous enrichment for an account","description":"### Input Data Requirements:\n* __salesforceId__ (string): This is the Salesforce record ID (e.g., Lead IDs often start with 00Q, Account IDs with 001).\n* You must provide either:\n  * __linkedinUrl__ (string): The LinkedIn profile URL (e.g., https://www.linkedin.com/company/example).\n  * OR both __name__ (string) and __website__ (string).\n* __force__ (boolean, optional): When true, bypasses the previously enriched check and starts a fresh enrichment.\n* Contextual Data (Optional but helpful): While the core requirement is above, providing additional context like emailDomain (e.g., kernel.ai from marcus@kernel.ai) will be beneficial depending on the specific workflow configuration, even if it is not strictly required by the current endpoint setup. Check with your Kernel contact to see if these are utilized.\n* Data Privacy: Do not send Personally Identifiable Information (PII) such as first name or last name, unless explicitly part of the agreed schema. You should send email_domain if available and relevant to the configuration.\n ","parameters":[{"in":"header","name":"x-api-key","schema":{"type":"string","description":"API key for authentication"},"required":true,"description":"API key for authentication"}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"type":"object","properties":{"name":{"type":"string","description":"The company name (required if website is provided and no linkedinUrl)"},"website":{"type":"string","description":"The company website (required if name is provided and no linkedinUrl)"},"linkedinUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The LinkedIn company profile URL"},"street":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The street address"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The city"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The state or province"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The country"},"legalName":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The legal company name"},"force":{"description":"When true, bypasses the previously enriched check and starts a fresh enrichment.","type":"boolean"},"salesforceId":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The Salesforce record ID (e.g., Lead IDs often start with 00Q, Account IDs with 001)"}},"required":["salesforceId"]},{"type":"object","properties":{"name":{"type":"string","description":"The company name (required if website is provided and no linkedinUrl)"},"website":{"type":"string","description":"The company website (required if name is provided and no linkedinUrl)"},"linkedinUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The LinkedIn company profile URL"},"street":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The street address"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The city"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The state or province"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The country"},"legalName":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The legal company name"},"force":{"description":"When true, bypasses the previously enriched check and starts a fresh enrichment.","type":"boolean"}},"required":["website"]}],"description":"Request body for initiating inbound enrichment"}}}},"responses":{"200":{"description":"Note the executionId. You will need this unique identifier to poll for results.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"anyOf":[{"type":"string","const":"starting","description":"New enrichment workflow has been initiated"},{"type":"string","const":"cached","description":"Account was recently enriched (within 14 days)"},{"type":"string","const":"in_progress","description":"Enrichment is in progress"}]},"executionId":{"type":"string","description":"Unique identifier for this enrichment job. Use this to poll for results."},"message":{"description":"Optional message providing additional context","type":"string"}},"required":["status","executionId"],"additionalProperties":false,"description":"Successful response from POST enrichment endpoint"}}}},"400":{"description":"400 Bad Request","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","const":"error","description":"Indicates the request failed"},"message":{"type":"string","description":"Error message explaining what went wrong"}},"required":["status","message"],"additionalProperties":false,"description":"Error response from POST enrichment endpoint"}}}}}}}}}
```

## Polling for Enrichment Status and Results

> Because enrichment happens asynchronously, you need to periodically check the status of your request using the executionId.

```json
{"openapi":"3.1.0","info":{"title":"Kernel Inbound API","version":"1.0.0"},"paths":{"/api/v1/inbound/enrichment":{"get":{"tags":["Enrichment"],"summary":"Polling for Enrichment Status and Results","description":"Because enrichment happens asynchronously, you need to periodically check the status of your request using the executionId.","parameters":[{"in":"header","name":"x-api-key","schema":{"type":"string","description":"API key for authentication"},"required":true,"description":"API key for authentication"},{"in":"query","name":"executionId","schema":{"type":"string","description":"The executionId returned from the POST /enrichment request"},"required":true,"description":"The executionId returned from the POST /enrichment request"}],"responses":{"200":{"description":"Enrichment status and results. The response includes dynamic fields based on your client configuration. Salesforce ID can be included as an output field when configured.","content":{"application/json":{"schema":{"anyOf":[{"allOf":[{"type":"object","properties":{"message":{"type":"string","description":"Status message describing the enrichment result"},"status":{"type":"string","const":"complete","description":"All enrichment data is available"}},"required":["message","status"],"additionalProperties":false},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}],"description":"Completed response from GET enrichment endpoint. Contains status, message, and dynamic fields based on client configuration"},{"allOf":[{"type":"object","properties":{"message":{"type":"string","description":"Status message describing the current processing state"},"status":{"anyOf":[{"type":"string","const":"processing"},{"type":"string","const":"fast-success"}],"description":"processing: Enrichment is still running. fast-success: Fast data points are available, but enrichment may still be processing slower data points"}},"required":["message","status"],"additionalProperties":false},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}],"description":"In-progress response from GET enrichment endpoint. Contains status, message, and dynamic fields based on client configuration"}]}}}},"400":{"description":"400 Bad Request","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"message":{"type":"string","description":"Error message explaining what went wrong"},"status":{"anyOf":[{"type":"string","const":"error"}],"description":"error: Unrecoverable error occurred."}},"required":["message","status"],"additionalProperties":false},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}],"description":"Error response from GET enrichment endpoint"}}}},"500":{"description":"500 Internal Server Error","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"message":{"type":"string","description":"Error message explaining what went wrong"},"status":{"anyOf":[{"type":"string","const":"error"}],"description":"error: Unrecoverable error occurred."}},"required":["message","status"],"additionalProperties":false},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}],"description":"Error response from GET enrichment endpoint"}}}}}}}}}
```


# SSO/SAML

## What is SSO (and SAML)?

* SSO: One secure login to access all your tools.
* SAML: An industry-standard protocol that lets your Identity Provider (IdP) (e.g., Okta, Microsoft Entra ID/Azure AD, Google Workspace, OneLogin, Ping) authenticate users for Kernel without new passwords.

## Why it matters

SSO lets your team manage Kernel access from your existing IdP. You can enforce MFA, conditional access, group-based access, offboarding, and audit logging in one place, while Kernel avoids storing user passwords.

## FAQ

Will this work with our IdP?

* Yes - Kernel can support Okta, Microsoft Entra ID (Azure AD), Google Workspace, OneLogin, Ping, and other SAML 2.0 providers.

Does Kernel support password authentication?

* No. Kernel currently supports email OTP and SSO. SAML can be enabled if your organization requires it.

What does Kernel use for the SSO/SAML?

* Kernel uses WorkOS for SSO/SAML connection management.


# Trust center

Kernel's security posture, certifications, and compliance documentation

For full details on Kernel's security posture, certifications, and compliance documentation, visit our Trust Center:

{% hint style="info" %}
[**Kernel Trust Center**](https://trust.kernel.ai)

Access our security documentation, SOC 2 reports, data processing agreements, and other compliance resources.
{% endhint %}

If you have security questions not covered by the Trust Center, contact <security@kernel.ai>.


