Documentation / Concepts / Pagination

Pagination

The API uses offset-based pagination. Every page costs credits — plan your iteration accordingly.

Paginated endpoints

Pagination is available on the endpoints that return lists:

  • GET /v1/transactions — DVF transactions (max limit 200)
  • GET /v1/portfolio — portfolio properties (max limit 100)
  • GET /v1/alerts — webhook alerts (max limit 50)

Parameters

ParameterDefaultDescription
limit50Results per page. Range: 1–200 for /v1/transactions.
offset0Start index (0-based). Pass the previous page's next_cursor value.

The next_cursor field

Every paginated response includes a next_cursor field:

  • A non-null value (number): there are more results. Use this value as the offset of the next request.
    next_cursor = offset + number of results returned
  • A null value: you are on the last page.
{
  "transactions": [ /* 50 objects */ ],
  "total_found": 312,
  "next_cursor": 50,
  "_credits": { "used": 5, "remaining": 95 },
  "query_time_ms": 143
}

// Last page:
{
  "transactions": [ /* 12 objects */ ],
  "total_found": 312,
  "next_cursor": null,
  "_credits": { "used": 5, "remaining": 90 },
  "query_time_ms": 121
}

Example — walking through every page

curl "https://mcp.normi.fr/v1/transactions?code_postal=75011&type_local=Appartement&limit=50&offset=0" \
  -H "X-API-Key: normi_your_token"
# Use next_cursor as the offset for the next page
curl "https://mcp.normi.fr/v1/transactions?code_postal=75011&type_local=Appartement&limit=50&offset=50" \
  -H "X-API-Key: normi_your_token"
import requests

def fetch_all(code_postal, type_local, api_key, limit=50):
    url = "https://mcp.normi.fr/v1/transactions"
    headers = {"X-API-Key": api_key}
    offset = 0
    all_results = []

    while True:
        r = requests.get(url, headers=headers, params={
            "code_postal": code_postal,
            "type_local": type_local,
            "limit": limit,
            "offset": offset,
        })
        data = r.json()
        all_results.extend(data["transactions"])

        if data.get("next_cursor") is None:
            break
        offset = data["next_cursor"]

    return all_results
Every page costs credits
Every paginated call to GET /v1/transactions costs 5 credits, whatever the page size. Narrow your filters (type_local, price, surface, date) to cut the number of pages and avoid spending your quota for nothing.

total_found vs returned results

The total_found field gives the total number of results matching your filters (before pagination). That lets you work out the number of pages:

total_pages = ceil(total_found / limit)
// e.g. 312 results / 50 per page = 7 pages (the last one with 12 results)
Result stability
DVF transactions rarely change (an annual release, no edits). Result order is stable across pages, but not guaranteed if new data is loaded while you iterate.