---
title: "Get a candidate in a project"
method: GET
path: "/api/v1/projects/{projectId}/candidates/{candidateId}"
tags: ["Projects"]
---

# Get a candidate in a project

`GET /api/v1/projects/{projectId}/candidates/{candidateId}`

## Request

- Base URL: `https://api.recruitwithatlas.com`
- URL: `https://api.recruitwithatlas.com/api/v1/projects/{projectId}/candidates/{candidateId}`
- Auth: HTTP bearer
- TypeScript: <https://skmtc.dev/recruitwithatlas/apis/atlas-api/docs/api/v1/projects/:projectId/candidates/:candidateId/get.ts> (a function with typed inputs; its response is checked against the schema)

Use this endpoint to retrieve the current pipeline position of a single candidate within a specific project — what stage and status they sit at, who owns the candidate record, whether they have been rejected (and why), and a snapshot of the person and project they relate to.

In Atlas, a **candidate** represents a person who has been added to a project's recruitment pipeline. Each candidate sits at a specific status within a specific stage of the pipeline (e.g. Sourcing → Added, Shortlist → Reviewed). This endpoint returns the full state of one candidate by its ID, scoped to the project provided in the URL.

**What you need to provide:**
- The **project ID** in the URL path — the project the candidate belongs to
- The **candidate ID** in the URL path — the candidate record to look up

Both IDs are UUIDs returned by the **List projects** and **List candidates in a project** endpoints respectively.

**What you get back:**
- `id` — the candidate record ID
- `stage` — the candidate's current pipeline stage (`id`, `name`, `phase`)
- `status` — the candidate's current status within the stage (`id`, `name`)
- `owner` — the user who owns the candidate record (`id`, `name`, `email`); this is the candidate's owner, not necessarily the project's owner
- `person` — a snapshot of the underlying person (`id`, `firstName`, `lastName`, `headlineRole`, `headlineCompanyName`, `avatarUrl`); use `person.id` with the People endpoints to fetch the person's full details
- `project` — a snapshot of the project the candidate is in (`id`, `jobRole`, `state`)
- `rejectedAt` / `rejectionType` / `rejectionReason` / `customRejectionReasonId` / `rejectionDetails` / `rejectedBy` — populated when the candidate has been rejected, otherwise `null`. `rejectionType` says who rejected the candidate (`by_us`, `by_client` or `self`). `rejectionReason` returns the label of the agency-defined reason when one was picked (the same label the Atlas app shows), and falls back to the predefined reason code (e.g. `not_qualified`) for rejections recorded before agency-defined reasons existed. `rejectedBy` is the Atlas user who recorded the rejection (`id`, `name`, `email`), and is `null` when Atlas rejected the candidate automatically or by import. Rejected candidates are not moved, so a rejected candidate's `stage` and `status` are the ones they were rejected at
- `comment` / `commentFormatted` — the recruiter's shortlist comment ("Notes" in Atlas) as plain text and as HTML, `null` when there is none. Change it with **Update a candidate in a project**

**Returns `404` when:**
- The candidate does not exist, does not belong to the specified project, or is in a different agency.

## Path parameters

- `projectId` string, uuid, required: Project ID
- `candidateId` string, uuid, required: Candidate ID

## Response `200`

Candidate detail within the specified project

