# Greenhouse 422 "candidate already exists in job" when the agent re-adds a past applicant

## TL;DR

The candidate already has an application on that job, so creating another 422s. Before creating, search the job's candidates by email; if they exist, update or move the existing application instead of creating a new one. Re-adds are updates wearing a creation costume.

```text
422 Unprocessable Entity: "candidate already exists in job"
```

## Steps

1. Search the job's existing candidates by email before creating.
   Expected: The existing application is found.

2. Update the existing application (or move its stage) instead of creating a new one.
   Expected: No 422, and history stays unified.

3. If the existing application is on a different job, use the appropriate endpoint for that case.
   Expected: The right object is touched.

4. Make the create path check-then-act, with the check and create as close together as possible.
   Expected: Races are minimized.

5. Log the already-exists path as a normal outcome, not an error; it is the common case for re-applicants.
   Expected: Metrics stop crying wolf.

## When to use

- Greenhouse 422s with "candidate already exists in job"
- the agent re-adds past applicants
- re-applicants are common

## When not to use

- the candidate is genuinely new (check the email normalization first)
- the error is on a different 422 (read the message; 422 has many meanings)
- duplicates are intended (rare; then handle the 422 explicitly)

## Tool compatibility

- Greenhouse Harvest API
- candidate creation flows

## Variant phrasings

- "greenhouse 422 candidate already exists"
- "candidate already exists in job fix"
- "greenhouse re-add applicant 422"

## Why it happens

Greenhouse enforces one application per candidate per job. Re-applicants, referrals of past candidates, and bulk re-imports all hit this. The agent treated 'create' as the only verb; for existing candidates the verbs are 'update' and 'move'.

## Edge cases

- email normalization (case, plus-addressing) decides whether the check finds the existing record
- the check-then-act still races under concurrency; handle the 422 as the backstop
- an existing application on a closed job needs reopening logic, not just a stage move

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_-BVdTnjNoKvUEDEGURVSwg
