OpenAPI 接入指南
OpenAPI 指 Costco 提供给外部的服务端使用的 HTTPS API 接口。
开发者可以通过调用这些接口,实现与 Costco 平台的业务系统进行数据交互与系统集成。
所有 OpenAPI 均通过统一的 API 网关进行访问,并遵循统一的认证与访问控制规则
接入地址(Base URL)
开放平台提供多个环境用于不同阶段的开发和验证。
调用接口时,请根据当前接入阶段选择对应环境的 Base URL,所有接口均通过 HTTPS 提供访问,不支持 HTTP 请求。
DEV / 开发环境
用于合作方进行接口开发与初步联调。
该环境主要用于功能开发验证,数据不保证稳定。
https://api-dev.costco.com.cn
QA / 测试环境
用于接口联调测试和业务验证。
在进入生产环境前,合作方需要在该环境完成完整的接口测试。
https://api-qa.costco.com.cn
PROD / 生产环境
仅用于正式业务调用。
只有完成测试验证并获得生产访问权限的合作方才可接入该环境。
https://api.costco.com.cn
API 路径结构
开放平台 API 统一采用以下路径结构:
/partners/{partner}/{business}/v{version}/{resource}示例:
/partners/abc/flagship/v1/orders
示例请求
完整调用示例:
GET https://api-qa.costco.com.cn/partners/abc/flagship/v1/orders
请求头示例:
Ocp-Apim-Subscription-Key: xxxxxxxxx
X-Timestamp: 1719999999
X-Signature: xxxxxxxxx
Subscription Key
什么是 Subscription Key
Subscription Key 是开发者调用 OpenAPI 时使用的访问凭证,用于标识调用方身份并控制接口访问权限。
每个合作伙伴在申请 API 访问权限并通过审核后,将获得对应的 Subscription Key。
平台会根据 Subscription Key 对请求进行访问控制、流量管理与调用统计。
获取 Subscription Key
Subscription Key 当前由 Costco 技术团队颁发给开发者。
请妥善保管 Subscription Key,避免泄露给未经授权的第三方。
在请求中使用 Subscription Key
调用 Costco OpenAPI 时,需要在 HTTP 请求的 Header 中携带 Subscription Key:
Ocp-Apim-Subscription-Key: {your-subscription-key}签名校验
签名校验说明
为了保障接口调用的安全性,平台在 Subscription Key 之外提供第二层安全机制:请求签名校验。
签名机制用于实现以下目标:
防止请求内容被篡改
防止 Subscription Key 泄露后被滥用
确保请求确实来自授权合作方系统
每个合作伙伴在接入时都会获得一个 Secret。
Secret 仅用于生成请求签名,不应在客户端或公开环境中暴露。
请求头
所有 OpenAPI 请求必须包含以下 Header:
Ocp-Apim-Subscription-Key:API 访问凭证X-Timestamp:请求时间戳(Unix 秒)X-Signature:请求签名
时间戳规则
X-Timestamp 表示请求发起时间(Unix 时间戳,单位:秒)。
平台会校验请求时间与服务器时间的偏差:
超过 1 小时 的请求将被拒绝处理
用于防止重放攻击
示例:
X-Timestamp: 1719475200
签名生成规则
签名生成规则如下。
Costco 提供在线加签工具,帮助开发者在更便捷地调试公私钥生成、签名等流程。
第一步:构造待签名字符串
待签名字符串由 4 行内容组成,每一行必须以换行符 \n 结尾。
METHOD\n
PATH_WITH_QUERY\n
TIMESTAMP\n
BODY_SHA256\n
参数说明
METHOD
HTTP 请求方法,必须为大写:
GET
POST
PUT
DELETE
PATH_WITH_QUERY
请求 URL 的 路径与查询参数部分,去除协议与域名。
示例:
请求地址:
https://api.costco.com.cn/partners/abc/member/v1/get-member-info?type=3
参与签名的内容:
/partners/abc/member/v1/get-member-info?type=3
TIMESTAMP
请求头中的 X-Timestamp 值。
示例:
1719475200
BODY_SHA256
请求体内容的 SHA256 值。
规则:
请求类型为 GET 或无 body 时,传入空字符串的 SHA256
请求类型为 POST / PUT 时,传入原始 body 的 SHA256
待签名字符串示例
POST
/partners/abc/member/v1/get-member-info?type=3
1719475200
21ce48f1b7624addc45271146d5420be63453d74e7f4d13553e9799d8ed60152
(每一行结尾包含 \n)
第二步:计算签名
使用以下算法计算签名:
signature = Base64( HMAC-SHA256(secret, canonical_string) )参数说明:
secret:接入时分配的签名密钥canonical_string:第一步生成的待签名字符串HMAC-SHA256:HMAC-SHA256 算法Base64:对计算结果进行 Base64 编码
请求示例
POST /partners/abc/member/v1/get-member-info HTTP/1.1
Host: qa-api.costco.com.cn
Content-Type: application/json
Ocp-Apim-Subscription-Key: your_subscription_key
X-Timestamp: 1719475200
X-Signature: AbCdEf1234567890
{
"memberId": "123456"
}
签名生成代码示例
Java 示例
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;
public class SignatureExample {
public static void main(String[] args) throws Exception {
String method = "POST";
String pathWithQuery = "/partners/abc/member/v1/get-member-info";
String timestamp = "1719475200";
String body = "{\"memberId\":\"123456\"}";
String secret = "your_secret";
String bodySha256 = sha256(body);
String canonicalString =
method + "\n" +
pathWithQuery + "\n" +
timestamp + "\n" +
bodySha256 + "\n";
String signature = hmacSha256Base64(secret, canonicalString);
System.out.println("Signature: " + signature);
}
private static String sha256(String data) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(data.getBytes(StandardCharsets.UTF_8));
return bytesToHex(hash);
}
private static String hmacSha256Base64(String key, String data) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey =
new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
mac.init(secretKey);
byte[] rawHmac = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(rawHmac);
}
private static String bytesToHex(byte[] bytes) {
StringBuilder hex = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
hex.append(String.format("%02x", b));
}
return hex.toString();
}
}
安全建议
为了保障接口安全,请遵循以下最佳实践:
不要在客户端代码中暴露 Secret
不要将 Secret 提交至公共代码仓库
定期检查接口调用日志
如发现密钥泄露,请立即联系平台技术支持