Skip to main content

First time? Set up Authentication

Learn how to authenticate your API requests with your API key.
A qualified search does two things POST /v1/search/talents does not: it evaluates every matching profile against your qualificationCriterias, and it keeps working on its own until it has found the number of green talents you asked for or spent the credits you allowed. You start it, then read the results as they arrive.

When to use it

Typical use cases:
  • Fill a shortlist in your ATS: ask for target.greens: 10, subscribe a webhook to green only, and push each talent_judged event into the job’s pipeline.
  • Budget-capped qualification per job: set target.maxCredits per requisition; Kalent never spends more on that call, and you decide with a continue whether the next budget is worth it.
  • Sourcing from a job description: send the description as a prompt to POST /v1/search/talents/qualified/by-prompt; Kalent turns it into filters and criteria, then qualifies.

Search, call, wave

A call stops at the first of: target.greens greens found during this call, target.maxCredits spent, no more matching talents, or a failed wave.

Scoring language

Every verdict text of a search (recap, criterion title, criterion result) is written in one language, en or fr. It is resolved once, when the search starts: the optional scoringLanguage of the start request, otherwise the language of the user who owns the API key, set at app.kalent.ai/settings/profile (English when unset). The resolved value is stored on the search, returned as scoringLanguage by GET, and reused by every continue; sending scoringLanguage on a continue returns validation_error. Quotes in references are copied verbatim from the profile and are never translated. The language is part of what identifies a search. The same filters or prompt and criteria in another language start a new search, with a new castingId, a fresh analysis, and new credits: verdicts are never translated after the fact. Changing the profile language only changes the default of the next start; running searches keep theirs.

The flow

  1. Start with POST /v1/search/talents/qualified or …/by-prompt. The response is 202 with a castingId and status: running.
  2. Follow the call: poll GET /v1/search/talents/qualified/{castingId} every 10–20 seconds while nextAction is wait, or wait for the end-of-call webhook. GET never costs credits.
  3. Act on nextAction: POST …/continue when it is continue (open a new call) or retry (rerun the failed wave). Stop when it is done.
canContinue on GET is kept for compatibility and equals nextAction === "continue".

Credits

  • Two credits per judged talent, whatever the verdict, red included. Charged at the end of each wave. Talents that Kalent opens but cannot judge, and talents never judged because the green quota was met first, are not charged.
  • Reservation. When a call opens, Kalent reserves min(workspace balance, target.maxCredits) from your balance and sizes the first wave on that reservation. Each following wave of the same call reserves what is left of the call’s budget, again capped by the balance at that moment.
  • Return of unused credits. When a wave ends, the credits of its reservation that were not charged go back to the balance. So the balance dips by the reservation right after start or continue, then settles to what was actually charged.
  • Not enough credits. A start or continue that cannot reserve at least 2 credits fails with 402 insufficient_api_credits. When the balance is lower than target.maxCredits, the call simply spends less; nothing fails.
Three numbers describe the money:

Worked example

Your balance is 200. You start with target: { greens: 1, maxCredits: 4 }.
  1. Kalent reserves min(200, 4) = 4. The start response carries X-Api-Credits-Remaining: 196 and status: running.
  2. The first wave may open up to two talents (4 credits ÷ 2), but with one green still needed it judges them one at a time. The first judged talent is green: the call has its green and stops there. No second judgement is started.
  3. GET now returns status: can_continue, nextAction: continue, canContinue: true, counts: { green: 1, amber: 0, red: 0 }, creditsCharged: 2, creditsRemaining: 0.
  4. The 2 unused reserved credits are returned: your balance is 198.

Why a call never overpays

Kalent never judges more talents in parallel than greens the call still needs: with one green missing, judgements run one at a time; with three missing, at most three run at once; as each green lands the allowance shrinks, and once the quota is met nothing else is started and nothing is left in flight. So a call never pays for a judgement started after its green quota was met. Amber and red talents judged before that point are charged like any other verdict, the call never exceeds target.maxCredits, and the price of this guarantee is time: the smaller the remaining green quota, the less parallel the last judgements, and the slower the end of the call.

Edge cases

  • Retry of a failed wave. A continue while nextAction is retry reruns the failed wave inside the same call: same call index, budget = what the call has not spent yet, again capped by the balance. Talents already charged on that wave are not charged again. If the balance is empty, GET keeps nextAction: retry with creditsRemaining: 0, and the continue answers 402 insufficient_api_credits until credits are added.
  • Retry with nothing left to judge. If the failed call had already found its target.greens, the continue does not start a wave: it closes the call and answers 200 with status: can_continue, nextAction: continue. The next continue opens a new call.
  • Start whose launch failed. If the search was created but its first wave could not start, the next identical start (same filters or prompt and criteria, same resolved scoring language, no forceNew) relaunches the first call on the same castingId and answers 202 with reused: true.
  • forceNew. Without it, a start whose filters or prompt, criteria, and resolved scoring language match an existing search returns that search (reused: true; 200 if it is not running). With forceNew: true, Kalent always creates a new search.
  • Concurrent continues. A continue while a call is running returns 409 casting_run_in_progress; a second start for the same request while the first is being created returns 409 casting_start_in_progress.
  • Partial target or webhook on continue. Each nested field is independent: a field you omit keeps its stored value, { "target": { "greens": 20 } } leaves the credit cap untouched. Omitting the whole object keeps everything.
  • GET verdicts filter. It only filters the talents array. counts stay the totals for the whole search and nothing is charged.

Webhooks

Set webhook.url on start (or replace it on continue) to receive events instead of polling. The URL must be HTTPS and must not point to localhost, a private network address, or a cloud metadata endpoint; other URLs are rejected with 400 validation_error.

Events

Every event shares the same envelope: eventType, castingId, customParams (the object stored on the search, or null), sentAt (ISO 8601), and data. Kalent sends one end-of-call event per call, to the URL stored on the search at that moment; nothing is sent between the internal waves of a call. Filtering webhook.verdicts changes neither the analysis nor the bill, and counts in the end event are always the full totals. creditsRemaining is not part of the webhook; read it on GET.

Delivery

  • Expected response. Any 2xx within 5 seconds. Redirects are not followed. The response body is ignored.
  • Retries. A network error, a timeout, 408, 429, 5xx, or any other non-2xx response that is not a 4xx is retried: at most 3 attempts in total, about 1 second then 5 seconds apart. Any other 4xx marks the delivery failed with no retry. Retries happen in the same process that produced the event; if that process stops, the remaining attempts are not resumed later.
  • Idempotency. Each event is delivered at most once per search: one talent_judged per talent and run, one end-of-call event per call. All attempts of one event carry the same X-Kalent-Delivery-Id, so deduplicate on it if you may receive the same attempt twice.
  • Ordering. Deliveries are sent as events happen and independently retried, so a talent_judged can arrive after the wave_finished of its call. Treat GET as the source of truth and webhooks as notifications.

Authenticating webhooks

Kalent does not sign webhook payloads. Put a secret value in customParams when you start the search (for example { "webhookSecret": "…" }): it is stored on the search and echoed unchanged on every event, so your endpoint can reject any request that does not carry it. Rotate it with a continue that sends new customParams.