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

# Imports

> Run a complete import session from file upload through validation, corrections, submission, and status tracking

# Imports

The Imports API is a session-based workflow for structured CSV/XLSX uploads where a client needs to:

* upload a source file
* map source columns to a Dcycle template
* inspect distinct raw values for category fields
* validate rows before ingestion
* patch a subset of rows when validation fails
* submit the cleaned dataset into downstream processing

This API is especially useful for automation, partner integrations, and CLI-driven uploads.

<Note>
  The Imports API is a session-based workflow. You do not send one big "import everything" request. Instead, you create an import session, validate it, optionally correct rows, and only then submit it.
</Note>

## End-to-End Flow

<Steps>
  <Step title="1. Create session">
    Call `POST /v2/imports/sessions` with a `template_id` and the source file as multipart form data.

    Required form fields:

    * `template_id` — backend template identifier (e.g. `logistics_requests`)
    * `upload_file` — the source CSV/XLSX file

    Optional form fields:

    * `numeric_locale` — locale for decimal parsing (e.g. `es_ES`)
    * `date_locale` — `DateLocaleEnum` value for date parsing when the source uses a non-ISO format
    * `sheet_name` — sheet to read for multi-sheet XLSX uploads
    * `project_id` / `folder_id` — UUIDs scoping the import to a project or folder
    * `raw_file_id` — UUID of a previously uploaded raw file to link the session to

    The backend parses the file and returns:

    * `import_id`
    * detected source columns
    * sample rows
    * sheet/header metadata
  </Step>

  <Step title="2. Suggest mapping">
    Call `POST /v2/imports/{import_id}/mapping/suggest`.

    This returns a best-effort mapping suggestion from source columns to template columns. Each suggestion includes a `column_confidence`, a `value_confidence` (category targets only), and a `restored_from_history` flag indicating when the match was lifted from a prior import rather than re-scored. Clients can accept these suggestions as-is or adjust them before validation.
  </Step>

  <Step title="3. Confirm mapping (optional but recommended)">
    Call `POST /v2/imports/{import_id}/mapping` with the mapping the user accepted.

    This persists the mapping on the session and transitions its status from `parsed` to `mapped`. It is optional because `validate` also accepts a mapping in the body, but confirming has two benefits:

    * `GET /v2/imports/sessions` surfaces the chosen mapping so a user can resume the session later
    * downstream calls (unique-values, validate) behave consistently with what the user saw
  </Step>

  <Step title="4. Inspect category values">
    Call `POST /v2/imports/{import_id}/unique-values` with the current mapping.

    The backend returns distinct raw values for mapped category columns only, each annotated with an auto-resolved canonical value and a `matched` / `unmatched` status. This is the right moment to build a value-mapping UI before row-level validation starts.
  </Step>

  <Step title="5. Validate rows">
    Call `POST /v2/imports/{import_id}/validate` with the chosen mapping. Optionally include `value_mappings` to pre-resolve category values the user remapped by hand.

    Query parameters:

    * `page`, `page_size` — paginate the returned rows
    * `errors_only=true` — return only rows that still have validation issues

    The response includes:

    * `valid_rows`
    * `error_rows`
    * `errors_by_column`
    * paginated row-level validation output
  </Step>

  <Step title="6. Review rows">
    Call `GET /v2/imports/{import_id}/rows` to page through the processed rows.

    Use `errors_only=true` when you only want rows that still need action.

    Sort by any column with `sort_by=<column_key>` and `sort_direction=asc|desc`. When `sort_by` is omitted rows come back in original `row_index` order.

    Filter rows server-side with `filters`, a JSON-encoded object mapping column keys to allowed values:

    ```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
    GET /v2/imports/{import_id}/rows?filters=%7B%22country%22%3A%5B%22Spain%22%2C%22France%22%5D%2C%22fuel%22%3A%5B%22diesel%22%5D%7D
    ```

    Decoded: `{"country": ["Spain", "France"], "fuel": ["diesel"]}`.

    Semantics:

    * **AND across columns, IN within a column.** A row is returned when *every* listed column matches *any* of its listed values.
    * **Whitespace-insensitive.** Values are compared after trimming, matching what the dropdown surfaces.
    * **Empty value list = no filter on that column.** Sending `{"country": []}` is equivalent to omitting `country`. To get zero rows, omit the request — there is no "match nothing" sentinel.
    * **Caps:** at most 20 filter keys per request, 500 values per key. Beyond either cap returns `422`.
    * **Allowed key characters:** `[a-zA-Z0-9_]+`.

    To populate filter dropdowns with cascading constraints (Excel-style), call `GET /v2/imports/{import_id}/rows/column-values?column=<column_key>&filters=<json>`. The endpoint returns the distinct values present in `column` *under the other columns' filters* (the filter for `column` itself is automatically excluded so the user keeps seeing the full list available for that column):

    ```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
    {
      "column": "country",
      "values": ["France", "Spain"],
      "total_unique": 2,
      "truncated": false
    }
    ```

    `truncated` is `true` when the unique-value count exceeds the server cap of 1000; `total_unique` always reports the real count of the filtered dataset.
  </Step>

  <Step title="7. Patch corrections">
    If some rows are invalid, call `PATCH /v2/imports/{import_id}/rows` with row-level corrections (max 500 per request).

    The backend revalidates the corrected rows and returns updated row data plus the remaining error count.
  </Step>

  <Step title="8. Submit import">
    Once the session is valid, call `POST /v2/imports/{import_id}/submit` with the `x-partner` header set to your partner identifier.

    Submission creates the downstream processing artifact and starts the heavy processing pipeline.
  </Step>

  <Step title="9. Check status">
    Call `GET /v2/imports/{import_id}/status` to retrieve the session status and processing summary.
  </Step>

  <Step title="10. Abandon a session (optional)">
    If the user discards an import before submitting, call `DELETE /v2/imports/sessions/{import_id}` to clear the cached rows and associated data. Submitted sessions cannot be deleted.
  </Step>
