Classify transactions

This page covers the classification of ledger transactions: deciding whether a row is business, personal, or mixed, adding the category and tax fields a calculation needs, recording mixed-use shares, batch and rule-based classification, and the review queue that lists everything still waiting for a decision. Use it after transactions are in the active profile’s ledger - imported rows have dates and amounts, but they do not yet say how the tax calculation should treat them.

Classifying a transaction changes only the record on your computer. It sends nothing to Spain’s Tax Agency (AEAT).

Before you start

You need:

  • An active taxpayer profile. Every command below works on the active profile; if none is set, the command refuses. See Set up your taxpayer profile.

  • A master-key passphrase. The tool prompts for it the first time it opens your encrypted storage in a session.

  • A ledger with transactions in it. See Import and manage transactions to import a bank statement or add rows by hand.

The CLI help and error text render in Spanish, even though this guide is in English. When a step sends you to --help, expect Spanish option names.

Review the row first

Find the transaction id, then inspect the row before you classify it. The sequence imports the standard quarter and inspects the unclassified expense:

1List the rows that still need a decision.
aeat app ledger list --filter classification=NOT_YET_PROCESSED
aeat app ledger list --filter classification=NOT_YET_PROCESSED
ACCOUNTING LEDGER TRANSACTIONS
id	full_id	date	amount	description	IVA category	review_status
71a5db2b	71a5db2bb891df295bee72544c40497d24d5c8cfc7edab745ce7339dcf8d6518	2026-02-10	1210	Cobro factura F-2026-001 servicios de consultoria		pending
e3eeac5e	e3eeac5e306efe4e82a72896e92e7c236245ec85348834b2c10b120c67fe087d	2026-02-11	605	Compra de material de oficina proveedor local		pending
2Inspect the row before changing it.
aeat --format json app ledger view e3eeac5e
aeat --format json app ledger view e3eeac5e
{
  "active_profile": "docs-sequence-sandbox",
  "command": "ledger.view",
  "notices": [],
  "result": {
    "bucket_id": "<bucket-id>",
    "review_status": "pending",
    "transaction": {
      "amount": "605",
      "attachment_ids": [],
      "booked_date": "2026-02-11",
      "business_classification": "NOT_YET_PROCESSED",
      "business_pct": null,
      "category_id": null,
      "classification_confidence": null,
      "classification_reason": "",
      "classified_at": null,
      "classified_by": "auto",
      "counterparty": "",
      "counterparty_country": null,
      "counterparty_identification_state": null,
      "created_at": "2026-04-01T09:00:00+00:00",
      "currency": "EUR",
      "date": "2026-02-11",
      "description": "Compra de material de oficina proveedor local",
      "direction": "OUTGOING",
      "fx_rate": null,
      "invoice_id": null,
      "irpf_category": null,
      "iva_amount": null,
      "iva_category": null,
      "iva_rate": null,
      "lifecycle_state": "ACTIVE",
      "m210_income_classification": null,
      "modified_at": "2026-04-01T09:00:00+00:00",
      "notes": "",
      "prorrata_reference": null,
      "purchase_invoice_evidence_id": null,
      "source_jurisdiction": null,
      "taxable_base": null,
      "transaction_id": "e3eeac5e306efe4e82a72896e92e7c236245ec85348834b2c10b120c67fe087d",
      "usage_ratio_id": null,
      "value_date": "2026-02-11",
      "value_in_eur": null
    },
    "transaction_id": "e3eeac5e306efe4e82a72896e92e7c236245ec85348834b2c10b120c67fe087d"
  },
  "schema_version": "2",
  "status": "success"
}

Confirm the row is still unclassified before you decide.

Use the description, amount, counterparty, source document, and business context to decide how to classify the row.

Choose the classification

Use one of the ledger classification states that command help accepts:

  • BUSINESS for a fully business-related transaction

  • PERSONAL for a personal transaction that should not feed tax calculations

  • MIXED for a transaction that is partly business and partly personal

Use only these three values. Cadrumo sets the others automatically.

Pick a category for expenses

List the accepted category ids, then classify an expense row with one. Expense rows normally need a category id before a modelo can calculate from them:

1List the accepted category ids.
aeat app ledger categories
aeat app ledger categories
RECOGNISED LEDGER SPENDING CATEGORIES
category-id (pass this exact value)	family
cuotas_colegiales	social_security
cuotas_autonomos_ss	social_security
mutualidad_alternativa	social_security
arrendamiento_local	premises
ibi_local_afecto	premises
suministros_local_afecto	premises
suministros_home_office_luz	home_office_suministros
suministros_home_office_agua	home_office_suministros
suministros_home_office_gas	home_office_suministros
suministros_home_office_internet	home_office_suministros
telefonia_fija	home_office_suministros
amortizacion_vivienda_afecto	home_office_ownership
ibi_vivienda_afecto	home_office_ownership
comunidad_vivienda_afecto	home_office_ownership
arrendamiento_vivienda_afecto	home_office_ownership
telefonia_movil	telecoms
material_oficina	office
software_suscripcion	office
hardware_amortizable	office
mobiliario_amortizable	office
reparaciones_conservacion	office
publicidad_marketing	office
formacion_profesional	office
vehiculo_combustible	vehicle
vehiculo_mantenimiento	vehicle
vehiculo_seguro	vehicle
vehiculo_peaje	vehicle
vehiculo_parking	vehicle
manutencion_dietas_nacional	meals
manutencion_dietas_extranjero	meals
asesoria_fiscal	professional_services
asesoria_juridica	professional_services
asesoria_contable	professional_services
viajes_transporte	travel
viajes_alojamiento	travel
seguros_responsabilidad_civil	insurance
seguros_salud_autonomo	insurance
gastos_bancarios	financial
gastos_financieros	financial
suministros_cliente_directos	direct_costs
subcontratacion	direct_costs
tributos_fiscalmente_deducibles	taxes
Pass the bare id, e.g. --category-id cuotas_colegiales (not a family-prefixed or compound key).
Deductible expenses only. Income (INCOMING) transactions are classified by direction and need no --category-id; `ledger check` and `ledger preflight` do not flag income as missing_category.

IRPF CATEGORIES FOR --irpf-category
irpf-category (pass this exact value)	use
actividad_economica	professional activity withholding (received or paid net)
arrendamiento_local	net-paid rent expense withholding
arrendamiento_vivienda_afecto	net-paid rent expense withholding
trabajo	employment income (not for net-paid invoices)
Net-paid rent example: --category-id arrendamiento_local --irpf-category arrendamiento_local. Net-paid professional service: --category-id asesoria_fiscal --irpf-category actividad_economica.
2Classify the expense as business with a category.
aeat app ledger classify e3eeac5e --classification BUSINESS --category-id \
  material_oficina
aeat app ledger classify e3eeac5e --classification BUSINESS --category-id `
  material_oficina
ID	e3eeac5e306efe4e82a72896e92e7c236245ec85348834b2c10b120c67fe087d
Date	2026-02-11
Amount	605
Description	Compra de material de oficina proveedor local
Review status	reviewed
3Confirm the row now carries the business classification and category.
aeat --format json app ledger view e3eeac5e
aeat --format json app ledger view e3eeac5e
{
  "active_profile": "docs-sequence-sandbox",
  "command": "ledger.view",
  "notices": [],
  "result": {
    "bucket_id": "<bucket-id>",
    "review_status": "reviewed",
    "transaction": {
      "amount": "605",
      "attachment_ids": [],
      "booked_date": "2026-02-11",
      "business_classification": "BUSINESS",
      "business_pct": null,
      "category_id": "material_oficina",
      "classification_confidence": "1",
      "classification_reason": "aeat app ledger classify",
      "classified_at": "2026-04-01T09:00:00+00:00",
      "classified_by": "manual",
      "counterparty": "",
      "counterparty_country": null,
      "counterparty_identification_state": null,
      "created_at": "2026-04-01T09:00:00+00:00",
      "currency": "EUR",
      "date": "2026-02-11",
      "description": "Compra de material de oficina proveedor local",
      "direction": "OUTGOING",
      "fx_rate": null,
      "invoice_id": null,
      "irpf_category": null,
      "iva_amount": null,
      "iva_category": null,
      "iva_rate": null,
      "lifecycle_state": "ACTIVE",
      "m210_income_classification": null,
      "modified_at": "2026-04-01T09:00:00+00:00",
      "notes": "",
      "prorrata_reference": null,
      "purchase_invoice_evidence_id": null,
      "source_jurisdiction": null,
      "taxable_base": null,
      "transaction_id": "e3eeac5e306efe4e82a72896e92e7c236245ec85348834b2c10b120c67fe087d",
      "usage_ratio_id": null,
      "value_date": "2026-02-11",
      "value_in_eur": null
    },
    "transaction_id": "e3eeac5e306efe4e82a72896e92e7c236245ec85348834b2c10b120c67fe087d"
  },
  "schema_version": "2",
  "status": "success"
}

Confirm the expense is classified as business with a category.

For money you received (income), Cadrumo does not usually need a category. It calculates income totals automatically.

Use OUTGOING plus an expense category for supplier purchases and other deductible expenses. Use INCOMING for issued invoices, client payments, or services rendered to customers. If you also track invoice records separately, use aeat app ledger invoice with --kind received for supplier invoices and --kind issued for customer invoices.

Add tax fields when needed

If a row needs regulated tax fields, add only the fields that apply. The sequence classifies the expense with its taxable base, rate, and IVA amount:

1Classify the expense with the IVA breakdown from the invoice.
aeat app ledger classify e3eeac5e --classification BUSINESS --category-id \
  material_oficina --taxable-base 500.00 --iva-rate 0.21 --iva-amount 105.00
aeat app ledger classify e3eeac5e --classification BUSINESS --category-id `
  material_oficina --taxable-base 500.00 --iva-rate 0.21 --iva-amount 105.00
