> ## 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.

# Create target

> Create a people-search or company-network Target and enqueue discovery.

Creates a standing Target and, when needed, a background discovery job. The operation is asynchronous: creation returns the Target and its current job summary, not the discovered member set.

## Request body

The body is a JSON object. Only documented fields are supported; the framework strips unknown top-level fields. `input` is required; the other fields are optional.

<ParamField body="input" type="string" required>
  1–400 characters before trimming. For `company`, use a company name, domain, or LinkedIn company URL. For `people`, use the Target name or fallback search text. Whitespace-only input passes the JSON length check but is rejected after trimming.
</ParamField>

<ParamField body="kind" type="string">
  Exact enum: `company` or `people`. Omitted means `company` in the service. Do not rely on inference: send the discriminator explicitly.
</ParamField>

<ParamField body="auto_admit" type="boolean">
  Default `false`. With `false`, discovered members start as `proposed`; with `true`, they start as `watching`. `watching` members are eligible for enrichment and Monitoring. Changing this later also promotes existing `proposed` members; it does not promote `excluded` members.
</ParamField>

<ParamField body="result_limit" type="integer">
  Optional. How many people this Target should discover. Minimum `1`. The service clamps the value to the deployment cap (`TRACK_COMPANY_MAX_PEOPLE`, default `4000`). Omit it to use that deployment cap. The dashboard always sends a limit, using remaining monitoring capacity as the ceiling.
</ParamField>

### `kind: "company"`

A company Target tracks the current company network, including current and alumni discovery performed by the worker. `input` is provisionally normalized locally, then resolved asynchronously. The company Target is unique per organization and resolved company. If an existing company Target has a latest job in `pending`, `running`, or `done`, this request returns it with `200` and does not enqueue another job. A failed latest job can be re-enqueued.

`query` and `app_query` are accepted by the route schema but are not used by the company creation service. Omit them.

### `kind: "people"`

A people Target runs the stored people query. The service always creates a new people Target and enqueues a `track_people` job; there is no request idempotency key.

<ParamField body="query" type="object">
  Optional provider-oriented discovery filters for `kind: "people"`. Supported properties and types are listed below. This is not the echoed graph-search query.
</ParamField>

`query` is the direct Gateway query. Its exact accepted shape is:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "keywords": "string",
  "titles": ["string"],
  "past_titles": ["string"],
  "seniorities": ["string"],
  "location_ids": ["string"],
  "exclude_location_ids": ["string"],
  "recently_changed_jobs": true,
  "company_ids": ["string"],
  "past_company_ids": ["string"]
}
```

All properties are optional. Arrays may be empty; the route declares no per-array item limit. Unknown properties are stripped by the framework. These values are passed into the people discovery query. `query.keywords`, when present, overrides the generated fallback keyword from `input`/`app_query`.

<ParamField body="app_query" type="object">
  Optional camelCase app-query object for `kind: "people"`. Use role-object and string arrays as described below; do not paste the snake\_case graph-search echo unchanged.
</ParamField>

`app_query` is an object with the following UI query shape. The route validates only that it is an object, so nested types are a service contract rather than a framework validation guarantee:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "keywords": "string",
  "company": "string",
  "location": "string",
  "excludeLocations": "string",
  "roles": [{
    "family": "string",
    "minMonths": 12,
    "scope": "string",
    "title": "string",
    "titles": ["string"],
    "titleMatch": "exact"
  }],
  "companyCurrent": true,
  "companyLeftMinMonthsAgo": 18,
  "betweenRoles": false,
  "skill": "string",
  "skills": ["string"],
  "skillMatch": "any",
  "language": "string",
  "languages": ["string"],
  "languageMatch": "all",
  "excludeLanguages": ["string"],
  "school": "string"
}
```

