/api/v1/datasetsRestituisce la lista paginata dei dataset pubblicati, ordinati con featured in cima. Endpoint pubblico, no auth.
curl https://federicocalo.dev/api/v1/datasetsREST API per consumare i dataset del marketplace. Endpoint pubblici per catalogo e metadata, endpoint autenticati con API key per query pay-per-query (€5 per 1000 query, crediti che non scadono mai).
Gli endpoint di sola lettura (catalogo, metadata, schema, sample) sono pubblici e non richiedono autenticazione. Le chiamate POST /query e i download completi richiedono invece una API key personale, generabile dall'area account dopo l'acquisto dei crediti.
Registrati o accedi, poi vai su Profilo → tab API per generare la tua chiave gratuita.
X-Api-Key di ogni richiesta autenticata. Il backend deduce il credito esatto consumato in base al numero di righe restituite. curl -H 'X-Api-Key: fc_live_xxxxx' \
https://federicocalo.dev/api/v1/datasetsDal tab API del tuo profilo puoi generare gratuitamente la tua prima API key, monitorare il consumo mensile delle 100 query incluse nel piano gratuito e acquistare crediti pay-per-query aggiuntivi.
Vai al Profilo → tab API Tutte le risposte JSON usano lo stesso envelope, sia in caso di successo che di errore. Il campo success è la fonte di verità: ignora data quando success: false.
{
"success": true,
"data": { ... },
"error": null,
"meta": {
"requestId": "150d99e7-d413-4fe0-8c9c-1dea2925e709",
"timestamp": "2026-04-25T14:39:27Z",
"durationMs": 42
}
}| Campo | Tipo | Descrizione |
|---|---|---|
success | boolean | true se la richiesta è andata a buon fine. |
data | any | null | Payload tipato per ogni endpoint, null in caso di errore. |
error | object | null | Oggetto con campi code, message, details presente solo in caso di errore. |
meta.requestId | string | UUID di correlation, da citare nei ticket di supporto. |
Gli errori usano i codici HTTP standard. Le chiamate sopra il rate limit ricevono 429 Too Many Requests con header Retry-After; le query senza crediti ricevono 402 Payment Required.
| Status | Codice | Significato |
|---|---|---|
| 400 | BAD_REQUEST | Parametri non validi (slug, limit, filtri). |
| 401 | UNAUTHORIZED | API key mancante o non valida. |
| 402 | NO_CREDITS | Crediti insufficienti per completare la query. |
| 404 | NOT_FOUND | Slug dataset non trovato o non pubblicato. |
| 429 | RATE_LIMITED | Limite 120 req/min superato; usa l'header Retry-After. |
| 500 | INTERNAL_ERROR | Errore interno; cita requestId al supporto. |
/api/v1/datasetsRestituisce la lista paginata dei dataset pubblicati, ordinati con featured in cima. Endpoint pubblico, no auth.
curl https://federicocalo.dev/api/v1/datasets/api/v1/datasets/search?q={testo}&limit=10&lang=it Ricerca semantica via embedding (sentence-transformers all-MiniLM-L6-v2) con re-ranking cross-encoder. Parametri: q (2–200 char), limit (1–50, default 10), lang (it / en).
curl 'https://federicocalo.dev/api/v1/datasets/search?q=appalti+pubblici&limit=5'/api/v1/datasets/{slug}Header completo del dataset: titolo, descrizione, licenza, versione corrente, conteggio righe, dimensione file, lista tag, link a methodology e changelog.
/api/v1/datasets/{slug}/schemaJSON Schema dei campi: nome colonna, tipo (string, integer, number, boolean, datetime), nullable, descrizione, esempio. Generato dal Parquet header quando disponibile.
/api/v1/datasets/{slug}/sampleSample di anteprima (max 100 righe). Endpoint pubblico, non consuma crediti ed è cacheato 5 minuti.
/api/v1/datasets/{slug}/methodologyMarkdown della metodologia: fonti, criteri di selezione, trasformazioni, frequenza di refresh, validazioni QA.
/api/v1/datasets/{slug}/changelogChangelog markdown semver per versione (breaking changes, schema diff, fix, deprecazioni).
/api/v1/datasets/{slug}/related?limit=5 Dataset correlati basati sulla tabella dataset_relations (relazioni curate manualmente) con fallback su related_dataset_slugs[] quando vuoto.
/api/v1/datasets/relations/graph Grafo D3-ready (nodes[] / edges[]) di tutte le relazioni tra dataset. Risposta cacheata 1 ora.
Le chiamate di questa sezione consumano crediti. Il deduzione esatta è ceil(rows / 1) per il payload restituito. Tutte richiedono l'header X-Api-Key.
/api/v1/datasets/{slug}/query Esegue una query con filtri sul dataset e restituisce le righe in JSON (formato CSV/NDJSON via header Accept). Il body accetta filters, columns, limit (max 10.000), offset.
curl -X POST \
-H 'Content-Type: application/json' \
-H 'X-Api-Key: fc_live_xxxxx' \
-d '{"filters":{"region":"Lombardia"},"limit":100}' \
https://federicocalo.dev/api/v1/datasets/anac-contratti-pubblici-2024/query/api/v1/datasets/{slug}/query/stream Variante Server-Sent Events: restituisce le righe come data: events NDJSON. Adatto per dataset multi-milione di righe senza materializzare in memoria. Stesso body di /query; limit qui può salire a 1.000.000.
/api/v1/datasets/{slug}/download Download del file completo (CSV/JSON) nella versione current. Consumo crediti = numero totale di righe del dataset.
La paginazione classica con offset ha costo O(N): su file con oltre 1 milione di righe ogni pagina richiede di scorrere tutte le righe precedenti. La keyset pagination risolve il problema: il server codifica la posizione dell'ultima riga restituita in un cursor opaco (Base64 di {"lastSortValue":"…","lastId":"…"} ) e la pagina successiva ricomincia esattamente da quel punto, con costo O(1) per pagina indipendentemente dalla profondità. Regola fondamentale: cursor wins — quando invii sia cursor sia offset, offset viene ignorato.
POST /api/v1/datasets/anac-contratti-pubblici-2024/query
X-Api-Key: fc_live_xxxxx
Content-Type: application/json
{
"filters": { "region": "Lombardia" },
"limit": 100,
"sortBy": "importo",
"sortDirection": "desc"
}
// Risposta (campo nextCursor presente se ci sono altre righe)
{
"success": true,
"data": {
"paginationMode": "offset",
"rowsReturned": 100,
"nextCursor": "eyJsYXN0U29ydFZhbHVlIjoiMTIzNDU2IiwibGFzdElkIjoiMTAwIn0",
"results": [ ... ]
}
}POST /api/v1/datasets/anac-contratti-pubblici-2024/query
X-Api-Key: fc_live_xxxxx
Content-Type: application/json
{
"filters": { "region": "Lombardia" },
"limit": 100,
"sortBy": "importo",
"sortDirection": "desc",
"cursor": "eyJsYXN0U29ydFZhbHVlIjoiMTIzNDU2IiwibGFzdElkIjoiMTAwIn0"
}
// Se nextCursor è null, hai raggiunto l'ultima pagina.| Campo request | Tipo | Note |
|---|---|---|
cursor | string (Base64) | Cursor opaco dalla risposta precedente. Se presente, offset viene ignorato. |
sortBy | string | Nome colonna di ordinamento. Assente = ordine naturale del file (nessun overhead). |
sortDirection | "asc" | "desc" | Default "asc". |
limit | integer | Righe per pagina: [1, 10 000], default 100. |
| Campo response | Tipo | Note |
|---|---|---|
nextCursor | string | null | Cursor per la pagina successiva. null = ultima pagina raggiunta. |
paginationMode | "offset" | "cursor" | Indica quale strategia è stata applicata per questa risposta. |
totalRows | integer | null | Conteggio totale da metadata cached. null se non disponibile senza scan. |
import os, requests
API = "https://federicocalo.dev/api/v1"
KEY = os.environ["PORTFOLIO_API_KEY"]
# Catalogo pubblico
catalog = requests.get(f"{API}/datasets").json()
print(len(catalog["data"]), "dataset disponibili")
# Query con filtri (richiede credito)
r = requests.post(
f"{API}/datasets/anac-contratti-pubblici-2024/query",
headers={"X-Api-Key": KEY},
json={"filters": {"importo_min": 100000}, "limit": 500},
timeout=30,
)
r.raise_for_status()
rows = r.json()["data"]const API = 'https://federicocalo.dev/api/v1';
const KEY = process.env.PORTFOLIO_API_KEY;
const res = await fetch(
`${API}/datasets/istat-popolazione-comuni-2024/query`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': KEY,
},
body: JSON.stringify({ filters: { provincia: 'MI' }, limit: 200 }),
},
);
const { data } = await res.json();