# HireLayer Talent Pool Search

> Find the resumes closest to a job in your resume database, filtered by location and date added, in one call.

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

HireLayer Talent Pool Search finds, in the resume database of a workspace, the resumes closest to a job: every resume parsed or scored there is searchable. One `POST /api/v4/resumes/search` call with a stored `job_id` or a job text returns up to 500 `application_id`s sorted by closeness, with a `similarity` and whether each one is already scored for the job, for 1 credit per search. A `job` whose text replaces a stored job (same `job_id`) has its stored scores evaluated again, 1 credit each, and the search is refused (`403`) when the credits left do not cover them. A job text identical to the stored job returns the job as stored: a `title` sent with it is ignored; change it with `PUT /jobs/{job_id}`. Filter by location (a city and a radius) and by date added.

The search does not evaluate or explain: send the resumes you choose to the [Resume Scorer](https://hirelayer.co/api-docs/score.md) by their `application_id`. Those already scored for the job, or being scored, cost nothing, unless their score is `stale` or its `verdict` was reset.

- **Endpoint:** `POST /api/v4/resumes/search`
- **Input:** A `job_id` or a job text, a `limit`, optional filters
- **Output:** Resume ids, closest first
- **Price:** 1 credit per search; 1 credit per stored score evaluated again when a new text replaces a stored job

## Search the talent pool for a job

`POST https://hirelayer.co/api/v4/resumes/search`

Finds in the workspace's resume database the resumes closest to a job, to choose which ones to score. Proximity only: no evaluation, no explanation. Score the chosen ones with `POST /resumes/score`.

- **Authentication:** `X-API-Key` header
- **Content type:** `application/json`
- **Billing:** 1 credit per search; 1 credit per stored score evaluated again when a new text replaces a stored job
- **Client timeout:** at least 60 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`. |

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `job_id` | `string` | No | Letters, digits and `. _ : ~ -`, up to 200 characters. |
| `job` | `object` | No | A job not stored yet; it is stored, as by `POST /jobs/extract-criteria`. |
| `job.text` | `string` | Yes | 30–50,000 characters. |
| `job.title` | `string` | No | ≤ 300 characters. |
| `job.job_id` | `string` | No | Store the job under your own id. |
| `limit` | `integer` | No | 1–500, default `50`. |
| `filters` | `object` | No |  |
| `filters.location` | `object` | No | Resumes whose location is unknown are left out when this filter is set. |
| `filters.location.city` | `string` | Yes |  |
| `filters.location.country_code` | `string` | No | ISO 3166-1 alpha-2, such as `FR`. Helps when a city name exists in several countries. |
| `filters.location.radius_km` | `integer` | No | 1–500, default `30`. |
| `filters.added_after` | `string (date-time)` | No |  |
| `do_not_store_data` | `boolean` | No | Enterprise plan only, else `403 enterprise_plan_required`: process without storing anything. See [Storage](https://hirelayer.co/api-docs/authentication.md#storage). Default `false`. |

Send exactly one of `job_id` or `job`.

### 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/resumes/search \
  -H "X-API-Key: $HIRELAYER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "job_id": "job_30cfa454c97663f3e1801e5a61b87d74",
    "limit": 20,
    "filters": {
      "location": {
        "city": "Paris",
        "radius_km": 30
      }
    }
  }'
```

**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/resumes/search",
    headers={"X-API-Key": os.environ["HIRELAYER_API_KEY"]},
    json={
        "job_id": "job_30cfa454c97663f3e1801e5a61b87d74",
        "limit": 20,
        "filters": {
            "location": {
                "city": "Paris",
                "radius_km": 30,
            },
        },
    },
    timeout=60,
)
response.raise_for_status()

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

**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/resumes/search', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.HIRELAYER_API_KEY!,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    job_id: 'job_30cfa454c97663f3e1801e5a61b87d74',
    limit: 20,
    filters: {
      location: {
        city: 'Paris',
        radius_km: 30,
      },
    },
  }),
  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.results)
