CONNECTED APPS · V1

Connected Apps API 參考

本頁是應用管理金鑰的 /api/apps/v1 合約。新流程填表使用 流程接入 API,不是本頁的普通 responses;流程資料副本另見同步 API。新流程正式答案固定由 OpenDashboard 管理。

同一套 API 可用於直接保存到 OpenDashboard、由網站讀回所屬應用資料,或由網站保留本地資料並同步給 OpenDashboard。保存方式及介面可獨立選擇。現有 API 及個人報告連結可直接使用;普通表單 API 不包含通用嵌入元件或自動雙向同步。流程已有連結/iframe 及單向資料變更通知,使用前述獨立契約。

瀏覽器透過你自己的後端操作;後端核對使用者對 respondent 的存取權,應用金鑰只保護應用範圍,不代替訪客登入。直接保存模式成功回傳 id/version 後才顯示提交成功。雙份保存需由網站選定主要寫入端、安排待同步工作及處理衝突,不能把本地保存成功當作 OpenDashboard 已接收。

通用網站接入契約。所有範例均為合成資料。路徑以 /api/apps/v1 開始,主機使用你的 OpenDashboard 部署,例如 https://registration.konnec.ai。本頁描述同版程式的能力;部署需包含相應資料庫變更,請先在管理員提供的測試環境驗證。

設計網站自己的填表頁,見自己的填表介面教程,內有可下載定義、答案及前後端分工範例。

驗證、格式及限制

http
Authorization: Bearer <SERVER_SIDE_APP_KEY>
Content-Type: application/json

JSON 欄位採 snake_case。JSON 請求最多 256 KB;每應用正式及測試合計 600 次/10 分鐘。金鑰只能由伺服器使用,不能送到瀏覽器。應用隔離及 test/live 隔離適用於所有資源;外來資源回 404,不透露存在與否。

錯誤格式:

json
{
  "ok": false, "error": "invalid_answers",
  "message": "The response was not saved. Fix these and send it again.",
  "fields": { "participants.0.category": "Unknown choice." }
}

某些定義驗證另帶 errors 陣列。以 error 判斷程式分支,用 fields/errors 協助更正,不要只依英文 message。

應用資訊

GET /me → 200 { "app": { "id": "demo-app", "name": "Demo app" }, "test": true }。

GET /me?capabilities=true 額外帶 capabilities: { "identified_mode": false }。預設呼叫保持原格式。identified mode 由超級管理員設定,才可宣告身份欄位及保存私人附件。

表單定義

POST /forms → 201 { "id": "evt_…" }。普通應用表單是 survey,由來源網站顯示,沒有公開 OpenDashboard 填表頁。正文為 title(必填,最多 200 字)、description(選填,最多 2000 字)、questions(最多 50 題)。建立不是冪等操作。

json
{
  "title": "Demo survey",
  "questions": [
    { "key": "category", "title": "類別", "type": "short_text", "personal": false },
    { "key": "rating", "title": "評分", "type": "number", "min": 0, "max": 10 }
  ]
}

每題 key 為 1–40 個小寫英文字、數字或 _,在表單內唯一。required: true 為必答,否則選填。不能提供 quiz 答案;來源應用自行評分。

type答案及設定
hidden文字最多 500 字;數字轉文字,永不進報表。
short_text / paragraph短答最多 500;長答預設 5,000,可用 max_length 設為 1–100,000 整數。計數為 UTF-16 字元,超長拒絕,不截斷。
number有限 JSON 數字;可選 min、max、integer、unit,0 保留。
dateYYYY-MM-DD。
single_choice / dropdown一個 options 值;options 2–30 個。
multiple_choiceoptions 值的陣列。
app_result以 fields 宣告的結果物件。
repeating_group以 fields 宣告的物件陣列。
section / text_block純排版,不接收答案。
email / phone / file_uploadidentified mode 專用;永遠 personal。電郵轉小寫;電話最多 40 字;檔案為 HTTPS URL,最多 2000 字,URL 本身不會把檔案複製到 OpenDashboard。

app_result 每題 1–30 fields,型別 number/text/labels;text 最多 500 字,labels 最多 50 個非空字串、每個最多 40 字。欄位可設 required,未填選填欄位為 null,未知 key 是錯誤。

重複群組及個人分類

