# Candidates

Candidates, with their attachments, values and sharing settings

Base URL: `https://api.cv-transformer.com/v1`. Endpoints: `GET /candidates`, `DELETE /candidates`, `PATCH /candidates`, `POST /candidates`, `POST /candidates/{candidate_id}/attach`, `POST /candidates/{candidate_id}/fill`, `POST /candidates/{candidate_id}/extract`. Authenticate with the header `Authorization: Bearer YOUR_API_KEY` (see [authentication](https://www.cv-transformer.com/es/api-reference/authentication.md)).

## The candidate object

| Attribute | Type | Description |
| --- | --- | --- |
| `id` | `uuid` | Unique identifier. |
| `layout_id` | `uuid \| null` | Identifier of the related layout. References [`layouts.id`](https://www.cv-transformer.com/es/api-reference/layouts.md). |
| `member_id` | `uuid \| null` | Identifier of the member responsible for the candidate, initially its creator. References [`members.id`](https://www.cv-transformer.com/es/api-reference/members.md). |
| `organization_id` | `uuid` | Identifier of the related organization. References [`organizations.id`](https://www.cv-transformer.com/es/api-reference/organizations.md). |
| `created_at` | `timestamp with time zone` | Creation timestamp. Read-only. |
| `updated_at` | `timestamp with time zone` | Last modification timestamp. Read-only. |
| `deleted_at` | `timestamp with time zone \| null` | Deletion timestamp. |
| `attachments` | `Record<string, CandidateAttachment>` | Files attached to the candidate, such as CVs and vacancies. |
| `avatar` | `text \| null` | Avatar image of the candidate. Filled automatically when the candidate is edited in the app. |
| `export_filename` | `text` | Filename of exported PDF files (without .pdf). When empty, the layout's `export_filename` applies. |
| `external_data` | `CandidateExternalData \| null` | Data imported from an external source, such as an ATS. |
| `fts` | `tsvector` | Full text search index. Read-only. |
| `language` | `Language` | Language in which the candidate is displayed. |
| `messages` | `Array<CandidateMessage>` | Messages exchanged with the AI about the candidate. |
| `name` | `text` | Name of the candidate, used for search. Filled automatically when the candidate is edited in the app. |
| `secret` | `text` | Secret string. Used to share the candidate's proposal privately, with personal information. Read-only. |
| `secret_anonymous` | `text` | Secret string. Used to share the candidate's proposal privately, with personal information redacted. Read-only. |
| `secret_editable` | `text` | Secret string. Used to share the candidate for editing. Read-only. |
| `section_attachments` | `Record<string, Array<SectionAttachment>>` | Attachments shown in each attachment section, by section. |
| `status` | `CandidateStatus` | Status of the candidate. Only `active` candidates can be shared. Read-only. |
| `values` | `JsonObject` | Values of the variables defined in the candidate's layout. |

```json
{
  "id": "00000000-0000-0000-0000-000000000000",
  "layout_id": "00000000-0000-0000-0000-000000000000",
  "member_id": "00000000-0000-0000-0000-000000000000",
  "organization_id": "00000000-0000-0000-0000-000000000000",
  "created_at": "2026-01-01T12:00:00+00:00",
  "updated_at": "2026-01-01T12:00:00+00:00",
  "deleted_at": null,
  "attachments": {
    "CV": {
      "detected_source_language": null,
      "external_id": null,
      "label": "CV",
      "text": {
        "default": "Text from the attachment"
      },
      "type": "pdf",
      "url": "https://ddcaybdkekwxbpgpotoa.supabase.co/storage/v1/object/public/common/organizations/00000000-0000-0000-0000-000000000000/document.pdf"
    }
  },
  "avatar": "",
  "export_filename": "",
  "external_data": {
    "avatar": "https://example.com/candidates/123/avatar.jpg",
    "name": "John Doe",
    "id": "123",
    "url": "https://integration.com/candidates/123",
    "values": {
      "first_name": "John",
      "last_name": "Doe"
    }
  },
  "fts": "'doe':2 'john':1",
  "language": "en",
  "messages": [
    {
      "comment": "Filled in values",
      "created_at": "2026-11-13T06:24:48.498Z",
      "id": "00000000-0000-0000-0000-000000000000",
      "member_id": "00000000-0000-0000-0000-000000000000",
      "prompt": "Fill in values",
      "selector": []
    }
  ],
  "name": "",
  "secret": "",
  "secret_anonymous": "",
  "secret_editable": "",
  "section_attachments": {},
  "status": "active",
  "values": {
    "name": "John Doe",
    "date_of_birth": "1995-01-01",
    "experiences": [
      {
        "interval": {
          "start": "2015-01-01",
          "end": "2018-01-01"
        },
        "company": "Example Inc.",
        "position": "Developer"
      }
    ]
  }
}
```

### Types

```typescript
type JsonObject = Record<string, Json>;

type Json = 
  | string
  | number
  | boolean
  | null
  | Array<Json>
  | Record<string, Json>;

type CandidateStatus = "active" | "pending";

type SectionAttachment = {
  attachment_id: string;
  grid?: SectionAttachmentGrid;
};

type SectionAttachmentGrid = {
  columns: number;
  rows: number;
};

type CandidateMessage = {
  comment: string;
  created_at: string;
  id: string;
  member_id: string | null;
  prompt: string;
  selector?: Array<string | number>;
};

type Language = 
  | "en"
  | "cs"
  | "da-DK"
  | "de"
  | "en-GB"
  | "es"
  | "fr"
  | "ja-JP"
  | "nb-NO"
  | "nl"
  | "pt-BR"
  | "sv-SE"
  | "yue"
  | "zh";

type CandidateExternalData = {
  avatar: string | null;
  name: string;
  id: string | number;
  url: string;
  values: Json;
};

type CandidateAttachment = {
  detected_source_language: string | null;
  external_id: string | number | null;
  label?: string;
  markings?: Array<Array<AttachmentMarking>>;
  text: MultiLanguageString;
  type: "image" | "image-pdf" | "link" | "pdf" | "text";
  url: string;
};

type AttachmentMarking = {
  color?: string;
  height: number;
  left: number;
  top: number;
  width: number;
};

type MultiLanguageString = {
  default: string;
  [x: string]: string;
};
```

### Related resources

Embed related resources with the [`select` parameter](https://www.cv-transformer.com/es/api-reference/selecting.md), for example `select=*,layouts(*)`. Resources related through more than one column need the column as a hint, like `table!column(*)`.

| Embed | Type | Description |
| --- | --- | --- |
| `layouts(*)` | `object \| null` | The [layout](https://www.cv-transformer.com/es/api-reference/layouts.md) referenced by `layout_id`. |
| `members(*)` | `object \| null` | The [member](https://www.cv-transformer.com/es/api-reference/members.md) referenced by `member_id`. |
| `organizations!organization_id(*)` | `object \| null` | The [organization](https://www.cv-transformer.com/es/api-reference/organizations.md) referenced by `organization_id`. |
| `candidate_analytics(*)` | `array` | The [candidate analytics](https://www.cv-transformer.com/es/api-reference/candidate_analytics.md) referencing this candidate through `candidate_analytics.candidate_id`. |
| `candidate_views(*)` | `array` | The [candidate views](https://www.cv-transformer.com/es/api-reference/candidate_views.md) referencing this candidate through `candidate_views.candidate_id`. |

## List candidates

`GET /v1/candidates` · requires the `candidates:read` scope

Filter on any attribute with `column=operator.value` (see [filtering](https://www.cv-transformer.com/es/api-reference/filtering.md)). Choose columns and embed related resources with `select` (see [selecting](https://www.cv-transformer.com/es/api-reference/selecting.md)), and sort and paginate with `order`, `limit` and `offset` (see [sorting and pagination](https://www.cv-transformer.com/es/api-reference/sorting-pagination.md)).

```bash
curl "https://api.cv-transformer.com/v1/candidates?limit=10&select=*" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Responses:

- `200`: A list of candidates with the selected columns. The `Content-Range` header holds the range returned and the total count, like `0-9/42`.
- `206`: Partial Content
- `400`: The request is malformed, for example an unknown column or an invalid filter.
- `401`: The API key is missing or invalid.
- `403`: The API key doesn't have the scope this request needs.
- `429`: The API key made more requests per minute than its rate limit allows. Retry after the number of seconds in the `Retry-After` header.

## Delete candidates

`DELETE /v1/candidates` · requires the `candidates:write` scope

Filter on any attribute with `column=operator.value` (see [filtering](https://www.cv-transformer.com/es/api-reference/filtering.md)). Without filters, this request deletes all candidates the API key has access to. Always filter on at least one column, like `id=eq.{id}`.

```bash
curl -X DELETE "https://api.cv-transformer.com/v1/candidates?id=eq.00000000-0000-0000-0000-000000000000" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Responses:

- `204`: Deleted. With the header `Prefer: return=representation`, the response is a `200` whose body holds the deleted candidates with the selected columns.
- `400`: The request is malformed, for example an unknown column or an invalid filter.
- `401`: The API key is missing or invalid.
- `403`: The API key doesn't have the scope this request needs, or no permission for this change, for example when writing rows of another organization.
- `429`: The API key made more requests per minute than its rate limit allows. Retry after the number of seconds in the `Retry-After` header.

## Update candidates

`PATCH /v1/candidates` · requires the `candidates:write` scope

Filter on any attribute with `column=operator.value` (see [filtering](https://www.cv-transformer.com/es/api-reference/filtering.md)). Without filters, this request updates all candidates the API key has access to. Always filter on at least one column, like `id=eq.{id}`. The body holds the attributes to change.

```bash
curl -X PATCH "https://api.cv-transformer.com/v1/candidates?id=eq.00000000-0000-0000-0000-000000000000" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "attachments": {
      "CV": {
        "detected_source_language": null,
        "external_id": null,
        "label": "CV",
        "text": { "default": "Text from the attachment" },
        "type": "pdf",
        "url": "https://ddcaybdkekwxbpgpotoa.supabase.co/storage/v1/object/public/common/organizations/00000000-0000-0000-0000-000000000000/document.pdf"
      }
    }
  }'
```

Responses:

- `204`: Updated. With the header `Prefer: return=representation`, the response is a `200` whose body holds the updated candidates with the selected columns.
- `400`: The request is malformed, for example an unknown column, an invalid filter or an invalid body.
- `401`: The API key is missing or invalid.
- `403`: The API key doesn't have the scope this request needs, or no permission for this change, for example when writing rows of another organization.
- `409`: The change conflicts with existing data, for example a duplicate key or a reference to a row that does not exist.
- `429`: The API key made more requests per minute than its rate limit allows. Retry after the number of seconds in the `Retry-After` header.

## Create candidate

`POST /v1/candidates` · requires the `candidates:write` scope

Create a new candidate. Uses one credit. When no credits are available in the organization, the candidate is created with `status="pending"` instead. Omitted fields get their default. An invalid field fails the request with a 400 that names it; nothing is created or billed. Files are uploaded with [Upload candidate attachment](#upload-attachment) after creating the candidate.

Body:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `attachments` | `Record<string, { detected_source_language: string \| null, external_id: string \| number \| null, label: string, text: MultiLanguageString, type: "image" \| "image-pdf" \| "link" \| "pdf" \| "text", url: string }>` | No | Files attached to the candidate, by key |
| `external_data` | `{ avatar: string \| null, name: string, id: string \| number, url: string, values?: unknown } \| null` | No | Data imported from an external source |
| `language` | `Language \| null` | No | Language the candidate is displayed in. Defaults to the layout's first language |
| `layout_id` | `string \| null` | No | Identifier of a layout of the organization. Defaults to the organization's default layout, also when the layout doesn't exist |
| `member_external_id` | `string \| null` | No | The ID of the member in an external system, to find the member by when `member_id` is not set |
| `member_id` | `string \| null` | No | Identifier of the member of the organization associated with the candidate. No member when it doesn't exist |
| `values` | `Record<string, unknown> \| null` | No | Values for the variables defined in the layout |

```bash
curl "https://api.cv-transformer.com/v1/candidates" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "attachments": {
      "CV": {
        "detected_source_language": null,
        "external_id": null,
        "label": "CV",
        "text": { "default": "" },
        "type": "link",
        "url": "https://example.com/cv"
      }
    },
    "language": "nl",
    "layout_id": "00000000-0000-0000-0000-000000000000",
    "member_id": "00000000-0000-0000-0000-000000000000",
    "values": { "key": "value" }
  }'
```

Responses:

- `201`: The created candidate
- `400`: The body is invalid: not JSON, a required field is missing or a field has the wrong type or value. The details name each invalid field and why.
- `401`: The API key is missing or invalid.
- `403`: The API key doesn't have the scope this request needs.
- `429`: The API key made more requests per minute than its rate limit allows. Retry after the number of seconds in the `Retry-After` header.

## Upload candidate attachment

`POST /v1/candidates/{candidate_id}/attach` · requires the `candidates:write` scope

Upload an attachment for a candidate. The body is the file, with its type in the `Content-Type` header. Supported file types: `.pdf`, `.doc`, `.docx`, `.jpeg`, `.png`, `.txt`, `.webp`.

Route parameters:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `candidate_id` | `uuid` | Yes | The identifier of the candidate. |

Query parameters:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `location` | `string` | No | Label of the attachment, like CV or Vacancy. Defaults to CV. |

```bash
curl "https://api.cv-transformer.com/v1/candidates/{candidate_id}/attach?location=CV" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/pdf" \
  --data-binary @cv.pdf
```

Responses:

- `200`: The updated candidate
- `400`: The file type isn't supported.
- `401`: The API key is missing or invalid.
- `403`: The API key doesn't have the scope this request needs.
- `404`: The candidate doesn't exist in the API key's organization.
- `429`: The API key made more requests per minute than its rate limit allows. Retry after the number of seconds in the `Retry-After` header.

## Fill / adjust candidate data

`POST /v1/candidates/{candidate_id}/fill` · requires the `candidates:write` scope

Fill or adjust a candidate's values.

Route parameters:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `candidate_id` | `uuid` | Yes | The identifier of the candidate. |

Query parameters:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `prompt` | `string` | No | Custom prompt, e.g. "Expand introduction", "Sort skills by level". Organization's default prompt is used when left empty. |
| `selector` | `Array<string>` | No | JSON selector to which the prompt should be applied, e.g. `["experiences", 0]` to apply the prompt to the first experience. Repeat the parameter for each part. If left empty, the prompt is applied to the entire candidate. |

```bash
curl -X POST "https://api.cv-transformer.com/v1/candidates/{candidate_id}/fill?prompt=&selector=experiences&selector=0" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Responses:

- `200`: The updated candidate
- `400`: The candidate has no layout.
- `401`: The API key is missing or invalid.
- `403`: The API key doesn't have the scope this request needs.
- `404`: The candidate doesn't exist in the API key's organization.
- `429`: The API key made more requests per minute than its rate limit allows. Retry after the number of seconds in the `Retry-After` header.

## Extract candidate data

`POST /v1/candidates/{candidate_id}/extract` · requires the `candidates:read` scope

Extract custom data from a candidate. The body is the [JSON Schema](https://json-schema.org) of the data to extract.

Route parameters:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `candidate_id` | `uuid` | Yes | The identifier of the candidate. |

```bash
curl "https://api.cv-transformer.com/v1/candidates/{candidate_id}/extract" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "object",
    "properties": {
      "email": { "type": "string" },
      "first_name": { "type": "string" },
      "last_name": { "type": "string" }
    },
    "$schema": "http://json-schema.org/draft-07/schema#"
  }'
```

Responses:

- `200`: The extracted data, following the JSON schema
- `400`: The candidate has no attachments with text.
- `401`: The API key is missing or invalid.
- `403`: The API key doesn't have the scope this request needs.
- `404`: The candidate doesn't exist in the API key's organization.
- `429`: The API key made more requests per minute than its rate limit allows. Retry after the number of seconds in the `Retry-After` header.

Errors have a JSON body with a `message`, `details` and `hint`, and for database errors a `code`. See the [introduction](https://www.cv-transformer.com/es/api-reference.md) for more on errors. The full API is described by the OpenAPI spec at https://api.cv-transformer.com/v1/openapi.json.
