---
title: "Import Bank Statement"
method: POST
path: "/api/autopilot/bank/import"
tags: ["autopilot"]
---

# Import Bank Statement

`POST /api/autopilot/bank/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/bank/import`
- Auth: HTTP bearer

Import one exported bank statement into the reconciliation inbox (AP-111).

Vendor-free by design: every Italian bank exports a CSV, so the first import
path is a file the user already has rather than an open-banking contract we
do not hold. The parser detects columns by *role* against alias sets, so an
export shape nobody has seen still imports as long as it carries a date and
an amount (or an entrate/uscite pair).

**Re-importing an overlapping statement is a no-op**, which is the property
that makes this usable at all — home banking has no stable per-row id and
every export overlaps the last one. The store's
``UNIQUE(user_id, booked_date, amount, details_hash)`` decides it with
``ON CONFLICT DO NOTHING``, so a second upload of the same month cannot
reset a ``match_status`` the user has already set. The response counts what
that produced instead of hiding it: ``already_present`` is how the UI says
"you have imported this before".

200 rather than 201: a re-import legitimately creates nothing, and the body
is a report about a file rather than a resource at a new URL.

A file with no recognisable header is a 422 carrying the parser's own
message, which names the columns it did find — the only form of "unsupported
export" that a user can act on. Individual unreadable rows are never a
failure: a running-balance footer is the normal case, and those come back
counted in ``skipped``.

Carries its own limiter rather than the shared write bucket: one call here is
a whole statement, so it is bounded per file (bytes, rows) *and* per minute.

## Response `200`

Successful Response

- BankImportSummary: ``POST /api/autopilot/bank/import`` — what one uploaded statement did. A one-to-one rendering of the store's ``ImportSummary``, and it keeps that dataclass's two separate duplicate counts rather than adding them up: ``duplicates_in_file`` is about the file the user picked (the same movement twice inside one export), ``already_present`` is about the statement they imported last month, and only the second one means "you can stop re-uploading this". ``skipped`` counts rows that carried no transaction at all — a totals footer, a blank line — which the parser reports rather than silently dropping.
  - `batch` string, uuid, required
  - `parsed` integer
  - `imported` integer
  - `already_present` integer
  - `duplicates_in_file` integer
  - `skipped` integer
  - `recorded` integer

## Other responses

- `413`: The uploaded file is larger than the byte cap
- `422`: The file has too many lines, or no recognisable statement 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)