```

### Response `200`

The closest resumes, closest first.

| Field | Type | Description |
| --- | --- | --- |
| `request_id` | `string` | The call's id, also in the `HireLayer-Request-Id` header. Quote it when contacting support. |
| `job_id` | `string` |  |
| `results` | `object[]` |  |
| `results[].rank` | `integer` | ≥ 1. |
| `results[].application_id` | `string` |  |
| `results[].similarity` | `number` | Proximity to the job. Compare it only within one search; it is not a score. 0–1. |
| `results[].already_scored` | `boolean` | Already scored for this job, or being scored (`pending`); scoring it again costs 0 unless the score is `stale` or its `verdict` was reset. |
| `credits_used` | `integer` | Credits this call cost. ≥ 0. |

```json
{
  "request_id": "2b8e6d14-7a3c-4f91-8e25-6c0d9b3f1a47",
  "job_id": "job_30cfa454c97663f3e1801e5a61b87d74",
  "results": [
    {
      "rank": 1,
      "application_id": "file_2c982f413a5e9e79563f8a1fcb5c7caa",
      "similarity": 0.81,
      "already_scored": true
    },
    {
      "rank": 2,
      "application_id": "cand-1042",
      "similarity": 0.77,
      "already_scored": false
    }
  ],
  "credits_used": 1
}
```

### Errors

| Status | `error.code` | `error.message` | When | Retry |
| --- | --- | --- | --- | --- |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `job.text must be 30 characters or more: send the job's full text.` | `param`: `job.text`. `job.text` is under 30 characters. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `job_id or job is required.` | `param`: `job_id`. Neither `job_id` nor `job`, or both. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `job.text is required.` | `param`: `job.text`. `job` without its text. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `limit must be an integer from 1 to 500.` | `param`: `limit`. `limit` out of range. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `No city named Lyonn: check the spelling, or add country_code.` | `param`: `filters.location.city`. A filter breaks a rule (here a city not found): `param` names it. | 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` is not a valid id. | Fix, then retry |
| `400` | [`invalid_request`](https://hirelayer.co/api-docs/errors.md#invalid_request) | `Unknown field lang.` | `param`: `lang`. 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) | `A search reads the stored resumes and jobs: it is not available without storage.` | `param`: `do_not_store_data`. `do_not_store_data: true` (Enterprise plan): a search needs storage. | Do not retry |
| `400` | [`storage_required`](https://hirelayer.co/api-docs/errors.md#storage_required) | `This workspace runs without storage (do_not_store_data): a search reads the stored resumes and jobs, so it is not available in it.` | `param`: `HireLayer-Workspace`. The workspace runs without storage. | 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 search needs 1 credit and none is left. Nothing was processed or billed.` | No credit left for the search (and for the stored scores a new job text evaluates again). | Fix, then retry |
| `403` | [`enterprise_plan_required`](https://hirelayer.co/api-docs/errors.md#enterprise_plan_required) | `do_not_store_data is available on the Enterprise plan only. Nothing was processed or billed.` | `param`: `do_not_store_data`. `do_not_store_data: true` below the Enterprise plan. | Do not 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 `job_id` in the workspace. | Do not retry |
| `413` | [`payload_too_large`](https://hirelayer.co/api-docs/errors.md#payload_too_large) | `job.text must be 50,000 characters or less.` | `param`: `job.text`. The job text is over 50,000 characters. | 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 |
| `503` | [`internal_error`](https://hirelayer.co/api-docs/errors.md#internal_error) | `The location filter is briefly unavailable: search without it, or retry later.` | `param`: `filters.location`. The location filter 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.text must be 30 characters or more: send the job's full text.",
    "param": "job.text",
    "doc_url": "https://hirelayer.co/api-docs/errors#invalid_request"
  },
  "request_id": "6f1c2a4e-1d2b-4c3a-9e8f-0a1b2c3d4e5f"
}
```

### Behavior

- **Credits:** 1 per search. A `job` whose text replaces a stored job (same `job_id`) has its criteria extracted again, as `POST /jobs/extract-criteria` does: its stored scores are evaluated again in the background, 1 credit each, and the call is refused (`403`) when the credits left do not cover them and the search. That check asks for the worst case, every stored score of the job evaluated again; only the evaluations that actually run are billed.
- **A stored job sent again:** a `job` whose text is the stored job's is used as it is, and its `title` is then ignored: change it with `PUT /jobs/{job_id}`.
- **Without storage:** not available (`400 storage_required`), since there is no resume database.

## 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 Recommendations](https://hirelayer.co/api-docs/search-jobs.md): Find the open jobs closest to a resume, sent as a stored id, a text or a file, in one call.
