> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kombo.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Job feeds and bulk imports

> Learn how you can import high volumes of job postings into AI Apply

## Overview

Job feeds allow you to easily synchronize the job postings you have in your database with AI Apply.

For each of your job inventory sources, set up a job feed, and periodically send an up-to-date list of all job postings using our bulk import endpoint. Kombo takes care of job posting creation, archival, updates, and more. A single job feed holds the jobs from one source, like a recruitment marketing partner or a specific employer with which you have a relationship.

## How an import works

```mermaid theme={null}
sequenceDiagram
    participant You
    participant Kombo
    You->>Kombo: POST bulk import
    Kombo->>Kombo: Create new, update existing, archive missing
    Kombo-->>You: created / processed / archived
    Kombo->>Kombo: Parse new postings in the background
    Kombo->>You: AI Apply data-changed webhook
```

The bulk import response comes back once your records are stored, which is before parsing has finished. At response time, the postings are queued for parsing and only become applyable once parsing completes.

We send an AI Apply data-changed webhook when a job posting's status changes, including the ones your import archived. You therefore never have to compare imports yourself to find out what changed. See [Webhooks](/ai-apply/webhooks) for the lifecycle and payloads.

## Creating a job feed

You must create the job feed before being able to bulk import. This can be done either in the Kombo Dashboard or through the API.

Via API, the [POST Job Feeds](/ai-apply/v1/post-job-feeds) endpoint returns the new feed:

```json theme={null}
{
  "id": "mK7pQw9xNvEr2LdY5sGh8TcZ",
  "label": "Standard Acquisition Services LLC"
}
```

The returned `id` identifies the feed in every import you send afterwards. You can look it up again at any time with [GET Job Feeds](/ai-apply/v1/get-job-feeds).

<Tip>
  Choose clear, descriptive names for job feeds. Whenever possible, use the data
  source name so you can easily track the origin of imported job postings later.
</Tip>

## Sending an import

Each import sends a newline-separated list of JSON records (NDJSON) as the request body, with one record per job posting.

<Note>
  General considerations for job postings (query parameters, location, etc.)
  still apply. See the [AI Apply
  documentation](/ai-apply/introduction#parsing-a-job-posting) for details.
</Note>

```json theme={null}
{ "url": "https://careers.acme.com/job/1", "career_site_label": "ACME Corp" }
{ "url": "https://careers.acme.com/job/2", "career_site_label": "ACME Corp", "job_code": "ENG-123" }
{ "url": "https://careers.acme.com/job/3", "career_site_label": "ACME Corp", "location": { "country": "US", "postal_code": "94115" } }
```

| Field               | Required | Behavior                                                                                                                                                                                                    |
| ------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `url`               | Yes      | The job posting we parse and later submit applications to.                                                                                                                                                  |
| `career_site_label` | Yes      | Groups the posting under a career site. We create a career site for each unique label automatically.                                                                                                        |
| `job_code`          | No       | Your own identifier for the job.                                                                                                                                                                            |
| `location`          | No       | Where we apply from. We route the browser session through that country, and through that state for US postal codes. Omitting it on a later import keeps the location we already have, and `null` clears it. |

## How postings are matched

Inside a feed, a posting is identified by its career site label, its URL, and its job code:

* A record whose combination we have seen before updates that posting.
* A new combination creates a posting and queues it for parsing.
* A posting from an earlier import that is missing from the current one is archived. Its parsed data is kept, so a later import containing it again can revive it.

If the same combination appears twice within one import, the last record wins and no error is raised.

<Tip>
  If you have frequent churn in job URLs between imports (e.g. a timestamp in
  the job URL) but your job codes are stable, please let us know. We can also
  match your feed on the job code alone, in which case every record needs a
  `job_code` and the `url` is upserted.
</Tip>

<Warning>
  **Note:** Job postings imported through different job feeds are always treated
  as distinct!
</Warning>

## Limits and failures

* Send the body as `application/x-ndjson`.
* A request may be at most 35 MB and take at most 5 minutes.
* Only one import can run per job feed at a time.

If a record is invalid or ill-formatted, we abort the import at that line. Records before it may already have been created or updated, and nothing is archived. Fix the faulty record and submit the request again: because postings are matched instead of blindly inserted, resubmitting the same import is safe.

## Example responses

A successful import responds with what happened to your postings:

```json theme={null}
{
  "status": "success",
  "data": {
    "created": 150,
    "processed": 197,
    "archived": 10
  }
}
```

* `processed`: records we read from your request.
* `created`: postings that did not exist yet and are now queued for parsing.
* `archived`: postings from earlier imports that were missing from this one.

Errors use our standard error format, with `code` as the field to branch on:

```json theme={null}
{
  "status": "error",
  "error": {
    "code": "PLATFORM.INPUT_INVALID",
    "title": "Your request contains invalid data and failed validation.",
    "message": "Error parsing line number 42: url: URL must start with http:// or https://",
    "log_url": "https://app.kombo.dev/my-prod/logs?interactionId=123456"
  }
}
```

## API reference

* [POST Bulk Import](/ai-apply/v1/post-job-feeds-job-feed-id-bulk-import), including every error this endpoint can return
* [POST Job Feeds](/ai-apply/v1/post-job-feeds)
* [GET Job Feeds](/ai-apply/v1/get-job-feeds)
