# Preview import

Preview import is a command in the HeadHoncho API. Read the staged files against this workspace and say what an import would do, row by row: what lands, what is already here, what is refused and why.

Page: https://headhoncho.app/docs/api/preview_import | Part of the HeadHoncho API docs: https://headhoncho.app/docs/api | Machine-readable: https://headhoncho.app/docs/api/openapi.json | Everything in one file: https://headhoncho.app/docs/api/llms-full.txt

## The request

Reference for **Preview import**, HeadHoncho API v1. It is a command, sent to the base address `https://api.headhoncho.app`.

`POST /api/v1/commands/preview-import`

The key needs the **Jobs** area at **read**. An answered call costs 1 credit; a refusal costs nothing. See [credits](https://headhoncho.app/docs/api/errors#credits).

It runs the app's own command `preview-import`. Send an `Idempotency-Key` header with every call: the same key with the same body returns the first answer and runs nothing twice. See [the model](https://headhoncho.app/docs/api/model#idempotency).

## Parameters

| Name | Kind | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `company_id` | id, in the JSON body | Yes | The company the jobs land in. From Who am I. | `cmp_14f5` |
| `plan` | text, in the JSON body | Yes | The staged files as a JSON list. Each item: staging_id (from Stage an import file), half (jobs, money or payments), mapping (field to column numbers) and date_order. | `[{"staging_id": "k3Zp...", "half": "jobs", "mapping": {"ref": [0], "client_name": [1]}}]` |
| `status_map` | object, in the JSON body | No | The file's status word to a target, from Map statuses. | `{"Done": "st_06"}` |
| `type_map` | object, in the JSON body | No | The file's job-type word to a target, from Map job types. | `{"Local Move": "jt_02"}` |

## Try it with your key

Paste your key and change the values. The box builds the call as a cURL line and a fetch line, ready to copy. It never sends anything itself.

Paste your key on the web page and it fills a cURL line and a fetch line with your key and the example values. The page never sends the key anywhere. The same lines with a placeholder key are under Examples below.

## What comes back

accepted and position, then rows (each file row with its issues, gaps and already_imported), technicians (each name and the worker it would match), statuses and job_types_to_create, counts (total, importable, already_imported, refused, incomplete), finance (the money rows and their counts), decisions where the two files disagree (key, field, options jobs and money, values, default) and files and range. Nothing is written.

It never deletes anything. It changes only what the command names, and it writes the change to the job's history with who asked for it.

## Every field in the answer

Types are read from the example answer. Money is a plain number in the company's currency. A time carries its offset from UTC (`Z` is UTC). Ids are opaque text. A field with no line of its own here has its name spelled out; the common fields mean the same on every page.

| Field | Type | Meaning |
| --- | --- | --- |
| `accepted` | true or false | True when the command ran. A command that cannot run is an error instead. |
| `position` | whole number | Where the change landed in the event log. Pass it as `after` on a read to see your own write. |
| `rows` | list of objects | The rows. |
| `rows[].line` | whole number | The line. |
| `rows[].ticket` | text | The job number the ticket shows. |
| `rows[].status` | text | The job status. |
| `rows[].job_type` | text | The job type. |
| `rows[].technician` | text | The technician. |
| `rows[].issues` | list | The issues. |
| `rows[].gaps` | list | The gaps. |
| `rows[].already_imported` | true or false | The already imported. |
| `technicians` | list of objects | The technicians. |
| `technicians[].name` | text | The name, as the app shows it. |
| `technicians[].suggested_worker_id` | id (text) | The id of the suggested worker. |
| `technicians[].suggested_worker_name` | text | The suggested worker name. |
| `technicians[].suggested_role` | text | The suggested role. |
| `statuses` | object | The statuses. |
| `statuses.Done` | object | The Done. |
| `statuses.Done.lands_on` | text | The date it was lands. |
| `statuses.Done.creates` | value or null | The creates. |
| `statuses.Done.archived` | value or null | The archived. |
| `statuses_to_create` | list | The statuses to create. |
| `job_types_to_create` | list | The job types to create. |
| `counts` | object | The counts. |
| `counts.total` | whole number | The total, in the company's currency. |
| `counts.importable` | whole number | The importable. |
| `counts.already_imported` | whole number | The already imported. |
| `counts.refused` | whole number | The refused. |
| `counts.addressless` | whole number | The addressless. |
| `counts.incomplete` | whole number | The incomplete. |
| `finance` | object | The finance. |
| `finance.rows` | list | The rows. |
| `finance.counts` | object | The counts. |
| `finance.counts.total` | whole number | The total, in the company's currency. |
| `finance.counts.importable` | whole number | The importable. |
| `decisions` | list | The decisions. |
| `files` | list of objects | The files. |
| `files[].name` | text | The name, as the app shows it. |
| `files[].half` | text | The half. |
| `files[].rows` | whole number | The rows. |
| `range` | value or null | The range. |

## A read that is sent as a command

It writes nothing and needs only read access to Jobs, as the app's preview does. It is a POST because the plan is too long for a web address.

## Personal details

The customer name, phone and street on each row follow the credential's personal-details switch. When it hides them, the raw address and the matched customer are left out too.

## Errors that apply

| Code | Status | What it means |
| --- | --- | --- |
| [`missing_key`](https://headhoncho.app/docs/api/errors#missing_key) | 401 | Send your key as Authorization: Bearer hh_live_... |
| [`unknown_key`](https://headhoncho.app/docs/api/errors#unknown_key) | 401 | That key is not one we issued, or it was revoked. |
| [`no_access`](https://headhoncho.app/docs/api/errors#no_access) | 403 | This credential does not reach that. Ask the person who made it for access. |
| [`not_found`](https://headhoncho.app/docs/api/errors#not_found) | 404 | No such record in the companies this credential reaches. |
| [`out_of_scope`](https://headhoncho.app/docs/api/errors#out_of_scope) | 404 | No such company in the companies this credential reaches. |
| [`invalid`](https://headhoncho.app/docs/api/errors#invalid) | 422 | Some fields were refused. See fields. |
| [`missing_idempotency_key`](https://headhoncho.app/docs/api/errors#missing_idempotency_key) | 400 | Send an Idempotency-Key header on every command. |
| [`idempotency_mismatch`](https://headhoncho.app/docs/api/errors#idempotency_mismatch) | 409 | This Idempotency-Key was used with a different body. |
| [`idempotency_in_progress`](https://headhoncho.app/docs/api/errors#idempotency_in_progress) | 409 | A command with this Idempotency-Key is still running. Wait a moment and send it again. |
| [`no_credits`](https://headhoncho.app/docs/api/errors#no_credits) | 402 | Out of credits. Top up on Settings › Credits, or turn on auto top-up. |

## Related

- [List import systems](https://headhoncho.app/docs/api/list_import_systems): The systems a book can be imported from, and for each the steps to get its file out, what the file cannot carry, and which one this workspace used last.
- [Get import fields](https://headhoncho.app/docs/api/get_import_fields): Every field an import reads, for the jobs file and the money file: its label, its tier and what leaving it blank costs.
- [Get import template](https://headhoncho.app/docs/api/get_import_template): A CSV to fill in, with every column the import reads and three example rows, for the jobs file, the money file or a payments list.
- [Stage import](https://headhoncho.app/docs/api/stage_import): Send one export file (CSV or Excel) and get back what each column looks like and the mapping we propose. Nothing is imported yet.
- [List import statuses](https://headhoncho.app/docs/api/list_import_statuses): The status words the staged files use, how many rows carry each, and which of our statuses each should land on. Nothing is written.
- [List import job types](https://headhoncho.app/docs/api/list_import_job_types): The job-type words the staged files use, how many rows carry each, and which of our job types each should land on. Nothing is written.
- [Rehearse import](https://headhoncho.app/docs/api/rehearse_import): Run the whole import for real inside one step that is then thrown away, and say what it would have landed and refused. Nothing is kept.
- [Commit import](https://headhoncho.app/docs/api/commit_import): Import every clean row of the staged files: the jobs, then the money on them. Rows already here are skipped, so sending it twice adds nothing.
- The same records in the app: [the Jobs panel](https://headhoncho.app/docs/jobs).
- How the API fits together: [the model](https://headhoncho.app/docs/api/model), [API keys](https://headhoncho.app/docs/api/keys), [errors](https://headhoncho.app/docs/api/errors) and [the MCP server](https://headhoncho.app/docs/api/mcp).

## Examples

### Answer

```json
{
  "accepted": true,
  "position": 88611,
  "rows": [
    {
      "line": 2,
      "ticket": "W-1001",
      "status": "Done",
      "job_type": "Local Move",
      "technician": "Dana R.",
      "issues": [],
      "gaps": [],
      "already_imported": false
    }
  ],
  "technicians": [
    {
      "name": "Dana R.",
      "suggested_worker_id": "wrk_3b90",
      "suggested_worker_name": "Dana R.",
      "suggested_role": "technician"
    }
  ],
  "statuses": {
    "Done": {
      "lands_on": "Closed",
      "creates": null,
      "archived": null
    }
  },
  "statuses_to_create": [],
  "job_types_to_create": [],
  "counts": {
    "total": 120,
    "importable": 118,
    "already_imported": 0,
    "refused": 2,
    "addressless": 3,
    "incomplete": 5
  },
  "finance": {
    "rows": [],
    "counts": {
      "total": 0,
      "importable": 0
    }
  },
  "decisions": [],
  "files": [
    {
      "name": "jobs.csv",
      "half": "jobs",
      "rows": 120
    }
  ],
  "range": null
}
```

### cURL

```bash
curl -X POST "https://api.headhoncho.app/api/v1/commands/preview-import" \
  -H "Authorization: Bearer hh_live_..." \
  -H "Idempotency-Key: 6f1d2c9e-1b7a-4c1e-9a52-0d3f8e4b7a10" \
  -H "Content-Type: application/json" \
  -d '{
  "company_id": "cmp_14f5",
  "plan": "[{\"staging_id\": \"k3Zp...\", \"half\": \"jobs\", \"mapping\": {\"ref\": [0], \"client_name\": [1]}}]",
  "status_map": {
    "Done": "st_06"
  },
  "type_map": {
    "Local Move": "jt_02"
  }
}'
```

### Python

```python
import requests
import uuid

response = requests.post(
    "https://api.headhoncho.app/api/v1/commands/preview-import",
    headers={
        "Authorization": "Bearer hh_live_...",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "company_id": "cmp_14f5",
        "plan": "[{\"staging_id\": \"k3Zp...\", \"half\": \"jobs\", \"mapping\": {\"ref\": [0], \"client_name\": [1]}}]",
        "status_map": {
            "Done": "st_06",
        },
        "type_map": {
            "Local Move": "jt_02",
        },
    },
)
response.raise_for_status()
print(response.json())
```

### JavaScript

```javascript
const response = await fetch("https://api.headhoncho.app/api/v1/commands/preview-import", {
  method: "POST",
  headers: {
    Authorization: "Bearer hh_live_...",
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "company_id": "cmp_14f5",
    "plan": "[{\"staging_id\": \"k3Zp...\", \"half\": \"jobs\", \"mapping\": {\"ref\": [0], \"client_name\": [1]}}]",
    "status_map": {
      "Done": "st_06"
    },
    "type_map": {
      "Local Move": "jt_02"
    }
  }),
});
const data = await response.json();
console.log(data);
```

### Your AI

```text
Tool: preview_import
Arguments: company_id (id, required), plan (text, required), status_map (object, optional), type_map (object, optional)
Access: read on Jobs
Hints: readOnly=false, destructive=false, idempotent=true, openWorld=false
Try asking: "Using HeadHoncho, preview import with company_id cmp_14f5 and plan [{"staging_id": "k3Zp...", "half": "jobs", "mapping": {"ref": [0], "client_name": [1]}}]."
```
