# Jobs and scores

> List, store under your own ids and delete the jobs of a workspace, edit their criteria, and read their ranking again with the recruiter’s priorities.

Source: https://hirelayer.co/api-docs/jobs · Markdown: https://hirelayer.co/api-docs/jobs.md

A job is stored with its criteria under its `job_id` by the [Job Criteria Extractor](https://hirelayer.co/api-docs/extract-criteria.md), the [Resume Scorer](https://hirelayer.co/api-docs/score.md) or `PUT /jobs/{job_id}`, and every score the Resume Scorer computes is stored with it. Only jobs with `status: "open"` are searched by [Job Recommendations](https://hirelayer.co/api-docs/search-jobs.md): close one with `{"status": "closed"}`.

- Listing, reading, storing and deleting jobs and scores is free, and so are removing a criterion and changing its `weight`, `requirement` or `type`: the stored scores are re-ranked at once.
- Adding a criterion, rewording one (`label` or `threshold`), or sending new `criteria` or `recompute_criteria: true` to `PUT /jobs/{job_id}` evaluates the job’s stored scores again, 1 credit each.
- Reading the ranking with `precise_ranking=true` adds the ranking reasons, 1 credit per read.

## Jobs

### List stored jobs

`GET https://hirelayer.co/api/v4/jobs`

The jobs of the workspace, most recently updated first. Free.

- **Authentication:** `X-API-Key` header
- **Billing:** Free
- **Client timeout:** at least 30 seconds

#### Headers

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `HireLayer-Workspace` | `string` | No | Your customer's workspace id. Case-insensitive, created on first use. Absent, calls use `default`. |

#### Query parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `limit` | `integer` | No | Page size. 1–100, default `20`. |
| `cursor` | `string` | No | The `next_cursor` of the previous page. |
| `status` | `string` | No | Only open jobs are found by `POST /jobs/search`. One of: `"open"`, `"closed"`. |

#### Example request

**cURL**

```bash
curl -g "https://hirelayer.co/api/v4/jobs?status=open" \
  -H "X-API-Key: $HIRELAYER_API_KEY"
```

**Python**

```python
import os

import requests

response = requests.get(
    "https://hirelayer.co/api/v4/jobs?status=open",
    headers={"X-API-Key": os.environ["HIRELAYER_API_KEY"]},
    timeout=30,
)
response.raise_for_status()

data = response.json()
print(data["data"])
```

**TypeScript**

```typescript
const response = await fetch('https://hirelayer.co/api/v4/jobs?status=open', {
  headers: {
    'X-API-Key': process.env.HIRELAYER_API_KEY!,
  },
  signal: AbortSignal.timeout(30_000),
})

if (!response.ok) {
  throw new Error(`HireLayer ${response.status}: ${await response.text()}`)
}

const data = await response.json()
console.log(data.data)
```

#### Response `200`

A page of jobs.

| Field | Type | Description |
| --- | --- | --- |
| `data` | `object[]` |  |
| `data[].job_id` | `string` |  |
| `data[].workspace` | `string` |  |
| `data[].title` | `string \| null` |  |
| `data[].text` | `string` | The job description. |
| `data[].location` | `string \| null` |  |
| `data[].language` | `string` | Language of the job's criteria labels and thresholds, score summaries and explanations, and ranking reasons. Sent, it is kept; left out, it is the language of the job's text when the job is created, and `en` when that text is in another language or its language cannot be told. One of: `"en"`, `"fr"`, `"de"`, `"es"`, `"pt"`. |
| `data[].status` | `string` | Only open jobs are found by `POST /jobs/search`. One of: `"open"`, `"closed"`. |
| `data[].criteria` | `object[]` |  |
| `data[].criteria[].criterion_id` | `string` | Stable id in the job, such as `crit_2`. Never reused. |
| `data[].criteria[].label` | `string` |  |
| `data[].criteria[].type` | `string` | One of: `"skill"`, `"experience"`, `"education"`, `"language"`, `"certification"`, `"location"`, `"soft_skill"`, `"other"`. |
| `data[].criteria[].requirement` | `string` | `required`: a resume that clearly lacks it loses 25 % of its score, never eliminated outright. `preferred` and `bonus` count only through their weight. One of: `"required"`, `"preferred"`, `"bonus"`. |
| `data[].criteria[].weight` | `integer` | Importance. 3 essential, 2 important, 1 nice to have, 0 off: kept on the job, left out of every score, of the required-criterion penalty, of `required_missing` and of `to_verify`. Texts already written (`summary`, `explanation`) stay as they are. 0–3. |
| `data[].criteria[].threshold` | `string \| null` |  |
| `data[].criteria[].source` | `string` | `extracted` by HireLayer, or `custom`: sent or edited by you. One of: `"extracted"`, `"custom"`. |
| `data[].scores_count` | `integer` | ≥ 0. |
| `data[].created_at` | `string (date-time)` |  |
| `data[].updated_at` | `string (date-time)` |  |
| `next_cursor` | `string \| null` |  |

#### Errors

| Status | `error.code` | `error.message` | When | Retry |
| --- | --- | --- | --- | --- |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `status must be open or closed.` | `param`: `status`. `status` is not `open` or `closed`. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `limit must be an integer from 1 to 100.` | `param`: `limit`. `limit` out of range. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `cursor is not a next_cursor returned by this API.` | `param`: `cursor`. `cursor` is not a `next_cursor` of this list. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `HireLayer-Workspace must contain letters, digits and . _ : ~ -, up to 200 characters.` | `param`: `HireLayer-Workspace`. `HireLayer-Workspace` is not a valid id. | Fix, then retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `The X-API-Key header is missing.` | No `X-API-Key` header. | Fix, then retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `Authorization: Bearer is not accepted: send the key in the X-API-Key header.` | The key sent as `Authorization: Bearer`: it goes in `X-API-Key`. | Fix, then retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `The key goes in the X-API-Key header, not in the URL.` | The key sent in the URL: it goes in `X-API-Key`. | Fix, then retry |
| `401` | [`invalid_api_key`](https://hirelayer.co/api-docs/errors.md#invalid_api_key) | `Invalid API key: it is unknown, malformed or revoked.` | The key is unknown, malformed or revoked. | Fix, then retry |
| `500` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `Internal error: retry later. If it persists, contact support with the request_id.` | An unexpected error. | Retry with backoff |
| `502` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The service could not be reached: retry later.` | The service could not be reached. | Retry with backoff |
| `503` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The credit service is briefly unavailable: retry in a second.` | The credit service is briefly unavailable. Headers: `Retry-After: <seconds>`. | Retry with backoff |
| `504` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The service did not answer in time. The change may still be applied: read it before sending it again.` | No answer in time. | Retry with backoff |

Example error (`400`):

```json
{
  "error": {
    "code": "invalid_request",
    "message": "status must be open or closed.",
    "param": "status",
    "doc_url": "https://hirelayer.co/api-docs/errors#invalid_request"
  },
  "request_id": "6f1c2a4e-1d2b-4c3a-9e8f-0a1b2c3d4e5f"
}
```

### Get a stored job

`GET https://hirelayer.co/api/v4/jobs/{job_id}`

Get a stored job.

- **Authentication:** `X-API-Key` header
- **Billing:** Free
- **Client timeout:** at least 30 seconds

#### Path parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `job_id` | `string` | Yes | Letters, digits and `. _ : ~ -`, up to 200 characters. |

#### Headers

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `HireLayer-Workspace` | `string` | No | Your customer's workspace id. Case-insensitive, created on first use. Absent, calls use `default`. |

#### Example request

**cURL**

```bash
# Example ids: replace job_30cfa454c97663f3e1801e5a61b87d74 with a job_id returned by POST /api/v4/jobs/extract-criteria.
curl https://hirelayer.co/api/v4/jobs/job_30cfa454c97663f3e1801e5a61b87d74 \
  -H "X-API-Key: $HIRELAYER_API_KEY"
```

**Python**

```python
# Example ids: replace job_30cfa454c97663f3e1801e5a61b87d74 with a job_id returned by POST /api/v4/jobs/extract-criteria.
import os

import requests

response = requests.get(
    "https://hirelayer.co/api/v4/jobs/job_30cfa454c97663f3e1801e5a61b87d74",
    headers={"X-API-Key": os.environ["HIRELAYER_API_KEY"]},
    timeout=30,
)
response.raise_for_status()

data = response.json()
print(data["criteria"])
```

**TypeScript**

```typescript
// Example ids: replace job_30cfa454c97663f3e1801e5a61b87d74 with a job_id returned by POST /api/v4/jobs/extract-criteria.
const response = await fetch('https://hirelayer.co/api/v4/jobs/job_30cfa454c97663f3e1801e5a61b87d74', {
  headers: {
    'X-API-Key': process.env.HIRELAYER_API_KEY!,
  },
  signal: AbortSignal.timeout(30_000),
})

if (!response.ok) {
  throw new Error(`HireLayer ${response.status}: ${await response.text()}`)
}

const data = await response.json()
console.log(data.criteria)
```

#### Response `200`

The job and its criteria.

| Field | Type | Description |
| --- | --- | --- |
| `job_id` | `string` |  |
| `workspace` | `string` |  |
| `title` | `string \| null` |  |
| `text` | `string` | The job description. |
| `location` | `string \| null` |  |
| `language` | `string` | Language of the job's criteria labels and thresholds, score summaries and explanations, and ranking reasons. Sent, it is kept; left out, it is the language of the job's text when the job is created, and `en` when that text is in another language or its language cannot be told. One of: `"en"`, `"fr"`, `"de"`, `"es"`, `"pt"`. |
| `status` | `string` | Only open jobs are found by `POST /jobs/search`. One of: `"open"`, `"closed"`. |
| `criteria` | `object[]` |  |
| `criteria[].criterion_id` | `string` | Stable id in the job, such as `crit_2`. Never reused. |
| `criteria[].label` | `string` |  |
| `criteria[].type` | `string` | One of: `"skill"`, `"experience"`, `"education"`, `"language"`, `"certification"`, `"location"`, `"soft_skill"`, `"other"`. |
| `criteria[].requirement` | `string` | `required`: a resume that clearly lacks it loses 25 % of its score, never eliminated outright. `preferred` and `bonus` count only through their weight. One of: `"required"`, `"preferred"`, `"bonus"`. |
| `criteria[].weight` | `integer` | Importance. 3 essential, 2 important, 1 nice to have, 0 off: kept on the job, left out of every score, of the required-criterion penalty, of `required_missing` and of `to_verify`. Texts already written (`summary`, `explanation`) stay as they are. 0–3. |
| `criteria[].threshold` | `string \| null` |  |
| `criteria[].source` | `string` | `extracted` by HireLayer, or `custom`: sent or edited by you. One of: `"extracted"`, `"custom"`. |
| `scores_count` | `integer` | ≥ 0. |
| `created_at` | `string (date-time)` |  |
| `updated_at` | `string (date-time)` |  |

```json
{
  "job_id": "job_30cfa454c97663f3e1801e5a61b87d74",
  "workspace": "acme",
  "title": "Ingénieur IA / Développeur backend Node.js (H/F)",
  "text": "Ingénieur IA / Développeur backend Node.js (H/F)\nLumio · Paris 9e · CDI · Hybride (2 jours de télétravail par semaine)\n\nLumio édite une plateforme SaaS qui automatise le traitement de documents administratifs pour les cabinets comptables (300 clients, 2 millions de documents par mois). Notre équipe produit compte 14 personnes.\n\nVos missions\n- Concevoir et faire évoluer les API backend (Node.js, TypeScript) de la plateforme\n- Intégrer des modèles de langage en production : RAG, extraction structurée, évaluation de la qualité des réponses\n- Déployer et superviser l'infrastructure sur AWS (Lambda, S3, DynamoDB) avec Terraform\n- Participer aux revues de code et accompagner deux développeurs juniors\n\nVotre profil\n- Diplôme d'ingénieur ou Bac+5 en informatique\n- 3 ans minimum d'expérience en développement backend Node.js / TypeScript\n- Expérience indispensable de l'intégration de LLM en production\n- Vous maîtrisez AWS (Lambda, S3, DynamoDB) ; une certification AWS est un plus\n- Anglais courant (B2 minimum) obligatoire : une partie de nos clients est à l'étranger\n- Idéalement, une première expérience d'encadrement technique\n- Vous acceptez une astreinte d'une semaine sur six\n\nCe que nous offrons\n- Salaire de 55 à 65 k€ selon expérience, BSPCE\n- Tickets restaurant, mutuelle prise en charge à 100 %\n- Démarrage dès que possible",
  "location": null,
  "language": "fr",
  "status": "open",
  "criteria": [
    {
      "criterion_id": "crit_1",
      "label": "Développement backend Node.js / TypeScript",
      "type": "skill",
      "requirement": "preferred",
      "weight": 3,
      "threshold": "3 ans minimum",
      "source": "extracted"
    },
    {
      "criterion_id": "crit_2",
      "label": "Intégration de LLM en production",
      "type": "skill",
      "requirement": "required",
      "weight": 3,
      "threshold": null,
      "source": "extracted"
    },
    {
      "criterion_id": "crit_3",
      "label": "AWS (Lambda, S3, DynamoDB)",
      "type": "skill",
      "requirement": "preferred",
      "weight": 2,
      "threshold": null,
      "source": "extracted"
    },
    {
      "criterion_id": "crit_4",
      "label": "Diplôme d'ingénieur ou Bac+5 en informatique",
      "type": "education",
      "requirement": "preferred",
      "weight": 2,
      "threshold": "Bac+5",
      "source": "extracted"
    },
    {
      "criterion_id": "crit_5",
      "label": "Anglais",
      "type": "language",
      "requirement": "required",
      "weight": 2,
      "threshold": "B2 minimum",
      "source": "extracted"
    },
    {
      "criterion_id": "crit_6",
      "label": "Encadrement technique",
      "type": "experience",
      "requirement": "bonus",
      "weight": 1,
      "threshold": null,
      "source": "extracted"
    },
    {
      "criterion_id": "crit_7",
      "label": "Certification AWS",
      "type": "certification",
      "requirement": "bonus",
      "weight": 1,
      "threshold": null,
      "source": "extracted"
    },
    {
      "criterion_id": "crit_8",
      "label": "Astreinte",
      "type": "other",
      "requirement": "required",
      "weight": 1,
      "threshold": "1 semaine sur 6",
      "source": "extracted"
    }
  ],
  "scores_count": 2,
  "created_at": "2026-10-26T22:38:23Z",
  "updated_at": "2026-10-26T22:38:23Z"
}
```

#### Errors

| Status | `error.code` | `error.message` | When | Retry |
| --- | --- | --- | --- | --- |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `job_id must contain letters, digits and . _ : ~ -, up to 200 characters.` | `param`: `job_id`. `job_id` in the path is not a valid id. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `HireLayer-Workspace must contain letters, digits and . _ : ~ -, up to 200 characters.` | `param`: `HireLayer-Workspace`. `HireLayer-Workspace` is not a valid id. | Fix, then retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `The X-API-Key header is missing.` | No `X-API-Key` header. | Fix, then retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `Authorization: Bearer is not accepted: send the key in the X-API-Key header.` | The key sent as `Authorization: Bearer`: it goes in `X-API-Key`. | Fix, then retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `The key goes in the X-API-Key header, not in the URL.` | The key sent in the URL: it goes in `X-API-Key`. | Fix, then retry |
| `401` | [`invalid_api_key`](https://hirelayer.co/api-docs/errors.md#invalid_api_key) | `Invalid API key: it is unknown, malformed or revoked.` | The key is unknown, malformed or revoked. | Fix, then retry |
| `404` | [`not_found`](https://hirelayer.co/api-docs/errors.md#not_found) | `No job req-7 in workspace default.` | `param`: `job_id`. No job under this id in the workspace. | Do not retry |
| `500` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `Internal error: retry later. If it persists, contact support with the request_id.` | An unexpected error. | Retry with backoff |
| `502` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The service could not be reached: retry later.` | The service could not be reached. | Retry with backoff |
| `503` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The credit service is briefly unavailable: retry in a second.` | The credit service is briefly unavailable. Headers: `Retry-After: <seconds>`. | Retry with backoff |
| `504` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The service did not answer in time. The change may still be applied: read it before sending it again.` | No answer in time. | Retry with backoff |

Example error (`400`):

```json
{
  "error": {
    "code": "invalid_request",
    "message": "job_id must contain letters, digits and . _ : ~ -, up to 200 characters.",
    "param": "job_id",
    "doc_url": "https://hirelayer.co/api-docs/errors#invalid_request"
  },
  "request_id": "6f1c2a4e-1d2b-4c3a-9e8f-0a1b2c3d4e5f"
}
```

### Store a job under your id

`PUT https://hirelayer.co/api/v4/jobs/{job_id}`

Creates or updates a job under your `job_id`, for an ATS that keeps its own ids. Without `criteria` on creation, they are extracted from its `text`, as by `POST /jobs/extract-criteria`. On update, criteria are kept unless you send `criteria` or `recompute_criteria: true`; a criterion with the same label and threshold as a current one keeps its id and its per-criterion results.

- **Authentication:** `X-API-Key` header
- **Content type:** `application/json`
- **Billing:** Free; 1 credit per stored score evaluated again
- **Client timeout:** at least 150 seconds

#### Path parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `job_id` | `string` | Yes | Letters, digits and `. _ : ~ -`, up to 200 characters. |

#### Headers

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `HireLayer-Workspace` | `string` | No | Your customer's workspace id. Case-insensitive, created on first use. Absent, calls use `default`. |

#### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `text` | `string` | No | The job description. Required to create the job. 30–50,000 characters. |
| `title` | `string \| null` | No | `null` erases it. ≤ 300 characters. |
| `location` | `string \| null` | No | `null` erases it. ≤ 300 characters. |
| `status` | `string` | No | Only open jobs are found by `POST /jobs/search`. One of: `"open"`, `"closed"`. |
| `language` | `string` | No | Language of the job's criteria labels and thresholds, score summaries and explanations, and ranking reasons. Sent, it is kept; left out, it is the language of the job's text when the job is created, and `en` when that text is in another language or its language cannot be told. One of: `"en"`, `"fr"`, `"de"`, `"es"`, `"pt"`. |
| `criteria` | `object[]` | No | 1–20 items. |
| `criteria[].label` | `string` | Yes | 1–300 characters. |
| `criteria[].type` | `string` | No | One of: `"skill"`, `"experience"`, `"education"`, `"language"`, `"certification"`, `"location"`, `"soft_skill"`, `"other"`. |
| `criteria[].requirement` | `string` | No | `required`: a resume that clearly lacks it loses 25 % of its score, never eliminated outright. `preferred` and `bonus` count only through their weight. One of: `"required"`, `"preferred"`, `"bonus"`. |
| `criteria[].weight` | `integer` | No | Importance. 3 essential, 2 important, 1 nice to have, 0 off: kept on the job, left out of every score, of the required-criterion penalty, of `required_missing` and of `to_verify`. Texts already written (`summary`, `explanation`) stay as they are. 0–3. |
| `criteria[].threshold` | `string \| null` | No | Minimum level in short text, such as `3 ans minimum`, `Bac+2` or `B2`. ≤ 100 characters. |
| `criteria[].criterion_id` | `string` | No |  |
| `criteria[].source` | `string` | No |  |
| `recompute_criteria` | `boolean` | No | Extract the criteria again from `text`. Not with `criteria`. Default `false`. |

#### Example request

**cURL**

```bash
curl -X PUT https://hirelayer.co/api/v4/jobs/req-2026-114 \
  -H "X-API-Key: $HIRELAYER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Ingénieur IA / Développeur backend Node.js (H/F)",
    "text": "Ingénieur IA / Développeur backend Node.js (H/F)\nLumio · Paris 9e · CDI · Hybride (2 jours de télétravail par semaine)\n- 3 ans minimum d'\''expérience en développement backend Node.js / TypeScript\n- Expérience indispensable de l'\''intégration de LLM en production\n- Vous maîtrisez AWS (Lambda, S3, DynamoDB) ; une certification AWS est un plus\n- Anglais courant (B2 minimum) obligatoire",
    "location": "Paris",
    "status": "open"
  }'
```

**Python**

```python
import os

import requests

response = requests.put(
    "https://hirelayer.co/api/v4/jobs/req-2026-114",
    headers={"X-API-Key": os.environ["HIRELAYER_API_KEY"]},
    json={
        "title": "Ingénieur IA / Développeur backend Node.js (H/F)",
        "text": "Ingénieur IA / Développeur backend Node.js (H/F)\nLumio · Paris 9e · CDI · Hybride (2 jours de télétravail par semaine)\n- 3 ans minimum d'expérience en développement backend Node.js / TypeScript\n- Expérience indispensable de l'intégration de LLM en production\n- Vous maîtrisez AWS (Lambda, S3, DynamoDB) ; une certification AWS est un plus\n- Anglais courant (B2 minimum) obligatoire",
        "location": "Paris",
        "status": "open",
    },
    timeout=150,
)
response.raise_for_status()

data = response.json()
print(data["criteria"])
```

**TypeScript**

```typescript
const response = await fetch('https://hirelayer.co/api/v4/jobs/req-2026-114', {
  method: 'PUT',
  headers: {
    'X-API-Key': process.env.HIRELAYER_API_KEY!,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    title: 'Ingénieur IA / Développeur backend Node.js (H/F)',
    text: 'Ingénieur IA / Développeur backend Node.js (H/F)\nLumio · Paris 9e · CDI · Hybride (2 jours de télétravail par semaine)\n- 3 ans minimum d\'expérience en développement backend Node.js / TypeScript\n- Expérience indispensable de l\'intégration de LLM en production\n- Vous maîtrisez AWS (Lambda, S3, DynamoDB) ; une certification AWS est un plus\n- Anglais courant (B2 minimum) obligatoire',
    location: 'Paris',
    status: 'open',
  }),
  signal: AbortSignal.timeout(150_000),
})

if (!response.ok) {
  throw new Error(`HireLayer ${response.status}: ${await response.text()}`)
}

const data = await response.json()
console.log(data.criteria)
```

#### Response `200`

Updated.

| Field | Type | Description |
| --- | --- | --- |
| `job_id` | `string` |  |
| `workspace` | `string` |  |
| `title` | `string \| null` |  |
| `text` | `string` | The job description. |
| `location` | `string \| null` |  |
| `language` | `string` | Language of the job's criteria labels and thresholds, score summaries and explanations, and ranking reasons. Sent, it is kept; left out, it is the language of the job's text when the job is created, and `en` when that text is in another language or its language cannot be told. One of: `"en"`, `"fr"`, `"de"`, `"es"`, `"pt"`. |
| `status` | `string` | Only open jobs are found by `POST /jobs/search`. One of: `"open"`, `"closed"`. |
| `criteria` | `object[]` |  |
| `criteria[].criterion_id` | `string` | Stable id in the job, such as `crit_2`. Never reused. |
| `criteria[].label` | `string` |  |
| `criteria[].type` | `string` | One of: `"skill"`, `"experience"`, `"education"`, `"language"`, `"certification"`, `"location"`, `"soft_skill"`, `"other"`. |
| `criteria[].requirement` | `string` | `required`: a resume that clearly lacks it loses 25 % of its score, never eliminated outright. `preferred` and `bonus` count only through their weight. One of: `"required"`, `"preferred"`, `"bonus"`. |
| `criteria[].weight` | `integer` | Importance. 3 essential, 2 important, 1 nice to have, 0 off: kept on the job, left out of every score, of the required-criterion penalty, of `required_missing` and of `to_verify`. Texts already written (`summary`, `explanation`) stay as they are. 0–3. |
| `criteria[].threshold` | `string \| null` |  |
| `criteria[].source` | `string` | `extracted` by HireLayer, or `custom`: sent or edited by you. One of: `"extracted"`, `"custom"`. |
| `scores_count` | `integer` | ≥ 0. |
| `created_at` | `string (date-time)` |  |
| `updated_at` | `string (date-time)` |  |

#### Response `201`

Created.

| Field | Type | Description |
| --- | --- | --- |
| `job_id` | `string` |  |
| `workspace` | `string` |  |
| `title` | `string \| null` |  |
| `text` | `string` | The job description. |
| `location` | `string \| null` |  |
| `language` | `string` | Language of the job's criteria labels and thresholds, score summaries and explanations, and ranking reasons. Sent, it is kept; left out, it is the language of the job's text when the job is created, and `en` when that text is in another language or its language cannot be told. One of: `"en"`, `"fr"`, `"de"`, `"es"`, `"pt"`. |
| `status` | `string` | Only open jobs are found by `POST /jobs/search`. One of: `"open"`, `"closed"`. |
| `criteria` | `object[]` |  |
| `criteria[].criterion_id` | `string` | Stable id in the job, such as `crit_2`. Never reused. |
| `criteria[].label` | `string` |  |
| `criteria[].type` | `string` | One of: `"skill"`, `"experience"`, `"education"`, `"language"`, `"certification"`, `"location"`, `"soft_skill"`, `"other"`. |
| `criteria[].requirement` | `string` | `required`: a resume that clearly lacks it loses 25 % of its score, never eliminated outright. `preferred` and `bonus` count only through their weight. One of: `"required"`, `"preferred"`, `"bonus"`. |
| `criteria[].weight` | `integer` | Importance. 3 essential, 2 important, 1 nice to have, 0 off: kept on the job, left out of every score, of the required-criterion penalty, of `required_missing` and of `to_verify`. Texts already written (`summary`, `explanation`) stay as they are. 0–3. |
| `criteria[].threshold` | `string \| null` |  |
| `criteria[].source` | `string` | `extracted` by HireLayer, or `custom`: sent or edited by you. One of: `"extracted"`, `"custom"`. |
| `scores_count` | `integer` | ≥ 0. |
| `created_at` | `string (date-time)` |  |
| `updated_at` | `string (date-time)` |  |

#### Errors

| Status | `error.code` | `error.message` | When | Retry |
| --- | --- | --- | --- | --- |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `text must be 30 characters or more: send the job's full text.` | `param`: `text`. `text` is under 30 characters. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `text is required to create a job.` | `param`: `text`. A new job without its text. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `status must be open or closed.` | `param`: `status`. `status` is not `open` or `closed`. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `criteria[0].weight must be 0, 1, 2 or 3.` | `param`: `criteria[0].weight`. A criterion breaks a rule (here its weight): `param` names the field. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `criteria must hold 1 to 20 criteria.` | `param`: `criteria`. No criterion, or more than 20. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `criteria[1] repeats criteria[0] (same label and threshold).` | `param`: `criteria[1].label`. Two criteria with the same label and threshold. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `title must be 300 characters or less.` | `param`: `title`. `title` or `location` over 300 characters. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `job_id must contain letters, digits and . _ : ~ -, up to 200 characters.` | `param`: `job_id`. `job_id` in the path is not a valid id. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `Unknown field description.` | `param`: `description`. A field the call does not take. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `The request body is not valid JSON.` | The body is not valid JSON, not an object, or not sent as `application/json`. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `HireLayer-Workspace must contain letters, digits and . _ : ~ -, up to 200 characters.` | `param`: `HireLayer-Workspace`. `HireLayer-Workspace` is not a valid id. | Fix, then retry |
| `400` | [`storage_required`](https://hirelayer.co/api-docs/errors.md#storage_required) | `This workspace runs without storage (do_not_store_data): nothing can be stored in it.` | `param`: `HireLayer-Workspace`. The workspace runs without storage, and the call would store something in it. | Do not retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `The X-API-Key header is missing.` | No `X-API-Key` header. | Fix, then retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `Authorization: Bearer is not accepted: send the key in the X-API-Key header.` | The key sent as `Authorization: Bearer`: it goes in `X-API-Key`. | Fix, then retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `The key goes in the X-API-Key header, not in the URL.` | The key sent in the URL: it goes in `X-API-Key`. | Fix, then retry |
| `401` | [`invalid_api_key`](https://hirelayer.co/api-docs/errors.md#invalid_api_key) | `Invalid API key: it is unknown, malformed or revoked.` | The key is unknown, malformed or revoked. | Fix, then retry |
| `403` | [`insufficient_credits`](https://hirelayer.co/api-docs/errors.md#insufficient_credits) | `This call evaluates 12 scores, 1 credit each: 12 credits, and 3 are left. Nothing was processed or billed.` | The change evaluates the job's stored scores again, and the credits left do not cover them. | Fix, then retry |
| `413` | [`payload_too_large`](https://hirelayer.co/api-docs/errors.md#payload_too_large) | `text must be 50,000 characters or less.` | `param`: `text`. The text is over 50,000 characters. | Fix, then retry |
| `413` | [`payload_too_large`](https://hirelayer.co/api-docs/errors.md#payload_too_large) | `The request body must be 10 MB or less.` | The body is over 10 MB. | Fix, then retry |
| `500` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `Internal error: retry later. If it persists, contact support with the request_id.` | An unexpected error. | Retry with backoff |
| `502` | [`model_unavailable`](https://hirelayer.co/api-docs/errors.md#model_unavailable) | `The analysis did not complete after several attempts. Nothing was stored or billed: retry with backoff.` | The analysis did not complete. | Retry with backoff |
| `502` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The service could not be reached: retry later.` | The service could not be reached. | Retry with backoff |
| `503` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The credit service is briefly unavailable: retry in a second.` | The credit service is briefly unavailable. Headers: `Retry-After: <seconds>`. | Retry with backoff |
| `504` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The service did not answer in time. The change may still be applied: read it before sending it again.` | No answer in time. | Retry with backoff |

Example error (`400`):

```json
{
  "error": {
    "code": "invalid_request",
    "message": "text must be 30 characters or more: send the job's full text.",
    "param": "text",
    "doc_url": "https://hirelayer.co/api-docs/errors#invalid_request"
  },
  "request_id": "6f1c2a4e-1d2b-4c3a-9e8f-0a1b2c3d4e5f"
}
```

#### Behavior

- **Partial update:** a field left out keeps its stored value (`text`, `title`, `location`, `status`, `language`, and the criteria as above); `null` erases `title` or `location`. To close a job, send `{"status": "closed"}`. `text` is required only to create a job: an unknown `job_id` without `text` is refused (`400`, `param: text`). A `text` holds at least 30 characters.
- **Language:** `en`, `fr`, `de`, `es` or `pt`, for the criteria extracted and the scores' summaries and explanations. Left out on creation, it is the language of `text` (`en` for a text in any other language); on update, the stored one stays. Criteria already extracted keep their wording: send `recompute_criteria: true` to extract them again in a new `language`.
- **Without storage:** refused in a workspace without storage (`400 storage_required`).
- **Credits:** 0, plus 1 per stored score evaluated again because criteria were added, reworded or extracted again.
- **Verdicts:** a criterion added, removed or reworded sets the `verdict` of the job's scores to `null`. Each comes back when its resume is sent again to `POST /resumes/score` for this job (1 credit, a full evaluation).

### Delete a job

`DELETE https://hirelayer.co/api/v4/jobs/{job_id}`

Deletes the job, its criteria and its scores. The resumes stay. Deleting a job that does not exist is not an error. Free.

- **Authentication:** `X-API-Key` header
- **Billing:** Free
- **Client timeout:** at least 30 seconds

#### Path parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `job_id` | `string` | Yes | Letters, digits and `. _ : ~ -`, up to 200 characters. |

#### Headers

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `HireLayer-Workspace` | `string` | No | Your customer's workspace id. Case-insensitive, created on first use. Absent, calls use `default`. |

#### Example request

**cURL**

```bash
curl -X DELETE https://hirelayer.co/api/v4/jobs/req-2026-114 \
  -H "X-API-Key: $HIRELAYER_API_KEY"
```

**Python**

```python
import os

import requests

response = requests.delete(
    "https://hirelayer.co/api/v4/jobs/req-2026-114",
    headers={"X-API-Key": os.environ["HIRELAYER_API_KEY"]},
    timeout=30,
)
response.raise_for_status()
print(response.status_code)
```

**TypeScript**

```typescript
const response = await fetch('https://hirelayer.co/api/v4/jobs/req-2026-114', {
  method: 'DELETE',
  headers: {
    'X-API-Key': process.env.HIRELAYER_API_KEY!,
  },
  signal: AbortSignal.timeout(30_000),
})

if (!response.ok) {
  throw new Error(`HireLayer ${response.status}: ${await response.text()}`)
}

console.log(response.status)
```

#### Response `204`

Deleted.

#### Errors

| Status | `error.code` | `error.message` | When | Retry |
| --- | --- | --- | --- | --- |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `job_id must contain letters, digits and . _ : ~ -, up to 200 characters.` | `param`: `job_id`. `job_id` in the path is not a valid id. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `HireLayer-Workspace must contain letters, digits and . _ : ~ -, up to 200 characters.` | `param`: `HireLayer-Workspace`. `HireLayer-Workspace` is not a valid id. | Fix, then retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `The X-API-Key header is missing.` | No `X-API-Key` header. | Fix, then retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `Authorization: Bearer is not accepted: send the key in the X-API-Key header.` | The key sent as `Authorization: Bearer`: it goes in `X-API-Key`. | Fix, then retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `The key goes in the X-API-Key header, not in the URL.` | The key sent in the URL: it goes in `X-API-Key`. | Fix, then retry |
| `401` | [`invalid_api_key`](https://hirelayer.co/api-docs/errors.md#invalid_api_key) | `Invalid API key: it is unknown, malformed or revoked.` | The key is unknown, malformed or revoked. | Fix, then retry |
| `500` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `Internal error: retry later. If it persists, contact support with the request_id.` | An unexpected error. | Retry with backoff |
| `502` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The service could not be reached: retry later.` | The service could not be reached. | Retry with backoff |
| `503` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The credit service is briefly unavailable: retry in a second.` | The credit service is briefly unavailable. Headers: `Retry-After: <seconds>`. | Retry with backoff |
| `504` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The service did not answer in time. The change may still be applied: read it before sending it again.` | No answer in time. | Retry with backoff |

Example error (`400`):

```json
{
  "error": {
    "code": "invalid_request",
    "message": "job_id must contain letters, digits and . _ : ~ -, up to 200 characters.",
    "param": "job_id",
    "doc_url": "https://hirelayer.co/api-docs/errors#invalid_request"
  },
  "request_id": "6f1c2a4e-1d2b-4c3a-9e8f-0a1b2c3d4e5f"
}
```

## Criteria

### Add a criterion

`POST https://hirelayer.co/api/v4/jobs/{job_id}/criteria`

Adds a criterion. The job's stored scores are evaluated on it in the background, 1 credit each (their `evaluation_status` is `pending` meanwhile), and their `verdict` is `null` until their next full evaluation (see `Verdict`).

- **Authentication:** `X-API-Key` header
- **Content type:** `application/json`
- **Billing:** 1 credit per stored score of the job
- **Client timeout:** at least 150 seconds

#### Path parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `job_id` | `string` | Yes | Letters, digits and `. _ : ~ -`, up to 200 characters. |

#### Headers

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `HireLayer-Workspace` | `string` | No | Your customer's workspace id. Case-insensitive, created on first use. Absent, calls use `default`. |

#### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `label` | `string` | Yes | 1–300 characters. |
| `type` | `string` | No | One of: `"skill"`, `"experience"`, `"education"`, `"language"`, `"certification"`, `"location"`, `"soft_skill"`, `"other"`. |
| `requirement` | `string` | No | `required`: a resume that clearly lacks it loses 25 % of its score, never eliminated outright. `preferred` and `bonus` count only through their weight. One of: `"required"`, `"preferred"`, `"bonus"`. |
| `weight` | `integer` | No | Importance. 3 essential, 2 important, 1 nice to have, 0 off: kept on the job, left out of every score, of the required-criterion penalty, of `required_missing` and of `to_verify`. Texts already written (`summary`, `explanation`) stay as they are. 0–3. |
| `threshold` | `string \| null` | No | Minimum level in short text, such as `3 ans minimum`, `Bac+2` or `B2`. ≤ 100 characters. |
| `criterion_id` | `string` | No |  |
| `source` | `string` | No |  |

#### Example request

**cURL**

```bash
# Example ids: replace job_30cfa454c97663f3e1801e5a61b87d74 with a job_id returned by POST /api/v4/jobs/extract-criteria.
curl -X POST https://hirelayer.co/api/v4/jobs/job_30cfa454c97663f3e1801e5a61b87d74/criteria \
  -H "X-API-Key: $HIRELAYER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "Terraform",
    "type": "skill",
    "requirement": "preferred",
    "weight": 2
  }'
```

**Python**

```python
# Example ids: replace job_30cfa454c97663f3e1801e5a61b87d74 with a job_id returned by POST /api/v4/jobs/extract-criteria.
import os

import requests

response = requests.post(
    "https://hirelayer.co/api/v4/jobs/job_30cfa454c97663f3e1801e5a61b87d74/criteria",
    headers={"X-API-Key": os.environ["HIRELAYER_API_KEY"]},
    json={
        "label": "Terraform",
        "type": "skill",
        "requirement": "preferred",
        "weight": 2,
    },
    timeout=150,
)
response.raise_for_status()

data = response.json()
print(data["criterion_id"])
```

**TypeScript**

```typescript
// Example ids: replace job_30cfa454c97663f3e1801e5a61b87d74 with a job_id returned by POST /api/v4/jobs/extract-criteria.
const response = await fetch('https://hirelayer.co/api/v4/jobs/job_30cfa454c97663f3e1801e5a61b87d74/criteria', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.HIRELAYER_API_KEY!,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    label: 'Terraform',
    type: 'skill',
    requirement: 'preferred',
    weight: 2,
  }),
  signal: AbortSignal.timeout(150_000),
})

if (!response.ok) {
  throw new Error(`HireLayer ${response.status}: ${await response.text()}`)
}

const data = await response.json()
console.log(data.criterion_id)
```

#### Response `201`

Added.

| Field | Type | Description |
| --- | --- | --- |
| `criterion_id` | `string` | Stable id in the job, such as `crit_2`. Never reused. |
| `label` | `string` |  |
| `type` | `string` | One of: `"skill"`, `"experience"`, `"education"`, `"language"`, `"certification"`, `"location"`, `"soft_skill"`, `"other"`. |
| `requirement` | `string` | `required`: a resume that clearly lacks it loses 25 % of its score, never eliminated outright. `preferred` and `bonus` count only through their weight. One of: `"required"`, `"preferred"`, `"bonus"`. |
| `weight` | `integer` | Importance. 3 essential, 2 important, 1 nice to have, 0 off: kept on the job, left out of every score, of the required-criterion penalty, of `required_missing` and of `to_verify`. Texts already written (`summary`, `explanation`) stay as they are. 0–3. |
| `threshold` | `string \| null` |  |
| `source` | `string` | `extracted` by HireLayer, or `custom`: sent or edited by you. One of: `"extracted"`, `"custom"`. |

#### Errors

| Status | `error.code` | `error.message` | When | Retry |
| --- | --- | --- | --- | --- |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `label is required.` | `param`: `label`. No label. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `type must be one of skill, experience, education, language, certification, location, soft_skill, other.` | `param`: `type`. A field out of its values (here `type`): `param` names it. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `weight must be 0, 1, 2 or 3.` | `param`: `weight`. A criterion breaks a rule (here its weight): `param` names the field. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `A job has at most 20 criteria.` | The job has 20 criteria already. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `Criterion crit_3 already has this label and threshold.` | `param`: `label`. Another criterion of the job has the same label and threshold. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `job_id must contain letters, digits and . _ : ~ -, up to 200 characters.` | `param`: `job_id`. `job_id` in the path is not a valid id. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `Unknown field name.` | `param`: `name`. A field the call does not take. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `The request body is not valid JSON.` | The body is not valid JSON, not an object, or not sent as `application/json`. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `HireLayer-Workspace must contain letters, digits and . _ : ~ -, up to 200 characters.` | `param`: `HireLayer-Workspace`. `HireLayer-Workspace` is not a valid id. | Fix, then retry |
| `400` | [`storage_required`](https://hirelayer.co/api-docs/errors.md#storage_required) | `This workspace runs without storage (do_not_store_data): nothing can be stored in it.` | `param`: `HireLayer-Workspace`. The workspace runs without storage, and the call would store something in it. | Do not retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `The X-API-Key header is missing.` | No `X-API-Key` header. | Fix, then retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `Authorization: Bearer is not accepted: send the key in the X-API-Key header.` | The key sent as `Authorization: Bearer`: it goes in `X-API-Key`. | Fix, then retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `The key goes in the X-API-Key header, not in the URL.` | The key sent in the URL: it goes in `X-API-Key`. | Fix, then retry |
| `401` | [`invalid_api_key`](https://hirelayer.co/api-docs/errors.md#invalid_api_key) | `Invalid API key: it is unknown, malformed or revoked.` | The key is unknown, malformed or revoked. | Fix, then retry |
| `403` | [`insufficient_credits`](https://hirelayer.co/api-docs/errors.md#insufficient_credits) | `This call evaluates 12 scores, 1 credit each: 12 credits, and 3 are left. Nothing was processed or billed.` | The change evaluates the job's stored scores again, and the credits left do not cover them. | Fix, then retry |
| `404` | [`not_found`](https://hirelayer.co/api-docs/errors.md#not_found) | `No job req-7 in workspace default.` | `param`: `job_id`. No job under this id in the workspace. | Do not retry |
| `413` | [`payload_too_large`](https://hirelayer.co/api-docs/errors.md#payload_too_large) | `The request body must be 10 MB or less.` | The body is over 10 MB. | Fix, then retry |
| `500` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `Internal error: retry later. If it persists, contact support with the request_id.` | An unexpected error. | Retry with backoff |
| `502` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The service could not be reached: retry later.` | The service could not be reached. | Retry with backoff |
| `503` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The credit service is briefly unavailable: retry in a second.` | The credit service is briefly unavailable. Headers: `Retry-After: <seconds>`. | Retry with backoff |
| `504` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The service did not answer in time. The change may still be applied: read it before sending it again.` | No answer in time. | Retry with backoff |

Example error (`400`):

```json
{
  "error": {
    "code": "invalid_request",
    "message": "label is required.",
    "param": "label",
    "doc_url": "https://hirelayer.co/api-docs/errors#invalid_request"
  },
  "request_id": "6f1c2a4e-1d2b-4c3a-9e8f-0a1b2c3d4e5f"
}
```

### Change a criterion

`PATCH https://hirelayer.co/api/v4/jobs/{job_id}/criteria/{criterion_id}`

A new `weight`, `requirement` or `type` re-ranks at once, for free; weight 0 turns the criterion off. A new `label` or `threshold` evaluates the criterion again on the job's stored scores, 1 credit each, and sets their `verdict` to `null` until their next full evaluation.

- **Authentication:** `X-API-Key` header
- **Content type:** `application/json`
- **Billing:** Free for weight, requirement and type; 1 credit per stored score for label or threshold
- **Client timeout:** at least 150 seconds

In a workspace without storage, refused (`400 storage_required`), as `POST /jobs/{job_id}/criteria` is; removing a criterion stays allowed.

#### Path parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `job_id` | `string` | Yes | Letters, digits and `. _ : ~ -`, up to 200 characters. |
| `criterion_id` | `string` | Yes |  |

#### Headers

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `HireLayer-Workspace` | `string` | No | Your customer's workspace id. Case-insensitive, created on first use. Absent, calls use `default`. |

#### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `label` | `string` | No | 1–300 characters. |
| `type` | `string` | No | One of: `"skill"`, `"experience"`, `"education"`, `"language"`, `"certification"`, `"location"`, `"soft_skill"`, `"other"`. |
| `requirement` | `string` | No | `required`: a resume that clearly lacks it loses 25 % of its score, never eliminated outright. `preferred` and `bonus` count only through their weight. One of: `"required"`, `"preferred"`, `"bonus"`. |
| `weight` | `integer` | No | Importance. 3 essential, 2 important, 1 nice to have, 0 off: kept on the job, left out of every score, of the required-criterion penalty, of `required_missing` and of `to_verify`. Texts already written (`summary`, `explanation`) stay as they are. 0–3. |
| `threshold` | `string \| null` | No | ≤ 100 characters. |

#### Example request

**cURL**

```bash
# Example ids: replace job_30cfa454c97663f3e1801e5a61b87d74 with a job_id returned by POST /api/v4/jobs/extract-criteria.
curl -X PATCH https://hirelayer.co/api/v4/jobs/job_30cfa454c97663f3e1801e5a61b87d74/criteria/crit_7 \
  -H "X-API-Key: $HIRELAYER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "weight": 0
  }'
```

**Python**

```python
# Example ids: replace job_30cfa454c97663f3e1801e5a61b87d74 with a job_id returned by POST /api/v4/jobs/extract-criteria.
import os

import requests

response = requests.patch(
    "https://hirelayer.co/api/v4/jobs/job_30cfa454c97663f3e1801e5a61b87d74/criteria/crit_7",
    headers={"X-API-Key": os.environ["HIRELAYER_API_KEY"]},
    json={
        "weight": 0,
    },
    timeout=150,
)
response.raise_for_status()

data = response.json()
print(data["weight"])
```

**TypeScript**

```typescript
// Example ids: replace job_30cfa454c97663f3e1801e5a61b87d74 with a job_id returned by POST /api/v4/jobs/extract-criteria.
const response = await fetch('https://hirelayer.co/api/v4/jobs/job_30cfa454c97663f3e1801e5a61b87d74/criteria/crit_7', {
  method: 'PATCH',
  headers: {
    'X-API-Key': process.env.HIRELAYER_API_KEY!,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    weight: 0,
  }),
  signal: AbortSignal.timeout(150_000),
})

if (!response.ok) {
  throw new Error(`HireLayer ${response.status}: ${await response.text()}`)
}

const data = await response.json()
console.log(data.weight)
```

#### Response `200`

Changed.

| Field | Type | Description |
| --- | --- | --- |
| `criterion_id` | `string` | Stable id in the job, such as `crit_2`. Never reused. |
| `label` | `string` |  |
| `type` | `string` | One of: `"skill"`, `"experience"`, `"education"`, `"language"`, `"certification"`, `"location"`, `"soft_skill"`, `"other"`. |
| `requirement` | `string` | `required`: a resume that clearly lacks it loses 25 % of its score, never eliminated outright. `preferred` and `bonus` count only through their weight. One of: `"required"`, `"preferred"`, `"bonus"`. |
| `weight` | `integer` | Importance. 3 essential, 2 important, 1 nice to have, 0 off: kept on the job, left out of every score, of the required-criterion penalty, of `required_missing` and of `to_verify`. Texts already written (`summary`, `explanation`) stay as they are. 0–3. |
| `threshold` | `string \| null` |  |
| `source` | `string` | `extracted` by HireLayer, or `custom`: sent or edited by you. One of: `"extracted"`, `"custom"`. |

#### Errors

| Status | `error.code` | `error.message` | When | Retry |
| --- | --- | --- | --- | --- |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `Send at least one field to change.` | An empty body. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `weight must be 0, 1, 2 or 3.` | `param`: `weight`. A criterion breaks a rule (here its weight): `param` names the field. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `criterion_id must contain letters, digits and . _ : ~ -, up to 200 characters.` | `param`: `criterion_id`. `criterion_id` in the path is not a valid id. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `Unknown field name.` | `param`: `name`. A field the call does not take. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `The request body is not valid JSON.` | The body is not valid JSON, not an object, or not sent as `application/json`. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `HireLayer-Workspace must contain letters, digits and . _ : ~ -, up to 200 characters.` | `param`: `HireLayer-Workspace`. `HireLayer-Workspace` is not a valid id. | Fix, then retry |
| `400` | [`storage_required`](https://hirelayer.co/api-docs/errors.md#storage_required) | `This workspace runs without storage (do_not_store_data): nothing can be stored in it.` | `param`: `HireLayer-Workspace`. The workspace runs without storage, and the call would store something in it. | Do not retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `The X-API-Key header is missing.` | No `X-API-Key` header. | Fix, then retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `Authorization: Bearer is not accepted: send the key in the X-API-Key header.` | The key sent as `Authorization: Bearer`: it goes in `X-API-Key`. | Fix, then retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `The key goes in the X-API-Key header, not in the URL.` | The key sent in the URL: it goes in `X-API-Key`. | Fix, then retry |
| `401` | [`invalid_api_key`](https://hirelayer.co/api-docs/errors.md#invalid_api_key) | `Invalid API key: it is unknown, malformed or revoked.` | The key is unknown, malformed or revoked. | Fix, then retry |
| `403` | [`insufficient_credits`](https://hirelayer.co/api-docs/errors.md#insufficient_credits) | `This call evaluates 12 scores, 1 credit each: 12 credits, and 3 are left. Nothing was processed or billed.` | The change evaluates the job's stored scores again, and the credits left do not cover them. | Fix, then retry |
| `404` | [`not_found`](https://hirelayer.co/api-docs/errors.md#not_found) | `No job req-7 in workspace default.` | `param`: `job_id`. No job under this id in the workspace. | Do not retry |
| `404` | [`not_found`](https://hirelayer.co/api-docs/errors.md#not_found) | `No criterion crit_9 in job req-7.` | `param`: `criterion_id`. No such criterion in the job. | Do not retry |
| `413` | [`payload_too_large`](https://hirelayer.co/api-docs/errors.md#payload_too_large) | `The request body must be 10 MB or less.` | The body is over 10 MB. | Fix, then retry |
| `500` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `Internal error: retry later. If it persists, contact support with the request_id.` | An unexpected error. | Retry with backoff |
| `502` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The service could not be reached: retry later.` | The service could not be reached. | Retry with backoff |
| `503` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The credit service is briefly unavailable: retry in a second.` | The credit service is briefly unavailable. Headers: `Retry-After: <seconds>`. | Retry with backoff |
| `504` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The service did not answer in time. The change may still be applied: read it before sending it again.` | No answer in time. | Retry with backoff |

Example error (`400`):

```json
{
  "error": {
    "code": "invalid_request",
    "message": "Send at least one field to change.",
    "doc_url": "https://hirelayer.co/api-docs/errors#invalid_request"
  },
  "request_id": "6f1c2a4e-1d2b-4c3a-9e8f-0a1b2c3d4e5f"
}
```

### Remove a criterion

`DELETE https://hirelayer.co/api/v4/jobs/{job_id}/criteria/{criterion_id}`

The criterion leaves the scores at once, with no new evaluation, and the `verdict` of the job's scores is `null` until their next full evaluation. A job keeps at least one criterion. Free.

- **Authentication:** `X-API-Key` header
- **Billing:** Free
- **Client timeout:** at least 30 seconds

#### Path parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `job_id` | `string` | Yes | Letters, digits and `. _ : ~ -`, up to 200 characters. |
| `criterion_id` | `string` | Yes |  |

#### Headers

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `HireLayer-Workspace` | `string` | No | Your customer's workspace id. Case-insensitive, created on first use. Absent, calls use `default`. |

#### Example request

**cURL**

```bash
# Example ids: replace job_30cfa454c97663f3e1801e5a61b87d74 with a job_id returned by POST /api/v4/jobs/extract-criteria.
curl -X DELETE https://hirelayer.co/api/v4/jobs/job_30cfa454c97663f3e1801e5a61b87d74/criteria/crit_7 \
  -H "X-API-Key: $HIRELAYER_API_KEY"
```

**Python**

```python
# Example ids: replace job_30cfa454c97663f3e1801e5a61b87d74 with a job_id returned by POST /api/v4/jobs/extract-criteria.
import os

import requests

response = requests.delete(
    "https://hirelayer.co/api/v4/jobs/job_30cfa454c97663f3e1801e5a61b87d74/criteria/crit_7",
    headers={"X-API-Key": os.environ["HIRELAYER_API_KEY"]},
    timeout=30,
)
response.raise_for_status()
print(response.status_code)
```

**TypeScript**

```typescript
// Example ids: replace job_30cfa454c97663f3e1801e5a61b87d74 with a job_id returned by POST /api/v4/jobs/extract-criteria.
const response = await fetch('https://hirelayer.co/api/v4/jobs/job_30cfa454c97663f3e1801e5a61b87d74/criteria/crit_7', {
  method: 'DELETE',
  headers: {
    'X-API-Key': process.env.HIRELAYER_API_KEY!,
  },
  signal: AbortSignal.timeout(30_000),
})

if (!response.ok) {
  throw new Error(`HireLayer ${response.status}: ${await response.text()}`)
}

console.log(response.status)
```

#### Response `204`

Removed.

#### Errors

| Status | `error.code` | `error.message` | When | Retry |
| --- | --- | --- | --- | --- |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `A job keeps at least one criterion: add the new one before deleting this one.` | The job's last criterion. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `criterion_id must contain letters, digits and . _ : ~ -, up to 200 characters.` | `param`: `criterion_id`. `criterion_id` in the path is not a valid id. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `HireLayer-Workspace must contain letters, digits and . _ : ~ -, up to 200 characters.` | `param`: `HireLayer-Workspace`. `HireLayer-Workspace` is not a valid id. | Fix, then retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `The X-API-Key header is missing.` | No `X-API-Key` header. | Fix, then retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `Authorization: Bearer is not accepted: send the key in the X-API-Key header.` | The key sent as `Authorization: Bearer`: it goes in `X-API-Key`. | Fix, then retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `The key goes in the X-API-Key header, not in the URL.` | The key sent in the URL: it goes in `X-API-Key`. | Fix, then retry |
| `401` | [`invalid_api_key`](https://hirelayer.co/api-docs/errors.md#invalid_api_key) | `Invalid API key: it is unknown, malformed or revoked.` | The key is unknown, malformed or revoked. | Fix, then retry |
| `404` | [`not_found`](https://hirelayer.co/api-docs/errors.md#not_found) | `No job req-7 in workspace default.` | `param`: `job_id`. No job under this id in the workspace. | Do not retry |
| `404` | [`not_found`](https://hirelayer.co/api-docs/errors.md#not_found) | `No criterion crit_9 in job req-7.` | `param`: `criterion_id`. No such criterion in the job. | Do not retry |
| `500` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `Internal error: retry later. If it persists, contact support with the request_id.` | An unexpected error. | Retry with backoff |
| `502` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The service could not be reached: retry later.` | The service could not be reached. | Retry with backoff |
| `503` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The credit service is briefly unavailable: retry in a second.` | The credit service is briefly unavailable. Headers: `Retry-After: <seconds>`. | Retry with backoff |
| `504` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The service did not answer in time. The change may still be applied: read it before sending it again.` | No answer in time. | Retry with backoff |

Example error (`400`):

```json
{
  "error": {
    "code": "invalid_request",
    "message": "A job keeps at least one criterion: add the new one before deleting this one.",
    "doc_url": "https://hirelayer.co/api-docs/errors#invalid_request"
  },
  "request_id": "6f1c2a4e-1d2b-4c3a-9e8f-0a1b2c3d4e5f"
}
```

## Scores

Read a job’s ranking again as often as needed, with other weights for one view (`?weights[crit_3]=0`), without evaluating anything again. Detaching a resume deletes its score and keeps the resume.

### Read a job's ranking

`GET https://hirelayer.co/api/v4/jobs/{job_id}/scores`

The stored scores of a job, best first, scored at read time with the priorities of this read. Free. `precise_ranking=true` costs 1 credit per read: it compares the 10 best of all the job's resumes and adds `ranking_reason`, and is billed when it ranks at least 2 completed scores (`credits_used`). It is made again only when those 10, the job's description or the priorities of the read (weights and requirements) change. In a workspace without storage (set after its jobs were stored), it is made again at every read and never kept.

- **Authentication:** `X-API-Key` header
- **Billing:** Free; 1 with precise_ranking=true
- **Client timeout:** at least 60 seconds

#### Path parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `job_id` | `string` | Yes | Letters, digits and `. _ : ~ -`, up to 200 characters. |

#### Headers

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `HireLayer-Workspace` | `string` | No | Your customer's workspace id. Case-insensitive, created on first use. Absent, calls use `default`. |

#### Query parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `limit` | `integer` | No | Page size. 1–100, default `20`. |
| `cursor` | `string` | No | The `next_cursor` of the previous page. |
| `weights` | `object` | No | Weights for this read only, by criterion id, such as `weights[crit_3]=0`. 0 turns a criterion off. |
| `requirements` | `object` | No | Requirements for this read only, by criterion id, such as `requirements[crit_1]=required`. |
| `min_score` | `number` | No | Only scores at least this high. 0–1. |
| `include` | `string` | No | `criteria` adds the per-criterion detail to each score. Value: `"criteria"`. |
| `precise_ranking` | `boolean` | No | Re-rank the 10 best of all the job's resumes and add `ranking_reason`. 1 credit. Default `false`. |
| `request_id` | `string` | No | Only the resumes of one scoring call, as `results_url` gives it: all of them from the start, each with its id (yours, or derived from its file or text on receipt), `pending` until evaluated; the call is done when none is. Ranks then cover that call only, with the precise ranking the call asked for. Only `include` goes with it: any other parameter of this read (`weights`, `requirements`, `min_score`, `precise_ranking`, `limit`, `cursor`) is refused (`400 invalid_request`, `param` naming the first one). |

#### Example request

**cURL**

```bash
# Example ids: replace job_30cfa454c97663f3e1801e5a61b87d74 with a job_id returned by POST /api/v4/jobs/extract-criteria.
curl -g "https://hirelayer.co/api/v4/jobs/job_30cfa454c97663f3e1801e5a61b87d74/scores?weights[crit_3]=0" \
  -H "X-API-Key: $HIRELAYER_API_KEY"
```

**Python**

```python
# Example ids: replace job_30cfa454c97663f3e1801e5a61b87d74 with a job_id returned by POST /api/v4/jobs/extract-criteria.
import os

import requests

response = requests.get(
    "https://hirelayer.co/api/v4/jobs/job_30cfa454c97663f3e1801e5a61b87d74/scores?weights[crit_3]=0",
    headers={"X-API-Key": os.environ["HIRELAYER_API_KEY"]},
    timeout=60,
)
response.raise_for_status()

data = response.json()
print(data["data"])
```

**TypeScript**

```typescript
// Example ids: replace job_30cfa454c97663f3e1801e5a61b87d74 with a job_id returned by POST /api/v4/jobs/extract-criteria.
const response = await fetch('https://hirelayer.co/api/v4/jobs/job_30cfa454c97663f3e1801e5a61b87d74/scores?weights[crit_3]=0', {
  headers: {
    'X-API-Key': process.env.HIRELAYER_API_KEY!,
  },
  signal: AbortSignal.timeout(60_000),
})

if (!response.ok) {
  throw new Error(`HireLayer ${response.status}: ${await response.text()}`)
}

const data = await response.json()
console.log(data.data)
```

#### Response `200`

A page of the ranking.

| Field | Type | Description |
| --- | --- | --- |
| `job_id` | `string` |  |
| `weights_used` | `object` | The weight of each criterion in this ranking, overrides included. |
| `precise_ranking` | `boolean` |  |
| `credits_used` | `integer` | Credits this call cost. ≥ 0. |
| `data` | `object[]` |  |
| `data[].rank` | `integer` | Place in the whole ranking, across pages: by score, except among the 10 best when `precise_ranking` is on. Pending and failed resumes come last. ≥ 1. |
| `data[].application_id` | `string \| null` | `null` without storage, and for a resume file that could not be read. |
| `data[].reference` | `string \| null` | The `reference` the resume was sent with. Given back in the answer of `POST /resumes/score`, its webhook and its `results_url`; `null` in a job's stored ranking and in one stored score, which outlive the call. |
| `data[].file_name` | `string \| null` | The name of the resume file sent to `POST /resumes/score`, given back as `reference` is. |
| `data[].evaluation_status` | `string` | One of: `"completed"`, `"pending"`, `"failed"`. |
| `data[].stale` | `boolean` | The resume was replaced after this evaluation. The result stays readable, and is evaluated again the next time the resume is sent to `POST /resumes/score` for this job. |
| `data[].score` | `number \| null` | Rounded to 2 decimals; `null` until evaluated. 0–1. |
| `data[].verdict` | `string \| null` | May be absent. The overall judgment of the resume for the job: `highly_relevant` (shortlist), `relevant` (worth a call), `marginally_relevant` (weak), `not_relevant` (off-topic). Present once the evaluation is `completed`, absent while `pending` or `failed`. It does not follow the weights of a read, so it can disagree with `score` after a weight change. `null` when a criterion of the job was added, removed or reworded (`label` or `threshold`) since the last full evaluation of the resume, or when no overall judgment could be given. A change of weight, requirement or type leaves it as is. After a reset, it comes back when the resume is sent again to `POST /resumes/score` for this job: a full evaluation, 1 credit, on top of the credit the new criterion's own evaluation cost. One of: `"highly_relevant"`, `"relevant"`, `"marginally_relevant"`, `"not_relevant"`, `null`. |
| `data[].summary` | `string \| null` | May be absent. Overall assessment, in the job's language. Present once the evaluation is `completed`. It follows `verdict`, written with it: `null` when `verdict` is `null` (a criterion of the job added, removed or reworded since the last full evaluation of the resume), back with the next full evaluation. |
| `data[].ranking_reason` | `string` | May be absent. One sentence that places the resume against the others. Only with `precise_ranking`, for ranks 1 to 10, when at least 2 resumes are compared; absent otherwise. |
| `data[].required_missing` | `object[]` | Required criteria marked `missing` with medium or high confidence; each multiplies the score by 0.75. |
| `data[].required_missing[].criterion_id` | `string` |  |
| `data[].required_missing[].label` | `string` |  |
| `data[].to_verify` | `object[]` | Required criteria not clearly met, to check with the candidate; those of `required_missing` included. |
| `data[].to_verify[].criterion_id` | `string` |  |
| `data[].to_verify[].label` | `string` |  |
| `data[].criteria` | `object[]` | May be absent. Per-criterion detail, in the job's criteria order, once the evaluation is `completed`. Always in `POST /resumes/score` answers; in `GET /jobs/{job_id}/scores`, only with `include=criteria`. |
| `data[].criteria[].criterion_id` | `string` |  |
| `data[].criteria[].label` | `string` |  |
| `data[].criteria[].requirement` | `string` | `required`: a resume that clearly lacks it loses 25 % of its score, never eliminated outright. `preferred` and `bonus` count only through their weight. One of: `"required"`, `"preferred"`, `"bonus"`. |
| `data[].criteria[].weight` | `integer` | Importance. 3 essential, 2 important, 1 nice to have, 0 off: kept on the job, left out of every score, of the required-criterion penalty, of `required_missing` and of `to_verify`. Texts already written (`summary`, `explanation`) stay as they are. 0–3. |
| `data[].criteria[].status` | `string` | The reading of the criterion. `met`; `partial`; `missing`, which can still carry a weak hint (score 0.33); `unknown`: the resume says nothing about something resumes often leave out, such as availability, and the criterion is left out of the score. One of: `"met"`, `"partial"`, `"missing"`, `"unknown"`. |
| `data[].criteria[].score` | `number \| null` | 1 met or exceeded, 0.67 partly met, 0.33 a weak hint, 0 absent, `null` unknown. The resume's score uses the exact values 2/3 and 1/3, then rounds to 2 decimals. One of: `0`, `0.33`, `0.67`, `1`, `null`. |
| `data[].criteria[].gap` | `string \| null` | Why the evidence is partial (scores 0.67 and 0.33): the same thing, less of it; a neighbouring skill of the same trade; plausible but not shown. One of: `"below_threshold"`, `"related"`, `"indirect"`, `null`. |
| `data[].criteria[].confidence` | `string` | A required criterion missing with `low` confidence does not lower the score. One of: `"high"`, `"medium"`, `"low"`. |
| `data[].criteria[].evidence` | `string` | Where the resume shows it: the section, then the passages quoted between quotation marks, in the resume's language. Empty when there is none. Free text, so search the quoted passages rather than the whole string. |
| `data[].criteria[].explanation` | `string` | One sentence, in the job's language. |
| `data[].error` | `object \| null` | Why this resume failed, when `evaluation_status` is `failed`. It was not billed. |
| `data[].error.code` | `string` | Stable codes: branch on them, never on `message`. `method_not_allowed` answers a method a path does not take (`405`, with an `Allow` header), outside any operation. One of: `"invalid_request"`, `"storage_required"`, `"missing_api_key"`, `"invalid_api_key"`, `"insufficient_credits"`, `"enterprise_plan_required"`, `"not_found"`, `"payload_too_large"`, `"resume_unreadable"`, `"job_unreadable"`, `"rate_limited"`, `"model_unavailable"`, `"internal_error"`, `"method_not_allowed"`. |
| `data[].error.message` | `string` | Readable explanation, for logs. |
| `data[].error.param` | `string` | May be absent. The field or parameter at fault, when there is one, such as `job.criteria` or `resumes[2].file`. |
| `data[].error.doc_url` | `string (URL)` |  |
| `data[].evaluated_at` | `string (date-time) \| null` |  |
| `next_cursor` | `string \| null` |  |

```json
{
  "job_id": "job_30cfa454c97663f3e1801e5a61b87d74",
  "weights_used": {
    "crit_1": 3,
    "crit_2": 3,
    "crit_3": 2,
    "crit_4": 2,
    "crit_5": 2,
    "crit_6": 1,
    "crit_7": 1,
    "crit_8": 1
  },
  "precise_ranking": false,
  "credits_used": 0,
  "data": [
    {
      "rank": 1,
      "application_id": "file_2c982f413a5e9e79563f8a1fcb5c7caa",
      "reference": null,
      "file_name": null,
      "evaluation_status": "completed",
      "stale": false,
      "score": 0.9,
      "verdict": "highly_relevant",
      "summary": "Candidat très pertinent avec une expertise forte en LLM et AWS, un excellent niveau d'anglais, à présélectionner malgré une expérience en Node.js légèrement sous le seuil.",
      "required_missing": [],
      "to_verify": [
        {
          "criterion_id": "crit_8",
          "label": "Astreinte"
        }
      ],
      "error": null,
      "evaluated_at": "2026-10-26T22:39:25Z"
    },
    {
      "rank": 2,
      "application_id": "file_addd6bc79e818deebe289d009058d6b1",
      "reference": null,
      "file_name": null,
      "evaluation_status": "completed",
      "stale": false,
      "score": 0.25,
      "verdict": "marginally_relevant",
      "summary": "Candidat exclusivement frontend (React/TypeScript) sans expérience backend Node.js ni intégration de LLM, deux critères essentiels non satisfaits.",
      "required_missing": [
        {
          "criterion_id": "crit_2",
          "label": "Intégration de LLM en production"
        }
      ],
      "to_verify": [
        {
          "criterion_id": "crit_2",
          "label": "Intégration de LLM en production"
        },
        {
          "criterion_id": "crit_8",
          "label": "Astreinte"
        }
      ],
      "error": null,
      "evaluated_at": "2026-10-26T22:40:51Z"
    }
  ],
  "next_cursor": null
}
```

#### Errors

| Status | `error.code` | `error.message` | When | Retry |
| --- | --- | --- | --- | --- |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `weights[crit_3] must be 0, 1, 2 or 3.` | `param`: `weights[crit_3]`. A query parameter out of its values (here a weight): `param` names it. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `limit does not apply with request_id: a Scorer call is read whole, with include only.` | `param`: `limit`. A parameter other than `include` with `request_id`. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `limit must be an integer from 1 to 100.` | `param`: `limit`. `limit` out of range. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `cursor is not a next_cursor returned by this API.` | `param`: `cursor`. `cursor` is not a `next_cursor` of this list. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `job_id must contain letters, digits and . _ : ~ -, up to 200 characters.` | `param`: `job_id`. `job_id` in the path is not a valid id. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `HireLayer-Workspace must contain letters, digits and . _ : ~ -, up to 200 characters.` | `param`: `HireLayer-Workspace`. `HireLayer-Workspace` is not a valid id. | Fix, then retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `The X-API-Key header is missing.` | No `X-API-Key` header. | Fix, then retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `Authorization: Bearer is not accepted: send the key in the X-API-Key header.` | The key sent as `Authorization: Bearer`: it goes in `X-API-Key`. | Fix, then retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `The key goes in the X-API-Key header, not in the URL.` | The key sent in the URL: it goes in `X-API-Key`. | Fix, then retry |
| `401` | [`invalid_api_key`](https://hirelayer.co/api-docs/errors.md#invalid_api_key) | `Invalid API key: it is unknown, malformed or revoked.` | The key is unknown, malformed or revoked. | Fix, then retry |
| `403` | [`insufficient_credits`](https://hirelayer.co/api-docs/errors.md#insufficient_credits) | `This call needs 1 credit and none is left. Nothing was processed or billed.` | No credit left for the precise ranking. | Fix, then retry |
| `404` | [`not_found`](https://hirelayer.co/api-docs/errors.md#not_found) | `No job req-7 in workspace default.` | `param`: `job_id`. No job under this id in the workspace. | Do not retry |
| `404` | [`not_found`](https://hirelayer.co/api-docs/errors.md#not_found) | `No Scorer call 5d0a3c1e-2f4b-4c8e-9a61-0b7e2d9f4c13 for job req-7.` | `param`: `request_id`. No Scorer call under `request_id` for this job (kept 30 days). | Do not retry |
| `500` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `Internal error: retry later. If it persists, contact support with the request_id.` | An unexpected error. | Retry with backoff |
| `502` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The service could not be reached: retry later.` | The service could not be reached. | Retry with backoff |
| `503` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The credit service is briefly unavailable: retry in a second.` | The credit service is briefly unavailable. Headers: `Retry-After: <seconds>`. | Retry with backoff |
| `504` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The service did not answer in time. The change may still be applied: read it before sending it again.` | No answer in time. | Retry with backoff |

Example error (`400`):

```json
{
  "error": {
    "code": "invalid_request",
    "message": "weights[crit_3] must be 0, 1, 2 or 3.",
    "param": "weights[crit_3]",
    "doc_url": "https://hirelayer.co/api-docs/errors#invalid_request"
  },
  "request_id": "6f1c2a4e-1d2b-4c3a-9e8f-0a1b2c3d4e5f"
}
```

### Read one stored score

`GET https://hirelayer.co/api/v4/jobs/{job_id}/scores/{application_id}`

One resume's score for a job, criterion by criterion, with the priorities of this read. Free.

- **Authentication:** `X-API-Key` header
- **Billing:** Free
- **Client timeout:** at least 30 seconds

#### Path parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `job_id` | `string` | Yes | Letters, digits and `. _ : ~ -`, up to 200 characters. |
| `application_id` | `string` | Yes | The resume's id, including one returned by the legacy `/api/v3/parser` in `info_resume.application_id`. |

#### Headers

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `HireLayer-Workspace` | `string` | No | Your customer's workspace id. Case-insensitive, created on first use. Absent, calls use `default`. |

#### Query parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `weights` | `object` | No | Weights for this read only, by criterion id, such as `weights[crit_3]=0`. 0 turns a criterion off. |
| `requirements` | `object` | No | Requirements for this read only, by criterion id, such as `requirements[crit_1]=required`. |

#### Example request

**cURL**

```bash
# Example ids: replace job_30cfa454c97663f3e1801e5a61b87d74 with a job_id returned by POST /api/v4/jobs/extract-criteria, and file_2c982f413a5e9e79563f8a1fcb5c7caa with an application_id returned by POST /api/v4/resumes/parse.
curl https://hirelayer.co/api/v4/jobs/job_30cfa454c97663f3e1801e5a61b87d74/scores/file_2c982f413a5e9e79563f8a1fcb5c7caa \
  -H "X-API-Key: $HIRELAYER_API_KEY"
```

**Python**

```python
# Example ids: replace job_30cfa454c97663f3e1801e5a61b87d74 with a job_id returned by POST /api/v4/jobs/extract-criteria, and file_2c982f413a5e9e79563f8a1fcb5c7caa with an application_id returned by POST /api/v4/resumes/parse.
import os

import requests

response = requests.get(
    "https://hirelayer.co/api/v4/jobs/job_30cfa454c97663f3e1801e5a61b87d74/scores/file_2c982f413a5e9e79563f8a1fcb5c7caa",
    headers={"X-API-Key": os.environ["HIRELAYER_API_KEY"]},
    timeout=30,
)
response.raise_for_status()

data = response.json()
print(data["score"])
```

**TypeScript**

```typescript
// Example ids: replace job_30cfa454c97663f3e1801e5a61b87d74 with a job_id returned by POST /api/v4/jobs/extract-criteria, and file_2c982f413a5e9e79563f8a1fcb5c7caa with an application_id returned by POST /api/v4/resumes/parse.
const response = await fetch('https://hirelayer.co/api/v4/jobs/job_30cfa454c97663f3e1801e5a61b87d74/scores/file_2c982f413a5e9e79563f8a1fcb5c7caa', {
  headers: {
    'X-API-Key': process.env.HIRELAYER_API_KEY!,
  },
  signal: AbortSignal.timeout(30_000),
})

if (!response.ok) {
  throw new Error(`HireLayer ${response.status}: ${await response.text()}`)
}

const data = await response.json()
console.log(data.score)
```

#### Response `200`

The score.

| Field | Type | Description |
| --- | --- | --- |
| `rank` | `integer` | Place in the whole ranking, across pages: by score, except among the 10 best when `precise_ranking` is on. Pending and failed resumes come last. ≥ 1. |
| `application_id` | `string \| null` | `null` without storage, and for a resume file that could not be read. |
| `reference` | `string \| null` | The `reference` the resume was sent with. Given back in the answer of `POST /resumes/score`, its webhook and its `results_url`; `null` in a job's stored ranking and in one stored score, which outlive the call. |
| `file_name` | `string \| null` | The name of the resume file sent to `POST /resumes/score`, given back as `reference` is. |
| `evaluation_status` | `string` | One of: `"completed"`, `"pending"`, `"failed"`. |
| `stale` | `boolean` | The resume was replaced after this evaluation. The result stays readable, and is evaluated again the next time the resume is sent to `POST /resumes/score` for this job. |
| `score` | `number \| null` | Rounded to 2 decimals; `null` until evaluated. 0–1. |
| `verdict` | `string \| null` | May be absent. The overall judgment of the resume for the job: `highly_relevant` (shortlist), `relevant` (worth a call), `marginally_relevant` (weak), `not_relevant` (off-topic). Present once the evaluation is `completed`, absent while `pending` or `failed`. It does not follow the weights of a read, so it can disagree with `score` after a weight change. `null` when a criterion of the job was added, removed or reworded (`label` or `threshold`) since the last full evaluation of the resume, or when no overall judgment could be given. A change of weight, requirement or type leaves it as is. After a reset, it comes back when the resume is sent again to `POST /resumes/score` for this job: a full evaluation, 1 credit, on top of the credit the new criterion's own evaluation cost. One of: `"highly_relevant"`, `"relevant"`, `"marginally_relevant"`, `"not_relevant"`, `null`. |
| `summary` | `string \| null` | May be absent. Overall assessment, in the job's language. Present once the evaluation is `completed`. It follows `verdict`, written with it: `null` when `verdict` is `null` (a criterion of the job added, removed or reworded since the last full evaluation of the resume), back with the next full evaluation. |
| `ranking_reason` | `string` | May be absent. One sentence that places the resume against the others. Only with `precise_ranking`, for ranks 1 to 10, when at least 2 resumes are compared; absent otherwise. |
| `required_missing` | `object[]` | Required criteria marked `missing` with medium or high confidence; each multiplies the score by 0.75. |
| `required_missing[].criterion_id` | `string` |  |
| `required_missing[].label` | `string` |  |
| `to_verify` | `object[]` | Required criteria not clearly met, to check with the candidate; those of `required_missing` included. |
| `to_verify[].criterion_id` | `string` |  |
| `to_verify[].label` | `string` |  |
| `criteria` | `object[]` | May be absent. Per-criterion detail, in the job's criteria order, once the evaluation is `completed`. Always in `POST /resumes/score` answers; in `GET /jobs/{job_id}/scores`, only with `include=criteria`. |
| `criteria[].criterion_id` | `string` |  |
| `criteria[].label` | `string` |  |
| `criteria[].requirement` | `string` | `required`: a resume that clearly lacks it loses 25 % of its score, never eliminated outright. `preferred` and `bonus` count only through their weight. One of: `"required"`, `"preferred"`, `"bonus"`. |
| `criteria[].weight` | `integer` | Importance. 3 essential, 2 important, 1 nice to have, 0 off: kept on the job, left out of every score, of the required-criterion penalty, of `required_missing` and of `to_verify`. Texts already written (`summary`, `explanation`) stay as they are. 0–3. |
| `criteria[].status` | `string` | The reading of the criterion. `met`; `partial`; `missing`, which can still carry a weak hint (score 0.33); `unknown`: the resume says nothing about something resumes often leave out, such as availability, and the criterion is left out of the score. One of: `"met"`, `"partial"`, `"missing"`, `"unknown"`. |
| `criteria[].score` | `number \| null` | 1 met or exceeded, 0.67 partly met, 0.33 a weak hint, 0 absent, `null` unknown. The resume's score uses the exact values 2/3 and 1/3, then rounds to 2 decimals. One of: `0`, `0.33`, `0.67`, `1`, `null`. |
| `criteria[].gap` | `string \| null` | Why the evidence is partial (scores 0.67 and 0.33): the same thing, less of it; a neighbouring skill of the same trade; plausible but not shown. One of: `"below_threshold"`, `"related"`, `"indirect"`, `null`. |
| `criteria[].confidence` | `string` | A required criterion missing with `low` confidence does not lower the score. One of: `"high"`, `"medium"`, `"low"`. |
| `criteria[].evidence` | `string` | Where the resume shows it: the section, then the passages quoted between quotation marks, in the resume's language. Empty when there is none. Free text, so search the quoted passages rather than the whole string. |
| `criteria[].explanation` | `string` | One sentence, in the job's language. |
| `error` | `object \| null` | Why this resume failed, when `evaluation_status` is `failed`. It was not billed. |
| `error.code` | `string` | Stable codes: branch on them, never on `message`. `method_not_allowed` answers a method a path does not take (`405`, with an `Allow` header), outside any operation. One of: `"invalid_request"`, `"storage_required"`, `"missing_api_key"`, `"invalid_api_key"`, `"insufficient_credits"`, `"enterprise_plan_required"`, `"not_found"`, `"payload_too_large"`, `"resume_unreadable"`, `"job_unreadable"`, `"rate_limited"`, `"model_unavailable"`, `"internal_error"`, `"method_not_allowed"`. |
| `error.message` | `string` | Readable explanation, for logs. |
| `error.param` | `string` | May be absent. The field or parameter at fault, when there is one, such as `job.criteria` or `resumes[2].file`. |
| `error.doc_url` | `string (URL)` |  |
| `evaluated_at` | `string (date-time) \| null` |  |

#### Errors

| Status | `error.code` | `error.message` | When | Retry |
| --- | --- | --- | --- | --- |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `weights[crit_3] must be 0, 1, 2 or 3.` | `param`: `weights[crit_3]`. A query parameter out of its values (here a weight): `param` names it. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `application_id must contain letters, digits and . _ : ~ -, up to 200 characters.` | `param`: `application_id`. `application_id` in the path is not a valid id. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `HireLayer-Workspace must contain letters, digits and . _ : ~ -, up to 200 characters.` | `param`: `HireLayer-Workspace`. `HireLayer-Workspace` is not a valid id. | Fix, then retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `The X-API-Key header is missing.` | No `X-API-Key` header. | Fix, then retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `Authorization: Bearer is not accepted: send the key in the X-API-Key header.` | The key sent as `Authorization: Bearer`: it goes in `X-API-Key`. | Fix, then retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `The key goes in the X-API-Key header, not in the URL.` | The key sent in the URL: it goes in `X-API-Key`. | Fix, then retry |
| `401` | [`invalid_api_key`](https://hirelayer.co/api-docs/errors.md#invalid_api_key) | `Invalid API key: it is unknown, malformed or revoked.` | The key is unknown, malformed or revoked. | Fix, then retry |
| `404` | [`not_found`](https://hirelayer.co/api-docs/errors.md#not_found) | `No job req-7 in workspace default.` | `param`: `job_id`. No job under this id in the workspace. | Do not retry |
| `404` | [`not_found`](https://hirelayer.co/api-docs/errors.md#not_found) | `No score of resume cand-42 for job req-7 in workspace default.` | `param`: `application_id`. The resume has no score for this job. | Do not retry |
| `500` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `Internal error: retry later. If it persists, contact support with the request_id.` | An unexpected error. | Retry with backoff |
| `502` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The service could not be reached: retry later.` | The service could not be reached. | Retry with backoff |
| `503` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The credit service is briefly unavailable: retry in a second.` | The credit service is briefly unavailable. Headers: `Retry-After: <seconds>`. | Retry with backoff |
| `504` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The service did not answer in time. The change may still be applied: read it before sending it again.` | No answer in time. | Retry with backoff |

Example error (`400`):

```json
{
  "error": {
    "code": "invalid_request",
    "message": "weights[crit_3] must be 0, 1, 2 or 3.",
    "param": "weights[crit_3]",
    "doc_url": "https://hirelayer.co/api-docs/errors#invalid_request"
  },
  "request_id": "6f1c2a4e-1d2b-4c3a-9e8f-0a1b2c3d4e5f"
}
```

### Detach a resume from a job

`DELETE https://hirelayer.co/api/v4/jobs/{job_id}/scores/{application_id}`

Deletes the score. The resume stays in the workspace. Free.

- **Authentication:** `X-API-Key` header
- **Billing:** Free
- **Client timeout:** at least 30 seconds

#### Path parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `job_id` | `string` | Yes | Letters, digits and `. _ : ~ -`, up to 200 characters. |
| `application_id` | `string` | Yes | The resume's id, including one returned by the legacy `/api/v3/parser` in `info_resume.application_id`. |

#### Headers

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `HireLayer-Workspace` | `string` | No | Your customer's workspace id. Case-insensitive, created on first use. Absent, calls use `default`. |

#### Example request

**cURL**

```bash
# Example ids: replace job_30cfa454c97663f3e1801e5a61b87d74 with a job_id returned by POST /api/v4/jobs/extract-criteria, and file_2c982f413a5e9e79563f8a1fcb5c7caa with an application_id returned by POST /api/v4/resumes/parse.
curl -X DELETE https://hirelayer.co/api/v4/jobs/job_30cfa454c97663f3e1801e5a61b87d74/scores/file_2c982f413a5e9e79563f8a1fcb5c7caa \
  -H "X-API-Key: $HIRELAYER_API_KEY"
```

**Python**

```python
# Example ids: replace job_30cfa454c97663f3e1801e5a61b87d74 with a job_id returned by POST /api/v4/jobs/extract-criteria, and file_2c982f413a5e9e79563f8a1fcb5c7caa with an application_id returned by POST /api/v4/resumes/parse.
import os

import requests

response = requests.delete(
    "https://hirelayer.co/api/v4/jobs/job_30cfa454c97663f3e1801e5a61b87d74/scores/file_2c982f413a5e9e79563f8a1fcb5c7caa",
    headers={"X-API-Key": os.environ["HIRELAYER_API_KEY"]},
    timeout=30,
)
response.raise_for_status()
print(response.status_code)
```

**TypeScript**

```typescript
// Example ids: replace job_30cfa454c97663f3e1801e5a61b87d74 with a job_id returned by POST /api/v4/jobs/extract-criteria, and file_2c982f413a5e9e79563f8a1fcb5c7caa with an application_id returned by POST /api/v4/resumes/parse.
const response = await fetch('https://hirelayer.co/api/v4/jobs/job_30cfa454c97663f3e1801e5a61b87d74/scores/file_2c982f413a5e9e79563f8a1fcb5c7caa', {
  method: 'DELETE',
  headers: {
    'X-API-Key': process.env.HIRELAYER_API_KEY!,
  },
  signal: AbortSignal.timeout(30_000),
})

if (!response.ok) {
  throw new Error(`HireLayer ${response.status}: ${await response.text()}`)
}

console.log(response.status)
```

#### Response `204`

Deleted.

#### Errors

| Status | `error.code` | `error.message` | When | Retry |
| --- | --- | --- | --- | --- |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `application_id must contain letters, digits and . _ : ~ -, up to 200 characters.` | `param`: `application_id`. `application_id` in the path is not a valid id. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `HireLayer-Workspace must contain letters, digits and . _ : ~ -, up to 200 characters.` | `param`: `HireLayer-Workspace`. `HireLayer-Workspace` is not a valid id. | Fix, then retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `The X-API-Key header is missing.` | No `X-API-Key` header. | Fix, then retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `Authorization: Bearer is not accepted: send the key in the X-API-Key header.` | The key sent as `Authorization: Bearer`: it goes in `X-API-Key`. | Fix, then retry |
| `401` | [`missing_api_key`](https://hirelayer.co/api-docs/errors.md#missing_api_key) | `The key goes in the X-API-Key header, not in the URL.` | The key sent in the URL: it goes in `X-API-Key`. | Fix, then retry |
| `401` | [`invalid_api_key`](https://hirelayer.co/api-docs/errors.md#invalid_api_key) | `Invalid API key: it is unknown, malformed or revoked.` | The key is unknown, malformed or revoked. | Fix, then retry |
| `500` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `Internal error: retry later. If it persists, contact support with the request_id.` | An unexpected error. | Retry with backoff |
| `502` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The service could not be reached: retry later.` | The service could not be reached. | Retry with backoff |
| `503` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The credit service is briefly unavailable: retry in a second.` | The credit service is briefly unavailable. Headers: `Retry-After: <seconds>`. | Retry with backoff |
| `504` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The service did not answer in time. The change may still be applied: read it before sending it again.` | No answer in time. | Retry with backoff |

Example error (`400`):

```json
{
  "error": {
    "code": "invalid_request",
    "message": "application_id must contain letters, digits and . _ : ~ -, up to 200 characters.",
    "param": "application_id",
    "doc_url": "https://hirelayer.co/api-docs/errors#invalid_request"
  },
  "request_id": "6f1c2a4e-1d2b-4c3a-9e8f-0a1b2c3d4e5f"
}
```

## Next

- [HireLayer Resume Scorer](https://hirelayer.co/api-docs/score.md): HireLayer candidate matching and ranking API: score one or many resumes against a job, criterion by criterion, with evidence, a verdict and an optional precise ranking.
- [HireLayer Job Criteria Extractor](https://hirelayer.co/api-docs/extract-criteria.md): HireLayer job description parsing API: send a job ad as text or a file and get its typed, weighted criteria back as JSON, for free.
