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:
| Parameter | What it does |
|---|---|
limit | Maximum number of items to return. |
offset | Offset (for paging). |
from | Range start date (ISO-8601). |
to | Range 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) returns409, anddetailchanges from a string to an object (code: "period_closed",reason,message,year,quarter,entry_number,filed_303,side,alternatives). For purchases the 409 carriesrequires_confirmation: trueandconfirm_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 withvat_deduction_date(the 409 carriesdeduction_paramandsuggested_vat_deduction_date; onPOST /purchases/{id}/issueit is a query param, onPOST/PATCH /purchasesa 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 acceptvat_deduction_date(422). Only a quarter that has ended counts: a settlement made before the quarter ends does not close it (it shows asprematureinGET /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:
409withcode: "period_closed",reason: "period_filed",modelsandrequires_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 (automaticvat_deduction_date, art. 99 LIVA);confirm_settled_periodno longer applies to filed periods. If the purchase was already included in the filed return (history, accountant), post it withdeclared_in_filed_period=true(query ofPOST /purchases/{id}/issueor body ofPOST/PATCH /purchaseswhile draft): it counts in its own period and its VAT is not deducted again. CSV imports skip sales of filed periods unlessdeclared_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}addspremature,filed_303_premature,can_revert,ledger_staleandperiod_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-movementsonly in cash treasuries (kind: cash); for banks, cards or gateways it answers422 treasury_not_cash(their movements come from the statement import or the integration). The movement is booked in the same step against itsdestination(invoice, account or transfer).external_idis stored and deduplicates: repeating it in the same cash box returns the existing movement withduplicate: trueand status200(instead of201).POST /accountsandPATCH /accounts/{id}validate the number: digits only (629.1is 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 returns422and a duplicate409.GET /accountsand 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 (newnumberparameter, e.g.?number=43000001).include_contacts=true|falseforces the behaviour. The MCP toollist_accountsacceptsinclude_contacts,numberandparent_id, andaikount ledger account 43000001finds them.GET /accounting/financial-statementsacceptscompare=previous|last_yearand returns422whenfrom_dateis afterto_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) andopening_warning(plus awarningsentry) while the555opening bridge account holds a balance. The old flat keys are kept;ingresos_excepcionalesandgastos_excepcionalesare always 0. Treasury opening balances of tenants on the previous chart stay against120and 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 inwarnings; a line on430/400/410with per-contact subaccounts returns422(use the contact's subaccount or the generic "varios" one).- Contacts:
DELETE /contacts/{id}returns409 contact_in_usewhen the contact has documents or postings on its subaccount (merge it instead);POST /contacts/bulk-deleteskips them withreason: "has_documents" | "has_postings".POST /contacts/{id}/mergealso carries the duplicate's ledger subaccounts over (subaccountsfield 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 awarningsentry, 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/dismissacceptscounterpart_movement_id(instead ofdocument_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.