請求慣例
這一頁的規則適用於每一支端點。資源參考只寫各端點自己的欄位。
送出 JSON
請求與回應都是 JSON,字元編碼 UTF-8。有內文的請求帶 Content-Type: application/json,包含 POST、PATCH 與帶確認碼的 DELETE。圖片上傳是唯一的例外,用 multipart/form-data。
請求內文不接受多餘欄位。送出規格以外的欄位回 422 validation_failed,details 指名該欄位。
回應的欄位順序沒有保證,未來也會新增欄位。以鍵名取值,不依賴順序或欄位數量。
填寫時間
時間欄位是 ISO 8601 且含時區位移,例如 2026-09-15T09:00:00+08:00。
- 送進來的時間沒帶時區回 422
validation_failed,只送日期同樣回 422。 - 回應的時間一律帶時區。
- 每日配額以台北時間的日界線計算。
帶上請求標頭
| 標頭 | 何時必要 | 用途 |
|---|---|---|
Authorization |
每個 /v1 端點 |
Bearer <金鑰>,格式見認證與金鑰 |
Content-Type |
有 JSON 內文時 | 固定 application/json |
Idempotency-Key |
建立文章、建立分類 | 這次建立的識別字串,規則見下方 |
X-Request-Id |
可選 | 自帶的追蹤碼,1 到 64 個字元,限英數與 _ . -,原樣回傳 |
讀回應標頭
| 標頭 | 出現時機 | 內容 |
|---|---|---|
X-Request-Id |
每個回應 | 這次請求的追蹤碼,回報問題時附上 |
X-Sfapp-Api-Version |
每個回應 | 目前的 API 版本,例 2026-09-14 |
Cache-Control |
每個 API 回應 | 固定 no-store |
X-RateLimit-Limit |
通過認證後 | 這一類(讀或寫)每分鐘的上限 |
X-RateLimit-Remaining |
通過認證後 | 這一分鐘的剩餘次數 |
X-RateLimit-Reset |
通過認證後 | 額度回滿的時間,Unix 秒數 |
Retry-After |
被擋下時 | 建議等待的秒數 |
對照版本承諾
API 版本是日期版號,目前是 2026-09-14。每個回應的 X-Sfapp-Api-Version 標頭帶著它,GET /v1/me 的 api_version 也看得到。
路徑上的 /v1 是主要版本。/v1 之內不做破壞性變更。
| 會發生 | 不會發生 |
|---|---|
| 新增端點 | 移除端點或改路徑 |
| 回應新增欄位 | 移除既有回應欄位、改變欄位型別 |
| 請求新增可選欄位 | 新增必填欄位 |
| 錯誤訊息文字調整 | 錯誤碼改名或改對應的 HTTP 狀態 |
| 列舉新增值 | 移除既有列舉值 |
解析回應時容忍沒見過的欄位與沒見過的列舉值。破壞性變更會以新的主要版本推出,舊版有重疊期。
翻頁
文章列表與稽核事件列表用游標分頁。分類列表一次回傳全部,沒有分頁參數。
| 查詢參數 | 型別 | 預設 | 用途 |
|---|---|---|---|
limit |
整數 | 20 | 每頁筆數,範圍 1 到 50 |
cursor |
字串 | 無 | 上一頁 meta.next_cursor 的值,第一頁不必帶 |
回應固定有 data 與 meta:
{
"data": [],
"meta": { "next_cursor": "MTAwMQ", "limit": 20 }
}
meta.next_cursor 是 null 代表已經是最後一頁。游標是不透明字串,原樣帶回即可。自行拼造或改寫的游標回 422 validation_failed。
翻頁期間其他查詢條件保持不變,例如 status 篩選。換條件就從第一頁重新開始。
帶冪等鍵
Idempotency-Key 讓同一次建立重送多次也只寫入一筆。
- 建立文章(
POST /v1/blog/posts)與建立分類(POST /v1/blog/categories)必須帶,沒帶回 422validation_failed。 - 值是 1 到 255 個可列印的 ASCII 字元,建議用 UUID。
- 每一次新的建立用一把新的鍵,重試沿用同一把。
- 記錄保存 24 小時,之後同一把鍵視為全新的請求。
- 圖片上傳不收冪等鍵,它以圖片內容去重。
同一把鍵再次送出時的結果:
| 情況 | 送出的內容 | 結果 |
|---|---|---|
| 回放 | 與第一次相同 | 回傳第一次的狀態碼與回應內容,不新增資料 |
| 衝突 | 與第一次不同 | 409 idempotency_key_reused,不寫入 |
| 處理中 | 相同,且第一次還沒結束 | 409 idempotency_in_progress,依 Retry-After(2 秒)再送 |
「相同」比對的是方法、路徑與整份內文。重試時改動內文會變成衝突。要送不同的內容就換一把新的鍵。
# 第一次
curl -sS -X POST https://api.sfec.cloud/v1/blog/posts \
-H "Authorization: Bearer $SFAPP_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1b2c74-8b1a-4f3e-9a0d-2c5e7b4d9a11" \
-d '{"title":"秋季新品上架","body_html":"<p>今年秋天的第一波新品已經到店。</p>"}'
# 逾時後用同一把鍵重送,結果仍是同一篇
curl -sS -X POST https://api.sfec.cloud/v1/blog/posts \
-H "Authorization: Bearer $SFAPP_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1b2c74-8b1a-4f3e-9a0d-2c5e7b4d9a11" \
-d '{"title":"秋季新品上架","body_html":"<p>今年秋天的第一波新品已經到店。</p>"}'處理內容去重
內容去重比對標題與內文。十分鐘內同一家商店出現標題與內文都相同的新文章時,第二篇回 409 duplicate_content,details 指出既有那篇的識別碼。收到這個錯誤時沿用既有的文章。需要兩篇內容相同的文章時,等十分鐘或讓標題有實質差異。
讀限流額度
限流分讀與寫兩條獨立額度,按金鑰計算,每分鐘補滿。預設值見限制與配額,自己的值以 GET /v1/me 的 rate_limits 為準。
通過認證的回應都帶額度標頭,被擋之前就看得到剩餘次數:
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118
X-RateLimit-Reset: 1789171200
被擋時依 Retry-After 指示的秒數等待後再送。429 的三個錯誤碼處理動作各不相同,見錯誤碼。
讀錯誤外殼
每個錯誤回應都是同一個外殼:
{
"error": {
"code": "validation_failed",
"message": "請求欄位驗證失敗,請依 details 修正後重送。",
"details": [{ "field": "title", "issue": "長度超過允許的上限。" }],
"request_id": "req_k4m2p7q9r3s5t8v6w2x4y7z3",
"retryable": false
}
}
| 欄位 | 型別 | 必填或可為 null | 用途 |
|---|---|---|---|
code |
字串 | 必填 | 錯誤碼,程式依這個值分支 |
message |
字串 | 必填 | 繁體中文說明,含可採取的下一步 |
details |
陣列 | 可省略 | 逐欄位說明,每筆有 field 與 issue;沒有欄位層級問題時是空陣列 |
request_id |
字串 | 必填 | 這次請求的追蹤碼,與 X-Request-Id 相同 |
retryable |
布林 | 必填 | 原樣重送是否可能成功 |
details 的 field 可能是內文欄位、查詢參數或標頭名稱,例如 Idempotency-Key。
錯誤訊息文字會調整,錯誤碼不會。程式依 code 分支,先看 retryable 決定要不要重送。逐碼的處理動作見錯誤碼。