Unofficial CLI for the Fatture in Cloud API v2 — JSON-first, jq-pipeable, agent-friendly.
Disclaimer: Unofficial. Not affiliated with or endorsed by TeamSystem S.p.A. "Fatture in Cloud" is a trademark of its owner, used here descriptively.
Requires Python 3.11+.
From PyPI (not yet published — use the clone method below for now):
uv tool install fic
# or
pipx install ficFrom a local clone:
git clone https://github.com/yourusername/fic-cli.git
cd fic-cli
uv tool install .
# or
pipx install .With the MCP server (for AI agents — see Use with an AI agent):
uv tool install 'fic[mcp]' # or: pipx install 'fic[mcp]' / uv tool install '.[mcp]'- Open the FIC web app → Settings → Connected Applications.
- Create a new application and copy the access token.
- Find your company id with
fic companies list(look for theidfield).
Set credentials via environment variables:
export FIC_ACCESS_TOKEN="your-token-here"
export FIC_COMPANY_ID=12345Or write them to ~/.config/fic/config.toml:
access_token = "your-token-here"
company_id = 12345Resolution order: environment variables take priority over the config file.
fic auth device --client-id YOUR_CLIENT_IDThis opens the FIC authorization page, polls for approval, and persists tokens to ~/.config/fic/config.toml.
Caveat: FIC enables the Device Code Flow on demand per application. If it is not enabled for your
client_id, the authorization step fails with a clear error message. Contact FIC support to enable it for your OAuth application.
# Show resolved token source and company_id (no network call, token redacted)
fic auth status
# Confirm the token works (network call)
fic auth whoami# List companies linked to your token (use to find your company id)
fic companies list
# Show current user info
fic auth whoami
# List all clients (auto-paginate)
fic clients list --all
# List invoices for 2026
fic docs list --type invoice --year 2026
# Get a specific document as JSON and pipe to jq
fic docs get 123 | jq '.data.number'
# Create a document from a JSON file
fic docs create --type invoice -f invoice.json
# Create a document from stdin
echo '{"data": {"type": "invoice", ...}}' | fic docs create --type invoice -f -
# Download a document PDF
fic docs pdf 123 -o invoice.pdf
# List VAT types (useful when building document payloads)
fic info vat-types | jq '.data[]'
# List payment accounts (needed for fic docs pay)
fic info payment-accounts | jq '.data[] | {id, name}'fic [--output json|table] [-v] [--company-id N] COMMAND
| Option | Description |
|---|---|
--output TEXT |
Output format: json (default) or table |
-v / --verbose |
Verbose diagnostics on stderr |
--company-id INTEGER |
Override the company id for this invocation |
The access token is never a CLI flag (shell-history risk). Use env vars or the config file.
Print the fic version.
Show resolved credentials (token redacted). No network call.
Show the current user's info (network call).
Log in via the OAuth 2.0 Device Code Flow and persist tokens.
| Option | Description |
|---|---|
--client-id TEXT |
OAuth app client id (required) |
--scope TEXT |
Space-separated scopes (default: situation:r situation:a) |
List all companies linked to the current token. Use this to discover your company_id.
Show the current company's info.
| Option | Description |
|---|---|
--all |
Auto-paginate all pages |
--query TEXT |
Filter (maps to the API q param) |
--page INTEGER |
Page number |
--per-page INTEGER |
Results per page (max 100) |
--sort TEXT |
Sort expression |
--fieldset TEXT |
basic, detailed, or fic_view |
Get a single client by id.
Create a client from JSON. FILE is a path or - for stdin (default: -).
Update a client by id from JSON.
Delete a client. Requires --yes to confirm.
Same commands as fic clients: list, get, create, update, delete with identical options.
Same commands as fic clients: list, get, create, update, delete with identical options.
Issued documents. The --type flag is required for list and create because the FIC API is
type-scoped.
Issued document types: invoice, quote, proforma, receipt, delivery_note,
credit_note, order, work_report, supplier_order, self_own_invoice, self_supplier_invoice.
| Option | Description |
|---|---|
--type TEXT |
Document type (required) |
--all |
Auto-paginate all pages |
--year INTEGER |
Sugar: filter by year via q date filter |
--query TEXT |
Filter (maps to the API q param) |
--page INTEGER |
Page number |
--per-page INTEGER |
Results per page |
--sort TEXT |
Sort expression |
--fieldset TEXT |
Fieldset variant |
Get a single issued document by id.
Create an issued document. --type is required. FILE defaults to stdin.
Update an issued document by id from JSON.
Delete an issued document. Requires --yes to confirm.
Convert (transform) a document to a different type.
| Option | Description |
|---|---|
--to TEXT |
New document type (required) |
--from TEXT |
Current document type |
--e-invoice |
Mark the output as an e-invoice |
--keep-copy |
Keep the original as a copy |
Download the document PDF. Writes to FILE or stdout if -o is omitted.
Note: there is no dedicated PDF API endpoint. The CLI fetches the document, reads the temporary
urlfield, and downloads the file from that URL.
Mark a document as paid. This is a read-modify-write operation — see Gotchas.
| Option | Description |
|---|---|
--payment-account-id INTEGER |
Payment account id (required; see fic info payment-accounts) |
--date TEXT |
Paid date in YYYY-MM-DD format |
--amount FLOAT |
Amount paid |
--yes |
Confirm the write |
Received documents. --type is required for list and create.
Received document types: expense, passive_credit_note, passive_delivery_note, self_invoice.
Same list options as fic docs list (--all, --query, --page, --per-page, --sort, --fieldset).
Get a single received document by id.
Create a received document.
Update a received document by id.
Delete a received document. Requires --yes to confirm.
E-invoicing / SDI operations.
Send an issued document to the SDI (Sistema di Interscambio). --dry-run validates without sending.
Show the e-invoice status of an issued document. Returns {"id", "ei_status"} (the SDI status
field only).
Note: there is no dedicated e-invoice status API endpoint. The CLI reads the
ei_statusfield from the document object.
Show the SDI rejection reason for a rejected e-invoice. Returns the rejection reason object from the SDI.
E-invoice status values: attempt, missing, not_sent, sent, pending, processing,
error, discarded, not_delivered, accepted, rejected, no_response, manual_accepted,
manual_rejected.
Reference data lookups (not paginated).
| Command | Description |
|---|---|
fic info vat-types |
List VAT types for the current company |
fic info payment-accounts |
List payment accounts for the current company |
fic info payment-methods |
List payment methods for the current company |
fic info units |
List units of measure (global, no company required) |
fic info currencies |
List currencies (global) |
Print the machine-readable manual (every command, flag, exit code, and the error-envelope
shape) as JSON, or as markdown with --output table. See
Use with an AI agent.
Run fic as an MCP server over stdio for AI agents (needs the mcp extra). On a terminal it
prints client setup instructions instead of starting; --stdio forces the server. See
Use with an AI agent.
- Default output is JSON on stdout — a single value, jq-pipeable, no decorative chrome.
- All diagnostics, progress, and errors go to stderr. Stdout is clean for piping.
--output tablerenders a human-readable Rich table; never use this in scripts.-v / --verboseprints a diagnostic line to stderr with the resolvedcompany_idand token source before each request.
# Safe to pipe
fic clients list --all | jq '[.data[] | {id, name}]'
fic docs get 123 | jq '.data.number'| Code | Meaning | Cause |
|---|---|---|
| 0 | Success | |
| 1 | Generic / unexpected error | Uncaught ApiError, network failure |
| 2 | Usage error | Bad arguments (Typer parsing) |
| 3 | Auth / config error | Missing token, 401 response |
| 4 | Validation error | 422 response |
| 5 | Rate-limit exhausted | 429 after all retries |
| 6 | Not found | 404 response |
| 7 | Forbidden | 403 non-throttle (permission / scope error) |
On failure, JSON mode (the default) prints exactly one error-envelope line on stderr; stdout stays empty:
{"error":{"code":"not_found","message":"Resource not found (404).","hint":"Check the id and that --company-id is correct."}}code is one of error, usage, config, auth, validation, rate_limited,
not_found, forbidden. A validation error adds fields (per-field messages) and, for a
totals mismatch, totals — enough for an agent to fix the input and retry. --output table
keeps the human error: <msg> form instead.
fic is built to be driven by coding agents — two surfaces, same data, same write-safety.
fic agent-docs # JSON: every command, flag, exit code, and the error envelope
fic --output table agent-docs # the same as a markdown manualPoint an agent at fic agent-docs and it can discover the whole surface in one call.
Run fic as a Model Context Protocol server so an agent
calls fic operations as tools (needs the mcp extra — see Install):
# Claude Code
claude mcp add fic -- fic mcp serveRun fic mcp serve in a terminal to print setup instructions for any stdio client (it does
not start the server on a TTY; pass --stdio to force it). It exposes ~13 tools:
- Reads (free):
fic_list,fic_get,fic_info,fic_account,fic_einvoice_status,fic_einvoice_rejection,fic_docs_pdf_url. - Writes (require
confirm=true):fic_create,fic_update,fic_delete,fic_docs_convert,fic_docs_pay,fic_einvoice_send. Called withoutconfirm=truethey refuse and do nothing — the MCP mirror of the CLI's--yes.
The server reads credentials exactly like the CLI (env / config / device login) and honors
--company-id on the fic command.
skills/fic/SKILL.md teaches an agent the search→inspect→act loop,
the document type enums, the destructive-op gate, and the known traps. Drop it into a
Claude/Hermes skills directory (or your agent's skill loader) and it is auto-discovered.
If any line item amount has more than 2 decimal places, FIC may reject the document with a 422
where validation_result is null and the message is
"Il totale dei pagamenti non corrisponde al totale da pagare.". The computed totals are surfaced
in the error response's extra.totals field and are printed to stderr by fic.
Fix: round all monetary values to 2 decimal places before submitting.
Creating a document for a client that is not in the FIC saved-clients list results in only partial entity details being stored. For full data fidelity, either:
- Create the client first (
fic clients create) and reference itsidin the document, or - Inline the full entity object in the document payload.
fic auth device uses the OAuth 2.0 Device Code Flow, which FIC enables on demand per
application. If it is not enabled for your client_id, the command fails with a clear error.
Contact FIC support to request it.
FIC uses 403 for two distinct situations: a real permission/scope denial, and a long-term quota
exhaustion (1 000 req/hr, 40 000 req/mo). fic retries on 403 only when a Retry-After
header is present (quota); a bare 403 is treated as a hard PermissionDenied and is not retried.
The short-term limit (300 req / 5 min, exits as 429) is shared across all applications and users for the same company — heavy automation elsewhere can exhaust your budget.
fic docs pdf downloads the PDF by fetching the document, reading the temporary url field, and
downloading that URL. The URL is short-lived; do not cache it.
fic einvoice status reads the ei_status field from the document object. There is no polling or
push endpoint.
fic docs pay has no dedicated "pay" endpoint. It fetches the document, patches the
payments_list entry, and calls modify_issued_document. This is a read-modify-write and
requires --yes to confirm. Concurrent modifications to the same document may conflict.
All business logic lives in fic.core — a pure Python package with no Typer, no print(),
no sys.exit(). It accepts plain arguments and returns plain data (dicts / SDK models), raising
typed exceptions (AuthError, NotFound, ValidationError, etc.).
Both surfaces are thin adapters over it: fic.cli (Typer — parses args, formats json/table, maps
exceptions to exit codes, writes diagnostics to stderr) and fic.mcp (the MCP server — wraps the
same core functions through per-resource dispatch adapters, with the confirm=true write gate).
Neither duplicates business logic, so the two surfaces can never disagree. The mcp SDK
dependency is confined to fic/mcp/ and imported lazily, so the base CLI never requires it.
# fic.mcp and fic.cli both call core like this:
from fic.core import documents
from fic.core.client import FicClient
with FicClient(access_token="...", company_id=12345) as client:
invoices = documents.list_issued(client, type="invoice", all=True)No Typer, no subprocess, no shell. The core functions are the stable, reusable API surface.
See AGENTS.md for the development contract.