From 7ece375cb7670ed989498f2a1ce093012a1b3c0e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Olivier=20L=C3=A9ger?=
+ ملاحظة: رأس المصادقة هو نفسه عبر جميع إصدارات واجهة برمجة التطبيقات. ما يتغير يعتمد على مسار الترحيل الذي تسلكه: إذا كنت تنتقل من KPI v1 إلى KPI v2، فأنت تحتاج فقط إلى تحديث مسار URL. أما إذا كنت تنتقل من KoboCAT v1 إلى KPI v2، فستحتاج أيضاً إلى تحديث النطاق الأساسي (
- ملاحظة: بنية الاستجابة متشابهة تقريباً، باستثناء أن
+ Nota: El encabezado de autenticación es el mismo en todas las versiones de la API. Lo que cambia depende de tu ruta de migración: si estás migrando de KPI v1 a KPI v2, solo necesitas actualizar la ruta URL. Si estás migrando de KoboCAT v1 a KPI v2, también deberás actualizar el dominio base (
- Nota: La estructura de respuesta es casi la misma, excepto que
+ Remarque : L'en-tête d'authentification est le même pour toutes les versions de l'API. Ce qui change dépend de votre chemin de migration : si vous migrez de KPI v1 vers KPI v2, vous devez uniquement mettre à jour le chemin URL. Si vous migrez de KoboCAT v1 vers KPI v2, vous devrez également mettre à jour le domaine de base (
- Remarque : La structure de réponse est presque identique, sauf que
+ Note: The authentication header is the same across all API versions. What changes depends on which migration path you are on: if you are migrating from KPI v1 to KPI v2, you only need to update the URL path. If you are migrating from KoboCAT v1 to KPI v2, you will also need to update the base domain (
- Note: The response structure is nearly the same, except that
- ملاحظة: بنية الاستجابة متشابهة تقريباً، باستثناء أن
- Nota: La estructura de respuesta es casi la misma, excepto que
- Remarque : La structure de réponse est presque identique, sauf que
- Note: The response structure is nearly the same, except that
+ Note: This article is intended for **advanced users** who use the KoboToolbox API for **project management workflows**. Integrations that use synchronous exports, such as dashboards and other external reporting tools, do not require any changes and are not affected by this migration.
+
+ Note: The authentication header is the same across all API versions. What changes depends on which migration path you are on: if you are migrating from KPI v1 to KPI v2, you only need to update the URL path. If you are migrating from KoboCAT v1 to KPI v2, you will also need to update the base domain (
- Note: The response structure is nearly the same, except that
- Note: This article is intended for **advanced users** who use the KoboToolbox API for **project management workflows**. Integrations that use synchronous exports, such as dashboards and other external reporting tools, do not require any changes and are not affected by this migration.
+ Note: This article is intended for advanced users who use the KoboToolbox API for project management workflows. Integrations that use synchronous exports, such as dashboards and other external reporting tools, do not require any changes and are not affected by this migration.
kc.kobotoolbox.org → kf.kobotoolbox.org)، ومسارات نقاط النهاية، وتكييف الكود للتعامل مع بنية الاستجابة الجديدة وأسماء الحقول — راجع الأقسام أدناه.
+curl
+
+```bash
+# v1 (قديم)
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/data"
+
+# v2
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/?asset_type=survey"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# v1 (قديم)
+# response = requests.get("https://kc.kobotoolbox.org/api/v1/data", headers=headers)
+
+# v2
+response = requests.get(
+ "https://kf.kobotoolbox.org/api/v2/assets/",
+ params={"asset_type": "survey"},
+ headers=headers
+)
+response.raise_for_status()
+projects = response.json()["results"]
+
+for project in projects:
+ print(project["uid"], project["name"])
+```
+R
+
+```r
+library(httr)
+library(jsonlite)
+
+TOKEN <- "xxxx"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+# v1 (قديم)
+# response <- GET("https://kc.kobotoolbox.org/api/v1/data", headers)
+
+# v2
+response <- GET(
+ "https://kf.kobotoolbox.org/api/v2/assets/",
+ query = list(asset_type = "survey"),
+ headers
+)
+projects <- content(response, as = "parsed")$results
+
+for (p in projects) {
+ cat(p$uid, p$name, "\n")
+}
+```
+استجابة v1
```json
{
@@ -55,8 +176,10 @@
"url": "https://kc.kobotoolbox.org/api/v1/data/474.json"
}
```
+المعادل في v2
```json
{
@@ -69,8 +192,8 @@
"data": "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/data/"
...
}
-
```
+v2 يقدم ترقيم الصفحات.
+ ملاحظة: بنية الاستجابة متشابهة تقريباً، باستثناء أن v2 يقدم ترقيم الصفحات. إذا كان لديك أكثر من 30,000 إرسال، ستحتاج إلى متابعة عنوان URL في `next` لاسترجاع الصفحات التالية.
curl
+
+```bash
+# v1 (قديم) — كان يستخدم معرف النموذج الصحيح
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/data/474"
+
+# v2 — استخدم uid الأبجدي الرقمي
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/data/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# v1 (قديم)
+# response = requests.get("https://kc.kobotoolbox.org/api/v1/data/474", headers=headers)
+# submissions = response.json() # أرجع قائمة مسطحة
+
+# v2 — النتائج مقسمة إلى صفحات
+url = f"https://kf.kobotoolbox.org/api/v2/assets/{ASSET_UID}/data/"
+all_submissions = []
-**مثال على استجابة `v1`**
+while url:
+ response = requests.get(url, headers=headers)
+ response.raise_for_status()
+ page = response.json()
+ all_submissions.extend(page["results"])
+ url = page["next"] # None عندما لا توجد صفحات أخرى
+
+print(f"Total submissions: {len(all_submissions)}")
+```
+R
+
+```r
+library(httr)
+library(jsonlite)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+# v1 (قديم)
+# response <- GET(paste0("https://kc.kobotoolbox.org/api/v1/data/474"), headers)
+# submissions <- content(response, as = "parsed") # قائمة مسطحة
+
+# v2 — التعامل مع ترقيم الصفحات
+url <- paste0("https://kf.kobotoolbox.org/api/v2/assets/", ASSET_UID, "/data/")
+all_submissions <- list()
+
+repeat {
+ response <- GET(url, headers)
+ page <- content(response, as = "parsed")
+ all_submissions <- c(all_submissions, page$results)
+ if (is.null(page$`next`)) break
+ url <- page$`next`
+}
+
+cat("Total submissions:", length(all_submissions), "\n")
+```
+استجابة v1
```json
[
@@ -99,7 +304,10 @@
}
]
```
-**استجابة `v2` المعادلة**
+المعادل في v2
```json
{
@@ -120,7 +328,7 @@
### نقاط نهاية النموذج
-تُرجع نقاط النهاية هذه سمات تفصيلية لجميع النماذج المشتركة معك أو حول نموذج معين، حيث `{uid}` هو المعرف الفريد للمشروع.
+تُرجع نقاط النهاية هذه سمات تفصيلية لجميع النماذج المشتركة معك أو حول نموذج معين، حيث `{uid}` هو المعرف الفريد للمشروع.
**تعيين نقطة النهاية**
@@ -136,6 +344,7 @@
**تعيين الحقول**
+
| حقل `v1` | معادل `v2` |
|----------------------------|------------------------------------------|
| `formid` | `uid`1 |
@@ -161,6 +370,91 @@
3 _لم تعد هذه الحقول معروضة. راجع قسم **الأذونات** أدناه لمزيد من التفاصيل._
4 _غير قابل للوصول مباشرة عبر نقطة نهاية الأصل. استخدم نقطة نهاية `/api/v2/asset_usage/` واسترجع حقل `storage_bytes` للمشروع المقابل._
+#### أمثلة على الكود
+
+curl
+
+```bash
+# v1 (قديم) — قائمة بجميع النماذج
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/forms"
+
+# v1 (قديم) — نموذج واحد بالمعرف الصحيح
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/forms/474"
+
+# v2 — قائمة بجميع النماذج (مع ترقيم الصفحات)
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/?asset_type=survey"
+
+# v2 — نموذج واحد بالـ uid
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# v1 (قديم)
+# response = requests.get("https://kc.kobotoolbox.org/api/v1/forms/474", headers=headers)
+# form = response.json()
+
+# v2 — نموذج واحد
+response = requests.get(
+ f"https://kf.kobotoolbox.org/api/v2/assets/{ASSET_UID}/",
+ headers=headers
+)
+response.raise_for_status()
+form = response.json()
+
+print(form["name"]) # was: form["title"]
+print(form["deployment__submission_count"]) # was: form["num_of_submissions"]
+print(form["tag_string"]) # was: ", ".join(form["tags"])
+```
+R
+
+```r
+library(httr)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+# v1 (قديم)
+# response <- GET("https://kc.kobotoolbox.org/api/v1/forms/474", headers)
+# form <- content(response, as = "parsed")
+
+# v2 — نموذج واحد
+response <- GET(
+ paste0("https://kf.kobotoolbox.org/api/v2/assets/", ASSET_UID, "/"),
+ headers
+)
+form <- content(response, as = "parsed")
+
+cat(form$name) # was: form$title
+cat(form$deployment__submission_count) # was: form$num_of_submissions
+cat(form$tag_string) # was: paste(form$tags, collapse = ", ")
+```
+مثال على استجابة
v1
@@ -294,15 +588,211 @@
{ "tag_string": "tag1,tag2,tag3" }
```
+#### أمثلة على الكود
+
+curl
+
+```bash
+curl -X PATCH \
+ -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ -d '{"tag_string": "tag1,tag2,tag3"}' \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+tags = ["tag1", "tag2", "tag3"]
+
+response = requests.patch(
+ f"https://kf.kobotoolbox.org/api/v2/assets/{ASSET_UID}/",
+ headers=headers,
+ json={"tag_string": ",".join(tags)}
+)
+response.raise_for_status()
+print("Tags updated:", response.json()["tag_string"])
+```
+R
+
+```r
+library(httr)
+library(jsonlite)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+tags <- c("tag1", "tag2", "tag3")
+
+response <- PATCH(
+ paste0("https://kf.kobotoolbox.org/api/v2/assets/", ASSET_UID, "/"),
+ headers,
+ body = toJSON(list(tag_string = paste(tags, collapse = ",")), auto_unbox = TRUE),
+ content_type_json()
+)
+result <- content(response, as = "parsed")
+cat("Tags updated:", result$tag_string, "\n")
+```
+curl
+
+```bash
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL = "https://kf.kobotoolbox.org"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+response = requests.get(f"{BASE_URL}/api/v2/assets/{ASSET_UID}/", headers=headers)
+response.raise_for_status()
+permissions = response.json()["permissions"]
+
+# Check which permissions are assigned to AnonymousUser
+# (equivalent of v1 public/public_data/require_auth fields)
+anon_permissions = [
+ p["permission"].split("/")[-2] # extract permission codename from URL
+ for p in permissions
+ if p["user"].endswith("/AnonymousUser/")
+]
+print("Anonymous user permissions:", anon_permissions)
+# e.g. ['view_asset', 'view_submissions']
+```
+R
+
+```r
+library(httr)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL <- "https://kf.kobotoolbox.org"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+response <- GET(paste0(BASE_URL, "/api/v2/assets/", ASSET_UID, "/"), headers)
+permissions <- content(response, as = "parsed")$permissions
+
+# Check which permissions are assigned to AnonymousUser
+anon_permissions <- Filter(
+ function(p) grepl("AnonymousUser", p$user),
+ permissions
+)
+for (p in anon_permissions) cat(p$label, "\n")
+```
+curl
+
+```bash
+# Grant AnonymousUser view_asset (equivalent of v1 public: true)
+curl -X POST \
+ -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ -d '{"user": "https://kf.kobotoolbox.org/api/v2/users/AnonymousUser/", "permission": "https://kf.kobotoolbox.org/api/v2/permissions/view_asset/"}' \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/permission-assignments/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL = "https://kf.kobotoolbox.org"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# Grant AnonymousUser view_asset (equivalent of v1 public: true)
+response = requests.post(
+ f"{BASE_URL}/api/v2/assets/{ASSET_UID}/permission-assignments/",
+ headers=headers,
+ json={
+ "user": f"{BASE_URL}/api/v2/users/AnonymousUser/",
+ "permission": f"{BASE_URL}/api/v2/permissions/view_asset/",
+ }
+)
+response.raise_for_status()
+print("Permission assigned:", response.json()["label"])
+```
+R
+
+```r
+library(httr)
+library(jsonlite)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL <- "https://kf.kobotoolbox.org"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+# Grant AnonymousUser view_asset (equivalent of v1 public: true)
+response <- POST(
+ paste0(BASE_URL, "/api/v2/assets/", ASSET_UID, "/permission-assignments/"),
+ headers,
+ body = toJSON(list(
+ user = paste0(BASE_URL, "/api/v2/users/AnonymousUser/"),
+ permission = paste0(BASE_URL, "/api/v2/permissions/view_asset/")
+ ), auto_unbox = TRUE),
+ content_type_json()
+)
+result <- content(response, as = "parsed")
+cat("Permission assigned:", result$label, "\n")
+```
+مثال: أذونات المستخدم المجهول في
@@ -375,9 +865,125 @@
1 _في `v2`، يمكن الوصول إلى المشروع ذي الصلة عبر حقل `asset`، الذي يحتوي على عنوان URL كامل بدلاً من معرف صحيح._
-**مثال على استجابة `v1`**
+#### أمثلة على الكود
+v2curl
+
+```bash
+# v1 (قديم) — قائمة بجميع ملفات الوسائط عبر جميع المشاريع
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/metadata"
+
+# v1 (قديم) — ملف واحد بالمعرف الصحيح
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/metadata/271"
+
+# v2 — قائمة ملفات الوسائط لمشروع معين
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/files/"
+
+# v2 — ملف واحد بالـ uid
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/files/afoeCcF3AcGNpWUoM6bvKj9/"
+
+# v2 — رفع ملف وسائط جديد
+curl -X POST \
+ -H "Authorization: Token xxxx" \
+ -F "file_type=form_media" \
+ -F "content=@/path/to/goose.jpg" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/files/"
```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL = "https://kf.kobotoolbox.org"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# v1 (قديم)
+# response = requests.get("https://kc.kobotoolbox.org/api/v1/metadata", headers=headers)
+# files = response.json() # قائمة مسطحة عبر جميع المشاريع
+
+# v2 — قائمة ملفات الوسائط لمشروع معين (مع ترقيم الصفحات)
+response = requests.get(
+ f"{BASE_URL}/api/v2/assets/{ASSET_UID}/files/",
+ headers=headers
+)
+response.raise_for_status()
+files = response.json()["results"]
+
+for f in files:
+ print(f["uid"], f["metadata"]["filename"]) # was: f["id"], f["data_filename"]
+
+# v2 — رفع ملف وسائط جديد
+with open("/path/to/goose.jpg", "rb") as fh:
+ upload = requests.post(
+ f"{BASE_URL}/api/v2/assets/{ASSET_UID}/files/",
+ headers=headers,
+ data={"file_type": "form_media"},
+ files={"content": fh}
+ )
+upload.raise_for_status()
+print("Uploaded:", upload.json()["metadata"]["filename"])
+```
+R
+
+```r
+library(httr)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL <- "https://kf.kobotoolbox.org"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+# v1 (قديم)
+# response <- GET("https://kc.kobotoolbox.org/api/v1/metadata", headers)
+# files <- content(response, as = "parsed") # قائمة مسطحة عبر جميع المشاريع
+
+# v2 — قائمة ملفات الوسائط لمشروع معين
+response <- GET(
+ paste0(BASE_URL, "/api/v2/assets/", ASSET_UID, "/files/"),
+ headers
+)
+files <- content(response, as = "parsed")$results
+
+for (f in files) {
+ cat(f$uid, f$metadata$filename, "\n") # was: f$id, f$data_filename
+}
+
+# v2 — رفع ملف وسائط جديد
+upload <- POST(
+ paste0(BASE_URL, "/api/v2/assets/", ASSET_UID, "/files/"),
+ headers,
+ body = list(
+ file_type = "form_media",
+ content = upload_file("/path/to/goose.jpg")
+ )
+)
+cat("Uploaded:", content(upload, as = "parsed")$metadata$filename, "\n")
+```
+استجابة v1
+
+```json
{
"id": 271,
"xform": 374,
@@ -391,23 +997,25 @@
"data_filename": "goose.jpg"
}
```
+المعادل في v2
-```
+```json
{
"uid": "afoeCcF3AcGNpWUoM6bvKj9",
- "asset": "http://kf.kobo.localhost/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/",
+ "asset": "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/",
"file_type": "form_media",
- "content": "http://kf.kobo.localhost/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/files/afoeCcF3AcGNpWUoM6bvKj9/content/",
+ "content": "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/files/afoeCcF3AcGNpWUoM6bvKj9/content/",
"metadata": {
"hash": "md5:93fb96bced1e3b392abfc22934afe51a",
"filename": "goose.jpg",
"mimetype": "image/jpeg"
- },
- ...
+ }
}
```
+curl
+
+```bash
+# v1 (قديم)
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/user"
+
+# v2
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/me/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+BASE_URL = "https://kf.kobotoolbox.org"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# v1 (قديم)
+# response = requests.get("https://kc.kobotoolbox.org/api/v1/user", headers=headers)
+# user = response.json()
+# print(user["username"], user["email"])
+
+# v2
+response = requests.get(f"{BASE_URL}/me/", headers=headers)
+response.raise_for_status()
+user = response.json()
+
+print(user["username"]) # same as v1
+print(user["email"]) # same as v1
+print(user["extra_details"]["organization"]) # was: user["organization"]
+print(user["extra_details"]["country"]) # was: user["country"]
+print(user["extra_details__uid"]) # was: user["id"]
+```
+R
+
+```r
+library(httr)
+
+TOKEN <- "xxxx"
+BASE_URL <- "https://kf.kobotoolbox.org"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+# v1 (قديم)
+# response <- GET("https://kc.kobotoolbox.org/api/v1/user", headers)
+# user <- content(response, as = "parsed")
+
+# v2
+response <- GET(paste0(BASE_URL, "/me/"), headers)
+user <- content(response, as = "parsed")
+
+cat(user$username, "\n") # same as v1
+cat(user$email, "\n") # same as v1
+cat(user$extra_details$organization, "\n") # was: user$organization
+cat(user$extra_details$country, "\n") # was: user$country
+cat(user$extra_details__uid, "\n") # was: user$id
+```
+استجابة v1
+
+```json
+{
+ "id": 42,
+ "username": "project_owner",
+ "email": "owner@example.org",
+ "city": "Nairobi",
+ "country": "KEN",
+ "organization": "Humanitarian Org",
+ "website": "https://example.org",
+ "twitter": "project_owner",
+ "gravatar": "https://www.gravatar.com/avatar/abc123?s=40",
+ "require_auth": true,
+ "api_token": "e291a91bf3dd94b45748f6865cd4710246219347"
+}
+```
+المعادل في v2
+
+```json
+{
+ "username": "project_owner",
+ "email": "owner@example.org",
+ "gravatar": "https://www.gravatar.com/avatar/abc123?s=40",
+ "extra_details": {
+ "city": "Nairobi",
+ "country": "KEN",
+ "organization": "Humanitarian Org",
+ "website": "https://example.org",
+ "twitter": "project_owner",
+ "require_auth": true
+ },
+ "extra_details__uid": "u9rb4EUVEgC22wbHfVfr7S"
+}
+```
+kc.kobotoolbox.org → kf.kobotoolbox.org), las rutas de los endpoints y adaptar tu código para manejar la nueva estructura de respuesta y los nuevos nombres de campos — consulta las secciones a continuación.
+curl
+
+```bash
+# v1 (obsoleto)
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/data"
+
+# v2
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/?asset_type=survey"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# v1 (obsoleto)
+# response = requests.get("https://kc.kobotoolbox.org/api/v1/data", headers=headers)
+
+# v2
+response = requests.get(
+ "https://kf.kobotoolbox.org/api/v2/assets/",
+ params={"asset_type": "survey"},
+ headers=headers
+)
+response.raise_for_status()
+projects = response.json()["results"]
+
+for project in projects:
+ print(project["uid"], project["name"])
+```
+R
+
+```r
+library(httr)
+library(jsonlite)
+
+TOKEN <- "xxxx"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+# v1 (obsoleto)
+# response <- GET("https://kc.kobotoolbox.org/api/v1/data", headers)
+
+# v2
+response <- GET(
+ "https://kf.kobotoolbox.org/api/v2/assets/",
+ query = list(asset_type = "survey"),
+ headers
+)
+projects <- content(response, as = "parsed")$results
+
+for (p in projects) {
+ cat(p$uid, p$name, "\n")
+}
+```
+Respuesta v1
```json
{
@@ -55,8 +176,10 @@ Este endpoint devuelve una lista de formularios a los que tienes acceso, con enl
"url": "https://kc.kobotoolbox.org/api/v1/data/474.json"
}
```
+Equivalente v2
```json
{
@@ -69,8 +192,8 @@ Este endpoint devuelve una lista de formularios a los que tienes acceso, con enl
"data": "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/data/"
...
}
-
```
+v2 introduce paginación.
+ Nota: La estructura de respuesta es casi la misma, excepto que v2 introduce paginación. Si tienes más de 30.000 envíos, deberás seguir la URL next para recuperar las páginas siguientes.
curl
+
+```bash
+# v1 (obsoleto) — usaba el ID entero del formulario
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/data/474"
+
+# v2 — usa el uid alfanumérico
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/data/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# v1 (obsoleto)
+# response = requests.get("https://kc.kobotoolbox.org/api/v1/data/474", headers=headers)
+# submissions = response.json() # devolvía una lista plana
+
+# v2 — resultados paginados
+url = f"https://kf.kobotoolbox.org/api/v2/assets/{ASSET_UID}/data/"
+all_submissions = []
-**Ejemplo de respuesta `v1`**
+while url:
+ response = requests.get(url, headers=headers)
+ response.raise_for_status()
+ page = response.json()
+ all_submissions.extend(page["results"])
+ url = page["next"] # None cuando no hay más páginas
+
+print(f"Total de envíos: {len(all_submissions)}")
+```
+R
+
+```r
+library(httr)
+library(jsonlite)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+# v1 (obsoleto)
+# response <- GET(paste0("https://kc.kobotoolbox.org/api/v1/data/474"), headers)
+# submissions <- content(response, as = "parsed") # lista plana
+
+# v2 — manejar paginación
+url <- paste0("https://kf.kobotoolbox.org/api/v2/assets/", ASSET_UID, "/data/")
+all_submissions <- list()
+
+repeat {
+ response <- GET(url, headers)
+ page <- content(response, as = "parsed")
+ all_submissions <- c(all_submissions, page$results)
+ if (is.null(page$`next`)) break
+ url <- page$`next`
+}
+
+cat("Total de envíos:", length(all_submissions), "\n")
+```
+Respuesta v1
```json
[
@@ -99,7 +304,10 @@ Basándote en la `url` que obtienes de la propiedad `data` en el endpoint del as
}
]
```
-**Respuesta equivalente `v2`**
+Equivalente v2
```json
{
@@ -136,6 +344,7 @@ Estos endpoints devuelven atributos detallados de todos los formularios comparti
**Mapeo de campos**
+
| Campo `v1` | Equivalente `v2` |
|----------------------------|------------------------------------------|
| `formid` | `uid`1 |
@@ -161,6 +370,91 @@ Estos endpoints devuelven atributos detallados de todos los formularios comparti
3 _Estos campos ya no están expuestos. Consulta la sección **Permisos** a continuación para más detalles._
4 _No es directamente accesible a través del endpoint del asset. Usa el endpoint `/api/v2/asset_usage/` y recupera el campo `storage_bytes` del proyecto correspondiente._
+#### Ejemplos de código
+
+curl
+
+```bash
+# v1 (obsoleto) — listar todos los formularios
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/forms"
+
+# v1 (obsoleto) — formulario individual por ID entero
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/forms/474"
+
+# v2 — listar todos los formularios (paginado)
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/?asset_type=survey"
+
+# v2 — formulario individual por uid
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# v1 (obsoleto)
+# response = requests.get("https://kc.kobotoolbox.org/api/v1/forms/474", headers=headers)
+# form = response.json()
+
+# v2 — formulario individual
+response = requests.get(
+ f"https://kf.kobotoolbox.org/api/v2/assets/{ASSET_UID}/",
+ headers=headers
+)
+response.raise_for_status()
+form = response.json()
+
+print(form["name"]) # era: form["title"]
+print(form["deployment__submission_count"]) # era: form["num_of_submissions"]
+print(form["tag_string"]) # era: ", ".join(form["tags"])
+```
+R
+
+```r
+library(httr)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+# v1 (obsoleto)
+# response <- GET("https://kc.kobotoolbox.org/api/v1/forms/474", headers)
+# form <- content(response, as = "parsed")
+
+# v2 — formulario individual
+response <- GET(
+ paste0("https://kf.kobotoolbox.org/api/v2/assets/", ASSET_UID, "/"),
+ headers
+)
+form <- content(response, as = "parsed")
+
+cat(form$name) # era: form$title
+cat(form$deployment__submission_count) # era: form$num_of_submissions
+cat(form$tag_string) # era: paste(form$tags, collapse = ", ")
+```
+Ejemplo de respuesta
v1
@@ -294,6 +588,66 @@ Ejemplo de payload:
{ "tag_string": "tag1,tag2,tag3" }
```
+#### Ejemplos de código
+
+curl
+
+```bash
+curl -X PATCH \
+ -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ -d '{"tag_string": "tag1,tag2,tag3"}' \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+tags = ["tag1", "tag2", "tag3"]
+
+response = requests.patch(
+ f"https://kf.kobotoolbox.org/api/v2/assets/{ASSET_UID}/",
+ headers=headers,
+ json={"tag_string": ",".join(tags)}
+)
+response.raise_for_status()
+print("Tags updated:", response.json()["tag_string"])
+```
+R
+
+```r
+library(httr)
+library(jsonlite)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+tags <- c("tag1", "tag2", "tag3")
+
+response <- PATCH(
+ paste0("https://kf.kobotoolbox.org/api/v2/assets/", ASSET_UID, "/"),
+ headers,
+ body = toJSON(list(tag_string = paste(tags, collapse = ",")), auto_unbox = TRUE),
+ content_type_json()
+)
+result <- content(response, as = "parsed")
+cat("Tags updated:", result$tag_string, "\n")
+```
+curl
+
+```bash
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL = "https://kf.kobotoolbox.org"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+response = requests.get(f"{BASE_URL}/api/v2/assets/{ASSET_UID}/", headers=headers)
+response.raise_for_status()
+permissions = response.json()["permissions"]
+
+# Verificar qué permisos están asignados al AnonymousUser
+# (equivalente de los campos v1 public/public_data/require_auth)
+anon_permissions = [
+ p["permission"].split("/")[-2] # extraer el codename del permiso desde la URL
+ for p in permissions
+ if p["user"].endswith("/AnonymousUser/")
+]
+print("Anonymous user permissions:", anon_permissions)
+# ej. ['view_asset', 'view_submissions']
+```
+R
+
+```r
+library(httr)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL <- "https://kf.kobotoolbox.org"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+response <- GET(paste0(BASE_URL, "/api/v2/assets/", ASSET_UID, "/"), headers)
+permissions <- content(response, as = "parsed")$permissions
+
+# Verificar qué permisos están asignados al AnonymousUser
+anon_permissions <- Filter(
+ function(p) grepl("AnonymousUser", p$user),
+ permissions
+)
+for (p in anon_permissions) cat(p$label, "\n")
+```
+Ejemplo: permisos de usuario/a anónimo/a en
+v2curl
+
+```bash
+# Otorgar a AnonymousUser view_asset (equivalente de v1 public: true)
+curl -X POST \
+ -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ -d '{"user": "https://kf.kobotoolbox.org/api/v2/users/AnonymousUser/", "permission": "https://kf.kobotoolbox.org/api/v2/permissions/view_asset/"}' \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/permission-assignments/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL = "https://kf.kobotoolbox.org"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# Otorgar a AnonymousUser view_asset (equivalente de v1 public: true)
+response = requests.post(
+ f"{BASE_URL}/api/v2/assets/{ASSET_UID}/permission-assignments/",
+ headers=headers,
+ json={
+ "user": f"{BASE_URL}/api/v2/users/AnonymousUser/",
+ "permission": f"{BASE_URL}/api/v2/permissions/view_asset/",
+ }
+)
+response.raise_for_status()
+print("Permission assigned:", response.json()["label"])
+```
+R
+
+```r
+library(httr)
+library(jsonlite)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL <- "https://kf.kobotoolbox.org"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+# Otorgar a AnonymousUser view_asset (equivalente de v1 public: true)
+response <- POST(
+ paste0(BASE_URL, "/api/v2/assets/", ASSET_UID, "/permission-assignments/"),
+ headers,
+ body = toJSON(list(
+ user = paste0(BASE_URL, "/api/v2/users/AnonymousUser/"),
+ permission = paste0(BASE_URL, "/api/v2/permissions/view_asset/")
+ ), auto_unbox = TRUE),
+ content_type_json()
+)
+result <- content(response, as = "parsed")
+cat("Permission assigned:", result$label, "\n")
+```
+Ejemplo: permisos de
v2 para el usuario anónimo
```json
@@ -339,7 +829,7 @@ Se aplican los siguientes mapeos:
### Endpoint de etiquetas
El endpoint `/api/v1/forms/curl
+
+```bash
+# v1 (obsoleto) — listar todos los archivos multimedia de todos los proyectos
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/metadata"
+
+# v1 (obsoleto) — archivo individual por ID entero
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/metadata/271"
+
+# v2 — listar archivos multimedia de un proyecto específico
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/files/"
+
+# v2 — archivo individual por uid
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/files/afoeCcF3AcGNpWUoM6bvKj9/"
+
+# v2 — subir un nuevo archivo multimedia
+curl -X POST \
+ -H "Authorization: Token xxxx" \
+ -F "file_type=form_media" \
+ -F "content=@/path/to/goose.jpg" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/files/"
```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL = "https://kf.kobotoolbox.org"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# v1 (obsoleto)
+# response = requests.get("https://kc.kobotoolbox.org/api/v1/metadata", headers=headers)
+# files = response.json() # lista plana de todos los proyectos
+
+# v2 — listar archivos multimedia de un proyecto específico (paginado)
+response = requests.get(
+ f"{BASE_URL}/api/v2/assets/{ASSET_UID}/files/",
+ headers=headers
+)
+response.raise_for_status()
+files = response.json()["results"]
+
+for f in files:
+ print(f["uid"], f["metadata"]["filename"]) # era: f["id"], f["data_filename"]
+
+# v2 — subir un nuevo archivo multimedia
+with open("/path/to/goose.jpg", "rb") as fh:
+ upload = requests.post(
+ f"{BASE_URL}/api/v2/assets/{ASSET_UID}/files/",
+ headers=headers,
+ data={"file_type": "form_media"},
+ files={"content": fh}
+ )
+upload.raise_for_status()
+print("Uploaded:", upload.json()["metadata"]["filename"])
+```
+R
+
+```r
+library(httr)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL <- "https://kf.kobotoolbox.org"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+# v1 (obsoleto)
+# response <- GET("https://kc.kobotoolbox.org/api/v1/metadata", headers)
+# files <- content(response, as = "parsed") # lista plana de todos los proyectos
+
+# v2 — listar archivos multimedia de un proyecto específico
+response <- GET(
+ paste0(BASE_URL, "/api/v2/assets/", ASSET_UID, "/files/"),
+ headers
+)
+files <- content(response, as = "parsed")$results
+
+for (f in files) {
+ cat(f$uid, f$metadata$filename, "\n") # era: f$id, f$data_filename
+}
+
+# v2 — subir un nuevo archivo multimedia
+upload <- POST(
+ paste0(BASE_URL, "/api/v2/assets/", ASSET_UID, "/files/"),
+ headers,
+ body = list(
+ file_type = "form_media",
+ content = upload_file("/path/to/goose.jpg")
+ )
+)
+cat("Uploaded:", content(upload, as = "parsed")$metadata$filename, "\n")
+```
+Respuesta v1
+
+```json
{
"id": 271,
"xform": 374,
@@ -391,23 +997,25 @@ Estos endpoints devuelven una lista plana de todos los archivos multimedia asoci
"data_filename": "goose.jpg"
}
```
+Equivalente v2
-```
+```json
{
"uid": "afoeCcF3AcGNpWUoM6bvKj9",
- "asset": "http://kf.kobo.localhost/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/",
+ "asset": "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/",
"file_type": "form_media",
- "content": "http://kf.kobo.localhost/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/files/afoeCcF3AcGNpWUoM6bvKj9/content/",
+ "content": "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/files/afoeCcF3AcGNpWUoM6bvKj9/content/",
"metadata": {
"hash": "md5:93fb96bced1e3b392abfc22934afe51a",
"filename": "goose.jpg",
"mimetype": "image/jpeg"
- },
- ...
+ }
}
```
+curl
+
+```bash
+# v1 (obsoleto)
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/user"
+
+# v2
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/me/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+BASE_URL = "https://kf.kobotoolbox.org"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# v1 (obsoleto)
+# response = requests.get("https://kc.kobotoolbox.org/api/v1/user", headers=headers)
+# user = response.json()
+# print(user["username"], user["email"])
+
+# v2
+response = requests.get(f"{BASE_URL}/me/", headers=headers)
+response.raise_for_status()
+user = response.json()
+
+print(user["username"]) # igual que v1
+print(user["email"]) # igual que v1
+print(user["extra_details"]["organization"]) # era: user["organization"]
+print(user["extra_details"]["country"]) # era: user["country"]
+print(user["extra_details__uid"]) # era: user["id"]
+```
+R
+
+```r
+library(httr)
+
+TOKEN <- "xxxx"
+BASE_URL <- "https://kf.kobotoolbox.org"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+# v1 (obsoleto)
+# response <- GET("https://kc.kobotoolbox.org/api/v1/user", headers)
+# user <- content(response, as = "parsed")
+
+# v2
+response <- GET(paste0(BASE_URL, "/me/"), headers)
+user <- content(response, as = "parsed")
+
+cat(user$username, "\n") # igual que v1
+cat(user$email, "\n") # igual que v1
+cat(user$extra_details$organization, "\n") # era: user$organization
+cat(user$extra_details$country, "\n") # era: user$country
+cat(user$extra_details__uid, "\n") # era: user$id
+```
+Respuesta v1
+
+```json
+{
+ "id": 42,
+ "username": "project_owner",
+ "email": "owner@example.org",
+ "city": "Nairobi",
+ "country": "KEN",
+ "organization": "Humanitarian Org",
+ "website": "https://example.org",
+ "twitter": "project_owner",
+ "gravatar": "https://www.gravatar.com/avatar/abc123?s=40",
+ "require_auth": true,
+ "api_token": "e291a91bf3dd94b45748f6865cd4710246219347"
+}
+```
+Equivalente v2
+
+```json
+{
+ "username": "project_owner",
+ "email": "owner@example.org",
+ "gravatar": "https://www.gravatar.com/avatar/abc123?s=40",
+ "extra_details": {
+ "city": "Nairobi",
+ "country": "KEN",
+ "organization": "Humanitarian Org",
+ "website": "https://example.org",
+ "twitter": "project_owner",
+ "require_auth": true
+ },
+ "extra_details__uid": "u9rb4EUVEgC22wbHfVfr7S"
+}
+```
+kc.kobotoolbox.org → kf.kobotoolbox.org), les chemins des points de terminaison, et adapter votre code pour gérer la nouvelle structure de réponse et les nouveaux noms de champs — voir les sections ci-dessous.
+curl
+
+```bash
+# v1 (obsolète)
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/data"
+
+# v2
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/?asset_type=survey"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# v1 (obsolète)
+# response = requests.get("https://kc.kobotoolbox.org/api/v1/data", headers=headers)
+
+# v2
+response = requests.get(
+ "https://kf.kobotoolbox.org/api/v2/assets/",
+ params={"asset_type": "survey"},
+ headers=headers
+)
+response.raise_for_status()
+projects = response.json()["results"]
+
+for project in projects:
+ print(project["uid"], project["name"])
+```
+R
+
+```r
+library(httr)
+library(jsonlite)
+
+TOKEN <- "xxxx"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+# v1 (obsolète)
+# response <- GET("https://kc.kobotoolbox.org/api/v1/data", headers)
+
+# v2
+response <- GET(
+ "https://kf.kobotoolbox.org/api/v2/assets/",
+ query = list(asset_type = "survey"),
+ headers
+)
+projects <- content(response, as = "parsed")$results
+
+for (p in projects) {
+ cat(p$uid, p$name, "\n")
+}
+```
+Réponse v1
```json
{
@@ -55,8 +177,10 @@ Ce point de terminaison renvoie une liste de formulaires auxquels vous avez acc
"url": "https://kc.kobotoolbox.org/api/v1/data/474.json"
}
```
+Équivalent v2
```json
{
@@ -69,8 +193,8 @@ Ce point de terminaison renvoie une liste de formulaires auxquels vous avez acc
"data": "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/data/"
...
}
-
```
+v2 introduit la pagination.
+ Remarque : La structure de réponse est presque identique, sauf que v2 introduit la pagination. Si vous avez plus de 30 000 soumissions, vous devrez suivre l'URL next pour récupérer les pages suivantes.
curl
+
+```bash
+# v1 (obsolète) — utilisait l'identifiant entier du formulaire
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/data/474"
+
+# v2 — utilise l'uid alphanumérique
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/data/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# v1 (obsolète)
+# response = requests.get("https://kc.kobotoolbox.org/api/v1/data/474", headers=headers)
+# submissions = response.json() # renvoyait une liste plate
-**Exemple de réponse `v1`**
+# v2 — résultats paginés
+url = f"https://kf.kobotoolbox.org/api/v2/assets/{ASSET_UID}/data/"
+all_submissions = []
+
+while url:
+ response = requests.get(url, headers=headers)
+ response.raise_for_status()
+ page = response.json()
+ all_submissions.extend(page["results"])
+ url = page["next"] # None quand il n'y a plus de pages
+
+print(f"Total soumissions : {len(all_submissions)}")
+```
+R
+
+```r
+library(httr)
+library(jsonlite)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+# v1 (obsolète)
+# response <- GET(paste0("https://kc.kobotoolbox.org/api/v1/data/474"), headers)
+# submissions <- content(response, as = "parsed") # liste plate
+
+# v2 — gérer la pagination
+url <- paste0("https://kf.kobotoolbox.org/api/v2/assets/", ASSET_UID, "/data/")
+all_submissions <- list()
+
+repeat {
+ response <- GET(url, headers)
+ page <- content(response, as = "parsed")
+ all_submissions <- c(all_submissions, page$results)
+ if (is.null(page$`next`)) break
+ url <- page$`next`
+}
+
+cat("Total soumissions :", length(all_submissions), "\n")
+```
+Réponse v1
```json
[
@@ -99,7 +305,10 @@ En vous basant sur l'`url` que vous obtenez de la propriété `data` dans le poi
}
]
```
-**Réponse équivalente `v2`**
+Équivalent v2
```json
{
@@ -136,6 +345,7 @@ Ces points de terminaison renvoient les attributs détaillés de tous les formul
**Correspondance des champs**
+
| Champ `v1` | Équivalent `v2` |
|----------------------------|------------------------------------------|
| `formid` | `uid`1 |
@@ -161,6 +371,91 @@ Ces points de terminaison renvoient les attributs détaillés de tous les formul
3 _Ces champs ne sont plus exposés. Voir la section **Permissions** ci-dessous pour plus de détails._
4 _Non directement accessible via le point de terminaison asset. Utilisez le point de terminaison `/api/v2/asset_usage/` et récupérez le champ `storage_bytes` du projet correspondant._
+#### Exemples de code
+
+curl
+
+```bash
+# v1 (obsolète) — lister tous les formulaires
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/forms"
+
+# v1 (obsolète) — formulaire unique par identifiant entier
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/forms/474"
+
+# v2 — lister tous les formulaires (paginé)
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/?asset_type=survey"
+
+# v2 — formulaire unique par uid
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# v1 (obsolète)
+# response = requests.get("https://kc.kobotoolbox.org/api/v1/forms/474", headers=headers)
+# form = response.json()
+
+# v2 — formulaire unique
+response = requests.get(
+ f"https://kf.kobotoolbox.org/api/v2/assets/{ASSET_UID}/",
+ headers=headers
+)
+response.raise_for_status()
+form = response.json()
+
+print(form["name"]) # était : form["title"]
+print(form["deployment__submission_count"]) # était : form["num_of_submissions"]
+print(form["tag_string"]) # était : ", ".join(form["tags"])
+```
+R
+
+```r
+library(httr)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+# v1 (obsolète)
+# response <- GET("https://kc.kobotoolbox.org/api/v1/forms/474", headers)
+# form <- content(response, as = "parsed")
+
+# v2 — formulaire unique
+response <- GET(
+ paste0("https://kf.kobotoolbox.org/api/v2/assets/", ASSET_UID, "/"),
+ headers
+)
+form <- content(response, as = "parsed")
+
+cat(form$name) # était : form$title
+cat(form$deployment__submission_count) # était : form$num_of_submissions
+cat(form$tag_string) # était : paste(form$tags, collapse = ", ")
+```
+Exemple de réponse
v1
@@ -294,6 +589,66 @@ Exemple de charge utile :
{ "tag_string": "tag1,tag2,tag3" }
```
+#### Exemples de code
+
+curl
+
+```bash
+curl -X PATCH \
+ -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ -d '{"tag_string": "tag1,tag2,tag3"}' \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+tags = ["tag1", "tag2", "tag3"]
+
+response = requests.patch(
+ f"https://kf.kobotoolbox.org/api/v2/assets/{ASSET_UID}/",
+ headers=headers,
+ json={"tag_string": ",".join(tags)}
+)
+response.raise_for_status()
+print("Tags mis à jour :", response.json()["tag_string"])
+```
+R
+
+```r
+library(httr)
+library(jsonlite)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+tags <- c("tag1", "tag2", "tag3")
+
+response <- PATCH(
+ paste0("https://kf.kobotoolbox.org/api/v2/assets/", ASSET_UID, "/"),
+ headers,
+ body = toJSON(list(tag_string = paste(tags, collapse = ",")), auto_unbox = TRUE),
+ content_type_json()
+)
+result <- content(response, as = "parsed")
+cat("Tags mis à jour :", result$tag_string, "\n")
+```
+curl
+
+```bash
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL = "https://kf.kobotoolbox.org"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+response = requests.get(f"{BASE_URL}/api/v2/assets/{ASSET_UID}/", headers=headers)
+response.raise_for_status()
+permissions = response.json()["permissions"]
+
+# Vérifier quelles permissions sont assignées à l'AnonymousUser
+# (équivalent des champs v1 public/public_data/require_auth)
+anon_permissions = [
+ p["permission"].split("/")[-2] # extraire le nom de code de la permission depuis l'URL
+ for p in permissions
+ if p["user"].endswith("/AnonymousUser/")
+]
+print("Permissions de l'utilisateur anonyme :", anon_permissions)
+# ex. ['view_asset', 'view_submissions']
+```
+R
+
+```r
+library(httr)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL <- "https://kf.kobotoolbox.org"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+response <- GET(paste0(BASE_URL, "/api/v2/assets/", ASSET_UID, "/"), headers)
+permissions <- content(response, as = "parsed")$permissions
+
+# Vérifier quelles permissions sont assignées à l'AnonymousUser
+anon_permissions <- Filter(
+ function(p) grepl("AnonymousUser", p$user),
+ permissions
+)
+for (p in anon_permissions) cat(p$label, "\n")
+```
+curl
+
+```bash
+# Accorder à l'AnonymousUser view_asset (équivalent de v1 public: true)
+curl -X POST \
+ -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ -d '{"user": "https://kf.kobotoolbox.org/api/v2/users/AnonymousUser/", "permission": "https://kf.kobotoolbox.org/api/v2/permissions/view_asset/"}' \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/permission-assignments/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL = "https://kf.kobotoolbox.org"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# Accorder à l'AnonymousUser view_asset (équivalent de v1 public: true)
+response = requests.post(
+ f"{BASE_URL}/api/v2/assets/{ASSET_UID}/permission-assignments/",
+ headers=headers,
+ json={
+ "user": f"{BASE_URL}/api/v2/users/AnonymousUser/",
+ "permission": f"{BASE_URL}/api/v2/permissions/view_asset/",
+ }
+)
+response.raise_for_status()
+print("Permission assignée :", response.json()["label"])
+```
+Exemple : permissions d'utilisatrice ou utilisateur anonyme
+v2R
+
+```r
+library(httr)
+library(jsonlite)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL <- "https://kf.kobotoolbox.org"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+# Accorder à l'AnonymousUser view_asset (équivalent de v1 public: true)
+response <- POST(
+ paste0(BASE_URL, "/api/v2/assets/", ASSET_UID, "/permission-assignments/"),
+ headers,
+ body = toJSON(list(
+ user = paste0(BASE_URL, "/api/v2/users/AnonymousUser/"),
+ permission = paste0(BASE_URL, "/api/v2/permissions/view_asset/")
+ ), auto_unbox = TRUE),
+ content_type_json()
+)
+result <- content(response, as = "parsed")
+cat("Permission assignée :", result$label, "\n")
+```
+Exemple : permissions d'utilisateur anonyme
v2
```json
@@ -375,9 +866,125 @@ Ces points de terminaison renvoient une liste plate de tous les fichiers médias
1 _Dans `v2`, le projet associé est accessible via le champ `asset`, qui contient une URL complète au lieu d'un ID entier._
-**Exemple de réponse `v1`**
+#### Exemples de code
+
+curl
+
+```bash
+# v1 (obsolète) — lister tous les fichiers médias de tous les projets
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/metadata"
+
+# v1 (obsolète) — fichier unique par identifiant entier
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/metadata/271"
+
+# v2 — lister les fichiers médias d'un projet spécifique
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/files/"
+
+# v2 — fichier unique par uid
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/files/afoeCcF3AcGNpWUoM6bvKj9/"
+
+# v2 — téléverser un nouveau fichier média
+curl -X POST \
+ -H "Authorization: Token xxxx" \
+ -F "file_type=form_media" \
+ -F "content=@/path/to/goose.jpg" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/files/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL = "https://kf.kobotoolbox.org"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# v1 (obsolète)
+# response = requests.get("https://kc.kobotoolbox.org/api/v1/metadata", headers=headers)
+# files = response.json() # liste plate sur tous les projets
+
+# v2 — lister les fichiers médias d'un projet spécifique (paginé)
+response = requests.get(
+ f"{BASE_URL}/api/v2/assets/{ASSET_UID}/files/",
+ headers=headers
+)
+response.raise_for_status()
+files = response.json()["results"]
+
+for f in files:
+ print(f["uid"], f["metadata"]["filename"]) # était : f["id"], f["data_filename"]
+
+# v2 — téléverser un nouveau fichier média
+with open("/path/to/goose.jpg", "rb") as fh:
+ upload = requests.post(
+ f"{BASE_URL}/api/v2/assets/{ASSET_UID}/files/",
+ headers=headers,
+ data={"file_type": "form_media"},
+ files={"content": fh}
+ )
+upload.raise_for_status()
+print("Téléversé :", upload.json()["metadata"]["filename"])
+```
+R
+
+```r
+library(httr)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL <- "https://kf.kobotoolbox.org"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+# v1 (obsolète)
+# response <- GET("https://kc.kobotoolbox.org/api/v1/metadata", headers)
+# files <- content(response, as = "parsed") # liste plate sur tous les projets
+
+# v2 — lister les fichiers médias d'un projet spécifique
+response <- GET(
+ paste0(BASE_URL, "/api/v2/assets/", ASSET_UID, "/files/"),
+ headers
+)
+files <- content(response, as = "parsed")$results
+
+for (f in files) {
+ cat(f$uid, f$metadata$filename, "\n") # était : f$id, f$data_filename
+}
+
+# v2 — téléverser un nouveau fichier média
+upload <- POST(
+ paste0(BASE_URL, "/api/v2/assets/", ASSET_UID, "/files/"),
+ headers,
+ body = list(
+ file_type = "form_media",
+ content = upload_file("/path/to/goose.jpg")
+ )
+)
+cat("Téléversé :", content(upload, as = "parsed")$metadata$filename, "\n")
```
+Réponse v1
+
+```json
{
"id": 271,
"xform": 374,
@@ -391,23 +998,25 @@ Ces points de terminaison renvoient une liste plate de tous les fichiers médias
"data_filename": "goose.jpg"
}
```
+Équivalent v2
-```
+```json
{
"uid": "afoeCcF3AcGNpWUoM6bvKj9",
- "asset": "http://kf.kobo.localhost/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/",
+ "asset": "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/",
"file_type": "form_media",
- "content": "http://kf.kobo.localhost/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/files/afoeCcF3AcGNpWUoM6bvKj9/content/",
+ "content": "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/files/afoeCcF3AcGNpWUoM6bvKj9/content/",
"metadata": {
"hash": "md5:93fb96bced1e3b392abfc22934afe51a",
"filename": "goose.jpg",
"mimetype": "image/jpeg"
- },
- ...
+ }
}
```
+curl
+
+```bash
+# v1 (obsolète)
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/user"
+
+# v2
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/me/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+BASE_URL = "https://kf.kobotoolbox.org"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# v1 (obsolète)
+# response = requests.get("https://kc.kobotoolbox.org/api/v1/user", headers=headers)
+# user = response.json()
+# print(user["username"], user["email"])
+
+# v2
+response = requests.get(f"{BASE_URL}/me/", headers=headers)
+response.raise_for_status()
+user = response.json()
+
+print(user["username"]) # identique à v1
+print(user["email"]) # identique à v1
+print(user["extra_details"]["organization"]) # était : user["organization"]
+print(user["extra_details"]["country"]) # était : user["country"]
+print(user["extra_details__uid"]) # était : user["id"]
+```
+R
+
+```r
+library(httr)
+
+TOKEN <- "xxxx"
+BASE_URL <- "https://kf.kobotoolbox.org"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+# v1 (obsolète)
+# response <- GET("https://kc.kobotoolbox.org/api/v1/user", headers)
+# user <- content(response, as = "parsed")
+
+# v2
+response <- GET(paste0(BASE_URL, "/me/"), headers)
+user <- content(response, as = "parsed")
+
+cat(user$username, "\n") # identique à v1
+cat(user$email, "\n") # identique à v1
+cat(user$extra_details$organization, "\n") # était : user$organization
+cat(user$extra_details$country, "\n") # était : user$country
+cat(user$extra_details__uid, "\n") # était : user$id
+```
+Réponse v1
+
+```json
+{
+ "id": 42,
+ "username": "project_owner",
+ "email": "owner@example.org",
+ "city": "Nairobi",
+ "country": "KEN",
+ "organization": "Humanitarian Org",
+ "website": "https://example.org",
+ "twitter": "project_owner",
+ "gravatar": "https://www.gravatar.com/avatar/abc123?s=40",
+ "require_auth": true,
+ "api_token": "e291a91bf3dd94b45748f6865cd4710246219347"
+}
+```
+Équivalent v2
+
+```json
+{
+ "username": "project_owner",
+ "email": "owner@example.org",
+ "gravatar": "https://www.gravatar.com/avatar/abc123?s=40",
+ "extra_details": {
+ "city": "Nairobi",
+ "country": "KEN",
+ "organization": "Humanitarian Org",
+ "website": "https://example.org",
+ "twitter": "project_owner",
+ "require_auth": true
+ },
+ "extra_details__uid": "u9rb4EUVEgC22wbHfVfr7S"
+}
+```
+kc.kobotoolbox.org → kf.kobotoolbox.org), the endpoint paths, and adapt your code to handle the new response structure and field names — see the sections below.
+curl
+
+```bash
+# v1 (deprecated)
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/data"
+
+# v2
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/?asset_type=survey"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# v1 (deprecated)
+# response = requests.get("https://kc.kobotoolbox.org/api/v1/data", headers=headers)
+
+# v2
+response = requests.get(
+ "https://kf.kobotoolbox.org/api/v2/assets/",
+ params={"asset_type": "survey"},
+ headers=headers
+)
+response.raise_for_status()
+projects = response.json()["results"]
+
+for project in projects:
+ print(project["uid"], project["name"])
+```
+R
+
+```r
+library(httr)
+library(jsonlite)
+
+TOKEN <- "xxxx"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+# v1 (deprecated)
+# response <- GET("https://kc.kobotoolbox.org/api/v1/data", headers)
+
+# v2
+response <- GET(
+ "https://kf.kobotoolbox.org/api/v2/assets/",
+ query = list(asset_type = "survey"),
+ headers
+)
+projects <- content(response, as = "parsed")$results
+
+for (p in projects) {
+ cat(p$uid, p$name, "\n")
+}
+```
+v1 response
```json
{
@@ -56,8 +177,10 @@ This endpoint returns a list of forms you have access to, with links to their su
"url": "https://kc.kobotoolbox.org/api/v1/data/474.json"
}
```
+v2 equivalent
```json
{
@@ -70,8 +193,8 @@ This endpoint returns a list of forms you have access to, with links to their su
"data": "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/data/"
...
}
-
```
+v2 introduces pagination.
+ Note: The response structure is nearly the same, except that v2 introduces pagination. If you have more than 30,000 submissions, you will need to follow the next URL to retrieve subsequent pages.
curl
+
+```bash
+# v1 (deprecated) — used the integer form ID
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/data/474"
+
+# v2 — use the alphanumeric uid
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/data/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# v1 (deprecated)
+# response = requests.get("https://kc.kobotoolbox.org/api/v1/data/474", headers=headers)
+# submissions = response.json() # returned a flat list
+
+# v2 — results are paginated
+url = f"https://kf.kobotoolbox.org/api/v2/assets/{ASSET_UID}/data/"
+all_submissions = []
-**Example `v1` response**
+while url:
+ response = requests.get(url, headers=headers)
+ response.raise_for_status()
+ page = response.json()
+ all_submissions.extend(page["results"])
+ url = page["next"] # None when there are no more pages
+
+print(f"Total submissions: {len(all_submissions)}")
+```
+R
+
+```r
+library(httr)
+library(jsonlite)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+# v1 (deprecated)
+# response <- GET(paste0("https://kc.kobotoolbox.org/api/v1/data/474"), headers)
+# submissions <- content(response, as = "parsed") # flat list
+
+# v2 — handle pagination
+url <- paste0("https://kf.kobotoolbox.org/api/v2/assets/", ASSET_UID, "/data/")
+all_submissions <- list()
+
+repeat {
+ response <- GET(url, headers)
+ page <- content(response, as = "parsed")
+ all_submissions <- c(all_submissions, page$results)
+ if (is.null(page$`next`)) break
+ url <- page$`next`
+}
+
+cat("Total submissions:", length(all_submissions), "\n")
+```
+v1 response
```json
[
@@ -100,7 +305,10 @@ Based on the `url` you get from the `data` property in the asset endpoint, you c
}
]
```
-**Equivalent `v2` response**
+v2 equivalent
```json
{
@@ -137,6 +345,7 @@ These endpoints return detailed attributes of all forms shared with you or about
**Field mapping**
+
| `v1` Field | `v2` Equivalent |
|----------------------------|------------------------------------------|
| `formid` | `uid`1 |
@@ -162,6 +371,91 @@ These endpoints return detailed attributes of all forms shared with you or about
3 _These fields are no longer exposed. See the **Permissions** section below for more details._
4 _Not directly accessible via the asset endpoint. Use the `/api/v2/asset_usage/` endpoint and retrieve the `storage_bytes` field of the corresponding project._
+#### Code examples
+
+curl
+
+```bash
+# v1 (deprecated) — list all forms
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/forms"
+
+# v1 (deprecated) — single form by integer ID
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/forms/474"
+
+# v2 — list all forms (paginated)
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/?asset_type=survey"
+
+# v2 — single form by uid
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# v1 (deprecated)
+# response = requests.get("https://kc.kobotoolbox.org/api/v1/forms/474", headers=headers)
+# form = response.json()
+
+# v2 — single form
+response = requests.get(
+ f"https://kf.kobotoolbox.org/api/v2/assets/{ASSET_UID}/",
+ headers=headers
+)
+response.raise_for_status()
+form = response.json()
+
+print(form["name"]) # was: form["title"]
+print(form["deployment__submission_count"]) # was: form["num_of_submissions"]
+print(form["tag_string"]) # was: ", ".join(form["tags"])
+```
+R
+
+```r
+library(httr)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+# v1 (deprecated)
+# response <- GET("https://kc.kobotoolbox.org/api/v1/forms/474", headers)
+# form <- content(response, as = "parsed")
+
+# v2 — single form
+response <- GET(
+ paste0("https://kf.kobotoolbox.org/api/v2/assets/", ASSET_UID, "/"),
+ headers
+)
+form <- content(response, as = "parsed")
+
+cat(form$name) # was: form$title
+cat(form$deployment__submission_count) # was: form$num_of_submissions
+cat(form$tag_string) # was: paste(form$tags, collapse = ", ")
+```
+Example
v1 response
@@ -295,6 +589,66 @@ Example payload:
{ "tag_string": "tag1,tag2,tag3" }
```
+#### Code examples
+
+curl
+
+```bash
+curl -X PATCH \
+ -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ -d '{"tag_string": "tag1,tag2,tag3"}' \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+tags = ["tag1", "tag2", "tag3"]
+
+response = requests.patch(
+ f"https://kf.kobotoolbox.org/api/v2/assets/{ASSET_UID}/",
+ headers=headers,
+ json={"tag_string": ",".join(tags)}
+)
+response.raise_for_status()
+print("Tags updated:", response.json()["tag_string"])
+```
+R
+
+```r
+library(httr)
+library(jsonlite)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+tags <- c("tag1", "tag2", "tag3")
+
+response <- PATCH(
+ paste0("https://kf.kobotoolbox.org/api/v2/assets/", ASSET_UID, "/"),
+ headers,
+ body = toJSON(list(tag_string = paste(tags, collapse = ",")), auto_unbox = TRUE),
+ content_type_json()
+)
+result <- content(response, as = "parsed")
+cat("Tags updated:", result$tag_string, "\n")
+```
+curl
+
+```bash
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL = "https://kf.kobotoolbox.org"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+response = requests.get(f"{BASE_URL}/api/v2/assets/{ASSET_UID}/", headers=headers)
+response.raise_for_status()
+permissions = response.json()["permissions"]
+
+# Check which permissions are assigned to AnonymousUser
+# (equivalent of v1 public/public_data/require_auth fields)
+anon_permissions = [
+ p["permission"].split("/")[-2] # extract permission codename from URL
+ for p in permissions
+ if p["user"].endswith("/AnonymousUser/")
+]
+print("Anonymous user permissions:", anon_permissions)
+# e.g. ['view_asset', 'view_submissions']
+```
+R
+
+```r
+library(httr)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL <- "https://kf.kobotoolbox.org"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+response <- GET(paste0(BASE_URL, "/api/v2/assets/", ASSET_UID, "/"), headers)
+permissions <- content(response, as = "parsed")$permissions
+
+# Check which permissions are assigned to AnonymousUser
+anon_permissions <- Filter(
+ function(p) grepl("AnonymousUser", p$user),
+ permissions
+)
+for (p in anon_permissions) cat(p$label, "\n")
+```
+curl
+
+```bash
+# Grant AnonymousUser view_asset (equivalent of v1 public: true)
+curl -X POST \
+ -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ -d '{"user": "https://kf.kobotoolbox.org/api/v2/users/AnonymousUser/", "permission": "https://kf.kobotoolbox.org/api/v2/permissions/view_asset/"}' \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/permission-assignments/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL = "https://kf.kobotoolbox.org"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# Grant AnonymousUser view_asset (equivalent of v1 public: true)
+response = requests.post(
+ f"{BASE_URL}/api/v2/assets/{ASSET_UID}/permission-assignments/",
+ headers=headers,
+ json={
+ "user": f"{BASE_URL}/api/v2/users/AnonymousUser/",
+ "permission": f"{BASE_URL}/api/v2/permissions/view_asset/",
+ }
+)
+response.raise_for_status()
+print("Permission assigned:", response.json()["label"])
+```
+R
+
+```r
+library(httr)
+library(jsonlite)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL <- "https://kf.kobotoolbox.org"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+# Grant AnonymousUser view_asset (equivalent of v1 public: true)
+response <- POST(
+ paste0(BASE_URL, "/api/v2/assets/", ASSET_UID, "/permission-assignments/"),
+ headers,
+ body = toJSON(list(
+ user = paste0(BASE_URL, "/api/v2/users/AnonymousUser/"),
+ permission = paste0(BASE_URL, "/api/v2/permissions/view_asset/")
+ ), auto_unbox = TRUE),
+ content_type_json()
+)
+result <- content(response, as = "parsed")
+cat("Permission assigned:", result$label, "\n")
+```
+Example:
@@ -376,9 +866,125 @@ These endpoints return a flat list of all media files associated with the curren
1 _In `v2`, the related project is accessible via the `asset` field, which contains a full URL instead of an integer ID._
-**Example `v1` response**
+#### Code examples
+v2 anonymous user permissionscurl
+
+```bash
+# v1 (deprecated) — list all media files across all projects
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/metadata"
+
+# v1 (deprecated) — single file by integer ID
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/metadata/271"
+
+# v2 — list media files for a specific project
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/files/"
+
+# v2 — single file by uid
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/files/afoeCcF3AcGNpWUoM6bvKj9/"
+
+# v2 — upload a new media file
+curl -X POST \
+ -H "Authorization: Token xxxx" \
+ -F "file_type=form_media" \
+ -F "content=@/path/to/goose.jpg" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/files/"
```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL = "https://kf.kobotoolbox.org"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# v1 (deprecated)
+# response = requests.get("https://kc.kobotoolbox.org/api/v1/metadata", headers=headers)
+# files = response.json() # flat list across all projects
+
+# v2 — list media files for a specific project (paginated)
+response = requests.get(
+ f"{BASE_URL}/api/v2/assets/{ASSET_UID}/files/",
+ headers=headers
+)
+response.raise_for_status()
+files = response.json()["results"]
+
+for f in files:
+ print(f["uid"], f["metadata"]["filename"]) # was: f["id"], f["data_filename"]
+
+# v2 — upload a new media file
+with open("/path/to/goose.jpg", "rb") as fh:
+ upload = requests.post(
+ f"{BASE_URL}/api/v2/assets/{ASSET_UID}/files/",
+ headers=headers,
+ data={"file_type": "form_media"},
+ files={"content": fh}
+ )
+upload.raise_for_status()
+print("Uploaded:", upload.json()["metadata"]["filename"])
+```
+R
+
+```r
+library(httr)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL <- "https://kf.kobotoolbox.org"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+# v1 (deprecated)
+# response <- GET("https://kc.kobotoolbox.org/api/v1/metadata", headers)
+# files <- content(response, as = "parsed") # flat list across all projects
+
+# v2 — list media files for a specific project
+response <- GET(
+ paste0(BASE_URL, "/api/v2/assets/", ASSET_UID, "/files/"),
+ headers
+)
+files <- content(response, as = "parsed")$results
+
+for (f in files) {
+ cat(f$uid, f$metadata$filename, "\n") # was: f$id, f$data_filename
+}
+
+# v2 — upload a new media file
+upload <- POST(
+ paste0(BASE_URL, "/api/v2/assets/", ASSET_UID, "/files/"),
+ headers,
+ body = list(
+ file_type = "form_media",
+ content = upload_file("/path/to/goose.jpg")
+ )
+)
+cat("Uploaded:", content(upload, as = "parsed")$metadata$filename, "\n")
+```
+v1 response
+
+```json
{
"id": 271,
"xform": 374,
@@ -392,23 +998,25 @@ These endpoints return a flat list of all media files associated with the curren
"data_filename": "goose.jpg"
}
```
+v2 equivalent
-```
+```json
{
"uid": "afoeCcF3AcGNpWUoM6bvKj9",
- "asset": "http://kf.kobo.localhost/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/",
+ "asset": "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/",
"file_type": "form_media",
- "content": "http://kf.kobo.localhost/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/files/afoeCcF3AcGNpWUoM6bvKj9/content/",
+ "content": "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/files/afoeCcF3AcGNpWUoM6bvKj9/content/",
"metadata": {
"hash": "md5:93fb96bced1e3b392abfc22934afe51a",
"filename": "goose.jpg",
"mimetype": "image/jpeg"
- },
- ...
+ }
}
```
+curl
+
+```bash
+# v1 (deprecated)
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/user"
+
+# v2
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/me/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+BASE_URL = "https://kf.kobotoolbox.org"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# v1 (deprecated)
+# response = requests.get("https://kc.kobotoolbox.org/api/v1/user", headers=headers)
+# user = response.json()
+# print(user["username"], user["email"])
+
+# v2
+response = requests.get(f"{BASE_URL}/me/", headers=headers)
+response.raise_for_status()
+user = response.json()
+
+print(user["username"]) # same as v1
+print(user["email"]) # same as v1
+print(user["extra_details"]["organization"]) # was: user["organization"]
+print(user["extra_details"]["country"]) # was: user["country"]
+print(user["extra_details__uid"]) # was: user["id"]
+```
+R
+
+```r
+library(httr)
+
+TOKEN <- "xxxx"
+BASE_URL <- "https://kf.kobotoolbox.org"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+# v1 (deprecated)
+# response <- GET("https://kc.kobotoolbox.org/api/v1/user", headers)
+# user <- content(response, as = "parsed")
+
+# v2
+response <- GET(paste0(BASE_URL, "/me/"), headers)
+user <- content(response, as = "parsed")
+
+cat(user$username, "\n") # same as v1
+cat(user$email, "\n") # same as v1
+cat(user$extra_details$organization, "\n") # was: user$organization
+cat(user$extra_details$country, "\n") # was: user$country
+cat(user$extra_details__uid, "\n") # was: user$id
+```
+v1 response
+
+```json
+{
+ "id": 42,
+ "username": "project_owner",
+ "email": "owner@example.org",
+ "city": "Nairobi",
+ "country": "KEN",
+ "organization": "Humanitarian Org",
+ "website": "https://example.org",
+ "twitter": "project_owner",
+ "gravatar": "https://www.gravatar.com/avatar/abc123?s=40",
+ "require_auth": true,
+ "api_token": "e291a91bf3dd94b45748f6865cd4710246219347"
+}
+```
+v2 equivalent
+
+```json
+{
+ "username": "project_owner",
+ "email": "owner@example.org",
+ "gravatar": "https://www.gravatar.com/avatar/abc123?s=40",
+ "extra_details": {
+ "city": "Nairobi",
+ "country": "KEN",
+ "organization": "Humanitarian Org",
+ "website": "https://example.org",
+ "twitter": "project_owner",
+ "require_auth": true
+ },
+ "extra_details__uid": "u9rb4EUVEgC22wbHfVfr7S"
+}
+```
+v2 يقدم ترقيم الصفحات. إذا كان لديك أكثر من 30,000 إرسال، ستحتاج إلى متابعة عنوان URL في `next` لاسترجاع الصفحات التالية.
+ ملاحظة: بنية الاستجابة متشابهة تقريباً، باستثناء أن v2 يقدم ترقيم الصفحات. إذا كان لديك أكثر من 1,000 إرسال، ستحتاج إلى متابعة عنوان URL في `next` لاسترجاع الصفحات التالية.
v2 introduce paginación. Si tienes más de 30.000 envíos, deberás seguir la URL next para recuperar las páginas siguientes.
+ Nota: La estructura de respuesta es casi la misma, excepto que v2 introduce paginación. Si tienes más de 1.000 envíos, deberás seguir la URL next para recuperar las páginas siguientes.
v2 introduit la pagination. Si vous avez plus de 30 000 soumissions, vous devrez suivre l'URL next pour récupérer les pages suivantes.
+ Remarque : La structure de réponse est presque identique, sauf que v2 introduit la pagination. Si vous avez plus de 1 000 soumissions, vous devrez suivre l'URL next pour récupérer les pages suivantes.
v2 introduces pagination. If you have more than 30,000 submissions, you will need to follow the next URL to retrieve subsequent pages.
+ Note: The response structure is nearly the same, except that v2 introduces pagination. If you have more than 1,000 submissions, you will need to follow the next URL to retrieve subsequent pages.
Exemple de réponse
v1
-
+
```json
{
"url": "https://kf.kobotoolbox.org/api/v1/forms/474",
@@ -667,9 +667,9 @@ cat("Tags mis à jour :", result$tag_string, "\n")
Dans `v2`, les champs `public`, `public_data` et `require_auth` ne sont plus exposés en tant qu'attributs booléens. Au lieu de cela, **l'accès anonyme est contrôlé via des attributions de permissions explicites à l'`AnonymousUser`**.
Les correspondances suivantes s'appliquent :
-- `public: true` → l'`AnonymousUser` a la permission `view_asset`
-- `public_data: true` → l'`AnonymousUser` a la permission `view_submissions`
-- `require_auth: false` → l'`AnonymousUser` a la permission `add_submissions`
+- `public: true` → l'`AnonymousUser` a la permission `view_asset`
+- `public_data: true` → l'`AnonymousUser` a la permission `view_submissions`
+- `require_auth: false` → l'`AnonymousUser` a la permission `add_submissions`
Les permissions sont disponibles sur le point de terminaison de détail de l'asset (`/api/v2/assets/{uid}/`) sous la propriété `permissions`.
@@ -811,7 +811,7 @@ cat("Permission assignée :", result$label, "\n")
Exemple : permissions d'utilisateur anonyme
v2
-
+
```json
[
{
diff --git a/source/migrating_api.md b/source/migrating_api.md
index 3284822fe..b7d6004de 100644
--- a/source/migrating_api.md
+++ b/source/migrating_api.md
@@ -1,5 +1,5 @@
# Migrating from v1 to v2 API
-**Last updated:** 25 May 2026
+**Last updated:** 25 May 2026
As part of our ongoing efforts to streamline and modernize the KoboToolbox platform, we are phasing out KPI and KoboCAT `v1` endpoints. All KPI and KoboCAT `v1` endpoints are now deprecated, and will be removed entirely in June 2026. `v1` endpoints are being phased out in favor of the more robust and fully supported KPI `v2` API.
From 4548da24e07c17be66883a09c440760542239eb2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Salom=C3=A9=20Garnier?= kc.kobotoolbox.org → kf.kobotoolbox.org), the endpoint paths, and adapt your code to handle the new response structure and field names — see the sections below.
+curl
+
+```bash
+# v1 (deprecated)
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/data"
+
+# v2
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/?asset_type=survey"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# v1 (deprecated)
+# response = requests.get("https://kc.kobotoolbox.org/api/v1/data", headers=headers)
+
+# v2
+response = requests.get(
+ "https://kf.kobotoolbox.org/api/v2/assets/",
+ params={"asset_type": "survey"},
+ headers=headers
+)
+response.raise_for_status()
+projects = response.json()["results"]
+
+for project in projects:
+ print(project["uid"], project["name"])
+```
+R
+
+```r
+library(httr)
+library(jsonlite)
+
+TOKEN <- "xxxx"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+# v1 (deprecated)
+# response <- GET("https://kc.kobotoolbox.org/api/v1/data", headers)
+
+# v2
+response <- GET(
+ "https://kf.kobotoolbox.org/api/v2/assets/",
+ query = list(asset_type = "survey"),
+ headers
+)
+projects <- content(response, as = "parsed")$results
+
+for (p in projects) {
+ cat(p$uid, p$name, "\n")
+}
+```
+v1 response
```json
{
@@ -69,8 +190,10 @@ This endpoint returns a list of forms you have access to, with links to their su
"url": "https://kc.kobotoolbox.org/api/v1/data/474.json"
}
```
+v2 equivalent
```json
{
@@ -83,29 +206,109 @@ This endpoint returns a list of forms you have access to, with links to their su
"data": "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/data/"
...
}
-
```
+v2 introduces pagination.
+ Note: The response structure is nearly the same, except that v2 introduces pagination. If you have more than 1,000 submissions, you will need to follow the next URL to retrieve subsequent pages.
curl
+
+```bash
+# v1 (deprecated) — used the integer form ID
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/data/474"
+
+# v2 — use the alphanumeric uid
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/data/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# v1 (deprecated)
+# response = requests.get("https://kc.kobotoolbox.org/api/v1/data/474", headers=headers)
+# submissions = response.json() # returned a flat list
+
+# v2 — results are paginated
+url = f"https://kf.kobotoolbox.org/api/v2/assets/{ASSET_UID}/data/"
+all_submissions = []
+
+while url:
+ response = requests.get(url, headers=headers)
+ response.raise_for_status()
+ page = response.json()
+ all_submissions.extend(page["results"])
+ url = page["next"] # None when there are no more pages
+
+print(f"Total submissions: {len(all_submissions)}")
+```
+R
+
+```r
+library(httr)
+library(jsonlite)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+# v1 (deprecated)
+# response <- GET(paste0("https://kc.kobotoolbox.org/api/v1/data/474"), headers)
+# submissions <- content(response, as = "parsed") # flat list
+
+# v2 — handle pagination
+url <- paste0("https://kf.kobotoolbox.org/api/v2/assets/", ASSET_UID, "/data/")
+all_submissions <- list()
+
+repeat {
+ response <- GET(url, headers)
+ page <- content(response, as = "parsed")
+ all_submissions <- c(all_submissions, page$results)
+ if (is.null(page$`next`)) break
+ url <- page$`next`
+}
-**Example `v1` response**
+cat("Total submissions:", length(all_submissions), "\n")
+```
+v1 response
```json
[
@@ -115,7 +318,10 @@ Based on the `url` you get from the `data` property in the asset endpoint, you c
}
]
```
-**Equivalent `v2` response**
+v2 equivalent
```json
{
@@ -152,6 +358,7 @@ These endpoints return detailed attributes of all forms shared with you or about
**Field mapping**
+
| `v1` Field | `v2` Equivalent |
|----------------------------|------------------------------------------|
| `formid` | `uid`1 |
@@ -177,6 +384,91 @@ These endpoints return detailed attributes of all forms shared with you or about
3 _These fields are no longer exposed. See the **Permissions** section below for more details._
4 _Not directly accessible via the asset endpoint. Use the `/api/v2/asset_usage/` endpoint and retrieve the `storage_bytes` field of the corresponding project._
+#### Code examples
+
+curl
+
+```bash
+# v1 (deprecated) — list all forms
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/forms"
+
+# v1 (deprecated) — single form by integer ID
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/forms/474"
+
+# v2 — list all forms (paginated)
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/?asset_type=survey"
+
+# v2 — single form by uid
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# v1 (deprecated)
+# response = requests.get("https://kc.kobotoolbox.org/api/v1/forms/474", headers=headers)
+# form = response.json()
+
+# v2 — single form
+response = requests.get(
+ f"https://kf.kobotoolbox.org/api/v2/assets/{ASSET_UID}/",
+ headers=headers
+)
+response.raise_for_status()
+form = response.json()
+
+print(form["name"]) # was: form["title"]
+print(form["deployment__submission_count"]) # was: form["num_of_submissions"]
+print(form["tag_string"]) # was: ", ".join(form["tags"])
+```
+R
+
+```r
+library(httr)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+# v1 (deprecated)
+# response <- GET("https://kc.kobotoolbox.org/api/v1/forms/474", headers)
+# form <- content(response, as = "parsed")
+
+# v2 — single form
+response <- GET(
+ paste0("https://kf.kobotoolbox.org/api/v2/assets/", ASSET_UID, "/"),
+ headers
+)
+form <- content(response, as = "parsed")
+
+cat(form$name) # was: form$title
+cat(form$deployment__submission_count) # was: form$num_of_submissions
+cat(form$tag_string) # was: paste(form$tags, collapse = ", ")
+```
+Example
v1 response
@@ -310,6 +602,66 @@ Example payload:
{ "tag_string": "tag1,tag2,tag3" }
```
+#### Code examples
+
+curl
+
+```bash
+curl -X PATCH \
+ -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ -d '{"tag_string": "tag1,tag2,tag3"}' \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+tags = ["tag1", "tag2", "tag3"]
+
+response = requests.patch(
+ f"https://kf.kobotoolbox.org/api/v2/assets/{ASSET_UID}/",
+ headers=headers,
+ json={"tag_string": ",".join(tags)}
+)
+response.raise_for_status()
+print("Tags updated:", response.json()["tag_string"])
+```
+R
+
+```r
+library(httr)
+library(jsonlite)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+tags <- c("tag1", "tag2", "tag3")
+
+response <- PATCH(
+ paste0("https://kf.kobotoolbox.org/api/v2/assets/", ASSET_UID, "/"),
+ headers,
+ body = toJSON(list(tag_string = paste(tags, collapse = ",")), auto_unbox = TRUE),
+ content_type_json()
+)
+result <- content(response, as = "parsed")
+cat("Tags updated:", result$tag_string, "\n")
+```
+curl
+
+```bash
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL = "https://kf.kobotoolbox.org"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+response = requests.get(f"{BASE_URL}/api/v2/assets/{ASSET_UID}/", headers=headers)
+response.raise_for_status()
+permissions = response.json()["permissions"]
+
+# Check which permissions are assigned to AnonymousUser
+# (equivalent of v1 public/public_data/require_auth fields)
+anon_permissions = [
+ p["permission"].split("/")[-2] # extract permission codename from URL
+ for p in permissions
+ if p["user"].endswith("/AnonymousUser/")
+]
+print("Anonymous user permissions:", anon_permissions)
+# e.g. ['view_asset', 'view_submissions']
+```
+R
+
+```r
+library(httr)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL <- "https://kf.kobotoolbox.org"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+response <- GET(paste0(BASE_URL, "/api/v2/assets/", ASSET_UID, "/"), headers)
+permissions <- content(response, as = "parsed")$permissions
+
+# Check which permissions are assigned to AnonymousUser
+anon_permissions <- Filter(
+ function(p) grepl("AnonymousUser", p$user),
+ permissions
+)
+for (p in anon_permissions) cat(p$label, "\n")
+```
+curl
+
+```bash
+# Grant AnonymousUser view_asset (equivalent of v1 public: true)
+curl -X POST \
+ -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ -d '{"user": "https://kf.kobotoolbox.org/api/v2/users/AnonymousUser/", "permission": "https://kf.kobotoolbox.org/api/v2/permissions/view_asset/"}' \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/permission-assignments/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL = "https://kf.kobotoolbox.org"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# Grant AnonymousUser view_asset (equivalent of v1 public: true)
+response = requests.post(
+ f"{BASE_URL}/api/v2/assets/{ASSET_UID}/permission-assignments/",
+ headers=headers,
+ json={
+ "user": f"{BASE_URL}/api/v2/users/AnonymousUser/",
+ "permission": f"{BASE_URL}/api/v2/permissions/view_asset/",
+ }
+)
+response.raise_for_status()
+print("Permission assigned:", response.json()["label"])
+```
+R
+
+```r
+library(httr)
+library(jsonlite)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL <- "https://kf.kobotoolbox.org"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+# Grant AnonymousUser view_asset (equivalent of v1 public: true)
+response <- POST(
+ paste0(BASE_URL, "/api/v2/assets/", ASSET_UID, "/permission-assignments/"),
+ headers,
+ body = toJSON(list(
+ user = paste0(BASE_URL, "/api/v2/users/AnonymousUser/"),
+ permission = paste0(BASE_URL, "/api/v2/permissions/view_asset/")
+ ), auto_unbox = TRUE),
+ content_type_json()
+)
+result <- content(response, as = "parsed")
+cat("Permission assigned:", result$label, "\n")
+```
+Example:
@@ -391,9 +879,125 @@ These endpoints return a flat list of all media files associated with the curren
1 _In `v2`, the related project is accessible via the `asset` field, which contains a full URL instead of an integer ID._
-**Example `v1` response**
+#### Code examples
+v2 anonymous user permissionscurl
+
+```bash
+# v1 (deprecated) — list all media files across all projects
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/metadata"
+
+# v1 (deprecated) — single file by integer ID
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/metadata/271"
+
+# v2 — list media files for a specific project
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/files/"
+
+# v2 — single file by uid
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/files/afoeCcF3AcGNpWUoM6bvKj9/"
+
+# v2 — upload a new media file
+curl -X POST \
+ -H "Authorization: Token xxxx" \
+ -F "file_type=form_media" \
+ -F "content=@/path/to/goose.jpg" \
+ "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/files/"
```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+ASSET_UID = "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL = "https://kf.kobotoolbox.org"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# v1 (deprecated)
+# response = requests.get("https://kc.kobotoolbox.org/api/v1/metadata", headers=headers)
+# files = response.json() # flat list across all projects
+
+# v2 — list media files for a specific project (paginated)
+response = requests.get(
+ f"{BASE_URL}/api/v2/assets/{ASSET_UID}/files/",
+ headers=headers
+)
+response.raise_for_status()
+files = response.json()["results"]
+
+for f in files:
+ print(f["uid"], f["metadata"]["filename"]) # was: f["id"], f["data_filename"]
+
+# v2 — upload a new media file
+with open("/path/to/goose.jpg", "rb") as fh:
+ upload = requests.post(
+ f"{BASE_URL}/api/v2/assets/{ASSET_UID}/files/",
+ headers=headers,
+ data={"file_type": "form_media"},
+ files={"content": fh}
+ )
+upload.raise_for_status()
+print("Uploaded:", upload.json()["metadata"]["filename"])
+```
+R
+
+```r
+library(httr)
+
+TOKEN <- "xxxx"
+ASSET_UID <- "a4etXeWtqcoodSxLV8a6Uq"
+BASE_URL <- "https://kf.kobotoolbox.org"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+# v1 (deprecated)
+# response <- GET("https://kc.kobotoolbox.org/api/v1/metadata", headers)
+# files <- content(response, as = "parsed") # flat list across all projects
+
+# v2 — list media files for a specific project
+response <- GET(
+ paste0(BASE_URL, "/api/v2/assets/", ASSET_UID, "/files/"),
+ headers
+)
+files <- content(response, as = "parsed")$results
+
+for (f in files) {
+ cat(f$uid, f$metadata$filename, "\n") # was: f$id, f$data_filename
+}
+
+# v2 — upload a new media file
+upload <- POST(
+ paste0(BASE_URL, "/api/v2/assets/", ASSET_UID, "/files/"),
+ headers,
+ body = list(
+ file_type = "form_media",
+ content = upload_file("/path/to/goose.jpg")
+ )
+)
+cat("Uploaded:", content(upload, as = "parsed")$metadata$filename, "\n")
+```
+v1 response
+
+```json
{
"id": 271,
"xform": 374,
@@ -407,23 +1011,25 @@ These endpoints return a flat list of all media files associated with the curren
"data_filename": "goose.jpg"
}
```
+v2 equivalent
-```
+```json
{
"uid": "afoeCcF3AcGNpWUoM6bvKj9",
- "asset": "http://kf.kobo.localhost/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/",
+ "asset": "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/",
"file_type": "form_media",
- "content": "http://kf.kobo.localhost/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/files/afoeCcF3AcGNpWUoM6bvKj9/content/",
+ "content": "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/files/afoeCcF3AcGNpWUoM6bvKj9/content/",
"metadata": {
"hash": "md5:93fb96bced1e3b392abfc22934afe51a",
"filename": "goose.jpg",
"mimetype": "image/jpeg"
- },
- ...
+ }
}
```
+curl
+
+```bash
+# v1 (deprecated)
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kc.kobotoolbox.org/api/v1/user"
+
+# v2
+curl -H "Authorization: Token xxxx" \
+ -H "Content-Type: application/json" \
+ "https://kf.kobotoolbox.org/me/"
+```
+Python
+
+```python
+import requests
+
+TOKEN = "xxxx"
+BASE_URL = "https://kf.kobotoolbox.org"
+headers = {"Authorization": f"Token {TOKEN}"}
+
+# v1 (deprecated)
+# response = requests.get("https://kc.kobotoolbox.org/api/v1/user", headers=headers)
+# user = response.json()
+# print(user["username"], user["email"])
+
+# v2
+response = requests.get(f"{BASE_URL}/me/", headers=headers)
+response.raise_for_status()
+user = response.json()
+
+print(user["username"]) # same as v1
+print(user["email"]) # same as v1
+print(user["extra_details"]["organization"]) # was: user["organization"]
+print(user["extra_details"]["country"]) # was: user["country"]
+print(user["extra_details__uid"]) # was: user["id"]
+```
+R
+
+```r
+library(httr)
+
+TOKEN <- "xxxx"
+BASE_URL <- "https://kf.kobotoolbox.org"
+headers <- add_headers(Authorization = paste("Token", TOKEN))
+
+# v1 (deprecated)
+# response <- GET("https://kc.kobotoolbox.org/api/v1/user", headers)
+# user <- content(response, as = "parsed")
+
+# v2
+response <- GET(paste0(BASE_URL, "/me/"), headers)
+user <- content(response, as = "parsed")
+
+cat(user$username, "\n") # same as v1
+cat(user$email, "\n") # same as v1
+cat(user$extra_details$organization, "\n") # was: user$organization
+cat(user$extra_details$country, "\n") # was: user$country
+cat(user$extra_details__uid, "\n") # was: user$id
+```
+v1 response
+
+```json
+{
+ "id": 42,
+ "username": "project_owner",
+ "email": "owner@example.org",
+ "city": "Nairobi",
+ "country": "KEN",
+ "organization": "Humanitarian Org",
+ "website": "https://example.org",
+ "twitter": "project_owner",
+ "gravatar": "https://www.gravatar.com/avatar/abc123?s=40",
+ "require_auth": true,
+ "api_token": "e291a91bf3dd94b45748f6865cd4710246219347"
+}
+```
+v2 equivalent
+
+```json
+{
+ "username": "project_owner",
+ "email": "owner@example.org",
+ "gravatar": "https://www.gravatar.com/avatar/abc123?s=40",
+ "extra_details": {
+ "city": "Nairobi",
+ "country": "KEN",
+ "organization": "Humanitarian Org",
+ "website": "https://example.org",
+ "twitter": "project_owner",
+ "require_auth": true
+ },
+ "extra_details__uid": "u9rb4EUVEgC22wbHfVfr7S"
+}
+```
+curl
@@ -180,7 +182,7 @@ for (p in projects) {
```
v1 response
@@ -229,7 +231,7 @@ Based on the `url` you get from the `data` property in the asset endpoint, you c
Note: The response structure is nearly the same, except that v2 introduces pagination. If you have more than 1,000 submissions, you will need to follow the next URL to retrieve subsequent pages.
v1 responsev2 anonymous user permissions