/api/v1/datasetsReturns paginated list of published datasets, ordered with featured at top. Public endpoint, no auth.
curl https://federicocalo.dev/api/v1/datasetsAPI for consuming the marketplace datasets. Public endpoints for catalog and metadata, authenticated endpoints with API key for pay-per-query queries (€5 per 1,000 queries, credits that never expire).
The read-only endpoints (catalog, metadata, schema, sample) are public and do not require authentication. The POST /query calls and complete downloads, however, require a personal API key, which can be generated from the account area after purchasing credits.
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/datasetsFrom the API tab of your profile, you can generate your first API key for free, monitor your monthly usage of the included 100 free queries, and purchase additional pay-per-query credits.
Go to Profile → API tab All JSON responses use the same envelope, both for success and error cases. The success field is the source of truth: ignore data when success: false.
{
"success": true,
"data": { ... },
"error": null,
"meta": {
"requestId": "150d99e7-d413-4fe0-8c9c-1dea2925e709",
"timestamp": "2026-04-25T14:39:27Z",
"durationMs": 42
}
}| Field | Type | Description |
|---|---|---|
success | boolean | true if the request was successful |
data | any | null | Payload typed for each endpoint, null in case of error |
error | object | null | Object with fields code, message, details present only in case of error. |
meta.requestId | string | Correlation ID to be mentioned in the technical support tickets. |
Errors use standard HTTP codes. Calls above the rate limit receive 429 Too Many Requests with header Retry-After; queries without credits receive 402 Payment Required.
| Active | Code | Meaning |
|---|---|---|
| 400 | BAD_REQUEST | Invalid parameters (slug, limits, filters) |
| 401 | UNAUTHORIZED | Missing or invalid API key. |
| 402 | NO_CREDITS | Insufficient credits to complete the request. |
| 404 | NOT_FOUND | Dataset slug not found or published. |
| 429 | RATE_LIMITED | Rate limit of 120 req/min exceeded; use the Retry-After header |
| 500 | INTERNAL_ERROR | Internal error; cite requestID to support team. |
/api/v1/datasetsReturns paginated list of published datasets, ordered with featured at top. Public endpoint, no auth.
curl https://federicocalo.dev/api/v1/datasets/api/v1/datasets/search?q={testo}&limit=10&lang=itSemantic search via embedding (sentence-transformers all-MiniLM-L6-v2) with cross-encoder re-ranking. Parameters: 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}Complete header of the dataset: title, description, license, current version, row count, file size, list tags, methodlogy and change log links.
/api/v1/datasets/{slug}/schemaJSON schema of fields: column name, type (string, integer, number, boolean, datetime), nullability, description, example. Generated from the Parquet header when available.
/api/v1/datasets/{slug}/samplePreview sample (up to 100 lines). Public endpoint that does not consume credits and is cached for five minutes.
/api/v1/datasets/{slug}/methodologyMarkdown methodology: Sources Selection criteria Transformations Refresh frequency QA validations
/api/v1/datasets/{slug}/changelogChangelog in Markdown SemVer for versions: (Breaking Changes, Schema Diff, Fix, Deprecations)
/api/v1/datasets/{slug}/related?limit=5Related datasets based on the table dataset_relations (manually curated relations) with fallback to related_dataset_slugs[] when empty.
/api/v1/datasets/relations/graphD3-ready graph (nodes[] / edges[]) of all relationships between datasets. Caching response 1 hour.
Calls in this section consume credits. The exact deduction is ceil(rows / 1) for the returned payload. All require the header X-Api-Key.
/api/v1/datasets/{slug}/query Executes a query with filters on the dataset and returns rows in JSON (CSV/NDJSON via header Accept). The body accepts 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 Server-Sent Events variant: returns rows as data: events NDJSON. Suitable for datasets with millions of rows without materializing in memory. Same body as /query; limit here can go up to 1,000,000.
/api/v1/datasets/{slug}/download Download the complete file (CSV/Parquet/JSON) in version current. Credit consumption = total number of rows in dataset.
Classic pagination with offset has a cost of O(N): on files over 1 million rows, every page requires scanning all previous rows. The keyset pagination solves the problem: the server encodes the position of the last row returned in an opaque cursor (Base64 of {"lastSortValue":"…","lastId":"…"} ) and the next page starts exactly from that point, with a cost of O(1) per page regardless of depth. Fundamental rule: cursor wins — when you send both cursor and offset, offset is ignored.
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();