群組宣告整數 0 <= min <= max <= 100,每層最多 50 fields。可巢狀一層,不能再深。答案物件按 key 對應,未知 key 拒絕。fields 可用 text/short_text、paragraph、number、date、選擇題、labels 及 identified mode 的身份欄位;選擇欄位可設 min_selected/max_selected。

json
{
  "key": "participants", "title": "參加者", "type": "repeating_group",
  "min": 0, "max": 100, "personal": false,
  "fields": [
    { "key": "category", "label": "類別", "type": "text", "personal": false },
    { "key": "topics", "label": "主題", "type": "multiple_choice", "options": ["A", "B"], "personal": false }
  ]
}

答案例:{ "participants": [{ "category": "Demo", "topics": ["A", "B"] }] }。正式回覆檢查群組最少數量及所有必填項目。

新定義的文字、hidden、labels 及群組預設 personal;數字、日期、選擇題預設非個人。電郵、電話、檔案永遠 personal。群組包含任何 personal 子欄位時,合集報表排除整個群組。早期沒有旗標的定義保留既有分類;改旗標前核對報表影響。只有安全的非識別文字才能明確標 personal: false。

保留題目 key:respondent、form_id、form_title、form_position、submitted_at、items_answered 及 avg_ 開頭。保留 app_result field key:respondent、form_id、form_title、form_position、item_key、item_title、item_position、submitted_at。

GET /forms/:id → 200,帶 id、title、description、test、created_at、questions,以及補齊的預設值。

PATCH /forms/:id/fields 只改 personal 分類,不能改型別或欄位結構。1–200 項,全成功或全失敗。群組內使用點分 field 路徑。

json
{ "fields": [
  { "question": "category", "personal": false },
  { "question": "participants", "field": "category", "personal": false }
] }

→ 200,傳回完整表單。分類在下次合集讀取、工作區刷新及個人報告開啟生效;已凍結分享/快照不重寫。

提交、取代及讀取回覆

POST /forms/:id/responses → 201,傳回 { "id": "reg_…", "version": 1 }。

json
{
  "respondent": "demo-contact-a", "answers": { "category": "Demo", "rating": 8 },
  "submitted_at": "2026-01-01T00:00:00Z", "replace": true, "expected_version": 0
}

respondent 為 4–64 個英文字、數字、_ 或 -;使用不含個人身份的穩定來源編號。每表單每 respondent 只有一份回覆。answers 以題目 key 為鍵,整份驗證,錯一欄整份不保存。

submitted_at 選填,預設現在;有 expected_version 時必須明確提供固定時間。ISO 時間不能超過現在 5 分鐘。第二次提交需 replace true,成功保留 id、version 加 1。

expected_version 選填,非負安全整數;0 表示期望不存在。只有目前版本吻合才寫入。已儲存版本等於 expected+1,且正規化答案和時間完全相同時,精確重試回 200 原 id/version,不再次寫入。其他過時版本回 409 response_changed。沒有 expected_version 的 replace 保持舊行為,重送仍可能加版本。

GET /forms/:id/responses → 200 { "responses": [...] },最早提交在前。每項含 id、respondent、version、submitted_at、answers,包括私人答案,因此只能由應用後端使用,不可當公開報表輸出。可加 ?respondent=demo-7f3a9c 只讀取該回覆者(不存在時為空陣列),服務端會先檢查應用/TEST 或 LIVE 歸屬再精確篩選;無效或重複參數回 400 invalid_respondent。不加參數仍讀取全部;這個端點沒有 cursor 分頁。

合集及資料表

POST /collections → 201 { "id": "col_…" }。title 必填,description 選填,form_ids 1–50 個唯一同應用、同模式的表單,按次序列出。

json
{ "title": "Demo collection", "form_ids": ["<FORM_ID>"] }

相同報表欄位 key 跨表單型別不一致會拒絕。合集不可修改;不同成員需新建。建立不是冪等操作。

GET /collections/:id → 200,帶 id、title、description、test 及 forms(id、title、position)。

GET /collections/:id/tables?respondent=<ref> → 200 { "tables": { ... } }。respondent 選填;指定時資料庫只讀該人的行。每個表含 columns、rows;columns 含 key、type(string/number/date)、role(dimension/measure)、description。空表保留字典。

表/unit一行代表甚麼計數含義
submissions一位回覆者在一份表單的回覆提交份數。
items一個已答 app_result 題目題目數,不是重複群組。
labelsapp_result 中一個非個人標籤標籤次數。
groups:…/項目安全群組中的一個項目群組項目數。
groups:…/選項群組選擇欄位的一個已選選項選項次數;可多於項目數。

