Ir al contenido

API pubblica · Riferimento canonico

Esta página aún no está disponible en tu idioma.

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.

Ogni richiesta porta la chiave nell’header X-API-Key:

X-API-Key: 8f14e45fceea167a5a36dedd4bea2543a1b0c9f2e4d7b8a6c3e5f9d2a4b6c8e0

Authorization: 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.

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.

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.

  • 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.

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.

Elenco dei dispositivi a cui la chiave ha accesso. Permesso: read_devices.

Query: limit (1–200, default 50), offset (default 0).

Finestra del terminale
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.

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)
Finestra del terminale
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.

Un dispositivo. Permesso: read_devices. Se l’IMEI non esiste — o non è tuo — la risposta è 404 DEVICE_NOT_FOUND.

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"
}
}

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 } }
}

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 }
]
}

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 } }
}

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.

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: v1
X-AiTrack-Timestamp: 1783062000000
X-AiTrack-Delivery-Id: 4c1f…
X-AiTrack-Delivery-Attempt: 1
import { 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();

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.

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.

  • 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.