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

# Search qualified talents

> Start a qualified talent search from structured filters and qualification criteria.

<Card title="First time? Set up Authentication" icon="key" href="/authentication">
  Learn how to authenticate your API requests with your API key.
</Card>

Start a **qualified search**: Kalent finds matching profiles and evaluates each one against your `qualificationCriterias`. New to qualified search? Read the [overview](/api-reference/qualified-search-overview) first. The request returns a `castingId` immediately. Poll [`GET /v1/search/talents/qualified/{castingId}`](/api-reference/get-qualified-search) while `nextAction` is `wait`, or receive a webhook when the call stops.

This is the filter-based counterpart of [`POST /v1/search/talents/qualified/by-prompt`](/api-reference/search-talents-qualified-by-prompt). Filters use the same model as [`POST /v1/search/talents`](/api-reference/search-talents).

## How a call runs

Each start (and each [continue](/api-reference/continue-qualified-search)) is a **call** with its own quota, `target`: `target.greens` greens to find during this call and a credit cap, `target.maxCredits`. Kalent chains analysis waves on its own until the call stops: green quota reached, cap spent, no more matching talents, or a failed wave. Two credits are charged per judged talent, at the end of each wave; Kalent never judges more talents in parallel than greens the call still needs, so a call never pays for a judgement started after its green quota was met. The full model — calls, waves, credits, reservation, edge cases — is on the [qualified search overview](/api-reference/qualified-search-overview).

## Request body