Every top-level field is optional; each `roles` item has required `family: string`, with the other role fields optional. The route declares no nested array length limits. Verified enums are `roles[].titleMatch: exact | contains`, `skillMatch: any | all`, and `languageMatch: any | all`. The mapper uses keywords/school, skills, languages, role titles/families, location/exclusions, and maps a positive `companyLeftMinMonthsAgo` or `betweenRoles: true` to `recently_changed_jobs: true` in the stored Gateway query; the numeric month value itself is not retained there. `company` may contribute to the UI-derived Target name, but neither `company` nor `companyCurrent` is copied into `PeopleQuery.company_ids`; use verified provider `query.company_ids` when a company filter is required; the echoed graph-search object is not a substitute. `minMonths` and `scope` are accepted role fields but are not mapped into the stored `PeopleQuery` by this service.

Certification is supported by graph people search, but is not supported by people Target discovery. Supplying either `app_query.certification` or `app_query.certifications` returns `400 certification_target_unsupported`; save and replay the echoed query through [Search people](/api-reference/people/search) when certification matching is required.

<Warning>
  Do not pass the echoed snake\_case people-search `query` straight into `app_query`. The two objects have different types and different meanings. Some fields would be ignored; a string `roles` value can cause a service error because the mapper expects an array. Do not invent provider company or location IDs.
</Warning>

## Search query compatibility

There is no lossless REST conversion from a graph search to a discovery Target:

| Graph search                                                        | Target discovery                                                                                                                        |
| ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `roles` is a token string                                           | `app_query.roles` is an array of role objects                                                                                           |
| `skills` / `languages` are delimited strings                        | `app_query.skills` / `languages` are string arrays                                                                                      |
| `role_family` and snake\_case availability flags                    | Not read by the Target app-query mapper                                                                                                 |
| `company` matches graph company text                                | `app_query.company` does not set a provider company filter                                                                              |
| `company_left_min_months_ago: 18` means left at least 18 months ago | `app_query.companyLeftMinMonthsAgo > 0` maps only to a provider recently-changed-jobs flag; it does not preserve the 18-month condition |
| `certification` / `certifications` match recorded graph credentials | People Target discovery rejects these certification filters; they are not provider filters                                              |
| Role tenure and scope                                               | Not preserved in discovery filters                                                                                                      |

For exact repeat searches, save and replay the query through [Search people](/api-reference/people/search). For discovery, build the documented Target inputs deliberately and review the returned `data.query.filters` and members. The MCP has an adapter for echoed queries, but that adapter does not make unsupported provider filters equivalent to graph filters.

## Response

* `202 Accepted` means a background discovery job was enqueued. A company Target may have been reused rather than newly created.
* `200 OK` means the request reused an existing company Target because its latest job was `pending`, `running`, or `done`.

The body is `{ "data": Target }` with the same Target schema as [List targets](/api-reference/targets/list). `job.status` is normally `pending` immediately after enqueue. `job.phase` starts as `queued`; later phases may include `resolving`, `discovering`, `enriching`, and `done`. `job.id` and `job.error` are nullable. `linkedin_url` is nullable until company resolution supplies one and is always `null` for people Targets.

<ResponseExample>
  ```json 202 theme={"theme":{"light":"github-light","dark":"github-dark"}}
  {
    "data": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "kind": "people",
      "name": "Foundry FDE discovery",
      "linkedin_url": null,
      "auto_admit": false,
      "watching_count": 0,
      "proposed_count": 0,
      "excluded_count": 0,
      "current_count": 0,
      "alum_count": 0,
      "enriched_count": 0,
      "member_count": 0,
      "allocation_used": 0,
      "in_graph_count": 0,
      "result_limit": 80,
      "created_at": "2026-09-16T11:43:00.000Z",
      "query": {
        "input": "Foundry FDE discovery",
        "filters": { "keywords": "Foundry", "keyword_leaves": ["Foundry"], "titles": ["Forward Deployed Engineer", "Forward-Deployed Engineer", "FDE"] },
        "app": { "keywords": "Foundry", "roles": [{ "family": "forward-deployed-engineer" }] },
        "result_limit": 80
      },
      "job": {
        "id": "7b1d9b3c-0b8f-4d4d-a8b4-72ef8fbb4c81",
        "status": "pending",
        "phase": "queued",
        "total": 0,
        "completed": 0,
        "failed": 0,
        "error": null
      }
    }
  }
  ```
