diff --git a/locales/ar/migrating_api.md b/locales/ar/migrating_api.md index 449d2993a..cba03a9d8 100644 --- a/locales/ar/migrating_api.md +++ b/locales/ar/migrating_api.md @@ -1,24 +1,71 @@ # الترحيل من واجهة برمجة التطبيقات v1 إلى v2 -**آخر تحديث:** 7 أغسطس 2025 +**آخر تحديث:** 19 مايو 2026 -كجزء من جهودنا المستمرة لتبسيط وتحديث منصة KoboToolbox، نقوم بإيقاف نقاط نهاية KPI وKoboCAT `v1` تدريجياً. جميع نقاط نهاية KPI وKoboCAT `v1` أصبحت الآن قديمة، وسيتم إزالتها بالكامل في يناير 2026. يتم إيقاف نقاط نهاية `v1` تدريجياً لصالح واجهة برمجة التطبيقات KPI `v2` الأكثر قوة والمدعومة بالكامل. +كجزء من جهودنا المستمرة لتبسيط وتحديث منصة KoboToolbox، نقوم بإيقاف نقاط نهاية KPI وKoboCAT `v1` تدريجياً. جميع نقاط نهاية KPI وKoboCAT `v1` أصبحت الآن قديمة، وسيتم إزالتها بالكامل في يونيو 2026. يتم إيقاف نقاط نهاية `v1` تدريجياً لصالح واجهة برمجة التطبيقات KPI `v2` الأكثر قوة والمدعومة بالكامل. يشرح هذا المقال كيفية ترحيل تكاملات واجهة برمجة التطبيقات الخاصة بك من واجهة برمجة التطبيقات `v1` (KoboCAT وKPI) إلى واجهة برمجة التطبيقات KPI `v2`. ويغطي كل نقطة نهاية `v1` قديمة ومعادلها في `v2` لمساعدتك في نقل سير العمل الخاص بك. +## المصادقة + +تتطلب جميع طلبات واجهة برمجة التطبيقات — v1 وv2 — رمز API. أدرجه في كل طلب باستخدام رأس `Authorization`: + +``` +Authorization: Token xxxx +``` + +استبدل `xxxx` برمزك الفعلي. يمكنك العثور على رمزك في `https://kf.kobotoolbox.org/token/` (أو ما يعادله على خادمك). + +**curl** +```bash +curl -H "Authorization: Token xxxx" "https://kf.kobotoolbox.org/api/v2/assets/" +``` + +**Python** (`requests`) +```python +import requests + +TOKEN = "xxxx" +BASE_URL = "https://kf.kobotoolbox.org" +headers = {"Authorization": f"Token {TOKEN}"} + +response = requests.get(f"{BASE_URL}/api/v2/assets/", headers=headers) +response.raise_for_status() +data = response.json() +``` + +**R** (`httr`) +```r +library(httr) + +TOKEN <- "xxxx" +BASE_URL <- "https://kf.kobotoolbox.org" + +response <- GET( + paste0(BASE_URL, "/api/v2/assets/"), + add_headers(Authorization = paste("Token", TOKEN)) +) +data <- content(response, as = "parsed") +``` + +

+ ملاحظة: رأس المصادقة هو نفسه عبر جميع إصدارات واجهة برمجة التطبيقات. ما يتغير يعتمد على مسار الترحيل الذي تسلكه: إذا كنت تنتقل من KPI v1 إلى KPI v2، فأنت تحتاج فقط إلى تحديث مسار URL. أما إذا كنت تنتقل من KoboCAT v1 إلى KPI v2، فستحتاج أيضاً إلى تحديث النطاق الأساسي (kc.kobotoolbox.orgkf.kobotoolbox.org)، ومسارات نقاط النهاية، وتكييف الكود للتعامل مع بنية الاستجابة الجديدة وأسماء الحقول — راجع الأقسام أدناه. +

