API
Read and write your InterventIO data from your own software. Version 1 — stable.
Address and version
Everything lives under https://interventio.alienproject.org/api/v1.
The version is in the URL and not in a header: you write it once in your
configuration and you can see it, while a forgotten header sends requests to the
wrong version without anyone noticing.
Authentication
A manager creates a token in Settings → API. It is shown once: we only keep its fingerprint. Send it on every request.
curl -H "Authorization: Bearer <token>" https://interventio.alienproject.org/api/v1/clienti
A token belongs to the person who created it and acts as them: it
sees exactly what they see, and it stops working if they leave the company or their
licence expires. It carries one of two abilities — lettura
(read) or scrittura (write, which always
includes read). Rate limit: 60 requests per minute, counted per user.
Responses and errors
Lists come back as data plus
links and meta.
Every error has the same shape: esito for your
program, messaggio for whoever reads the log.
Writes answer with esito too, even when they
succeed, so you never have to tell cases apart from the status code alone.
| esito | HTTP | When |
|---|---|---|
| creato | 201 | The record was created |
| aggiornato | 200 | The record was updated |
| gia_esistente | 200 | You repeated a creation we had already accepted |
| non_autenticato | 401 | No token, unknown token, or expired token |
| utente_non_attivo | 403 | The person the token belongs to no longer works there |
| solo_fornitori | 403 | The API is for service providers |
| licenza_scaduta | 403 | The licence is no longer valid |
| funzione_non_inclusa | 403 | The plan does not include the API |
| abilita_mancante | 403 | A read-only token tried to write |
| non_trovato | 404 | Wrong address, or a record that is not yours |
| metodo_non_ammesso | 405 | That address does not accept that method |
| validazione_fallita | 422 | Check errori for the offending fields |
| troppe_richieste | 429 | Slow down |
A record belonging to another company answers 404, not 403. Saying "forbidden" would confirm that the id exists, which is a way of counting somebody else's documents.
Reading
| Endpoint | Filters of its own |
|---|---|
| GET /clienti · GET /clienti/{id} | cerca, attivi |
| GET /commesse · GET /commesse/{id} | cerca, id_cliente, attive |
| GET /rapportini · GET /rapportini/{id} | id_cliente, stato, dal, al |
| GET /fatture · GET /fatture/{id} | id_cliente, stato, dal, al |
Every list also takes per_pagina (50 by
default, 200 at most) and aggiornati_dopo.
There is no parameter for choosing whose data you get: it is always
the company the token belongs to. Field names are the ones in the database, the same
as in the XLSX export — three vocabularies for one column would be three chances to
get it wrong. Amounts are JSON numbers, dates are Y-m-d,
moments are ISO 8601.
Detail responses carry the lines; lists do not. Invoices are the exception: totals
(imponibile, iva,
totale, totale_incassato,
residuo) are on both, because reconciling
payments should not mean opening one invoice at a time.
Staying in sync
Pass aggiornati_dopo to get only what changed.
Every list hands back the moment to use next time:
{
"data": [ … ],
"meta": { "total": 128, "prossimo_aggiornati_dopo": "2026-07-31T09:14:03+00:00" }
}
Use that value, not the newest aggiornato_il
you saw. Lists are ordered by id, so a record changed while you were paging may have
an id you have already passed: a checkpoint computed from what you read would lose
it forever, while the moment of the request picks it up next time. You may see a few
records twice — that is the right way round to be wrong.
Writing
Needs a token with the scrittura ability.
| Endpoint | Notes |
|---|---|
| POST /clienti | codice_esterno must be unique among your customers |
| PUT|PATCH /clienti/{id} | Only the fields you send are touched |
| POST /commesse | id_cliente must be one of your customers |
| PUT|PATCH /commesse/{id} | id_cliente cannot be changed |
| POST /rapportini | uuid is required and makes retries safe; optional esiti per system |
PUT and PATCH do the same thing and update only what arrives. Strict
PUT semantics would mean that a system syncing addresses wipes the VAT number of half
an address book by omitting it. To clear a field, send null
— something you write on purpose.
A POST that times out is the normal way HTTP fails. On intervention
reports, send a uuid you generate: repeating
the same call gives you back gia_esistente and
the same document, never a duplicate. On customers and jobs that job is done by
codice_esterno.
Check result per system. Next to impianti
(the ids of the systems the report is about) you can send
esiti: a list of
{"id_impianto": 12, "esito": "non_conforme", "nota": "Gauge in the red"},
where esito is conforme,
non_conforme, non_controllato
or null. It is optional: a result for a system the report is not
linked to is skipped. GET /rapportini/{id} returns the same
information in impianti.
Invoices are read-only. Numbering, series and the rules about when a document may be issued belong to the application: handing them to an external client would mean exposing all of them. There is no DELETE either — nothing here is deleted, things are deactivated.
Webhooks
The other direction: we call you when something happens here, so you do not have to keep asking. Set them up in Settings → Webhooks.
{
"evento": "fattura_emessa",
"momento": "2026-07-31T09:14:03+00:00",
"id_azienda": 12,
"titolo": "Invoice issued",
"messaggio": "Invoice FA/2026/00042 has been issued.",
"oggetto": { "tipo": "FA", "id": 908, "numero_completo": "FA/2026/00042" }
}
oggetto is filled in for events about a
document and is null for the others —
licences, sign-ins, users — because documents are the only things this API can show
you, and a reference to anything else would send you knocking at a door that does not
exist.
Check the signature. Each call carries
X-InterventIO-Momento and
X-InterventIO-Firma. Redo the same sum with
your secret and compare; refuse anything older than a few minutes.
firma = "sha256=" + hmac_sha256(momento + "." + body, secret)
The moment is inside the signature on purpose: signing the body alone would leave a captured call replayable months later with a signature that still checks out. Answer with any 2xx. We try five times — after a minute, five, thirty, then two hours — and after ten deliveries that fail in a row the webhook switches itself off, with a note on the customer's board saying why.
Version policy
Version 1 will not break. We may add endpoints, add fields to
existing responses, and add optional parameters — so parse leniently and ignore
fields you do not know. What we will not do inside v1 is remove or rename a field,
change the type or meaning of one, make an optional parameter required, or change
what an esito means. Anything that would
require you to change your code ships as
/api/v2, and v1 keeps working for at least
twelve months after v2 appears. There is no v2 today: an unknown version answers
non_trovato.
Changelog
-
2026-07-31 — v1
First release. Tokens and abilities; customers, jobs, intervention reports and invoices for reading; customers, jobs and intervention reports for writing; webhooks; Fatture in Cloud connector.