> ## 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.

# Storing application forms

> Pull job postings together with their application forms and keep your own copy up to date.

## Overview

[GET Job Postings Forms](/ai-apply/v1/get-postings-forms) returns all job postings, each with its application form and submission token.

Once you have a candidate's answers, include the submission token in the application request so we know which job posting the application is for.

This flow relies on you to keep your database in sync by polling this endpoint regularly. Whenever our parsing updates a job posting or its form, you need to fetch again to pick up the change.

<Tip>
  Use this flow if you need to store the application form in your product (e.g.
  if you are a service distributing the form to other job boards). If you are a
  job board yourself, use our [inquiry flow](/ai-apply/posting-inquiry) for a
  better candidate experience.

  **We generally do not recommend this flow unless you have a hard requirement
  to cache the data**. Job postings change over time. With the
  [inquiry flow](/ai-apply/posting-inquiry), you always get the newest version
  of the application form. Owning the sync yourself can quickly become a complex
  infrastructure-heavy operation.
</Tip>

## How it works

```mermaid theme={null}
sequenceDiagram
    participant You
    participant Kombo
    You->>Kombo: GET postings/forms (updated_after)
    Kombo-->>You: Postings with forms and multi-use tokens
    You->>You: Show a stored form, collect answers
    You->>Kombo: POST apply (stored token + answers)
    Kombo-->>You: Application id, status PENDING
    Kombo->>You: Application status updated webhook
```

## Fetching the forms

Each response carries a page of postings and a `next` cursor. Page until `next` is `null`.

```json theme={null}
{
  "results": [
    {
      "id": "9QGNv3B98kL3hyELE1qsZ86s",
      "career_site": { "id": "Chc4dua5asAQ48KUERDVF1bs", "label": "Acme" },
      "url": "https://careers.acme.com/jobs/fullstack-engineer-14102",
      "job_code": "ACME_13",
      "availability": "APPLYABLE",
      "archived_at": null,
      "archived_reason": null,
      "created_at": "2025-01-01T00:00:00.000Z",
      "updated_at": "2025-03-02T23:12:32.000Z",
      "application_form": [],
      "submission_token": "<token>"
    }
  ],
  "next": null
}
```

<Note>
  Pages hold at most five postings, because every applyable one on the page
  carries a full application form. An initial sync of a large catalogue
  therefore takes many requests. Later syncs are small, as long as you filter
  them.
</Note>

The endpoint accepts the following query parameter filters: `career_site_ids` and `job_codes` to narrow the catalogue, and `updated_after` to fetch only what changed.

[Data fetching](/ai-apply/webhooks#data-changed-webhook) describes how to keep your copy current with `updated_after` and the data-changed webhook.

### Posting availability

Job posting results include applyable and non-applyable postings. Only jobs with the `APPLYABLE` availability include the `application_form` and `submission_token`. In every other state, both properties are `null`. The `availability` property tells you why:

| Availability  | What it means                                   |
| ------------- | ----------------------------------------------- |
| `APPLYABLE`   | The form and token are present. You can submit. |
| `PENDING`     | We are still parsing the posting.               |
| `UNAVAILABLE` | Parsing failed.                                 |
| `ARCHIVED`    | The job is gone. Stop displaying it.            |

Clear the form you are storing when a posting stops being `APPLYABLE`, rather than keeping it around in case the job comes back. If it does, the next sync hands you a fresh one.

## Submission token mechanics

Every applyable posting includes a unique `submission_token`. Unlike the short-lived single-use tokens from an [inquiry](/ai-apply/posting-inquiry), these are meant to be stored:

* **They do not expire.** A token stays valid for as long as the form it came with is current.
* **They are reusable.** Every accepted submission creates its own application, so one token serves every candidate who applies to that job.
* **They belong to one form.** Store the token and the form together and replace them together. A token paired with the wrong form is rejected.

<Warning>
  Do not hold a token longer than the form it arrived with. When a sync returns
  a posting with a new form, the pair you stored is out of date, and an
  application submitted with it could fail against the live job site.
</Warning>

## Sending an application

[POST Apply](/ai-apply/v1/post-apply) takes the stored token and the answers:

```json theme={null}
{
  "submission_token": "<token>",
  "candidate_email": "john.doe@gmail.com",
  "screening_question_answers": [
    {
      "question_id": "6VrjehyBk685vubNydiR1hSn",
      "answer": "John"
    }
  ]
}
```

| Field                        | Required | Behavior                                                                                                                        |
| ---------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `submission_token`           | Yes      | The token from your sync, paired with the form you stored.                                                                      |
| `candidate_email`            | Yes      | Supply this from your own system. Do not ask for it on the form. It does not prevent duplicate applications.                    |
| `screening_question_answers` | Yes      | One entry per answered question. See [Candidate answers](/ai-apply/introduction#candidate-answers) for formats.                 |
| `query_params`               | No       | Appended to the job posting URL when applying. See [Query parameters](/ai-apply/introduction#query-parameters-in-applications). |
| `additional_clicks`          | No       | Simulates the job being opened this many extra times, scattered over 60 minutes by default.                                     |

Answers have to match the form the token was issued with. A question ID that is not in that form, or a missing required answer, fails the request rather than being ignored.

## Return value and webhooks

Applying is asynchronous. The response confirms we accepted the application and queued it:

```json theme={null}
{
  "id": "ADbmw5XSkeCSE1fAucoxEGnwZ",
  "posting_id": "JDn252PEYa4rMhKbJBjtn3ng",
  "status": "PENDING",
  "created_at": "2025-01-01T00:00:00.000Z",
  "updated_at": "2025-03-02T23:12:32.000Z"
}
```

Keep the returned `id`. It is how you match this application to webhooks later on.

| Status      | Meaning                                                          |
| ----------- | ---------------------------------------------------------------- |
| `PENDING`   | We are applying, or the application is waiting on manual review. |
| `SUBMITTED` | The application reached the job site.                            |
| `DUPLICATE` | The ATS rejected it because this candidate had already applied.  |
| `FAILED`    | We established that the application cannot be submitted.         |

We emit the [application status updated webhook](/ai-apply/webhooks#application-updated-webhook) on every status except `PENDING`, so one arrives per application once it settles. [GET Applications](/ai-apply/v1/get-applications) is the polling alternative, and carries the same `updated_after` filter as the posting endpoints.

<Note>
  `proxy_email` is only populated once an application is `SUBMITTED`. See
  [Candidate account creation](/ai-apply/candidate-account-creation) to learn
  when we create an account on the candidate's behalf.
</Note>

A failure does not reach you immediately. Every failed submission goes through Kombo's QA first, and we only report `FAILED` once we have confirmed we cannot submit the application in any way. See [QA Mode](/ai-apply/introduction#qa-mode) for more info.

## API reference

* [GET Job Postings Forms](/ai-apply/v1/get-postings-forms)
* [POST Apply](/ai-apply/v1/post-apply)
* [GET Applications](/ai-apply/v1/get-applications)
