PuppyIP / Developer Docs
客户 API 签名文档
PuppyIP 客户 API 使用 HMAC-SHA256 签名。每个请求都需要带上 App ID、时间戳、随机 nonce 和签名,服务端会校验请求内容、防重放并按 API Key 权限放行。
当前可用接口
现在客户 API 只开放只读账号接口:GET /api/v1/account。API 下单、续费、充值、修改代理账号密码和切换 IP 暂未开放。
live_app_ 开头,沙盒 App ID 以 sbx_app_ 开头,两个环境不能混用。
请求头
| Header | 说明 | 示例 |
|---|---|---|
| X-API-AppId | 账号页生成的 App ID。 | live_app_01J2EXAMPLE000000000000000 |
| X-API-Timestamp | 绝对时间戳,必须带时区,允许 5 分钟内的时钟偏差。 | 2026-06-28T12:00:00+00:00 |
| X-API-Nonce | 每次请求唯一随机值,重复使用会被拒绝。 | nonce-empty-body |
| X-API-Signature | 用 API Secret 对 canonical string 计算出的 HMAC-SHA256 小写十六进制值。 | e3c27a2055ad38f16861790294802b9fac81e02e8d72fbeaa87299598785832d |
Canonical String
签名前先把请求拆成 7 行,用换行符 \n 连接:
METHOD
host
path
canonical_query
timestamp
nonce
sha256(body)
METHOD 使用大写,例如 GET。
host 使用小写,不包含协议,例如 puppyip.com。
path 必须包含开头斜杠,例如 /api/v1/account。
canonical_query 对 query 的 key 和 value 分别 URL 解码后再 rawurlencode,按 key、value 升序排序,重复 key 不合并。没有 query 时这一行留空。
body 使用原始请求体计算 SHA-256。GET 请求没有 body 时,使用空字符串的 SHA-256。
GET /api/v1/account 示例
GET
puppyip.com
/api/v1/account
2026-06-28T12:00:00+00:00
nonce-empty-body
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
上面这组固定示例使用文档测试 Secret puppyip-docs-test-secret 计算出的签名是
e3c27a2055ad38f16861790294802b9fac81e02e8d72fbeaa87299598785832d。
代码示例
以下示例只使用假 App ID 和假 Secret。请不要把真实 API Secret 写入前端页面、公开仓库或日志。
curl
curl https://puppyip.com/api/v1/account \
-H "X-API-AppId: live_app_01J2EXAMPLE000000000000000" \
-H "X-API-Timestamp: 2026-06-28T12:00:00+00:00" \
-H "X-API-Nonce: nonce-empty-body" \
-H "X-API-Signature: e3c27a2055ad38f16861790294802b9fac81e02e8d72fbeaa87299598785832d"
PHP
$method = 'GET';
$host = 'puppyip.com';
$path = '/api/v1/account';
$query = '';
$timestamp = gmdate('Y-m-d\TH:i:s\Z');
$nonce = 'nce_'.bin2hex(random_bytes(16));
$body = '';
$secret = getenv('PUPPYIP_API_SECRET') ?: '';
$canonical = implode("\n", [
$method,
strtolower($host),
$path,
$query,
$timestamp,
$nonce,
hash('sha256', $body),
]);
$signature = hash_hmac('sha256', $canonical, $secret);
Node.js
import crypto from 'node:crypto';
const method = 'GET';
const host = 'puppyip.com';
const path = '/api/v1/account';
const query = '';
const timestamp = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
const nonce = `nce_${crypto.randomBytes(16).toString('hex')}`;
const body = '';
const secret = process.env.PUPPYIP_API_SECRET ?? '';
const canonical = [
method,
host.toLowerCase(),
path,
query,
timestamp,
nonce,
crypto.createHash('sha256').update(body).digest('hex'),
].join('\n');
const signature = crypto
.createHmac('sha256', secret)
.update(canonical)
.digest('hex');
常见错误
- invalid_signature
- 签名缺失、App ID 错误、canonical string 拼接不一致,或使用了错误的 API Secret。
- stale_timestamp
- 时间戳没有时区、格式不是 ISO 8601,或客户端时间与服务器相差超过 5 分钟。
- replayed_nonce
- 同一个 API Key 下重复使用了相同 nonce。每次请求都应该生成新的随机 nonce。
- insufficient_scope
- API Key 没有所需权限,或请求来源 IP 不在允许范围内。