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})以及账号是否有身份验证器应用(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