API REST per entità territoriali, logistica, CRM, immobili, fornitori e intelligence geografica. Autenticazione Bearer, risposta JSON versionata, quota per tenant.
https://your-host/api/v1
Authorization: Bearer gsm_...
Tre passi per la prima integrazione funzionante.
Verifica che il token sia valido:
curl -H "Authorization: Bearer " \
https://your-host/api/v1/auth/me
{
"tenant_id": "xxxxxxxx-...",
"user_id": "yyyyyyyy-...",
"role": "owner",
"permissions": [
"entities.read", "entities.write",
"logistics.read", "logistics.write",
"sales.read", "sales.write", ...
],
"auth_mode": "api_token"
}
Le entità territoriali sono il cuore del tuo CRM geografico: clienti, magazzini, property, cantieri.
curl -X POST \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"name": "Sede Principale",
"entity_type": "customer",
"city": "Milano",
"address_line": "Via Torino 12",
"latitude": 45.4642,
"longitude": 9.1900,
"metadata": { "sector": "tech" }
}' \
https://your-host/api/v1/entities
Tipi disponibili: customer, lead, prospect, property, warehouse, site, building, unit, e altri.
{
"id": "aaaaaaaa-...",
"code": "ENT-0001",
"name": "Sede Principale",
"entity_type": "customer",
"status": "active",
"city": "Milano",
"address_line": "Via Torino 12",
"latitude": 45.4642,
"longitude": 9.19,
"metadata": { "sector": "tech" },
"created_at": "2026-04-11T10:00:00Z"
}
id per le operazioni successive.Cerca le entità vicine a un punto GPS entro un raggio (PostGIS).
curl -H "Authorization: Bearer " \ "https://your-host/api/v1/entities/nearby\ ?latitude=45.4642\ &longitude=9.19\ &radius_meters=5000\ &entity_type=customer"
Profilo completo di una città (meteo + business + demo):
curl -H "Authorization: Bearer " \ "https://your-host/api/v1/cities/Milano\ ?country=IT"
[
{
"id": "aaaaaaaa-...",
"name": "Sede Principale",
"entity_type": "customer",
"city": "Milano",
"latitude": 45.4642,
"longitude": 9.19,
"distance_meters": 120.5
},
...
]
distance_meters viene calcolato da PostGIS con ST_Distance in proiezione metrica. Ordine: dal più vicino al più lontano.
Riferimento live generato dalla spec OpenAPI (sempre allineato all'API). Documentazione interattiva su Swagger · ReDoc · spec grezza openapi.json.
Carico la spec…
| Metodo | Endpoint | Descrizione |
|---|---|---|
|
endpoint totali nella spec.
| Metodo | Endpoint | Descrizione | Permesso |
|---|---|---|---|
| GET | /api/v1/entities | Lista entità con filtri (entity_type, status, city, limit, offset) | entities.read |
| POST | /api/v1/entities | Crea una nuova entità | entities.write |
| GET | /api/v1/entities/{id} | Dettaglio singola entità | entities.read |
| PATCH | /api/v1/entities/{id} | Aggiorna campi (parziale) | entities.write |
| DELETE | /api/v1/entities/{id} | Elimina entità | entities.write |
| GET | /api/v1/entities/nearby | Entità entro raggio GPS (PostGIS) | entities.read |
| POST | /api/v1/entities/bulk | Import massivo (JSON/CSV) | entities.write |
| GET | /api/v1/entities/{id}/locations | Storico posizioni GPS | entities.read |
| Metodo | Endpoint | Descrizione | Permesso |
|---|---|---|---|
| GET | /api/v1/leads | Lista lead (status, owner, limit) | sales.read |
| POST | /api/v1/leads | Crea lead | sales.write |
| PATCH | /api/v1/leads/{id} | Aggiorna lead (status, assignee, note) | sales.write |
| GET | /api/v1/opportunities | Lista opportunità commerciali | sales.read |
| POST | /api/v1/opportunities | Crea opportunità | sales.write |
| GET | /api/v1/customers | Lista clienti | sales.read |
| POST | /api/v1/activities | Log attività (visita, chiamata, email) | sales.write |
| GET | /api/v1/territories | Zone commerciali (poligoni GIS) | sales.read |
| Metodo | Endpoint | Descrizione | Permesso |
|---|---|---|---|
| GET | /api/v1/deliveries | Lista consegne (status, date_range, assignee) | logistics.read |
| POST | /api/v1/deliveries | Crea consegna | logistics.write |
| PATCH | /api/v1/deliveries/{id}/status | Aggiorna stato consegna (+ GPS opzionale) | logistics.write |
| GET | /api/v1/vehicles | Lista veicoli del tenant | logistics.read |
| GET | /api/v1/warehouses | Lista magazzini e hub logistici | logistics.read |
| GET | /api/v1/routing/route | Calcolo percorso ottimale tra punti | logistics.read |
| POST | /api/v1/routing/optimize | Ottimizzazione multi-stop (async job) | logistics.write |
| Metodo | Endpoint | Descrizione | Permesso |
|---|---|---|---|
| GET | /api/v1/suppliers | Lista fornitori con filtri | suppliers.read |
| POST | /api/v1/suppliers | Crea fornitore | suppliers.write |
| GET | /api/v1/suppliers/nearby | Fornitori nel raggio GPS | suppliers.read |
| PATCH | /api/v1/suppliers/{id} | Aggiorna fornitore | suppliers.write |
| Metodo | Endpoint | Descrizione | Piano |
|---|---|---|---|
| GET | /api/v1/cities/{city} | Risolve città in coordinate e metadati canonici | Base |
| GET | /api/v1/cities/{city}/weather | Meteo corrente e previsioni (1-7 giorni) | Base |
| GET | /api/v1/cities/{city}/business | Indicatori economici e densità imprese | Business |
| GET | /api/v1/cities/{city}/real-estate | Indicatori immobiliari (potere d'acquisto, mercato) | Business |
| GET | /api/v1/cities/{city}/population | Demografica ISTAT (fasce età, densità) | Premium |
| GET | /api/v1/cities/{city}/profile | Aggregato multi-dominio (tutto in una sola call) | Premium |
| GET | /api/v1/metadata/sources | Catalogo fonti dati | — |
| GET | /api/v1/metadata/plans | Piani con quota RPM ed endpoint inclusi | — |
| Metodo | Endpoint | Descrizione | Permesso |
|---|---|---|---|
| GET | /api/v1/geocode | Geocoding singolo indirizzo → lat/lon | utilities.read |
| GET | /api/v1/reverse-geocode | Reverse geocoding lat/lon → indirizzo | utilities.read |
| POST | /api/v1/bulk/geocode | Batch geocoding (async, fino a 800 indirizzi) | utilities.write |
| GET | /api/v1/jobs/{id} | Stato job async (polling) | utilities.read |
| Metodo | Endpoint | Descrizione | Permesso |
|---|---|---|---|
| POST | /auth/login | Login con API token → JWT mobile | — |
| GET | /tasks/assigned | Consegne assegnate all'agente corrente | logistics.read |
| PATCH | /tasks/{id}/status | Aggiorna stato consegna + GPS opzionale | logistics.write |
| GET | /map/points | Bundle mappa: consegne + hub nel raggio | logistics.read |
| GET | /sales/items | Lead e opportunità assegnate all'agente | sales.read |
| POST | /sales/visit | Log visita/chiamata sul campo | sales.write |
| GET | /properties | Entità immobiliari del tenant | entities.read |
| PATCH | /properties/{id}/note | Aggiungi nota di campo a un immobile | entities.write |
| GET | /suppliers/nearby | Fornitori nel raggio GPS corrente | suppliers.read |
| GET | /sync/status | Stato sincronizzazione offline | logistics.read |
Snippets pronti all'uso. Il token viene pre-compilato se lo hai inserito in alto.
# Crea entità
curl -X POST \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{"name":"Cliente A","entity_type":"customer","city":"Roma","latitude":41.9028,"longitude":12.4964}' \
/api/v1/entities
# Cerca entità vicine (5km)
curl -H "Authorization: Bearer " \
"/api/v1/entities/nearby?latitude=41.9028&longitude=12.4964&radius_meters=5000"
import requests
TOKEN = ""
BASE = "https://your-host"
H = {"Authorization": f"Bearer {TOKEN}"}
# Crea entità
r = requests.post(f"{BASE}/api/v1/entities", headers=H, json={
"name": "Cliente A", "entity_type": "customer",
"city": "Roma", "latitude": 41.9028, "longitude": 12.4964,
})
entity_id = r.json()["id"]
# Nearby
nearby = requests.get(f"{BASE}/api/v1/entities/nearby", headers=H,
params={"latitude": 41.9028, "longitude": 12.4964, "radius_meters": 5000})
print(nearby.json())
const TOKEN = "";
const BASE = "https://your-host";
const H = { "Authorization": `Bearer ${TOKEN}`, "Content-Type": "application/json" };
// Crea entità
const res = await fetch(`${BASE}/api/v1/entities`, {
method: "POST", headers: H,
body: JSON.stringify({ name:"Cliente A", entity_type:"customer",
city:"Roma", latitude:41.9028, longitude:12.4964 }),
});
const { id } = await res.json();
// Nearby
const nearby = await fetch(
`${BASE}/api/v1/entities/nearby?latitude=41.9028&longitude=12.4964&radius_meters=5000`,
{ headers: H }
);
console.log(await nearby.json());
# Profilo città completo
curl -H "Authorization: Bearer " \
"/api/v1/cities/Milano?country=IT"
# Crea consegna
curl -X POST \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{"destination_entity_id":"uuid-entita","scheduled_date":"2026-04-15","priority":"high"}' \
/api/v1/deliveries
import requests
TOKEN = ""
BASE = "https://your-host"
H = {"Authorization": f"Bearer {TOKEN}"}
# Profilo città
city = requests.get(f"{BASE}/api/v1/cities/Milano", headers=H, params={"country":"IT"})
data = city.json()
print(data["location"]["canonical_name"], data["data"])
# Crea consegna
delivery = requests.post(f"{BASE}/api/v1/deliveries", headers=H, json={
"destination_entity_id": "uuid-entita",
"scheduled_date": "2026-04-15",
"priority": "high",
})
print(delivery.json()["id"])
const TOKEN = "";
const BASE = "https://your-host";
const H = { "Authorization": `Bearer ${TOKEN}` };
// Profilo città
const city = await fetch(`${BASE}/api/v1/cities/Milano?country=IT`, { headers: H });
const { location, data } = await city.json();
console.log(location.canonical_name, data);
// Crea consegna
const delivery = await fetch(`${BASE}/api/v1/deliveries`, {
method: "POST",
headers: { ...H, "Content-Type": "application/json" },
body: JSON.stringify({ destination_entity_id: "uuid-entita",
scheduled_date: "2026-04-15", priority: "high" }),
});
console.log((await delivery.json()).id);
# Login mobile (ottieni JWT breve durata)
curl -X POST \
-H "Content-Type: application/json" \
-d '{"api_token":""}' \
/api/v1/mobile/v1/auth/login
# Aggiorna stato consegna dal campo
curl -X PATCH \
-H "Authorization: Bearer <jwt_mobile>" \
-H "Content-Type: application/json" \
-d '{"status":"delivered","latitude":45.46,"longitude":9.19,"note":"Consegnato al portiere"}' \
/api/v1/mobile/v1/tasks/<delivery_id>/status
import requests
API_TOKEN = ""
BASE = "https://your-host/api/v1/mobile/v1"
# Login mobile
login = requests.post(f"{BASE}/auth/login", json={"api_token": API_TOKEN})
jwt = login.json()["access_token"]
MH = {"Authorization": f"Bearer {jwt}"}
# Leggi task assegnati
tasks = requests.get(f"{BASE}/tasks/assigned", headers=MH)
for t in tasks.json():
print(t["code"], t["status"])
# Aggiorna stato
requests.patch(f"{BASE}/tasks/{tasks.json()[0]['task_id']}/status",
headers=MH, json={"status":"delivered","latitude":45.46,"longitude":9.19})
const BASE = "https://your-host/api/v1/mobile/v1";
const API_TOKEN = "";
// Login mobile
const login = await fetch(`${BASE}/auth/login`, {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ api_token: API_TOKEN }),
});
const { access_token } = await login.json();
const MH = { "Authorization": `Bearer ${access_token}` };
// Task assegnati
const tasks = await (await fetch(`${BASE}/tasks/assigned`, { headers: MH })).json();
// Aggiorna primo task
await fetch(`${BASE}/tasks/${tasks[0].task_id}/status`, {
method: "PATCH", headers: { ...MH, "Content-Type": "application/json" },
body: JSON.stringify({ status: "delivered", latitude: 45.46, longitude: 9.19 }),
});
# 401 — token mancante o revocato
# {"detail": "Bearer token invalid or expired."}
# 403 — permesso mancante
# {"detail": "Permission denied: requires logistics.write"}
# 429 — quota superata
# Headers: Retry-After: 12
# {"error":"rate_limit_exceeded","retry_after_seconds":12}
# 422 — validazione payload
# {"detail":[{"loc":["body","name"],"msg":"field required"}]}
import requests
TOKEN = ""
r = requests.get("https://your-host/api/v1/entities",
headers={"Authorization": f"Bearer {TOKEN}"})
if r.status_code == 200:
entities = r.json()
elif r.status_code == 401:
raise Exception("Token non valido o revocato")
elif r.status_code == 403:
raise Exception(f"Permesso negato: {r.json()['detail']}")
elif r.status_code == 429:
retry_after = r.headers.get("Retry-After", "?")
raise Exception(f"Quota superata. Riprova tra {retry_after}s")
else:
r.raise_for_status()
async function apiCall(url, options = {}) {
const TOKEN = "";
const res = await fetch(url, {
headers: { "Authorization": `Bearer ${TOKEN}`, "Accept": "application/json" },
...options,
});
if (res.status === 429) {
const retryAfter = res.headers.get("Retry-After") || "?";
throw new Error(`Quota superata. Riprova tra ${retryAfter}s`);
}
const body = await res.json();
if (!res.ok) throw new Error(body?.detail || res.statusText);
return body;
}
{
"request_id": "uuid",
"version": "v1",
"query": { "city": "Milano", "country": "IT" },
"location": {
"canonical_name": "Milano",
"istat_code": "015146",
"boundary_level": "comune",
"latitude": 45.4642,
"longitude": 9.19,
"country_code": "IT",
"source": "admin_boundaries_name"
},
"data": { ... },
"sources": ["ISTAT", "open-meteo"],
"warnings": [],
"generated_at": "2026-04-11T10:30:00Z"
}
admin_boundaries_code | Trovato in DB per codice ISTAT/NUTS |
admin_boundaries_name | Trovato in DB per nome |
geocoder | Risolto via geocoder esterno |
geocoder+reverse | Geocoder + reverse lookup |
warnings non vuoto indica che alcuni domini non erano disponibili. La risposta è comunque valida per i domini presenti.
X-Request-Id | UUID della request, utile per debug |
Retry-After | Secondi da attendere (solo su 429) |
X-API-Version | Versione contratto endpoint |
Prova qualsiasi endpoint direttamente da questa pagina.
Da /api/v1/metadata/plans