Aree (geofence)
Este conteúdo não está disponível em sua língua ainda.
Un’“area” è una geofence — cerchio, luogo, poligono o traccia — nello scope del proprietario della
chiave (sue e della sua organizzazione, stesso perimetro della v2). Un’area di un altro cliente
risponde sempre 404, mai 403: la chiave non può nemmeno scoprire che esiste.
Modello
Sezione intitolata “Modello”| Campo | Tipo | Note |
|---|---|---|
id |
integer | |
name |
string | max 100 caratteri |
color |
string | #rrggbb; #3b82f6 se omesso alla creazione |
type |
enum | circle | polygon | polyline | point |
center |
object | null | { lat, lng } — cerchi e luoghi; sui poligoni è il centroide calcolato |
radius_m |
number | null | solo circle (minimo 25 m) e point (default 50 m se omesso) — massimo 100000 m |
buffer_m |
number | null | solo polyline: distanza massima dal percorso, 5–5000 m, default 50 m |
points |
array | { lat, lng }[] — poligoni (≥3) e tracce (≥2), massimo 2000 |
external_id |
string | null | codice dell’area nel tuo sistema (gestionale, CRM…); unico per account; 1–100 caratteri tra lettere, numeri e . _ : / @ # - |
dwell_seconds |
integer | permanenza minima prima di notificare l’entrata, in secondi; 0 = subito (default, comportamento invariato) — vedi Permanenza minima |
metrics.area_m2 |
number | null | poligoni e cerchi |
metrics.perimeter_m |
number | null | poligoni e cerchi |
metrics.length_m |
number | null | solo tracce |
bounds |
object | null | { south, west, north, east } |
created_at |
string (ISO 8601) | null |
Permanenza minima (dwell_seconds)
Sezione intitolata “Permanenza minima (dwell_seconds)”Un intero da 0 a 86400 secondi, pensato per non notificare chi attraversa un’area senza
fermarsi (una strada che passa dentro un cantiere, un incrocio dentro il raggio di un cliente).
0 è il default e si comporta esattamente come sempre: l’entrata si notifica appena il
dispositivo è dentro.
Con un valore maggiore di zero, l’evento di entrata (notifiche, automazioni, e la voce in
GET /areas/{id}/events e in /api/v3/events)
scatta solo dopo che il dispositivo è rimasto dentro l’area per almeno quel tempo, misurato
sugli orari GPS dei punti ricevuti — non sull’orologio del server. Chi attraversa l’area senza
fermarsi altrettanto a lungo non genera né entrata né uscita. Se il dispositivo esce
prima che il tempo sia trascorso, il conteggio si azzera: un’entrata successiva riparte da zero.
L’uscita, di conseguenza, si notifica solo dopo un’entrata già confermata.
Si imposta con dwell_seconds in POST/PATCH/PUT .../by-external-id
e in properties.dwell_seconds su POST /areas/import; nell’editor
delle aree dell’app corrisponde al campo “Notifica l’entrata dopo (minuti)”. Fuori range →
422 VALIDATION_ERROR con code: "OUT_OF_RANGE" — vedi Codici di validazione.
GET /api/v3/areas
Sezione intitolata “GET /api/v3/areas”Elenco delle aree. Permesso: read_areas.
| Parametro | Default | Note |
|---|---|---|
limit |
50 | 1–200, clampato |
offset |
0 | |
type |
— | circle | polygon | polyline | point; altri valori → 400 BAD_REQUEST |
format |
json |
geojson restituisce una FeatureCollection in data |
external_id |
— | cerca l’area con questo codice esterno esatto (0 o 1 risultato); non è un filtro parziale |
curl -H "X-API-Key: $AITRACK_API_KEY" \ "https://api.aitrack.it/api/v3/areas?type=circle&limit=50"{ "data": [ { "id": 42, "name": "Magazzino Nord", "color": "#3b82f6", "type": "circle", "center": { "lat": 45.4642, "lng": 9.19 }, "radius_m": 150, "buffer_m": null, "points": [], "metrics": { "area_m2": 70686, "perimeter_m": 942, "length_m": null }, "bounds": { "south": 45.4629, "west": 9.1881, "north": 45.4655, "east": 9.1919 }, "created_at": "2026-09-20T10:00:00.000Z" } ], "meta": { "pagination": { "limit": 50, "offset": 0, "count": 1 }, "total": 1 }}Con ?format=geojson la stessa area esce come Feature dentro una FeatureCollection — vedi
GeoJSON: input e output.
POST /api/v3/areas
Sezione intitolata “POST /api/v3/areas”Crea un’area. Permesso: write_areas. Header opzionale Idempotency-Key (24 h) — vedi
Panoramica. Il corpo accetta la forma JSON v3 oppure
{ "geojson": <Feature> }.
curl -X POST https://api.aitrack.it/api/v3/areas \ -H "X-API-Key: $AITRACK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Magazzino Nord", "color": "#3b82f6", "type": "circle", "center": { "lat": 45.4642, "lng": 9.19 }, "radius_m": 150 }'curl -X POST https://api.aitrack.it/api/v3/areas \ -H "X-API-Key: $AITRACK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "geojson": { "type": "Feature", "properties": { "name": "Magazzino Nord", "color": "#3b82f6", "radius": 150 }, "geometry": { "type": "Point", "coordinates": [9.19, 45.4642] } } }'Le proprietà del corpo (name, color, radius_m, buffer_m, external_id, dwell_seconds)
hanno la precedenza su quelle della feature (properties.external_id, properties.dwell_seconds
inclusi), se inviate entrambe.
{ "data": { "id": 42, "name": "Magazzino Nord", "color": "#3b82f6", "type": "circle", "center": { "lat": 45.4642, "lng": 9.19 }, "radius_m": 150, "buffer_m": null, "points": [], "metrics": { "area_m2": 70686, "perimeter_m": 942, "length_m": null }, "bounds": { "south": 45.4629, "west": 9.1881, "north": 45.4655, "east": 9.1919 }, "created_at": "2026-09-27T09:00:00.000Z" }}Risposta 201, header Location: /api/v3/areas/42. Se ci sono avvisi non bloccanti (vertici quasi
coincidenti, area molto piccola o molto grande) compare anche meta.warnings. Dati non validi →
422 VALIDATION_ERROR con details[] — vedi Codici di validazione.
external_id già usato da un’altra tua area → 409 CONFLICT: usa
PUT /api/v3/areas/by-external-id/{external_id} per
aggiornarla invece di provare a ricrearla.
PUT /api/v3/areas/by-external-id/{externalId}
Sezione intitolata “PUT /api/v3/areas/by-external-id/{externalId}”Crea l’area se non esiste ancora un’area con questo external_id, altrimenti la aggiorna —
upsert. Permesso: write_areas. Pensata per una sincronizzazione che rimanda periodicamente
tutto il proprio elenco: chiamata più volte con lo stesso elenco, non crea mai doppioni.
| Parametro path | Note |
|---|---|
externalId |
1–100 caratteri tra lettere, numeri e . _ : / @ # -; altrimenti 400 BAD_REQUEST prima di consultare il database |
Il corpo è la stessa forma JSON v3 di POST/PATCH. Se il corpo include anche external_id, deve
coincidere con quello nel percorso, altrimenti 422 VALIDATION_ERROR (code: "MISMATCH").
# Prima sincronizzazione del cliente 4521: l'area non esiste ancora → creata (201)curl -X PUT https://api.aitrack.it/api/v3/areas/by-external-id/erp-cliente-4521 \ -H "X-API-Key: $AITRACK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Cliente 4521 — Rossi Trasporti", "type": "circle", "center": { "lat": 45.4642, "lng": 9.19 }, "radius_m": 150 }'{ "data": { "id": 88, "name": "Cliente 4521 — Rossi Trasporti", "color": "#3b82f6", "type": "circle", "center": { "lat": 45.4642, "lng": 9.19 }, "radius_m": 150, "buffer_m": null, "points": [], "external_id": "erp-cliente-4521", "dwell_seconds": 0, "metrics": { "area_m2": 70686, "perimeter_m": 942, "length_m": null }, "bounds": { "south": 45.4629, "west": 9.1881, "north": 45.4655, "east": 9.1919 }, "created_at": "2026-09-27T02:00:00.000Z" }, "meta": { "upsert": "created" }}La notte dopo, la stessa chiamata con lo stesso externalId — per esempio col centro aggiornato,
perché il cliente ha cambiato sede — aggiorna l’area 88 invece di crearne una nuova (200,
meta.upsert: "updated"); come su PATCH, solo i campi inviati cambiano:
{ "data": { "id": 88, "name": "Cliente 4521 — Rossi Trasporti", "center": { "lat": 45.4650, "lng": 9.1905 } }, "meta": { "upsert": "updated" } }Risposta 201 (creata, header Location) o 200 (aggiornata) — distinguibile anche da
meta.upsert ("created" | "updated"). 400 BAD_REQUEST se l’externalId nel percorso non è
valido; 422 VALIDATION_ERROR se i dati non sono validi o se il corpo contiene un external_id
diverso da quello nel percorso (MISMATCH).
GET /api/v3/areas/{id}
Sezione intitolata “GET /api/v3/areas/{id}”Dettaglio di un’area. Permesso: read_areas. ?format=geojson restituisce una Feature. Id
malformato (non un intero positivo) → 400 BAD_REQUEST, prima di consultare il database. Id
valido ma inesistente o di un altro cliente → 404 NOT_FOUND (le due situazioni non si
distinguono).
curl -H "X-API-Key: $AITRACK_API_KEY" \ https://api.aitrack.it/api/v3/areas/42PATCH /api/v3/areas/{id}
Sezione intitolata “PATCH /api/v3/areas/{id}”Modifica parziale. Permesso: write_areas. Solo i campi inviati cambiano; quelli omessi
restano quelli salvati.
# Solo il nomecurl -X PATCH https://api.aitrack.it/api/v3/areas/42 \ -H "X-API-Key: $AITRACK_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "Magazzino Nord — ingresso camion"}'# Da cerchio a poligono: va inviata la nuova geometriacurl -X PATCH https://api.aitrack.it/api/v3/areas/42 \ -H "X-API-Key: $AITRACK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "polygon", "points": [ { "lat": 45.4640, "lng": 9.1895 }, { "lat": 45.4648, "lng": 9.1900 }, { "lat": 45.4638, "lng": 9.1912 } ] }'external_id e dwell_seconds si modificano come ogni altro campo — vedi Modello e
Permanenza minima. Inviare "external_id": null rimuove
il codice esterno dall’area (per esempio se smetti di sincronizzarla da un sistema esterno);
ometterlo lascia quello già salvato, come per ogni altro campo di una PATCH parziale.
Risposta 200 con l’area completa aggiornata (stessa forma di GET). 400 BAD_REQUEST se l’id
nel percorso è malformato (non un intero positivo). 404 NOT_FOUND se non trovata, 422 VALIDATION_ERROR se i dati non sono validi.
DELETE /api/v3/areas/{id}
Sezione intitolata “DELETE /api/v3/areas/{id}”Elimina un’area. Permesso: write_areas. Risposta 204 senza corpo. 400 BAD_REQUEST se l’id è
malformato. 404 NOT_FOUND se non trovata o non tua.
curl -X DELETE https://api.aitrack.it/api/v3/areas/42 \ -H "X-API-Key: $AITRACK_API_KEY"POST /api/v3/areas/import
Sezione intitolata “POST /api/v3/areas/import”Importa più aree da GeoJSON in una chiamata. Permesso: write_areas. Massimo 200 aree per
chiamata (le feature oltre la 200ª vengono scartate e segnalate in errors, le prime 200 si
processano comunque). Corpo: una FeatureCollection, una Feature o una Geometry nuda, come
corpo diretto oppure dentro { "geojson": ... }. Una feature non valida non blocca le altre.
?dry_run=true (o "dry_run": true nel corpo) valida senza salvare nulla: utile per mostrare
all’utente cosa importerebbe prima di confermare.
Una feature con properties.external_id aggiorna l’area esistente con quel codice invece di
duplicarla (elencata in data.updated, non in data.created) — stessa logica di
PUT .../by-external-id, utile per importare
un’esportazione GeoJSON già arricchita con i codici del proprio sistema. properties.dwell_seconds
imposta la permanenza minima di ogni area importata, con la stessa regola di POST/PATCH diretti.
Una geometria Multi* (MultiPoint, MultiLineString, MultiPolygon) con properties.external_id
sulla stessa feature produrrebbe più aree con lo stesso codice: viene rifiutata con l’errore
MULTI_GEOMETRY_EXTERNAL_ID — dividerla in una feature per parte, ciascuna col proprio
external_id, oppure ometterlo.
curl -X POST "https://api.aitrack.it/api/v3/areas/import?dry_run=true" \ -H "X-API-Key: $AITRACK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "name": "Cantiere Sud", "radius": 100 }, "geometry": { "type": "Point", "coordinates": [9.195, 45.460] } }, { "type": "Feature", "properties": { "name": "Deposito" }, "geometry": { "type": "Polygon", "coordinates": [[[9.10,45.40],[9.10,45.40]]] } } ] }'{ "data": { "dry_run": true, "valid": 1, "created": [], "updated": [], "errors": [{ "index": 1, "code": "errPolygonMin" }], "warnings": [] }}curl -X POST https://api.aitrack.it/api/v3/areas/import \ -H "X-API-Key: $AITRACK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "name": "Cantiere Sud", "radius": 100 }, "geometry": { "type": "Point", "coordinates": [9.195, 45.460] } }, { "type": "Feature", "properties": { "name": "Cliente 4521", "external_id": "erp-cliente-4521" }, "geometry": { "type": "Point", "coordinates": [9.19, 45.4642] } } ] }'{ "data": { "dry_run": false, "valid": 2, "created": [{ "index": 0, "id": 91 }], "updated": [{ "index": 1, "id": 88, "external_id": "erp-cliente-4521" }], "errors": [], "warnings": [] }}Risposta 200 se dry_run, oppure se la chiamata reale ha solo aggiornato aree esistenti
(nessuna creata); 201 se almeno un’area è stata creata; 422 se nessuna era valida.
errors[] mescola due famiglie di codici, distinguibili dalla presenza di message:
- dalla lettura del GeoJSON (solo
indexecode):errGeoJsonInvalid,errGeometryUnsupported,errCoordinates,errTooManyPoints,errPolygonMin,errPolylineMin,errTooManyFeatures(quest’ultimo conindex: -1, quando ci sono più di 200 feature),MULTI_GEOMETRY_EXTERNAL_ID(index,codeemessage: unexternal_idsu una geometriaMulti*). - dalla validazione dell’area risultante (
index,code,message): gli stessi codici della tabella Codici di validazione qui sotto.
warnings[] (non bloccanti): warnHolesIgnored (i buchi interni di un poligono vengono ignorati),
warnSimplifiedDuplicates (vertici quasi coincidenti semplificati).
GET /api/v3/areas/{id}/contains
Sezione intitolata “GET /api/v3/areas/{id}/contains”Il punto lat/lng è dentro l’area? Permesso: read_areas. Stessa regola geometrica del motore
che genera gli eventi di ingresso/uscita, quindi risponde in modo coerente con le notifiche che
riceve chi ha configurato un avviso su quell’area.
| Parametro | Obbligatorio |
|---|---|
lat |
sì |
lng |
sì |
curl -H "X-API-Key: $AITRACK_API_KEY" \ "https://api.aitrack.it/api/v3/areas/42/contains?lat=45.4642&lng=9.19"{ "data": { "inside": true, "distance_m": -37.5 } }distance_m è negativa dentro l’area (metri dal bordo verso l’interno), positiva fuori; può
essere null se non calcolabile. lat/lng mancanti o fuori range, oppure id malformato →
400 BAD_REQUEST. Id valido ma di un’area inesistente o di un altro cliente → 404 NOT_FOUND.
GET /api/v3/areas/{id}/events
Sezione intitolata “GET /api/v3/areas/{id}/events”Entrate e uscite registrate dalle regole di notifica geofence del proprietario della chiave su
quest’area — con una permanenza minima impostata su dwell_seconds,
le entrate qui elencate rispettano quella regola: chi ha solo attraversato l’area non compare.
Permesso: read_areas.
| Parametro | Default | Note |
|---|---|---|
event |
— | entry | exit — filtra solo ingressi o solo uscite; altro valore → 400 BAD_REQUEST |
device |
— | filtro per IMEI |
from / to |
— | finestra temporale, ISO 8601; non parsabile → 400 BAD_REQUEST |
limit |
50 | 1–200 |
offset |
0 |
curl -H "X-API-Key: $AITRACK_API_KEY" \ "https://api.aitrack.it/api/v3/areas/42/events?event=entry&limit=50"{ "data": [ { "id": 5521, "area_id": 42, "device": "352093081452312", "event": "entry", "position": { "lat": 45.4641, "lng": 9.1899 }, "timestamp": "2026-09-27T08:59:00.000Z" } ], "meta": { "pagination": { "limit": 50, "offset": 0, "count": 1 } }}Id malformato → 400 BAD_REQUEST, prima di consultare il database. Id valido ma di un’area
inesistente o di un altro cliente → 404 NOT_FOUND.
Codici di validazione
Sezione intitolata “Codici di validazione”Ogni voce è un elemento di details[] nella risposta 422 VALIDATION_ERROR di POST/PATCH/
PUT .../by-external-id (creazione, modifica e upsert diretti) — su POST /areas/import gli
stessi codici compaiono in errors[].code insieme a quelli della lettura del GeoJSON, vedi sopra.
external_id già usato da un’altra area non è un errore di validazione: risponde 409 CONFLICT
(senza details[]) solo su POST /areas — vedi creazione e
upsert qui sopra.
| Campo | Codice | Quando |
|---|---|---|
name |
REQUIRED |
nome mancante o vuoto |
name |
TOO_LONG |
oltre 100 caratteri |
type |
INVALID |
mancante o diverso da circle/polygon/polyline/point |
color |
INVALID |
non nel formato #rrggbb |
center |
REQUIRED |
manca il centro su un cerchio o un luogo |
center |
INVALID_COORDINATES |
lat/lng del centro fuori range o non numerici |
points |
INVALID_COORDINATES |
un punto ha lat/lng fuori range o non numerici |
external_id |
INVALID |
non rispetta il formato: 1–100 caratteri tra lettere, numeri e . _ : / @ # - |
external_id |
MISMATCH |
solo su PUT .../by-external-id/{externalId}: il corpo contiene un external_id diverso da quello nel percorso |
dwell_seconds |
OUT_OF_RANGE |
non è un intero tra 0 e 86400 |
points |
TOO_FEW_POINTS |
meno di 3 punti su un poligono, meno di 2 su una traccia |
points |
TOO_MANY_POINTS |
oltre 2000 punti |
points |
SELF_INTERSECTION |
i lati del poligono si incrociano |
radius_m |
OUT_OF_RANGE |
sotto 25 m (solo cerchio) o sopra 100000 m (cerchio e luogo) |
buffer_m |
OUT_OF_RANGE |
fuori da 5–5000 m (solo traccia) |
geojson |
INVALID_GEOJSON |
{ "geojson": ... } non contiene una geometria valida |
| (nessuno) | INVALID_BODY |
il corpo della richiesta non è un oggetto JSON |
GeoJSON: input e output
Sezione intitolata “GeoJSON: input e output”Per creare o modificare un’area da GeoJSON, il corpo di POST/PATCH /areas/{id} accetta
{ "geojson": <Feature | Geometry> } — una geometria per chiamata. POST /areas/import accetta
invece direttamente una FeatureCollection, una Feature o una Geometry nuda (anche dentro
{ "geojson": ... }), fino a 200 elementi, con Multi* che diventano un’area per parte:
| Geometria GeoJSON | Diventa | Note |
|---|---|---|
Point |
point (luogo, raggio 50 m) |
oppure circle se la feature ha properties.radius |
LineString |
polyline |
properties.bufferDistance (o buffer_distance) imposta il margine, altrimenti 50 m |
Polygon |
polygon |
i buchi interni (anelli oltre il primo) sono scartati con l’avviso warnHolesIgnored |
MultiPoint / MultiLineString / MultiPolygon |
più aree | solo su /areas/import, un’area per parte |
properties.name (o properties.title) e properties.color (o properties.stroke, se #rrggbb)
diventano name/color se il corpo della richiesta non li sovrascrive. Stessa regola per
properties.external_id e properties.dwell_seconds → external_id/dwell_seconds.
In lettura, ?format=geojson su GET /areas e GET /areas/{id} restituisce una Feature (o una
FeatureCollection sull’elenco) RFC 7946:
{ "data": { "type": "Feature", "id": 42, "geometry": { "type": "Point", "coordinates": [9.19, 45.4642] }, "properties": { "id": 42, "name": "Magazzino Nord", "color": "#3b82f6", "type": "circle", "radius": 150, "external_id": null, "dwell_seconds": 0, "metrics": { "area_m2": 70686, "perimeter_m": 942, "length_m": null }, "created_at": "2026-09-27T09:00:00.000Z" } }}