固定上下文含 respondent、form_id、form_title、form_position、submitted_at。submissions 另帶 items_answered、app_result 數值的 avg_ 欄位及安全頂層簡答/單選/下拉/數字。items 帶 item_key、item_title、item_position 及安全 number/text 結果。labels 帶 field_key、label。hidden 和個人答案不輸出。

群組表鍵是不透明字串,使用回傳 label、unit、description 及字典,不要自行拼接。帶 response_key、item_key 及巢狀 parent_item_key;選項表帶 choice。父層安全文字維度可附為 parent:key,父層數值不重複。每份表單最多展開 250,000 項目/選項,完整合集最多 4 MiB,超限回 413 collection_tables_too_large,沒有部分成功資料。

個人報告連結

管理員需先在應用工作區建立儀表板,指定 Page.template,例如 workshop_report。API 使用範本名稱,不使用 page id。

POST /collections/:id/report-links:

json
{ "respondent": "demo-contact-a", "template": "workshop_report", "expires_in_days": 30 }

→ 201 { "id": "…", "url": "https://registration.konnec.ai/dashboard/r/<id>", "expires_at": "…" }。template 1–40 個小寫英文字、數字、_;expires_in_days 整數 1–3650,預設 365。同一 respondent/template 仍有效時傳回原連結 200;過期或停用後建立新連結 201。

每次開啟重新按 respondent 讀合集及應用記錄。手動來源/固定文字對所有人相同,範本不可含全體個人資料。staff-only 或 live operational 範本不能做個人報告。持有 URL 可查看,不必登入;不要公開 URL。

DELETE /report-links/:id → 200 { "id": "…", "revoked_at": "…" },立即停用,重複停用同結果。應用停用、範本刪除/改名、過期也使連結失效。分享快照 /dashboard/s/... 是另一功能。

歷史記錄 Records

responses 保存最新答案;records 是不能取代的歷史。每筆包含 kind、schema、summary 和私人 data。summary 是經驗證的報表欄位;data 是任意 JSON,永不進入資料表、模板、分享或報告。

POST /record-kinds → 201;相同聲明重送為 200,不同聲明同 kind/schema 回 409。

json
{
  "kind": "practice", "schema": "demo.practice/1",
  "summary_fields": [
    { "key": "score", "type": "number", "min": 0, "max": 100 },
    { "key": "category", "type": "text", "personal": false },
    { "key": "tasks", "type": "list", "personal": false, "fields": [
      { "key": "category", "type": "text", "personal": false },
      { "key": "score", "type": "number" }
    ] }
  ]
}

kind 1–40 小寫字、數字、_;schema 1–100 英文字、數字、_ . : / -,首字為英文字或數字。summary_fields 1–30 欄,number/text/labels 規則同 app_result;list 最多 100 物件,內含 1–30 個葉欄位,不能巢狀 list。保留 key:respondent、record_id、kind、record_kind、record_schema、recorded_at、is_test、deleted_at、list_key、item_position。

GET /record-kinds?kind= → { "record_kinds": [...] },依 kind、schema 排序。改聲明需新 schema,不修改舊記錄。

POST /records → 201 { "id": "rec_…", "record_id": "demo-attempt-1", "recorded_at": "…" }。

json
{
  "record_id": "demo-attempt-1", "respondent": "demo-contact-a",
  "kind": "practice", "schema": "demo.practice/1",
  "recorded_at": "2026-01-01T00:00:00Z",
  "summary": { "score": 80, "category": "Demo", "tasks": [] },
  "data": { "private_notes": "Synthetic notes only" }
}

record_id 必填,1–128 英文字、數字、_ . : -,同應用/模式永久唯一。recorded_at 必填,不能超過現在 5 分鐘;respondent 規則同回覆。summary 按聲明全份驗證;data 選填,預設 null。compact UTF-8 JSON 大小:summary ≤8192 bytes、data ≤65,536 bytes。

固定 record_id 精確重送傳 200 原結果;內容不同為 409 record_exists;刪除過為 409 record_deleted,不可重用。JSON 物件 key 次序/空白不影響相同判斷;陣列順序及正規化內容有影響。

GET /records?respondent=&kind=&since=&limit=&cursor=&include_deleted= → records、next_cursor。最早 recorded_at 然後 id 排序;since inclusive;limit 1–500,預設 100。保持相同篩選及帶 next_cursor 取下頁,到 null 為止。晚到但時間較早的記錄可能需要另一次讀取。include_deleted=true 會包含 tombstone,其 summary/data 為 null。