+ + ## الترحيل من KPI v1 إلى KPI v2 -الترحيل من واجهة برمجة التطبيقات KPI القديمة (`v1`) إلى الإصدار الجديد (`v2`) أمر مباشر في معظم الحالات. +الترحيل من واجهة برمجة التطبيقات KPI القديمة (`v1`) إلى الإصدار الجديد (`v2`) أمر مباشر في معظم الحالات. بشكل عام، تحتاج فقط إلى تحديث المسار الأساسي من `/endpoint/` إلى `/api/v2/endpoint/`. ### استثناء لنقطة نهاية exports الاستثناء الوحيد للقاعدة أعلاه هو نقطة نهاية `/exports/`. في `v1`، كانت نقطة نهاية `/exports/` تُرجع **جميع الصادرات للمستخدم المصادق عليه** عبر جميع المشاريع. -في `v2`، لأسباب تتعلق بالأداء، أصبحت الصادرات الآن **محددة النطاق لكل مشروع** ويجب الوصول إليها عبر `/api/v2/assets/{asset_uid}/exports/`. +في `v2`، لأسباب تتعلق بالأداء، أصبحت الصادرات الآن **محددة النطاق لكل مشروع** ويجب الوصول إليها عبر `/api/v2/assets/{asset_uid}/exports/`. -## الترحيل من KoboCAT v1 إلى KPI v2 +## الترحيل من KoboCAT v1 إلى KPI v2 يسرد القسم التالي نقاط النهاية الرئيسية من واجهة برمجة التطبيقات KoboCAT `v1` ويوفر معادلاتها في `v2`. ### نقاط نهاية البيانات: قائمة المشاريع @@ -44,7 +91,81 @@ 1 _في نقطة نهاية `/api/v2/assets`، لم تعد المعرفات الصحيحة المتسلسلة مستخدمة. يتم تحديد كل إدخال بشكل فريد بواسطة `uid` أبجدي رقمي_ -**مثال على استجابة `v1`** +#### أمثلة على الكود + +
+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` المعادلة** +
+المعادل في v2 ```json { @@ -69,8 +192,8 @@ "data": "https://kf.kobotoolbox.org/api/v2/assets/a4etXeWtqcoodSxLV8a6Uq/data/" ... } - ``` +
--- @@ -82,14 +205,96 @@ | `/api/v1/data/` | `/api/v2/assets/{uid}/data/` | | `/api/v1/data//` | `/api/v2/assets/{uid}/data/{submission_id}/` | -بناءً على `url` الذي تحصل عليه من خاصية `data` في نقطة نهاية الأصل، يمكنك جلب بيانات الإرسال في `v2` باستخدام. +بناءً على `url` الذي تحصل عليه من خاصية `data` في نقطة نهاية الأصل، يمكنك جلب بيانات الإرسال في `v2` باستخدام.

- ملاحظة: بنية الاستجابة متشابهة تقريباً، باستثناء أن v2 يقدم ترقيم الصفحات. + ملاحظة: بنية الاستجابة متشابهة تقريباً، باستثناء أن v2 يقدم ترقيم الصفحات. إذا كان لديك أكثر من 1,000 إرسال، ستحتاج إلى متابعة عنوان URL في `next` لاسترجاع الصفحات التالية.