ID	e3eeac5e306efe4e82a72896e92e7c236245ec85348834b2c10b120c67fe087d
Date	2026-02-11
Amount	605
Description	Compra de material de oficina proveedor local
Review status	reviewed
2Confirm the taxable base and IVA fields were recorded.
aeat --format json app ledger view e3eeac5e
aeat --format json app ledger view e3eeac5e
{
  "active_profile": "docs-sequence-sandbox",
  "command": "ledger.view",
  "notices": [],
  "result": {
    "bucket_id": "<bucket-id>",
    "review_status": "reviewed",
    "transaction": {
      "amount": "605",
      "attachment_ids": [],
      "booked_date": "2026-02-11",
      "business_classification": "BUSINESS",
      "business_pct": null,
      "category_id": "material_oficina",
      "classification_confidence": "1",
      "classification_reason": "aeat app ledger classify",
      "classified_at": "2026-04-01T09:00:00+00:00",
      "classified_by": "manual",
      "counterparty": "",
      "counterparty_country": null,
      "counterparty_identification_state": null,
      "created_at": "2026-04-01T09:00:00+00:00",
      "currency": "EUR",
      "date": "2026-02-11",
      "description": "Compra de material de oficina proveedor local",
      "direction": "OUTGOING",
      "fx_rate": null,
      "invoice_id": null,
      "irpf_category": null,
      "iva_amount": "105",
      "iva_category": null,
      "iva_rate": "0.21",
      "lifecycle_state": "ACTIVE",
      "m210_income_classification": null,
      "modified_at": "2026-04-01T09:00:00+00:00",
      "notes": "",
      "prorrata_reference": null,
      "purchase_invoice_evidence_id": null,
      "source_jurisdiction": null,
      "taxable_base": "500",
      "transaction_id": "e3eeac5e306efe4e82a72896e92e7c236245ec85348834b2c10b120c67fe087d",
      "usage_ratio_id": null,
      "value_date": "2026-02-11",
      "value_in_eur": null
    },
    "transaction_id": "e3eeac5e306efe4e82a72896e92e7c236245ec85348834b2c10b120c67fe087d"
  },
  "schema_version": "2",
  "status": "success"
}

Confirm the taxable base and IVA fields were recorded on the row.

Common fields include taxable base, IVA rate, IVA amount, IVA category, IRPF category, and counterparty EU member state for intracommunity IVA cases. Use aeat app ledger classify --help for the exact current option list.

For ordinary domestic IVA, use the taxable base, rate, and amount shown by the invoice. For example, a EUR 121.00 purchase with 21 percent IVA usually has --taxable-base 100.00 --iva-rate 0.21 --iva-amount 21.00.

Most purchases at the standard 21% rate need no --iva-category. Add it only for special cases: reduced rate (food, books), exempt supplies, purchases from EU suppliers, or recargo de equivalencia. Run aeat app ledger classify --help to see the accepted values.

Classify mixed-use transactions

A mixed-use transaction is one you use partly for business and partly personally, such as a phone bill, a car cost, or a home-office expense. Record the business share so the calculation counts only the deductible part. The split can come from three places, in rising order of grounding: a percentage on a single record, a saved default ratio for a whole category, or a ratio worked out from your registered facts (for example, the registered home-office area against the size of your home). All three keep only the part of a cost that genuinely belongs to the business.

A MIXED row needs a proportionality reference before a modelo can calculate from it. The reference is a saved category ratio applied through --usage-ratio-id. A bare --business-pct records a percentage but does not make the row ready; preflight still reports missing_proportionality_reference until the row carries --usage-ratio-id.

For one-row commands, --usage-ratio-id lives on the allocate verb (and on add), not on classify. Its value is the spending-category id, and the same category must already have a saved ratio. The sequence checks the eligible categories, saves a ratio, and allocates the share on a row:

