---
title: "Reconcile a candidate from a membership source"
method: POST
path: "/candidates/reconcile"
tags: ["Candidates"]
---

# Reconcile a candidate from a membership source

`POST /candidates/reconcile`

## Request

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

Body-keyed membership sync. `sourceKey` + `externalMemberId` identify the member; they stay in the body because request paths land in logs. `sourceRevision` orders writes: equal is a no-op, lower is rejected. Creates, links, or updates the Candidate and optionally applies a profile projection. Pair this push with the `candidate.profile.updated` webhook and `GET /candidates/{id}/profile` pull. Requires `candidates.manage`. The request is idempotent when an `Idempotency-Key` header is supplied; equal source revisions are also idempotent without the header.

## Request body

- ReconcileCandidateBody
  - `sourceKey` string, required: Opaque caller-chosen identifier for the membership system this write comes from (1–64 characters, `[a-z0-9._-]`). Identifies the source, not the person, and must stay stable: changing it treats the caller as a new source and cannot update an existing link.
  - `externalMemberId` string, required: The member id in the caller's system (1–256 characters). Sent in the body, never the URL: request paths land in logs and traces. Together with `sourceKey` this is the idempotent membership-sync key.
  - `sourceRevision` integer, required: Monotonically increasing revision per member, typically the source system's change timestamp in milliseconds. An equal revision is an idempotent no-op (`unchanged`); a lower revision is rejected as stale so a delayed write can never resurrect stale profile data or reactivate a deactivated account. Revisions above 2^53-1 are rejected.
  - `email` string, email, required: The candidate's email address. Used to create or link the Board user for this membership source.
  - `displayName` string, required: The candidate's display name.
  - `profile` object: Optional bounded profile projection, the same shape as `PUT /candidates/{id}/profile`. Absent keys are left unchanged; `null` on a scalar clears it; a provided collection array replaces that collection wholesale. Pair with `candidate.profile.updated` and `GET /candidates/{id}/profile` for notification-plus-pull.
    - `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.
  - `active` boolean, required: When `false`, deactivates the candidate (sign-in disabled, sessions revoked, profile hidden from talent surfaces). When `true`, restores sign-in and talent-surface visibility; the candidate signs in again to establish a new session. Profile and application history are preserved either way.

## Response `200`

Candidate reconciled.

- CandidateReconcileResult
  - `status` 'created' | 'updated' | 'unchanged' | 'upgraded', required: `created` when a new Candidate is provisioned, `updated` when identity or profile changed, `unchanged` when the source revision was already applied, `upgraded` when an existing non-candidate Board user became a Candidate.
  - `candidate` object, required: The candidate after this reconcile, same serialization as `GET /candidates/{id}` (including `deactivatedAt`).
    - `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 taken, a newer source revision has already been applied, this candidate is already linked to a different member id for this source, or `sandbox_persona_protected` when a reconcile changes a persona's email or deactivates a persona on a sandbox board.

## 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/reconcile/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)
