驗證、格式及限制
Authorization: Bearer <SERVER_SIDE_APP_KEY>
Content-Type: application/jsonJSON 欄位採 snake_case。JSON 請求最多 256 KB;每應用正式及測試合計 600 次/10 分鐘。金鑰只能由伺服器使用,不能送到瀏覽器。應用隔離及 test/live 隔離適用於所有資源;外來資源回 404,不透露存在與否。
錯誤格式:
{
"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 題)。建立不是冪等操作。
{
"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 保留。 |
| date | YYYY-MM-DD。 |
| single_choice / dropdown | 一個 options 值;options 2–30 個。 |
| multiple_choice | options 值的陣列。 |
| app_result | 以 fields 宣告的結果物件。 |
| repeating_group | 以 fields 宣告的物件陣列。 |
| section / text_block | 純排版,不接收答案。 |
| email / phone / file_upload | identified 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。
{
"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 路徑。
{ "fields": [
{ "question": "category", "personal": false },
{ "question": "participants", "field": "category", "personal": false }
] }→ 200,傳回完整表單。分類在下次合集讀取、工作區刷新及個人報告開啟生效;已凍結分享/快照不重寫。
提交、取代及讀取回覆
POST /forms/:id/responses → 201,傳回 { "id": "reg_…", "version": 1 }。
{
"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 個唯一同應用、同模式的表單,按次序列出。
{ "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 題目 | 題目數,不是重複群組。 |
| labels | app_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:
{ "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。
{
"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": "…" }。
{
"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。
{
"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。
// 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 刪除不連帶抹除附件,保留/清除需另由管理員處理。
錯誤及重試速查
| HTTP | error | 處理 |
|---|---|---|
| 400 | invalid_json / invalid_body / invalid_form / invalid_answers / invalid_fields / invalid_collection | 按 fields 或 errors 更正,整份重新送。 |
| 400 | invalid_respondent / invalid_query / invalid_record_kind / invalid_record / summary_invalid / invalid_report_link / invalid_upload | 更正識別、聲明或限制。 |
| 400 | identified_mode_required | 聯絡管理員確認身份模式。 |
| 401 | invalid_app_key / app_disabled | 核對金鑰或應用狀態,不盲目重試。 |
| 404 | form_not_found / collection_not_found / record_kind_unknown / upload_not_found / report_link_not_found / template_not_found | 核對應用、模式和 id/範本設定。 |
| 409 | already_submitted / response_changed | 讀取最新回覆,處理取代或版本衝突。 |
| 409 | record_kind_conflict / record_exists / record_deleted / upload_exists | 使用正確聲明/相同內容,或新識別,不覆寫不可變資料。 |
| 409 | upload_not_ready / upload_content_mismatch | 核對 bytes/傳輸結果,再完成上傳。 |
| 413 | request_body_too_large / record_too_large / collection_tables_too_large | 縮減或分拆資料,不接受部分結果。 |
| 429 | rate_limited | 等 Retry-After 秒後以相同冪等內容有限次重試。 |
| 500 | internal_error | GET 可重讀;寫入遵守各端點冪等規則並查核未知結果。 |
報告設定另可能回 400 template_not_for_collection/template_staff_only。參考上面的報告條件。表單/合集建立沒有冪等鍵;回覆、record、upload、report link 各有自己的重試規則,不應共用一個「所有 POST 都重送」機制。
原站回寫 Intakes 的適用範圍
/intakes、/intake-commands 及確認端點用於原站保存正式答案時的指令回寫,見完整契約與例子。專用簽名通知只包含待處理指令的 generation,不代表普通 responses 具有通用變更 webhook。OpenDashboard 中央保存不需要原站確認;目前 intake 沒有切換中央保存的 direct 參數。