---
name: agentledger
description: Aikount backend (FastAPI accounting SaaS). Use when the user mentions invoices, purchases, expense ingestion, bank reconciliation, treasuries, IVA, modelo 303, Stripe/PayPal/GoCardless/Ponto, or any Spanish accounting action. Reads/writes the user's books via a public REST API.
---

# Aikount skill

Aikount is an agent-first Spanish accounting SaaS. All state lives behind a
REST API at `$AIKOUNT_API` (defaults to `https://api.aikount.com/api/v1`).
The OpenAPI spec at `$AIKOUNT_API/../openapi.json` is the source of truth —
fetch it first if you need to do something this file doesn't cover.

> **Prefer MCP?** Aikount also exposes a Model Context Protocol server with the
> same tools. Hosted (zero install): connect to `https://api.aikount.com/mcp`
> (streamable-HTTP) with header `Authorization: Bearer $AIKOUNT_TOKEN`. Local:
> `uvx aikount-mcp` (https://github.com/mutonby/aikount-mcp). This skill covers
> the raw REST API, which both MCP servers wrap.

## Setup

The user should have these two env vars exported (the in-app "Conectar agente"
button mints the token and gives you the exact `export` lines):

```bash
export AIKOUNT_TOKEN="agl_..."             # long-lived API key (scope *)
export AIKOUNT_API="https://api.aikount.com/api/v1"
```

Every request needs `Authorization: Bearer $AIKOUNT_TOKEN`.

Verify before doing anything else by reading the active tenant:

```bash
curl -sS -H "Authorization: Bearer $AIKOUNT_TOKEN" "$AIKOUNT_API/tenants/me" | jq
```

A 200 returns the tenant `{id, name, vat, currency, ...}`. A 401/403 means the
token is missing, revoked or malformed — ask the user to re-run "Conectar
agente" in the web app.

> Use `/tenants/me`, **not** `/auth/me`, to verify. `/auth/me` is reserved for
> interactive session logins (onboarding/membership flow) and rejects `agl_`
> API keys with a 403.

## Discovering the surface

```bash
# All endpoints
curl -sS "${AIKOUNT_API%/api/v1}/openapi.json" | jq '.paths | keys'

# Schema of a specific endpoint
curl -sS "${AIKOUNT_API%/api/v1}/openapi.json" | jq '.paths["/api/v1/invoices"]'
```

## Common workflows

### List recent invoices

```bash
curl -sS -H "Authorization: Bearer $AIKOUNT_TOKEN" \
  "$AIKOUNT_API/invoices?limit=10&status=issued" | jq
```

### Create + issue an invoice

```bash
# 1) Find the contact (free-text match is the `search` param)
curl -sS -H "Authorization: Bearer $AIKOUNT_TOKEN" \
  "$AIKOUNT_API/contacts?search=acme" | jq

# 2) Resolve the tax type id. Lines reference a tax by `tax_type_id` (a UUID),
#    not by a string code. List the tenant's taxes and pick the one whose
#    `code`/`rate` you want (e.g. code "S21" → 21%). Omit `tax_type_id` to
#    inherit the contact's/product's default.
curl -sS -H "Authorization: Bearer $AIKOUNT_TOKEN" "$AIKOUNT_API/taxes" | jq

# 3) Create draft. Amounts are DECIMAL euros (not cents): unit_price 1200.00.
curl -sS -X POST -H "Authorization: Bearer $AIKOUNT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "contact_id": "<uuid>",
    "doc_date": "2026-05-21",
    "lines": [
      {"description": "Consultoría mayo", "quantity": 1, "unit_price": 1200.00, "tax_type_id": "<tax-uuid>"}
    ]
  }' \
  "$AIKOUNT_API/invoices" | jq

# 4) Issue (assigns the next sequential number)
curl -sS -X POST -H "Authorization: Bearer $AIKOUNT_TOKEN" \
  "$AIKOUNT_API/invoices/<id>/issue" | jq
```

### Ingest a purchase PDF

Send a PDF/image as multipart. This is the **only** safe way to add a purchase:
the AI extracts contact, lines and taxes and creates the purchase doc,
deduplicating by invoice identity so the same invoice never lands twice. It
runs async — the call returns a queued job immediately; poll the job until it
is done, then read its `result`.

```bash
# Queue it (add -F "force=true" only to bypass dedup deliberately)
job=$(curl -sS -X POST -H "Authorization: Bearer $AIKOUNT_TOKEN" \
  -F "file=@invoice.pdf" \
  "$AIKOUNT_API/ai/ingest-purchase")
echo "$job" | jq

# Poll until status is done/failed
curl -sS -H "Authorization: Bearer $AIKOUNT_TOKEN" \
  "$AIKOUNT_API/ai/ingest-jobs/$(echo "$job" | jq -r .id)" | jq
```

> Do **not** add purchases via raw `POST /purchases` — it does not deduplicate,
> and duplicate purchases corrupt expense totals, IVA soportado and bank
> reconciliation.

### List treasuries with live balances

```bash
curl -sS -H "Authorization: Bearer $AIKOUNT_TOKEN" \
  "$AIKOUNT_API/treasuries" | jq
```

Per-treasury movements live at `$AIKOUNT_API/bank-movements?treasury_id=<uuid>`.

Stripe/PayPal treasuries return live balances pulled on read; bank-feed
treasuries return the last synced snapshot.

### Reconcile a movement against a document

```bash
curl -sS -X POST -H "Authorization: Bearer $AIKOUNT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"movement_id": "<uuid>", "document_id": "<uuid>"}' \
  "$AIKOUNT_API/reconciliation/manual-match" | jq
```

The system auto-reconciles when its confidence ≥ 0.95. Anything below is
suggested only — see the suggestions at `GET $AIKOUNT_API/reconciliation/board`.

### Modelo 303 (quarterly VAT)

```bash
# JSON summary across every quarter with activity:
curl -sS -H "Authorization: Bearer $AIKOUNT_TOKEN" \
  "$AIKOUNT_API/aeat/303/summary" | jq

# AEAT-format CSV for one quarter:
curl -sS -H "Authorization: Bearer $AIKOUNT_TOKEN" \
  "$AIKOUNT_API/aeat/303?year=2026&quarter=2"
```

## Conventions

- **Money**: decimal major units (euros), not cents — `unit_price`,
  `subtotal`, `tax_amount`, `total` are all decimals like `1200.00`. EUR
  unless `currency` says otherwise.
- **Dates**: ISO-8601 `YYYY-MM-DD`.
- **IDs**: UUID v4.
- **Taxes**: lines reference a tax by `tax_type_id` (UUID from
  `GET /taxes`), never a string code. Each tax type carries a `code`
  (`S21` 21%, `S10` 10% reducido, `S04` 4% superreducido, `E0` exento,
  `IGIC`/`IPSI` for Canarias/Ceuta/Melilla) and a `rate` — match on those to
  pick the right id.
- **PGC accounts**: 572 (bancos), 430 (clientes), 410 (proveedores), 477
  (IVA repercutido), 472 (IVA soportado), 705 (ventas servicios), 600
  (compras), 626 (servicios bancarios).
- **Pagination**: `limit` (default 50, max 200) + `offset`. Some endpoints
  also accept `from` / `to` (ISO dates).
- **Errors**: HTTP status + `{"detail": "..."}`. Treat 5xx as transient and
  retry once; 4xx is a contract issue — surface to the user.

## Safety rules

- Never `DELETE` invoices or movements without confirming with the user.
  Sequential numbering breaks if you delete issued invoices.
- The token has scope `*` — it can do anything the user can. Treat it as a
  password; never echo it back into chat or commit it.
- Tenant isolation is automatic from the token. You cannot accidentally
  touch another tenant's data unless given a different token.

## When in doubt

Read the OpenAPI spec. It's exhaustive and authoritative — this skill is just
a friendly cheat sheet on top of it.
