/api/v1/datasetsReturns the 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 header of every authenticated request. The backend deducts the exact credit consumed based on the number of rows returned. 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 tabAll JSON responses use the same envelope, both for success and error cases. The success field is the source of truth: ignore date 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 | If the request has been successfully completed. |
data | any | null | Preloaded payload for each endpoint, null on error. |
error | object | null | Object with code fields, message, and details present only in case of error. |
meta.requestId | string | Correlation ID, to be cited in support tickets. |
They use standard HTTP codes for errors. Calls above the rate limit receive 429 Too Many Requests with a header of Retry-After; queries without credits receive 402 Payment Required.
| Active | Code | Meaning |
|---|---|---|
| 400 | BAD_REQUEST | Invalid parameters (slug, limit, filters). |
| 401 | UNAUTHORIZED | Missing or invalid API key. |
| 402 | NO_CREDITS | Insufficient credits to complete the query. |
| 404 | NOT_FOUND | The slug dataset was not found or published. |
| 429 | RATE_LIMITED | Exceeded limit of 120 requests per minute; use the header Retry-After. |
| 500 | INTERNAL_ERROR | Internal error; cite requestId to support. |
/api/v1/datasetsReturns the 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 through embedding (using 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}Full header of the dataset: title, description, license, current version, row count, file size, list of tags, link to methodology and changelog.
/api/v1/datasets/{slug}/schemaJSON Schema of the fields: column name, type (string, integer, number, boolean, datetime), nullable, description, example. Generated from the Parquet header when available.
/api/v1/datasets/{slug}/sampleSample preview (max 100 lines). Public endpoint, does not consume credits and is cached for 5 minutes.
/api/v1/datasets/{slug}/methodologyMarkdown of methodology: sources, selection criteria, transformations, refresh frequency, QA validations.
/api/v1/datasets/{slug}/changelogChangelog in Markdown format following semantic versioning (breaking changes, schema differences, fixes, deprecations).
/api/v1/datasets/{slug}/related?limit=5Dataset related to the table dataset_relations (manually curated relationships) with a fallback on related_dataset_slugs[] when empty.
/api/v1/datasets/relations/graphD3-ready graph (nodes[] / edges[]) of all relationships between datasets. Caching response for 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}/queryExecutes a query with filters on the dataset and returns rows in JSON (CSV/NDJSON format via the Accept header). The body accepts filters, columns, limit (max 10,000), and 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/streamServer-Sent Events variant: returns rows as data: NDJSON events. Suitable for multi-million-row datasets without materializing them in memory. Same body as /query; here limit can go up to 1,000,000.
/api/v1/datasets/{slug}/downloadDownload the full file (CSV/JSON) in the current version. Credit consumption = total number of rows in the dataset.
Classic pagination with offset has O(N) cost: on files with over a million lines, each page requires scrolling through all previous lines. The keyset pagination solves the problem: the server encodes the position of the last returned row in an opaque cursor (Base64 encoded){"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();