+#### أمثلة على الكود + +استبدل `a4etXeWtqcoodSxLV8a6Uq` بـ `uid` مشروعك (راجع [نقطة نهاية قائمة المشاريع](#data-endpoints-project-list) أعلاه). + +
+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") +``` +
+ **الأذونات** في `v2`، لم تعد الحقول `public` و`public_data` و`require_auth` معروضة كسمات منطقية. بدلاً من ذلك، **يتم التحكم في الوصول المجهول عبر تعيينات أذونات صريحة لـ `AnonymousUser`**. تنطبق التعيينات التالية: -- `public: true` ← لدى `AnonymousUser` إذن `view_asset` -- `public_data: true` ← لدى `AnonymousUser` إذن `view_submissions` -- `require_auth: false` ← لدى `AnonymousUser` إذن `add_submissions` +- `public: true` → لدى `AnonymousUser` إذن `view_asset` +- `public_data: true` → لدى `AnonymousUser` إذن `view_submissions` +- `require_auth: false` → لدى `AnonymousUser` إذن `add_submissions` + +الأذونات متاحة في نقطة نهاية تفاصيل الأصل (`/api/v2/assets/{uid}/`) تحت خاصية `permissions`. + +#### أمثلة على الكود + +**قراءة الأذونات** + +
+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") +``` +
+ +**تعيين الأذونات لـ AnonymousUser** +لتكرار إعداد `public: true` في v1، أرسل طلب POST لتعيين إذن جديد إلى نقطة نهاية `permission-assignments`. + +
+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") +``` +
+ +#### أمثلة على الاستجابات
مثال: أذونات المستخدم المجهول في v2 @@ -375,9 +865,125 @@ 1 _في `v2`، يمكن الوصول إلى المشروع ذي الصلة عبر حقل `asset`، الذي يحتوي على عنوان URL كامل بدلاً من معرف صحيح._ -**مثال على استجابة `v1`** +#### أمثلة على الكود +
+curl + +```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` المعادلة** +
+المعادل في 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" - }, - ... + } } ``` +
--- @@ -443,4 +1051,119 @@ 1 _لم يتم نقله إلى `v2`_ -2 _استخدم https://kf.domain.tld/token بدلاً من ذلك_ \ No newline at end of file +2 _استخدم https://kf.domain.tld/token بدلاً من ذلك_ + +#### أمثلة على الكود + +
+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" +} +``` +
diff --git a/locales/es/migrating_api.md b/locales/es/migrating_api.md index d807f34dc..77d49e09e 100644 --- a/locales/es/migrating_api.md +++ b/locales/es/migrating_api.md @@ -1,11 +1,58 @@ # Migración de la API v1 a v2 -**Última actualización:** 7 ago 2025 +**Última actualización:** 19 may 2026 -Como parte de nuestros esfuerzos continuos para optimizar y modernizar la plataforma KoboToolbox, estamos eliminando gradualmente los endpoints `v1` de KPI y KoboCAT. Todos los endpoints `v1` de KPI y KoboCAT están ahora obsoletos y se eliminarán por completo en enero de 2026. Los endpoints `v1` están siendo reemplazados por la API KPI `v2`, que es más robusta y cuenta con soporte completo. +Como parte de nuestros esfuerzos continuos para optimizar y modernizar la plataforma KoboToolbox, estamos eliminando gradualmente los endpoints `v1` de KPI y KoboCAT. Todos los endpoints `v1` de KPI y KoboCAT están ahora obsoletos y se eliminarán por completo en junio de 2026. Los endpoints `v1` están siendo reemplazados por la API KPI `v2`, que es más robusta y cuenta con soporte completo. Este artículo explica cómo migrar tus integraciones de API desde la API `v1` (KoboCAT y KPI) a la API KPI `v2`. Cubre cada endpoint `v1` obsoleto y su equivalente en `v2` para ayudarte a realizar la transición de tus flujos de trabajo. +## Autenticación + +Todas las solicitudes a la API — v1 y v2 — requieren un token de API. Inclúyelo en cada solicitud mediante el encabezado `Authorization`: + +``` +Authorization: Token xxxx +``` + +Reemplaza `xxxx` con tu token real. Puedes encontrar tu token en `https://kf.kobotoolbox.org/token/` (o el equivalente de tu servidor). + +**curl** +```bash +curl -H "Authorization: Token xxxx" "https://kf.kobotoolbox.org/api/v2/assets/" +``` + +**Python** (`requests`) +```python +import requests + +TOKEN = "xxxx" +BASE_URL = "https://kf.kobotoolbox.org" +headers = {"Authorization": f"Token {TOKEN}"} + +response = requests.get(f"{BASE_URL}/api/v2/assets/", headers=headers) +response.raise_for_status() +data = response.json() +``` + +**R** (`httr`) +```r +library(httr) + +TOKEN <- "xxxx" +BASE_URL <- "https://kf.kobotoolbox.org" + +response <- GET( + paste0(BASE_URL, "/api/v2/assets/"), + add_headers(Authorization = paste("Token", TOKEN)) +) +data <- content(response, as = "parsed") +``` + +

