API pubblica · Riferimento canonico
Dieser Inhalt ist noch nicht in deiner Sprache verfügbar.
API Aitrack — v2
Sezione intitolata “API Aitrack — v2”Versione API: v2 · Base URL: https://api.aitrack.it/api/v2
Questo file è la FONTE DI VERITÀ della documentazione API mostrata dentro l’app. Il dialog
“Documentazione” (Impostazioni → API Keys) è generato da qui
(apps/app/src/data/apiDocs.generated.ts) e un test fallisce se i due divergono. Non
cancellare questo file: aggiornalo ogni volta che cambia una rotta pubblica, uno scope o un
limite.
Autenticazione
Sezione intitolata “Autenticazione”Ogni richiesta porta la chiave nell’header X-API-Key:
X-API-Key: 8f14e45fceea167a5a36dedd4bea2543a1b0c9f2e4d7b8a6c3e5f9d2a4b6c8e0Authorization: Bearer <chiave> non funziona: quell’header è riservato ai token di
sessione dell’app, e una chiave API passata lì viene rifiutata con 403.
La chiave si crea da API Keys → Nuova chiave e la vedi una volta sola, alla creazione. Di essa conserviamo soltanto l’impronta SHA-256 e le prime sei cifre (per riconoscerla nell’elenco): nessuno può rileggerla — né noi, né un amministratore, né chi rubasse una copia del database. Persa una chiave, si revoca e se ne crea un’altra.
Una chiave agisce sul tuo account dall’esterno, senza sessione: trattala come una password.
Restrizione per IP (opzionale)
Sezione intitolata “Restrizione per IP (opzionale)”A ogni chiave puoi legare una lista di indirizzi ammessi (fino a 50 voci): IPv4 singoli o
in notazione CIDR (10.0.0.0/24), IPv6 solo come indirizzo esatto. Una chiamata che arriva
da un IP fuori lista viene rifiutata con 403 API_KEY_IP_BLOCKED, anche se la chiave è
valida.
Permessi
Sezione intitolata “Permessi”Una chiave non può fare niente per cui non abbia il permesso: si concedono uno per uno.
| Permesso | Cosa apre |
|---|---|
read_devices |
Elenco e anagrafica dei dispositivi |
read_locations |
Ultima posizione nota |
read_history |
Storico delle posizioni |
read_history_summary |
Storico in forma aggregata |
send_commands |
Coda comandi (in v2 solo in lettura) |
read_events |
Eventi della flotta |
Senza il permesso, la risposta è 403 con {"error": "API key senza permesso: <permesso>"}.
L’invio dei comandi non è esposto in v2: da una chiave si può leggere la coda, non muovere un mezzo. È una scelta, non una dimenticanza.
Limiti di frequenza
Sezione intitolata “Limiti di frequenza”- 120 richieste al minuto per chiave
- 1000 richieste all’ora per chiave
- 120 richieste al minuto per indirizzo IP
Oltre il limite la risposta è 429 con {"error": "Rate limit superato (per key/minuto)."}.
Le risposte non portano header X-RateLimit-* né Retry-After: regola il tuo client
sui numeri qui sopra.
Forma delle risposte
Sezione intitolata “Forma delle risposte”Ogni risposta v2 ha la stessa busta:
{ "data": { }, "meta": { "pagination": { "limit": 50, "offset": 0, "count": 12 } }}meta.pagination compare solo sugli elenchi.
| Stato | Significato |
|---|---|
400 |
Parametro mancante o non valido |
403 |
Chiave assente, sconosciuta, revocata, IP non ammesso, permesso mancante |
404 |
La risorsa non esiste — o non è tua (non distinguiamo: dire “esiste ma non è tuo” è già un’informazione di troppo) |
429 |
Limite di frequenza superato |
500 |
Errore nostro |
Gli errori v2 hanno un codice stabile su cui puoi scrivere del codice:
{ "error": { "code": "DEVICE_NOT_FOUND", "message": "Dispositivo non trovato." } }Codici: API_KEY_REQUIRED, DEVICE_NOT_FOUND, LOCATION_NOT_FOUND, IMEI_REQUIRED,
BAD_REQUEST, FORBIDDEN, INTERNAL_ERROR.
Dispositivi
Sezione intitolata “Dispositivi”GET /api/v2/devices
Sezione intitolata “GET /api/v2/devices”Elenco dei dispositivi a cui la chiave ha accesso. Permesso: read_devices.
Query: limit (1–200, default 50), offset (default 0).
curl https://api.aitrack.it/api/v2/devices?limit=10 \ -H "X-API-Key: $AITRACK_API_KEY"{ "data": [ { "id": 128, "imei": "352093081452312", "name": "Furgone 1", "plate": "AB123CD", "device_model": "FMB920", "odometer_km": 84213, "status": "moving", "groups": ["Consegne"], "location": { "latitude": 45.4642, "longitude": 9.19, "speed_kmh": 52, "recorded_at": "2026-07-14T09:00:00.000Z" }, "updated_at": "2026-07-14T09:00:03.000Z" } ], "meta": { "pagination": { "limit": 10, "offset": 0, "count": 1 } }}status vale moving, parked, offline o unknown.
GET /api/v2/devices/nearest
Sezione intitolata “GET /api/v2/devices/nearest”Il dispositivo più vicino a una coordinata — e quanto dista. Permesso: read_locations.
| Parametro | Obbligatorio | Valore |
|---|---|---|
lat |
sì | Latitudine, da -90 a 90 |
lng |
sì | Longitudine, da -180 a 180 |
limit |
no | Quanti restituirne, da 1 a 50. Predefinito: 1 |
max_km |
no | Scarta chi è oltre questo raggio (km) |
curl -H "X-API-Key: LA_TUA_CHIAVE" \ "https://api.aitrack.it/api/v2/devices/nearest?lat=45.4642&lng=9.19&limit=3"{ "data": [ { "id": "359000000000001", "imei": "359000000000001", "name": "Furgone 1", "plate": "AB123CD", "status": "moving", "location": { "latitude": 45.4642, "longitude": 9.19, "speed_kmh": 52, "recorded_at": "2026-07-14T09:00:00.000Z" }, "distance_km": 0.412 } ], "meta": { "query": { "lat": 45.4642, "lng": 9.19, "limit": 3, "max_km": null }, "distance": "straight_line_km" }}Tre cose da sapere prima di usarlo per mandare un mezzo da qualcuno:
distance_kmè la distanza in linea d’aria, non quella su strada. Il dispositivo più vicino in linea d’aria può essere il più lontano da guidare — un fiume in mezzo, un’uscita d’autostrada dalla parte sbagliata. Se ti serve il tempo di percorrenza reale, questo numero è un punto di partenza, non una risposta.- Si cerca solo tra i tuoi dispositivi, quelli a cui la chiave ha accesso.
- I dispositivi senza una posizione nota non compaiono: non sono “lontani”, sono ignoti. Se
nessuno ha una posizione (o nessuno rientra in
max_km),dataè una lista vuota — non un errore.
Coordinate mancanti → 400 COORDINATES_REQUIRED. Coordinate fuori dai limiti o non
numeriche → 400 INVALID_COORDINATES.
GET /api/v2/devices/{imei}
Sezione intitolata “GET /api/v2/devices/{imei}”Un dispositivo. Permesso: read_devices. Se l’IMEI non esiste — o non è tuo — la risposta
è 404 DEVICE_NOT_FOUND.
GET /api/v2/devices/{imei}/location
Sezione intitolata “GET /api/v2/devices/{imei}/location”Ultima posizione nota. Permesso: read_locations. 404 LOCATION_NOT_FOUND se il
dispositivo non ha ancora inviato nulla.
{ "data": { "latitude": 45.4642, "longitude": 9.19, "altitude_m": 122, "speed_kmh": 52, "recorded_at": "2026-07-14T09:00:00.000Z" }}GET /api/v2/devices/{imei}/history
Sezione intitolata “GET /api/v2/devices/{imei}/history”Storico delle posizioni. Permesso: read_history.
Query: from e to (ISO-8601), limit (1–1000, default 500).
La finestra effettivamente leggibile dipende dal piano: il server taglia le richieste che vanno più indietro di quanto il piano conservi.
{ "data": [ { "latitude": 45.4642, "longitude": 9.19, "speed_kmh": 52, "heading": 91, "satellites": 11, "recorded_at": "2026-07-14T09:00:00.000Z" } ], "meta": { "pagination": { "limit": 500, "offset": 0, "count": 1 } }}GET /api/v2/devices/{imei}/commands
Sezione intitolata “GET /api/v2/devices/{imei}/commands”Coda dei comandi, in sola lettura. Permesso: send_commands.
Query: status (valori separati da virgola tra pending, sent, canceled; default
pending,sent), limit (1–100, default 50).
{ "data": [ { "id": 91, "type": "engine_lock", "status": "pending", "queued_at": "…", "sent_at": null } ]}GET /api/v2/events
Sezione intitolata “GET /api/v2/events”Eventi della flotta. Permesso: read_events.
Query: type, device (IMEI), from, to (ISO-8601), limit (1–200, default 50),
offset.
{ "data": [ { "id": 5521, "type": "geofence_enter", "message": "Furgone 1 è entrato in Magazzino", "device_imei": "352093081452312", "data": { "geofence_id": 12 }, "read": false, "occurred_at": "2026-07-14T08:59:00.000Z" } ], "meta": { "pagination": { "limit": 50, "offset": 0, "count": 1 } }}Webhook
Sezione intitolata “Webhook”Invece di chiedere ogni minuto se è successo qualcosa, fatti chiamare: registri un indirizzo e ti mandiamo un POST quando l’evento accade. Si configurano in Impostazioni → Moduli → API e Webhook.
Il corpo è sempre questo:
{ "id": "evt_9f2c…", "type": "geofence_enter", "created_at": "2026-07-14T09:00:00.000Z", "data": { }}Gli eventi che puoi ricevere sono gli stessi che fanno scattare le automazioni (ingresso e
uscita da un’area, accensione, sosta, allarmi…). Un endpoint iscritto a * li riceve tutti.
Verifica la firma
Sezione intitolata “Verifica la firma”Ogni consegna è firmata. Verificala: un endpoint che accetta qualunque POST è una porta aperta per chiunque scopra il tuo indirizzo.
X-AiTrack-Signature: sha256=<HMAC-SHA256 esadecimale del corpo, con il segreto dell'endpoint>X-AiTrack-Signature-Version: v1X-AiTrack-Timestamp: 1783062000000X-AiTrack-Delivery-Id: 4c1f…X-AiTrack-Delivery-Attempt: 1import { createHmac, timingSafeEqual } from 'node:crypto';
const atteso = 'sha256=' + createHmac('sha256', process.env.AITRACK_WEBHOOK_SECRET) .update(rawBody) // il corpo GREZZO, prima di JSON.parse .digest('hex');
const ricevuto = req.headers['x-aitrack-signature'];const ok = ricevuto?.length === atteso.length && timingSafeEqual(Buffer.from(ricevuto), Buffer.from(atteso));if (!ok) return res.status(401).end();Consegna
Sezione intitolata “Consegna”Accettiamo solo URL https://, e mai indirizzi interni o privati (è la stessa
protezione che impedisce a un webhook di diventare una sonda dentro la nostra rete).
Timeout 10 secondi, nessun redirect. Se il tuo endpoint non risponde 2xx riproviamo:
con exponential (predefinito) gli intervalli raddoppiano — 2, 4, 8, 16 minuti — fino a
5 tentativi; con fixed ogni 5 minuti. Esauriti i tentativi la consegna finisce in
“lettera morta” e la puoi rilanciare a mano.
L’header Idempotency-Key è stabile per evento: se ti arriva due volte lo stesso id,
scartalo.
Versionamento
Sezione intitolata “Versionamento”La versione sta nel percorso: /api/v2. Non impostare l’header X-API-Version (accetta
solo v1 e farebbe fallire la chiamata con 400).
La v1 — le rotte usate dall’app — resta accessibile alle chiavi per compatibilità, ma è
deprecata: le risposte portano Deprecation: true e la data di spegnimento
(Sunset: 2027-03-08). Ogni versione nuova ha almeno 12 mesi di sovrapposizione con la
precedente: nessuna rotta pubblica sparisce da un giorno all’altro.
Tenere al sicuro una chiave
Sezione intitolata “Tenere al sicuro una chiave”- Mettila in una variabile d’ambiente. Mai nel codice del sito, mai in un repository: una chiave in un repo pubblico è un accesso aperto alla tua flotta.
- Una chiave per integrazione, così revocarne una non rompe le altre.
- Limita gli IP quando puoi: una chiave rubata da un indirizzo non ammesso non serve a nulla.
- Se sospetti che sia trapelata, revocala subito da API Keys: smette di funzionare alla chiamata successiva.