猫咪云 猫咪云开放平台maomiyun.com
API v1 开发者中心

MAOMIYUN OPENAPI · V1

把猫咪云接入你的平台

通过服务端接口读取你的实际商品价格、查询余额、创建订单并跟踪进度。所有接口都使用 HTTPS、AppId、HMAC-SHA256 签名和服务器 IP 白名单。

Base URL https://maomiyun.com/openapi/v1
01

快速开始

整个接入只需要四步。接口应用创建成功后,AppSecret 只完整显示一次,请当场保存到你服务器的环境变量中。

  1. 创建接口应用登录猫咪云,在“个人中心 → 开放接口”创建应用,取得 AppId 和 AppSecret。
  2. 添加 IP 白名单填写运行对接程序那台服务器的出口公网 IP;不是手机或家里电脑的 IP。
  3. 计算请求签名每次请求生成 Timestamp、Nonce,并用 AppSecret 计算 HMAC-SHA256。
  4. 先调 Ping连通测试成功后,再读取商品并创建订单。
重要:AppSecret 只能放在服务端。不要写进 HTML、浏览器 JavaScript、手机 App、小程序或公开仓库。
02

请求头与安全要求

除公开文档外,所有 /openapi/v1/* 请求都必须携带以下请求头。

请求头是否必填说明
X-App-Id开发者中心生成的 AppId
X-Timestamp当前 Unix 秒,允许与服务器时间相差 300 秒
X-Nonce每次请求新生成,建议 16 字节以上随机值;10 分钟内不能重复
X-Signature-Version固定填写 v1
X-Signature64 位小写十六进制 HMAC-SHA256 签名
Idempotency-Key仅下单8-64 位唯一键,防止网络重试造成重复扣款
Content-TypePOST 必填application/json

IP 白名单

白名单只接受精确的公网 IPv4 或 IPv6,每个应用最多 5 个。你在开发者中心添加或删除后立即生效,不需要管理员审核。未添加白名单时,所有接口调用都会返回 IP_NOT_ALLOWED

03

签名算法

先按下面的顺序组成待签名字符串。每个字段之间使用一个换行符 \n,末尾不再添加换行。

Canonical String
UPPERCASE_HTTP_METHOD
EXACT_REQUEST_PATH
RFC3986_SORTED_QUERY_STRING
SHA256_HEX_OF_RAW_BODY
X_TIMESTAMP
X_NONCE

然后计算:

Signature
signature = hex_lowercase(
  HMAC_SHA256(APP_SECRET, canonical_string)
)

查询参数排序

将所有参数按参数名升序排列;参数名相同时按参数值升序。名称和值都按 RFC 3986 百分号编码,再以 & 连接。没有查询参数时,这一行为空。

请求体哈希

必须对实际发送的原始 UTF-8 字节计算 SHA-256。GET 请求没有请求体时,对长度为 0 的空字节串计算,其结果固定为:

e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
常见错误:先签名一个 JSON,再让 SDK 重新格式化后发送,会导致请求体字节不同。请先生成一次 rawBody 字符串,签名和发送都使用同一个值。
GET/ping连通测试

验证 AppId、签名、时间、Nonce 和 IP 白名单是否都正确。

请求参数

响应示例
{
  "ok": true,
  "code": "SUCCESS",
  "message": "success",
  "data": {
    "connected": true,
    "server_time": "2026-08-09T08:30:00.000Z"
  },
  "request_id": "..."
}
GET/account/balance查询账户余额

返回接口应用绑定账号的可用余额。金额字段使用十进制字符串,调用方不要用二进制浮点数直接做财务计算。

响应 data
{
  "balance": "126.50",
  "currency": "CNY"
}
GET/categories获取商品分类

返回当前在售商品对应的平台和分类。

响应 data
[
  {
    "category_id": "d387c149a5e2163c",
    "platform": "抖音",
    "name": "抖音点赞"
  }
]
GET/products获取商品列表
参数必填说明
platform按平台名称筛选
category按二级分类名称筛选
keyword按商品名称搜索
page页码,默认 1
page_size每页数量,默认 20,最大 100
价格规则:unit_price 是当前接口账号的实际售价,已自动计算账号单独价或上级设置的下级价,不是上游成本。
GET/product?product_id=...获取商品详情

商品详情会额外返回下单字段 fields。创建订单时,params 的键必须使用字段的 key

响应 data
{
  "product_id": "p-example",
  "name": "抖音点赞",
  "platform": "抖音",
  "category": "抖音点赞",
  "unit_price": "0.0125",
  "min_quantity": 10,
  "max_quantity": 100000,
  "quantity_step": 10,
  "status": "available",
  "description": "商品说明",
  "fields": [
    {
      "key": "视频地址",
      "label": "视频地址",
      "type": "text",
      "required": true,
      "placeholder": "请输入短视频链接"
    }
  ]
}
POST/orders创建订单

下单会实时使用绑定账号的实际售价并从余额扣款。请勿提交价格、总金额、用户编号或订单状态。

JSON 字段必填说明
client_order_no你系统内的唯一订单号,6-64 位
product_id猫咪云商品编号
quantity正整数,遵守商品最小值、最大值和递增步长
params按商品商品详情 fields 要求的下单参数
请求体
{
  "client_order_no": "YOUR-ORDER-20260809-0001",
  "product_id": "p-example",
  "quantity": 100,
  "params": {
    "视频地址": "https://v.douyin.com/example/"
  }
}

幂等规则

每次创建订单都必须携带 Idempotency-Key。网络超时后,请使用相同的请求体和相同的 Key 重试:服务端会返回第一次结果,不会再次扣款或重复派单。同一个 Key 搭配不同请求体会返回 IDEMPOTENCY_CONFLICT

GET/order查询单个订单

任选一个查询参数:order_id(猫咪云订单号)或 client_order_no(你的订单号)。接口只会返回当前应用绑定账号自己的订单。

请求路径
/openapi/v1/order?client_order_no=YOUR-ORDER-20260809-0001

批量查询可使用 GET /orders?client_order_no=...,不传筛选条件时返回最近 100 笔订单。

04

订单状态

pending待处理
processing处理中
done已完成
refunding退款中
refunded已退款
error异常,需关注

订单创建后建议每 20-60 秒查询一次;订单进入 donerefunded 后停止轮询。

05

错误码

HTTPcode处理建议
400INVALID_ARGUMENT检查参数格式、数量和必填字段
400INVALID_JSON确保请求体是 UTF-8 JSON 对象
401AUTH_FAILED检查 AppId、时间、Nonce、签名和原始请求体
401REPLAY_DETECTED重新生成 Nonce 后发起新请求
403IP_NOT_ALLOWED将调用服务器的出口公网 IP 加入白名单
403APP_DISABLED在开发者中心重新启用应用
404PRODUCT_NOT_FOUND刷新商品列表,商品可能已下架
404ORDER_NOT_FOUND检查订单号及应用绑定账号
409IDEMPOTENCY_CONFLICT为不同下单内容使用新的幂等 Key
409CLIENT_ORDER_NO_CONFLICT同一个商户订单号不能对应不同商品或数量
422INSUFFICIENT_BALANCE充值后使用新的幂等 Key 再下单
422ORDER_PARAMS_INVALID按商品详情返回的字段和数量规则修正
429RATE_LIMITEDRetry-After 等待后重试
502SUPPLIER_DISPATCH_FAILED保留订单号并查询结果,不要盲目换 Key 重下

所有响应都包含唯一的 request_id。需要协助排查时,请把该编号发给客服,不要发送 AppSecret。

06

完整签名示例

Node.js 18+
const crypto = require("crypto");

const appId = process.env.MAOMIYUN_APP_ID;
const appSecret = process.env.MAOMIYUN_APP_SECRET;
const baseUrl = "https://maomiyun.com";

function encode(value) {
  return encodeURIComponent(value).replace(/[!'()*]/g, c =>
    `%${c.charCodeAt(0).toString(16).toUpperCase()}`
  );
}

async function request(method, pathname, query = {}, body = null, idempotencyKey = "") {
  const entries = Object.entries(query).sort(([ak, av], [bk, bv]) =>
    ak === bk ? String(av).localeCompare(String(bv)) : ak.localeCompare(bk)
  );
  const canonicalQuery = entries.map(([k, v]) => `${encode(k)}=${encode(v)}`).join("&");
  const rawBody = body == null ? "" : JSON.stringify(body);
  const timestamp = String(Math.floor(Date.now() / 1000));
  const nonce = crypto.randomBytes(18).toString("base64url");
  const bodyHash = crypto.createHash("sha256").update(rawBody).digest("hex");
  const canonical = [method.toUpperCase(), pathname, canonicalQuery, bodyHash, timestamp, nonce].join("\n");
  const signature = crypto.createHmac("sha256", appSecret).update(canonical).digest("hex");
  const url = `${baseUrl}${pathname}${canonicalQuery ? `?${canonicalQuery}` : ""}`;
  const response = await fetch(url, {
    method,
    headers: {
      "Content-Type": "application/json",
      "X-App-Id": appId,
      "X-Timestamp": timestamp,
      "X-Nonce": nonce,
      "X-Signature-Version": "v1",
      "X-Signature": signature,
      ...(idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {})
    },
    body: rawBody || undefined
  });
  const result = await response.json();
  if (!response.ok) throw new Error(`${result.code}: ${result.message}`);
  return result.data;
}

request("GET", "/openapi/v1/account/balance").then(console.log);