API conventions

Cross-cutting rules that apply to all endpoints. Knowing them avoids the most common mistakes when writing an agent or a script.

Money

Amounts are in decimal euros, not cents. One thousand two hundred euros is 1200.00, not 120000. Use a decimal point and two decimals where appropriate.

{ "base": 1000.00, "iva": 210.00, "total": 1210.00 }

Dates

Dates are in ISO-8601 (YYYY-MM-DD, for example 2026-07-08). Timestamps follow the same standard. Don't use local formats like 08/07/2026.

Identifiers

Resource IDs are UUIDs (for example 3f2a…). This holds for invoices, purchases, treasuries, movements, documents and tax types. On invoice/purchase lines, the tax is referenced by its tax_type_id (a UUID you get from /api/v1/taxes).

Pagination and filters

List endpoints accept these query parameters:

ParameterWhat it does
limitMaximum number of items to return.
offsetOffset (for paging).
fromRange start date (ISO-8601).
toRange end date (ISO-8601).

Example:

curl "https://api.aikount.com/api/v1/bank-movements?treasury_id=<uuid>&from=2026-04-01&to=2026-06-30&limit=50" \
  -H "Authorization: Bearer agl_your_key_here"

Errors

An error arrives as an HTTP status plus a JSON body with the detail key:

{ "detail": "..." }

The most common authentication ones are 401 (token missing, invalid or revoked) and 403 (valid token lacking the required scope). See Authentication.

detail can be a string or an object with at least code (a stable identifier to program against) and message (text to show a person). Always handle both: when detail is an object, display detail.message.

{ "detail": { "code": "period_closed", "reason": "vat_settled", "message": "El 3T 2026 tiene la liquidación del IVA contabilizada…", "year": 2026, "quarter": 3, "side": "sale", "alternatives": [ … ] } }
- **Stale VAT settlements.** `GET /taxes/vat-settlement-alerts` lists posted settlements that no longer match their documents (`stale`, with `difference`) or were posted before the quarter ended (`premature`), with an `action` (`redo` | `regularize` | `revert`). Exporting the 303 (`/aeat/303`, `/aeat/303/{year}/{quarter}/export`, `/taxes/model-303/{year}/{quarter}/export`) or marking/uploading it as filed while its settlement is stale returns `409 vat_settlement_stale` unless `?confirm_stale_settlement=true`.
- **Issued invoices → not deleted.** `DELETE /invoices/{id}` and `POST /invoices/bulk-delete` only delete an issued invoice when it is the **last of its series**, has not been sent or collected (nor shared by public link) and is not registered in VERI*FACTU (its number is freed, no gap). Otherwise `409` with `code: "issued_invoice_not_deletable"`, `reason` (`not_last` | `sent` | `verifactu` | `filed_period`) and `alternatives` (`rectify` → `POST /invoices/{id}/rectify`; `cancel` when it was issued by mistake and has no collections). `force` no longer bypasses this rule (RD 1619/2012 arts. 6 and 15; RD 1007/2023 art. 8).

