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

# Get Unique Values

> The distinct raw values of each mapped category column, with what the system could resolve them to

[← Imports](/api-reference/imports/overview)

After you choose which source column feeds which field, this returns the **distinct raw values** found in every mapped category column, each with what the system managed to resolve it to. It is the step between mapping columns and mapping values: the user confirms that `"ES-Madrid"` in their file means the Madrid facility before anything is imported.

<Note>
  **It is a `POST` because you send the mapping in the body.** Nothing is *imported* here — no rows of your data are created. It is not a pure read either: a call that actually resolves values records what it did, so the same call twice leaves two entries in that log. A call that resolves nothing — an empty `mapping`, or a column whose values are all blank — still returns `200` and writes nothing. Treat it as a step of the flow, not as a query you can poll freely.
</Note>

<Warning>
  **Null fields are omitted, not sent as `null`.** The response is serialised with `response_model_exclude_none`, so a value the system could not resolve comes back **without** a `resolved` key at all — not as `"resolved": null`.

  This applies to `null` only: a field that is `false` or `0` is still sent.

  Read it as `entry.get("resolved")` in Python or `entry.resolved ?? null` in JavaScript. Code that assumes the key is always present will throw on exactly the rows that need human attention.
</Warning>

## Request

### Headers

<ParamField header="x-organization-id" type="string" required>
  UUID of the organization the import belongs to.

  **Format:** UUID
</ParamField>

<ParamField header="x-api-key" type="string">
  Your API key.
</ParamField>

### Path Parameters

<ParamField path="import_id" type="string" required>
  UUID of the import session, as returned by [Create Session](/api-reference/imports/create-session).

  **Format:** UUID
</ParamField>

### Body Parameters

<ParamField body="mapping" type="object" required>
  The column mapping the user has chosen: `{ target_field: source_column }`. A target field with no source column is sent as `null`.

  **Example:** `{ "facility": "Site", "waste_code": "LER", "notes": null }`
</ParamField>

<ParamField body="constant_values" type="object">
  Fixed values for fields that have no source column, as `{ target_field: value }`.

  A constant on a category column still shows up in the response — as a column with `is_constant: true` and a single raw value to resolve — so the user confirms it once instead of per row.
</ParamField>

## Response

<ResponseField name="columns" type="array[object]">
  One entry per mapped category column. Columns that need no value mapping are not listed.

  <Expandable title="Column">
    <ResponseField name="column_key" type="string">
      The target field this column feeds.
    </ResponseField>

    <ResponseField name="source_column" type="string">
      The column in the uploaded file it was mapped from. Always present: for a column whose value came from `constant_values` it arrives as an **empty string**, not as `null` or absent.
    </ResponseField>

    <ResponseField name="total_unique" type="integer">
      How many distinct raw values the column holds in total.
    </ResponseField>

    <ResponseField name="truncated" type="boolean" default="false">
      `true` when `values` is a subset of `total_unique`. Compare the two before telling the user the list is complete.
    </ResponseField>

    <ResponseField name="is_constant" type="boolean" default="false">
      `true` when the column has no source column and its single value came from `constant_values` rather than from the file.
    </ResponseField>

    <ResponseField name="values" type="array[object]">
      The distinct raw values and how far the system got with each.

      <Expandable title="Value Entry">
        <ResponseField name="raw" type="string">
          The value exactly as it appears in the file.
        </ResponseField>

        <ResponseField name="status" type="string">
          `matched` — resolved with confidence, nothing to ask.

          `suggested` — a candidate was found but should be confirmed.

          `unmatched` — nothing was found; the user must choose.
        </ResponseField>

        <ResponseField name="resolved" type="string">
          What the raw value resolved to. **Absent entirely** when nothing was resolved — see the warning above.
        </ResponseField>
      </Expandable>

      An entry also carries diagnostic fields when the system has them — among others `resolution_origin`, `embedding_score`, `embedding_margin`, `llm_confidence` and `top_candidates`. They follow the same rule as `resolved`: present when set, absent when null. A `suggested` entry in practice always carries `resolution_origin`, so do not treat the three fields above as the whole object.
    </ResponseField>
  </Expandable>
</ResponseField>

