快速開始
這一輪跑完查身分、建草稿、讀回文章、刪除草稿。每一步都附 curl、Python 與 JavaScript 範例。
Python 範例需要 requests 套件,先執行 pip install requests。JavaScript 範例存成副檔名 .mjs 的檔案,用 Node 18 以上執行。
設定環境變數
把金鑰放進環境變數,後面每個範例都從這裡讀。
export SFAPP_KEY='sfapp_live_ab12cd34_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
export SFAPP_BASE='https://api.sfec.cloud'
import os
api_key = os.environ["SFAPP_KEY"]
base_url = "https://api.sfec.cloud"
print("金鑰前綴:", api_key[:19])
const apiKey = process.env.SFAPP_KEY;
const baseUrl = "https://api.sfec.cloud";
console.log("金鑰前綴:", apiKey.slice(0, 19));
金鑰格式與保管方式見認證與金鑰。
查身分
GET /v1/me 回傳商店名稱、這把金鑰的權限、功能開關、欄位上限與今日用量。
curl -sS "$SFAPP_BASE/v1/me" \
-H "Authorization: Bearer $SFAPP_KEY"
import os
import requests
BASE_URL = "https://api.sfec.cloud"
response = requests.get(
f"{BASE_URL}/v1/me",
headers={"Authorization": f"Bearer {os.environ['SFAPP_KEY']}"},
timeout=30,
)
response.raise_for_status()
me = response.json()
print("商店:", me["shop_name"])
print("權限:", me["scopes"])
print("功能:", me["features"])
print("今日文章:", me["quotas"]["posts_created_today"], "/", me["quotas"]["posts_per_day"])
const baseUrl = "https://api.sfec.cloud";
const response = await fetch(`${baseUrl}/v1/me`, {
headers: { Authorization: `Bearer ${process.env.SFAPP_KEY}` },
});
const me = await response.json();
console.log("商店:", me.shop_name);
console.log("權限:", me.scopes);
console.log("功能:", me.features);
console.log("今日文章:", me.quotas.posts_created_today, "/", me.quotas.posts_per_day);
回應節錄:
{
"app_id": "app_7f3a9c1e",
"app_name": "內容代理程式",
"shop_name": "範例商店",
"shop_public_url": "https://www.example-shop.tw",
"scopes": ["read_blog", "write_blog", "read_audit"],
"features": {
"blog": true,
"blog_categories": false,
"media_upload": true,
"summary": true,
"cover_image": true,
"published_at_sort": true
},
"quotas": {
"posts_created_today": 3,
"posts_per_day": 50
},
"api_version": "2026-09-14",
"key_expires_at": "2027-09-14T00:00:00+08:00"
}
scopes 是這把金鑰能做的事。features 為 false 的功能,相關端點回 404。limits 與 quotas 的逐鍵說明見限制與配額。
建立草稿
POST /v1/blog/posts 建出來的文章狀態是 draft,顧客看不到。這支端點要帶 Idempotency-Key:新建用一把新的鍵,重試沿用同一把,規則見請求慣例。
curl -sS -X POST "$SFAPP_BASE/v1/blog/posts" \
-H "Authorization: Bearer $SFAPP_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"title": "秋季新品上架",
"body_html": "<p>今年秋天的第一波新品已經到店。</p>",
"summary": "秋季新品搶先看"
}'
import os
import uuid
import requests
BASE_URL = "https://api.sfec.cloud"
response = requests.post(
f"{BASE_URL}/v1/blog/posts",
headers={
"Authorization": f"Bearer {os.environ['SFAPP_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"title": "秋季新品上架",
"body_html": "<p>今年秋天的第一波新品已經到店。</p>",
"summary": "秋季新品搶先看",
},
timeout=30,
)
response.raise_for_status()
post = response.json()
print("文章識別碼:", post["id"], "狀態:", post["status"])
print("淨化提醒:", post.get("warnings", []))
import { randomUUID } from "node:crypto";
const baseUrl = "https://api.sfec.cloud";
const response = await fetch(`${baseUrl}/v1/blog/posts`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SFAPP_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": randomUUID(),
},
body: JSON.stringify({
title: "秋季新品上架",
body_html: "<p>今年秋天的第一波新品已經到店。</p>",
summary: "秋季新品搶先看",
}),
});
const post = await response.json();
console.log("文章識別碼:", post.id, "狀態:", post.status);
console.log("淨化提醒:", post.warnings ?? []);
成功回 201 與整篇文章。status 是 draft,published_at 是 null。內文依白名單淨化,被移除的項目列在 warnings。
讀回文章
用上一步的識別碼讀回整篇,確認標題、摘要與淨化後的內文。
curl -sS "$SFAPP_BASE/v1/blog/posts/1001" \
-H "Authorization: Bearer $SFAPP_KEY"
import os
import requests
BASE_URL = "https://api.sfec.cloud"
POST_ID = 1001
response = requests.get(
f"{BASE_URL}/v1/blog/posts/{POST_ID}",
headers={"Authorization": f"Bearer {os.environ['SFAPP_KEY']}"},
timeout=30,
)
response.raise_for_status()
post = response.json()
print(post["title"], post["status"], post["public_url"])
print(post["body_html"])
const baseUrl = "https://api.sfec.cloud";
const postId = 1001;
const response = await fetch(`${baseUrl}/v1/blog/posts/${postId}`, {
headers: { Authorization: `Bearer ${process.env.SFAPP_KEY}` },
});
const post = await response.json();
console.log(post.title, post.status, post.public_url);
console.log(post.body_html);
草稿的 public_url 是 null,發布後才有前台網址。發布流程見發布文章。
刪除草稿
刪除分兩次送出,完整流程見 文章。已發布或已排程的文章要先下架。
# 第一次:取得確認碼,回應為 409
curl -sS -X DELETE "$SFAPP_BASE/v1/blog/posts/1001" \
-H "Authorization: Bearer $SFAPP_KEY"
# 第二次:填入上一步的 confirmation_token
curl -sS -i -X DELETE "$SFAPP_BASE/v1/blog/posts/1001" \
-H "Authorization: Bearer $SFAPP_KEY" \
-H "Content-Type: application/json" \
-d '{"confirmation_token": "貼上第一次拿到的確認碼"}'
import os
import requests
BASE_URL = "https://api.sfec.cloud"
POST_ID = 1001
HEADERS = {"Authorization": f"Bearer {os.environ['SFAPP_KEY']}"}
def delete_post(body: dict | None) -> tuple[int, dict | None]:
"""送出刪除請求,回傳狀態碼與回應內容;204 沒有內容。"""
response = requests.delete(
f"{BASE_URL}/v1/blog/posts/{POST_ID}",
headers=HEADERS,
json=body,
timeout=30,
)
return response.status_code, None if response.status_code == 204 else response.json()
status, body = delete_post(None)
if status != 409:
raise SystemExit(f"第一次應回 409,實際為 {status}:{body}")
details = {item["field"]: item["issue"] for item in body["error"]["details"]}
print("即將刪除:", details["post.title"])
print("確認碼有效秒數:", details["expires_in_seconds"])
# 人工確認後再送第二次
status, body = delete_post({"confirmation_token": details["confirmation_token"]})
print("刪除結果:", status)
const baseUrl = "https://api.sfec.cloud";
const postId = 1001;
const headers = {
Authorization: `Bearer ${process.env.SFAPP_KEY}`,
"Content-Type": "application/json",
};
async function deletePost(body) {
const response = await fetch(`${baseUrl}/v1/blog/posts/${postId}`, {
method: "DELETE",
headers,
body: body === null ? undefined : JSON.stringify(body),
});
return { status: response.status, body: response.status === 204 ? null : await response.json() };
}
const first = await deletePost(null);
if (first.status !== 409) {
throw new Error(`第一次應回 409,實際為 ${first.status}`);
}
const details = Object.fromEntries(
first.body.error.details.map((item) => [item.field, item.issue]),
);
console.log("即將刪除:", details["post.title"]);
console.log("確認碼有效秒數:", details["expires_in_seconds"]);
// 人工確認後再送第二次
const second = await deletePost({ confirmation_token: details["confirmation_token"] });
console.log("刪除結果:", second.status);一行跑完
這一行依序查身分、建一篇草稿、讀回文章,並印出每步結果。腳本只讀取與建立草稿,不發布、不刪除、不上傳圖片,只依賴 curl 與 python3。跑完會留下一篇標題開頭為 [Quickstart] 的草稿,照上一節刪掉即可。
curl -fsSL https://api.sfec.cloud/quickstart.sh | SFAPP_KEY="$SFAPP_KEY" bash