> ## Documentation Index
> Fetch the complete documentation index at: https://docs.freshtalent.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Save query

> Persist a compacted echoed people-search object for later reuse.

Creates a saved query for the authenticated organization. The recommended source is the `query` object returned by [GET /people](/api-reference/people/search), because it uses the Gateway's public snake\_case filter names. The Gateway compacts the object before storing it: only the allowlisted keys below survive, and `null` or empty-string values are omitted.

This route creates a new record every time. It does not update an existing query, deduplicate by name, or accept an idempotency key.

<ParamField body="name" type="string" required>
  Human-readable name, 1–80 characters. The Gateway trims it before storing it. `null` is not accepted.
</ParamField>

<ParamField body="query" type="object" required>
  JSON object. The top-level value must be an object and may be empty. Unknown nested keys are silently dropped by compaction; the endpoint does not validate nested search bounds or require a particular filter.
</ParamField>

The top-level body accepts only `name` and `query`. Nested `query` values are compacted according to this allowlist:

| Key                           | Type        | Search-route bounds/defaults and nullability                                                                                                                                                     |
| ----------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `nl`                          | string      | Natural-language search text; `GET /people` accepts 1–400 chars. Optional; omitted when null/empty.                                                                                              |
| `company`                     | string      | Optional non-empty company filter on `GET /people`; omitted when null/empty.                                                                                                                     |
| `min_company_months`          | integer     | Search minimum 1; optional.                                                                                                                                                                      |
| `company_active_during`       | string      | Search accepts 4–7 chars; optional.                                                                                                                                                              |
| `company_current`             | boolean     | Optional; `false` is preserved.                                                                                                                                                                  |
| `company_left_min_months_ago` | integer     | Search range 1–600; optional.                                                                                                                                                                    |
| `role_family`                 | string      | Optional non-empty role-family filter.                                                                                                                                                           |
| `min_role_months`             | integer     | Search minimum 1 and requires `role_family` or `roles` when searching; optional when saving.                                                                                                     |
| `roles`                       | string      | Comma-separated role-token string, not JSON; 1–1,500 chars on `GET /people`; optional.                                                                                                           |
| `q_in`                        | string enum | `people`, `name`, or `headline`; optional.                                                                                                                                                       |
| `keywords`                    | string      | Optional non-empty free-text filter.                                                                                                                                                             |
| `school`                      | string      | Optional non-empty school filter.                                                                                                                                                                |
| `skill`                       | string      | Optional single-skill filter.                                                                                                                                                                    |
| `skills`                      | string      | Search accepts 1–800 chars; semicolon/comma-separated terms. Optional.                                                                                                                           |
| `skill_match`                 | string enum | `any` (default search behavior) or `all`; optional.                                                                                                                                              |
| `language`                    | string      | Optional single-language filter.                                                                                                                                                                 |
| `languages`                   | string      | Search accepts 1–800 chars; semicolon/comma-separated terms. Optional.                                                                                                                           |
| `language_match`              | string enum | `any` (default search behavior) or `all`; optional.                                                                                                                                              |
| `exclude_languages`           | string      | Search accepts 1–800 chars; semicolon/comma-separated terms. Optional.                                                                                                                           |
| `certification`               | string      | Single-term compatibility alias; search accepts 1–200 chars and matches a literal case-insensitive substring of certification name or issuing authority. Optional.                               |
| `certifications`              | string      | String containing a JSON list of up to 10 distinct trimmed terms, each 1–200 chars; each term matches a literal case-insensitive substring of certification name or issuing authority. Optional. |
| `certification_match`         | string enum | `any` (default, OR) or `all` (AND) for `certifications`; optional.                                                                                                                               |
| `location`                    | string      | Optional location filter.                                                                                                                                                                        |
| `exclude_locations`           | string      | Search accepts 1–2,420 chars; optional.                                                                                                                                                          |
| `open_to_work`                | boolean     | Optional; `false` is preserved.                                                                                                                                                                  |
| `between_roles`               | boolean     | Optional; `false` is preserved.                                                                                                                                                                  |
| `exploring`                   | boolean     | Optional; `false` is preserved.                                                                                                                                                                  |
| `github_hireable`             | boolean     | Optional; `false` is preserved.                                                                                                                                                                  |