1Check which categories accept a ratio.
aeat app ledger ratios eligible
aeat app ledger ratios eligible
bucket	<profile-id>
count	15
amortizacion_vivienda_afecto	usage_ratio_home_area	default=-	override=.
arrendamiento_vivienda_afecto	usage_ratio_home_area	default=-	override=.
comunidad_vivienda_afecto	usage_ratio_home_area	default=-	override=.
ibi_vivienda_afecto	usage_ratio_home_area	default=-	override=.
suministros_home_office_agua	usage_ratio_home_area	default=-	override=.
suministros_home_office_gas	usage_ratio_home_area	default=-	override=.
suministros_home_office_internet	usage_ratio_home_area	default=-	override=.
suministros_home_office_luz	usage_ratio_home_area	default=-	override=.
telefonia_fija	usage_ratio_home_area	default=-	override=.
telefonia_movil	usage_ratio_personal	default=-	override=.
vehiculo_combustible	usage_ratio_personal	default=-	override=.
vehiculo_mantenimiento	usage_ratio_personal	default=-	override=.
vehiculo_parking	usage_ratio_personal	default=-	override=.
vehiculo_peaje	usage_ratio_personal	default=-	override=.
vehiculo_seguro	usage_ratio_personal	default=-	override=.
2Save the ratio for the category, as a percentage from 0 to 1.
aeat app ledger ratios set telefonia_movil 0.5
aeat app ledger ratios set telefonia_movil 0.5
bucket	<profile-id>
telefonia_movil	0.5
3Allocate the share on the row, naming the same category for both flags.
aeat app ledger allocate {transaction_id} --business-pct 0.5 --usage-ratio-id \
  telefonia_movil --category-id telefonia_movil
aeat app ledger allocate {transaction_id} --business-pct 0.5 --usage-ratio-id `
  telefonia_movil --category-id telefonia_movil
ID	80f1bbebb9176b41674513c8a1c593f2ba8e6304d6fa0a9bfe104dd2f081a73e
Date	2026-03-15
Amount	60.5
Description	Factura movil
Review status	reviewed
4Confirm the row is now mixed with the allocated share.
aeat --format json app ledger view {transaction_id}
aeat --format json app ledger view {transaction_id}
{
  "active_profile": "docs-sequence-sandbox",
  "command": "ledger.view",
  "notices": [],
  "result": {
    "bucket_id": "<bucket-id>",
    "review_status": "reviewed",
    "transaction": {
      "amount": "60.5",
      "attachment_ids": [],
      "booked_date": "2026-03-15",
      "business_classification": "MIXED",
      "business_pct": "0.5",
      "category_id": "telefonia_movil",
      "classification_confidence": "1",
      "classification_reason": "aeat app ledger allocate",
      "classified_at": "2026-04-01T09:00:00+00:00",
      "classified_by": "manual",
      "counterparty": "",
      "counterparty_country": null,
      "counterparty_identification_state": null,
      "created_at": "2026-04-01T09:00:00+00:00",
      "currency": "EUR",
      "date": "2026-03-15",
      "description": "Factura movil",
      "direction": "OUTGOING",
      "fx_rate": null,
      "invoice_id": null,
      "irpf_category": null,
      "iva_amount": null,
      "iva_category": null,
      "iva_rate": null,
      "lifecycle_state": "ACTIVE",
      "m210_income_classification": null,
      "modified_at": "2026-04-01T09:00:00+00:00",
      "notes": "",
      "prorrata_reference": null,
      "purchase_invoice_evidence_id": null,
      "source_jurisdiction": "ES",
      "taxable_base": null,
      "transaction_id": "80f1bbebb9176b41674513c8a1c593f2ba8e6304d6fa0a9bfe104dd2f081a73e",
      "usage_ratio_id": "telefonia_movil",
      "value_date": null,
      "value_in_eur": null
    },
    "transaction_id": "80f1bbebb9176b41674513c8a1c593f2ba8e6304d6fa0a9bfe104dd2f081a73e"
  },
  "schema_version": "2",
  "status": "success"
}

Confirm the mixed-use row carries the allocated business share.

A row’s --usage-ratio-id must name a ratio-eligible category (the ones ratios eligible lists, such as telefonia_movil or a home-office suministros category), and the same category is passed as --category-id. The --business-pct value must match the saved ratio for that category. The classification follows the share automatically: a 0.5 allocation becomes MIXED, a 1 allocation becomes BUSINESS, and a 0 allocation becomes PERSONAL.

List or check the saved ratios at any time. The sequence saves a ratio, lists the saved ratios, and validates them:

1Save a ratio for a ratio-eligible category.
aeat app ledger ratios set telefonia_movil 0.5
aeat app ledger ratios set telefonia_movil 0.5
bucket	<profile-id>
telefonia_movil	0.5
2List the saved ratios.
aeat app ledger ratios list
aeat app ledger ratios list
bucket	<profile-id>
count	1
telefonia_movil	0.5
3Confirm the saved ratios are internally consistent.
aeat --format json app ledger ratios validate
aeat --format json app ledger ratios validate
{
  "active_profile": "docs-sequence-sandbox",
  "command": "ledger.ratios.validate",
  "notices": [],
  "result": {
    "bucket_id": "<bucket-id>",
    "eligible_count": 15,
    "findings": [],
    "missing_overrides": [],
    "overrides_count": 1,
    "profile_present": true
  },
  "schema_version": "2",
  "status": "success"
}

Confirm the saved ratios validate cleanly.

Remove a category ratio you no longer want with aeat app ledger ratios unset <category-id>.

For home-office expenses, save a ratio for the relevant home-office category and allocate as above. Home-office ratios follow the manual or saved ratio workflow; keep the census facts in your profile correct first - see Maintain Modelo 036 census facts in your profile.

Classify many rows from CSV

For bulk review, classify --file reads a CSV with columns transaction_id, classification, and optional classification facts such as category_id, business_pct, usage_ratio_id, taxable-base and IVA columns, iva_category, and irpf_category. Prepare a narrow CSV holding only the rows you mean to change, one row per transaction:

transaction_id,classification,category_id,business_pct,usage_ratio_id
<business-expense-id>,BUSINESS,<category-id>,,
<mixed-expense-id>,MIXED,<category-id>,0.5,<category-id>
<private-row-id>,PERSONAL,,,

The sequence below imports the quarter, lists the rows still needing a decision, exports a review snapshot to work from, applies a prepared CSV, and confirms the result:

1Select the rows still needing a decision.
aeat app ledger list --filter period=1T --filter year=2026 --filter \
  classification=NOT_YET_PROCESSED
aeat app ledger list --filter period=1T --filter year=2026 --filter `
  classification=NOT_YET_PROCESSED