</ResponseExample>

## Examples

<RequestExample>
  ```bash cURL company theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl --fail-with-body --max-time 30 -X POST "https://api.freshtalent.ai/v1/targets" \
    -H "Authorization: Bearer $FRESHTALENT_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"input":"anduril.com","kind":"company"}'
  ```

  ```bash cURL people theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl --fail-with-body --max-time 30 -X POST "https://api.freshtalent.ai/v1/targets" \
    -H "Authorization: Bearer $FRESHTALENT_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "input":"Foundry FDE discovery",
      "kind":"people",
      "app_query": {
        "keywords":"Foundry",
        "roles":[{"family":"forward-deployed-engineer"}]
      },
      "result_limit": 80
    }'
  ```

  ```js JavaScript fetch theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const response = await fetch("https://api.freshtalent.ai/v1/targets", {
    method: "POST",
    signal: AbortSignal.timeout(30_000),
    headers: {
      Authorization: `Bearer ${process.env.FRESHTALENT_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      input: "Foundry FDE discovery",
      kind: "people",
      app_query: { keywords: "Foundry", roles: [{ family: "forward-deployed-engineer" }] },
    }),
  });
  const body = await response.json();
  if (![200, 202].includes(response.status)) throw new Error(`${response.status}: ${JSON.stringify(body)}`);
  console.log(body.data.id, body.data.job.status);
  ```

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

  payload = json.dumps({
      "input": "Foundry FDE discovery",
      "kind": "people",
      "app_query": {"keywords": "Foundry", "roles": [{"family": "forward-deployed-engineer"}]},
  }).encode()
  request = urllib.request.Request(
      "https://api.freshtalent.ai/v1/targets",
      data=payload,
      method="POST",
      headers={
          "Authorization": f"Bearer {os.environ['FRESHTALENT_API_KEY']}",
          "Content-Type": "application/json",
      },
  )
  with urllib.request.urlopen(request, timeout=30) as response:
      body = json.load(response)
  print(body["data"]["id"], body["data"]["job"]["status"])
  ```
</RequestExample>

## Lifecycle, side effects, and retries

Creation inserts a Target, enqueues discovery, and attempts to create its default list. Discovery and enrichment happen later. Discovery is capped by `result_limit` when provided, otherwise by `TRACK_COMPANY_MAX_PEOPLE` (default `4000`, configurable at deployment); each saturated provider search leaf is capped at `2500`. `auto_admit: false` leaves new members `proposed`; setting members to `watching` is the operation that queues enrichment. A run selects up to `2000` initially unenriched watching members, or up to `500` stale members during refresh, so large Targets may require later work. The worker deduplicates discovered URLs within a Target and uses bounded enrichment retries internally. Discovery usually finishes within 24 hours; the API does not promise a completion time or monitoring freshness.

Do not blindly retry a timed-out `POST`: a people request may already have created a second Target, while a company request may have reused or created a job. First call [List targets](/api-reference/targets/list) and reconcile by `id`, `kind`, name, and stored query. A `202` is acceptance of the job, not completion.

## Errors and recovery

* `400 invalid_request`: missing/blank `input`, invalid `kind`, invalid top-level type, or invalid JSON schema. Fix the body.
* `401 unauthorized`, `402 payment_required`: fix the API key or organization access.
* `404 not_found`: can surface from a service lookup; verify the organization and input.
* `409 conflict`: the route preserves a service conflict response if one occurs; reconcile before replaying.
* `429`/`5xx`: back off. For an uncertain write, read the Target collection before retrying rather than replaying blindly.
* A `202` Target whose `job.status` later becomes `failed` is not fixed by polling faster; inspect `job.error`, then use [Retry target](/api-reference/targets/retry) after the underlying issue is resolved.

## Next

* [Get a target](/api-reference/targets/get) or [List targets](/api-reference/targets/list) to reconcile asynchronous progress.
* [List target members](/api-reference/targets/members) to inspect `proposed` results.
* [Accept target members](/api-reference/targets/admit) to move selected members to `watching`.