工作區的「應用記錄」產生 records/record_items 長表;只含安全 summary 數值、明確非個人文字及非個人 list,labels 不展開。data 永不進報表,不同 schema 欄位型別衝突時該欄不輸出。

DELETE /records?respondent=<ref> → { "respondent": "demo-contact-a", "deleted": 2 }。清除所有該人記錄 summary/data,但永久保留識別及刪除時間;不刪表單答案、附件或凍結分享/快照。

私人附件 Uploads

identified mode 專用。metadata 和檔案均私有,傳給應用後端的下載 URL 只有 60 秒。這不是公開表單上傳頁。

POST /uploads → 200,必填 external_id、name、size、content_type、sha256。

json
{
  "external_id": "demo-file-1", "name": "synthetic.txt", "size": 5,
  "content_type": "text/plain",
  "sha256": "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
}

這個摘要對應 UTF-8 原文 hello(沒有換行)。external_id 1–128 英文字、數字、_ . : -;name 非空最多 200 字;size 為 1 byte 至 50 MiB 的整數;sha256 為 64 個小寫十六進制。content_type 是合法 MIME type,最多 150 字。

未完成時回 id、metadata、completed false、upload_url 及 token。相同 external_id/metadata 可重試;不同內容回 409 upload_exists。保存 id,不記錄 signed URL/token。

js
// reservation 是 POST /uploads 的 JSON;bytes 是相同五個 UTF-8 bytes。
if (!reservation.completed) {
  const put = await fetch(reservation.upload_url, {
    method: 'PUT',
    headers: { 'Content-Type': 'text/plain', 'x-upsert': 'false' },
    body: new TextEncoder().encode('hello')
  });
  if (!put.ok) throw new Error('Upload outcome requires reconciliation');
}

只把檔案 bytes 送到 signed URL,不能把 OpenDashboard app key 轉寄給儲存服務。POST /uploads/:id 驗證實際 bytes 的大小/SHA-256 後標 completed,重試為 200。未上傳回 409 upload_not_ready;大小/摘要不符回 upload_content_mismatch,不能跳過校驗。

GET /uploads/:id → 完成 metadata、url、expires_in 60,private/no-store。未完成不能下載,外來應用/模式 id 回 404。私人引用保存於私人 data,不放儀表板。現有 uploads 沒有刪除端點;record 刪除不連帶抹除附件,保留/清除需另由管理員處理。

錯誤及重試速查

HTTPerror處理
400invalid_json / invalid_body / invalid_form / invalid_answers / invalid_fields / invalid_collection按 fields 或 errors 更正,整份重新送。
400invalid_respondent / invalid_query / invalid_record_kind / invalid_record / summary_invalid / invalid_report_link / invalid_upload更正識別、聲明或限制。
400identified_mode_required聯絡管理員確認身份模式。
401invalid_app_key / app_disabled核對金鑰或應用狀態,不盲目重試。
404form_not_found / collection_not_found / record_kind_unknown / upload_not_found / report_link_not_found / template_not_found核對應用、模式和 id/範本設定。
409already_submitted / response_changed讀取最新回覆,處理取代或版本衝突。
409record_kind_conflict / record_exists / record_deleted / upload_exists使用正確聲明/相同內容,或新識別,不覆寫不可變資料。
409upload_not_ready / upload_content_mismatch核對 bytes/傳輸結果,再完成上傳。
413request_body_too_large / record_too_large / collection_tables_too_large縮減或分拆資料,不接受部分結果。
429rate_limited等 Retry-After 秒後以相同冪等內容有限次重試。
500internal_errorGET 可重讀;寫入遵守各端點冪等規則並查核未知結果。

報告設定另可能回 400 template_not_for_collection/template_staff_only。參考上面的報告條件。表單/合集建立沒有冪等鍵;回覆、record、upload、report link 各有自己的重試規則,不應共用一個「所有 POST 都重送」機制。

原站回寫 Intakes 的適用範圍

/intakes、/intake-commands 及確認端點用於原站保存正式答案時的指令回寫,見完整契約與例子。專用簽名通知只包含待處理指令的 generation,不代表普通 responses 具有通用變更 webhook。OpenDashboard 中央保存不需要原站確認;目前 intake 沒有切換中央保存的 direct 參數。