+ 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 (kc.kobotoolbox.orgkf.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. +

+ + ## Migración de KPI v1 a KPI v2 La migración de la API KPI antigua (`v1`) a la nueva versión (`v2`) es sencilla en la mayoría de los casos. @@ -44,7 +91,81 @@ Este endpoint devuelve una lista de formularios a los que tienes acceso, con enl 1 _En el endpoint `/api/v2/assets`, ya no se utilizan identificadores enteros secuenciales. Cada entrada se identifica de forma única mediante un `uid` alfanumérico_ -**Ejemplo de respuesta `v1`** +#### Ejemplos de código + +
+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") +} +``` +
+ +#### Ejemplos de respuestas + +
+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" } ``` +
-**Respuesta equivalente `v2`** +
+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/" ... } - ``` +
--- @@ -85,11 +208,93 @@ Estos endpoints recuperan todos los datos de envío de un proyecto específico o Basándote en la `url` que obtienes de la propiedad `data` en el endpoint del asset, puedes obtener los datos de envío en `v2`.

- Nota: La estructura de respuesta es casi la misma, excepto que v2 introduce paginación. + 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.

+#### Ejemplos de código + +Reemplaza `a4etXeWtqcoodSxLV8a6Uq` con el `uid` de tu proyecto (consulta el [endpoint de lista de proyectos](#endpoints-de-datos-lista-de-proyectos) arriba). + +
+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") +``` +
+ +#### Ejemplos de respuestas + +
+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 = ", ") +``` +
+ +#### Ejemplos de respuestas +
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") +``` +
+ **Permisos** En `v2`, los campos `public`, `public_data` y `require_auth` ya no están expuestos como atributos booleanos. En su lugar, **el acceso anónimo se controla mediante asignaciones de permisos explícitas al `AnonymousUser`**. @@ -303,9 +657,145 @@ Se aplican los siguientes mapeos: - `public_data: true` → el `AnonymousUser` tiene el permiso `view_submissions` - `require_auth: false` → el `AnonymousUser` tiene el permiso `add_submissions` +Los permisos están disponibles en el endpoint de detalle del asset (`/api/v2/assets/{uid}/`) bajo la propiedad `permissions`. + +#### Ejemplos de código + +**Lectura de permisos** + +
+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") +``` +
+ +**Asignación de permisos al AnonymousUser** + +Para replicar la configuración `public: true` de v1, envía un POST con una nueva asignación de permisos al endpoint `permission-assignments`.
-Ejemplo: permisos de usuario/a anónimo/a en v2 +curl + +```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") +``` +
+ +#### Ejemplos de respuestas + +
+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//labels` de `v1` devuelve un diccionario que mapea cada nombre de campo en el formulario a su etiqueta correspondiente, permitiendo una visualización más amigable de los datos del formulario. -Este endpoint no se ha portado a `v2`, pero aún es posible **etiquetar** un proyecto, como se describe [arriba](#tags). +Este endpoint no se ha portado a `v2`, pero aún es posible **etiquetar** o **marcar** un proyecto, como se describe [arriba](#tags). --- @@ -375,9 +865,125 @@ Estos endpoints devuelven una lista plana de todos los archivos multimedia asoci 1 _En `v2`, el proyecto relacionado es accesible a través del campo `asset`, que contiene una URL completa en lugar de un ID entero._ -**Ejemplo de respuesta `v1`** +#### Ejemplos de código +
+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") +``` +
+ +#### Ejemplos de respuestas + +
+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" } ``` +
-**Respuesta equivalente `v2`** +
+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" - }, - ... + } } ``` +
--- @@ -443,4 +1051,119 @@ Este endpoint devuelve información de perfil sobre el/la usuario/a autenticado/ 1 _No portado a `v2`_ -2 _Usa https://kf.domain.tld/token en su lugar_ \ No newline at end of file +2 _Usa https://kf.domain.tld/token en su lugar_ + +#### Ejemplos de código + +
+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 +``` +
+ +#### Ejemplos de respuestas + +
+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" +} +``` +
diff --git a/locales/fr/migrating_api.md b/locales/fr/migrating_api.md index e24a166cf..05cc75899 100644 --- a/locales/fr/migrating_api.md +++ b/locales/fr/migrating_api.md @@ -1,11 +1,59 @@ # Migration de l'API v1 vers v2 -**Dernière mise à jour :** 7 août 2025 +**Dernière mise à jour :** 19 mai 2026 -Dans le cadre de nos efforts continus pour rationaliser et moderniser la plateforme KoboToolbox, nous supprimons progressivement les points de terminaison KPI et KoboCAT `v1`. Tous les points de terminaison KPI et KoboCAT `v1` sont désormais obsolètes et seront entièrement supprimés en janvier 2026. Les points de terminaison `v1` sont progressivement supprimés au profit de l'API KPI `v2`, plus robuste et entièrement prise en charge. + +Dans le cadre de nos efforts continus pour rationaliser et moderniser la plateforme KoboToolbox, nous supprimons progressivement les points de terminaison KPI et KoboCAT `v1`. Tous les points de terminaison KPI et KoboCAT `v1` sont désormais obsolètes et seront entièrement supprimés en juin 2026. Les points de terminaison `v1` sont progressivement supprimés au profit de l'API KPI `v2`, plus robuste et entièrement prise en charge. Cet article explique comment migrer vos intégrations API de l'API `v1` (KoboCAT et KPI) vers l'API KPI `v2`. Il couvre chaque point de terminaison `v1` obsolète et son équivalent `v2` pour vous aider à faire la transition de vos flux de travail. +## Authentification + +Toutes les requêtes API — v1 et v2 — nécessitent un token API. Incluez-le dans chaque requête via l'en-tête `Authorization` : + +``` +Authorization: Token xxxx +``` + +Remplacez `xxxx` par votre token réel. Vous pouvez trouver votre token à l'adresse `https://kf.kobotoolbox.org/token/` (ou l'équivalent de votre serveur). + +**curl** +```bash +curl -H "Authorization: Token xxxx" "https://kf.kobotoolbox.org/api/v2/assets/" +``` + +**Python** (`requests`) +```python +import requests + +TOKEN = "xxxx" +BASE_URL = "https://kf.kobotoolbox.org" +headers = {"Authorization": f"Token {TOKEN}"} + +response = requests.get(f"{BASE_URL}/api/v2/assets/", headers=headers) +response.raise_for_status() +data = response.json() +``` + +**R** (`httr`) +```r +library(httr) + +TOKEN <- "xxxx" +BASE_URL <- "https://kf.kobotoolbox.org" + +response <- GET( + paste0(BASE_URL, "/api/v2/assets/"), + add_headers(Authorization = paste("Token", TOKEN)) +) +data <- content(response, as = "parsed") +``` + +

