Backup Import
const url = 'https://example.com/api/v1/import/backup';const form = new FormData();form.append('file', 'example');form.append('sections', 'example');
const options = {method: 'POST'};
options.body = form;
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://example.com/api/v1/import/backup \ --header 'Content-Type: multipart/form-data' \ --form file=example \ --form sections=exampleImport a full JSON backup file with all account metadata and snapshots.
Single-step import (no parse/commit split) — JSON is already structured. Matches accounts by (name, institution) pair. Creates missing accounts with full metadata (type, color, category, is_liability, etc.). Updates existing records with backup values (upsert mode).
Optional sections form field (comma-separated section names)
restricts the import to only those sections. Omitting it imports
everything present in the file. Unknown section names are rejected
with a 422 (import_unknown_backup_section).
Request Bodyrequired
Section titled “Request Bodyrequired”Responses
Section titled “Responses”Successful Response
Response from the /api/import/commit endpoint.
partial success with summary. Import proceeds even if some snapshots fail — the summary reports counts and error details.
records_updated: count of existing records whose fields were updated from the backup (institutions, categories, accounts, snapshots, etc.).
sections/duplicate_details: a NEW, independent per-section breakdown
computed alongside the existing aggregate counters above — the
aggregate counters’ existing behavior is unchanged for backward
compatibility (purely additive, non-breaking response shape). The new
per-section entries additionally distinguish a true no-op duplicate
(skipped_duplicate — matched an existing row whose fields are already
identical) from a genuine update (updated — matched an existing row
where at least one field changed), a distinction the legacy aggregate
records_updated counter does not make.
categorized_from_payee: count of imported rows that took their category from the payee’s default category, counted only once the row was stored.
object
Error detail for a single failed import row.
section: which importable section this error belongs to (mirrors ImportDuplicate.section) – lets the frontend filter error_details per section when rendering the per-section breakdown.
object
Per-section created/updated/skipped/error breakdown for one of the 13 importable backup sections.
object
Detail for a single row skipped as a true no-op duplicate.
Mirrors ImportError’s field shape, plus section (which importable
section this row belongs to) and description — a human-readable
identifier for the skipped record (e.g. “Transaction 2026-01-15 −
45.00€”), analogous in spirit to ImportError.error but describing what
was skipped rather than what failed.
object
A categorization rule whose regex condition ran out of time during an import and was skipped for the rest of that run (the rule itself is unchanged).
object
Example
{ "accounts_created": 0, "institutions_imported": 0, "snapshots_imported": 0, "duplicates_skipped": 0, "records_updated": 0, "errors": 0, "sections": [ { "created": 0, "updated": 0, "skipped_duplicate": 0, "errors": 0 } ], "categorized_from_payee": 0}Validation Error
object
object
object
Example generated
{ "detail": [ { "loc": [ "example" ], "msg": "example", "type": "example", "input": "example", "ctx": {} } ]}