REST API
控制台建立在 https://zenavo.app/api 上的 JSON REST API 之上。控制台能做的事,都可以用它來做。
認證
以 Bearer 權杖的形式傳送 API 金鑰:
sh
curl -H "Authorization: Bearer $ZENAVO_API_KEY" https://zenavo.app/api/auth/me金鑰的權限和專案限制決定它可以呼叫哪些介面;其他介面返回 403。帳單、購買、團隊成員和 /api/account/* 只能透過瀏覽器工作階段使用。
來自瀏覽器工作階段的請求,除 GET 以外都還需要帶上請求頭 X-Zenavo-Request: 1;使用 Bearer 權杖的請求不需要。
請求只需要有效的工作階段或擁有相應角色的 API 金鑰;沒有任何操作要求二次確認。尚未驗證電子郵件的帳號對自己帳號以外的修改會收到 403 和 {"email_unverified": true}。
約定
- 請求體是 JSON。時間是 Unix 秒。
- ID 帶有字首:
team_、srv_、proj_、job_、bak_、tok_。 - 錯誤返回狀態碼和
{"detail": …}。校驗錯誤(422)會列出每個欄位。 - 耗時較長的操作(部署、重啟、備份)返回一個任務:
{"id": "job_…"}。輪詢GET /api/jobs/{id}取得status(queued、running、succeeded、failed)、log和每個服務的progress。
介面
帳號
| 方法 | 路徑 | |
|---|---|---|
GET | /api/auth/me | 當前登入的使用者、所在的團隊和 email_verified |
PATCH | /api/account | 名稱和頭像 |
GET POST | /api/account/tokens | 列出或建立 API 金鑰(僅限瀏覽器工作階段)。建立時傳入 {name, days, scope, project_ids}:scope 為 read、deploy 或 full(預設);project_ids 把金鑰限定在這些專案(省略或 null 表示全部) |
DELETE | /api/account/tokens/{id} | 刪除 API 金鑰 |
GET | /api/account/sessions | 已登入的工作階段 |
POST | /api/auth/2fa | 登入的第二步:在 /api/auth/login 返回 {two_factor: true} 後提交 {code} |
POST | /api/account/verify | 確認身份:開啟兩步驟驗證時提交 {code},否則提交 {password}。目前沒有操作需要它 |
POST | /api/auth/verify-email | 開啟電子郵件驗證連結:{token}(只能使用一次,24 小時內有效) |
POST | /api/account/email/verify | 傳送新的驗證連結;位址已驗證時返回 {verified: true} |
GET | /api/account/2fa | 兩步驟驗證狀態:enabled(應用或通行金鑰)、totp、剩餘復原碼數量、通行金鑰數量 |
POST | /api/account/2fa/setup /enable /disable /recovery-codes | 管理兩步驟驗證(僅限瀏覽器工作階段) |
POST | /api/auth/2fa/passkey/options 然後 /api/auth/2fa/passkey | 用通行金鑰完成登入的第二步:WebAuthn 請求選項(沒有通行金鑰時為 {options: null})以及帳號是否有驗證器 App(totp),然後提交 {credential} |
GET | /api/account/passkeys | 你的通行金鑰:名稱、AAGUID、是否同步、建立時間、最近使用時間 |
POST | /api/account/passkeys/options 然後 /api/account/passkeys | 註冊通行金鑰:先取得建立選項,再提交 {name, credential}(帳號的第一個第二重驗證方式還會返回 recovery_codes) |
DELETE | /api/account/passkeys/{id} | 移除通行金鑰(移除最後一個第二重驗證方式會關閉兩步驟驗證) |
GET | /api/account/audit?before=&limit= | 你自己的安全活動 |
POST | /api/auth/device/code | 開始 CLI 登入(裝置流程):{client_name, scope, client} → {device_code, user_code, verification_uri, expires_in, interval}。scope 預設為 full;client 為 cli(預設)或 mcp,金鑰分別命名為“CLI on …”或“MCP on …” |
POST | /api/auth/device/token | 用 {device_code} 輪詢:等待中返回 202,輪詢快於 interval 返回 429,批准後返回 200 {token, scope, project_ids}(批准人可能縮小了範圍) |
團隊
| 方法 | 路徑 | |
|---|---|---|
GET POST | /api/teams | 列出或建立團隊 |
PATCH | /api/teams/{tid} | 重新命名 |
GET | /api/teams/{tid}/members | 成員 |
PATCH DELETE | /api/teams/{tid}/members/{uid} | 修改角色、移除成員 |
GET POST | /api/teams/{tid}/invites | 邀請 |
GET | /api/teams/{tid}/audit?before=&limit= | 稽核日誌(管理員) |
GET POST | /api/teams/{tid}/notification-channels | 通知管道(管理員)。建立時傳入 {kind, name, events, url}(Slack、Discord、Webhook)或 {kind: "telegram", name, events, bot_token, chat_id};Webhook 的 secret 只返回一次 |
PATCH DELETE | /api/teams/{tid}/notification-channels/{cid} | 修改 name、events、enabled 或目標位址;移除 |
POST | /api/teams/{tid}/notification-channels/{cid}/test | 立即傳送一條測試訊息 |
POST | /api/teams/{tid}/notification-channels/{cid}/secret | Webhook 的新簽名金鑰(只返回一次) |
GET POST | /api/teams/{tid}/log-drains | 日誌轉發(管理員;建立需要 Pro 及以上方案)。建立時傳入 {kind: "http", name, url}、{kind: "datadog", name, site, api_key} 或 {kind: "loki", name, url, username, password, tenant_id},再加上 projects(為空表示全部);HTTP 轉發目標的 secret 只返回一次 |
PATCH DELETE | /api/teams/{tid}/log-drains/{did} | 修改 name、projects、enabled 或目標位址;移除 |
POST | /api/teams/{tid}/log-drains/{did}/test | 立即傳送一行測試日誌 |
POST | /api/teams/{tid}/log-drains/{did}/secret | HTTP 轉發目標的新簽名金鑰(只返回一次) |
用量與帳單
| 方法 | 路徑 | |
|---|---|---|
GET | /api/teams/{tid}/usage?period=YYYY-MM | 方案額度、伺服器、成員,以及按專案、服務和日期統計的建置分鐘數 |
GET | /api/teams/{tid}/usage/builds.csv?period= | 某個月每一次計量的建置,CSV 格式 |
GET | /api/teams/{tid}/transfer | 本週期的映像檔傳輸:bytes、included_bytes、overage_cents、limit_cents、blocked、percent,以及週期的開始和結束時間 |
GET | /api/teams/{tid}/billing | 方案、訂閱狀態、席位和方案目錄(管理員) |
GET | /api/teams/{tid}/billing/invoices | 最近的發票,附 Stripe 頁面和 PDF 連結(管理員) |
GET | /api/teams/{tid}/server-billing | 購買的伺服器及其已付費月份和自動續費狀態,以及最近的伺服器扣款(管理員) |
POST | /api/teams/{tid}/billing/checkout | 開始訂閱:{plan} → Stripe Checkout 的 {url}(擁有者) |
POST | /api/teams/{tid}/billing/change | 把訂閱切換到其他方案:{plan}(擁有者) |
POST | /api/teams/{tid}/billing/cancel /resume | 在週期結束時取消,或撤銷取消(擁有者) |
PUT | /api/teams/{tid}/billing/spend-limit | 設定消費上限:{cents},0 到 1,000,000(擁有者) |
POST | /api/teams/{tid}/billing/portal | Stripe 客戶門戶的 {url},用於管理卡片、帳單資訊和發票(擁有者) |
伺服器
| 方法 | 路徑 | |
|---|---|---|
GET | /api/servers?team_id= | 列出伺服器 |
POST | /api/servers?team_id= | 新增伺服器:{name, host, port, user, password | private_key} |
GET PATCH | /api/servers/{sid} | 讀取、重新命名 |
POST | /api/servers/{sid}/bootstrap | 重新執行初始化:{password | private_key};初始化失敗後一天內可以傳空請求體,沿用保留的憑據 |
PATCH | /api/servers/{sid}/billing | 購買的伺服器的自動續費:{auto_renew}(管理員)。GET /api/servers/{sid} 在 bought 下顯示它,以及 paid_until |
POST | /api/servers/{sid}/renew | 立即支付購買的伺服器下個月的費用(管理員) |
GET | /api/servers/{sid}/usage?hours= | CPU、記憶體和磁碟的歷史資料 |
GET | /api/servers/{sid}/system | 平台元件 |
GET | /api/servers/{sid}/ports | 連接埠檢查 |
GET | /api/servers/{sid}/jobs | 初始化和其他任務 |
POST | /api/servers/{sid}/delete | 移除:{confirmation: "<server name>"} |
POST | /api/servers/{sid}/reinstall | 解除安裝 K3s 並從頭初始化伺服器:{confirmation: "<server name>", password | private_key}(管理員;伺服器上不能有專案)。返回伺服器,初始化已進入佇列 |
POST | /api/servers/{sid}/reinstall-os | 重灌購買的伺服器的作業系統並從頭初始化,不需要 SSH 憑證;新的登入密碼會以電子郵件寄給呼叫者:{confirmation: "<server name>"}(管理員;伺服器上不能有專案,且其 bought.reinstall_os 為 true)。回傳伺服器,重灌已排入佇列 |
GET | /api/servers/{sid}/ssh-logins?hours=&failed= | SSH 登入記錄(成員) |
POST | /api/servers/{sid}/agent | 把在 agent 之前新增的伺服器遷移到 agent(管理員) |
POST | /api/servers/{sid}/agent/update | 更新 agent(管理員;返回一個任務) |
GET | /api/servers/{sid}/firewall | 伺服器的防火牆(成員)。購買的伺服器:Zenavo 的固定規則(Web、HTTPS、ping、初始化用的 SSH)和團隊的規則,在服務供應商處生效;你接入的伺服器:需要在那裡開放的連接埠 |
POST | /api/servers/{sid}/firewall/rules | 為購買的伺服器新增放行規則:{protocol, ports, sources, note},protocol 為 tcp、udp 或 icmp,ports 例如 8080 或 30000-30100,sources 是位址或 CIDR 網段列表(管理員;最多 30 條) |
PUT DELETE | /api/servers/{sid}/firewall/rules/{rid} | 修改規則(請求體相同)或刪除規則(管理員) |
POST | /api/servers/{sid}/firewall/apply | 服務供應商處應用失敗後,重新應用規則(管理員) |
專案
| 方法 | 路徑 | |
|---|---|---|
GET | /api/projects?team_id= | 列出專案 |
POST | /api/projects | 建立:{team_id, server_id, manifest} |
GET | /api/projects/{pid} | 概要、服務和狀態 |
GET | /api/projects/{pid}/manifest | 完整設定,包括變數 |
PUT | /api/projects/{pid} | 儲存設定:{manifest, server_id, revision} |
POST | /api/projects/{pid}/deploy | 部署所有服務(返回一個任務) |
POST | /api/projects/{pid}/rollback | {target_job} |
POST | /api/projects/{pid}/services/{service}/rollback | {target_job}:把一個服務還原到那次部署;其他服務保持當前部署(返回一個任務) |
POST | /api/projects/{pid}/restart /stop | 重啟或停止所有服務 |
GET | /api/projects/{pid}/runtime | 每個服務的即時狀態 |
POST | /api/projects/{pid}/clone /transfer | 複製:{name, team_id, server_id};或者把從未部署過的專案轉移到其他團隊:{team_id, server_id}(需要是兩個團隊的管理員) |
GET | /api/projects/{pid}/move | 最近一次遷移到其他伺服器 |
GET | /api/projects/{pid}/move/preflight?server_id= | 檢查遷移,不做任何改動(管理員) |
POST | /api/projects/{pid}/move | 把專案連同資料遷移到團隊的另一台伺服器:{server_id, confirmation: "<project name>"}(管理員;返回一個任務) |
POST | /api/projects/{pid}/move/finish /move/rollback | 刪除舊副本,或回到舊伺服器(管理員;返回一個任務) |
POST | /api/projects/{pid}/delete | {confirmation: "<project name>"} |
PUT | /api/projects/{pid}/checklist | 遷移檢查清單:{source_backup, data_restored, application_verified, dns_switched, rollback_ready},每項為 true 或 false(成員) |
服務
{service} 是服務 ID;服務當前的名稱也可以使用。在專案的設定中,每個服務都有一個 id 和一個可選的 hostname;新服務兩者都不填時,Zenavo 會給它一個隨機的 svc-… ID,並根據名稱生成主機名。
| 方法 | 路徑 | |
|---|---|---|
POST | /api/projects/{pid}/services/{service}/deploy | 只部署這個服務;其他服務保持當前部署(返回一個任務) |
PUT | /api/projects/{pid}/services/{service}/source?create=&port=&deploy=&acknowledge_init= | 上傳一個目錄的 gzip 壓縮 tar 包(最大 500 MB)作為服務的來源並部署(返回 {upload_id, service, created, job});create=1 會新增不存在的服務,deploy=0 只儲存不部署 |
POST | /api/projects/{pid}/services/{service}/restart /suspend /resume | |
POST | /api/projects/{pid}/services/{service}/exec | 在容器中執行指令 |
GET | /api/projects/{pid}/services/{service}/terminal | WebSocket:互動式終端機(成員)。二進位制幀傳輸輸入和輸出;文字幀傳輸 JSON:用戶端傳送 {"type":"resize","cols","rows"},伺服器端傳送 {"type":"exit","code"} 或 {"type":"error","message"}。瀏覽器必須傳送控制台的 Origin |
GET | /api/projects/{pid}/services/{service}/files?path= | 列出容器中的資料夾(成員) |
GET | /api/projects/{pid}/services/{service}/files/content?path= | 檔案的前 256 KB,以文字形式返回 |
GET | /api/projects/{pid}/services/{service}/files/download?path= | 下載檔案(最大 500 MB) |
PUT | /api/projects/{pid}/services/{service}/files/upload?dir=&name= | 上傳原始請求體(最大 50 MB,必須提供 Content-Length);&overwrite=1 會替換已有檔案 |
POST | /api/projects/{pid}/services/{service}/files/mkdir | 新建資料夾:{"path"} |
POST | /api/projects/{pid}/services/{service}/files/rename | 原地重新命名:{"path", "name"} |
POST | /api/projects/{pid}/services/{service}/files/delete | 刪除檔案或資料夾:{"path"} |
GET | /api/projects/{pid}/services/{service}/usage?hours= | CPU 和記憶體的歷史資料 |
GET | /api/projects/{pid}/logs/{service} | 最近的執行日誌 |
GET | /api/projects/{pid}/logs/{service}/stream | 即時執行日誌(Server-Sent Events) |
GET | /api/projects/{pid}/logs/{service}/history?from=&to=&q=&cursor=&limit= | 已儲存的執行日誌,從舊到新:from 和 to 之間包含 q(不區分大小寫)的行(RFC 3339 或 Unix 秒;預設最近一小時)。把返回的 next 作為 cursor 傳回即可取得更多 |
GET | /api/projects/{pid}/logs/{service}/download?from=&to=&q= | 把某個時間範圍內已儲存的執行日誌下載為文字檔案 |
POST | /api/projects/{pid}/services/{service}/domains | 生成網域 |
DELETE | /api/projects/{pid}/domains/{domain} | 移除生成的網域 |
資料庫檢視
資料庫服務的資料庫檢視。成員可以讀取;寫入需要團隊管理員。每個請求都在資料庫容器內執行,並記錄在稽核日誌中,但不記錄 SQL、鍵、值或文件。 … 即 /api/projects/{pid}/services/{service}。
| 方法 | 路徑 | |
|---|---|---|
GET | …/database/tables | PostgreSQL、MySQL、MariaDB:表和檢視,以及估算的行數和列 |
GET | …/database/rows?schema=&table=&offset=&limit= | 一頁表資料 |
POST | …/database/query | 執行一條 SQL 語句:{sql, allow_writes, max_rows}。除非 allow_writes(管理員),否則唯讀 |
POST | …/database/keys | Redis、Valkey:用 SCAN 列出鍵:{db, match, cursor, count} |
POST | …/database/key | 單個鍵的值:{db, key, cursor, count};不是 UTF-8 的鍵用 key_b64 |
POST | …/database/command | 執行一條指令:{db, command, allow_writes}。除非 allow_writes(管理員),否則只能執行讀取指令 |
GET | …/database/collections | MongoDB:資料庫和集合,以及估算的文件數 |
POST | …/database/find | 查詢文件:{db, collection, filter, sort, projection, skip, limit},均為 JSON 字串;預設每頁 50 個 |
POST | …/database/document | 修改一個文件:{op, db, collection, filter, update, document},op 為 insert、update 或 delete(管理員) |
備份
| 方法 | 路徑 | |
|---|---|---|
GET | /api/backups?project_id= | 列出備份 |
POST | /api/projects/{pid}/backups | 備份一個服務:{service, engine} |
GET | /api/backups/{bid}/download | 下載 |
POST | /api/backups/{bid}/restore | 恢復:{confirmation},與控制台中輸入的內容相同 |
Git 與 GitHub
| 方法 | 路徑 | |
|---|---|---|
POST | /api/git/refs | 公開儲存庫的分支和標籤:{url} |
POST | /api/git/plan | 建置計劃預覽:{team_id, url, ref, context} |
GET | /api/github/repos?team_id= | 團隊的 GitHub 安裝可以讀取的儲存庫 |
GET | /api/github/branches?team_id=&repo= | 其中某個儲存庫的分支 |
映像檔與網域
| 方法 | 路徑 | |
|---|---|---|
GET | /api/images/ports?team_id=&image= | 公開映像檔 EXPOSE 的連接埠,從映像檔儲存庫讀取,不拉取映像檔 |
GET | /api/images/mirror?image= | Zenavo 面向中國大陸的映像檔是否有某個映像檔,以及它在映像檔站中的位址 |
GET | /api/images/mirror/search?q= | 映像檔站中名稱包含 q 的映像檔 |
GET | /api/domains/available?name= | 某個生成的網域是否可用:{available, reason} |
模板
| 方法 | 路徑 | |
|---|---|---|
GET | /api/templates | 模板庫(公開,不需要金鑰) |
GET | /api/templates/{code} | 單個模板及其 manifest(公開) |
POST | /api/imports/preview | 檢查 Zeabur 模板或 Zenavo 匯出檔案:{text, team_id, variables};或者模板庫中的模板:{template, team_id, variables} |
POST | /api/imports/{did}/commit | 建立專案:{server_id} |
範例:部署並等待完成
sh
JOB=$(curl -s -X POST -H "Authorization: Bearer $ZENAVO_API_KEY" \
-H "Content-Type: application/json" -d '{}' \
https://zenavo.app/api/projects/$PROJECT/deploy | jq -r .id)
until curl -s -H "Authorization: Bearer $ZENAVO_API_KEY" https://zenavo.app/api/jobs/$JOB \
| jq -e '.status == "succeeded" or .status == "failed"' >/dev/null; do sleep 5; done