# Accepted filter values Source: https://docs.kalent.ai/api-reference/accepted-filter-values Reference of all filter types and their accepted values for the Search Talents endpoint. Each filter in the [Search Talents](/api-reference/search-talents) request body requires a `filterType` discriminator. This page lists every supported type and the values it accepts. ## Filter types overview | Filter type | Value format | `radius` | `history` | | --------------------------- | ----------------------- | :------: | :-------: | | `JOB_TITLE` | Free text | — | ✓ | | `LOCATION` | City or country | ✓ | — | | `YEARS_OF_EXPERIENCE` | Range bucket | — | — | | `SKILL` | Free text | — | — | | `KEYWORD` | Free text | — | — | | `LANGUAGE` | Lowercase language name | — | — | | `LANGUAGE_PROFICIENCY` | Proficiency level | — | — | | `COMPANY_NAME` | Free text | — | ✓ | | `COMPANY_SIZE` | Range bucket | — | — | | `COMPANY_INDUSTRY` | Industry string | — | — | | `EDUCATION_DEGREE` | Degree level | — | — | | `EDUCATION_SCHOOL_NAME` | Free text | — | — | | `CERTIFICATION_NAME` | Free text | — | — | | `SENIORITY` | Contract type | — | — | | `DURATION_IN_JOB` | Range bucket | — | — | | `GRADUATION_YEAR` | Four-digit year | — | — | | `LINKEDIN_RELATIONS_DEGREE` | LinkedIn relationship | — | — | *** ## YEARS\_OF\_EXPERIENCE Accepted values: `0-1`, `1-3`, `3-5`, `5-10`, `10-15`, `15-20`, `20-30`, `30-100` ## COMPANY\_SIZE Accepted values: `1-10`, `11-50`, `51-200`, `201-500`, `501-1000`, `1001-5000`, `5001-10000`, `10001+` ## EDUCATION\_DEGREE Accepted values: `bachelors`, `masters`, `doctorates` ## DURATION\_IN\_JOB Accepted values: `0-1`, `1-3`, `3-5`, `5-10`, `10-15`, `15-20`, `20-30`, `30-100` ## LANGUAGE Lowercase language names. Common values: `english`, `french`, `spanish`, `german`, `portuguese`, `mandarin`, `japanese`, `korean`, `arabic`, `hindi`, `italian`, `dutch`, `russian`, `turkish`, `polish`, `swedish` ## SENIORITY Accepted values: `Freelance` ## LINKEDIN\_RELATIONS\_DEGREE Accepted values: `1st degree`, `invited` Filters talents by their relationship to **your connected LinkedIn account**. * `1st degree` means accepted first-degree LinkedIn connections only. * `invited` means pending LinkedIn invitations only. These values are distinct: `1st degree` does **not** include pending invitations. The API resolves the LinkedIn account from your authenticated Kalent user; you do not need to provide an account identifier. ## history Applies to `JOB_TITLE` and `COMPANY_NAME`. Accepted values: `PAST`, `CURRENT`, `CURRENT_AND_PAST` ## COMPANY\_INDUSTRY The `value` must be one of the accepted industry values below. Any other value is rejected. `information technology and services`, `government administration`, `retail`, `banking`, `construction`, `computer software`, `management consulting`, `real estate`, `hospital & health care`, `insurance`, `automotive`, `financial services`, `higher education`, `transportation/trucking/railroad`, `civic & social organization`, `environmental services`, `food production`, `aviation & aerospace`, `wholesale`, `mechanical or industrial engineering`, `telecommunications`, `research`, `pharmaceuticals`, `professional training & coaching`, `marketing and advertising`, `logistics and supply chain`, `hospitality`, `non-profit organization management`, `consumer goods`, `internet`, `renewables & environment`, `individual & family services`, `accounting`, `restaurants`, `defense & space`, `luxury goods & jewelry`, `machinery`, `chemicals`, `electrical/electronic manufacturing`, `human resources`, `sports`, `building materials`, `cosmetics`, `medical devices`, `oil & energy`, `staffing and recruiting`, `apparel & fashion`, `leisure, travel & tourism`, `architecture & planning`, `utilities`, `education management`, `health, wellness and fitness`, `farming`, `airlines/aviation`, `public policy`, `law practice`, `biotechnology`, `security and investigations`, `events services`, `food & beverages`, `industrial automation`, `broadcast media`, `facilities services`, `consumer services`, `medical practice`, `civil engineering`, `entertainment`, `wine and spirits`, `legal services`, `primary/secondary education`, `business supplies and equipment`, `media production`, `supermarkets`, `textiles`, `furniture`, `design`, `performing arts`, `publishing`, `sporting goods`, `semiconductors`, `newspapers`, `mining & metals`, `public relations and communications`, `packaging and containers`, `computer & network security`, `information services`, `international trade and development`, `music`, `e-learning`, `museums and institutions`, `computer games`, `plastics`, `printing`, `fine art`, `consumer electronics`, `mental health care`, `government relations`, `paper & forest products`, `public safety`, `online media`, `investment management`, `international affairs`, `recreational facilities and services`, `motion pictures and film`, `outsourcing/offshoring`, `maritime`, `military`, `graphic design`, `package/freight delivery`, `glass, ceramics & concrete`, `arts and crafts`, `market research`, `computer hardware`, `commercial real estate`, `venture capital & private equity`, `photography`, `shipbuilding`, `investment banking`, `railroad manufacture`, `gambling & casinos`, `veterinary`, `import and export`, `philanthropy`, `think tanks`, `translation and localization`, `animation`, `computer networking`, `political organization`, `warehousing`, `law enforcement`, `dairy`, `writing and editing`, `religious institutions`, `judiciary`, `ranching`, `libraries`, `nanotechnology`, `wireless`, `legislative office`, `program development`, `fishery`, `executive office`, `capital markets`, `alternative medicine`, `fund-raising`, `tobacco`, `alternative dispute resolution` # Add talent to sourcing Source: https://docs.kalent.ai/api-reference/add-talent-to-sourcing POST /v1/sourcings/{sourcingId}/candidates Link a talent to a sourcing as a candidate. Learn how to authenticate your API requests with your API key. Creates or updates the candidate row for `(talentId, sourcingId)` in your workspace. Status is forced to **TO\_BE\_CONTACTED**. Existing candidates keep their `userRating`; new candidates use server defaults. ## Path parameters The sourcing id returned by [Create sourcing](/api-reference/create-sourcing). ## Request body Talent identifier to attach. ## Response When `success` is `true`, `data` includes `candidateId`, `talentId`, `sourcingId`, `statusHandle`, `userRating`, and `createdAt`. ## Errors | Code | HTTP | | ---------------------- | ---- | | `validation_error` | 400 | | `insufficient_credits` | 402 | | `not_found` | 404 | | `internal_error` | 500 | ```bash theme={null} curl -X POST https://app.kalent.ai/api/v1/sourcings/SOURCING_ID/candidates \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{"talentId":"TALENT_ID"}' ``` # Start LinkedIn contact enrichment Source: https://docs.kalent.ai/api-reference/contact-enrich POST /v1/contact/enrich Start enrichment from a LinkedIn profile URL. Learn how to authenticate your API requests with your API key. Triggers asynchronous phone and/or email enrichment for the canonical LinkedIn profile URL. ### Request body Public LinkedIn profile URL for one person only. One of `phone`, `personalEmail`, or `all`. ### Response When `success` is `true`, `data` includes `talentId`, canonical `linkedinUrl`, and `enrichmentType`. ```bash theme={null} curl -X POST https://app.kalent.ai/api/v1/contact/enrich \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{"linkedinUrl":"https://www.linkedin.com/in/example","enrichmentType":"all"}' ``` ## Errors | Code | HTTP | | -------------------------- | ---- | | `validation_error` | 400 | | `missing_enrich_providers` | 400 | | `insufficient_credits` | 402 | | `not_found` | 404 | | `internal_error` | 500 | # Create sequence blueprint Source: https://docs.kalent.ai/api-reference/create-sequence-blueprint POST /v1/sourcings/{sourcingId}/sequence-blueprints Create a manual outreach sequence blueprint for a sourcing. Learn how to authenticate your API requests with your API key. Creates a manual sequence blueprint attached to a sourcing in your workspace. You can create it empty and add steps later, or provide the full `steps` array immediately. ## Path parameters The sourcing id returned by [Create sourcing](/api-reference/create-sourcing). ## Request body Display name for the sequence blueprint. Optional ordered outreach steps. See [Step format](#step-format). Optional sequence-level configuration. ## Step format Each item in `steps` must be an object with this shape: | Field | Type | Required | Description | | ---------------------------- | ------- | ----------------- | ------------------------------------------------------------------------------------------- | | `id` | string | Yes | Stable client-generated step id, unique within the blueprint. | | `name` | string | Yes | Human-readable step label. | | `type` | string | Yes | `LINKEDIN`, `WHATSAPP`, `EMAIL`, or `SMS`. | | `linkedInType` | string | Only for LinkedIn | Required when `type` is `LINKEDIN`; forbidden for other types. | | `content` | string | Yes | Message body. For `LINKEDIN_INVITATION`, content is accepted but stored as an empty string. | | `subject` | string | Email only | Email subject. | | `temporalityType` | string | Yes | `ASAP`, `DELAYED`, or `AFTER_INVITATION_SETTLED`. | | `delay` | object | For delayed steps | Required when `temporalityType` is `DELAYED`. | | `delay.value` | integer | With `delay` | Positive delay value. | | `delay.unit` | string | With `delay` | `day`, `minute`, or `second`. | | `contentSource` | string | No | `manual` or `suggestedByKalent`. | | `config.userId` | string | No | Optional sender/user override. | | `policy.skipStepIfNoContact` | boolean | No | Skip the step when the required contact is unavailable. | | `policy.autoValidateAIDraft` | boolean | No | Automatically validate AI-generated drafts. | | `policy.inviteTimeoutDays` | integer | No | Positive number of days before invite-related follow-up logic. | Allowed `linkedInType` values: * `LINKEDIN_INVITATION` * `LINKEDIN_INVITATION_WITH_MESSAGE` * `LINKEDIN_MESSAGE` * `LINKEDIN_INMAIL` Only one invite step is allowed per blueprint: either `LINKEDIN_INVITATION` or `LINKEDIN_INVITATION_WITH_MESSAGE`. ## Supported template variables Only these variables are supported in `content` and `subject`: | Variable | Meaning | | -------------------------- | --------------------------- | | `{{firstname}}` | Candidate first name | | `{{lastname}}` | Candidate last name | | `{{candidateJobTitle}}` | Candidate current job title | | `{{candidateCompanyName}}` | Candidate current company | | `{{candidateLocation}}` | Candidate location | | `{{sourcingJobTitle}}` | Sourcing target job title | | `{{sourcingLocation}}` | Sourcing target location | | `{{recruiterFirstname}}` | Recruiter first name | | `{{recruiterLastname}}` | Recruiter last name | Unknown variables such as `{{firstName}}`, `{{company}}`, or `{{job}}` are rejected with `validation_error`. Do not use unresolved bracket placeholders such as `[company]`, `[specific project or stack]`, `[one-pager link]`, or `[1 detail to add]` in `content` or `subject`. Replace them with real text, remove that part of the sentence, or ask the user for the missing detail before creating the blueprint. Payloads containing bracket placeholders are rejected with `validation_error`. ## Example with steps ```json theme={null} { "name": "First outreach sequence", "steps": [ { "id": "step-1", "name": "LinkedIn invite", "type": "LINKEDIN", "linkedInType": "LINKEDIN_INVITATION_WITH_MESSAGE", "content": "Hi {{firstname}}, I came across your profile and wanted to connect.", "temporalityType": "ASAP", "contentSource": "manual" }, { "id": "step-2", "name": "Email follow-up", "type": "EMAIL", "subject": "Quick follow-up", "content": "Hi {{firstname}}, would you be open to discussing the role?", "temporalityType": "DELAYED", "delay": { "value": 2, "unit": "day" }, "policy": { "skipStepIfNoContact": true } } ] } ``` ## Response When `success` is `true`, `data` contains: * `blueprintId` — identifier for the created sequence blueprint * `name` — blueprint display name * `sourcingId` — sourcing the blueprint belongs to ## Errors | Code | HTTP | | ------------------ | ---- | | `validation_error` | 400 | | `not_found` | 404 | | `internal_error` | 500 | ```bash theme={null} curl -X POST https://app.kalent.ai/api/v1/sourcings/SOURCING_ID/sequence-blueprints \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{"name":"First outreach sequence","steps":[{"id":"step-1","name":"LinkedIn invite","type":"LINKEDIN","linkedInType":"LINKEDIN_INVITATION_WITH_MESSAGE","content":"Hi {{firstname}}, I wanted to connect.","temporalityType":"ASAP"}]}' ``` # Create sourcing Source: https://docs.kalent.ai/api-reference/create-sourcing POST /v1/sourcings Create an empty sourcing campaign linked to your workspace. Learn how to authenticate your API requests with your API key. Creates a new sourcing (job offer) with default filters and a sourcing dashboard. The authenticated API key user becomes the sourcing owner. ## Request body Send an empty JSON object `{}`. Unknown keys return `validation_error`. ## Response When `success` is `true`, `data` contains: * `sourcingId` — identifier for the new sourcing (same as internal job offer id) * `name` — generated display name ## Errors Common `error.code` values: | Code | HTTP | | ----------------------- | ---- | | `validation_error` | 400 | | `plan_upgrade_required` | 402 | | `not_found` | 404 | | `internal_error` | 500 | When the workspace has no eligible Kalent subscription, the API returns: ```json theme={null} { "success": false, "error": { "code": "plan_upgrade_required", "message": "A Kalent subscription or plan upgrade is required to create sourcings. Please subscribe or upgrade your plan in Kalent billing: https://app.kalent.ai/settings/billing", "debugTrackingCode": "...", "details": { "code": "subscription_or_plan_upgrade_required", "action": "subscribe_or_upgrade", "capability": "create_sourcings", "billingUrl": "https://app.kalent.ai/settings/billing" } } } ``` ```bash theme={null} curl -X POST https://app.kalent.ai/api/v1/sourcings \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{}' ``` # Enrich candidate contacts Source: https://docs.kalent.ai/api-reference/enrich-candidate-contacts POST /v1/candidates/{candidateId}/enrich-contacts Start phone or email enrichment for the talent behind a candidate. Learn how to authenticate your API requests with your API key. Triggers asynchronous contact enrichment for the talent linked to the candidate. Your workspace must have enrichment providers configured. Credits apply unless the user is a super-admin. ## Path parameters Candidate id in your workspace. ## Request body One of `phone`, `personalEmail`, or `all`. ## Response When `success` is `true`, `data` includes `candidateId`, `talentId`, and `enrichmentType` (mirrors the request kind). ## Errors | Code | HTTP | | -------------------------- | ---- | | `validation_error` | 400 | | `missing_enrich_providers` | 400 | | `insufficient_credits` | 402 | | `not_found` | 404 | | `internal_error` | 500 | ```bash theme={null} curl -X POST https://app.kalent.ai/api/v1/candidates/CANDIDATE_ID/enrich-contacts \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{"enrichmentType":"all"}' ``` # Get candidate Source: https://docs.kalent.ai/api-reference/get-candidate GET /v1/candidates/{candidateId} Fetch a candidate with secured talent profile and contact loading flags. Learn how to authenticate your API requests with your API key. Returns candidate metadata plus a talent payload processed through the same contact-visibility rules as the product UI. Contact objects are not returned directly: visible emails are exposed as `talent.emails`, and visible sanitized phone numbers are exposed as `talent.phones`. While enrichment is pending, `talent.contactsLoading.email` and `talent.contactsLoading.phone` indicate loading state. ## Path parameters Candidate id belonging to the API key workspace. ## Response When `success` is `true`, `data` contains: * `candidate` — `id`, `statusHandle`, `createdAt`, `sourcingId`, `talentId` * `talent` — secured talent fields plus `emails`, `phones`, and `contactsLoading`. Internal LinkedIn/email/WhatsApp interaction flags are not returned. * `lastActivityAt`, `lastActivityDirection`, `lastActivityChannel`, `lastActivityContent`, `replyStatus` — latest outreach activity, see below The response does not expose internal `workspaceId` or `userId` fields. ### Outreach activity The same three fields as [List candidates](/api-reference/get-candidates#outreach-activity), computed with the same rules, so a candidate returns identical values from both endpoints. They aggregate every channel (LinkedIn, email, WhatsApp, SMS) across legacy and dynamic sequences, scoped to the outreach performed by the user who owns the API key (messages and invitations sent by other members of the workspace are not taken into account). | Field | Type | Description | | ----------------------- | ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `lastActivityAt` | ISO-8601 string \| `null` | Date of the most recent outreach event: message sent, LinkedIn invitation sent or accepted, or reply received. `null` when the candidate was never contacted. | | `lastActivityDirection` | `INBOUND` \| `OUTBOUND` \| `null` | `INBOUND` when the latest event comes from the talent (reply, accepted invitation), `OUTBOUND` when it comes from the API key owner (message or invitation sent). `null` when the candidate was never contacted. | | `lastActivityChannel` | `LINKEDIN` \| `WHATSAPP` \| `EMAIL` \| `SMS` \| `null` | Channel of that latest event. `null` when the candidate was never contacted. | | `lastActivityContent` | string \| `null` | Text of that latest event when it is a sent or received message. `null` when the candidate was never contacted, when the latest event is a LinkedIn invitation sent or accepted (Kalent does not store the invitation note), or when the message body is empty. Email subject is not included. | | `replyStatus` | `REPLIED` \| `NO_REPLY` \| `NOT_CONTACTED` | `REPLIED` as soon as at least one reply was received on any channel, `NO_REPLY` when you contacted the talent without any reply, `NOT_CONTACTED` when no outreach happened. | `replyStatus` stays `REPLIED` even if you sent a follow-up after the talent replied (`lastActivityDirection` then becomes `OUTBOUND`). An accepted LinkedIn invitation counts as an inbound activity but not as a reply. ```json theme={null} { "success": true, "data": { "candidate": { "id": "candidate_123", "statusHandle": "CONTACTED", "createdAt": "2026-05-19T09:00:00.000Z", "sourcingId": "sourcing_123", "talentId": "talent_123" }, "talent": { "id": "talent_123", "firstname": "Grace", "lastname": "Hopper", "emails": ["grace@example.com"], "phones": [], "contactsLoading": { "email": false, "phone": false } }, "lastActivityAt": "2026-05-21T14:32:00.000Z", "lastActivityDirection": "INBOUND", "lastActivityChannel": "LINKEDIN", "lastActivityContent": "Yes, I am interested.", "replyStatus": "REPLIED" } } ``` ## Errors | Code | HTTP | | ---------------- | ---- | | `not_found` | 404 | | `internal_error` | 500 | ```bash theme={null} curl -X GET https://app.kalent.ai/api/v1/candidates/CANDIDATE_ID \ -H "x-api-key: YOUR_API_KEY" ``` # Get candidate dynamic sequence status Source: https://docs.kalent.ai/api-reference/get-candidate-dynamic-sequence GET /v1/candidates/{candidateId}/dynamic-sequence Read the current or latest dynamic outreach sequence status for a candidate. Learn how to authenticate your API requests with your API key. Returns the latest dynamic sequence for the candidate in their sourcing, including high-level progress and whether user action is required. Use this endpoint after [Start sequence blueprint](/api-reference/start-sequence-blueprint) to poll outreach progress by `candidateId`. ## Path parameters Candidate id in your workspace. ## Response When `success` is `true`, `data` contains: * `candidateId`, `talentId`, `sourcingId` * `sequence` — `null` when no dynamic sequence exists for this candidate in the sourcing * When present, `sequence` includes `sequenceId`, `name`, `rawStatus`, `displayStatus`, step progress fields, and optional `actionRequired` `displayStatus` can be `SCHEDULED`, `ACTION_REQUIRED`, `ERROR`, `TALENT_REPLIED`, `WAITING_FOR_ACCEPTANCE`, `COMPLETED`, or `PAUSED`. `TALENT_REPLIED` only describes the dynamic sequence: it is set when a reply interrupts a **running** dynamic sequence, so it does not cover legacy sequences nor replies received once the sequence is no longer running. To know whether a talent replied at all, regardless of sequence type or timing, use `replyStatus` returned by [Get candidate](/api-reference/get-candidate#outreach-activity) and [List candidates](/api-reference/get-candidates#outreach-activity). ## Errors | Code | HTTP | | ---------------- | ---- | | `not_found` | 404 | | `internal_error` | 500 | ```bash theme={null} curl https://app.kalent.ai/api/v1/candidates/CANDIDATE_ID/dynamic-sequence \ -H "x-api-key: YOUR_API_KEY" ``` # List candidates Source: https://docs.kalent.ai/api-reference/get-candidates GET /v1/sourcings/{sourcingId}/candidates Returns paginated candidates for a sourcing. Learn how to authenticate your API requests with your API key. Returns candidates for a sourcing using the same pagination, filters, and ordering as the Kalent candidates table. ## Path parameters Sourcing id in the authenticated workspace. ## Query parameters Zero-based page index. Page size (max 50). Free-text search on candidate/talent fields. Repeatable status filter. When omitted, defaults to all statuses except `NOT_RETAINED`. Repeatable city filter. Repeatable job title filter. Repeatable current company filter. One of `name`, `companyName`, `statusHandle`, `city`, `jobTitle`, `userRating`, `contacts`, `createdAt`, `history`. `history` is not a true activity sort: the page is selected by talent creation date (`talent.createdAt`, in the requested `sortOrder`), then the candidates of that page are re-ordered in memory by their latest outreach activity (with `desc`, candidates without any activity come first, then the most recent activity first). To find who replied, read `replyStatus` on each item rather than relying on this sort. `asc` or `desc`. ## Response When `success` is `true`, `data` contains: * `candidates` — list of `{ candidate, talent, lastActivityAt, lastActivityDirection, lastActivityChannel, lastActivityContent, replyStatus }` summaries: * `candidate.id` * `candidate.statusHandle` * `candidate.createdAt` * `candidate.sourcingId` * `candidate.talentId` * `talent.id` * `talent.firstname` * `talent.lastname` * `talent.jobTitle` * `talent.city` * `talent.linkedinUrl` * `lastActivityAt`, `lastActivityDirection`, `lastActivityChannel`, `lastActivityContent`, `replyStatus` — see [Outreach activity](#outreach-activity) * `pagination` — `pageIndex`, `pageSize`, `total`, `hasNextPage` ### Outreach activity Each item carries the latest outreach activity between the talent and the user who owns the API key, aggregated across every channel (LinkedIn, email, WhatsApp, SMS) and across legacy and dynamic sequences. This is the same scope as the candidates table this user sees in Kalent: messages and invitations sent by other members of the workspace are not taken into account. | Field | Type | Description | | ----------------------- | ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `lastActivityAt` | ISO-8601 string \| `null` | Date of the most recent outreach event: message sent, LinkedIn invitation sent or accepted, or reply received. `null` when the candidate was never contacted. | | `lastActivityDirection` | `INBOUND` \| `OUTBOUND` \| `null` | `INBOUND` when the latest event comes from the talent (reply, accepted invitation), `OUTBOUND` when it comes from the API key owner (message or invitation sent). `null` when the candidate was never contacted. | | `lastActivityChannel` | `LINKEDIN` \| `WHATSAPP` \| `EMAIL` \| `SMS` \| `null` | Channel of that latest event. `null` when the candidate was never contacted. | | `lastActivityContent` | string \| `null` | Text of that latest event when it is a sent or received message. `null` when the candidate was never contacted, when the latest event is a LinkedIn invitation sent or accepted (Kalent does not store the invitation note), or when the message body is empty. Email subject is not included. | | `replyStatus` | `REPLIED` \| `NO_REPLY` \| `NOT_CONTACTED` | `REPLIED` as soon as at least one reply was received on any channel, `NO_REPLY` when you contacted the talent without any reply, `NOT_CONTACTED` when no outreach happened. | `replyStatus` stays `REPLIED` even if you sent a follow-up after the talent replied (`lastActivityDirection` then becomes `OUTBOUND`). An accepted LinkedIn invitation counts as an inbound activity but not as a reply. ```json theme={null} { "success": true, "data": { "candidates": [ { "candidate": { "id": "candidate_123", "statusHandle": "CONTACTED", "createdAt": "2026-05-19T09:00:00.000Z", "sourcingId": "sourcing_123", "talentId": "talent_123" }, "talent": { "id": "talent_123", "firstname": "Grace", "lastname": "Hopper", "jobTitle": "Software Engineer", "city": "Paris", "linkedinUrl": "https://www.linkedin.com/in/example" }, "lastActivityAt": "2026-05-21T14:32:00.000Z", "lastActivityDirection": "INBOUND", "lastActivityChannel": "LINKEDIN", "lastActivityContent": "Yes, I am interested.", "replyStatus": "REPLIED" } ], "pagination": { "pageIndex": 0, "pageSize": 25, "total": 1, "hasNextPage": false } } } ``` ## Errors | Code | HTTP | | ------------------ | ---- | | `validation_error` | 400 | | `not_found` | 404 | | `internal_error` | 500 | ```bash theme={null} curl -G "https://app.kalent.ai/api/v1/sourcings/SOURCING_ID/candidates" \ -H "x-api-key: YOUR_API_KEY" \ --data-urlencode "pageIndex=0" \ --data-urlencode "pageSize=25" ``` # Get LinkedIn contact enrichment result Source: https://docs.kalent.ai/api-reference/get-contact-enrichment-result GET /v1/contact/enrich Read contact enrichment results for a workspace talent. Learn how to authenticate your API requests with your API key. Returns **contact fields only** for a talent and your workspace: `emails`, `phones`, and `contactsLoading` (`email` / `phone` booleans). This uses the same visibility rules as in-app candidate views (only values that are unlocked for the requesting user are returned). ## Query parameters Talent id returned by the POST start call (or any known talent id for your workspace). ## Response When `success` is `true`, `data` includes `talentId`, `emails`, `phones`, and `contactsLoading`. ```bash theme={null} curl -G https://app.kalent.ai/api/v1/contact/enrich \ -H "x-api-key: YOUR_API_KEY" \ --data-urlencode "talentId=TALENT_ID" ``` ## Errors If no `EnrichTransaction` exists for the pair `(talentId, workspace)` (for example enrichment was never started for that workspace), the API returns HTTP `404` with `error.code` `not_found`. # List dynamic sequences Source: https://docs.kalent.ai/api-reference/get-dynamic-sequences GET /v1/sourcings/{sourcingId}/sequence-blueprints Returns paginated sequence blueprints (dynamic sequences) for a sourcing. Learn how to authenticate your API requests with your API key. Lists sequence blueprints attached to a sourcing. Use a returned `blueprintId` with [Start sequence blueprint](/api-reference/start-sequence-blueprint). ## Path parameters Sourcing id. ## Query parameters Zero-based page index. Page size (max 50). Optional case-insensitive filter on blueprint name. ## Response When `success` is `true`, `data` contains: * `sequenceBlueprints` — each item includes: * `blueprintId` * `name` * `sourcingId` * `createdAt` * `updatedAt` * `steps` * `stepCount` * `channels` — unique outreach types used in the blueprint (for example `LINKEDIN`, `EMAIL`) * `pagination` — `page`, `pageSize`, `total`, `hasNextPage` Returned steps do not expose internal `workspaceId` or `userId` fields. ```json theme={null} { "success": true, "data": { "sequenceBlueprints": [ { "blueprintId": "bp_123", "name": "LinkedIn outreach", "sourcingId": "sourcing_123", "createdAt": "2026-01-01T00:00:00.000Z", "updatedAt": "2026-01-02T00:00:00.000Z", "steps": [], "stepCount": 0, "channels": [] } ], "pagination": { "page": 0, "pageSize": 20, "total": 1, "hasNextPage": false } } } ``` ## Typical flow 1. [Create sourcing](/api-reference/create-sourcing) 2. [Add talent to sourcing](/api-reference/add-talent-to-sourcing) 3. **List dynamic sequences** (this endpoint) or [Create sequence blueprint](/api-reference/create-sequence-blueprint) 4. [Start sequence blueprint](/api-reference/start-sequence-blueprint) # List sourcings Source: https://docs.kalent.ai/api-reference/get-sourcings GET /v1/sourcings Returns paginated sourcings for the authenticated workspace. Learn how to authenticate your API requests with your API key. Returns sourcings visible to the API key user. For now, each item only includes the sourcing id and display name. ## Query parameters Zero-based page index. Page size (max 50). `mine` or `all`. `in-progress` or `closed`. ## Response When `success` is `true`, `data` contains: * `sourcings` — list of sourcing references: * `sourcingId` * `name` * `pagination` — `page`, `pageSize`, `total`, `hasNextPage` ```json theme={null} { "success": true, "data": { "sourcings": [ { "sourcingId": "sourcing_123", "name": "Senior Backend Engineer" } ], "pagination": { "page": 0, "pageSize": 8, "total": 1, "hasNextPage": false } } } ``` ## Errors | Code | HTTP | | ------------------ | ---- | | `validation_error` | 400 | | `internal_error` | 500 | ```bash theme={null} curl -G https://app.kalent.ai/api/v1/sourcings \ -H "x-api-key: YOUR_API_KEY" \ --data-urlencode "page=0" \ --data-urlencode "pageSize=8" \ --data-urlencode "ownership=mine" \ --data-urlencode "status=in-progress" ``` # Remove candidate from sourcing Source: https://docs.kalent.ai/api-reference/remove-candidate-from-sourcing DELETE /v1/candidates/{candidateId} Remove a candidate from a sourcing flow (sets status to NOT_RETAINED). Learn how to authenticate your API requests with your API key. Removes a candidate from a sourcing flow by setting its status to **`NOT_RETAINED`**. The candidate row is kept (not hard-deleted): it is excluded from the active pipeline and any active outreach tasks are cancelled. This is equivalent to calling [Update candidate status](/api-reference/update-candidate-status) with `statusHandle: "NOT_RETAINED"`. ## Path parameters The candidate id to remove. ## Response When `success` is `true`, `data` includes `candidateId`, `talentId`, `sourcingId`, `statusHandle` (`NOT_RETAINED`), `updatedAt`, and `removed: true`. ## Errors | Code | HTTP | | -------------------- | ---- | | `not_found` | 404 | | `method_not_allowed` | 405 | | `internal_error` | 500 | ```bash theme={null} curl -X DELETE https://app.kalent.ai/api/v1/candidates/CANDIDATE_ID \ -H "x-api-key: YOUR_API_KEY" ``` # Search talents Source: https://docs.kalent.ai/api-reference/search-talents POST /v1/search/talents Find professional profiles matching a set of filters. Learn how to authenticate your API requests with your API key. Search the Kalent talent database using a combination of filters. Each filter targets a specific attribute (job title, location, skill, etc.) and can be marked as required, excluded, or exact-match. Results are ranked by best overall match. Only **ready** profiles are returned: talents that are already up to date, or that were synchronously refreshed and passed refiltering. The search keeps fetching until `nbToFetch` ready profiles are found or an internal page/time budget is reached; when fewer profiles are delivered the response is still `200` and `resultCompleteness` says why. ## Request body Array of filter objects. Each object must include a `filterType` discriminator, a `value`, and modifier flags. Every filter accepts the following common fields: The filter discriminator. See [filter types](#filter-types) below. The search value. Accepted values depend on the `filterType`. When `true`, only talents matching this filter appear in results. Default to `true` unless the criterion is explicitly optional. When `true`, talents matching this filter are removed from results. When `true`, the value must match exactly with no fuzzy matching. Search radius in kilometers. Only applies to `LOCATION` filters. Temporal scope for the filter. One of `PAST`, `CURRENT`, or `CURRENT_AND_PAST`. Array of `searchTransactionId` values from previous search responses. When provided, talents that were returned in those previous searches are excluded from the current results. Use this field for **pagination**: after each search, collect the `searchTransactionId` from the response and include it (along with any earlier ones) in your next request to receive fresh, non-overlapping results. Maximum 300 IDs. See [Pagination](#pagination) below. How many ready talent profiles to return, between `1` and `10`. One credit is charged per profile actually returned. When fewer than `nbToFetch` ready profiles can be delivered, the response is still `200` and `resultCompleteness` explains why. ## Filter types Search by job title or role. | Field | Value | | ------------ | ------------------------------------------------------------ | | `filterType` | `JOB_TITLE` | | `value` | Free text — e.g. `"Product Designer"`, `"Software Engineer"` | ```json theme={null} { "filterType": "JOB_TITLE", "value": "Product Designer", "isRequired": true, "isExcluded": false, "isExactMatch": false, "history": "CURRENT" } ``` Search by geographic location. | Field | Value | | ------------ | --------------------------------------------------- | | `filterType` | `LOCATION` | | `value` | City or country — e.g. `"Paris"`, `"United States"` | | `radius` | Optional. Kilometers around the location. | ```json theme={null} { "filterType": "LOCATION", "value": "San Francisco", "isRequired": true, "isExcluded": false, "isExactMatch": false, "radius": 50 } ``` Search by years of professional experience. | Field | Value | | ------------ | ------------------------------------------------------------------------ | | `filterType` | `YEARS_OF_EXPERIENCE` | | `value` | One of: `0-1`, `1-3`, `3-5`, `5-10`, `10-15`, `15-20`, `20-30`, `30-100` | ```json theme={null} { "filterType": "YEARS_OF_EXPERIENCE", "value": "5-10", "isRequired": true, "isExcluded": false, "isExactMatch": false } ``` Search by technical or soft skill. | Field | Value | | ------------ | -------------------------------------------------- | | `filterType` | `SKILL` | | `value` | Free text — e.g. `"React"`, `"Project Management"` | ```json theme={null} { "filterType": "SKILL", "value": "React", "isRequired": true, "isExcluded": false, "isExactMatch": false } ``` Free-text search across the entire profile. | Field | Value | | ------------ | ---------------------------------------- | | `filterType` | `KEYWORD` | | `value` | Free text — e.g. `"Figma"`, `"B2B SaaS"` | ```json theme={null} { "filterType": "KEYWORD", "value": "B2B SaaS", "isRequired": false, "isExcluded": false, "isExactMatch": false } ``` Search by spoken language. | Field | Value | | ------------ | -------------------------------------------------------------------- | | `filterType` | `LANGUAGE` | | `value` | Lowercase language name — e.g. `"french"`, `"english"`, `"mandarin"` | Common values: `english`, `french`, `spanish`, `german`, `portuguese`, `mandarin`, `japanese`, `korean`, `arabic`, `hindi`, `italian`, `dutch`, `russian`, `turkish`, `polish`, `swedish`. ```json theme={null} { "filterType": "LANGUAGE", "value": "french", "isRequired": true, "isExcluded": false, "isExactMatch": false } ``` Search by language proficiency level. | Field | Value | | ------------ | ----------------------------------------------- | | `filterType` | `LANGUAGE_PROFICIENCY` | | `value` | Proficiency level or language+level combination | ```json theme={null} { "filterType": "LANGUAGE_PROFICIENCY", "value": "native", "isRequired": false, "isExcluded": false, "isExactMatch": false } ``` Search by current or past employer. | Field | Value | | ------------ | ----------------------------------------- | | `filterType` | `COMPANY_NAME` | | `value` | Free text — e.g. `"Google"`, `"McKinsey"` | ```json theme={null} { "filterType": "COMPANY_NAME", "value": "Google", "isRequired": false, "isExcluded": false, "isExactMatch": false, "history": "CURRENT_AND_PAST" } ``` Search by employer company size. | Field | Value | | ------------ | --------------------------------------------------------------------------------------------- | | `filterType` | `COMPANY_SIZE` | | `value` | One of: `1-10`, `11-50`, `51-200`, `201-500`, `501-1000`, `1001-5000`, `5001-10000`, `10001+` | ```json theme={null} { "filterType": "COMPANY_SIZE", "value": "51-200", "isRequired": false, "isExcluded": false, "isExactMatch": false } ``` Search by employer industry sector. | Field | Value | | ------------ | ------------------------------------------------------------------------------------------------ | | `filterType` | `COMPANY_INDUSTRY` | | `value` | Industry string — e.g. `"computer software"`, `"financial services"`, `"hospital & health care"` | See the full list of accepted industries in the [industry values reference](#industry-values). ```json theme={null} { "filterType": "COMPANY_INDUSTRY", "value": "computer software", "isRequired": false, "isExcluded": false, "isExactMatch": false } ``` Search by highest education level. | Field | Value | | ------------ | -------------------------------------------- | | `filterType` | `EDUCATION_DEGREE` | | `value` | One of: `bachelors`, `masters`, `doctorates` | ```json theme={null} { "filterType": "EDUCATION_DEGREE", "value": "masters", "isRequired": false, "isExcluded": false, "isExactMatch": false } ``` Search by school or university. | Field | Value | | ------------ | --------------------------------------- | | `filterType` | `EDUCATION_SCHOOL_NAME` | | `value` | Free text — e.g. `"MIT"`, `"HEC Paris"` | ```json theme={null} { "filterType": "EDUCATION_SCHOOL_NAME", "value": "HEC Paris", "isRequired": false, "isExcluded": false, "isExactMatch": false } ``` Search by professional certification. | Field | Value | | ------------ | ------------------------------------------- | | `filterType` | `CERTIFICATION_NAME` | | `value` | Free text — e.g. `"AWS Certified"`, `"PMP"` | ```json theme={null} { "filterType": "CERTIFICATION_NAME", "value": "AWS Certified", "isRequired": false, "isExcluded": false, "isExactMatch": false } ``` Search by contract type. | Field | Value | | ------------ | -------------------------- | | `filterType` | `SENIORITY` | | `value` | Currently only `Freelance` | ```json theme={null} { "filterType": "SENIORITY", "value": "Freelance", "isRequired": true, "isExcluded": false, "isExactMatch": false } ``` Search by tenure in current or last role. | Field | Value | | ------------ | ------------------------------------------------------------------------ | | `filterType` | `DURATION_IN_JOB` | | `value` | One of: `0-1`, `1-3`, `3-5`, `5-10`, `10-15`, `15-20`, `20-30`, `30-100` | ```json theme={null} { "filterType": "DURATION_IN_JOB", "value": "1-3", "isRequired": false, "isExcluded": false, "isExactMatch": false } ``` Search by graduation year. | Field | Value | | ------------ | ------------------------------- | | `filterType` | `GRADUATION_YEAR` | | `value` | Four-digit year — e.g. `"2020"` | ```json theme={null} { "filterType": "GRADUATION_YEAR", "value": "2020", "isRequired": false, "isExcluded": false, "isExactMatch": false } ``` Search by relationship to your connected LinkedIn account. | Field | Value | | -------------- | ------------------------------- | | `filterType` | `LINKEDIN_RELATIONS_DEGREE` | | `value` | One of: `1st degree`, `invited` | | `isExactMatch` | Always treated as `false` | `1st degree` means accepted first-degree LinkedIn connections only. `invited` means pending LinkedIn invitations only. These values are distinct: `1st degree` does **not** include pending invitations. The API resolves the LinkedIn account from your authenticated Kalent user. If no LinkedIn account is connected, the request returns `missing_connected_linkedin_account`. ```json theme={null} { "filterType": "LINKEDIN_RELATIONS_DEGREE", "value": "1st degree", "isRequired": true, "isExcluded": false, "isExactMatch": false } ``` ## Response Whether the request completed without errors. Present when `success` is `true`. Array of matching talent profiles, ordered by relevance. Up to 10 results per request. Unique talent identifier. First name. Last name. Gender. City of residence. Country of residence. Current job title. Professional summary. State or region. LinkedIn headline. URL to profile photo. LinkedIn profile URL. Current employer. Organization ID. Company name. Company logo URL. Work experience entries. Company name. Job title. Start date (ISO). End date (ISO), `null` if current. Role description. Whether this is the current role. Company logo URL. Company name. Education entries. Institution name. School logo URL. Education summary. Degrees obtained. Major fields of study. Minor fields of study. Start date. End date. School website domain. List of skills. List of interests. Spoken languages. Language name. Proficiency level. Professional certifications. Certification name. Issuing organization. Issue date. Expiry date. Estimated total number of talents matching the query across the entire database. Unique identifier for this search transaction. Pass this value in `relatedSearchTransactionIds` on subsequent requests to paginate through results. Also useful for support requests and debugging. Always present. Tells you whether the requested number of ready profiles could be delivered. `complete` when `returnedCount` equals `requestedCount`, `partial` otherwise. The `nbToFetch` value of the request. Number of profiles actually returned in `talents`. Present only when `status` is `partial`. One of `pool_exhausted` (no more profiles match these filters), `safety_budget_reached` (the search hit its internal page or time budget), `insufficient_ready_profiles` (the remaining matching profiles could not be refreshed), `insufficient_credits` (the workspace ran out of API search credits) or `search_interrupted` (the search failed mid-flight, the returned profiles are still valid). Error details, present when `success` is `false`. Machine-readable error code. One of: `validation_error`, `too_many_search_transactions`, `insufficient_api_credits`, `search_engine_timeout`, `missing_connected_linkedin_account`, `missing_api_key`, `invalid_api_key`, `rate_limit_exceeded`, `internal_error`. Human-readable error description. Unique tracking code for this request. Include it when contacting support. Zod validation details. Only present when `code` is `validation_error`. Rate limit details. Only present when `code` is `rate_limit_exceeded`. The time window that was exceeded. One of: `minute`, `hour`, `day`, `month`. Maximum number of requests allowed in this window. Number of requests already made in this window. ## Pagination The search API uses a **transaction-based pagination** model instead of traditional page numbers. ### How it works 1. **First request** — call the endpoint with your filters. The response includes a `searchTransactionId` and up to 10 matching talents. 2. **Next page** — send the **same filters** again, but add the previous `searchTransactionId` to the `relatedSearchTransactionIds` array. The API will exclude all talents that were already returned and give you the next batch. 3. **Subsequent pages** — keep accumulating `searchTransactionId` values in the array. Each new request excludes all talents from every prior transaction. The array accepts at most 300 IDs. ```bash theme={null} # Page 1 curl -X POST https://app.kalent.ai/api/v1/search/talents \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "filters": [ { "filterType": "JOB_TITLE", "value": "Software Engineer", "isRequired": true, "isExcluded": false, "isExactMatch": false } ] }' # → response includes "searchTransactionId": "tx_aaa111" # Page 2 curl -X POST https://app.kalent.ai/api/v1/search/talents \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "filters": [ { "filterType": "JOB_TITLE", "value": "Software Engineer", "isRequired": true, "isExcluded": false, "isExactMatch": false } ], "relatedSearchTransactionIds": ["tx_aaa111"] }' # → response includes "searchTransactionId": "tx_bbb222" # Page 3 curl -X POST https://app.kalent.ai/api/v1/search/talents \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "filters": [ { "filterType": "JOB_TITLE", "value": "Software Engineer", "isRequired": true, "isExcluded": false, "isExactMatch": false } ], "relatedSearchTransactionIds": ["tx_aaa111", "tx_bbb222"] }' ``` **Non-deterministic results** — Search results are not guaranteed to be identical across requests, even with the same filters. This is by design: * **Real-time profile refresh**: talent profiles are enriched and updated in real time during search. A profile that did not match a filter moments ago may match now (and vice versa) after a refresh. * **AI-powered scoring**: result ranking uses AI models whose outputs can vary slightly between calls. * **Database updates**: new talents are continuously indexed and existing profiles are updated from external sources. The `relatedSearchTransactionIds` mechanism guarantees that you will **not see the same talent twice** across paginated requests, but the total pool of matching talents may shift between calls. This is inherent to a live, AI-augmented search engine and does not affect result accuracy — every returned talent genuinely matches your filters at the time of the request. Read more in the [Non-deterministic Results](/non-deterministic-results) guide. ## Response examples ```bash Request theme={null} curl -X POST https://app.kalent.ai/api/v1/search/talents \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "filters": [ { "filterType": "JOB_TITLE", "value": "Software Engineer", "isRequired": true, "isExcluded": false, "isExactMatch": false }, { "filterType": "LOCATION", "value": "Paris", "isRequired": true, "isExcluded": false, "isExactMatch": false, "radius": 30 } ] }' ``` ```json Response theme={null} { "success": true, "data": { "talents": [ { "id": "clx1abc2d3e4f5g6h7i8j9k0", "firstname": "Marie", "lastname": "Dupont", "gender": "female", "city": "Paris", "country": "France", "jobTitle": "Senior Software Engineer", "summary": "Full-stack engineer with 8 years of experience...", "state": "Île-de-France", "headline": "Senior Software Engineer at Acme Corp", "photoUrl": "https://media.licdn.com/dms/image/...", "linkedinUrl": "https://www.linkedin.com/in/marie-dupont", "currentOrganization": { "id": "org_abc123", "name": "Acme Corp", "logoUrl": "https://media.licdn.com/dms/image/..." }, "experiences": [ { "organizationName": "Acme Corp", "title": "Senior Software Engineer", "startDate": "2021-03-01T00:00:00.000Z", "endDate": null, "description": "Leading the frontend team...", "isCurrent": true, "organization": { "logoUrl": "https://media.licdn.com/dms/image/...", "name": "Acme Corp" } } ], "educations": [ { "schoolName": "École Polytechnique", "schoolLogo": "https://media.licdn.com/dms/image/...", "summary": null, "degrees": ["Master of Science"], "majors": ["Computer Science"], "minors": [], "startDate": "2013-09-01T00:00:00.000Z", "endDate": "2017-06-30T00:00:00.000Z", "schoolDomain": "polytechnique.edu" } ], "skills": ["TypeScript", "React", "Node.js", "PostgreSQL"], "interests": ["Open Source", "Machine Learning"], "languages": [ { "name": "French", "proficiency": "native_or_bilingual" }, { "name": "English", "proficiency": "full_professional" } ], "certifications": [ { "name": "AWS Solutions Architect", "organizationName": "Amazon Web Services", "startDate": "2023-01-15T00:00:00.000Z", "endDate": "2026-01-15T00:00:00.000Z" } ] } ], "estimationCount": 2430, "searchTransactionId": "st_xyz789", "resultCompleteness": { "status": "complete", "requestedCount": 10, "returnedCount": 10 } } } ``` Returned when the request body does not match the expected schema. ```bash Request theme={null} curl -X POST https://app.kalent.ai/api/v1/search/talents \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "filters": [ { "filterType": "INVALID_TYPE", "value": "test", "isRequired": true, "isExcluded": false, "isExactMatch": false } ] }' ``` ```json Response theme={null} { "success": false, "error": { "code": "validation_error", "message": "Validation failed", "debugTrackingCode": "V1StGXR8_Z5jdHi6B-myT", "issues": [ { "code": "invalid_union_discriminator", "options": ["JOB_TITLE", "LOCATION", "YEARS_OF_EXPERIENCE"], "path": ["filters", 0, "filterType"], "message": "Invalid discriminator value" } ] } } ``` Returned when `relatedSearchTransactionIds` contains more than 300 entries. Refine your search filters instead of paginating further. ```bash Request theme={null} curl -X POST https://app.kalent.ai/api/v1/search/talents \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "filters": [ { "filterType": "JOB_TITLE", "value": "Software Engineer", "isRequired": true, "isExcluded": false, "isExactMatch": false } ], "relatedSearchTransactionIds": ["tx_1", "tx_2", "...", "tx_301"] }' ``` ```json Response theme={null} { "success": false, "error": { "code": "too_many_search_transactions", "message": "Too many relatedSearchTransactionIds provided (max 300). Please refine your search filters to get more relevant results instead of paginating further.", "debugTrackingCode": "V1StGXR8_Z5jdHi6B-myT" } } ``` Returned when the API key is missing or invalid. ```bash Request theme={null} curl -X POST https://app.kalent.ai/api/v1/search/talents \ -H "Content-Type: application/json" \ -d '{ "filters": [ { "filterType": "JOB_TITLE", "value": "Software Engineer", "isRequired": true, "isExcluded": false, "isExactMatch": false } ] }' ``` ```json Response (missing key) theme={null} { "success": false, "error": { "code": "missing_api_key", "message": "Missing API key", "debugTrackingCode": "V1StGXR8_Z5jdHi6B-myT" } } ``` ```json Response (invalid key) theme={null} { "success": false, "error": { "code": "invalid_api_key", "message": "Invalid API key", "debugTrackingCode": "V1StGXR8_Z5jdHi6B-myT" } } ``` Returned when you exceed the rate limit for your API key. The `details` object tells you which time window was hit, the maximum allowed, and how many requests you have already made. ```json Response theme={null} { "success": false, "error": { "code": "rate_limit_exceeded", "message": "Rate limit exceeded: too many requests per minute", "debugTrackingCode": "V1StGXR8_Z5jdHi6B-myT", "details": { "window": "minute", "limit": 10, "current": 10 } } } ``` Returned when the search engine request times out. Retry the same request; include the `debugTrackingCode` when contacting support. ```json Response theme={null} { "success": false, "error": { "code": "search_engine_timeout", "message": "The search request timed out. Please try again.", "debugTrackingCode": "V1StGXR8_Z5jdHi6B-myT" } } ``` Returned when an unexpected error occurs. Include the `debugTrackingCode` when contacting support. ```json Response theme={null} { "success": false, "error": { "code": "internal_error", "message": "An error occurred during search talents", "debugTrackingCode": "V1StGXR8_Z5jdHi6B-myT" } } ``` ## Industry values `information technology and services`, `government administration`, `retail`, `banking`, `construction`, `computer software`, `management consulting`, `real estate`, `hospital & health care`, `insurance`, `automotive`, `financial services`, `higher education`, `transportation/trucking/railroad`, `civic & social organization`, `environmental services`, `food production`, `aviation & aerospace`, `wholesale`, `mechanical or industrial engineering`, `telecommunications`, `research`, `pharmaceuticals`, `professional training & coaching`, `marketing and advertising`, `logistics and supply chain`, `hospitality`, `non-profit organization management`, `consumer goods`, `internet`, `renewables & environment`, `individual & family services`, `accounting`, `restaurants`, `defense & space`, `luxury goods & jewelry`, `machinery`, `chemicals`, `electrical/electronic manufacturing`, `human resources`, `sports`, `building materials`, `cosmetics`, `medical devices`, `oil & energy`, `staffing and recruiting`, `apparel & fashion`, `leisure, travel & tourism`, `architecture & planning`, `utilities`, `education management`, `health, wellness and fitness`, `farming`, `airlines/aviation`, `public policy`, `law practice`, `biotechnology`, `security and investigations`, `events services`, `food & beverages`, `industrial automation`, `broadcast media`, `facilities services`, `consumer services`, `medical practice`, `civil engineering`, `entertainment`, `wine and spirits`, `legal services`, `primary/secondary education`, `business supplies and equipment`, `media production`, `supermarkets`, `textiles`, `furniture`, `design`, `performing arts`, `publishing`, `sporting goods`, `semiconductors`, `newspapers`, `mining & metals`, `public relations and communications`, `packaging and containers`, `computer & network security`, `information services`, `international trade and development`, `music`, `e-learning`, `museums and institutions`, `computer games`, `plastics`, `printing`, `fine art`, `consumer electronics`, `mental health care`, `government relations`, `paper & forest products`, `public safety`, `online media`, `investment management`, `international affairs`, `recreational facilities and services`, `motion pictures and film`, `outsourcing/offshoring`, `maritime`, `military`, `graphic design`, `package/freight delivery`, `glass, ceramics & concrete`, `arts and crafts`, `market research`, `computer hardware`, `commercial real estate`, `venture capital & private equity`, `photography`, `shipbuilding`, `investment banking`, `railroad manufacture`, `gambling & casinos`, `veterinary`, `import and export`, `philanthropy`, `think tanks`, `translation and localization`, `animation`, `computer networking`, `political organization`, `warehousing`, `law enforcement`, `dairy`, `writing and editing`, `religious institutions`, `judiciary`, `ranching`, `libraries`, `nanotechnology`, `wireless`, `legislative office`, `program development`, `fishery`, `executive office`, `capital markets`, `alternative medicine`, `fund-raising`, `tobacco`, `alternative dispute resolution` # Search talents by prompt Source: https://docs.kalent.ai/api-reference/search-talents-by-prompt POST /v1/search/talents/by-prompt Find professional profiles from a natural-language description. Learn how to authenticate your API requests with your API key. Search the Kalent talent database with a plain-language description of who you are looking for. Instead of building a `filters` array yourself, you send a single `prompt` and the API converts it into structured filters automatically — the same behaviour as the Kalent MCP [`search_talents_by_prompt`](/mcp/search-talents-by-prompt) tool. This endpoint is the natural-language counterpart of [`POST /v1/search/talents`](/api-reference/search-talents). The success response is identical to that endpoint. ## Everything you mention is a requirement Each kind of criterion found in your prompt (job title, location, skill, language, years of experience, …) is treated as a requirement, with one rule: * **Across kinds of criteria — all of them are required.** A profile must satisfy every kind of criterion you mentioned. * **Within one kind of criterion — at least one value is enough.** When you give several values of the same kind, a profile only needs to match one of them, not all — unless the prompt explicitly says they are all mandatory (`"must master both Python and React"`), in which case each one is required. For example, `"Software engineers in Paris or Lyon who know Python and React"` returns profiles that are software engineers **and** based in Paris or Lyon **and** have Python **or** React — a profile with only Python still qualifies. To require both skills, say so explicitly in the prompt, or use [`POST /v1/search/talents`](/api-reference/search-talents) and set `isRequired: true` on each `SKILL` filter. Within the matching set, results are ordered by best overall match. This differs from [`POST /v1/search/talents`](/api-reference/search-talents), where you control each filter's `isRequired` flag: filters with `isRequired: false` are preferences — profiles that do not match them can still be returned, and matching profiles come first. If you need optional preferences, exclusions, exact matches, or a location radius that you control precisely, build the `filters` array yourself and call the filter-based endpoint. Keep the prompt to the criteria that are truly mandatory. An over-specified prompt can shrink the result pool quickly because every kind of criterion you mention must be satisfied. Only **ready** profiles are returned: talents that are already up to date, or that were synchronously refreshed and passed refiltering. The search keeps fetching until `nbToFetch` ready profiles are found or an internal page/time budget is reached; when fewer profiles are delivered the response is still `200` and `resultCompleteness` says why. ## Request body A natural-language description of the talent you are looking for — e.g. `"Senior React developer in Paris with 5+ years experience"`. The API interprets the prompt and derives the structured filters (job title, location, skill, years of experience, etc.) used to run the search. Every kind of criterion found in the prompt is required; when several values of the same kind are given, matching one of them is enough unless the prompt explicitly makes them all mandatory. If the prompt cannot be turned into any usable filter, the request returns `prompt_interpretation_failed`. Array of `searchTransactionId` values from previous search responses. When provided, talents that were returned in those previous searches are excluded from the current results. Use this field for **pagination**: after each search, collect the `searchTransactionId` from the response and include it (along with any earlier ones) in your next request to receive fresh, non-overlapping results. Maximum 300 IDs. See [Pagination](#pagination) below. How many ready talent profiles to return, between `1` and `10`. One credit is charged per profile actually returned. When fewer than `nbToFetch` ready profiles can be delivered, the response is still `200` and `resultCompleteness` explains why. ## Response Whether the request completed without errors. Present when `success` is `true`. Array of matching talent profiles, ordered by relevance. Up to 10 results per request. Unique talent identifier. First name. Last name. Gender. City of residence. Country of residence. Current job title. Professional summary. State or region. LinkedIn headline. URL to profile photo. LinkedIn profile URL. Current employer. Organization ID. Company name. Company logo URL. Work experience entries. Company name. Job title. Start date (ISO). End date (ISO), `null` if current. Role description. Whether this is the current role. Company logo URL. Company name. Education entries. Institution name. School logo URL. Education summary. Degrees obtained. Major fields of study. Minor fields of study. Start date. End date. School website domain. List of skills. List of interests. Spoken languages. Language name. Proficiency level. Professional certifications. Certification name. Issuing organization. Issue date. Expiry date. Estimated total number of talents matching the query across the entire database. Unique identifier for this search transaction. Pass this value in `relatedSearchTransactionIds` on subsequent requests to paginate through results. Also useful for support requests and debugging. Always present. Tells you whether the requested number of ready profiles could be delivered. `complete` when `returnedCount` equals `requestedCount`, `partial` otherwise. The `nbToFetch` value of the request. Number of profiles actually returned in `talents`. Present only when `status` is `partial`. One of `pool_exhausted` (no more profiles match these filters), `safety_budget_reached` (the search hit its internal page or time budget), `insufficient_ready_profiles` (the remaining matching profiles could not be refreshed), `insufficient_credits` (the workspace ran out of API search credits) or `search_interrupted` (the search failed mid-flight, the returned profiles are still valid). Error details, present when `success` is `false`. Machine-readable error code. One of: `validation_error`, `prompt_interpretation_failed`, `too_many_search_transactions`, `insufficient_api_credits`, `search_engine_timeout`, `missing_connected_linkedin_account`, `missing_api_key`, `invalid_api_key`, `rate_limit_exceeded`, `internal_error`. Human-readable error description. Unique tracking code for this request. Include it when contacting support. Zod validation details. Only present when `code` is `validation_error`. Rate limit details. Only present when `code` is `rate_limit_exceeded`. The time window that was exceeded. One of: `minute`, `hour`, `day`, `month`. Maximum number of requests allowed in this window. Number of requests already made in this window. ## Pagination Pagination works exactly like the filter-based endpoint: it uses a **transaction-based pagination** model instead of traditional page numbers. Send the **same `prompt`** again and accumulate previous `searchTransactionId` values in the `relatedSearchTransactionIds` array — each new request excludes all talents from every prior transaction so you never see the same talent twice. The array accepts at most 300 IDs. See the [Pagination section of `POST /v1/search/talents`](/api-reference/search-talents#pagination) for the full mechanism. ```bash theme={null} # Page 1 curl -X POST https://app.kalent.ai/api/v1/search/talents/by-prompt \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "prompt": "Senior React developer in Paris with 5+ years experience" }' # → response includes "searchTransactionId": "st_aaa111" # Page 2 curl -X POST https://app.kalent.ai/api/v1/search/talents/by-prompt \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "prompt": "Senior React developer in Paris with 5+ years experience", "relatedSearchTransactionIds": ["st_aaa111"] }' ``` **Non-deterministic results** — Search results are not guaranteed to be identical across requests, even with the same prompt. This is by design: * **Prompt interpretation**: the same prompt may be interpreted slightly differently between calls, which can shift the result pool. * **Real-time profile refresh**: talent profiles are enriched and updated in real time during search. A profile that did not match moments ago may match now (and vice versa) after a refresh. * **AI-powered scoring**: result ranking uses AI models whose outputs can vary slightly between calls. * **Database updates**: new talents are continuously indexed and existing profiles are updated from external sources. The `relatedSearchTransactionIds` mechanism guarantees that you will **not see the same talent twice** across paginated requests, but the total pool of matching talents may shift between calls. This is inherent to a live, AI-augmented search engine and does not affect result accuracy. Read more in the [Non-deterministic Results](/non-deterministic-results) guide. ## Response examples ```bash Request theme={null} curl -X POST https://app.kalent.ai/api/v1/search/talents/by-prompt \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "prompt": "Senior React developer in Paris with 5+ years experience" }' ``` ```json Response theme={null} { "success": true, "data": { "talents": [ { "id": "clx1abc2d3e4f5g6h7i8j9k0", "firstname": "Marie", "lastname": "Dupont", "gender": "female", "city": "Paris", "country": "France", "jobTitle": "Senior Software Engineer", "summary": "Full-stack engineer with 8 years of experience...", "state": "Île-de-France", "headline": "Senior Software Engineer at Acme Corp", "photoUrl": "https://media.licdn.com/dms/image/...", "linkedinUrl": "https://www.linkedin.com/in/marie-dupont", "currentOrganization": { "id": "org_abc123", "name": "Acme Corp", "logoUrl": "https://media.licdn.com/dms/image/..." }, "experiences": [ { "organizationName": "Acme Corp", "title": "Senior Software Engineer", "startDate": "2021-03-01T00:00:00.000Z", "endDate": null, "description": "Leading the frontend team...", "isCurrent": true, "organization": { "logoUrl": "https://media.licdn.com/dms/image/...", "name": "Acme Corp" } } ], "educations": [ { "schoolName": "École Polytechnique", "schoolLogo": "https://media.licdn.com/dms/image/...", "summary": null, "degrees": ["Master of Science"], "majors": ["Computer Science"], "minors": [], "startDate": "2013-09-01T00:00:00.000Z", "endDate": "2017-06-30T00:00:00.000Z", "schoolDomain": "polytechnique.edu" } ], "skills": ["TypeScript", "React", "Node.js", "PostgreSQL"], "interests": ["Open Source", "Machine Learning"], "languages": [ { "name": "French", "proficiency": "native_or_bilingual" }, { "name": "English", "proficiency": "full_professional" } ], "certifications": [ { "name": "AWS Solutions Architect", "organizationName": "Amazon Web Services", "startDate": "2023-01-15T00:00:00.000Z", "endDate": "2026-01-15T00:00:00.000Z" } ] } ], "estimationCount": 2430, "searchTransactionId": "st_xyz789", "resultCompleteness": { "status": "complete", "requestedCount": 10, "returnedCount": 10 } } } ``` Returned when the prompt could not be turned into any usable filter. Rephrase your prompt with more concrete search criteria (a role, a location, a skill, etc.). ```bash Request theme={null} curl -X POST https://app.kalent.ai/api/v1/search/talents/by-prompt \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "prompt": "someone good" }' ``` ```json Response theme={null} { "success": false, "error": { "code": "prompt_interpretation_failed", "message": "Could not interpret the prompt into any usable search filter. Please rephrase with more concrete criteria such as a job title, location, or skill.", "debugTrackingCode": "V1StGXR8_Z5jdHi6B-myT" } } ``` Returned when the request body does not match the expected schema — for example when `prompt` is missing or empty. ```bash Request theme={null} curl -X POST https://app.kalent.ai/api/v1/search/talents/by-prompt \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{}' ``` ```json Response theme={null} { "success": false, "error": { "code": "validation_error", "message": "Validation failed", "debugTrackingCode": "V1StGXR8_Z5jdHi6B-myT", "issues": [ { "code": "invalid_type", "expected": "string", "received": "undefined", "path": ["prompt"], "message": "Required" } ] } } ``` Returned when the API key is missing or invalid. ```bash Request theme={null} curl -X POST https://app.kalent.ai/api/v1/search/talents/by-prompt \ -H "Content-Type: application/json" \ -d '{ "prompt": "Senior React developer in Paris with 5+ years experience" }' ``` ```json Response (missing key) theme={null} { "success": false, "error": { "code": "missing_api_key", "message": "Missing API key", "debugTrackingCode": "V1StGXR8_Z5jdHi6B-myT" } } ``` ```json Response (invalid key) theme={null} { "success": false, "error": { "code": "invalid_api_key", "message": "Invalid API key", "debugTrackingCode": "V1StGXR8_Z5jdHi6B-myT" } } ``` Returned when your account does not have enough API credits to run the search. Top up your credits and retry. ```json Response theme={null} { "success": false, "error": { "code": "insufficient_api_credits", "message": "Insufficient API credits to perform this search.", "debugTrackingCode": "V1StGXR8_Z5jdHi6B-myT" } } ``` # Start sequence blueprint Source: https://docs.kalent.ai/api-reference/start-sequence-blueprint POST /v1/sequence-blueprints/{blueprintId}/start Start dynamic outreach sequences from a blueprint for candidates. Learn how to authenticate your API requests with your API key. Starts a dynamic outreach sequence workflow for the given blueprint and candidate ids. The blueprint must belong to a sourcing in your workspace and should contain valid steps created with [Create sequence blueprint](/api-reference/create-sequence-blueprint) or [Update sequence blueprint](/api-reference/update-sequence-blueprint). ## Path parameters Sequence blueprint id to start. ## Request body Candidate ids to enroll in the sequence. The array must contain at least one id. ## Response When `success` is `true`, `data` contains: * `started` — whether the workflow was dispatched * `blueprintId` — started blueprint id * `candidateCount` — number of candidates requested Poll progress with [Get candidate dynamic sequence status](/api-reference/get-candidate-dynamic-sequence) using each `candidateId`. ## Prerequisites * The blueprint must have at least one valid step. * Every `candidateId` must belong to the same sourcing as the blueprint. * Required outreach channels must be connected and synced in Kalent. * LinkedIn steps require a connected LinkedIn account. * WhatsApp steps require a connected WhatsApp account. * Email steps require a connected Gmail, Outlook, or IMAP account. ## Errors | Code | HTTP | | ----------------------- | ---- | | `validation_error` | 400 | | `forbidden` | 403 | | `plan_upgrade_required` | 402 | | `not_found` | 404 | | `temporal_unavailable` | 503 | | `internal_error` | 500 | For `forbidden` errors caused by missing or disconnected outreach accounts, `error.details.code` indicates the blocked provider, such as `linkedin_not_connected_or_syncing`, `whatsapp_not_connected_or_syncing`, or `email_not_connected_or_syncing`. When the workspace plan does not include outreach integrations, the API returns `plan_upgrade_required` with billing remediation details: ```json theme={null} { "success": false, "error": { "code": "plan_upgrade_required", "message": "A Kalent subscription or plan upgrade is required to use outreach integrations. Please subscribe or upgrade your plan in Kalent billing: https://app.kalent.ai/settings/billing", "debugTrackingCode": "...", "details": { "code": "subscription_or_plan_upgrade_required", "action": "subscribe_or_upgrade", "capability": "use_outreach_integrations", "billingUrl": "https://app.kalent.ai/settings/billing" } } } ``` ```json theme={null} { "success": false, "error": { "code": "forbidden", "message": "LinkedIn account is not connected or not synced", "debugTrackingCode": "...", "details": { "code": "linkedin_not_connected_or_syncing" } } } ``` ```bash theme={null} curl -X POST https://app.kalent.ai/api/v1/sequence-blueprints/BLUEPRINT_ID/start \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{"candidateIds":["CANDIDATE_ID"]}' ``` # Update candidate status Source: https://docs.kalent.ai/api-reference/update-candidate-status PATCH /v1/candidates/{candidateId}/status Change the status of a candidate in a sourcing. Learn how to authenticate your API requests with your API key. Updates the status of a candidate in your workspace. The `statusHandle` must be a global status (`TO_BE_CONTACTED`, `CONTACTED`, `NOT_RETAINED`, `OUTREACH_SEQUENCE_STARTED`) or a custom status defined in your workspace. Setting the status to `NOT_RETAINED` removes the candidate from the active pipeline and cancels any active outreach tasks. ## Path parameters The candidate id (from [Get candidates](/api-reference/get-candidates) or [Add talent to sourcing](/api-reference/add-talent-to-sourcing)). ## Request body The target status handle. ## Response When `success` is `true`, `data` includes `candidateId`, `talentId`, `sourcingId`, `statusHandle`, and `updatedAt`. ## Errors | Code | HTTP | | -------------------- | ---- | | `validation_error` | 400 | | `not_found` | 404 | | `method_not_allowed` | 405 | | `internal_error` | 500 | ```bash theme={null} curl -X PATCH https://app.kalent.ai/api/v1/candidates/CANDIDATE_ID/status \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{"statusHandle":"CONTACTED"}' ``` # Update sequence blueprint Source: https://docs.kalent.ai/api-reference/update-sequence-blueprint PATCH /v1/sequence-blueprints/{blueprintId} Update a sequence blueprint name, steps, or configuration. Learn how to authenticate your API requests with your API key. Updates a sequence blueprint in your workspace. Send at least one of `name`, `steps`, or `config`. ## Path parameters Sequence blueprint id returned by [Create sequence blueprint](/api-reference/create-sequence-blueprint). ## Request body New display name for the blueprint. Ordered outreach steps. Sending `steps` replaces the full blueprint step list. See [Step format](#step-format). Optional sequence-level configuration. ## Step shape Each item in `steps` must be an object with this shape: | Field | Type | Required | Description | | ---------------------------- | ------- | ----------------- | ------------------------------------------------------------------------------------------- | | `id` | string | Yes | Stable client-generated step id, unique within the blueprint. | | `name` | string | Yes | Human-readable step label. | | `type` | string | Yes | `LINKEDIN`, `WHATSAPP`, `EMAIL`, or `SMS`. | | `linkedInType` | string | Only for LinkedIn | Required when `type` is `LINKEDIN`; forbidden for other types. | | `content` | string | Yes | Message body. For `LINKEDIN_INVITATION`, content is accepted but stored as an empty string. | | `subject` | string | Email only | Email subject. | | `temporalityType` | string | Yes | `ASAP`, `DELAYED`, or `AFTER_INVITATION_SETTLED`. | | `delay` | object | For delayed steps | Required when `temporalityType` is `DELAYED`. | | `delay.value` | integer | With `delay` | Positive delay value. | | `delay.unit` | string | With `delay` | `day`, `minute`, or `second`. | | `contentSource` | string | No | `manual` or `suggestedByKalent`. | | `config.userId` | string | No | Optional sender/user override. | | `policy.skipStepIfNoContact` | boolean | No | Skip the step when the required contact is unavailable. | | `policy.autoValidateAIDraft` | boolean | No | Automatically validate AI-generated drafts. | | `policy.inviteTimeoutDays` | integer | No | Positive number of days before invite-related follow-up logic. | Allowed `linkedInType` values: * `LINKEDIN_INVITATION` * `LINKEDIN_INVITATION_WITH_MESSAGE` * `LINKEDIN_MESSAGE` * `LINKEDIN_INMAIL` Only one invite step is allowed per blueprint: either `LINKEDIN_INVITATION` or `LINKEDIN_INVITATION_WITH_MESSAGE`. ## Supported template variables Only these variables are supported in `content` and `subject`: | Variable | Meaning | | -------------------------- | --------------------------- | | `{{firstname}}` | Candidate first name | | `{{lastname}}` | Candidate last name | | `{{candidateJobTitle}}` | Candidate current job title | | `{{candidateCompanyName}}` | Candidate current company | | `{{candidateLocation}}` | Candidate location | | `{{sourcingJobTitle}}` | Sourcing target job title | | `{{sourcingLocation}}` | Sourcing target location | | `{{recruiterFirstname}}` | Recruiter first name | | `{{recruiterLastname}}` | Recruiter last name | Unknown variables such as `{{firstName}}`, `{{company}}`, or `{{job}}` are rejected with `validation_error`. Do not use unresolved bracket placeholders such as `[company]`, `[specific project or stack]`, `[one-pager link]`, or `[1 detail to add]` in `content` or `subject`. Replace them with real text, remove that part of the sentence, or ask the user for the missing detail before updating the blueprint. Payloads containing bracket placeholders are rejected with `validation_error`. ## Validation errors Invalid payloads return the standard error envelope: ```json theme={null} { "success": false, "error": { "code": "validation_error", "message": "Validation failed", "debugTrackingCode": "...", "issues": [ { "path": ["steps", 0, "linkedInType"], "message": "linkedInType is required when type is LINKEDIN" } ] } } ``` ## Example ```json theme={null} { "steps": [ { "id": "step-1", "name": "LinkedIn invite", "type": "LINKEDIN", "linkedInType": "LINKEDIN_INVITATION_WITH_MESSAGE", "content": "Hi {{firstname}}, I came across your profile and wanted to connect.", "temporalityType": "ASAP", "contentSource": "manual" }, { "id": "step-2", "name": "Email follow-up", "type": "EMAIL", "subject": "Quick follow-up", "content": "Hi {{firstname}}, would you be open to discussing the role?", "temporalityType": "DELAYED", "delay": { "value": 2, "unit": "day" }, "policy": { "skipStepIfNoContact": true } } ] } ``` ## Response When `success` is `true`, `data` contains `blueprintId`, `name`, and `sourcingId`. ## Errors | Code | HTTP | | ------------------ | ---- | | `validation_error` | 400 | | `not_found` | 404 | | `internal_error` | 500 | ```bash theme={null} curl -X PATCH https://app.kalent.ai/api/v1/sequence-blueprints/BLUEPRINT_ID \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{"steps":[{"id":"step-1","name":"LinkedIn invite","type":"LINKEDIN","linkedInType":"LINKEDIN_INVITATION_WITH_MESSAGE","content":"Hi {{firstname}}, I wanted to connect.","temporalityType":"ASAP"}]}' ``` # Authentication Source: https://docs.kalent.ai/authentication Authenticate your requests with an API key. Every request to the Kalent API must include your workspace API key. You can pass it in one of two ways: ```bash theme={null} curl -X POST https://app.kalent.ai/api/v1/search/talents \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"filters": []}' ``` ```bash theme={null} curl -X POST https://app.kalent.ai/api/v1/search/talents \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"filters": []}' ``` ## Get your API key 1. Log in to the [Kalent dashboard](https://app.kalent.ai). 2. Navigate to your workspace settings. 3. Open the **API Keys** section. 4. Click **Create API Key** and copy the generated secret. Store your API key securely. Do not commit it to version control or expose it in client-side code. ## Error responses All REST API errors use the same envelope: ```json theme={null} { "success": false, "error": { "code": "error_code", "message": "Human-readable message", "debugTrackingCode": "..." } } ``` | Status | `error.code` | Meaning | | ------ | ----------------------- | ------------------------------------------------------------------------------------------------- | | `401` | `missing_api_key` | No key was provided in the request. | | `401` | `invalid_api_key` | The key does not match any active workspace key, or the OAuth access token is invalid or expired. | | `402` | `plan_upgrade_required` | The workspace must subscribe to Kalent or upgrade its plan before using this API capability. | | `429` | `rate_limit_exceeded` | IP-level or API-key-level rate limit exceeded. | Subscription and plan errors include an actionable `details` object: ```json theme={null} { "success": false, "error": { "code": "plan_upgrade_required", "message": "A Kalent subscription or plan upgrade is required to create sourcings. Please subscribe or upgrade your plan in Kalent billing: https://app.kalent.ai/settings/billing", "debugTrackingCode": "...", "details": { "code": "subscription_or_plan_upgrade_required", "action": "subscribe_or_upgrade", "capability": "create_sourcings", "billingUrl": "https://app.kalent.ai/settings/billing" } } } ``` # API Overview Source: https://docs.kalent.ai/introduction Search millions of professional profiles with the Kalent API. The Kalent API gives you programmatic access to a talent search engine powered by real-time professional data. Build custom sourcing workflows, integrate talent discovery into your ATS, or automate candidate pipelines — all from a single endpoint. ## What you can do Find professionals matching complex criteria: job title, location, skills, experience, education, and more. Stack up to 17 filter types with required/optional, include/exclude, and exact-match modifiers for precise results. Understand why search results may vary between identical requests and how to work with this behavior. ## Base URL All API requests are made to: ``` https://app.kalent.ai/api ``` ## Quick example ```bash theme={null} curl -X POST https://app.kalent.ai/api/v1/search/talents \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "filters": [ { "filterType": "JOB_TITLE", "value": "Software Engineer", "isRequired": true, "isExcluded": false, "isExactMatch": false }, { "filterType": "LOCATION", "value": "Paris", "isRequired": true, "isExcluded": false, "isExactMatch": false, "radius": 30 } ] }' ``` ## Rate limits Each API key has configurable rate limits across four time windows: | Window | Default | | ---------- | -------------- | | Per minute | Varies by plan | | Per hour | Varies by plan | | Per day | Varies by plan | | Per month | Varies by plan | When a limit is exceeded the API returns `429 Too Many Requests` with a description of which window was hit. # add_talent_to_sourcing Source: https://docs.kalent.ai/mcp/add-talent-to-sourcing Attach a talent to a sourcing and ensure candidate status TO_BE_CONTACTED. Ensures there is a candidate row for the talent in the given sourcing. ## Input | Parameter | Type | Required | Description | | ------------ | ------ | -------- | ----------------------------------- | | `sourcingId` | string | Yes | Sourcing id from `create_sourcing`. | | `talentId` | string | Yes | Talent to attach. | ## Output JSON block with `success: true` and candidate identifiers plus status handle. ## Errors `session not found`, `Not found`, or credit-related messages when creation is blocked. # create_sequence_blueprint Source: https://docs.kalent.ai/mcp/create-sequence-blueprint Create a manual outreach sequence blueprint for a sourcing. Creates a sequence blueprint tied to a sourcing in the authenticated MCP workspace. You can create an empty blueprint or provide the full `steps` array immediately. ## Input | Parameter | Type | Required | Description | | ------------ | ------ | -------- | -------------------------------------------------------- | | `sourcingId` | string | Yes | Sourcing id from `create_sourcing`. | | `name` | string | Yes | Display name for the blueprint. | | `steps` | array | No | Ordered outreach steps. See [Step format](#step-format). | | `config` | object | No | Optional sequence-level configuration. | ## Step format Each item in `steps` must include: | Field | Type | Required | Description | | ---------------------------- | ------------------------------------------------- | ----------------- | ---------------------------------------------------------- | | `id` | string | Yes | Stable step id, unique within the blueprint. | | `name` | string | Yes | Step label. | | `type` | `LINKEDIN` \| `WHATSAPP` \| `EMAIL` \| `SMS` | Yes | Outreach channel. | | `linkedInType` | string | For LinkedIn | Required when `type` is `LINKEDIN`; not allowed otherwise. | | `content` | string | Yes | Message body. | | `subject` | string | Email only | Email subject. | | `temporalityType` | `ASAP` \| `DELAYED` \| `AFTER_INVITATION_SETTLED` | Yes | Scheduling rule. | | `delay` | object | For delayed steps | Required when `temporalityType` is `DELAYED`. | | `delay.value` | integer | With `delay` | Positive delay value. | | `delay.unit` | `day` \| `minute` \| `second` | With `delay` | Delay unit. | | `contentSource` | `manual` \| `suggestedByKalent` | No | Message origin. | | `policy.skipStepIfNoContact` | boolean | No | Skip if the required contact is missing. | Allowed `linkedInType` values are `LINKEDIN_INVITATION`, `LINKEDIN_INVITATION_WITH_MESSAGE`, `LINKEDIN_MESSAGE`, and `LINKEDIN_INMAIL`. Only one invite step is allowed per blueprint. ## Supported template variables Only these variables are supported in `content` and `subject`: | Variable | Meaning | | -------------------------- | --------------------------- | | `{{firstname}}` | Candidate first name | | `{{lastname}}` | Candidate last name | | `{{candidateJobTitle}}` | Candidate current job title | | `{{candidateCompanyName}}` | Candidate current company | | `{{candidateLocation}}` | Candidate location | | `{{sourcingJobTitle}}` | Sourcing target job title | | `{{sourcingLocation}}` | Sourcing target location | | `{{recruiterFirstname}}` | Recruiter first name | | `{{recruiterLastname}}` | Recruiter last name | Do not invent variables. Unknown variables such as `{{firstName}}`, `{{company}}`, or `{{job}}` are rejected. Do not use unresolved bracket placeholders such as `[company]`, `[specific project or stack]`, `[one-pager link]`, or `[1 detail to add]` in `content` or `subject`. Replace them with real text, remove that part of the sentence, or ask the user for the missing detail before calling `create_sequence_blueprint`. ## Example input ```json theme={null} { "sourcingId": "SOURCING_ID", "name": "First outreach sequence", "steps": [ { "id": "step-1", "name": "LinkedIn invite", "type": "LINKEDIN", "linkedInType": "LINKEDIN_INVITATION_WITH_MESSAGE", "content": "Hi {{firstname}}, I came across your profile and wanted to connect.", "temporalityType": "ASAP", "contentSource": "manual" }, { "id": "step-2", "name": "Email follow-up", "type": "EMAIL", "subject": "Quick follow-up", "content": "Hi {{firstname}}, would you be open to discussing the role?", "temporalityType": "DELAYED", "delay": { "value": 2, "unit": "day" }, "policy": { "skipStepIfNoContact": true } } ] } ``` ## Output JSON block with `success: true` and `data`: `blueprintId`, `name`, and `sourcingId`. ## Errors `session not found`, `Not found`, or a human-readable validation message when the sourcing cannot be used. # create_sourcing Source: https://docs.kalent.ai/mcp/create-sourcing Create an empty sourcing campaign in the authenticated workspace. Creates a new sourcing tied to your MCP session workspace and returns its identifier and name. ## Input No parameters. ## Output JSON block with `success: true` and `data`: `sourcingId`, `name`. ## Errors `session not found` when the MCP session is missing. When the workspace has no eligible Kalent subscription, the tool returns an error explaining that the user must subscribe or upgrade before retrying: ```text theme={null} A Kalent subscription or plan upgrade is required to create sourcings. Ask the user to subscribe or upgrade their Kalent plan before retrying. They can manage billing here: https://app.kalent.ai/settings/billing ``` Other human-readable messages may be returned for plan limits or insufficient workspace configuration. # enrich_candidate_contacts Source: https://docs.kalent.ai/mcp/enrich-candidate-contacts Trigger asynchronous phone or email enrichment for a candidate talent. ## Input | Parameter | Type | Required | Description | | ---------------- | ------ | -------- | ----------------------------------- | | `candidateId` | string | Yes | Candidate in your workspace. | | `enrichmentType` | string | Yes | `phone`, `personalEmail`, or `all`. | ## Output JSON block confirming dispatch with `candidateId`, `talentId`, and `enrichmentType`. ## Errors Messages when enrichment providers are missing, credits are insufficient, or the candidate cannot be resolved. # enrich_linkedin_contacts Source: https://docs.kalent.ai/mcp/enrich-linkedin-contacts Start phone or email enrichment from a LinkedIn profile URL (creates a sparse internal talent when needed). ## Input | Parameter | Type | Required | Description | | ---------------- | ------ | -------- | ----------------------------------- | | `linkedinUrl` | string | Yes | LinkedIn profile URL. | | `enrichmentType` | string | Yes | `phone`, `personalEmail`, or `all`. | ## Output JSON block with `talentId`, canonical `linkedinUrl`, and `enrichmentType` when dispatch succeeds. ## Errors Validation for invalid LinkedIn URLs, missing enrichment providers, insufficient credits, or workspace/user resolution failures. # get_candidate Source: https://docs.kalent.ai/mcp/get-candidate Load candidate metadata with secured talent data and contact loading flags. ## Input | Parameter | Type | Required | Description | | ------------- | ------ | -------- | ---------------------- | | `candidateId` | string | Yes | Candidate id to fetch. | ## Output JSON block with `success: true` and nested `candidate` plus secured `talent`. Contact objects are not returned directly: visible emails are exposed as `talent.emails`, and visible sanitized phone numbers are exposed as `talent.phones`. The response also includes `contactsLoading` for email and phone while enrichment jobs run. The candidate object exposes `sourcingId` for the sourcing that owns the candidate. The talent object does not include internal LinkedIn/email/WhatsApp interaction flags. The response does not expose internal `workspaceId` or `userId` fields. ### Outreach activity `data` also carries `lastActivityAt`, `lastActivityDirection`, `lastActivityChannel`, `lastActivityContent` and `replyStatus`: the latest outreach activity between the talent and the user who owns the API key, aggregated across every channel (LinkedIn, email, WhatsApp, SMS) and across legacy and dynamic sequences. Messages and invitations sent by other members of the workspace are not taken into account. They are computed with the same rules as `get_candidates`, so a candidate returns identical values from both tools. | Field | Type | Description | | ----------------------- | ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `lastActivityAt` | ISO-8601 string \| `null` | Date of the most recent outreach event: message sent, LinkedIn invitation sent or accepted, or reply received. `null` when the candidate was never contacted. | | `lastActivityDirection` | `INBOUND` \| `OUTBOUND` \| `null` | `INBOUND` when the latest event comes from the talent (reply, accepted invitation), `OUTBOUND` when it comes from the API key owner (message or invitation sent). `null` when the candidate was never contacted. | | `lastActivityChannel` | `LINKEDIN` \| `WHATSAPP` \| `EMAIL` \| `SMS` \| `null` | Channel of that latest event. `null` when the candidate was never contacted. | | `lastActivityContent` | string \| `null` | Text of that latest event when it is a sent or received message. `null` when the candidate was never contacted, when the latest event is a LinkedIn invitation sent or accepted (Kalent does not store the invitation note), or when the message body is empty. Email subject is not included. | | `replyStatus` | `REPLIED` \| `NO_REPLY` \| `NOT_CONTACTED` | `REPLIED` as soon as at least one reply was received on any channel, `NO_REPLY` when the talent was contacted without any reply, `NOT_CONTACTED` when no outreach happened. | `replyStatus` stays `REPLIED` even if a follow-up was sent after the talent replied (`lastActivityDirection` then becomes `OUTBOUND`). An accepted LinkedIn invitation counts as an inbound activity but not as a reply. ```json theme={null} { "success": true, "data": { "candidate": { "id": "candidate_123", "statusHandle": "CONTACTED", "createdAt": "2026-05-19T09:00:00.000Z", "sourcingId": "sourcing_123", "talentId": "talent_123" }, "talent": { "id": "talent_123", "firstname": "Grace", "lastname": "Hopper", "emails": ["grace@example.com"], "phones": [], "contactsLoading": { "email": false, "phone": false } }, "lastActivityAt": "2026-05-21T14:32:00.000Z", "lastActivityDirection": "INBOUND", "lastActivityChannel": "LINKEDIN", "lastActivityContent": "Yes, I am interested.", "replyStatus": "REPLIED" } } ``` ## Errors `session not found` or `Not found` when the candidate is outside the session workspace. # get_candidate_dynamic_sequence_status Source: https://docs.kalent.ai/mcp/get-candidate-dynamic-sequence-status Read the current or latest dynamic outreach sequence status for a candidate. Returns the latest dynamic sequence status for a candidate in the authenticated workspace sourcing. Use this tool after `start_dynamic_sequences` to poll progress by `candidateId`. ## Input | Parameter | Type | Required | Description | | ------------- | ------ | -------- | ---------------------------- | | `candidateId` | string | Yes | Candidate in your workspace. | ## Output JSON block with `success: true` and `data`: `candidateId`, `talentId`, `sourcingId`, and `sequence` (or `null`). When `displayStatus` is `ACTION_REQUIRED`, the response also includes `requiresAction: true` and `actionRequired` details. `displayStatus: "TALENT_REPLIED"` only describes the dynamic sequence: it is set when a reply interrupts a **running** dynamic sequence, so it does not cover legacy sequences nor replies received once the sequence is no longer running. To know whether a talent replied at all, regardless of sequence type or timing, use `replyStatus` returned by `get_candidate` and `get_candidates`. ## Errors `session not found`, `Not found`, or a human-readable error when the candidate cannot be resolved. # get_candidates Source: https://docs.kalent.ai/mcp/get-candidates List paginated candidates for a sourcing. Read-only MCP tool that returns candidates for one sourcing using the same filters and ordering as the Kalent candidates table. ## Input | Field | Type | Required | Description | | ---------------- | --------------- | -------- | ------------------------------------------------------------------------------------------------------------------ | | `sourcingId` | string | yes | Sourcing id | | `pageIndex` | number | no | Zero-based page index | | `pageSize` | number | no | Page size (max 50) | | `search` | string | no | Free-text search | | `statusHandle` | string\[] | no | Status filters | | `location` | string\[] | no | City filters | | `jobTitle` | string\[] | no | Job title filters | | `currentCompany` | string\[] | no | Company filters | | `sortField` | string | no | One of `name`, `companyName`, `statusHandle`, `city`, `jobTitle`, `userRating`, `contacts`, `createdAt`, `history` | | `sortOrder` | `asc` \| `desc` | no | Sort order | `sortField: "history"` is not a true activity sort: the page is selected by talent creation date (`talent.createdAt`, in the requested `sortOrder`), then the candidates of that page are re-ordered in memory by their latest outreach activity (with `desc`, candidates without any activity come first, then the most recent activity first). To find who replied, read `replyStatus` on each item rather than relying on this sort. ## Response Each item is `{ candidate, talent, lastActivityAt, lastActivityDirection, lastActivityChannel, lastActivityContent, replyStatus }`. ### Outreach activity The latest outreach activity between the talent and the user who owns the API key, aggregated across every channel (LinkedIn, email, WhatsApp, SMS) and across legacy and dynamic sequences. This is the same scope as the candidates table this user sees in Kalent: messages and invitations sent by other members of the workspace are not taken into account. `get_candidate` returns the same fields with the same values. | Field | Type | Description | | ----------------------- | ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `lastActivityAt` | ISO-8601 string \| `null` | Date of the most recent outreach event: message sent, LinkedIn invitation sent or accepted, or reply received. `null` when the candidate was never contacted. | | `lastActivityDirection` | `INBOUND` \| `OUTBOUND` \| `null` | `INBOUND` when the latest event comes from the talent (reply, accepted invitation), `OUTBOUND` when it comes from the API key owner (message or invitation sent). `null` when the candidate was never contacted. | | `lastActivityChannel` | `LINKEDIN` \| `WHATSAPP` \| `EMAIL` \| `SMS` \| `null` | Channel of that latest event. `null` when the candidate was never contacted. | | `lastActivityContent` | string \| `null` | Text of that latest event when it is a sent or received message. `null` when the candidate was never contacted, when the latest event is a LinkedIn invitation sent or accepted (Kalent does not store the invitation note), or when the message body is empty. Email subject is not included. | | `replyStatus` | `REPLIED` \| `NO_REPLY` \| `NOT_CONTACTED` | `REPLIED` as soon as at least one reply was received on any channel, `NO_REPLY` when the talent was contacted without any reply, `NOT_CONTACTED` when no outreach happened. | `replyStatus` stays `REPLIED` even if a follow-up was sent after the talent replied (`lastActivityDirection` then becomes `OUTBOUND`). An accepted LinkedIn invitation counts as an inbound activity but not as a reply. ```json theme={null} { "success": true, "data": { "candidates": [ { "candidate": { "id": "candidate_123", "statusHandle": "CONTACTED", "createdAt": "2026-05-19T09:00:00.000Z", "sourcingId": "sourcing_123", "talentId": "talent_123" }, "talent": { "id": "talent_123", "firstname": "Grace", "lastname": "Hopper", "jobTitle": "Software Engineer", "city": "Paris", "linkedinUrl": "https://www.linkedin.com/in/example" }, "lastActivityAt": "2026-05-21T14:32:00.000Z", "lastActivityDirection": "INBOUND", "lastActivityChannel": "LINKEDIN", "lastActivityContent": "Yes, I am interested.", "replyStatus": "REPLIED" } ], "pagination": { "pageIndex": 0, "pageSize": 25, "total": 1, "hasNextPage": false } } } ``` # get_contact_enrichment_result Source: https://docs.kalent.ai/mcp/get-contact-enrichment-result Read emails, phones, and loading flags for a talent in your workspace after contact enrichment was started. ## Input | Parameter | Type | Required | Description | | ---------- | ------ | -------- | -------------------------------------------------------- | | `talentId` | string | Yes | Talent id (for example from `enrich_linkedin_contacts`). | ## Output JSON block with `talentId`, `emails`, `phones`, and `contactsLoading` (`email` / `phone` booleans). ## Errors Returns a not-found style error when your workspace has no enrichment transaction for that talent (enrichment was never started in this workspace). # get_dynamic_sequences Source: https://docs.kalent.ai/mcp/get-dynamic-sequences List sequence blueprints (dynamic sequences) for a sourcing. Read-only MCP tool that returns paginated sequence blueprints attached to a sourcing in the authenticated workspace. Use this before `start_dynamic_sequences` when you need to discover which `blueprintId` to start. ## Input | Parameter | Type | Required | Description | | ------------ | ------- | -------- | ----------------------------------------- | | `sourcingId` | string | yes | Sourcing id | | `page` | integer | no | Zero-based page index (default `0`) | | `pageSize` | integer | no | Page size, max `50` (default `20`) | | `search` | string | no | Case-insensitive filter on blueprint name | ## Output JSON with: * `sequenceBlueprints` — `blueprintId`, `name`, `sourcingId`, `createdAt`, `updatedAt`, `steps`, `stepCount`, `channels` * `pagination` — `page`, `pageSize`, `total`, `hasNextPage` Returned steps do not expose internal `workspaceId` or `userId` fields. ## Example flow 1. `create_sourcing` 2. `add_talent_to_sourcing` 3. `get_dynamic_sequences` with `sourcingId` 4. `start_dynamic_sequences` with `blueprintId` from the list See also [start\_dynamic\_sequences](/mcp/start-dynamic-sequences) and [create\_sequence\_blueprint](/mcp/create-sequence-blueprint). # get_sourcings Source: https://docs.kalent.ai/mcp/get-sourcings List paginated sourcings for the authenticated workspace. Read-only MCP tool that returns sourcing ids and names for the authenticated workspace. ## Input | Field | Type | Default | Description | | ----------- | ------------------------- | ------------- | ---------------------- | | `page` | number | `0` | Zero-based page index | | `pageSize` | number | `8` | Page size (max 50) | | `ownership` | `mine` \| `all` | `mine` | Visibility filter | | `status` | `in-progress` \| `closed` | `in-progress` | Sourcing status filter | ## Response ```json theme={null} { "success": true, "data": { "sourcings": [], "pagination": { "page": 0, "pageSize": 8, "total": 0, "hasNextPage": false } } } ``` Each sourcing item has this shape: ```json theme={null} { "sourcingId": "sourcing_123", "name": "Senior Backend Engineer" } ``` # Non-deterministic Results Source: https://docs.kalent.ai/mcp/non-deterministic-results Understand why search results may vary between identical requests and how to work with this behavior. The Kalent search engine is a **live, AI-augmented system**. Search results are not guaranteed to be identical across requests, even when you describe the exact same criteria. This is by design and reflects how the platform continuously improves data quality and relevance. ## Why results vary Three factors contribute to non-deterministic behavior: Talent profiles are enriched and updated **live during each search**. A profile that didn't match before may match now after a refresh (and vice versa). This means every search benefits from the latest available data. Result ranking uses AI models whose outputs can **differ slightly between calls**. The ranking reflects the best possible ordering at the time of the request, but small fluctuations are normal and expected. New talents are added and existing profiles are updated from external sources **on an ongoing basis**. The pool of matching candidates grows and evolves constantly. ## What this means in practice * **Same prompt, different results**: two identical searches sent minutes apart may return different talents or rank them differently. * **Result counts may shift**: the `estimationCount` value can change between calls as new profiles are indexed or existing ones are updated. * **No two pages are "frozen"**: when you ask for more results, each new batch reflects the live state of the database at the time of the request. ## What stays consistent Despite the non-deterministic nature of results, the system provides strong guarantees: The pagination mechanism ensures you will **never see the same talent twice** within a paginated session. Every returned talent **genuinely matches your criteria** at the time of the request. Non-determinism does not affect accuracy — only ordering and pool composition. ## Recommendations Treat results as a set of matching candidates rather than a strictly ordered list. If you need a stable ranking, ask the AI to sort results by a specific criterion after receiving them. Simply ask for "more results" — the AI handles pagination automatically using `searchTransactionId` values, ensuring you always get fresh profiles. See [Pagination](/mcp/search-talents-by-prompt#pagination) for details. Use `estimationCount` as an approximation, not an exact figure. The count reflects a live, constantly updating dataset. # MCP Overview Source: https://docs.kalent.ai/mcp/overview Connect your AI assistant to Kalent's talent database using the Model Context Protocol (MCP). ## What is the Kalent MCP Server? Find and contact top talent without leaving your AI workspace. Search +200M professional profiles with AI-powered matching, access verified emails and mobile numbers, and launch personalized outreach campaigns effortlessly. Built for recruiting firms, staffing agencies, and HR teams, Kalent delivers up to 60% reply rates powered by 50+ data sources. Discover developers, sales reps, accountants, or any role worldwide. Research candidates, build strong talent pipelines, and fill positions faster — all directly within your favorite AI assistant. Turn any LLM into a full recruiting platform with Kalent. Describe the talent you need in everyday language — the AI translates your request into structured filters automatically. Get full professional profiles including experience, education, skills, languages, and certifications. ## API vs MCP — When to use what | | REST API | MCP Server | | ---------------------- | ----------------------------------------------------- | ----------------------------------------------------------------- | | **Interface** | HTTP endpoints with JSON payloads | AI assistant (Claude, Cursor, etc.) | | **Authentication** | API key via header | OAuth 2.0 (one-time browser flow) | | **How you search** | Build a JSON filter array | Describe the candidate in natural language | | **Best for** | Programmatic integrations, ATS pipelines, automations | Interactive sourcing, ad-hoc searches, conversational exploration | | **Filter translation** | You build the filters manually | The AI builds them for you from your prompt | Use the **REST API** when you need to integrate Kalent into your own software. Use the **MCP server** when you want to search talent interactively from an AI assistant like Claude. *** ## Setup with Claude You can add Kalent to Claude in one click from **Settings → MCP Connection** in the Kalent app: the **Add to Claude** button opens Claude with the Kalent connector pre-filled. To add it manually, follow the steps below. In Claude Desktop or on [claude.ai](https://claude.ai/customize/connectors), open **Settings → Connectors**. Click **Add custom connector** and enter the following server URL: ``` https://app.kalent.ai/api/mcp ``` Claude will redirect you to Kalent's login page. Sign in with your Kalent credentials and authorize the connection. Once approved, you are redirected back to Claude — the connector is now ready to use. The OAuth flow uses **OAuth 2.0 with PKCE**. Your AI client handles the entire handshake automatically — you only need to sign in and click **Authorize**. *** ## Setup with Cursor You can add Kalent to Cursor in one click from **Settings → MCP Connection** in the Kalent app: the **Add to Cursor** button opens Cursor and prompts you to install the Kalent MCP server. To add it manually, follow the steps below. In Cursor, open **Settings → MCP** (or **Tools & Integrations → MCP**) and click **Add custom MCP server**. Add the following entry to your `mcp.json`: ```json theme={null} { "mcpServers": { "Kalent": { "url": "https://app.kalent.ai/api/mcp" } } } ``` Cursor opens a browser window to Kalent's login page. Sign in and authorize the connection — the server then shows as connected in Cursor. *** ## Setup with ChatGPT In ChatGPT, go to **Settings** → **Connectors** → **Advanced** and enable **Developer Mode**. Add a new app with the following URL: ``` https://app.kalent.ai/api/mcp ``` Complete the Kalent authentication flow. Once approved, the connector is ready to use. *** ## Authentication The Kalent MCP server uses **OAuth 2.0** to authenticate your AI assistant. This is different from the REST API, which uses a static API key. ### How it works 1. Your AI client (Claude, Cursor, etc.) connects to `https://app.kalent.ai/api/mcp`. 2. The server responds with a `401` and points to its OAuth metadata. 3. The client opens a browser window where you sign in to Kalent. 4. You see a consent screen asking you to authorize the connection to your workspace. 5. After clicking **Authorize**, the client receives an access token and the MCP session starts. The entire flow is handled transparently by your AI client. You only interact with the Kalent login and consent pages. Each MCP session is scoped to a single Kalent workspace. If you have access to multiple workspaces, the connection uses the workspace associated with your API key. *** ## Available Tools The Kalent MCP server exposes search, sourcing, enrichment, and outreach sequence tools. Default talent search. Describe who you need in natural language. Everything you mention is treated as a requirement. Use only when you explicitly want structured Kalent filters, with per-filter control over required vs. preferred criteria. Create an empty sourcing campaign. Attach a talent to a sourcing as a candidate. Create a manual outreach sequence blueprint. List sequence blueprints for a sourcing. Update sequence blueprint steps or configuration. Start outreach sequences for candidates. Start contact enrichment for a candidate. Start LinkedIn contact enrichment for a talent. Read enrichment results for a talent. List paginated sourcings for the workspace. List paginated candidates for a sourcing. Resolve candidate and talent details. Read dynamic sequence progress for a candidate. Search results are **non-deterministic by design** — identical requests may return different results due to real-time profile enrichment, AI-powered scoring, and continuous indexing. Learn more in the [Non-deterministic Results](/mcp/non-deterministic-results) guide. *** ## Example Prompts Once connected, you can search for talent by simply describing what you need. Here are some examples: **Prompt:** > Find software engineers who have worked at OpenAI for at least 3 years. The AI will create filters for: * `JOB_TITLE` = "Software Engineer" * `COMPANY_NAME` = "OpenAI" with `history` = "CURRENT\_AND\_PAST" * `DURATION_IN_JOB` = "3-5" (or higher) **Prompt:** > I need to find talent in Paris (within 20km radius) with expertise in SEO and marketing strategies. The AI will create filters for: * `LOCATION` = "Paris" with `radius` = 20 * `KEYWORD` = "SEO" * `KEYWORD` = "Marketing Strategy" **Prompt:** > Find talent with 10+ years of experience as a UX designer who has worked for a YC startup. The AI will create filters for: * `JOB_TITLE` = "UX Designer" * `YEARS_OF_EXPERIENCE` = "10-15" (or higher) * `KEYWORD` = "Y Combinator" **About location radius:** The radius parameter is always in **kilometers**. If you mention miles in your prompt (e.g., "within 10 miles"), the AI will automatically convert to kilometers before making the search. You don't need to worry about unit conversion. *** ## Response Format For each search, the MCP server returns two content blocks: 1. **Structured JSON** — Complete profile data (experiences, educations, skills, languages, certifications) that the AI uses for reasoning and follow-up questions. 2. **Markdown table** — A formatted summary with name, job title, company, location, and LinkedIn profile link, displayed directly in the conversation. Up to **5 matching profiles** are returned per search. ## Subscription and plan errors When a tool is blocked because the workspace has no eligible Kalent subscription or needs a higher plan, the MCP response is marked as an error and tells the AI assistant to ask the user to subscribe or upgrade before retrying. The message also includes the billing URL: ```text theme={null} A Kalent subscription or plan upgrade is required to create sourcings. Ask the user to subscribe or upgrade their Kalent plan before retrying. They can manage billing here: https://app.kalent.ai/settings/billing ``` *** ## Supported AI Clients The Kalent MCP server implements the [Model Context Protocol](https://modelcontextprotocol.io) standard and is compatible with any MCP-compliant client, including: * **Claude Desktop** — Full OAuth support with built-in connector management * **Claude Code** — Add via CLI: `claude mcp add --transport http kalent https://app.kalent.ai/api/mcp` * **ChatGPT** — Enable Developer Mode in Settings → Connectors → Advanced, then add the app URL * **Cursor** — Configure as a custom MCP server in settings * Any other MCP-compatible AI client # remove_candidate_from_sourcing Source: https://docs.kalent.ai/mcp/remove-candidate-from-sourcing Remove a candidate from a sourcing flow (sets status to NOT_RETAINED). Removes a candidate from a sourcing flow by setting its status to `NOT_RETAINED`. The candidate row is kept; it is excluded from the active pipeline and any active outreach tasks are cancelled. ## Input | Parameter | Type | Required | Description | | ------------- | ------ | -------- | ----------------------- | | `candidateId` | string | Yes | Candidate id to remove. | ## Output JSON block with `success: true` and `data` containing `candidateId`, `talentId`, `sourcingId`, `statusHandle` (`NOT_RETAINED`), `updatedAt`, and `removed: true`. ## Errors `session not found` or `Not found` when the candidate does not belong to the workspace. # search_talents_by_filters Source: https://docs.kalent.ai/mcp/search-talents-by-filters Search the Kalent talent database with structured Kalent filters via your AI assistant. The `search_talents_by_filters` tool searches the Kalent talent database with **structured Kalent filters** — the same filter model used by the Kalent app and by the [`POST /v1/search/talents`](/api-reference/search-talents) API. **Do not use this tool for ordinary talent searches.** When a user asks to find or search talents in natural language (for example *"cherche des talents"*, *"find a senior React developer in Paris"*), the assistant must call [`search_talents_by_prompt`](/mcp/search-talents-by-prompt) instead. Call `search_talents_by_filters` **only** when the user explicitly asks to search with structured Kalent filters (a `filters` array, `filterType`, `isRequired`, `isExcluded`, `isExactMatch`, `radius`, or the Kalent filter model). It returns up to **5 matching profiles** with full career details. This tool was previously available in development environments as `search_talents_with_filters`. It is now exposed to every MCP client under the name `search_talents_by_filters`. ## How it works 1. The user explicitly asks to search with structured Kalent filters, or provides a filter list. 2. The AI builds a `filters` array, choosing the filter types, values, and modifiers (`isRequired`, `isExcluded`, `isExactMatch`, `radius`, `history`). 3. The MCP server executes the search and returns matching profiles. 4. The AI presents a formatted table and can answer follow-up questions using the full profile data. Every criterion you mention is treated as a **requirement** by default (`isRequired: true`). If you want a criterion to be a nice-to-have rather than mandatory, say so explicitly (e.g., *"ideally with experience in fintech"* or *"bonus if they speak Spanish"*) — the AI then sets `isRequired: false` on that filter, and it becomes a **preference**: profiles that do not match it can still be returned, and matching profiles come first. ## When to use this tool vs. `search_talents_by_prompt` | | `search_talents_by_prompt` | `search_talents_by_filters` | | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | | Default? | **Yes** — use this whenever the user asks to search or find talents in natural language | **No** — only if the user explicitly asks for structured Kalent filters | | Input | Free-text `prompt` | Structured `filters` array | | Who builds the filters | Kalent, from the prompt | Your AI assistant (visible, editable) | | `isRequired: false` filters | Not applicable — every kind of criterion in the prompt is required; with several values of one kind, matching one is enough unless the prompt says they are all mandatory | Preferences — non-matching profiles can still be returned, matching ones first | | Best for | Everyday talent search ("cherche des talents", "find a React developer in Paris") | Explicit Kalent filter arrays, reusable filter specs, per-filter modifiers | *** ## Input | Parameter | Type | Required | Description | | ----------------------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `filters` | object\[] | Yes | Array of Kalent filters (see [Filter Types](#filter-types) and [Filter Modifiers](#filter-modifiers)). Several filters may share the same `filterType`. | | `relatedSearchTransactionIds` | string\[] | No | Array of `searchTransactionId` values from previous calls for pagination. Maximum 300 IDs (see [Pagination](#pagination)). | ```json theme={null} { "filters": [ { "filterType": "JOB_TITLE", "value": "Product Designer", "isRequired": true, "isExcluded": false, "isExactMatch": false, "history": "CURRENT" }, { "filterType": "LOCATION", "value": "Paris", "isRequired": true, "isExcluded": false, "isExactMatch": false, "radius": 30 }, { "filterType": "KEYWORD", "value": "Figma", "isRequired": false, "isExcluded": false, "isExactMatch": false } ] } ``` Accepted values for enumerated filters (languages, industries, ranges, …) are listed in [Accepted filter values](/api-reference/accepted-filter-values). *** ## Filter Types The AI selects from the following filter types: | Filter type | Value format | Description | | --------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | `JOB_TITLE` | Free text | Job title or role (e.g., "Product Designer", "Software Engineer") | | `LOCATION` | City or country | Geographic location with optional radius in km | | `YEARS_OF_EXPERIENCE` | Range bucket | Professional experience: `0-1`, `1-3`, `3-5`, `5-10`, `10-15`, `15-20`, `20-30`, `30-100` | | `SKILL` | Free text | Dedicated skills list on a profile (often incomplete). Prefer `KEYWORD` for a tech or competency. Retry as `KEYWORD` if this returns no results. | | `KEYWORD` | Free text | Full profile text (headline, summary, experience). Prefer this for a tech or competency (e.g., React, SEO, Figma). | | `LANGUAGE` | Lowercase name | Spoken language (e.g., "french", "english", "mandarin") | | `LANGUAGE_PROFICIENCY` | Level string | Language proficiency level | | `COMPANY_NAME` | Free text | Current or past employer | | `COMPANY_SIZE` | Range bucket | Company headcount: `1-10`, `11-50`, `51-200`, `201-500`, `501-1000`, `1001-5000`, `5001-10000`, `10001+` | | `COMPANY_INDUSTRY` | Industry string | Employer industry sector (e.g., "computer software", "financial services") | | `EDUCATION_DEGREE` | Degree level | Highest degree: `bachelors`, `masters`, `doctorates` | | `EDUCATION_SCHOOL_NAME` | Free text | School or university name | | `CERTIFICATION_NAME` | Free text | Professional certification (e.g., "AWS Certified", "PMP") | | `SENIORITY` | Contract type | Currently supports: `Freelance` | | `DURATION_IN_JOB` | Range bucket | Tenure in current/last role (same buckets as years of experience) | | `GRADUATION_YEAR` | Four-digit year | Graduation year (e.g., "2020") | | `LINKEDIN_RELATIONS_DEGREE` | `1st degree` or `invited` | Relationship to your connected LinkedIn account. `1st degree` means accepted first-degree connections only; `invited` means pending invitations only. | *** ## Filter Modifiers Each filter supports the following modifiers: | Modifier | Type | Description | | -------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `isRequired` | boolean | When `true`, the talent **must** match this criterion. When `false`, the criterion is a preference: non-matching talents can still be returned, matching ones first. Set to `true` by default for every filter. | | `isExcluded` | boolean | When `true`, talents matching this filter are **excluded** from results. | | `isExactMatch` | boolean | When `true`, the value must match exactly (no fuzzy matching). | | `radius` | number | Search radius in **kilometers**. Only applies to `LOCATION` filters. | | `history` | string | Temporal scope: `CURRENT`, `PAST`, or `CURRENT_AND_PAST`. Only applies to `JOB_TITLE` and `COMPANY_NAME`. | **About location radius:** The radius is always in **kilometers**. You can mention miles in your prompt — the AI automatically handles the conversion. For example, "within 30 miles of London" will be converted to approximately 48 km. **Prefer `KEYWORD` for a technology or competency.** `SKILL` only matches the dedicated skills list on a profile, which many people leave empty. `KEYWORD` searches the whole profile. If a search that uses `SKILL` returns `estimationCount: 0` or no talents, call the tool again with the same value as `KEYWORD`. Do not add both on the first call. **About LinkedIn relationship filters:** Use this filter only when the request explicitly refers to your own LinkedIn network, such as "exclude people I know" or "search within my connections." The values `1st degree` and `invited` are distinct: accepted first-degree connections do not include pending invitations. *** ## Response Each search returns two content blocks: ### 1. Structured JSON Complete profile data for each matching talent, including: * `searchTransactionId` — unique identifier for this search (used for pagination) * `estimationCount` — the total estimated number of talents matching the filters in the database, not just the ones returned in this batch * `resultCompleteness` — `status` (`complete` or `partial`), `requestedCount`, `returnedCount` and, when partial, a `reason` (`pool_exhausted`, `safety_budget_reached`, `insufficient_ready_profiles`, `insufficient_credits`, `search_interrupted`) * `credits` — credits consumed by this search and remaining balance * Full name, location, headline * Current job title and organization * Work experiences (company, title, dates, description) * Education (school, degree, major, dates) * Skills * Languages and proficiency levels * Professional certifications * LinkedIn profile URL The AI uses this data to answer follow-up questions, compare candidates, or refine searches. The `searchTransactionId` is used internally for pagination (see below). The `estimationCount` helps convey how large the overall talent pool is for the given criteria. When `resultCompleteness.status` is `partial`, the tool explains in plain language how many profiles were delivered out of the requested number and what to do next. Only **ready** profiles are returned: talents that are already up to date, or that were synchronously refreshed and passed refiltering. The tool accepts an optional `nbToFetch` (1 to 5, default 5) and keeps fetching until that many ready profiles are found or an internal page/time budget is reached. ### 2. Markdown Summary Table A formatted table displayed directly in the conversation: | # | Name | Job Title | Company | Location | LinkedIn | | - | ------------ | ------------------------ | --------- | ------------- | -------------------------------------- | | 1 | Marie Dupont | Senior Software Engineer | Acme Corp | Paris, France | [Profile](https://linkedin.com/in/...) | | 2 | John Smith | Staff Engineer | Tech Inc | London, UK | [Profile](https://linkedin.com/in/...) | *** ## Errors If the search engine times out, the tool returns `isError: true` with a message asking you to try again and a `ref:` line containing the request `debugTrackingCode` for support. If a LinkedIn relationship filter is used but no LinkedIn account is connected, the tool returns an error asking you to connect LinkedIn in Kalent settings before filtering by your network. ## Credits Each search consumes **API search credits**. The number of credits consumed corresponds to the number of profiles returned (up to 5 per search). The response includes a `credits` object showing how many credits were used and how many remain. If you run out of credits, the tool will return an error prompting you to purchase more at [https://app.kalent.ai/settings/billing](https://app.kalent.ai/settings/billing). *** ## Pagination When you ask for more results, the AI automatically handles pagination by passing previous `searchTransactionId` values via the `relatedSearchTransactionIds` parameter. `relatedSearchTransactionIds` is an array of `searchTransactionId` strings from previous `search_talents_by_filters` calls — talents already returned in those earlier searches are excluded from the new results, so you always see fresh profiles. > **You:** Search with Kalent filters for React developers in Paris. > > *\[5 results displayed — estimationCount: 120]* > > **You:** Show me more. > > *\[5 new, different results displayed — no duplicates from the first batch]* The AI collects `searchTransactionId` values from each response and passes them in subsequent requests so you never see the same talent twice. The array accepts at most 300 IDs. The `estimationCount` tells you how many total matches exist, helping you decide whether to paginate further. *** ## Non-deterministic Results Search results may vary between identical requests. This is expected and by design: * **Real-time profile refresh** — talent profiles are enriched and updated live during each search. A profile that didn't match before may match now after a refresh (and vice versa). * **AI-powered scoring** — result ranking uses AI models whose outputs can differ slightly between calls. * **Continuous indexing** — new talents are added and existing profiles are updated from external sources on an ongoing basis. The pagination mechanism guarantees you will **never see the same talent twice** within a paginated session. However, the overall set of matching talents may shift between calls. This is inherent to a live, AI-augmented search engine and does not affect result accuracy — every returned talent genuinely matches your criteria at the time of the request. Read more in the [Non-deterministic Results](/mcp/non-deterministic-results) guide. *** ## Example Conversations These examples assume the user asked to search with structured Kalent filters. If they only described the talent they need, use [`search_talents_by_prompt`](/mcp/search-talents-by-prompt). ### Finding engineers with specific company experience > **You:** Search with structured Kalent filters: software engineers who have worked at OpenAI for at least 3 years. > > **Assistant:** I'll build a Kalent filter array for software engineers with OpenAI experience and at least 3 years in the role. > > *\[Results displayed as a table with 5 matching profiles]* > > I found 5 software engineers who match your criteria. Would you like me to look into any of these profiles in more detail, or would you like to refine the search? ### Location-based search with skills > **You:** Use Kalent filters: talent in Paris (within 20km radius) with expertise in SEO and marketing strategies. > > **Assistant:** I'll search with structured LOCATION and KEYWORD filters for marketing professionals near Paris with SEO expertise. > > *\[Results displayed]* > > Here are 5 professionals in the Paris area with SEO and marketing strategy skills. Several of them also have experience in content marketing and digital analytics. ### Combining experience, role, and company type > **You:** Search with Kalent filters: 10+ years of experience as a UX designer who has worked for a YC startup. > > **Assistant:** I'll build structured filters for senior UX designers with Y Combinator startup experience. > > *\[Results displayed]* > > I found 5 UX designers with 10+ years of experience and ties to Y Combinator startups. Want me to narrow down by location or specific skills? *** ## Tips for Better Results Mention specific job titles, companies, locations, and skills. The more precise you are, the better the results. All criteria are treated as mandatory by default. Use words like "ideally", "bonus", or "nice-to-have" for optional preferences. Refine your search iteratively. Ask to narrow down by adding criteria, or broaden by relaxing constraints. You can combine as many filters as needed in a single call: job title + location + skills + experience + company type. # search_talents_by_prompt Source: https://docs.kalent.ai/mcp/search-talents-by-prompt Search the Kalent talent database using natural language via your AI assistant. The `search_talents_by_prompt` tool is the **default** way for your AI assistant to search the Kalent talent database. When you ask to find or search talents — in any language — the assistant should call this tool with a natural language prompt. Describe who you are looking for in plain text — the system extracts the right search filters automatically. It returns up to **5 matching profiles** with full career details. This tool was previously named `search_talents`. Update your MCP client configuration, prompts, and skills to call `search_talents_by_prompt`. Calls to the old name return an "unknown tool" error. ## How it works 1. You describe the talent you need in everyday language. 2. The MCP server converts your prompt into structured search filters using AI. 3. The search engine finds matching profiles and returns the results. 4. The AI presents a formatted table and can answer follow-up questions using the full profile data. ## Everything you mention is a requirement Each kind of criterion found in your prompt (job title, location, skill, language, years of experience, …) is treated as a requirement, with one rule: * **Across kinds of criteria — all of them are required.** A profile must satisfy every kind of criterion you mentioned. * **Within one kind of criterion — at least one value is enough.** When you give several values of the same kind, a profile only needs to match one of them, not all — unless you explicitly say they are all mandatory (*"must master both Python and React"*), in which case each one is required. For example, *"Software engineers in Paris or Lyon who know Python and React"* returns profiles that are software engineers **and** based in Paris or Lyon **and** have Python **or** React — a profile with only Python still qualifies. To require both skills, say so explicitly in the prompt (*"must master both Python and React"*). Within the matching set, results are ordered by best overall match. This makes prompt searches predictable — what you ask for is what you get — but it also means an over-specified prompt can shrink the pool quickly. Keep the prompt to the criteria that are truly mandatory, and refine iteratively. Stay on this tool for follow-ups, optional preferences, and refinements — just say them in the prompt. Use [`search_talents_by_filters`](/mcp/search-talents-by-filters) only when you explicitly ask the assistant to search with structured Kalent filters. *** ## Input The tool accepts a single `prompt` parameter — a natural language description of the talent you are looking for. | Parameter | Type | Required | Description | | ----------------------------- | --------- | -------- | -------------------------------------------------------------------------------------------------------------------------- | | `prompt` | string | Yes | Natural language description of the talent you need. | | `relatedSearchTransactionIds` | string\[] | No | Array of `searchTransactionId` values from previous calls for pagination. Maximum 300 IDs (see [Pagination](#pagination)). | ### Prompt examples * *"I need a senior React developer in Paris with 5+ years of experience"* * *"Find me product managers who speak French and have worked at a startup"* * *"Software engineers in London with AWS certifications and fintech experience"* * *"UX designers with 10+ years of experience who have worked at a YC startup"* * *"Find senior backend engineers in Paris, excluding people already in my LinkedIn network"* LinkedIn relationship filters are only used when your prompt explicitly refers to your own network, for example "exclude people I know" or "search within my connections." Accepted first-degree connections and pending invitations are handled as separate values. *** ## Response Each search returns two content blocks: ### 1. Structured JSON Complete profile data for each matching talent, including: * `searchTransactionId` — unique identifier for this search (used for pagination) * `estimationCount` — the total estimated number of talents matching the filters in the database, not just the ones returned in this batch * `resultCompleteness` — `status` (`complete` or `partial`), `requestedCount`, `returnedCount` and, when partial, a `reason` (`pool_exhausted`, `safety_budget_reached`, `insufficient_ready_profiles`, `insufficient_credits`, `search_interrupted`) * `credits` — credits consumed by this search and remaining balance * Full name, location, headline * Current job title and organization * Work experiences (company, title, dates, description) * Education (school, degree, major, dates) * Skills * Languages and proficiency levels * Professional certifications * LinkedIn profile URL The AI uses this data to answer follow-up questions, compare candidates, or refine searches. The `searchTransactionId` is used internally for pagination (see below). The `estimationCount` helps convey how large the overall talent pool is for the given criteria. When `resultCompleteness.status` is `partial`, the tool explains in plain language how many profiles were delivered out of the requested number and what to do next. Only **ready** profiles are returned: talents that are already up to date, or that were synchronously refreshed and passed refiltering. The tool accepts an optional `nbToFetch` (1 to 5, default 5) and keeps fetching until that many ready profiles are found or an internal page/time budget is reached. ### 2. Markdown Summary Table A formatted table displayed directly in the conversation: | # | Name | Job Title | Company | Location | LinkedIn | | - | ------------ | ------------------------ | --------- | ------------- | -------------------------------------- | | 1 | Marie Dupont | Senior Software Engineer | Acme Corp | Paris, France | [Profile](https://linkedin.com/in/...) | | 2 | John Smith | Staff Engineer | Tech Inc | London, UK | [Profile](https://linkedin.com/in/...) | *** ## Errors If the search engine times out, the tool returns `isError: true` with a message asking you to try again and a `ref:` line containing the request `debugTrackingCode` for support. If your prompt uses a LinkedIn relationship filter but no LinkedIn account is connected, the tool returns an error asking you to connect LinkedIn in Kalent settings before filtering by your network. ## Credits Each search consumes **API search credits**. The number of credits consumed corresponds to the number of profiles returned (up to 5 per search). The response includes a `credits` object showing how many credits were used and how many remain. If you run out of credits, the tool will return an error prompting you to purchase more at [https://app.kalent.ai/settings/billing](https://app.kalent.ai/settings/billing). *** ## Pagination When you ask for more results, the AI automatically handles pagination by passing previous `searchTransactionId` values via the `relatedSearchTransactionIds` parameter. Talents already returned in those earlier searches are excluded from the new results, so you always see fresh profiles. > **You:** Find me React developers in Paris. > > *\[5 results displayed — estimationCount: 120]* > > **You:** Show me more. > > *\[5 new, different results displayed — no duplicates from the first batch]* The AI collects `searchTransactionId` values from each response and passes them in subsequent requests so you never see the same talent twice. The array accepts at most 300 IDs. The `estimationCount` tells you how many total matches exist, helping you decide whether to paginate further. *** ## Non-deterministic Results Search results may vary between identical requests. This is expected and by design: * **Real-time profile refresh** — talent profiles are enriched and updated live during each search. A profile that didn't match before may match now after a refresh (and vice versa). * **AI-powered scoring** — result ranking uses AI models whose outputs can differ slightly between calls. * **Continuous indexing** — new talents are added and existing profiles are updated from external sources on an ongoing basis. The pagination mechanism guarantees you will **never see the same talent twice** within a paginated session. However, the overall set of matching talents may shift between calls. This is inherent to a live, AI-augmented search engine and does not affect result accuracy — every returned talent genuinely matches your criteria at the time of the request. Read more in the [Non-deterministic Results](/mcp/non-deterministic-results) guide. *** ## Example Conversations ### Finding engineers with specific company experience > **You:** Find software engineers who have worked at OpenAI for at least 3 years. > > **Assistant:** I'll search for software engineers with OpenAI experience and at least 3 years in the role. > > *\[Results displayed as a table with matching profiles]* > > I found several software engineers who match your criteria. Would you like me to look into any of these profiles in more detail, or would you like to refine the search? ### Location-based search with skills > **You:** I need to find talent in Paris (within 20km radius) with expertise in SEO and marketing strategies. > > **Assistant:** Let me search for marketing professionals near Paris with SEO expertise. > > *\[Results displayed]* > > Here are professionals in the Paris area with SEO and marketing strategy skills. Several of them also have experience in content marketing and digital analytics. ### Combining experience, role, and company type > **You:** Find talent with 10+ years of experience as a UX designer who has worked for a YC startup. > > **Assistant:** I'll search for senior UX designers with Y Combinator startup experience. > > *\[Results displayed]* > > I found UX designers with 10+ years of experience and ties to Y Combinator startups. Want me to narrow down by location or specific skills? *** ## Tips for Better Results Every criterion you mention becomes a requirement. Mention the job titles, locations, and skills that are truly required; add preferences in a follow-up prompt. Refine your search iteratively. Ask to narrow down by adding criteria, or broaden by relaxing constraints. You can combine as many criteria as needed in a single prompt: job title + location + skills + experience + company type. Each search consumes credits based on the number of profiles returned. Check your remaining balance at any time in the billing settings. # start_dynamic_sequences Source: https://docs.kalent.ai/mcp/start-dynamic-sequences Start dynamic outreach sequences from a blueprint for candidates. Starts a dynamic outreach sequence workflow for the given blueprint and candidate ids in the authenticated MCP workspace. ## Input | Parameter | Type | Required | Description | | -------------- | --------- | -------- | ---------------------------------------- | | `blueprintId` | string | Yes | Sequence blueprint to start. | | `candidateIds` | string\[] | Yes | Candidate ids to enroll in the sequence. | ## Output JSON block with `success: true` and `data`: `started`, `blueprintId`, `candidateCount`, and `candidatesAlreadyInSequenceElsewhere` (talents skipped because they already have an active sequence on another campaign). Poll progress with `get_candidate_dynamic_sequence_status` for each `candidateId`. ## Account connection actions If required outreach accounts are missing or need attention, the tool returns a structured action payload instead of asking you to visit Kalent settings manually. ```json theme={null} { "success": false, "requiresAction": true, "action": "open_kalent_messaging_settings", "missingAccounts": ["LINKEDIN"], "connectionUrl": "https://app.kalent.ai/settings/outreach", "retryTool": "start_dynamic_sequences" } ``` Possible `action` values: * `open_kalent_messaging_settings` — open `connectionUrl` (Kalent `/settings/outreach`) to connect or reconnect LinkedIn, WhatsApp, or email accounts before retrying. `missingAccounts` can include `LINKEDIN`, `WHATSAPP`, `EMAIL_GOOGLE`, `EMAIL_OUTLOOK`, or `EMAIL_IMAP`. ## Errors `session not found`, `Not found`, validation messages, subscription/plan upgrade messages, or Temporal availability messages when the sequence cannot be started. When the workspace plan does not include outreach integrations, the tool returns an error explaining that the user must subscribe or upgrade before retrying: ```text theme={null} A Kalent subscription or plan upgrade is required to use outreach integrations. Ask the user to subscribe or upgrade their Kalent plan before retrying. They can manage billing here: https://app.kalent.ai/settings/billing ``` # update_candidate_status Source: https://docs.kalent.ai/mcp/update-candidate-status Change the status of a candidate in a sourcing. Updates the status of a candidate in the authenticated workspace. ## Input | Parameter | Type | Required | Description | | -------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `candidateId` | string | Yes | Candidate id to update. | | `statusHandle` | string | Yes | Target status handle. A global status (`TO_BE_CONTACTED`, `CONTACTED`, `NOT_RETAINED`, `OUTREACH_SEQUENCE_STARTED`) or a custom workspace status. | ## Output JSON block with `success: true` and `data` containing `candidateId`, `talentId`, `sourcingId`, `statusHandle`, and `updatedAt`. ## Errors `session not found`, `Not found`, or `Validation error` when the status handle is unknown. # update_sequence_blueprint Source: https://docs.kalent.ai/mcp/update-sequence-blueprint Update a sequence blueprint name, steps, or configuration. Updates an existing sequence blueprint in the authenticated MCP workspace. Send at least one of `name`, `steps`, or `config`. ## Input | Parameter | Type | Required | Description | | ------------- | ------ | -------- | --------------------------------------------------------------- | | `blueprintId` | string | Yes | Sequence blueprint to update. | | `name` | string | No | New display name for the blueprint. | | `steps` | array | No | Ordered outreach steps. Sending `steps` replaces the full list. | | `config` | object | No | Optional sequence-level configuration. | ## Step shape Each item in `steps` must include: | Field | Type | Required | Description | | ---------------------------- | ------------------------------------------------- | ----------------- | ----------------------------------------------------------- | | `id` | string | Yes | Stable step id, unique within the blueprint. | | `name` | string | Yes | Step label. | | `type` | `LINKEDIN` \| `WHATSAPP` \| `EMAIL` \| `SMS` | Yes | Outreach channel. | | `linkedInType` | string | For LinkedIn | Required when `type` is `LINKEDIN`; not allowed otherwise. | | `content` | string | Yes | Message body. | | `subject` | string | Email only | Email subject. | | `temporalityType` | `ASAP` \| `DELAYED` \| `AFTER_INVITATION_SETTLED` | Yes | Scheduling rule. | | `delay` | object | For delayed steps | Required when `temporalityType` is `DELAYED`. | | `delay.value` | integer | With `delay` | Positive delay value. | | `delay.unit` | `day` \| `minute` \| `second` | With `delay` | Delay unit. | | `contentSource` | `manual` \| `suggestedByKalent` | No | Message origin. | | `policy.skipStepIfNoContact` | boolean | No | Skip if the required contact is missing. | | `policy.autoValidateAIDraft` | boolean | No | Automatically validate AI-generated drafts. | | `policy.inviteTimeoutDays` | integer | No | Positive number of days for invite-related follow-up logic. | Allowed `linkedInType` values are `LINKEDIN_INVITATION`, `LINKEDIN_INVITATION_WITH_MESSAGE`, `LINKEDIN_MESSAGE`, and `LINKEDIN_INMAIL`. Only one invite step is allowed per blueprint. ## Supported template variables Only these variables are supported in `content` and `subject`: | Variable | Meaning | | -------------------------- | --------------------------- | | `{{firstname}}` | Candidate first name | | `{{lastname}}` | Candidate last name | | `{{candidateJobTitle}}` | Candidate current job title | | `{{candidateCompanyName}}` | Candidate current company | | `{{candidateLocation}}` | Candidate location | | `{{sourcingJobTitle}}` | Sourcing target job title | | `{{sourcingLocation}}` | Sourcing target location | | `{{recruiterFirstname}}` | Recruiter first name | | `{{recruiterLastname}}` | Recruiter last name | Do not invent variables. Unknown variables such as `{{firstName}}`, `{{company}}`, or `{{job}}` are rejected. Do not use unresolved bracket placeholders such as `[company]`, `[specific project or stack]`, `[one-pager link]`, or `[1 detail to add]` in `content` or `subject`. Replace them with real text, remove that part of the sentence, or ask the user for the missing detail before calling `update_sequence_blueprint`. ## Example input ```json theme={null} { "blueprintId": "BLUEPRINT_ID", "steps": [ { "id": "step-1", "name": "LinkedIn invite", "type": "LINKEDIN", "linkedInType": "LINKEDIN_INVITATION_WITH_MESSAGE", "content": "Hi {{firstname}}, I came across your profile and wanted to connect.", "temporalityType": "ASAP", "contentSource": "manual" }, { "id": "step-2", "name": "Email follow-up", "type": "EMAIL", "subject": "Quick follow-up", "content": "Hi {{firstname}}, would you be open to discussing the role?", "temporalityType": "DELAYED", "delay": { "value": 2, "unit": "day" }, "policy": { "skipStepIfNoContact": true } } ] } ``` ## Output JSON block with `success: true` and `data`: `blueprintId`, `name`, and `sourcingId`. ## Errors `session not found`, `Not found`, or a human-readable validation message when the blueprint cannot be updated. # Non-deterministic Results Source: https://docs.kalent.ai/non-deterministic-results Understand why search results may vary between identical API requests and how to work with this behavior. The Kalent search engine is a **live, AI-augmented system**. Search results are not guaranteed to be identical across requests, even when you send the exact same filters. This is by design and reflects how the platform continuously improves data quality and relevance. ## Why results vary Three factors contribute to non-deterministic behavior: Talent profiles are enriched and updated **in real time** during each search. A profile that did not match a filter moments ago may match now (and vice versa) after a refresh. This means every search benefits from the latest available data. Result ranking uses AI models whose outputs can **vary slightly between calls**. The ranking reflects the best possible ordering at the time of the request, but small fluctuations are normal and expected. New talents are **continuously indexed** and existing profiles are updated from external sources. The pool of matching candidates grows and evolves constantly. ## What this means in practice * **Same filters, different results**: two identical requests sent minutes apart may return different talents or rank them differently. * **Result counts may shift**: the `estimationCount` value can change between calls as new profiles are indexed or existing ones are updated. * **No two pages are "frozen"**: when paginating with `relatedSearchTransactionIds`, each page reflects the live state of the database at the time of the request. ## What stays consistent Despite the non-deterministic nature of results, the API provides strong guarantees: The `relatedSearchTransactionIds` mechanism ensures you will **never see the same talent twice** across paginated requests. Every returned talent **genuinely matches your filters** at the time of the request. Non-determinism does not affect accuracy — only ordering and pool composition. ## Recommendations Treat results as a set of matching candidates rather than a strictly ordered list. If you need a stable ranking, apply your own sorting logic after receiving results. Always accumulate `searchTransactionId` values across pages. This is the only way to guarantee non-overlapping results. See [Pagination](/api-reference/search-talents#pagination) for details. Use `estimationCount` as an approximation, not an exact figure. The count reflects a live, constantly updating dataset.