+ 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 (kc.kobotoolbox.orgkf.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. +

+ + ## Migration de KPI v1 vers KPI v2 La migration de l'ancienne API KPI (`v1`) vers la nouvelle version (`v2`) est simple dans la plupart des cas. @@ -44,7 +92,81 @@ Ce point de terminaison renvoie une liste de formulaires auxquels vous avez acc 1 _Dans le point de terminaison `/api/v2/assets`, les identifiants entiers séquentiels ne sont plus utilisés. Chaque entrée est identifiée de manière unique par un `uid` alphanumérique_ -**Exemple de réponse `v1`** +#### Exemples de code + +
+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") +} +``` +
+ +#### Exemples de réponses + +
+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" } ``` +
-**Réponse équivalente `v2`** +
+É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/" ... } - ``` +
--- @@ -85,11 +209,93 @@ Ces points de terminaison récupèrent toutes les données de soumission pour un En vous basant sur l'`url` que vous obtenez de la propriété `data` dans le point de terminaison asset, vous pouvez récupérer les données de soumission dans `v2`.

- Remarque : La structure de réponse est presque identique, sauf que v2 introduit la pagination. + 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.

+#### Exemples de code + +Remplacez `a4etXeWtqcoodSxLV8a6Uq` par l'`uid` de votre projet (voir le [point de terminaison de liste de projets](#points-de-terminaison-de-données--liste-de-projets) ci-dessus). + +
+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") +``` +
+ +#### Exemples de réponses + +
+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 = ", ") +``` +
+ +#### Exemples de réponses +
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") +``` +
+ **Permissions** 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`**. @@ -303,9 +658,145 @@ Les correspondances suivantes s'appliquent : - `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`. + +#### Exemples de code + +**Lecture des permissions** + +
+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") +``` +
+ +**Assigner des permissions à l'AnonymousUser** + +Pour répliquer le paramètre `public: true` de v1, envoyez une requête POST d'attribution de permission au point de terminaison `permission-assignments`. + +
+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 v2 +R + +```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") +``` +
+ +#### Exemples de réponses + +
+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") ``` +
+ +#### Exemples de réponses + +
+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" } ``` +
-**Réponse équivalente `v2`** +
+É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" - }, - ... + } } ``` +
--- @@ -443,4 +1052,119 @@ Ce point de terminaison renvoie les informations de profil sur l'utilisatrice ou 1 _Non porté vers `v2`_ -2 _Utilisez https://kf.domain.tld/token à la place_ \ No newline at end of file +2 _Utilisez https://kf.domain.tld/token à la place_ + +#### Exemples de code + +
+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 +``` +
+ +#### Exemples de réponses + +
+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" +} +``` +
diff --git a/source/migrating_api.md b/source/migrating_api.md index 18608b91f..f60b931d5 100644 --- a/source/migrating_api.md +++ b/source/migrating_api.md @@ -1,5 +1,5 @@ # Migrating from v1 to v2 API -**Last updated:** 10 Dec 2025 +**Last updated:** 19 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. @@ -7,6 +7,53 @@ As part of our ongoing efforts to streamline and modernize the KoboToolbox platf This article explains how to migrate your API integrations from the `v1` API (KoboCAT and KPI) to the KPI `v2` API. It covers each deprecated `v1` endpoint and its `v2` equivalent to help you transition your workflows. +## Authentication + +All API requests — v1 and v2 — require an API token. Include it in every request using the `Authorization` header: + +``` +Authorization: Token xxxx +``` + +Replace `xxxx` with your actual token. You can find your token at `https://kf.kobotoolbox.org/token/` (or your server's equivalent). + +**curl** +```bash +curl -H "Authorization: Token xxxx" "https://kf.kobotoolbox.org/api/v2/assets/" +``` + +**Python** (`requests`) +```python +import requests + +TOKEN = "xxxx" +BASE_URL = "https://kf.kobotoolbox.org" +headers = {"Authorization": f"Token {TOKEN}"} + +response = requests.get(f"{BASE_URL}/api/v2/assets/", headers=headers) +response.raise_for_status() +data = response.json() +``` + +**R** (`httr`) +```r +library(httr) + +TOKEN <- "xxxx" +BASE_URL <- "https://kf.kobotoolbox.org" + +response <- GET( + paste0(BASE_URL, "/api/v2/assets/"), + add_headers(Authorization = paste("Token", TOKEN)) +) +data <- content(response, as = "parsed") +``` + +

