---
title: "Update candidate"
method: PUT
path: "/candidates/{id}"
tags: ["Candidates"]
---

# Update candidate

`PUT /candidates/{id}`

## Request

- Base URL: `https://api.100hires.com/v2`
- URL: `https://api.100hires.com/v2/candidates/{id}`
- Auth: HTTP bearer

Updates candidate fields, profile answers, and optional CV payload. Designed for bi-directional sync from ATS, CRM, sourcing, or enrichment tools.

## Path parameters

- `id` string, required

## Request body

- CandidateWriteRequest
  - `company_id` integer: Target company ID. Required only when the API key has access to multiple companies.
  - `first_name` string: Candidate first name.
  - `last_name` string: Candidate last name.
  - `email` string, email: Candidate email address. Used for deduplication.
  - `phone` string: Candidate phone number.
  - `profile` object: Key-value map of profile field answers. Keys can be either question text (string) or question_id (integer from GET /questions). Library fields (First name, Last name, Email, Phone, etc.) and custom fields both support text-based keys. Example: {"Years of experience": "5"} or {"42": "5"}. Must be a JSON object — a JSON array or a plain string is rejected with 400 Validation Error. Every key must resolve to a question of the target company and every value must be acceptable for that question's type; otherwise the request is rejected with 400 Validation Error listing the offending keys in validation_errors.profile, and nothing is saved. Multi-value fields such as Email accept an array of values, e.g. {"Email": ["a@example.com", "b@example.com"]}. Yes/No questions take 1 or 0, as a number or as a string — send JSON false as 0, since a JSON false is indistinguishable from an empty value and is rejected.
  - `job_id` integer: Job ID to create an application for this candidate. If omitted, candidate is created without an application.
  - `stage_id` integer: Pipeline stage ID for the initial application. Requires job_id. If omitted with job_id, defaults to the first stage.
  - `cv` FileData
    - `data` string, required: Base64 content
    - `file_name` string, required: Original file name.
    - `mime_type` string, required: MIME type (e.g. application/pdf).
    - `size` integer: Optional for uploads, returned in responses
  - `city` string: Candidate city. Used together with state/country to resolve timezone and location display in the profile header. If omitted or empty, taken from the candidate's 'City' profile answer when present.
  - `state` string: Candidate state or region. If omitted or empty, taken from the candidate's 'State' profile answer when present.
  - `country` string: Candidate country name or ISO 3166-1 alpha-2 code (e.g. 'United States' or 'US'). If omitted or empty, taken from the candidate's 'Country' profile answer when present.
  - `timezone` string: IANA timezone (e.g. 'America/Los_Angeles'). If omitted, resolved automatically from city/country. If provided alongside city, overrides the auto-resolved timezone.

## Response `200`

Updated candidate

- Candidate
  - `id` integer, required: Unique identifier
  - `alias` string, nullable: Short alphanumeric alias used in profile URLs. Use this to build candidate profile links — `https://app.100hires.com/candidate/{alias}`. Also accepted as `{id}` in API requests.
  - `url_backoffice` string, uri: Absolute backoffice URL of the candidate in the 100Hires recruiter app. Use to render deep-links in agent UIs.
  - `company_id` integer, required: ID of the company this candidate belongs to
  - `first_name` string, nullable: Candidate's first name
  - `last_name` string, nullable: Candidate's last name
  - `emails` string[], nullable: List of candidate email addresses
  - `phones` string[], nullable: List of candidate phone numbers
  - `source` IdName
    - `id` integer, required: Unique identifier.
    - `name` string, required: Display name.
  - `origin` IdName
    - `id` integer, required: Unique identifier.
    - `name` string, required: Display name.
  - `profile` ProfileAnswer[]: Candidate profile field answers (application form responses). Present only when `view=full` (default); omitted entirely when `view=summary` to keep list responses inside the agent context budget — a single candidate can carry dozens of free-form answers.
    - `question_id` integer, required: Question ID.
    - `question_text` string, required: Question label.
    - `value` unknown, required
  - `ip` string, nullable: IP address from which the candidate applied
  - `applications` CandidateApplicationSummary[]: Summary of all applications this candidate has across jobs
    - `id` integer, required: Application ID.
    - `job` object, required: Job summary (id, title, status).
      - `id` integer, required
      - `title` string, required
    - `stage` IdName
      - `id` integer, required: Unique identifier.
      - `name` string, required: Display name.
  - `created_at` integer, required: Unix timestamp (seconds)
  - `updated_at` integer, required: Unix timestamp (seconds)
  - `last_message_at` integer, nullable: Unix timestamp (seconds) of the most recent message (email or SMS) sent to or received from this candidate. Null if no messages exist.
  - `days_since_last_message` integer, nullable: Number of whole days since the last message. Computed at request time. Null if no messages exist.

## Other responses

- `400`: Validation error
- `401`: Unauthorized
- `403`: Forbidden
- `404`: Not found
- `415`: Content-Type must be application/json
- `429`: Too many requests

---

[API](https://skmtc.dev/100hires/apis/100hires-api.md) · [All operations](https://skmtc.dev/100hires/apis/100hires-api/llms.txt) · [OpenAPI document](https://skmtc.dev/100hires/apis/100hires-api/revisions/e63e2d3115f7?raw)
