API 接入文档

通过 API Key 将 apkgo cloud 集成到你的 CI/CD 流水线中

认证方式

Open API 的 base path 是 /openapi/v1,路径中不需要带 orgId — 组织信息已经绑定在 API Key 上。请求头加上 X-API-Key 即可。

curl -H "X-API-Key: apkgo_your_key_here" \
  https://apkgo.baici.tech/openapi/v1/uploads

Open API 只接受 X-API-Key,不接受 JWT;Dashboard 仍然走 /api/v1/orgs/{orgId}/...

上传 APK(三步直传)

APK 字节不再经过 apkgo cloud 服务器,而是直传对象存储。完整流程三步:领上传凭证 → 直传 APK → 创建分发任务。

第 1 步 · 获取上传凭证

curl -X POST \
  -H "X-API-Key: apkgo_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"file_name": "app-release.apk"}' \
  https://apkgo.baici.tech/openapi/v1/uploads/tickets

# → {"data": {"provider": "qiniu",
#             "object_key": "apks/<org>/incoming/<uuid>-app-release.apk",
#             "upload_url": "https://upload-xxx.qiniup.com",
#             "token": "<uptoken>",
#             "expires_at": "..."}}

第 2 步 · 直传 APK 到对象存储

curl -X POST \
  -F "token=<token>" \
  -F "key=<object_key>" \
  -F "file=@app-release.apk" \
  <upload_url>

这一步直连七牛上传节点,带宽与 apkgo cloud 服务器无关。凭证 1 小时内有效,仅能写入该 object_key、限 1GB。

第 3 步 · 创建分发任务(JSON)

curl -X POST \
  -H "X-API-Key: apkgo_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "object_key": "<object_key>",
    "package_name": "com.example.app",
    "version_name": "1.2.0",
    "version_code": 12,
    "release_notes": "修复若干问题",
    "target_stores": ["huawei", "oppo", "vivo", "honor"]
  }' \
  https://apkgo.baici.tech/openapi/v1/uploads

返回 202 Accepted,包含 job ID,上传在后台异步执行。package_name 必填(传 app_id 时可省):服务器不再解析 APK,worker 下载后会按二进制实际包名校验,不一致任务会失败。target_stores 省略时分发到该应用绑定的全部商店。可选 sha256 供 worker 校验包体完整性。

支持的商店:huaweixiaomioppovivohonortencentsamsunggoogleplaypgyerfir。未在控制台为应用绑定凭证的商店会被自动跳过。

CI 脚本示例(bash + jq)

KEY="apkgo_your_key_here"
BASE="https://apkgo.baici.tech/openapi/v1"
APK="app-release.apk"

T=$(curl -s -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d "{\"file_name\":\"$APK\"}" $BASE/uploads/tickets)
OBJ=$(echo "$T" | jq -r .data.object_key)

curl -sf -F "token=$(echo "$T" | jq -r .data.token)" \
  -F "key=$OBJ" -F "file=@$APK" "$(echo "$T" | jq -r .data.upload_url)"

curl -s -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d "{\"object_key\":\"$OBJ\",\"package_name\":\"com.example.app\"}" \
  $BASE/uploads

⚠️ 旧版 -F "apk=@..." multipart 直传接口已移除,继续调用会收到 400 与迁移提示。

查询上传状态

curl -H "X-API-Key: apkgo_your_key_here" \
  https://apkgo.baici.tech/openapi/v1/uploads/{jobId}

状态值:pendingprocessingcompletedfailed

API 端点

POST/openapi/v1/uploads/tickets获取直传对象存储的上传凭证
POST/openapi/v1/uploads围绕已直传的 APK 创建分发任务
GET/openapi/v1/uploads列出最近的上传任务
GET/openapi/v1/uploads/{jobId}查询单个任务状态与结果
POST/openapi/v1/uploads/{jobId}/cancel取消任务
POST/openapi/v1/uploads/{jobId}/retry重试失败任务

应用与凭证的管理(创建、绑定、修改)只在控制台进行;上传时应用会按 package_name 自动查找或创建,目标商店从你在控制台配好的绑定中解析。

权限

API Key 携带一组权限(默认 ["upload"])。当前 Open API 所有端点都需要 upload 权限;通配 "*" 等于全开。

错误处理

所有 API 响应格式统一:

// 成功
{"data": { ... }}

// 失败
{"error": "错误信息"}
401API Key 缺失、无效或过期
403权限不足(缺 upload 权限或超出套餐配额)
429请求频率超限(600 次 / 分钟)

Webhook 回调

在 API 密钥页面配置 Webhook URL 后,每次上传任务完成(成功或失败)系统会自动向该 URL 发送 POST 请求。

请求格式

POST https://your-server.com/webhook
Content-Type: application/json
X-Webhook-Signature: sha256=<HMAC-SHA256(body, secret)>

{
  "event": "upload.completed",
  "job_id": "550e8400-e29b-41d4-a716-446655440000",
  "app_name": "我的应用",
  "package_name": "com.example.app",
  "version_name": "1.2.0",
  "version_code": 12,
  "status": "completed",
  "results": [
    {
      "store_name": "huawei",
      "success": true,
      "duration_ms": 3200
    },
    {
      "store_name": "xiaomi",
      "success": false,
      "error": "credential expired",
      "duration_ms": 1500
    }
  ],
  "timestamp": "2026-04-10T10:00:00Z"
}

事件类型

upload.completed所有商店上传成功
upload.failed至少一个商店上传失败

签名验证

如果配置了签名密钥,请求头 X-Webhook-Signature 包含 HMAC-SHA256 签名。验证示例:

# Python
import hmac, hashlib

def verify(body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(), body, hashlib.sha256
    ).hexdigest()
    return signature == f"sha256={expected}"

# Go
func verify(body []byte, signature, secret string) bool {
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(body)
    expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(signature), []byte(expected))
}

# Node.js
const crypto = require("crypto");
function verify(body, signature, secret) {
    const expected = "sha256=" + crypto
        .createHmac("sha256", secret)
        .update(body)
        .digest("hex");
    return signature === expected;
}

接收端示例

# Python (Flask)
@app.route("/webhook", methods=["POST"])
def webhook():
    sig = request.headers.get("X-Webhook-Signature", "")
    if SECRET and not verify(request.data, sig, SECRET):
        return "invalid signature", 401

    data = request.json
    if data["event"] == "upload.completed":
        for r in data["results"]:
            if r["success"]:
                print(f"✅ {r['store_name']}: {r['duration_ms']}ms")
            else:
                print(f"❌ {r['store_name']}: {r['error']}")
    return "ok"

# Go
func webhookHandler(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body)
    // verify signature...

    var payload struct {
        Event   string `json:"event"`
        AppName string `json:"app_name"`
        Status  string `json:"status"`
        Results []struct {
            StoreName  string `json:"store_name"`
            Success    bool   `json:"success"`
            Error      string `json:"error"`
            DurationMs int64  `json:"duration_ms"`
        } `json:"results"`
    }
    json.Unmarshal(body, &payload)

    for _, r := range payload.Results {
        if r.Success {
            log.Printf("✅ %s: %dms", r.StoreName, r.DurationMs)
        } else {
            log.Printf("❌ %s: %s", r.StoreName, r.Error)
        }
    }
    w.Write([]byte("ok"))
}

注意事项

  • • 请求超时 10 秒,超时不重试
  • • 返回 2xx 视为成功,其他状态码记录日志但不重试
  • • 签名密钥为空时不发送 X-Webhook-Signature 头
  • • Webhook 在 API 密钥页面底部配置