# List applications in a job stage Source: https://developers.cutshort.io/api-reference/applications/list-applications-in-a-job-stage /openapi/public-api-v1.json get /jobs/{jobId}/applications Lists applications for one stage (default: applications / candidates). Fitment filter default strong,fair (excludes weak/not_a_fit); pass fitment=strong or fitment=strong,fair,weak. autoUnlock default true unlocks locked candidates on the page; autoUnlock=false returns only already-unlocked applications. Unlocks always run upstream (plan credits, download counts, events); only the sent stage counts against the workspace API unlock quota. Requires applications:read. # Move an application to another stage Source: https://developers.cutshort.io/api-reference/applications/move-an-application-to-another-stage /openapi/public-api-v1.json post /applications/{applicationId}/move-stage Moves one application to a different pipeline stage on the same job. Get applicationId from GET /jobs/{jobId}/applications and stageId from GET /jobs/{jobId}/stages. From the Applications stage (type candidates), only Shortlisted (connected) or Rejected are valid — other targets return 400 invalid_stage_move. Requires scope `stages:write` (production keys). # Get candidate detail (auto-unlock) Source: https://developers.cutshort.io/api-reference/candidates/get-candidate-detail-auto-unlock /openapi/public-api-v1.json get /candidates/{talentCardId} Returns full candidate identity, the actual resume (text + signed file download URL), and structured profile. If the candidate is locked for your workspace, this call unlocks them first — spending **one unlock credit** against the workspace unlock quota (check GET /usage). Already-unlocked candidates are never re-charged. Requires scope `candidates:read` (eval + production keys). Rate limits: 30 sensitiveRead + 120 global requests per 60s per API key. # Delete a job (soft-delete) Source: https://developers.cutshort.io/api-reference/jobs/delete-a-job-soft-delete /openapi/public-api-v1.json delete /jobs/{jobId} Soft-deletes a job the same way the Cutshort UI does. Deleted jobs no longer appear in GET /jobs. Requires `jobs:write`. Optional body: `reason` + `reasonText` (defaults to cleanup / Public API). # List workspace jobs Source: https://developers.cutshort.io/api-reference/jobs/list-workspace-jobs /openapi/public-api-v1.json get /jobs Lists your workspace's jobs. Requires scope `jobs:read` (eval + production keys). # Post a job Source: https://developers.cutshort.io/api-reference/jobs/post-a-job /openapi/public-api-v1.json post /jobs Creates a fully configured job from minimal input: skills/tags derived from the JD, screening questionnaire auto-attached, and auto-outreach to matching candidates enabled. After posting, the job is reviewed and then goes live — typically ~30 minutes on business days; matching-candidate outreach starts once approved. Key requirement importance is honoured exactly as sent — we strongly recommend at most 3 `must_have` items (over-tight configs shrink outreach reach). Requires scope `jobs:write` (production keys). Rate limits: 5 createJob + 120 global requests per 60s per API key. # Invite a candidate to apply Source: https://developers.cutshort.io/api-reference/outreach/invite-a-candidate-to-apply /openapi/public-api-v1.json post /invite-to-apply FIRST CONTACT only: invites a talent-database / search candidate to apply to one of your jobs, with your custom message. Tip: include an interview booking link so interested candidates can pick a slot. One invite per (job, candidate) pair — if already invited or already in the job pipeline, returns 409 already_exists_in_job_pipeline; use POST /messages with threadId for follow-ups instead. Replies land in your Cutshort inbox. Requires scope `messages:write` (production keys). Metered on the workspace messages quota (check GET /usage). Rate limits: 10 inviteToApply + 120 global requests per 60s per API key. # Send a follow-up message in a thread Source: https://developers.cutshort.io/api-reference/outreach/send-a-follow-up-message-in-a-thread /openapi/public-api-v1.json post /messages Sends a reply in an EXISTING conversation thread (candidates already in the job pipeline). Get threadId from GET /jobs/{jobId}/applications or from the invite-to-apply response when present. This is NOT first-contact outreach — use POST /invite-to-apply for search candidates not yet in the pipeline. Requires scope `messages:write` (production keys). Does not consume the invite-to-apply messages quota. Rate limit: global 120 requests per 60s per API key. # Search talent database Source: https://developers.cutshort.io/api-reference/search/search-talent-database /openapi/public-api-v1.json get /search/candidates Structured and/or AI/NLP talent search. Hits carry triage data only (headline, experience, profileSignals, redacted resume excerpt) — no names, email, or phone. Use GET /candidates/{talentCardId} for identity (auto-unlocks). Requires scope `search:read`. Metered on the workspace searches quota (each page request counts). Rate limits: 10 search + 120 global requests per 60s per API key. At least one search filter is required (q, inresume, locations, experience, salary, profile attributes, or activity); bare pagination-only requests return 400 search_criteria_required. # List stages for a job (with application counts) Source: https://developers.cutshort.io/api-reference/stages/list-stages-for-a-job-with-application-counts /openapi/public-api-v1.json get /jobs/{jobId}/stages Returns the job pipeline stages with applicationCount per stage. Use stageId with GET /jobs/{jobId}/applications to list a specific stage. Requires scope `jobs:read` (eval + production keys). # API usage meters Source: https://developers.cutshort.io/api-reference/usage/api-usage-meters /openapi/public-api-v1.json get /usage Returns the workspace's cycle usage as used/limit meter pairs for unlocks, searches (structured + AI combined), and candidate messages. `cycleEndsOn` is when usage resets; `planExpiresOn` is when the subscription ends (non-free plans). Check before spending unlocks or messages. No special scope — any valid API key. # Authentication Source: https://developers.cutshort.io/authentication API keys, scopes, and request headers All `/api/v1/*` requests require a workspace API key in the `Authorization` header. ```http theme={null} Authorization: Bearer cs_live_ Content-Type: application/json ``` ## Where to get a key | Step | Detail | | ------------------ | ---------------------------------------------------------------------------------------------- | | **Prerequisite** | Be an **approved employer** on Cutshort (see [Introduction](/introduction#getting-api-access)) | | **In the product** | [Settings → Integrations → API Key](https://cutshort.io/profile/hiring-settings/integrations) | | **Generate** | Choose **Eval access** or **Production access**, then click **Generate API key** | When generating a key you choose: | Option | Availability | Scopes | | ---------------------------------- | ------------------------------------ | ---------------------------------------------------------------------------------------------- | | **Eval access** (limited quotas) | All approved employers | `search:read`, `candidates:read`, `applications:read`, `jobs:read` | | **Production access** (all scopes) | Workspace has production API enabled | Adds job posting (`jobs:write`) and candidate messaging (`messages:write`), with higher quotas | Production self-serve requires Cutshort to enable access on your workspace. Email **[hello@cutshort.io](mailto:hello@cutshort.io?subject=Cutshort%20API%20production%20access%20request)** to request it. ## Key properties | Property | Value | | -------- | ------------------------------------------------------------------ | | Prefix | `cs_live_` | | Scope | One workspace | | Storage | Treat like a password — do not commit to git or share in chat logs | Keys minted in **Settings → Integrations** can be **Revealed** again later from the same page. Keys provisioned externally by Cutshort may not support Reveal — use the copy you received at creation. ## Scopes Each key carries a set of **scopes** that control which endpoints it can call. If a key lacks a scope, the API returns `403` with `error_code: insufficient_scope`. | Endpoint | Scope | | ------------------------------------ | ------------------------------------------------------------------------------------------- | | `GET /search/candidates` | `search:read` (eval + production) | | `GET /candidates/{talentCardId}` | `candidates:read` (eval + production; auto-unlock is metered by the workspace unlock quota) | | `GET /jobs` | `jobs:read` (eval + production) | | `GET /jobs/{jobId}/stages` | `jobs:read` (eval + production) | | `POST /jobs` | `jobs:write` (production keys) | | `POST /invite-to-apply` | `messages:write` (production keys) | | `POST /messages` | `messages:write` (production keys) | | `POST /applications/{id}/move-stage` | `stages:write` (production keys) | | `GET /usage` | Any valid key | ## Errors | HTTP | `error_code` | Meaning | | ---- | ---------------------------- | ----------------------------------------------------- | | 401 | `invalid_api_key` | Missing, malformed, revoked, or unknown key | | 403 | `insufficient_scope` | Key cannot call this endpoint | | 403 | `approved_employer_required` | Only approved employers can manage keys in Settings | | 403 | `beta_access_required` | Workspace does not have production API access enabled | ## API support Email **[engineering@cutshort.io](mailto:engineering@cutshort.io?subject=Cutshort%20API%20support)** for API questions, integration help, or quota issues. Send the mail from the email address on your Cutshort account so we can identify your workspace. # Get candidate details Source: https://developers.cutshort.io/guides/get-candidate-details Contact info, resume file, and auto-unlock credits Search hits give you triage data only — **no name, email, or phone**. To contact a candidate you found in search, call **`GET /candidates/{talentCardId}`**. If the candidate is still locked for your workspace, this call **unlocks them automatically** — spending **one unlock credit** — and returns the full detail in the same response. There is no separate unlock endpoint. Available to **eval and production keys** (`candidates:read`). Eval workspaces get 10 unlock credits per cycle by default; production workspaces get higher grants. Check `GET /usage`. ## Typical flow ```mermaid theme={null} sequenceDiagram participant You participant Search as GET /search/candidates participant Usage as GET /usage participant Detail as GET /candidates/:id You->>Search: Find talentCardId Search-->>You: profileSignals, redactedResumeText (no contacts) You->>Usage: Check apiUnlocks used/limit Usage-->>You: credits remaining You->>Detail: Fetch shortlisted candidate Detail-->>You: email, phone, resume (unlockedNow: true, 1 credit) You->>Detail: Fetch same candidate again later Detail-->>You: same data (unlockedNow: false, no charge) ``` 1. **Search** — pick `talentCardId` values from [Search candidates](/guides/search-candidates). 2. **Shortlist using triage data** — use `profileSignals` and `redactedResumeText` on search hits; do not fetch details speculatively. 3. **Check credits** — `GET /usage` → `apiUnlocks` (`used` vs `limit`). 4. **Fetch details** — `GET /candidates/{talentCardId}` for each candidate you want to contact. Locked candidates are unlocked on the spot (irreversible, one credit each). ## GET /candidates/ ``` GET /api/v1/candidates/{talentCardId} ``` **Scope:** `candidates:read` **Rate limits:** 30 sensitiveRead + 120 global requests per 60s per API key. ### Response ```json theme={null} { "talentCardId": "64f1a2b3c4d5e6f7a8b9c0d1", "lockStatus": "unlocked", "unlockedNow": true, "displayName": "Priya Sharma", "email": "priya@example.com", "phone": "+919876543210", "originalResumeFileName": "Priya_Sharma_Resume.pdf", "originalResumeDownloadUrl": "https://s3.amazonaws.com/…/original.pdf?X-Amz-…", "profile": { "text_from_resume": "…full resume text…", "past_companies": ["…"] } } ``` | Field | Meaning | | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- | | `unlockedNow` | `true` = this call spent one unlock credit; `false` = candidate was already unlocked (no charge) | | `originalResumeDownloadUrl` + `originalResumeFileName` | Signed link to the candidate's uploaded resume file (PDF/DOC) — same as **Download resume** in the Cutshort UI | | `profile` | Structured profile including the actual resume text (\~8k chars), past companies, education | Resume file URLs expire after **1 hour**. Re-call the endpoint for fresh links — repeat calls on unlocked candidates are free. ## Unlock credits Each **new** unlock counts against your workspace's `apiUnlocks` meter for the current cycle. Already-unlocked candidates never re-charge. Check remaining credits: ```bash theme={null} curl -s "https://cutshort.io/api/v1/usage" \ -H "Authorization: Bearer cs_live_YOUR_KEY" ``` ```json theme={null} { "apiUnlocks": { "used": 3, "limit": 10 }, "searches": { "used": 40, "limit": 100 }, "messages": { "used": 2, "limit": 20 }, "cycleEndsOn": "2026-08-15T00:00:00.000Z" } ``` When API credits are exhausted, `GET /candidates/{id}` on a locked candidate returns **429** with `error_code: api_unlock_limit_reached`. Already-unlocked candidates keep working. If the API quota still shows remaining credits but unlock fails, the workspace's **product plan unlock pool** may be exhausted. This returns `429 plan_unlock_limit_reached` — a separate limit from the API quota. ## Example — search, then fetch ```bash theme={null} # 1. Search curl -s -G "https://cutshort.io/api/v1/search/candidates" \ -H "Authorization: Bearer cs_live_YOUR_KEY" \ --data-urlencode "q=senior python backend" \ --data-urlencode "minexp=4" \ --data-urlencode "page=1" # 2. Check credits curl -s "https://cutshort.io/api/v1/usage" \ -H "Authorization: Bearer cs_live_YOUR_KEY" # 3. Fetch details for the shortlist (auto-unlocks; 1 credit per new candidate) curl -s "https://cutshort.io/api/v1/candidates/TALENT_CARD_ID" \ -H "Authorization: Bearer cs_live_YOUR_KEY" ``` ## Best practices * **Shortlist from search first** — use [Profile signals](/guides/profile-signals) and `redactedResumeText`; only fetch details for candidates you intend to contact. * **Confirm before fetching in bulk** — each new candidate spends a credit and unlocking cannot be undone. * **Check `apiUnlocks`** before batch fetches. * **Fetch sequentially** — not in parallel — to stay within rate limits and make quota errors easier to handle. ## Related * [Search candidates](/guides/search-candidates) — find `talentCardId` values * [Invite to apply](/guides/invite-to-apply) — outreach invite (no unlock needed) * [Rate limits](/guides/rate-limits) — burst caps and quotas * [Authentication](/authentication) — eval vs production key scopes # Invite to apply Source: https://developers.cutshort.io/guides/invite-to-apply Outreach shortlisted candidates with an invite to apply to your job Requires a **production key** with the `messages:write` scope. To upgrade from eval access, email [hello@cutshort.io](mailto:hello@cutshort.io). For API support, email [engineering@cutshort.io](mailto:engineering@cutshort.io) from your Cutshort account email. `POST /invite-to-apply` sends a candidate an **invite to apply** to one of your jobs, with your custom message. This is **first-contact** outreach in the core loop: after searching and shortlisting, invite the best candidates to your posted job. The candidate is notified by Cutshort. If they reply or apply, the conversation and application appear in your Cutshort inbox and job pipeline. No unlock is needed — you can invite any candidate found in search using their `talentCardId`. ## When to use this vs send message | | `POST /invite-to-apply` (this guide) | [`POST /messages`](/guides/send-message) | | -------------------------- | ------------------------------------------- | ---------------------------------------- | | **Use for** | First contact from search / talent database | Follow-up in an existing thread | | **Needs** | `talentCardId` + `jobId` + `message` | `threadId` + `message` | | **If already in pipeline** | **409** — do not retry; use send message | Correct tool for replies | After inviting, [list applications](/guides/list-applications) for `threadId`s, then [send a message](/guides/send-message) for follow-ups. ## Request ``` POST /api/v1/invite-to-apply ``` ```json theme={null} { "talentCardId": "64f1a2b3c4d5e6f7a8b9c0d1", "jobId": "64f1a2b3c4d5e6f7a8b9c0d2", "message": "Hi! Your data engineering background looks like a great fit for our Senior Data Engineer role. We'd love for you to apply — you can also book a chat directly: https://cal.com/acme/intro" } ``` | Field | Required | Notes | | -------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | `talentCardId` | Yes | From [search results](/guides/search-candidates) | | `jobId` | Yes | A job in your workspace — typically from [`POST /jobs`](/guides/post-a-job) | | `message` | Yes | Plain text, max 3,000 characters. Personalize it — reference their specific experience. Including a scheduling link (Calendly, cal.com) works well. | ## Response ```json theme={null} { "success": true, "talentCardId": "64f1a2b3c4d5e6f7a8b9c0d1", "jobId": "64f1a2b3c4d5e6f7a8b9c0d2" } ``` ## One invite per candidate per job Each (job, candidate) pair can receive **one** invite. If they were already invited **or** already exist in that job’s pipeline, a second attempt returns **409** `already_exists_in_job_pipeline`. To reach the same candidate about a different role, use that role's `jobId`. ## Limits * **Rate limit:** 10 `inviteToApply` + 120 global requests per 60s per API key * **Quota:** 20 messages per cycle by default (workspace-level) — check `messages` in [`GET /usage`](/guides/rate-limits#checking-usage) ## Typical flow 1. [Search candidates](/guides/search-candidates) and shortlist using `profileSignals` and the resume excerpt 2. [Post a job](/guides/post-a-job) (or reuse an existing `jobId`). New jobs are reviewed first and typically go live in \~30 minutes on business days — you can still invite candidates with the returned `jobId` immediately. 3. Optionally [get candidate details](/guides/get-candidate-details) for a closer look before reaching out 4. Invite each shortlisted candidate with a personalized note ```bash theme={null} curl -s -X POST "https://cutshort.io/api/v1/invite-to-apply" \ -H "Authorization: Bearer cs_live_PROD_KEY" \ -H "Content-Type: application/json" \ -d '{ "talentCardId": "TALENT_CARD_ID", "jobId": "JOB_ID", "message": "Hi! We loved your profile and would like you to apply…" }' ``` ## Errors | `error_code` | HTTP | Meaning | | -------------------------------- | ---- | ---------------------------------------------------- | | `insufficient_scope` | 403 | Key lacks `messages:write` (eval keys cannot invite) | | `talent_card_not_found` | 404 | Unknown `talentCardId` | | `job_not_in_workspace` | 404 | Job does not belong to your workspace | | `already_exists_in_job_pipeline` | 409 | Already invited or already in this job’s pipeline | | `api_message_limit_reached` | 429 | Message quota exhausted for the cycle | | `message_candidate_failed` | 502 | Upstream send failed — safe to retry once | # List applications Source: https://developers.cutshort.io/guides/list-applications List candidates in a job stage with fitment filter and optional auto-unlock `GET /jobs/{jobId}/applications` returns applications in one pipeline stage — by default the **Applications** stage — with triage fields, contact details, resume download URL, and screening Q\&A. Requires scope `applications:read`. ## Request ``` GET /api/v1/jobs/{jobId}/applications?fitment=strong,fair&autoUnlock=true&page=1&pageSize=10 ``` | Param | Default | Notes | | ------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `stageId` | Applications stage | From [List stages](/guides/list-stages). Omit to use the applications stage. | | `fitment` | `strong,fair` | Comma-separated: `strong`, `fair`, `weak`. Rows with no AI fitment verdict are always included. | | `autoUnlock` | `true` | Unlock locked candidates on the page. Upstream unlock always runs; **API unlock quota applies only on the sent stage**. `false` returns **only already-unlocked** applications. | | `sort` | `relevance` | `relevance` or `recency` | | `page` / `pageSize` | `1` / `10` | Max pageSize 20 | ### Fitment | Alias | Internal value | When to use | | -------- | -------------- | -------------------------------------- | | `strong` | `strong_fit` | Tight shortlist when the pool is large | | `fair` | `maybe_a_fit` | Included in the default | | `weak` | `not_a_fit` | Include when the pool is thin | Default `strong,fair` excludes weak / not-a-fit. ### Auto-unlock With `autoUnlock=true` (default), every locked candidate on the **returned page** is unlocked first. Unlocks always run upstream (plan credits, download counts, events). **Only the sent stage** counts against the workspace API unlock quota — same preflight as [Get candidate details](/guides/get-candidate-details) when listing sent. If remaining API unlock quota is less than the number of locked candidates on that page, the call returns **429** `api_unlock_limit_reached` and unlocks nothing. With `autoUnlock=false`, locked applications are omitted — you only get candidates already unlocked for your workspace. No unlocks are attempted. ## Response ```json theme={null} { "jobId": "…", "stages": [ { "stageId": "…", "stageType": "candidates", "stageName": "Applications", "applications": [ { "applicationId": "…", "threadId": "…", "talentCardId": "…", "displayName": "…", "email": "…", "phone": "…", "headline": "…", "joiningAvailability": { "status": "on_notice_period", "noticePeriodEndsOn": "2026-09-01T00:00:00.000Z", "canJoinHowSoonDays": null }, "lastSeen": "2026-08-01T12:00:00.000Z", "relevance": { "fitment": "strong_fit", "fitmentReason": "…", "shortlistingCriteriaScore": 3 }, "originalResumeDownloadUrl": "…", "screeningQuestions": [], "unlockedNow": true } ] } ], "page": 1, "pageSize": 10, "totalCount": 12 } ``` The `stages` array is shaped so multiple stages can be returned later; today it always contains the one requested stage. `threadId` (when present) is the conversation for [Send a message](/guides/send-message) follow-ups. Do not use [Invite to apply](/guides/invite-to-apply) again for these candidates. ## Typical flow after outreach 1. [Invite candidates](/guides/invite-to-apply) for first contact 2. Later: list applications on the job (default stage) to see who applied 3. [Send a message](/guides/send-message) using `threadId` for follow-ups 4. Optionally [list stages](/guides/list-stages) if you need counts in other pipeline stages # List workspace jobs Source: https://developers.cutshort.io/guides/list-jobs Get your active jobs and their IDs for use in search and outreach `GET /jobs` returns the jobs in your workspace. Use the returned `jobId` to list stages, list applications, invite candidates to apply, or check pipeline counts. Requires scope `jobs:read` — available to both **eval and production keys**. ## Request ``` GET /api/v1/jobs ``` | Parameter | Type | Default | Description | | --------------- | ------- | ------- | ----------------------------------------------------------------- | | `page` | integer | 1 | Page number | | `pageSize` | integer | 10 | Results per page (max 20) | | `includePaused` | boolean | false | Set `true` to also return paused jobs. Deleted jobs never appear. | ## Response ```json theme={null} { "results": [ { "jobId": "64f1a2b3c4d5e6f7a8b9c0d1", "title": "Senior Backend Engineer", "status": "active", "createdOn": "2026-07-01T10:00:00.000Z" } ], "page": 1, "pageSize": 10, "totalCount": 3 } ``` | Field | Meaning | | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `jobId` | Use this in [Invite to apply](/guides/invite-to-apply), [List stages](/guides/list-stages), and [List applications](/guides/list-applications) | | `status` | `active` or `paused` | ## Next steps * [List stages](/guides/list-stages) — pipeline stages and application counts for a job * [List applications](/guides/list-applications) — candidates in a stage * [Invite to apply](/guides/invite-to-apply) — send a personalized invite (production keys) # List job stages Source: https://developers.cutshort.io/guides/list-stages See pipeline stages and application counts for a job `GET /jobs/{jobId}/stages` returns the hiring pipeline stages for a job, including how many applications are in each stage. Use this when you need counts across the pipeline, or a `stageId` to pass to [List applications](/guides/list-applications) for a non-default stage. Requires scope `jobs:read` (eval and production keys that can list jobs). ## Request ``` GET /api/v1/jobs/{jobId}/stages ``` ## Response ```json theme={null} { "results": [ { "stageId": "…", "title": "Applications", "type": "candidates", "order": 1, "applicationCount": 12, "automations": [] } ] } ``` The applications inbox stage has `type: "candidates"`. That is the default stage used by list-applications when `stageId` is omitted. Use `stageId` with [List applications](/guides/list-applications) or [Move application stage](/guides/move-application-stage). # MCP Server Source: https://developers.cutshort.io/guides/mcp-server Connect Claude, Cursor, or any MCP client to Cutshort Give your AI agent recruiting superpowers — Claude, Cursor, and other agents connect through MCP to Cutshort to find candidates, compare, outreach, and set up interviews The Cutshort [MCP](https://modelcontextprotocol.io) server gives AI agents programmatic access to talent search, candidate detail, job posting, and outreach — using the same API key and quotas as the [REST API](/introduction). **Endpoint:** `https://mcp.cutshort.io/mcp` You need a Cutshort API key (`cs_live_…`) first. Approved employers can generate one at [Settings → Integrations → API Key](https://cutshort.io/profile/hiring-settings/integrations) — start with **Eval access**. See [Getting API access](/introduction#getting-api-access). ## Connect from Claude Desktop Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS): ```json theme={null} { "mcpServers": { "cutshort": { "command": "npx", "args": [ "mcp-remote", "https://mcp.cutshort.io/mcp", "--header", "Authorization: Bearer cs_live_" ] } } } ``` Restart Claude Desktop. In **Settings → Developer**, you should see **cutshort** listed with status **running**: Claude Desktop Developer settings showing the cutshort local MCP server running with npx mcp-remote configuration You should also see **cutshort** in the chat tool list. Requires Node.js 18+. ## Connect from Cursor In **Cursor Settings → MCP**, add: ```json theme={null} { "cutshort": { "url": "https://mcp.cutshort.io/mcp", "headers": { "Authorization": "Bearer cs_live_" } } } ``` ## Connect with mcp-remote (CLI) ```bash theme={null} npx mcp-remote https://mcp.cutshort.io/mcp \ --header "Authorization: Bearer $CUTSHORT_API_KEY" ``` ## Eval vs production keys | | Eval key | Production key | | --------------------------------------------- | --------------------- | ----------------------------------------- | | Search & candidate detail | Yes (small quotas) | Yes | | Post jobs, invite / send message, delete jobs | No | Yes | | Typical use | Try MCP before buying | Full sourcing → outreach → follow-up loop | Tool registration is scope-aware: the server reads `GET /capabilities` at connect time and only exposes tools your key can use. ## Available tools | Tool | Eval | Production | Notes | | ------------------------ | ---- | ---------- | -------------------------------------------------------------------------------------------------------------- | | `get_usage` | ✓ | ✓ | Check quotas before bulk unlock | | `kickstart_sourcing` | ✓ | ✓ | Recommended first step for a new search | | `search_candidates` | ✓ | ✓ | Full filter control — see [Search guide](/guides/search-candidates) | | `get_searched_candidate` | ✓ | ✓ | Auto-unlocks (1 credit) — confirm before bulk fetch | | `list_jobs` | ✓ | ✓ | | | `list_applications` | ✓ | ✓ | Default Applications stage; optional `stageId`; `autoUnlock` defaults to true (only sent stage is API-metered) | | `get_job_stages` | ✓ | ✓ | Pipeline stages + counts — source of `stageId` | | `post_job` | — | ✓ | Reviewed then live (\~30 min on business days) | | `invite_to_apply` | — | ✓ | **First contact** invite — confirm first | | `send_message` | — | ✓ | **Follow-up** in an existing thread (`threadId` from `list_applications`) — confirm first | | `move_application_stage` | — | ✓ | Move application to another stage — confirm first | | `delete_job` | — | ✓ | Soft-delete — confirm first | ### Messaging guidance | Tool | Use when | | ----------------- | --------------------------------------------------------------------------------------------- | | `invite_to_apply` | Candidate is from search / talent DB and **not** yet in that job’s pipeline | | `send_message` | Candidate already has a conversation (`threadId` from `list_applications` or invite response) | Do not call `invite_to_apply` again for someone already invited or already in Applications — that returns **409**. Use `send_message` instead. ### Pipeline guidance | Tool | Use when | | ------------------------ | ------------------------------------------------ | | `get_job_stages` | You need `stageId`s / counts across the pipeline | | `move_application_stage` | Move a candidate after confirming with the human | From **Applications**, only Shortlisted or Rejected are valid targets (`invalid_stage_move` otherwise). See [Move application stage](/guides/move-application-stage). ### Agent resource At session start, agents can read **`cutshort://agent-guide`** — deep reference on search filters, profile signals, auto-unlock, pagination, and error codes. Same content as `GET /api/v1/agent-guide`. ## Starter prompt Try this after connecting: > I'm hiring a senior backend engineer in Bangalore, 4–8 years, strong on Go and Kubernetes, 25–45 LPA. Search Cutshort and show me the top matches with headline, experience, and a short fit reason from the resume text. With a **production key**, you can extend the flow: post a job → `invite_to_apply` → `list_applications` → `send_message` / `move_application_stage`. See [Invite to apply](/guides/invite-to-apply), [Send a message](/guides/send-message), [Move application stage](/guides/move-application-stage), and [List applications](/guides/list-applications). ## Security * Treat API keys like passwords — do not commit them or paste them in public chats. * `get_searched_candidate` auto-unlocks locked candidates (1 credit each). * Write tools (`post_job`, `invite_to_apply`, `send_message`, `move_application_stage`, `delete_job`) instruct agents to confirm with you before calling. ## Support For API or MCP integration help, email [engineering@cutshort.io](mailto:engineering@cutshort.io?subject=Cutshort%20MCP%20support) from the email on your Cutshort account. Source code: [github.com/CutShortHQ/cutshort-mcp](https://github.com/CutShortHQ/cutshort-mcp) # Move application stage Source: https://developers.cutshort.io/guides/move-application-stage Move a candidate to another pipeline stage Requires a **production key** with the `stages:write` scope. To upgrade from eval access, email [hello@cutshort.io](mailto:hello@cutshort.io). For API support, email [engineering@cutshort.io](mailto:engineering@cutshort.io) from your Cutshort account email. `POST /applications/{applicationId}/move-stage` moves one application to a different hiring pipeline stage for the same job. ## When to use 1. [List applications](/guides/list-applications) to get `applicationId` 2. [List stages](/guides/list-stages) to get the target `stageId` 3. Move the application (confirm first — stage changes can trigger ATS automations) ## Applications-stage rule From the **Applications** inbox (`type: candidates`), you can only move to: | Target stage type | Typical title | Effect | | ----------------- | ------------- | -------------------- | | `connected` | Shortlisted | Same as UI shortlist | | `rejected` | Rejected | Same as UI reject | Any other target from Applications returns **400** `invalid_stage_move`. From other stages, you can move by `stageId` directly. ## Request ``` POST /api/v1/applications/{applicationId}/move-stage ``` ```json theme={null} { "stageId": "64f1a2b3c4d5e6f7a8b9c0d4" } ``` | Field | Required | Notes | | --------- | -------- | -------------------------------------------------------- | | `stageId` | Yes | From [List stages](/guides/list-stages) for the same job | ## Response ```json theme={null} { "success": true, "applicationId": "…", "movedToStage": { "stageId": "…", "title": "Shortlisted", "type": "connected" }, "taskId": null } ``` ```bash theme={null} curl -s -X POST "https://cutshort.io/api/v1/applications/APPLICATION_ID/move-stage" \ -H "Authorization: Bearer cs_live_PROD_KEY" \ -H "Content-Type: application/json" \ -d '{ "stageId": "STAGE_ID" }' ``` ## Errors | `error_code` | HTTP | Meaning | | ------------------------- | ---- | --------------------------------------------------------------------- | | `insufficient_scope` | 403 | Key lacks `stages:write` | | `application_not_found` | 404 | Unknown application | | `stage_not_found_for_job` | 404 | `stageId` not on this job | | `invalid_stage_move` | 400 | Illegal move (e.g. Applications → Interview without shortlist/reject) | # Post a job Source: https://developers.cutshort.io/guides/post-a-job Create a job with auto-configured pipeline and outreach Requires a **production key** with the `jobs:write` scope. To upgrade from eval access, email [hello@cutshort.io](mailto:hello@cutshort.io). For API support, email [engineering@cutshort.io](mailto:engineering@cutshort.io) from your Cutshort account email. `POST /jobs` creates a fully configured job from minimal input. Cutshort derives skills and tags, attaches an auto-generated screening questionnaire, and enables auto-outreach to matching candidates. After posting, the job is reviewed and then goes live — typically **\~30 minutes on business days**. ## Request ```json theme={null} { "title": "Senior Data Engineer", "remoteType": "remote_okay", "locations": ["Bangalore", "Pune"], "experienceRange": { "min": 4, "max": 8 }, "salaryRange": { "min": 2500000, "max": 4500000 }, "hideSalary": false, "mandatorySkillsMatchType": "any", "roleType": "full_time", "jobDescription": "Full JD text…", "keyRequirements": [ { "requirement": "Must have worked primarily on data engineering, not general backend", "importance": "must_have" }, { "requirement": "Early-stage startup experience", "importance": "nice_to_have" } ] } ``` ### Fields | Field | Required | Notes | | -------------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `title` | Yes | Max 80 characters | | `remoteType` | Yes | `remote_only`, `remote_okay`, or `remote_not_okay` | | `locations` | If not remote-only | Plain city names — canonicalized server-side (max 5) | | `experienceRange` | Yes | `{ min, max }` in years | | `salaryRange` | Yes | INR per annum (`2500000` = ₹25 LPA) | | `hideSalary` | No | `true` hides the salary range on the public job page (default `false`) | | `mandatorySkillsMatchType` | No | How derived must-have skills gate matching: `any` (match any) or `all` (match all). Defaults to `any` when omitted (same as the job-setup UI). Always persisted so the job can be edited later in Cutshort. | | `roleType` | No | `full_time` (default) or `internship` | | `jobDescription` | Yes | Max 20,000 characters | | `keyRequirements` | No | Max 8 items | | `teamId` | No | Post under a specific team; defaults to your first team in the workspace | ### `keyRequirements` rules * Default importance is `nice_to_have` * Use `must_have` only for outright-rejection criteria — we **strongly recommend at most 3** (more are accepted, but each must-have shrinks outreach reach) * Over-tight must-haves shrink reach to zero — prefer `nice_to_have` for preferences Experience range and (for non-remote roles) location criteria are added automatically — do not duplicate them in `keyRequirements`. ## Response ```json theme={null} { "jobId": "…", "status": "submitted_for_review", "publicUrl": null } ``` After posting, the job is **reviewed and then goes live**. Review typically takes **\~30 minutes on business days**. Matching-candidate outreach and the public job page start once approved; `publicUrl` populates when the job is live. ## Delete a job ``` DELETE /api/v1/jobs/{jobId} ``` Soft-deletes the job (same as deleting in the Cutshort UI). It disappears from `GET /jobs`. Optional body: `{ "reason": "duplicate_job", "reasonText": "…" }`. ## Rate limit 5 `createJob` + 120 global requests per 60 seconds per API key. ## Typical flow with search 1. Use [Search candidates](/guides/search-candidates) to validate role criteria and pool size 2. Post the job with aligned title, experience, salary, and locations 3. [Invite shortlisted candidates](/guides/invite-to-apply) to apply 4. Monitor applicants in the Cutshort product UI ## Errors | `error_code` | HTTP | Meaning | | ----------------------- | ---- | --------------------------------------------------- | | `insufficient_scope` | 403 | Key lacks `jobs:write` (eval keys cannot post jobs) | | `location_not_resolved` | 400 | City name could not be canonicalized | | `no_team_in_workspace` | 400 | Acting user has no team in workspace | | `team_not_in_workspace` | 400 | Provided `teamId` does not belong to your workspace | | `job_create_failed` | 502 | Upstream creation failed | # Profile signals Source: https://developers.cutshort.io/guides/profile-signals AI-evaluated pedigree, quality, and industry tags on search hits Every search hit includes a **`profileSignals`** object — pre-digested AI evaluation from the candidate's profile. Use it for triage alongside `redactedResumeText`. ## Shape ```json theme={null} { "educationPedigree": { "score": 3, "level": "tier1plus", "reason": "B.Tech from IIT Delhi…" }, "workQuality": { "score": 3, "level": "high", "reason": "…" }, "careerGrowth": { "score": 2, "level": "fast", "reason": "…" }, "startupExperience": { "score": 3, "level": "funded_startup", "reason": "…" }, "entrepreneurship": { "score": 2, "level": "early_employee", "reason": "…" }, "jobTenure": { "score": 1, "level": "stable", "reason": "…" }, "industryTags": [ { "tag": "ecommerce", "label": "E-commerce", "score": 3, "reason": "…" } ] } ``` Each scalar field is either `{ score, level, reason }` or **`null`**. ## How to interpret | Field | Meaning | | ------------ | ------------------------------------------------------- | | **`reason`** | Authoritative one-line explanation — show this to users | | **`level`** | Normalized bucket for comparison | | **`score`** | Raw strength 0–3 | | **`null`** | Not evaluated — **not a negative signal** | ## Scalar fields and levels | Field | Levels (best → weakest) | | ------------------- | ----------------------------------- | | `educationPedigree` | `tier1plus` → `tier1` → `tier2` | | `workQuality` | `high` → `medium` → `low` | | `careerGrowth` | `fast` → `moderate` → `slow` | | `startupExperience` | `funded_startup` → `other_startup` | | `entrepreneurship` | `cofounder` → `early_employee` | | `jobTenure` | `short_tenure` → `mixed` → `stable` | ### Education pedigree ↔ search filter | `level` | Colleges | | ----------- | ----------------------------------------- | | `tier1plus` | IIT, IIM, BITS Pilani, top NIT/IIIT | | `tier1` | Other NITs, IIITs, top state universities | | `tier2` | Other engineering / management colleges | Filter with `eduPed=tier1plus-tier1` on search, or run a broader search and rank by `profileSignals.educationPedigree` post-fetch. ## Industry tags `industryTags` lists domain experience (e-commerce, B2B SaaS, fintech, etc.) with `label`, `score`, and `reason`. Only tags with score ≥ 2 are included. Filter by domain at search time with the `domainTags` param. ## Mapping to search filters | Signal / level | Search param | | ------------------------------------------ | ------------------------------------ | | `educationPedigree.level` | `eduPed=tier1plus-tier1` etc. | | `startupExperience.level = funded_startup` | `startupExp=funded_startup` | | `entrepreneurship.level = cofounder` | `entrExp=cofounder` | | `industryTags` | `domainTags=b2b_saas-ecommerce` etc. | `workQuality`, `careerGrowth`, and `jobTenure` are **response-only** signals — there is no search filter for them. When those matter, run a broader search and rank hits by the relevant `profileSignals` field post-fetch. When requirements are preferences (not hard filters), prefer that broader-search-then-rank approach — or use `profileAttributesMode=or` (see [Search candidates](/guides/search-candidates)) instead of over-narrowing filters. # Rate limits and quotas Source: https://developers.cutshort.io/guides/rate-limits Burst limits, cycle quotas, and error handling Two separate mechanisms limit API usage: 1. **Rate limits** — per-minute burst protection, keyed by **API key**. Hitting one means *slow down and retry*. 2. **Cycle quotas** — workspace-level `used`/`limit` meters (unlocks, searches, messages) that reset each cycle. Hitting one means *you're done until reset or upgrade*. ## Request rate limits (stacking) Every authenticated request consumes the **global** limit. Some routes also consume a **route-class** limit. | Class | Limit (per key / 60s) | Block duration | Routes | | ----------------- | --------------------- | -------------- | -------------------------------- | | **global** | 120 | 60s | All `/api/v1/*` | | **search** | 10 | 120s | `GET /search/candidates` | | **sensitiveRead** | 30 | 60s | `GET /candidates/{talentCardId}` | | **createJob** | 5 | 300s | `POST /jobs` | | **inviteToApply** | 10 | 120s | `POST /invite-to-apply` | ### On rate limit exceeded HTTP **429** with: ```json theme={null} { "error_code": "rate_limit_exceeded", "error_description": "Rate limit exceeded for this search endpoint. Retry after 42 seconds." } ``` The response includes a **`Retry-After`** header (seconds). Back off and retry — do not retry in a tight loop. ## Cycle quotas Workspace-level running totals, reset each usage cycle. Defaults (production workspaces get custom grants): | Meter | Default limit / cycle | Consumed by | Exhausted → `error_code` | | ------------ | --------------------- | -------------------------------------------------------------- | --------------------------- | | `apiUnlocks` | 10 | `GET /candidates/{id}` on a **locked** candidate (auto-unlock) | `api_unlock_limit_reached` | | `searches` | 100 | `GET /search/candidates` (each page request) | `api_search_limit_reached` | | `messages` | 20 | `POST /invite-to-apply` only (not `POST /messages` follow-ups) | `api_message_limit_reached` | Quota errors are also HTTP **429**, but retrying does not help — the meter resets at `cycleEndsOn`, or contact Cutshort for a higher grant. ## Checking usage `GET /usage` returns every meter as a `used`/`limit` pair: ```json theme={null} { "workspaceId": "…", "apiUnlocks": { "used": 3, "limit": 10 }, "searches": { "used": 42, "limit": 100 }, "messages": { "used": 5, "limit": 20 }, "cycleEndsOn": "2026-08-15T00:00:00.000Z", "planExpiresOn": null } ``` | Field | Meaning | | --------------- | ------------------------------------------------------------ | | `cycleEndsOn` | When usage meters reset | | `planExpiresOn` | When the workspace subscription ends (`null` for free plans) | ## Other 429 responses | `error_code` | Meaning | | --------------------------- | -------------------------------------------------------------------- | | `rate_limit_exceeded` | Burst limit — retry after `Retry-After` seconds | | `api_unlock_limit_reached` | Unlock quota exhausted for the cycle | | `api_search_limit_reached` | Search quota exhausted for the cycle | | `api_message_limit_reached` | Message quota exhausted for the cycle | | `search_throttled` | Plan-level search throttle from upstream (not the Redis burst limit) | ## Best practices * Run searches sequentially, not in parallel * Refine filters instead of paginating deep — every page request costs one search * Check `apiUnlocks` before batch candidate fetches * Cache results client-side when iterating on the same role * Check `Retry-After` on 429 and exponential-backoff retry (burst limits only) # Search candidates Source: https://developers.cutshort.io/guides/search-candidates Structured filters, AI search, and resume boolean queries `GET /search/candidates` is the primary v1 endpoint. It searches Cutshort's talent database and returns **10 hits per page** with triage data — no identity or contact fields. ## Response fields Each hit includes: | Field | Description | | ----------------------------------- | ---------------------------------------------------------------------------------------------- | | `talentCardId` | Stable ID for follow-up API calls — see [Get candidate details](/guides/get-candidate-details) | | `experience` | Total years | | `locations` | Desired work locations | | `currentLocation`, `currentCompany` | Current placement | | `joiningAvailability` | `{ status, noticePeriodEndsOn }` — status is `immediate` / `on_notice_period` / `not_resigned` | | `lastSeen` | Last activity on Cutshort (ISO timestamp) | | `currentSalary` | Annual INR | | `workHistory` | Compact strings, most recent first (max 8) | | `education` | Compact degree/college strings (max 4) | | `profileSignals` | AI-evaluated pedigree, quality, industry — see [Profile signals](/guides/profile-signals) | | `redactedResumeText` | Identity-stripped resume excerpt (\~4k chars) | Use `profileSignals` and `redactedResumeText` together to judge fit without contact details. When you want email, phone, or the resume file, call [Get candidate details](/guides/get-candidate-details) — it auto-unlocks the candidate (one credit). ## AI / NLP search At least one search filter is required (`q`, `inresume`, location/experience/salary, profile attributes, or `activity`). Bare pagination-only requests are rejected with `400 search_criteria_required`. Add `q` (up to 20,000 characters; inputs above 50,000 are rejected) for semantic ranking: ``` GET /search/candidates?q=ML engineer with startup pedigree&minexp=3&maxexp=8 ``` Cutshort rewrites `q` with a role-summary prompt, embeds it, and runs kNN search. Short phrases like `python developer` work. Combine `q` with structured filters — filters narrow the pool, `q` ranks within it. Response includes `searchType: "ai"` when `q` is present, otherwise `"structured"`. ## Skill search — use `inresume` Skill requirements are expressed with **`inresume`** — a Lucene boolean query over the **full resume text**. It catches every mention (tagged skills, job descriptions, projects), so recall is much higher than tag-based filtering. There is no separate skills parameter. ### Good `inresume` examples ``` (python OR django OR fastapi) AND (pytorch OR tensorflow OR sklearn OR "machine learning") (java OR kotlin OR go) AND (microservices OR "distributed systems") (react OR "react.js" OR nextjs) AND (typescript) AND (jest OR cypress) ``` Rules: * Use uppercase `AND` / `OR` / `NOT` * Quote phrases: `"machine learning"`, `"system design"` * Within a group, list synonyms and abbreviations with `OR` (`ML OR "machine learning"`) * Keep 2–4 skill groups — more groups reduce recall * Avoid version suffixes: `python`, not `python 3.10` * Do not URL-encode manually; pass the raw expression ## Location encoding | Param | Meaning | | -------------- | --------------------------------------------- | | `locations` | Where the candidate should be willing to work | | `currlocation` | Candidate's current city | Encoding: * Spaces → `+` (e.g. `Navi+Mumbai`) * Multiple cities → dash-separated (`Mumbai-Pune`) * Bangalore → `Bengaluru+%28Bangalore%29` ## Experience and salary Always pass **both** bounds when filtering experience: | Param | Notes | | ------------------- | ------------------------------- | | `minexp` / `maxexp` | Years; decimals OK (e.g. `4.5`) | | `minsal` / `maxsal` | Current CTC in INR | If only one experience bound is sent, the API fills the other automatically. ## Availability | Param | Meaning | | ---------------------------- | ---------------------------------------------------------------------------------- | | `canjoinin` | Max days to join (e.g. `30`) — includes candidates on notice with enough time left | | `onNoticePeriodOrNotWorking` | `true` = only candidates serving notice or not working (stricter than `canjoinin`) | ## Profile attribute filters | Param | Values (dash-separated) | Meaning | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | | `eduPed` | `tier1plus`, `tier1`, `tier2` | College pedigree | | `startupExp` | `funded_startup`, `other_startup` | Startup background | | `not_itExp` | `largeITServices`, `otherITServices` | Exclude IT-services background | | `entrExp` | `cofounder`, `earlyEmployee` | Entrepreneurship | | `domainTags` | `banking_and_finance`, `healthcare`, `ecommerce`, `computer_security`, `travel`, `edtech_education`, `gaming`, `consumer_internet`, `b2b_saas`, `software_product`, `gcc` | Industry / domain background (high-confidence matching) | | `activity` | `active-3-months`, `active-6-months` | Recency on Cutshort | These filters correspond to fields in [`profileSignals`](/guides/profile-signals) on each hit. Dash-separated values *within* one param are OR'd (e.g. `eduPed=tier1plus-tier1` = either tier). ### Combining profile attributes: AND vs OR By default, supplying more than one of `eduPed`, `startupExp`, `entrExp`, `domainTags` requires a candidate to match **all** of them. Add `profileAttributesMode=or` to match **any**: ``` # AND (default): tier1plus education AND cofounder background eduPed=tier1plus&entrExp=cofounder # OR: tier1plus education OR cofounder background eduPed=tier1plus&entrExp=cofounder&profileAttributesMode=or ``` `profileAttributesMode=or` affects only these four params. All other filters (location, experience, salary, `inresume`, availability, `activity`) always stay ANDed. ## Pagination | Param | Default | Max | | ---------- | ------- | ------ | | `page` | 1 | — | | `pageSize` | 10 | **20** | ## Search discipline * Run searches **sequentially** — not in parallel * Review each page before refining * Avoid near-identical queries (overlapping results waste quota) * When exploring multiple angles, use disjoint slices (different experience bands or locations) See [Rate limits](/guides/rate-limits) for burst caps. ## Example — ML engineer in Bangalore ```bash theme={null} curl -G "https://cutshort.io/api/v1/search/candidates" \ -H "Authorization: Bearer cs_live_YOUR_KEY" \ --data-urlencode 'q=ML model training production experience' \ --data-urlencode 'minexp=3' \ --data-urlencode 'maxexp=8' \ --data-urlencode 'eduPed=tier1plus-tier1' \ --data-urlencode 'locations=Bengaluru+%28Bangalore%29' \ --data-urlencode 'inresume=(python OR pytorch OR tensorflow) AND ("machine learning" OR ML)' ``` # Send a message Source: https://developers.cutshort.io/guides/send-message Reply in an existing candidate conversation thread Requires a **production key** with the `messages:write` scope. To upgrade from eval access, email [hello@cutshort.io](mailto:hello@cutshort.io). For API support, email [engineering@cutshort.io](mailto:engineering@cutshort.io) from your Cutshort account email. `POST /messages` sends a **follow-up reply** in an existing Cutshort conversation thread — for candidates who are already in your job pipeline (for example in Applications after an invite or apply). ## When to use this vs invite to apply | | [`POST /invite-to-apply`](/guides/invite-to-apply) | `POST /messages` (this guide) | | -------------------------- | -------------------------------------------------- | -------------------------------------------------------------- | | **Use for** | First contact from search / talent database | Follow-up in an existing thread | | **Needs** | `talentCardId` + `jobId` + `message` | `threadId` + `message` | | **If already in pipeline** | **409** `already_exists_in_job_pipeline` | Send here instead | | **Quota** | Counts against workspace `messages` meter | Not counted on the invite quota (still needs `messages:write`) | Get `threadId` from [List applications](/guides/list-applications) (each application row may include `threadId`), or from the invite response when present. ## Request ``` POST /api/v1/messages ``` ```json theme={null} { "threadId": "64f1a2b3c4d5e6f7a8b9c0d3", "message": "Thanks for applying — are you free for a 20-min intro this week? https://cal.com/acme/intro" } ``` | Field | Required | Notes | | ---------- | -------- | -------------------------------------------- | | `threadId` | Yes | Conversation thread for this job ↔ candidate | | `message` | Yes | Plain text reply | ## Response ```json theme={null} { "success": true, "threadId": "64f1a2b3c4d5e6f7a8b9c0d3", "messageId": "…" } ``` ## Typical flow 1. [Invite to apply](/guides/invite-to-apply) for first contact 2. [List applications](/guides/list-applications) to find candidates in Applications and their `threadId` 3. Send a personalized follow-up with `POST /messages` ```bash theme={null} curl -s -X POST "https://cutshort.io/api/v1/messages" \ -H "Authorization: Bearer cs_live_PROD_KEY" \ -H "Content-Type: application/json" \ -d '{ "threadId": "THREAD_ID", "message": "Thanks for applying — happy to chat this week…" }' ``` ## Errors | `error_code` | HTTP | Meaning | | ------------------------- | ---- | -------------------------------------------------- | | `insufficient_scope` | 403 | Key lacks `messages:write` (eval keys cannot send) | | `thread_not_in_workspace` | 404 | Unknown thread or not in your workspace | | `send_message_failed` | 400 | Upstream send failed — safe to retry once | # Introduction Source: https://developers.cutshort.io/introduction Search India's tech talent with the Cutshort Public API The Cutshort Public API lets you **search India's tech talent database, hire from it, and reach out to candidates** from your product, internal tools, AI agents, or automation scripts. Prefer an AI agent in Claude or Cursor? Connect the Cutshort MCP server with your API key (see the MCP package README). This site is the REST reference with an interactive playground. ## What's in v1 | Endpoint | Purpose | | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | | [`GET /search/candidates`](/api-reference/search/search-candidates) | **Primary** — structured + AI search, 10 results per page | | [`GET /candidates/{talentCardId}`](/api-reference/candidates/get-candidate-detail) | Full candidate detail — **auto-unlocks** locked candidates (spends one credit) | | [`GET /jobs`](/api-reference/jobs/list-jobs) | List your workspace's jobs | | [`POST /jobs`](/api-reference/jobs/create-job) | Post a job with auto-configured pipeline and outreach | | [`POST /message-candidate`](/api-reference/messaging/message-candidate) | Invite a candidate to apply to your job, with a custom message | | [`GET /jobs/{jobId}/stages`](/guides/list-stages) | Pipeline stages with application counts | | [`GET /jobs/{jobId}/applications`](/guides/list-applications) | Applications in a stage (default: Applications) | | [`GET /usage`](/api-reference/usage/get-usage) | Usage meters — `used`/`limit` for unlocks, searches, messages | **The core loop:** search candidates → get details for your shortlist (auto-unlock) → post a job → message candidates to apply → list applications later. ## Base URL ``` https://cutshort.io/api/v1 ``` ## What search returns Each hit includes headline, experience, salary, work history, education, **profileSignals** (AI-evaluated pedigree, quality, industry tags), and a **redacted resume excerpt** (\~4k chars). Search hits do **not** include candidate names, email, or phone. To get contact details and the actual resume for someone you shortlist, call [Get candidate details](/guides/get-candidate-details) — it unlocks the candidate automatically (one credit per new candidate). ## Getting API access ### In the product If you are an **approved employer** on Cutshort: 1. Sign in at [cutshort.io](https://cutshort.io/?auth=employer\&intent=mcp) 2. Go to **Settings → Integrations → API Key** 3. Choose key type and click **Generate API key** | Key type | Who can generate | What it can do | | --------------------- | -------------------------------------- | -------------------------------------------------------------------------------------------- | | **Eval access** | All approved employers | Search + get candidate details, within small quotas (10 unlocks, 100 searches per cycle) | | **Production access** | Workspaces with production API enabled | Everything, with higher workspace-specific quotas — adds job posting and candidate messaging | The **API Key** section appears only for approved employers. If you don't see it, complete employer verification on Cutshort first. ### Request production access Eval keys are available immediately — use them to try the core search → get-candidate experience. To upgrade to **production** keys with higher limits and the full endpoint set, email **[hello@cutshort.io](mailto:hello@cutshort.io?subject=Cutshort%20API%20production%20access%20request)** with your workspace name and use case. See [Authentication](/authentication) for headers, scopes, and key handling. ## API support For API questions, integration help, or quota issues, email **[engineering@cutshort.io](mailto:engineering@cutshort.io?subject=Cutshort%20API%20support)**. Send the mail from the email address on your Cutshort account so we can identify your workspace. ## Next steps Run your first search in 2 minutes Filters, AI search, and `inresume` booleans Contact info, resume file, and auto-unlock credits Invite shortlisted candidates to apply to your job # Quickstart Source: https://developers.cutshort.io/quickstart Your first talent search in two minutes You need an API key first. Approved employers can generate one at [Settings → Integrations → API Key](https://cutshort.io/profile/hiring-settings/integrations) — start with **Eval access**. See [Getting API access](/introduction#getting-api-access). ## 1. Generate an API key 1. Open [Settings → Integrations → API Key](https://cutshort.io/profile/hiring-settings/integrations) 2. Select **Eval access** — available to all approved employers 3. Click **Generate API key** 4. Copy the `cs_live_…` value from the confirmation modal Need **Production access** (all scopes)? Your workspace must have production API enabled — email [hello@cutshort.io](mailto:hello@cutshort.io?subject=Cutshort%20API%20production%20access%20request), then choose **Production access** when generating a key. For API support (questions, integration help, quotas), email [engineering@cutshort.io](mailto:engineering@cutshort.io?subject=Cutshort%20API%20support) from the email address on your Cutshort account. ## 2. Search candidates ```bash curl theme={null} curl -s -G "https://cutshort.io/api/v1/search/candidates" \ -H "Authorization: Bearer cs_live_YOUR_KEY" \ --data-urlencode "q=senior backend engineer python django" \ --data-urlencode "minexp=4" \ --data-urlencode "maxexp=10" \ --data-urlencode "locations=Bengaluru+%28Bangalore%29" \ --data-urlencode "page=1" ``` ```python Python theme={null} import requests API_KEY = "cs_live_YOUR_KEY" params = { "q": "senior backend engineer python django", "minexp": "4", "maxexp": "10", "locations": "Bengaluru (Bangalore)", "page": 1, } resp = requests.get( "https://cutshort.io/api/v1/search/candidates", headers={"Authorization": f"Bearer {API_KEY}"}, params=params, timeout=60, ) resp.raise_for_status() data = resp.json() print(f"Found {data['totalCount']} matches, showing {len(data['results'])} on page 1") for hit in data["results"]: print(hit["experience"], "yrs", hit.get("currentCompany", "")) ``` ```javascript JavaScript theme={null} const params = new URLSearchParams({ q: "senior backend engineer python django", minexp: "4", maxexp: "10", locations: "Bengaluru (Bangalore)", page: "1", }); const res = await fetch( `https://cutshort.io/api/v1/search/candidates?${params}`, { headers: { Authorization: "Bearer cs_live_YOUR_KEY" } } ); const data = await res.json(); console.log(data.totalCount, data.results.length); ``` ## 3. Read the response ```json theme={null} { "results": [ { "talentCardId": "…", "experience": 6, "currentSalary": 2800000, "profileSignals": { "educationPedigree": { "score": 3, "level": "tier1plus", "reason": "…" }, "workQuality": { "score": 3, "level": "high", "reason": "…" } }, "redactedResumeText": "…" } ], "page": 1, "pageSize": 10, "totalCount": 847, "searchType": "ai" } ``` `pageSize` defaults to **10** and accepts up to **20** on the search endpoint. ## 4. Check usage ```bash theme={null} curl -s "https://cutshort.io/api/v1/usage" \ -H "Authorization: Bearer cs_live_YOUR_KEY" ``` ```json theme={null} { "apiUnlocks": { "used": 0, "limit": 10 }, "searches": { "used": 1, "limit": 100 }, "messages": { "used": 0, "limit": 20 }, "cycleEndsOn": "2026-08-15T00:00:00.000Z" } ``` Every meter is a `used`/`limit` pair; meters reset at `cycleEndsOn`. ## 5. Get contact info (auto-unlock) After shortlisting a candidate from search, one call gets you their identity, resume text, and a resume file download link. If the candidate is locked for your workspace, this call **unlocks them automatically** — spending one unlock credit: ```bash theme={null} # Check remaining unlock credits first curl -s "https://cutshort.io/api/v1/usage" \ -H "Authorization: Bearer cs_live_YOUR_KEY" # Fetch details (auto-unlocks; 1 credit per new candidate) curl -s "https://cutshort.io/api/v1/candidates/TALENT_CARD_ID" \ -H "Authorization: Bearer cs_live_YOUR_KEY" ``` See [Get candidate details](/guides/get-candidate-details) for the full flow. ## Next * [Search candidates guide](/guides/search-candidates) — filters, `inresume`, location encoding * [Get candidate details](/guides/get-candidate-details) — contact info, resume file, auto-unlock credits * [Profile signals](/guides/profile-signals) — interpret `profileSignals` on each hit * [Post a job](/guides/post-a-job), [Invite to apply](/guides/invite-to-apply), and [Send a message](/guides/send-message) — production keys * [Rate limits](/guides/rate-limits) — burst caps, quotas, and error handling