## Example

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl -X POST "https://api.dcycle.io/v2/imports/YOUR_IMPORT_ID/unique-values" \
    -H "x-api-key: YOUR_API_KEY" \
    -H "x-organization-id: YOUR_ORGANIZATION_ID" \
    -H "Content-Type: application/json" \
    -d '{
      "mapping": {
        "facility": "Site",
        "waste_code": "LER"
      },
      "constant_values": {
        "treatment": "recycling"
      }
    }'
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  import requests

  HEADERS = {
      "x-api-key": "YOUR_API_KEY",
      "x-organization-id": "YOUR_ORGANIZATION_ID",
  }

  data = requests.post(
      f"https://api.dcycle.io/v2/imports/{import_id}/unique-values",
      headers=HEADERS,
      json={
          "mapping": {"facility": "Site", "waste_code": "LER"},
          "constant_values": {"treatment": "recycling"},
      },
      timeout=60,
  ).json()

  for column in data["columns"]:
      if column["truncated"]:
          print(f"{column['column_key']}: showing part of {column['total_unique']} values")

      # `resolved` is ABSENT when nothing matched — never assume the key exists
      needs_user = [v for v in column["values"] if v.get("resolved") is None]
      print(f"{column['column_key']}: {len(needs_user)} values need a decision")
  ```

  ```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const response = await fetch(
    `https://api.dcycle.io/v2/imports/${importId}/unique-values`,
    {
      method: "POST",
      headers: {
        "x-api-key": "YOUR_API_KEY",
        "x-organization-id": "YOUR_ORGANIZATION_ID",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        mapping: { facility: "Site", waste_code: "LER" },
        constant_values: { treatment: "recycling" },
      }),
    },
  );
  const data = await response.json();

  for (const column of data.columns) {
    // `resolved` may be missing entirely, not null
    const needsUser = column.values.filter((v) => v.resolved === undefined);
    console.log(`${column.column_key}: ${needsUser.length} need a decision`);
  }
  ```
</CodeGroup>

### Successful Response

Returns `200 OK`.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "columns": [
    {
      "column_key": "facility",
      "source_column": "Site",
      "total_unique": 3,
      "truncated": false,
      "is_constant": false,
      "values": [
        {
          "raw": "ES-Madrid",
          "status": "matched",
          "resolved": "Madrid Office"
        },
        {
          "raw": "ES-Bcn",
          "status": "suggested",
          "resolved": "Barcelona Office",
          "resolution_origin": "lexical_fuzzy",
          "embedding_score": 0.82,
          "embedding_margin": 0.11
        },
        {
          "raw": "Planta 4",
          "status": "unmatched"
        }
      ]
    }
  ]
}
```

Note the third entry: no `resolved` key at all, rather than `"resolved": null`.

Only `null` fields disappear. `truncated` and `is_constant` are `false` here and **are** present — `exclude_none` drops `None`, not falsy values — so those two you can read directly.

## Common Errors

### 422 Unprocessable Entity

**Cause:** `mapping` is missing from the body. It is the only required field.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "detail": [
    {
      "loc": ["body", "mapping"],
      "msg": "field required",
      "type": "value_error.missing"
    }
  ]
}
```

**Cause:** `import_id` is not a valid UUID.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "detail": [
    {
      "loc": ["path", "import_id"],
      "msg": "value is not a valid uuid",
      "type": "type_error.uuid"
    }
  ]
}
```

## Use Cases

### Build the value-mapping screen

Render one section per column. Values with `status: matched` can be collapsed; `suggested` shows the candidate with a confirm control; `unmatched` needs a picker. Sorting by status puts the work that needs a human first.

### Do not promise a complete list

When `truncated` is `true`, `values` holds only part of `total_unique`. Saying "3 values to map" when there are 300 turns into a failed import later — show the real total and page the rest.

## Related Endpoints

<CardGroup cols={2}>
  <Card title="Confirm Mapping" icon="check" href="/api-reference/imports/confirm-mapping">
    Send back the decisions the user made here
  </Card>

  <Card title="Suggest Mapping" icon="wand-magic-sparkles" href="/api-reference/imports/suggest-mapping">
    The column mapping this endpoint takes as input
  </Card>

  <Card title="Validate Import" icon="list-check" href="/api-reference/imports/validate">
    The step after the values are mapped
  </Card>

  <Card title="Imports API" icon="file-import" href="/api-reference/imports/overview">
    The whole import flow, in order
  </Card>
</CardGroup>