ACCOUNTING LEDGER TRANSACTIONS
id	full_id	date	amount	description	IVA category	review_status
71a5db2b	71a5db2bb891df295bee72544c40497d24d5c8cfc7edab745ce7339dcf8d6518	2026-02-10	1210	Cobro factura F-2026-001 servicios de consultoria		pending
e3eeac5e	e3eeac5e306efe4e82a72896e92e7c236245ec85348834b2c10b120c67fe087d	2026-02-11	605	Compra de material de oficina proveedor local		pending
2Export the period as a review snapshot to work from.
aeat app ledger export --output ./ledger-2026-q1.csv --year 2026 --period 1T
aeat app ledger export --output ./ledger-2026-q1.csv --year 2026 --period 1T
Bucket	<profile-id>
Export ID	5f0ddcff2e977990b1b5fd3fc6e107cf9b3cb3b5f7ac82e87d072c77fed11cf4
Rows	2
SHA-256	0becceaa2ef5f6edba6a89f5fee123b09f1a044d477005dd381cecb52f66213d
Output	ledger-2026-q1.csv
3Apply the prepared classification CSV to the quarter's rows.
aeat app ledger classify --file fixtures/classifications-2026-1t.csv
aeat app ledger classify --file fixtures/classifications-2026-1t.csv
bulk classify: 2 rows, 2 applied, 0 skipped, 0 failed
4Confirm the expense row is now classified as business.
aeat --format json app ledger view e3eeac5e
aeat --format json app ledger view e3eeac5e
{
  "active_profile": "docs-sequence-sandbox",
  "command": "ledger.view",
  "notices": [],
  "result": {
    "bucket_id": "<bucket-id>",
    "review_status": "reviewed",
    "transaction": {
      "amount": "605",
      "attachment_ids": [],
      "booked_date": "2026-02-11",
      "business_classification": "BUSINESS",
      "business_pct": null,
      "category_id": "material_oficina",
      "classification_confidence": "1",
      "classification_reason": "aeat app ledger classify --file",
      "classified_at": "2026-04-01T09:00:00+00:00",
      "classified_by": "manual",
      "counterparty": "",
      "counterparty_country": null,
      "counterparty_identification_state": null,
      "created_at": "2026-04-01T09:00:00+00:00",
      "currency": "EUR",
      "date": "2026-02-11",
      "description": "Compra de material de oficina proveedor local",
      "direction": "OUTGOING",
      "fx_rate": null,
      "invoice_id": null,
      "irpf_category": null,
      "iva_amount": null,
      "iva_category": null,
      "iva_rate": null,
      "lifecycle_state": "ACTIVE",
      "m210_income_classification": null,
      "modified_at": "2026-04-01T09:00:00+00:00",
      "notes": "",
      "prorrata_reference": null,
      "purchase_invoice_evidence_id": null,
      "source_jurisdiction": null,
      "taxable_base": null,
      "transaction_id": "e3eeac5e306efe4e82a72896e92e7c236245ec85348834b2c10b120c67fe087d",
      "usage_ratio_id": null,
      "value_date": "2026-02-11",
      "value_in_eur": null
    },
    "transaction_id": "e3eeac5e306efe4e82a72896e92e7c236245ec85348834b2c10b120c67fe087d"
  },
  "schema_version": "2",
  "status": "success"
}