+ 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 (kc.kobotoolbox.orgkf.kobotoolbox.org), the endpoint paths, and adapt your code to handle the new response structure and field names — see the sections below. +

+ + ## Migrating from KPI v1 to KPI v2 Migrating from the old KPI API (`v1`) to the new version (`v2`) is straightforward in most cases. @@ -45,7 +92,81 @@ This endpoint returns a list of forms you have access to, with links to their su 1 _In the `/api/v2/assets` endpoint, sequential integer identifiers are no longer used. Each entry is uniquely identified by an alphanumeric `uid`_ -**Example `v1` response** +#### Code examples + +
+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") +} +``` +
+ +#### Response examples + +
+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" } ``` +
-**Equivalent `v2` response** +
+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/" ... } - ``` +
--- @@ -86,11 +209,93 @@ These endpoints retrieve all submission data for a specific project or a single Based on the `url` you get from the `data` property in the asset endpoint, you can fetch the submission data in `v2` using.

- Note: The response structure is nearly the same, except that 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.

+#### Code examples + +Replace `a4etXeWtqcoodSxLV8a6Uq` with your project's `uid` (see [project list endpoint](#data-endpoints-project-list) above). + +
+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") +``` +
+ +#### Response examples + +
+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 = ", ") +``` +
+ +#### Response examples +
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") +``` +
+ **Permissions** In `v2`, the fields `public`, `public_data`, and `require_auth` are no longer exposed as boolean attributes. Instead, **anonymous access is controlled via explicit permission assignments to the `AnonymousUser`**. @@ -304,6 +658,142 @@ The following mappings apply: - `public_data: true` → the `AnonymousUser` has the `view_submissions` permission - `require_auth: false` → the `AnonymousUser` has the `add_submissions` permission +Permissions are available on the asset detail endpoint (`/api/v2/assets/{uid}/`) under the `permissions` property. + +#### Code examples + +**Reading permissions** + +
+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") +``` +
+ +**Assigning permissions to AnonymousUser** + +To replicate a v1 `public: true` setting, POST a new permission assignment to the `permission-assignments` endpoint. + +
+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") +``` +
+ +#### Response examples
Example: v2 anonymous user permissions @@ -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 +
+curl + +```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") +``` +
+ +#### Response examples + +
+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" } ``` +
-**Equivalent `v2` response** +
+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" - }, - ... + } } ``` +
--- @@ -446,6 +1054,121 @@ This endpoint returns profile information about the authenticated user, includin 2 _Use https://kf.domain.tld/token instead_ +#### Code examples + +
+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 +``` +
+ +#### Response examples + +
+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" +} +``` +
+