Skip to main content
Return to marketplacePublic API

API Documentation Dataset

API 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).

https://www.federico-calò.com/en/blog/2022-01-31-angular-spring-boot-and-oollama-part-one.html120 req/min · 5000 req/dayData Formats: JSON - CSV - NDJSON - Server-Sent Events (SSE)

Authentication and API key

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.

  1. Buy credits from the Account → Premium page (€5 for 1,000 queries, VAT excluded).
  2. Genera una API key dall'area Account → API key: ne ottieni il valore una sola volta, conservalo in un secret manager.
  3. Passa la key sull'header X-Api-Key di ogni richiesta autenticata. Il backend deduce il credito esatto consumato in base al numero di righe restituite.
curl
curl -H 'X-Api-Key: fc_live_xxxxx' \
  https://federicocalo.dev/api/v1/datasets

Manage your API key and credits

From 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

Response format (Envelope)

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.

JSON
{
  "success": true,
  "data": { ... },
  "error": null,
  "meta": {
    "requestId": "150d99e7-d413-4fe0-8c9c-1dea2925e709",
    "timestamp": "2026-04-25T14:39:27Z",
    "durationMs": 42
  }
}
Fields of the response envelope
FieldTypeDescription
successbooleantrue if the request was successful
dataany | nullPayload typed for each endpoint, null in case of error
errorobject | nullObject with fields code, message, details present only in case of error.
meta.requestIdstringCorrelation ID to be mentioned in the technical support tickets.

Errors and Rate Limiting

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.

ActiveCodeMeaning
400BAD_REQUESTInvalid parameters (slug, limits, filters)
401UNAUTHORIZEDMissing or invalid API key.
402NO_CREDITSInsufficient credits to complete the request.
404NOT_FOUNDDataset slug not found or published.
429RATE_LIMITEDRate limit of 120 req/min exceeded; use the Retry-After header
500INTERNAL_ERRORInternal error; cite requestID to support team.

Inventory Catalogue List

GET/api/v1/datasets

Returns paginated list of published datasets, ordered with featured at top. Public endpoint, no auth.

curl
curl https://federicocalo.dev/api/v1/datasets
GET/api/v1/datasets/search?q={testo}&limit=10&lang=it

Semantic 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
curl 'https://federicocalo.dev/api/v1/datasets/search?q=appalti+pubblici&limit=5'

Meta data

GET/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.

GET/api/v1/datasets/{slug}/schema

JSON schema of fields: column name, type (string, integer, number, boolean, datetime), nullability, description, example. Generated from the Parquet header when available.

GET/api/v1/datasets/{slug}/sample

Preview sample (up to 100 lines). Public endpoint that does not consume credits and is cached for five minutes.

GET/api/v1/datasets/{slug}/methodology

Markdown methodology: Sources Selection criteria Transformations Refresh frequency QA validations

GET/api/v1/datasets/{slug}/changelog

Changelog in Markdown SemVer for versions: (Breaking Changes, Schema Diff, Fix, Deprecations)

Exploration

GET/api/v1/datasets/{slug}/related?limit=5

Related datasets based on the table dataset_relations (manually curated relations) with fallback to related_dataset_slugs[] when empty.

GET/api/v1/datasets/relations/graph

D3-ready graph (nodes[] / edges[]) of all relationships between datasets. Caching response 1 hour.

Search and Download

Calls in this section consume credits. The exact deduction is ceil(rows / 1) for the returned payload. All require the header X-Api-Key.

POST/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
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
POST/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.

GET/api/v1/datasets/{slug}/download

Download the complete file (CSV/Parquet/JSON) in version current. Credit consumption = total number of rows in dataset.

PERFORMANCE

Cursor-Based Pagination

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.

json — prima pagina (no cursor)
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": [ ... ]
  }
}
json — pagina successiva (con cursor)
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 requestTipoNote
cursorstring (Base64)Cursor opaco dalla risposta precedente. Se presente, offset viene ignorato.
sortBystringNome colonna di ordinamento. Assente = ordine naturale del file (nessun overhead).
sortDirection"asc" | "desc"Default "asc".
limitintegerRighe per pagina: [1, 10 000], default 100.
Campo responseTipoNote
nextCursorstring | nullCursor per la pagina successiva. null = ultima pagina raggiunta.
paginationMode"offset" | "cursor"Indica quale strategia è stata applicata per questa risposta.
totalRowsinteger | nullConteggio totale da metadata cached. null se non disponibile senza scan.

Complete examples

Requests Library for Python is used to make HTTP requests in the application.

python
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"]

Fetch API is not supported by this browser or Node environment.

javascript
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();