---
title: "Create a candidate"
method: POST
path: "/candidates"
tags: ["Candidates"]
---

# Create a candidate

`POST /candidates`

## Request

- Base URL: `https://api.cavuno.com/v1`
- URL: `https://api.cavuno.com/v1/candidates`
- Auth: HTTP bearer

Creates a passwordless candidate. Magic link is the default activation. An optional initial profile is seeded once. Requires `candidates.manage`. The request is idempotent when an `Idempotency-Key` header is supplied.

## Request body

- CreateCandidateBody
  - `email` string, email, required: The candidate's email address. Must be unique on the account.
  - `displayName` string, required: The candidate's display name.
  - `profile` object: Optional initial profile projection. Seeded once at create; the Candidate owns it afterwards unless a later sync source claims the group. Absent keys are left unset; `null` on a scalar clears it.
    - `headline` string, nullable: Absent key leaves the stored headline unchanged; `null` clears it.
    - `bio` string, nullable: Absent key leaves the stored bio unchanged; `null` clears it.
    - `location` string, nullable: Absent key leaves the stored location unchanged; `null` clears it.
    - `countryCode` string, nullable: Absent key leaves the stored country code unchanged; `null` clears it. Ignored without an error while the candidate has a home place that this request keeps (a changed `location` removes it): the country code is then that place's country.
    - `skills` string[]: Absent key leaves the stored skills unchanged. Providing an array replaces the collection wholesale (empty array clears). Maximum 200 rows.
    - `languages` CandidateProfileLanguageProjection[]: Absent key leaves the stored languages unchanged. Providing an array replaces the collection wholesale (empty array clears). Maximum 50 rows.
      - `name` string, required: Language name.
      - `proficiency` string, required: Proficiency label.
      - `sortOrder` integer, required: Display order, lower first.
    - `experience` CandidateProfileExperienceProjection[]: Absent key leaves the stored experience unchanged. Providing an array replaces the collection wholesale (empty array clears). Maximum 50 rows.
      - `title` string, required: Job title.
      - `companyName` string, required: Employer name.
      - `companyUrl` string, nullable: Employer URL.
      - `location` string, nullable: Role location.
      - `employmentType` string, nullable: Employment type.
      - `locationType` string, nullable: Location type.
      - `foundVia` string, nullable: How the role was found.
      - `startDate` string, required: Start date.
      - `endDate` string, nullable: End date.
      - `description` string, nullable: Role description.
      - `experienceSkills` string[], nullable: Per-role skills; replaced with the row.
      - `sortOrder` integer, required: Display order, lower first.
    - `education` CandidateProfileEducationProjection[]: Absent key leaves the stored education unchanged. Providing an array replaces the collection wholesale (empty array clears). Maximum 50 rows.
      - `institutionName` string, required: Institution name.
      - `institutionUrl` string, nullable: Institution URL.
      - `degree` string, nullable: Degree awarded.
      - `fieldOfStudy` string, nullable: Field of study.
      - `grade` string, nullable: Grade or GPA.
      - `activitiesAndSocieties` string, nullable: Activities and societies.
      - `startDate` string, nullable: Start date.
      - `endDate` string, nullable: End date.
      - `description` string, nullable: Education description.
      - `sortOrder` integer, required: Display order, lower first.

## Response `201`

Candidate created.

- CandidateDetail
  - `id` string, required: Unique identifier for the candidate.
  - `object` 'candidate', required: String representing the object's type.
  - `email` string, required: The candidate's email address.
  - `displayName` string, nullable, required: The candidate's display name.
  - `handle` string, nullable, required: Public-profile handle, or `null`.
  - `avatarUrl` string, nullable, required: Avatar URL, or `null`.
  - `hasResume` boolean, required: Whether a resume is on file. On list rows this reflects the stored pointer; on the single-candidate detail it reflects the resolved blob (always agrees with `resume !== null`), so the two can briefly disagree if a stored file was garbage-collected.
  - `createdAt` string, required: ISO-8601 timestamp of when the candidate registered.
  - `candidateCreatedAt` string, required: ISO-8601 timestamp when this Board user became an operator-visible Candidate. This is the reconciliation counterpart of candidate.created, not newsletter permission.
  - `updatedAt` string, nullable, required: ISO-8601 timestamp of the latest approved Candidate identity update, or null for older rows.
  - `revision` integer, required: Monotonic Candidate lifecycle revision; legacy rows are 0 until the rollout migration runs.
  - `lastActiveAt` string, nullable, required: ISO-8601 timestamp of the last activity on the board (a sign-in or signed-in use), to the hour, or `null`.
  - `deactivatedAt` string, date-time, nullable, required: ISO 8601 timestamp when sign-in was disabled, or `null` when the candidate can sign in. Deactivate revokes all sessions, blocks sign-in, and hides the profile from talent surfaces; profile and application history are preserved. Reactivate restores sign-in and talent-surface visibility; the candidate signs in again to establish a new session.
  - `profile` AdminCandidateProfile, nullable, required: Admin-visible profile detail, or `null` when no profile exists.
    - `headline` string, nullable, required
    - `location` string, nullable, required
    - `bio` string, nullable, required
    - `jobSearchStatus` string, nullable, required
    - `profileVisibility` string, nullable, required
    - `openToRelocate` boolean, nullable, required
    - `seniority` string[], required
    - `skills` string[], required
    - `workingPermits` string[], required
    - `interestedRoles` string[], required
  - `savedJobCount` number, required: Number of jobs the candidate has saved. Values are capped at 1,000 in write-operation responses (reconcile/deactivate/reactivate/create/update); GET detail returns 422 `candidate_resource_limit` beyond the cap.
  - `alertCount` number, required: Number of job alerts the candidate has. Values are capped at 1,000 in write-operation responses (reconcile/deactivate/reactivate/create/update); GET detail returns 422 `candidate_resource_limit` beyond the cap.
  - `resume` CandidateResumeFile, nullable, required: Protected download path and metadata for the uploaded resume, or `null` when no resume is on file (or the storage blob is missing).
    - `downloadUrl` string, required: Stable, permission-protected Cavuno API path for downloading the candidate resume.
    - `contentType` string, required: MIME type of the stored resume file.
    - `sizeBytes` number, required: Size of the resume file in bytes.

## Other responses

- `400`: The request was malformed.
- `409`: The email is already registered to another board user on this account.

## Changes

- **2026-10-19** `c34152c38176`: 2 info
  - added the optional property `error/upgrade` to the response with the `400` status
  - added the optional property `error/upgrade` to the response with the `409` status

[Operation history](https://skmtc.dev/cavuno/apis/cavuno-v1-rest-api/changes/candidates/post.md)

---

[API](https://skmtc.dev/cavuno/apis/cavuno-v1-rest-api.md) · [All operations](https://skmtc.dev/cavuno/apis/cavuno-v1-rest-api/llms.txt) · [OpenAPI document](https://skmtc.dev/cavuno/apis/cavuno-v1-rest-api/revisions/d5974dabff14?raw)