- object
  - `status` 'ok', required
  - `data` object, required
    - `id` string, uuid, required: Candidate record ID
    - `stage` object, required: Candidate's current pipeline stage. Rejected candidates are not moved, so for a rejected candidate this is the stage they were rejected at (unless that stage was later deleted from the pipeline)
      - `id` string, uuid, required: Pipeline stage ID
      - `name` string, required: Pipeline stage name
      - `phase` 'internal_selection' | 'presentation' | 'first_round' | 'late_stage', required: Pipeline phase of the stage
    - `status` object, required: Candidate's current pipeline status within the stage
      - `id` string, uuid, required: Pipeline status ID
      - `name` string, required: Pipeline status name
    - `owner` object, nullable, required: User who owns this candidate record
      - `id` string, uuid, required: Owner user ID
      - `name` string, required: Owner name
      - `email` string, required: Owner email
    - `person` object, required: The person record for this candidate
      - `id` string, uuid, required: Person ID (people.id)
      - `firstName` string, nullable, required: Person first name
      - `lastName` string, nullable, required: Person last name
      - `headlineRole` string, nullable, required: Current/most recent job title
      - `headlineCompanyName` string, nullable, required: Current/most recent company name
      - `avatarUrl` string, nullable, required: Public URL of the person's profile photo (the avatar shown in Atlas), or null when the person has none
    - `project` object, nullable, required: The project the candidate is in
      - `id` string, uuid, required: Project ID
      - `jobRole` string, required: Project job role/title
      - `state` 'active' | 'closed' | 'on_hold' | 'talent_pool' | 'lead' | 'pitch' | 'opportunity', required: Project state
    - `rejectedAt` string, nullable, required: ISO 8601 — when the candidate was rejected, null if active
    - `rejectionType` 'by_us' | 'by_client' | 'self' | 'null', nullable, required: Who rejected the candidate: `by_us` (the agency), `by_client`, or `self` (the candidate withdrew). Null if active, and on rejections recorded without a type
    - `rejectionReason` string, nullable, required: Rejection reason, null if active. Returns the label of the agency-defined reason when one was picked (see `customRejectionReasonId`), otherwise the predefined reason code (e.g. `not_qualified`)
    - `customRejectionReasonId` string, uuid, nullable, required: ID of the agency-defined rejection reason recorded against the candidate, null when none was picked
    - `rejectionDetails` string, nullable, required: Free-text rejection details, null when none were given
    - `rejectedBy` object, nullable, required: The Atlas user who recorded the rejection — for a `by_client` rejection this is the recruiter who logged it, not the client. Null if active, when Atlas rejected the candidate automatically (e.g. auto-reject of unresponsive candidates) or by import, or when the user has since been removed
      - `id` string, uuid, required: User ID
      - `name` string, required: User name
      - `email` string, required: User email
    - `comment` string, nullable, required: The shortlist comment ("Notes" in Atlas) as plain text, null when there is none. Clients see it on the client portal card and in shared reports
    - `commentFormatted` string, nullable, required: The shortlist comment as sanitized HTML, keeping its formatting. null when there is none

## Other responses

- `401`: Unauthorized - missing or invalid API key
- `404`: Candidate not found
- `422`: Validation error - the request failed schema validation (`errors.fieldErrors`) or a business rule (`errors.formErrors`)
- `429`: Too many requests - the caller has exceeded the per-agency rate limit for the tier this endpoint counts against (default per minute: 1200 read / 400 write / 60 upload). Inspect the `RateLimit-*` headers — returned on every response, not only on 429s — and back off until the window resets. See the "Rate limits" section of the introduction for details.
- `500`: Internal server error

## Changes

- **2026-11-03** `158ee454751c`: 1 warning, 2 info
  - added the new `undefined` enum value to the `data/rejectionType` response property for the response status `200`
  - added the required property `data/comment` to the response with the `200` status
  - added the required property `data/commentFormatted` to the response with the `200` status
- **2026-10-29** `1e0f6672989f`: 2 info
  - added the required property `data/rejectedBy` to the response with the `200` status
  - added the required property `data/rejectionType` to the response with the `200` status
- **2026-10-13** `1e937bc70e59`: 1 info
  - added the required property `data/person/avatarUrl` to the response with the `200` status
- **2026-10-03** `cd4bc8bbd95e`: 1 info
  - added the optional property `error` to the response with the `422` status

[Operation history](https://skmtc.dev/recruitwithatlas/apis/atlas-api/changes/api/v1/projects/:projectId/candidates/:candidateId/get.md)

---

[API](https://skmtc.dev/recruitwithatlas/apis/atlas-api.md) - [All operations](https://skmtc.dev/recruitwithatlas/apis/atlas-api/llms.txt) - [OpenAPI document](https://skmtc.dev/recruitwithatlas/apis/atlas-api/revisions/158ee454751c?raw)
