---
title: "Import Pre Signup Invoices"
method: POST
path: "/api/autopilot/import"
tags: ["autopilot"]
---

# Import Pre Signup Invoices

`POST /api/autopilot/import`

## Request

- Base URL: `https://api.taxcompass.it` (the document declares no server; this is the origin it was published from)
- URL: `https://api.taxcompass.it/api/autopilot/import`
- Auth: HTTP bearer

Read a pre-signup invoice list, and on ``commit=true`` write it (AP-095b).

Someone signing up in September has already issued invoices this year; until
the Cassetto Fiscale delega exists (AP-101) the file they have is a CSV their
commercialista or their spreadsheet exported. Without that history the
cash-basis estimate, the acconto and the Redditi quadro LM are all short by a
year's revenue, so this is the first thing mid-year onboarding asks for.

**Two modes, one path and one response shape.** ``commit=false`` (the
default) parses and returns: every importable row, and every row that failed
with the line and column that failed it, so the user sees what will land
before anything does. ``commit=true`` parses the same way and then writes,
adding the store's summary — what was imported, what it collected, and the
document numbers the year already held. A preview reads *nothing* from the
database: the parser takes bytes and returns rows, so what the user is shown
is reproducible from the file alone, and the only DB statements this route
can make are the store's own user-scoped writes.

Failures follow the bank import's shapes, because they are the same failures:
a file whose header carries no number / date / client / amount column is a 422
quoting the parser's message (which names the columns it *did* find, the only
form of "unsupported export" a user can act on), an oversized upload a 413,
and too many lines a 422 — both bounds applied before the parser walks a byte.
Individual bad rows are never a failure of the file: they come back in
``errors`` for the preview to show.

200 rather than 201: a re-uploaded file legitimately writes nothing (the
``UNIQUE(user_id, year, number)`` constraint stops a number already on file,
and those come back in ``summary.skipped``), and the body is a report about a
file rather than a resource at a new URL.

Carries the import limiter rather than the shared write bucket, for the
reason AP-099 parked both limits here: one call is a whole year of documents,
so it is bounded per file (bytes, rows) *and* per minute, more tightly than a
single-row mutation.

## Query parameters

- `commit` boolean

## Response `200`

Successful Response

- InvoiceImportPreview: ``POST /api/autopilot/import`` — what one uploaded invoice list holds. One shape for both modes. A preview (``commit=false``) carries the rows and every error with ``summary`` unset, because nothing was written; a commit carries the same rows *and* the store's :class:`InvoiceImportSummary`, so the import report the UI shows is the file it just read plus what the write actually produced — the two differ whenever a number was already on file. ``columns`` is the role → header mapping the parser matched, which is how a user whose export uses unexpected names can see that "Totale documento" was read as the amount.
  - `columns` object
  - `invoices` ImportedInvoiceRow[]
    - `row` integer, required
    - `number` string, required
    - `sequence` integer, required
    - `issue_date` string, date, required
    - `client_name` string, required
    - `amount` string, required
    - `paid_date` string, date, nullable
    - `ateco` string, nullable
    - `doc_type` string
  - `errors` InvoiceImportRowError[]
    - `row` integer, required
    - `column` string, required
    - `message` string, required
  - `summary` InvoiceImportSummary: What one pre-signup import wrote (AP-095), as the store reports it. ``skipped`` carries the document numbers the year already held: the ``UNIQUE(user_id, year, number)`` constraint is what makes a re-uploaded file a no-op instead of a duplicate history, and a row it stopped is reported rather than silently dropped — the same posture as the parser's :class:`~app.services.invoice_import.RowError`, which the preview shows. ``collected`` is the cash the import added to the ledger — the payments behind the ``incassi`` count, which is what moves the estimate.
    - `imported` integer
    - `incassi` integer
    - `collected` string
    - `skipped` string[]

## Other responses

- `413`: The uploaded file is larger than the byte cap
- `422`: The file has too many lines, or no recognisable invoice header

---

[API](https://skmtc.dev/taxcompass/apis/taxcompass-api.md) · [All operations](https://skmtc.dev/taxcompass/apis/taxcompass-api/llms.txt) · [OpenAPI document](https://skmtc.dev/taxcompass/apis/taxcompass-api/revisions/fef5fc6a9b91?raw)
