Dear community,
In our continuous efforts to deliver a smooth and reliable service, we’re making an important update to the /api/v2/assets/{uid_asset}/data endpoint that will help preserve performance, stability and scalability of our platform.
Please note:
This change does not affect the synchronous export endpoints (i.e.
/api/v2/assets/{uid_asset}/export-settings/{uid_export}/data.xlsx|csv).
These export routes will continue to operate exactly as before.
Effective in the first January release (during the week of January 12, 2026) 9 March 2026, the default limit of results returned per request will be changed:
-
The maximum number of results per page will now be 1,000 (instead of 30,000).
-
The default number of results per page (if you do not explicitly specify
limit=) will now be 100 (previously 30,000).
Why this change? As our user base expands, maintaining a fast and reliable experience for everyone requires continuous optimization. Restricting the amount of data retrieved per call allows us to balance server load and sustain the same high-quality performance as the platform scales.
What to do?
-
If you are currently requesting very large pages of data (e.g., 30,000 records at once), please update your application logic to page through results in increments of 1,000 or fewer.
-
Use the
next/previouslinks in the response to iterate through the data set safely. -
If you specify
limit=explicitly, you may request up to 1,000 records per page.
Example response after request:
{
"count": 1000,
"next": "https://kf.kobotoolbox.org/api/v2/assets/aGAcvhamUDuDTtbkyeikbD/data.json?limit=100&start=100",
"previous": null,
"results": [ … ]
}
If you have any questions or experience any issues, please reach out via our usual support channels.
Warm regards,
The KoboToolbox team
Code Examples
Python
import requests
API_URL = "https://kf.kobotoolbox.org/api/v2/assets/YOUR_ASSET_ID/data.json"
TOKEN = "YOUR_TOKEN_HERE"
headers = {"Authorization": f"Token {TOKEN}"}
start = 0
limit = 100
while True:
params = {"limit": limit, "start": start}
resp = requests.get(API_URL, headers=headers, params=params)
resp.raise_for_status()
data = resp.json()
print(f"Fetched {len(data['results'])} records (start={start})")
if not data["next"]:
break
start += limit
R
library(httr)
library(jsonlite)
api_url <- "https://kf.kobotoolbox.org/api/v2/assets/YOUR_ASSET_ID/data.json"
token <- "YOUR_TOKEN_HERE"
start <- 0
limit <- 100
repeat {
resp <- GET(api_url,
add_headers(Authorization = paste("Token", token)),
query = list(limit = limit, start = start))
stop_for_status(resp)
data <- fromJSON(content(resp, as = "text", encoding = "UTF-8"))
cat("Fetched", length(data$results), "records (start =", start, ")\n")
if (is.null(data$next)) break
start <- start + limit
}
Node.js (JavaScript)
const fetch = require("node-fetch");
const API_URL = "https://kf.kobotoolbox.org/api/v2/assets/YOUR_ASSET_ID/data.json";
const TOKEN = "YOUR_TOKEN_HERE";
async function fetchAll(limit = 1000) {
let start = 0;
while (true) {
const url = new URL(API_URL);
url.searchParams.append("limit", limit);
url.searchParams.append("start", start);
const resp = await fetch(url.toString(), {
headers: { "Authorization": `Token ${TOKEN}` },
});
const data = await resp.json();
console.log(`Fetched ${data.results.length} records (start=${start})`);
if (!data.next) break;
start += limit;
}
}
fetchAll().catch(console.error);
curl
TOKEN="YOUR_TOKEN_HERE"
ASSET_ID="YOUR_ASSET_ID"
BASE_URL="https://kf.kobotoolbox.org"
# First page
curl -H "Authorization: Token $TOKEN" \
"$BASE_URL/api/v2/assets/$ASSET_ID/data.json?limit=1000&start=0"
# Next page
curl -H "Authorization: Token $TOKEN" \
"$BASE_URL/api/v2/assets/$ASSET_ID/data.json?limit=1000&start=1000"
Tip: You can also fetch data incrementally by using a query parameter with the last known _id.
For example:
?query={"_id":{"$gte": <last_pk> }}
This will return only submissions with `_id` greater or equal to <last_pk>.
You can combine it with pagination parameters like limit and start.
Python
import requests, json
API_URL = "https://kf.kobotoolbox.org/api/v2/assets/YOUR_ASSET_ID/data.json"
TOKEN = "YOUR_TOKEN_HERE"
headers = {"Authorization": f"Token {TOKEN}"}
params = {
"limit": 1000,
"query": json.dumps({"_id": {"$gte": 52149}}) # fetch from last known _id, here 52149
}
resp = requests.get(API_URL, headers=headers, params=params)
resp.raise_for_status()
data = resp.json()
print(f"Fetched {len(data['results'])} records starting from _id >= 52149")
