概覽
API 概覽
Sofia API v1 的所有端點及其參數、權限範圍和範例,並提供可用你自己的金鑰逐個試用的 Playground。
基礎 URL https://api.sofia-post.com/api · OpenAPI 文件
Sofia API 讓你的程式碼存取你的 Sofia 帳號:你的 Workspace 中的商家、它們已連接的平台、媒體庫、貼文和統計數據。這是基於 HTTPS 的 REST API;請求和回應都是 JSON。它可以讓你:
- 發布:立即發布、排程發布或存為草稿,可同時發往一個或多個平台。
- 發布前驗證:透過模擬告訴你每個平台是否會接受這條貼文,不會寫入任何內容。
- 管理媒體:上傳檔案,或從 URL 匯入到商家的媒體庫中。
- 跟蹤貼文:讀取每個平台的狀態、永久連結或失敗原因。也可以透過已簽名的 Webhook 通知你。
- 讀取統計數據:Sofia 為每個已連接帳號收集的最新數據。
- 連接平台:建立一個托管連結,讓擁有者連接尚缺的平台。
SDK 與整合
- `sofia` CLI:發布在 npm 上、名為
sofia-post的 CLI,用來在終端中操控 Sofia。 - MCP 伺服器:把 Sofia 接入你的 AI 代理。
- Zapier:Sofia 整合使用你的個人金鑰連接。它提供觸發條件「Post Published」和「Post Failed」、操作「Create Post」(發布、排程或儲存草稿)以及搜尋「Find Post」。它尚未提交到 Zapier 的公開目錄。
身分驗證
每次呼叫都在 Authorization 請求標頭中攜帶 Bearer 權杖。Sofia 每次呼叫都會重新讀取與它關聯的權限:被移出 Workspace 的人,或被撤銷的授權,會讓權杖從下一次請求起失效。
有兩種身分驗證方式:
| 方式 | 適用於 | 權杖 |
|---|---|---|
| 個人金鑰 | 自動化你自己的帳號、指令碼、AI 代理 | sofia_pk_live_…,在到期前有效 |
| OAuth,授權碼 + PKCE | 代表其使用者操作的產品,作用於使用者所選的 Workspace | sofia_live_at_…,一小時,可續期 |
| OAuth,client credentials | 你的某台伺服器,用來自動化你作為擁有者的 Workspace | sofia_live_at_…,一小時 |
API 金鑰
如果你自動化的是自己的 Workspace,一個個人金鑰就夠了。在 Sofia 應用程式中,開啟設定 › API,進入個人存取標籤頁,建立一個金鑰。選擇它的名稱、有效期和權限。金鑰只顯示一次:請複製並儲存在金鑰保險庫中,絕不要放進你的程式碼裡。
在設定 › API › 個人存取中建立一個金鑰。它以你的身份作用於你所有的 Workspace:你指定的商家決定 Workspace,你在其中的角色決定金鑰能做什麼。
把金鑰放在 Authorization 請求標頭中,作為 Bearer 權杖傳送:
curl https://api.sofia-post.com/api/v1/establishments \
-H "Authorization: Bearer sofia_pk_live_…"`GET /v1/me` 會告訴你金鑰代表誰操作、它的 Workspace 和它的權限。
缺少所需權限(scope)的呼叫會返回 403 insufficient_scope。
OAuth2 權杖
如果是代表使用者在其自己的 Workspace 上操作的 SaaS 產品,請註冊一個 OAuth 客戶端:每個人在自己選擇的 Workspace 上授權你的應用程式。OAuth 存取權杖以 sofia_live_at_… 開頭,有效期一小時,使用方式相同:
curl https://api.sofia-post.com/api/v1/establishments \
-H "Authorization: Bearer sofia_live_at_…"你要建立一個供其他 Sofia 帳號連接的應用程式嗎?它的 authorization code + PKCE 流程、client credentials 和 scope 在 OAuth2 中說明。
基礎 URL
所有端點都位於同一個基礎 URL 之下:
https://api.sofia-post.com/api/v1速率限制
Sofia API 在三個時間視窗內統計請求:15 分鐘、24 小時和 30 天。
| 權杖 | 15 分鐘 | 24 小時 | 30 天 |
|---|---|---|---|
| 個人金鑰(按帳號) | 150 | 1,000 | 10,000 |
| OAuth 客戶端 | 100 | 500 | 15,000 |
| OAuth 客戶端,按每位授權使用者(最多 1,000 位) | +10 | +25 | +100 |
請求標頭
每個回應都會告訴你剩餘額度,採用 IETF 格式:
RateLimit: "15m";r=144;t=43, "24h";r=994;t=13543, "30d";r=9994;t=13543
RateLimit-Policy: "15m";q=150;w=900, "24h";q=1000;w=86400, "30d";q=10000;w=2592000r 是剩餘請求數,t 是視窗重置前的秒數,q 是配額,w 是視窗的秒數。
視窗用盡時
回應為 429 rate_limit_exceeded,帶有 Retry-After 請求標頭,並在 details 中給出已用盡的視窗。請等待該時長後再重試。
高效使用 API
- 註冊 Webhook,而不是循環輪詢某條貼文。
- 使用最大為 100 的
limit讀取清單,以減少頁數。 - 快取不常變化的數據:平台、商家、已連接帳號。
錯誤
每個錯誤都會返回一個 HTTP 狀態碼和同樣的 JSON 信封。code 是你的程式碼讀取的約定;message 是寫給人看的,可能會變。
{
"error": {
"code": "invalid_request",
"message": "The request is invalid",
"details": [
{ "field": "targets[0].text", "code": "required", "message": "targets[0].text is required" }
],
"docUrl": "https://www.sofia-post.com/developers/errors#invalid_request",
"requestId": "2aab5943-6333-4caf-8478-25a85ebd4abf"
}
}details提供更多資訊:被拒絕的欄位、某個平台的原因、已用盡的限制視窗。docUrl指向本頁對該錯誤碼的說明。requestId標記這次請求:請把它提供給支援團隊。
錯誤碼
| 狀態碼 | 錯誤碼 | 意義 |
|---|---|---|
| 400 | invalid_request | 請求本文、查詢或某個參數無效。每個被拒絕的欄位對應一則詳細資料:field、code(required、is_string、max……)和 message。 |
| 400 | invalid_json | 請求本文不是有效的 JSON。 |
| 400 | invalid_cursor | 該游標不是由 Sofia API 回傳的。 |
| 400 | webhook_url_invalid | webhook 位址不是 https,或指向私人位址。 |
| 400 | webhook_event_invalid | 未知的 webhook 事件。 |
| 400 | application_name_invalid | 設定 › API:用戶端名稱缺失或過長。 |
| 400 | application_profile_invalid | 設定 › API:用戶端資料中有欄位無效;詳細資料會指出是哪一個。 |
| 400 | logo_invalid | 設定 › API:標誌被拒絕(至少 180×180 px,PNG、JPEG 或 WebP,最大 2 MB)。 |
| 401 | invalid_token | 權杖缺失、未知、已過期或已被撤銷,或權杖背後的使用者已失去其所帶的權限。請參閱 WWW-Authenticate 標頭。 |
| 401 | unauthorized | 需要驗證。 |
| 402 | subscription_required | live 寫入(包括試跑)需要有效的訂閱或試用。 |
| 403 | insufficient_scope | 權杖缺少該端點所需的 scope;詳細資料中會指出是哪一個。 |
| 403 | insufficient_role | 你在此 Workspace 中的角色不允許執行該動作。 |
| 403 | live_mode_required | 用測試權杖進行了真實寫入。 |
| 403 | route_closed_to_applications | 該端點不對應用程式開放。 |
| 403 | forbidden | 存取被拒絕。 |
| 403 | session_required | 設定 › API:此操作需要 Sofia 工作階段,而不是權杖。 |
| 403 | impersonation_forbidden | 設定 › API:此操作在借用的工作階段中會被拒絕。 |
| 403 | workspace_owner_required | 只有 Workspace 的擁有者可以執行此操作。 |
| 403 | workspace_member_required | 你不是此 Workspace 的成員。 |
| 404 | route_not_found | 沒有端點回應該方法和路徑。 |
| 404 | establishment_not_found | 未知的門市,或超出權杖的存取範圍。 |
| 404 | media_not_found | 未知的媒體,或超出權杖的存取範圍。 |
| 404 | post_not_found | 未知的貼文,或超出權杖的存取範圍。 |
| 404 | webhook_not_found | 未知的 webhook,或屬於另一個應用程式。 |
| 404 | not_found | 資源不存在。 |
| 404 | application_not_found | 設定 › API:未知的用戶端。 |
| 404 | grant_not_found | 授權未知,或已被撤銷。 |
| 404 | personal_key_not_found | 設定 › API:未知的個人金鑰。 |
| 404 | consent_not_found | 授權請求已過期或不存在:請從授權端點重新開始。 |
| 405 | method_not_allowed | 此端點不允許使用該方法。 |
| 409 | post_in_flight | 有網路正在傳送該貼文:請稍後重試刪除。 |
| 409 | post_not_editable | 已有網路送出了該貼文:不能再編輯。 |
| 409 | account_already_connected | 該網路已連結到此門市。 |
| 409 | webhook_limit_reached | 該應用程式已有 10 個 webhook。 |
| 409 | idempotency_in_progress | 使用此 Idempotency-Key 的第一個請求仍在處理中。 |
| 409 | conflict | 請求與資源的目前狀態衝突。 |
| 409 | scope_removal_forbidden | 設定 › API:用戶端的權限可以新增,但不能移除。 |
| 409 | public_client_has_no_secret | 設定 › API:公開用戶端沒有密鑰。 |
| 413 | payload_too_large | 檔案超出上傳限制。 |
| 415 | unsupported_media_type | 不支援的內容類型。 |
| 422 | post_refused | 某個網路、門市或你的方案會拒絕該貼文。每個原因對應一則詳細資料,各自帶有 message。 |
| 422 | media_import_refused | 無法從該 URL 匯入媒體;詳細資料中給出了原因。 |
| 422 | media_upload_refused | 上傳的檔案不是支援的圖片或影片。 |
| 422 | platform_not_offered | 該網路不提供連結。 |
| 422 | idempotency_key_reused | 此 Idempotency-Key 曾用於另一個請求本文。 |
| 422 | unprocessable_entity | 請求可以理解,但無法處理。 |
| 429 | rate_limit_exceeded | 速率限制時段已滿:請等待 Retry-After 指定的時間。 |
貼文被拒絕的原因
post_refused 會在 details[].code 中列出每個原因:
| 錯誤碼 | 意義 |
|---|---|
unsupported_platform | 不支援該網路。 |
duplicate_platform | 同一個網路被指定了兩次。 |
unsupported_format | 該網路不提供這種格式。 |
caption_too_long | 文字過長(max、actual)。 |
too_many_hashtags | 主題標籤過多(max、actual)。 |
too_many_mentions | 標註過多(max、actual)。 |
too_many_thread_posts | 串文過長(max、actual)。 |
too_many_media | 媒體過多(max、actual)。 |
media_mix_refused | 該網路不允許混合相片和影片。 |
media_required | 該網路需要相片或影片。 |
media_type_refused | 該網路不接受這種類型的媒體。 |
scheduled_in_past | scheduledAt 在過去。 |
scheduled_at_invalid | 排程貼文需要未來的 scheduledAt。 |
account_not_connected | 此門市在該網路上沒有已連結的帳號。 |
account_ambiguous | 該網路上連結了多個帳號:請用 accountId 指定一個。 |
media_not_found | 該媒體不在此門市的媒體庫中。 |
media_url_invalid | 媒體 URL 必須是公開的 https 位址。 |
platform_unavailable | 該網路在 Sofia 上尚未開放。 |
publication_quota_exceeded | 已達到你方案的發布額度。 |
處理錯誤
- 請根據
code來編寫邏輯,不要依賴message。 - 遇到
429,在Retry-After之後重試;遇到5xx,使用指數退避重試。 - 不要原樣重試
4xx:先修正請求。 - 每次失敗都記錄
requestId。
分頁
清單(GET /v1/posts、GET /v1/media)按最新優先返回,一次一頁。limit 設定每頁大小,範圍 1 到 100(預設 20)。
{
"data": [ … ],
"nextCursor": "WyIyMDI2LTEwLTI3VDE1OjExOjU5LjAxN1oiLCIx…"
}把 nextCursor 作為 cursor 傳入即可讀取下一頁。當沒有 nextCursor 時,說明已經到末尾。
let cursor;
do {
const url = new URL('https://api.sofia-post.com/api/v1/posts');
url.searchParams.set('limit', '100');
if (cursor) url.searchParams.set('cursor', cursor);
const page = await globalThis.fetch(url, { headers: { Authorization: `Bearer ${key}` } }).then((r) => r.json());
handle(page.data);
cursor = page.nextCursor;
} while (cursor);游標是不透明的:切勿自行構造或修改。Sofia 沒有返回過的游標會返回 400 invalid_cursor。
冪等性
網路錯誤可能讓你無法確定貼文是否已建立。請在 POST /v1/posts 和 POST /v1/media 中傳送 Idempotency-Key 請求標頭,並用同一個金鑰重試:Sofia 會返回第一次的回應,不會重複建立任何內容。
curl -X POST https://api.sofia-post.com/api/v1/posts \
-H "Authorization: Bearer $SOFIA_API_KEY" \
-H 'Content-Type: application/json' \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-d @post.json- 每條新貼文使用一個新的隨機金鑰(UUID),同一條貼文的每次重試使用同一個金鑰。
- 同一個金鑰配上不同的請求本文,會返回
422 idempotency_key_reused。 - 第一次請求仍在處理時,同一個金鑰會返回
409 idempotency_in_progress:請稍後重試。
測試模式與模擬
模擬。在 POST /v1/posts 後加上 ?dry_run=true:Sofia 會執行真實發布的各項檢查(每個平台的規則、已連接帳號、媒體、日期和你的方案),但不會儲存或傳送任何內容。回應的詳情見發布頁面。
測試模式。以測試模式建立的應用程式(權杖 sofia_test_at_…)可以讀取你真實的 Workspace,但絕不會寫入:它的貼文是模擬的,並按同樣的規則判定,任何真實寫入都會返回 403 live_mode_required。在設定 › API 中建立的金鑰和客戶端是正式模式;如需在不發布的前提下檢查,請使用模擬。
OAuth
/v1/oauth/authorize發起授權在使用者的瀏覽器中打開。未知用戶端或未註冊的 redirect_uri 回傳 400,否則重新導向至 Sofia 授權頁面。GET/.well-known/oauth-authorization-server/api/v1/oauth取得伺服器中繼資料回傳 Sofia API 的 OAuth 2.0 授權伺服器中繼資料(RFC 8414),方便用戶端探索端點和支援的選項。POST/v1/oauth/token簽發存取權杖client_credentials、authorization_code、refresh_token 可換取 3600 秒有效的權杖。帳號
平台
門市
/v1/establishments列出門市列出權杖所操作 Workspace 的門市。發布、列出媒體或讀取數據洞察時,用其識別碼作為 establishmentId。GET/v1/establishments/{id}/accounts列出已連結帳號列出門市已連結的網路,不含權杖或網路金鑰,只提供為帳號命名並在發布時選作 accountId 所需的全部資訊。POST/v1/establishments/{id}/connection-links建立連線連結回傳託管連結,使用者在 Sofia 上授權網路,因此網路權杖不會到達你的應用。連結有效期 30 分鐘,僅限正式應用使用。GET/v1/establishments/{id}/insights取得門市數據洞察回傳 Sofia 為每個已連結帳號收集的最新有效資料。尚未收集到資料的帳號顯示 awaiting,絕不會顯示為零。媒體
/v1/media列出媒體由新到舊分頁列出門市的媒體庫,附目前時間視窗內有效的簽章 URL。在測試模式和正式模式下都可以使用,用於挑選已有素材。POST/v1/media匯入或上傳媒體把檔案(multipart)或 Sofia 抓取的 https URL 新增到門市媒體庫。需要正式應用和有效訂閱。GET/v1/media/{id}取得媒體回傳媒體庫中的一項媒體,附目前時間視窗內有效的簽章 URL,其識別碼可在發布貼文時直接複用,無需重新上傳檔案。DELETE/v1/media/{id}移除媒體像應用一樣,把媒體從其所屬門市的媒體庫中移除。檔案本身不會被銷毀,已排程的貼文仍然保留它的視覺素材,不受影響。貼文
/v1/posts列出貼文由新到舊列出權杖可存取門市的貼文,可按門市、狀態、日期範圍和作者篩選,分頁回傳,並用游標取得下一頁,每頁最多 100 筆。POST/v1/posts發布貼文正式應用可立即發布、定時發布或存為草稿,測試應用只做模擬。加上 ?dry_run=true 則只檢查、不寫入。GET/v1/posts/{id}取得貼文回傳貼文及各網路的狀態、發布後的連結和失敗時的錯誤。正式模式可讀 Workspace 內任何貼文,測試模式讀模擬貼文。PATCH/v1/posts/{id}編輯貼文任何網路開始傳送前,可替換 targets、設定 scheduledAt,或在 draft 與 scheduled 之間切換,其餘欄位不變。DELETE/v1/posts/{id}刪除貼文像應用一樣刪除貼文:已排程的不再傳送,已發布的會從各網路上移除。測試應用沒有任何真實內容,因此沒有東西可刪除。Webhook
/v1/webhooks列出 webhook列出應用的 webhook,不含簽章金鑰;該金鑰只在註冊 webhook 時回傳一次,之後無法再次取得,請自行妥善保存。POST/v1/webhooks註冊 webhook註冊 Sofia 以帶簽章 JSON 事件呼叫的 https URL,失敗最多重試五次。金鑰僅顯示一次,最多 10 個。DELETE/v1/webhooks/{id}刪除 webhook刪除 webhook。之後它不再收到任何內容,包括仍在等待重試的遞送;呼叫成功後不回傳任何本文,刪除後也無法恢復。POST/v1/webhooks/{id}/test傳送測試事件像其他事件一樣遞送帶簽章的 webhook.test 事件,無需等待接收方,便於驗證簽章。每分鐘最多傳送五次。