> For the complete documentation index, see [llms.txt](https://docs.kernel.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.kernel.ai/developer/inbound-api.md).

# 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.md), 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"}}}}}}}}}
```
