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), 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")