Recent API changes (September 2026)

  • Settled quarters → 409 period_closed. Creating, editing, voiding or deleting a sale (invoice, receipt, corrective invoice) dated in a quarter whose VAT is already settled (or whose 303 is filed) returns 409, and detail changes from a string to an object (code: "period_closed", reason, message, year, quarter, entry_number, filed_303, side, alternatives). For purchases the 409 carries requires_confirmation: true and confirm_param: "confirm_settled_period": the normal path is to record it with its date and deduct its VAT in the open period (art. 99 LIVA): repeat the call with vat_deduction_date (the 409 carries deduction_param and suggested_vat_deduction_date; on POST /purchases/{id}/issue it is a query param, on POST/PATCH /purchases a body field). The settled quarter does not change. To deduct it in the original quarter instead, repeat with ?confirm_settled_period=true (the settlement becomes stale). Purchases with self-assessed VAT (reverse charge, intra-EU, deferred import) or under the cash-accounting scheme do not accept vat_deduction_date (422). Only a quarter that has ended counts: a settlement made before the quarter ends does not close it (it shows as premature in GET /taxes/vat-settlement/{year}/{quarter}).
  • Filed periods → never change. If a period has a VAT-family return filed (303, 369, 349, 390, IGIC), its sales cannot be created, edited (amounts, taxes, date, customer), voided or deleted, and no confirmation overrides it: 409 with code: "period_closed", reason: "period_filed", models and requires_confirmation: false; correct it with a corrective invoice dated today. A purchase dated in a period whose VAT return is filed is recorded with its date and its VAT is deducted today (automatic vat_deduction_date, art. 99 LIVA); confirm_settled_period no longer applies to filed periods. If the purchase was already included in the filed return (history, accountant), post it with declared_in_filed_period=true (query of POST /purchases/{id}/issue or body of POST/PATCH /purchases while draft): it counts in its own period and its VAT is not deducted again. CSV imports skip sales of filed periods unless declared_history=true (already-declared history). 111/115, payments on account (130/202) and informative returns never block purchases, banks or rules; the 200 freezes the result of its fiscal year. To correct a filed period: POST /taxes/filings/{id}/reopen (owner/admin, reason), POST /taxes/reopens/{id}/close (corrective return, corrected_reference) or /cancel (only if nothing changed). Tax model screens and exports of a filed period return what was filed (filed.frozen: true), not a recomputation.
  • GET /taxes/vat-settlement/{year}/{quarter} adds premature, filed_303_premature, can_revert, ledger_stale and period_drift (skipped_count, posted_count, items): documents that automatic processes (syncs, imports) changed or recorded after the quarter was closed without redoing their journal entry.
  • Future date → 422 future_issue_date. A sales invoice cannot be issued with a date later than today (one-day margin for time zones). Drafts and purchases with a future date only get a warning.
  • POST /bank-movements only in cash treasuries (kind: cash); for banks, cards or gateways it answers 422 treasury_not_cash (their movements come from the statement import or the integration). The movement is booked in the same step against its destination (invoice, account or transfer). external_id is stored and deduplicates: repeating it in the same cash box returns the existing movement with duplicate: true and status 200 (instead of 201).
  • POST /accounts and PATCH /accounts/{id} validate the number: digits only (629.1 is expanded), a subaccount starts with its parent's number (the parent cannot itself be a subaccount) and has the tenant's chart length or any other length the tenant already uses (charts mixing 7- and 8-digit subaccounts); the group comes from the first digit. An invalid number returns 422 and a duplicate 409.
  • GET /accounts and contact subaccounts (430xxxxx/400xxxxx/410xxxxx, auto_kind: "contact"): the broad listing leaves them out, but they are included automatically when searching (search), listing an account's children (parent_id) or asking for an exact number (new number parameter, e.g. ?number=43000001). include_contacts=true|false forces the behaviour. The MCP tool list_accounts accepts include_contacts, number and parent_id, and aikount ledger account 43000001 finds them.
  • GET /accounting/financial-statements accepts compare=previous|last_year and returns 422 when from_date is after to_date. New keys: pnl.partidas (P&L in the official PGC model, items 1-17 and subtotals A.1-A.4), pnl.gestion (gross margin, % of revenue and comparison) and opening_warning (plus a warnings entry) while the 555 opening bridge account holds a balance. The old flat keys are kept; ingresos_excepcionales and gastos_excepcionales are always 0. Treasury opening balances of tenants on the previous chart stay against 120 and raise no warning.
  • POST /journal (manual entry): a line on a grouping account (2-4 digits) is moved to its default subaccount and the response explains it in warnings; a line on 430/400/410 with per-contact subaccounts returns 422 (use the contact's subaccount or the generic "varios" one).
  • Contacts: DELETE /contacts/{id} returns 409 contact_in_use when the contact has documents or postings on its subaccount (merge it instead); POST /contacts/bulk-delete skips them with reason: "has_documents" | "has_postings". POST /contacts/{id}/merge also carries the duplicate's ledger subaccounts over (subaccounts field in the response).
  • Sales CSV import (POST /invoices/import-csv): invoices dated in a settled quarter are no longer skipped; they are imported as historical with a warnings entry, and that quarter's VAT settlement becomes stale.
  • Editing an issued document only redoes its journal entry when something fiscal changes (lines, discounts, contact, date, currency, lease flag): changing notes, tags, project or due date never returns the settled-quarter 409.
  • POST /reconciliation/dismiss accepts counterpart_movement_id (instead of document_id) to dismiss a transfer proposal.

The source of truth

This documentation describes the stable conventions, but the OpenAPI spec is the authoritative reference for each endpoint's exact parameters, types, fields and responses:

https://api.aikount.com/openapi.json

You can also explore it in Swagger UI (/docs) or Redoc (/redoc). If anything here disagrees with openapi.json, openapi.json wins.

See also

Cookies & privacy

We use our own cookieless analytics. With your permission, we measure registrations and payments from Google and ChatGPT ads. Learn more · Privacy