<ParamField body="filters" type="object[]" required>
  Array of filter objects. Same shape as [`POST /v1/search/talents`](/api-reference/search-talents): each object needs `filterType`, `value`, `isRequired`, `isExcluded`, and `isExactMatch`. See [filter types](/api-reference/search-talents#filter-types) and [accepted filter values](/api-reference/accepted-filter-values).
</ParamField>

<ParamField body="qualificationCriterias" type="string[]" required>
  Criteria Kalent uses to qualify matching profiles. At least one item is required. Maximum 10 items. Each string is trimmed, must be at least 1 character, and at most 300 characters.
</ParamField>

<ParamField body="scoringLanguage" type="string">
  Language of the qualification texts (`recap`, criterion `title` and `result`). `en` or `fr`. Omitted: the language of the user who owns the API key, changeable at [app.kalent.ai/settings/profile](https://app.kalent.ai/settings/profile); English when that user has none. Resolved once at start and stored on the search; every continue keeps it. The same filters and criteria in another language are a **different search**: new `castingId`, new analysis, new credits. Quotes in `references` stay in the profile's original language. See [Scoring language](#scoring-language).
</ParamField>

<ParamField body="target" type="object">
  Quota of **this call**. Optional; each field you omit falls back to its default.

  <Expandable title="target fields">
    <ParamField body="target.greens" type="number" default="10">
      Number of green talents to find during this call. Integer from 1 to 50. Greens already found on earlier calls do not count. A later continue reuses this number as the quota of that new call, unless the continue sends another value.
    </ParamField>

    <ParamField body="target.maxCredits" type="number" default="60">
      Maximum credits this call may spend. Even integer from 2 to 300. Two credits per analysed talent, so 60 credits is at most 30 talents.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="webhook" type="object">
  Optional. Where and for which verdicts Kalent sends events for this search. Stored on the search and reused by later continues unless they send another `webhook`. See [Webhooks](#webhooks).

  <Expandable title="webhook fields">
    <ParamField body="webhook.url" type="string" required>
      HTTPS URL that receives the events. Must not be `localhost` or a private network address.
    </ParamField>

    <ParamField body="webhook.verdicts" type="string[]" default="[&#x22;green&#x22;, &#x22;amber&#x22;, &#x22;red&#x22;]">
      Verdicts for which a `talent_judged` event is sent. One to three of `green`, `amber`, `red`, without duplicates. Omitted: all three. Filtering events changes neither the analysis nor the credits charged, and the end-of-call event is always sent.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="customParams" type="object">
  Optional JSON object with string keys. Stored on the search and echoed on webhook payloads. The JSON serialization must be at most 4096 bytes.
</ParamField>

<ParamField body="forceNew" type="boolean" default="false">
  When `false`, a request with the same filters, qualification criteria, and resolved `scoringLanguage` as an existing qualified search returns that search (`reused: true`) instead of starting another. When `true`, Kalent always starts a new search.
</ParamField>

## Scoring language

Kalent writes `recap`, criterion `title`, and criterion `result` in one language for the whole search. By default this is the language of the user who owns the API key (set at [app.kalent.ai/settings/profile](https://app.kalent.ai/settings/profile); English when unset). Send `scoringLanguage` to override it for this search only. Quotes in `references` are copied verbatim from the profile and are never translated.

The language is resolved when the search starts and is returned as `scoringLanguage` on [GET](/api-reference/get-qualified-search). It cannot change afterwards: sending `scoringLanguage` on [continue](/api-reference/continue-qualified-search) returns `validation_error`. Starting the same request in another language starts another search, with its own analysis and its own credits; change the profile language and the default follows for the next start, without touching running searches.

## Response

New searches return **202**. A reused search that is not `running` returns **200**.

The `X-Api-Credits-Remaining` header is your workspace balance right after the request, that is after Kalent reserved `min(balance, target.maxCredits)` for this call. Unused reserved credits come back when each wave ends, so the balance goes back up. GET does not send this header.

<ResponseField name="success" type="boolean">
  Whether the request completed without errors.
</ResponseField>

<ResponseField name="data" type="object">
  Present when `success` is `true`.

  <Expandable title="data fields">
    <ResponseField name="castingId" type="string">
      Identifier of the qualified search. Use it with GET and continue.
    </ResponseField>

    <ResponseField name="status" type="string">
      One of `running`, `can_continue`, `pool_exhausted`, `failed_retryable`.
    </ResponseField>

    <ResponseField name="reused" type="boolean">
      `true` when an existing qualified search for the same request was returned.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="error" type="object">
  Present when `success` is `false`.

  <Expandable title="error fields">
    <ResponseField name="code" type="string">
      One of: `validation_error`, `no_qualification_criterias`, `insufficient_api_credits`, `casting_start_in_progress`, `search_engine_timeout`, `internal_error`, plus the authentication codes documented in [Authentication](/authentication).
    </ResponseField>

    <ResponseField name="message" type="string">
      Human-readable error description.
    </ResponseField>

    <ResponseField name="debugTrackingCode" type="string">
      Include this when contacting support.
    </ResponseField>

    <ResponseField name="available" type="number">
      Present when `code` is `insufficient_api_credits`.
    </ResponseField>

    <ResponseField name="required" type="number">
      Present when `code` is `insufficient_api_credits`. At least 2 credits are required to start.
    </ResponseField>
  </Expandable>
</ResponseField>

| HTTP status | Code                         | When                                                                                                                                                                                                                                                 |
| ----------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400         | `validation_error`           | Invalid body (including a non-HTTPS or private `webhook.url`, a `webhook` without `url`, `customParams` over 4096 bytes, an odd or out-of-range `target.maxCredits`, a `target.greens` outside 1–50, or a `scoringLanguage` other than `en` or `fr`) |
| 400         | `no_qualification_criterias` | No qualification criterion remains after processing                                                                                                                                                                                                  |
| 402         | `insufficient_api_credits`   | Fewer than 2 credits available                                                                                                                                                                                                                       |
| 409         | `casting_start_in_progress`  | Another start for the same request is already in progress                                                                                                                                                                                            |
| 504         | `search_engine_timeout`      | The search timed out                                                                                                                                                                                                                                 |
| 500         | `internal_error`             | Unexpected error                                                                                                                                                                                                                                     |

## Status values and `nextAction`

GET and the end-of-call webhook return both a `status` and a `nextAction`; follow `nextAction`. `running` / `wait`, `can_continue` / `continue`, `failed_retryable` / `retry`, `pool_exhausted` / `done`. See the [overview](/api-reference/qualified-search-overview#the-flow).

## Webhooks

When the search has a `webhook.url`, Kalent POSTs JSON to that URL for each judged talent whose verdict is in `webhook.verdicts` (`talent_judged`) and **once** when a call stops (`wave_finished` or `pool_exhausted`). Delivery rules, retries, headers, and how to authenticate the calls are on the [overview](/api-reference/qualified-search-overview#webhooks).

Headers:

| Header                 | Value                                                 |
| ---------------------- | ----------------------------------------------------- |
| `Content-Type`         | `application/json`                                    |
| `X-Kalent-Event`       | `talent_judged`, `wave_finished`, or `pool_exhausted` |
| `X-Kalent-Delivery-Id` | Identifier of this delivery                           |
| `X-Kalent-Source-Type` | `api_casting_run`                                     |
| `X-Kalent-Source-Id`   | Identifier of the run that produced the event         |

`talent_judged` body:

```json theme={null}
{
  "eventType": "talent_judged",
  "castingId": "3f9d2c1e-7b4a-4e8f-9c21-5d6a8b0e4f17",
  "customParams": null,
  "sentAt": "2026-09-15T15:00:00.000Z",
  "data": {
    "talent": {}
  }
}
```

`data.talent` is the same object as one item in the GET `talents` array. Each criterion in `data.talent.scoring.criterias` carries `title`, `result`, `status`, `evaluable`, and `references`; `recap`, `title`, and `result` are written in the search's [scoring language](#scoring-language).

`wave_finished` and `pool_exhausted` body (end of a call):

```json theme={null}
{
  "eventType": "wave_finished",
  "castingId": "3f9d2c1e-7b4a-4e8f-9c21-5d6a8b0e4f17",
  "customParams": null,
  "sentAt": "2026-09-15T15:00:00.000Z",
  "data": {
    "status": "can_continue",
    "nextAction": "continue",
    "counts": { "green": 10, "amber": 4, "red": 6 },
    "creditsCharged": 40
  }
}
```

`eventType` is `pool_exhausted` when the talent pool is exhausted, otherwise `wave_finished` (including when a wave failed, with `data.status` `failed_retryable` and `data.nextAction` `retry`). `data.counts` is `{ green, amber, red }` for the whole search, regardless of `webhook.verdicts`. `data.creditsCharged` is the total charged on the search so far; `creditsRemaining` is only on GET. `customParams` is the object stored on the search, or `null`. `sentAt` is an ISO 8601 timestamp. `eventType`, `castingId`, `customParams`, and `sentAt` are the same on every event.

## Example

```bash theme={null}
curl -X POST https://app.kalent.ai/api/v1/search/talents/qualified \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "filters": [
      {
        "filterType": "JOB_TITLE",
        "value": "Engineer",
        "isRequired": true,
        "isExcluded": false,
        "isExactMatch": false
      }
    ],
    "qualificationCriterias": ["Led a team"],
    "scoringLanguage": "en",
    "target": { "greens": 10, "maxCredits": 60 },
    "webhook": { "url": "https://ats.example.com/kalent/hook", "verdicts": ["green", "amber"] }
  }'
```

```json theme={null}
{
  "success": true,
  "data": {
    "castingId": "3f9d2c1e-7b4a-4e8f-9c21-5d6a8b0e4f17",
    "status": "running",
    "reused": false
  }
}
```
