# Recipe: sync CV data back to your ATS

> Build a two-way flow — pull a CV from your ATS, parse it with RemakeCV, and write the extracted candidate details back onto the record.

Source: https://www.remakecv.com/help/api-reference/recipes/sync-to-your-ats
Last updated: 2026-08-21

---
To integrate RemakeCV with an unsupported ATS, pull the candidate's CV from your system and `POST` it to `/cvs/process`, then write the extracted candidate name, employer and role back onto the record. The API returns parsed fields and the stored source file — it does not render a formatted document.

## The shape of the integration

### Trigger

A consultant clicks a button on the candidate record, or a webhook fires when a CV is attached. Prefer an explicit trigger over formatting everything automatically — it wastes credits on candidates nobody submits.

### Fetch the source CV

Pull the raw file from your ATS.

### Resolve the acting user

Map the ATS user to their RemakeCV email. This determines template visibility and credit attribution — see [authentication](https://www.remakecv.com/help/api-reference/authentication.md).

### Process

`POST /cvs/process` with the file, `acting_user_email` and optionally `template_id`.

### Store the identifier

Persist the returned `id` against your candidate record.

### Write the extracted fields back

`candidate_name`, `latest_company` and `latest_role` are the useful output. Write them onto the candidate record, or use them to reconcile against what your ATS already holds.

## Worked example

```javascript title="sync.mjs"
async function formatAndAttach(candidateId) {
  const candidate = await ats.getCandidate(candidateId);
  const source = await ats.downloadAttachment(candidate.cvAttachmentId);

  const form = new FormData();
  form.append('file', new Blob([source.buffer]), source.filename);
  form.append('acting_user_email', resolveActingUser(candidate.ownerId));
  form.append('template_id', String(templateFor(candidate.clientId)));

  const response = await fetch(`${BASE}/cvs/process`, {
    method: 'POST',
    headers: { Authorization: `Bearer ${KEY}` },
    body: form,
  });

  if (!response.ok) {
    const { error } = await response.json();
    // request_id is what support needs to trace this exact call.
    throw new Error(`${error.code} (${error.request_id}): ${error.message}`);
  }

  const { data: cv } = await response.json();

  // The parsed fields are the value here — not the file.
  await ats.updateCandidate(candidateId, {
    latestEmployer: cv.latest_company,
    latestRole: cv.latest_role,
  });

  // Lets you re-fetch the stored source file later without spending another credit.
  await ats.setCustomField(candidateId, 'remakecv_id', cv.id);

  // OCR ran, so the extraction is worth a human check.
  if (cv.processing_method === 'image') {
    await ats.addNote(candidateId, 'CV was OCR-processed — verify extracted details.');
  }

  return cv;
}
```

## Design decisions worth getting right

| Decision | Recommendation |
|---|---|
| Trigger | Explicit consultant action, not automatic on every upload — credits |
| Acting user | Real consultant, never a shared service account |
| Template selection | Derive from the client on the record, so the right variant is used |
| Idempotency | Check for an existing `remakecv_id` before reprocessing — there is no reprocess flag on the public API, so every call is a new CV and a new credit |
| Failure handling | Surface the `request_id` to the consultant, do not swallow it |
| Rate limiting | Queue with backoff — see [rate limits](https://www.remakecv.com/help/api-reference/rate-limits.md) |

> **Danger:** 
**The public API does not return a formatted CV.** `download_url` gives you back the file you uploaded, converted to PDF. If your goal is a branded document on the candidate record, this recipe cannot deliver it — use a [native integration](https://www.remakecv.com/help/integrations/integrations-overview.md) or the web app.

> **Warning:** 
Guard against double-processing. A consultant clicking twice, or a webhook firing on retry, will process the same CV twice and spend two credits. Check for an existing `remakecv_id` before you call the API.

## Re-downloading later

Do not store `download_url` — it expires after an hour. Store `id`, and when a consultant asks for the file again:

```javascript
const { data } = await request(`/cvs/${remakecvId}/file`);
// data.url is a signed URL to the source CV as a PDF, valid for data.expires_in seconds (3600).
```

Note the file is the **source CV as a PDF**, not a formatted document, and the URL lasts an hour.

See [`GET /cvs/{cvId}/file`](https://www.remakecv.com/help/api-reference/endpoints/get-cv-file.md).

## Frequently asked questions

### Should I store the file or the CV id?

Store the id. It lets you fetch a fresh signed URL for the stored source file at any time without spending another credit. There is no way to re-render or reprocess through the public API.

### How do I map ATS users to acting_user_email?

Match on email address. If your ATS emails differ from RemakeCV logins, keep an explicit mapping table rather than falling back to a service account.