Confirm the CSV batch classified the quarter's rows.

Use the CSV path when filtered review shows many rows you can classify safely from their descriptions, counterparties, and source documents. Keep a copy of the file. It gives you a record of how you classified that period if you are later asked to justify your return. This path does not batch-update amounts, descriptions, IVA values, notes, attachments, or split/merge state; use the transaction workflow for those row-level edits.

Apply stored rules automatically

Rules automatically classify transactions whose description contains a word or phrase you specify. Matching ignores uppercase and lowercase differences. The sequence adds a rule, lists the rules, previews the effect, then applies it:

1Add a rule that classifies matching descriptions.
aeat app ledger rule add --description-pattern material --classification BUSINESS \
  --category-id material_oficina
aeat app ledger rule add --description-pattern material --classification BUSINESS `
  --category-id material_oficina
rule_id	16719284a6b5e971...
pattern	material
classification	BUSINESS
priority	100
2List the stored rules.
aeat app ledger rule list
aeat app ledger rule list
priority	classification	pattern	rule_id
100	BUSINESS	material	16719284a6b5e971...
3Preview the effect without changing any rows.
aeat app ledger rule apply --dry-run
aeat app ledger rule apply --dry-run
dry-run: 0 transaction(s) would be classified
4Apply the rule to matching rows.
aeat --format json app ledger rule apply
aeat --format json app ledger rule apply
{
  "active_profile": "docs-sequence-sandbox",
  "command": "ledger.rule.apply",
  "notices": [],
  "result": {
    "applied": [],
    "count": null,
    "dry_run": null,
    "matched": 0,
    "no_match": 0,
    "rules_evaluated": 1,
    "skipped_already_classified": 0,
    "transactions_scanned": 0,
    "would_match": null
  },
  "schema_version": "2",
  "status": "success"
}

Confirm the stored rule is registered and applies cleanly.

Run --dry-run first. Add --reaffirm only if you want the rule to overwrite classifications you already set by hand.

Use an LLM suggestion

Cadrumo can use a configured language model to suggest how to classify each transaction. The suggestion is a starting point. You must confirm or correct it. It does not fill in tax amounts such as taxable base, IVA rate, or IRPF category.

Use Classify transactions with an LLM for the full provider, preview, apply, and override flow.

See everything that still needs a decision

The review queue is one profile-wide list of everything that still wants your attention before a filing: transactions without a classification, invoice records that are unmatched or disputed, and verification findings on modelo drafts. Each row names the exact command that resolves it, so the queue is a to-do list you can work through top to bottom. The queue is read-only; items clear when you fix the underlying record with the command the row names. The sequence imports the quarter, then reads the queue:

1Read the profile-wide review queue.
aeat --format json app review queue
aeat --format json app review queue
{
  "active_profile": "docs-sequence-sandbox",
  "command": "review.queue",
  "notices": [],
  "result": {
    "operation": "review.queue",
    "rows": [
      {
        "affected_object_id": "71a5db2bb891df295bee72544c40497d24d5c8cfc7edab745ce7339dcf8d6518",
        "blocking": false,
        "bucket_id": "<bucket-id>",
        "canonical_next_command": "aeat app ledger review 71a5db2bb891df295bee72544c40497d24d5c8cfc7edab745ce7339dcf8d6518",
        "current_owner_surface": "app ledger",
        "item_id": "71a5db2bb891df295bee72544c40497d24d5c8cfc7edab745ce7339dcf8d6518",
        "kind": "ledger_transaction",
        "legal_refs": [],
        "modelo": null,
        "period": "2026-02",
        "reason": "Transaction pending classification",
        "severity": "normal",
        "since": "2026-02-10T00:00:00+00:00",
        "source_kind": "ledger_transaction",
        "state": "pending",
        "summary": "Transaction pending classification"
      },
      {
        "affected_object_id": "e3eeac5e306efe4e82a72896e92e7c236245ec85348834b2c10b120c67fe087d",
        "blocking": false,
        "bucket_id": "<bucket-id>",
        "canonical_next_command": "aeat app ledger review e3eeac5e306efe4e82a72896e92e7c236245ec85348834b2c10b120c67fe087d",
        "current_owner_surface": "app ledger",
        "item_id": "e3eeac5e306efe4e82a72896e92e7c236245ec85348834b2c10b120c67fe087d",
        "kind": "ledger_transaction",
        "legal_refs": [],
        "modelo": null,
        "period": "2026-02",
        "reason": "Transaction pending classification",
        "severity": "normal",
        "since": "2026-02-11T00:00:00+00:00",
        "source_kind": "ledger_transaction",
        "state": "pending",
        "summary": "Transaction pending classification"
      }
    ]
  },
  "schema_version": "2",
  "status": "success"
}

Confirm the review queue lists the pending work.

Each row shows the item id, its kind, the affected record, the period, a severity (critical, high, normal, or info), and a final column with the command to run next. Narrow the list by kind, modelo, or state:

1Narrow the queue to ledger-transaction items.
aeat app review queue --kind ledger_transaction
aeat app review queue --kind ledger_transaction
ID	Kind	Source kind	Object	Period	Severity	Next
71a5db2bb891df295bee72544c40497d24d5c8cfc7edab745ce7339dcf8d6518	ledger_transaction	ledger_transaction	71a5db2bb891df295bee72544c40497d24d5c8cfc7edab745ce7339dcf8d6518	2026-02	normal	aeat app ledger review 71a5db2bb891df295bee72544c40497d24d5c8cfc7edab745ce7339dcf8d6518
e3eeac5e306efe4e82a72896e92e7c236245ec85348834b2c10b120c67fe087d	ledger_transaction	ledger_transaction	e3eeac5e306efe4e82a72896e92e7c236245ec85348834b2c10b120c67fe087d	2026-02	normal	aeat app ledger review e3eeac5e306efe4e82a72896e92e7c236245ec85348834b2c10b120c67fe087d
2Narrow the queue to modelo findings for one modelo.
aeat app review queue --kind modelo_finding --modelo 303
aeat app review queue --kind modelo_finding --modelo 303
ID	Kind	Source kind	Object	Period	Severity	Next
No items pending review.
3Show every state, not only pending items.
aeat --format json app review queue --state all
aeat --format json app review queue --state all
{
  "active_profile": "docs-sequence-sandbox",
  "command": "review.queue",
  "notices": [],
  "result": {
    "operation": "review.queue",
    "rows": [
      {
        "affected_object_id": "71a5db2bb891df295bee72544c40497d24d5c8cfc7edab745ce7339dcf8d6518",
        "blocking": false,
        "bucket_id": "<bucket-id>",
        "canonical_next_command": "aeat app ledger review 71a5db2bb891df295bee72544c40497d24d5c8cfc7edab745ce7339dcf8d6518",
        "current_owner_surface": "app ledger",
        "item_id": "71a5db2bb891df295bee72544c40497d24d5c8cfc7edab745ce7339dcf8d6518",
        "kind": "ledger_transaction",
        "legal_refs": [],
        "modelo": null,
        "period": "2026-02",
        "reason": "Transaction pending classification",
        "severity": "normal",
        "since": "2026-02-10T00:00:00+00:00",
        "source_kind": "ledger_transaction",
        "state": "all",
        "summary": "Transaction pending classification"
      },
      {
        "affected_object_id": "e3eeac5e306efe4e82a72896e92e7c236245ec85348834b2c10b120c67fe087d",
        "blocking": false,
        "bucket_id": "<bucket-id>",
        "canonical_next_command": "aeat app ledger review e3eeac5e306efe4e82a72896e92e7c236245ec85348834b2c10b120c67fe087d",
        "current_owner_surface": "app ledger",
        "item_id": "e3eeac5e306efe4e82a72896e92e7c236245ec85348834b2c10b120c67fe087d",
        "kind": "ledger_transaction",
        "legal_refs": [],
        "modelo": null,
        "period": "2026-02",
        "reason": "Transaction pending classification",
        "severity": "normal",
        "since": "2026-02-11T00:00:00+00:00",
        "source_kind": "ledger_transaction",
        "state": "all",
        "summary": "Transaction pending classification"
      }
    ]
  },
  "schema_version": "2",
  "status": "success"
}

Confirm the queue can be narrowed by kind.

Accepted --kind tokens are ledger_transaction, purchase_invoice_evidence, payable_invoice, collectible_invoice, and modelo_finding. The default state is pending. Inspect one item in full, including the suggested next command, with aeat app review view <item-id>. Modelo findings are grounded in registry rules; add --explain to show the legal references, or use the global --format json flag (before the command) for a legal_refs field on every row.

Transaction items clear when you classify the row (this page). Invoice items clear when you link or update the invoice - see Attach invoices and receipts. Modelo findings clear when you fix the reported values and verify again - see Verify a filing.

Confirm readiness

Run preflight after classification. The sequence imports and classifies the quarter, then runs preflight and reads the ledger status:

1Run preflight for the target period.
aeat app ledger preflight --year 2026 --period 1T
aeat app ledger preflight --year 2026 --period 1T
bucket	<profile-id>
period	2026 1T
checked	2
issues	0
ready	true
2Read the overall ledger state for the period.
aeat --format json app ledger status --year 2026 --period 1T
aeat --format json app ledger status --year 2026 --period 1T
{
  "active_profile": "docs-sequence-sandbox",
  "command": "ledger.status",
  "notices": [],
  "result": {
    "active_count": 2,
    "archived_count": 0,
    "bucket_id": "<bucket-id>",
    "business_expense_total": "605",
    "business_income_total": "1210",
    "business_net_total": "605",
    "checked_transaction_count": 2,
    "pending_review_count": 0,
    "period": {
      "code": "1T",
      "filing_year": 2026
    },
    "readiness_issue_count": 0,
    "readiness_issues": [],
    "ready": true,
    "reviewed_count": 2,
    "skipped_count": 0,
    "split_count": 0,
    "stale_filings": [],
    "stashed_count": 0,
    "total_count": 2,
    "unconverted_currency_count": 0
  },
  "schema_version": "2",
  "status": "success"
}

Confirm preflight reports the classified quarter's readiness.

Preflight names rows that still need category, taxable base, IVA amount, IVA rate, currency, or proportionality reference.

Correct a classification

Re-run classify on the same transaction id. A manual decision replaces the previous classification. The sequence classifies a row as business, then corrects it to personal:

1Re-classify the same row as personal.
aeat app ledger classify e3eeac5e --classification PERSONAL
aeat app ledger classify e3eeac5e --classification PERSONAL
ID	e3eeac5e306efe4e82a72896e92e7c236245ec85348834b2c10b120c67fe087d
Date	2026-02-11
Amount	605
Description	Compra de material de oficina proveedor local
Review status	reviewed
2Confirm the row now reads personal.
aeat --format json app ledger view e3eeac5e
aeat --format json app ledger view e3eeac5e
{
  "active_profile": "docs-sequence-sandbox",
  "command": "ledger.view",
  "notices": [],
  "result": {
    "bucket_id": "<bucket-id>",
    "review_status": "reviewed",
    "transaction": {
      "amount": "605",
      "attachment_ids": [],
      "booked_date": "2026-02-11",
      "business_classification": "PERSONAL",
      "business_pct": null,
      "category_id": null,
      "classification_confidence": "1",
      "classification_reason": "aeat app ledger classify",
      "classified_at": "2026-04-01T09:00:00+00:00",
      "classified_by": "manual",
      "counterparty": "",
      "counterparty_country": null,
      "counterparty_identification_state": null,
      "created_at": "2026-04-01T09:00:00+00:00",
      "currency": "EUR",
      "date": "2026-02-11",
      "description": "Compra de material de oficina proveedor local",
      "direction": "OUTGOING",
      "fx_rate": null,
      "invoice_id": null,
      "irpf_category": null,
      "iva_amount": null,
      "iva_category": null,
      "iva_rate": null,
      "lifecycle_state": "ACTIVE",
      "m210_income_classification": null,
      "modified_at": "2026-04-01T09:00:00+00:00",
      "notes": "",
      "prorrata_reference": null,
      "purchase_invoice_evidence_id": null,
      "source_jurisdiction": null,
      "taxable_base": null,
      "transaction_id": "e3eeac5e306efe4e82a72896e92e7c236245ec85348834b2c10b120c67fe087d",
      "usage_ratio_id": null,
      "value_date": "2026-02-11",
      "value_in_eur": null
    },
    "transaction_id": "e3eeac5e306efe4e82a72896e92e7c236245ec85348834b2c10b120c67fe087d"
  },
  "schema_version": "2",
  "status": "success"
}

Confirm the re-classification replaced the previous decision.

Inspect the row again with ledger view before calculating.

Next steps