Those bounds are the `GET /people` request bounds, not `POST /queries` validation. **POST /queries only checks that `query` is an object**; it does not validate nested types, enum values, ranges, or dependencies. A value under an allowlisted key is retained if it is not `null` or `""`, even if it would not be accepted by `GET /people`. To ensure a query is executable, copy it from a successful people-search response and use the search route to validate it.

`query` has no nullable fields in the persisted response: null and empty-string values are removed, while omitted optional keys remain absent. Numeric `0` and boolean `false` are not removed by compaction, although `GET /people` would reject values that violate its own bounds.

## Response schema

Returns `201` with `data`:

| Field        | Type                        | Nullability and meaning                                  |
| ------------ | --------------------------- | -------------------------------------------------------- |
| `id`         | string (UUID)               | Never null.                                              |
| `name`       | string                      | Never null after Gateway trimming.                       |
| `query`      | object                      | Never null; compacted allowlisted object, possibly `{}`. |
| `created_at` | string (ISO 8601 timestamp) | Never null.                                              |

<ResponseExample>
  ```json 201 theme={"theme":{"light":"github-light","dark":"github-dark"}}
  {
    "data": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Palantir FDE leavers",
      "query": {
        "company": "palantir",
        "role_family": "forward-deployed-engineer",
        "company_left_min_months_ago": 18,
        "open_to_work": true
      },
      "created_at": "2026-09-07T10:00:00.000Z"
    }
  }
  ```
</ResponseExample>

<RequestExample>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl --fail-with-body --max-time 30 -X POST "https://api.freshtalent.ai/v1/queries" \
    -H "Authorization: Bearer $FRESHTALENT_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "name":"Palantir FDE leavers",
      "query":{
        "company":"palantir",
        "role_family":"forward-deployed-engineer",
        "company_left_min_months_ago":18,
        "open_to_work":true
      }
    }'
  ```

  ```js JavaScript (native fetch) theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const query = {
    company: "palantir",
    role_family: "forward-deployed-engineer",
    company_left_min_months_ago: 18,
    open_to_work: true,
  };
  const response = await fetch("https://api.freshtalent.ai/v1/queries", {
    method: "POST",
    signal: AbortSignal.timeout(30_000),
    headers: {
      Authorization: `Bearer ${process.env.FRESHTALENT_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ name: "Palantir FDE leavers", query }),
  });
  if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);
  const { data: saved } = await response.json();
  console.log(saved.id, saved.query);
  ```

  ```python Python (stdlib) theme={"theme":{"light":"github-light","dark":"github-dark"}}
  import json
  import os
  from urllib.request import Request, urlopen

  payload = json.dumps({
      "name": "Palantir FDE leavers",
      "query": {
          "company": "palantir",
          "role_family": "forward-deployed-engineer",
          "company_left_min_months_ago": 18,
          "open_to_work": True,
      },
  }).encode()
  request = Request(
      "https://api.freshtalent.ai/v1/queries",
      data=payload,
      method="POST",
      headers={
          "Authorization": f"Bearer {os.environ['FRESHTALENT_API_KEY']}",
          "Content-Type": "application/json",
      },
  )
  with urlopen(request, timeout=30) as response:
      saved = json.load(response)["data"]
  print(saved["id"], saved["query"])
  ```
</RequestExample>

## Errors, side effects, and retry safety

* `400`: missing/invalid `name` or `query`, a non-object `query`, or a missing `X-FreshTalent-Org-Id` UUID when using the platform API key. The Gateway does not return a nested-field validation error for unknown query keys; it drops them.
* `401` (`unauthorized`): provide a valid API key.
* `402` (`payment_required`): restore entitlement.
* `429`: back off, but first consider whether the insert already succeeded.
* `5xx`: the outcome can be unknown. This POST is **not idempotent**; retrying can create duplicate saved queries. Reconcile with [List saved queries](/api-reference/queries/list) and compare `name` plus the persisted compact `query` before deciding whether to retry.

A saved query is data only; saving it does not execute the search, create a Target, or start monitoring. Its snake\_case `query` can be replayed on people search, but is not directly compatible with Target `app_query`. See [Search query compatibility](/api-reference/targets/create#search-query-compatibility).

## Next steps

[Get saved query](/api-reference/queries/get) to retrieve one record, or [List saved queries](/api-reference/queries/list) to reconcile a possibly completed POST.