</Steps>

## Recommended Client Behavior

For most clients, the safest workflow is:

1. Create the session.
2. Request mapping suggestions.
3. Confirm the chosen mapping so the session is resumable.
4. Inspect distinct values for mapped category columns.
5. Validate with the chosen mapping (and any user-supplied `value_mappings`).
6. If there are errors, fetch or display only invalid rows.
7. Patch corrections until the session is clean.
8. Submit the import.
9. Poll status until downstream processing has clearly started.

## Typical Session Example

### 1. Create the session

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST "https://api.dcycle.io/v2/imports/sessions" \
  -H "x-api-key: ${DCYCLE_API_KEY}" \
  -H "x-organization-id: ${DCYCLE_ORG_ID}" \
  -F "template_id=logistics_requests" \
  -F "numeric_locale=es_ES" \
  -F "upload_file=@./logistics.csv;type=text/csv"
```

Example response:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "import_id": "11111111-1111-1111-1111-111111111111",
  "file_name": "logistics.csv",
  "total_rows": 240,
  "numeric_locale": "es_ES",
  "date_locale": null,
  "sheets": [],
  "selected_sheet": null,
  "detected_header_row": 0,
  "source_columns": [
    "Trip Date",
    "Client",
    "Distance",
    "Origin Country",
    "Destination Country"
  ],
  "sample_rows": [
    ["2026-03-01", "ACME", "125,4", "ES", "FR"]
  ]
}
```

### 2. Ask for mapping suggestions

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST "https://api.dcycle.io/v2/imports/11111111-1111-1111-1111-111111111111/mapping/suggest" \
  -H "x-api-key: ${DCYCLE_API_KEY}" \
  -H "x-organization-id: ${DCYCLE_ORG_ID}" \
  -H "Content-Type: application/json" \
  -d '{}'
```

### 3. Confirm the user-selected mapping

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST "https://api.dcycle.io/v2/imports/11111111-1111-1111-1111-111111111111/mapping" \
  -H "x-api-key: ${DCYCLE_API_KEY}" \
  -H "x-organization-id: ${DCYCLE_ORG_ID}" \
  -H "Content-Type: application/json" \
  -d @mapping.json
```

### 4. Validate using a chosen mapping

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "mapping": {
    "trip_date": "Trip Date",
    "client": "Client",
    "distance_km": "Distance",
    "origin_country": "Origin Country",
    "destination_country": "Destination Country"
  }
}
```

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST "https://api.dcycle.io/v2/imports/11111111-1111-1111-1111-111111111111/validate?page=1&page_size=50" \
  -H "x-api-key: ${DCYCLE_API_KEY}" \
  -H "x-organization-id: ${DCYCLE_ORG_ID}" \
  -H "Content-Type: application/json" \
  -d @mapping.json
```

### 5. Inspect distinct category values

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST "https://api.dcycle.io/v2/imports/11111111-1111-1111-1111-111111111111/unique-values" \
  -H "x-api-key: ${DCYCLE_API_KEY}" \
  -H "x-organization-id: ${DCYCLE_ORG_ID}" \
  -H "Content-Type: application/json" \
  -d @mapping.json
```

### 6. Patch one bad row

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "corrections": [
    {
      "row_index": 17,
      "column": "distance_km",
      "value": 128.4
    }
  ]
}
```

### 7. Submit the import

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST "https://api.dcycle.io/v2/imports/11111111-1111-1111-1111-111111111111/submit" \
  -H "x-api-key: ${DCYCLE_API_KEY}" \
  -H "x-organization-id: ${DCYCLE_ORG_ID}" \
  -H "x-partner: dcycle" \
  -H "Content-Type: application/json" \
  -d '{"ignore_errors": false}'
```

Example response:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "status": "processing",
  "file_id": "22222222-2222-2222-2222-222222222222",
  "processing_job_id": "33333333-3333-3333-3333-333333333333",
  "rows_submitted": 240
}
```

## Template Metadata

Use `GET /v2/imports/templates/{template_id}` to retrieve the target template definition before or during mapping.

This is helpful when clients need to:

* render a column picker
* show required vs optional target fields
* display allowed category options
* apply defaults from the backend template definition

When a category column uses `options_source` instead of inline `options`, call
`GET /v2/imports/options/{source_key}` to fetch the resolved list of selectable
values and labels from the backend registry.

When a client needs to inspect the raw values present in the uploaded file before
validation, call `POST /v2/imports/{import_id}/unique-values` with the chosen mapping.

Typical examples:

* `countries` for country fields
* `logistics_tocs` for logistics vehicle types
* `fuels` for logistics recharge fuel selection

## Status Lifecycle

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
created -> parsed -> mapped -> validating -> validated -> submitting -> submitted
                     |
                     +-> failed
```

In practice, clients will most commonly observe:

* `created` briefly at session creation time before parsing metadata is persisted
* `parsed` after session creation
* `validated` after a successful validation pass
* `submitted` after the import has been handed off for downstream processing

## Best Practices

* Persist the `import_id` on the client side so users can safely resume a session.
* Use `errors_only=true` when rendering review tables for large imports.
* Use the unique-values endpoint before validation if your client supports category value mapping.
* Treat mapping suggestions as hints, not guaranteed matches.
* Do not submit until validation is clean unless you intentionally support `ignore_errors=true`.
* Use the template metadata endpoint to avoid hardcoding target columns in the client.
