OwlProxy API
  • 简体中文
  • English
  • 简体中文
  • English
  • 产品介绍
  • 产品类型
  • 产品计费
  • OpenAPI
    • 接口文档
    • 调用说明
    • 更新记录
    • LLMs.txt(AI快速参考)
    • OpenAPI 规范(AI 专用)
    • 错误码

调用说明

概述

OwlProxy OpenAPI 使用 V2 SHA-256 签名进行请求鉴权。

获取 AK/SK

调用 API 前,请联系技术对接人获取:

  • Access Key ID(AK)
  • Secret Access Key(SK)

请妥善保管 SK,不要在前端代码、公开仓库或日志中暴露。

请求头

每个 OpenAPI 请求必须包含以下鉴权请求头:

请求头类型必填说明示例值
X-Access-Keystring是Access Key IDak_xxxxxxxxxxxx
X-Timestampstring是10 位 Unix 秒级时间戳1747555200
X-Signstring是64 位 SHA-256 十六进制签名6069dfb4cb3ac57b...
Content-TypestringPOST/PUT 必填请求体类型application/json

X-Timestamp 与当前时间的偏差不能超过 5 分钟。

签名算法

签名字符串直接拼接,字段之间不添加分隔符:

signString = SK + X-Timestamp + path + bodyOrQuery
X-Sign = lowerHex(SHA-256(signString_UTF8))
字段取值规则
SKSecret Access Key
X-Timestamp与请求头完全一致的秒级时间戳
path完整请求路径,例如 /owlproxy/api/openApi/vcProxy/queryByorderIdList
bodyOrQuery根据请求类型取值,见下表

bodyOrQuery 取值

请求类型bodyOrQuery
GET原始 query string;无参数时为空字符串
POST/PUT JSON实际发送的请求体字符串
无请求体空字符串

GET 参数顺序及 URL 编码必须与实际请求一致。JSON 请求体建议使用单行格式,签名后不要再格式化或重新序列化。

签名示例

以下示例调用订单查询接口:

SK = sk_xxxxxxxxxxxx
X-Timestamp = 1747555200
path = /owlproxy/api/openApi/vcProxy/queryByorderIdList
bodyOrQuery = {"orderIds":["ORDER-00001"]}

待签名字符串:

sk_xxxxxxxxxxxx1747555200/owlproxy/api/openApi/vcProxy/queryByorderIdList{"orderIds":["ORDER-00001"]}

对该字符串的 UTF-8 字节计算 SHA-256:

X-Sign = 39c4d29bc564bdfdbffc8fba89bd088d79be40b29eb3a9f5050d007f416ab05e

调用示例

curl

timestamp=$(date +%s)
accessKey="ak_xxxxxxxxxxxx"
secretKey="sk_xxxxxxxxxxxx"
path="/owlproxy/api/openApi/vcProxy/queryByorderIdList"
body='{"orderIds":["ORDER-00001"]}'

sign=$(printf "%s" "${secretKey}${timestamp}${path}${body}" | openssl dgst -sha256 -hex | awk '{print $2}')

curl -X POST "https://api.owlproxy.com${path}" \
  -H "X-Access-Key: ${accessKey}" \
  -H "X-Timestamp: ${timestamp}" \
  -H "X-Sign: ${sign}" \
  -H "Content-Type: application/json" \
  --data-binary "${body}"

Java

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;

public class OwlProxyOpenApiExample {
    private static final String BASE_URL = "https://api.owlproxy.com";
    private static final String ACCESS_KEY = "ak_xxxxxxxxxxxx";
    private static final String SECRET_KEY = "sk_xxxxxxxxxxxx";

    private static String sha256Hex(String value) throws Exception {
        byte[] digest = MessageDigest.getInstance("SHA-256")
                .digest(value.getBytes(StandardCharsets.UTF_8));
        StringBuilder hex = new StringBuilder(digest.length * 2);
        for (byte item : digest) {
            hex.append(String.format("%02x", item & 0xff));
        }
        return hex.toString();
    }

    public static void main(String[] args) throws Exception {
        String path = "/owlproxy/api/openApi/vcProxy/queryByorderIdList";
        String body = "{\"orderIds\":[\"ORDER-00001\"]}";
        String timestamp = String.valueOf(System.currentTimeMillis() / 1000L);
        String sign = sha256Hex(SECRET_KEY + timestamp + path + body);

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(BASE_URL + path))
                .header("X-Access-Key", ACCESS_KEY)
                .header("X-Timestamp", timestamp)
                .header("X-Sign", sign)
                .header("Content-Type", "application/json")
                .POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8))
                .build();

        HttpResponse<String> response = HttpClient.newHttpClient().send(
                request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
        System.out.println(response.body());
    }
}

Python

import hashlib
import time

import requests

baseUrl = "https://api.owlproxy.com"
accessKey = "ak_xxxxxxxxxxxx"
secretKey = "sk_xxxxxxxxxxxx"
path = "/owlproxy/api/openApi/vcProxy/queryByorderIdList"
body = '{"orderIds":["ORDER-00001"]}'
timestamp = str(int(time.time()))
sign = hashlib.sha256(
    (secretKey + timestamp + path + body).encode("utf-8")
).hexdigest()

response = requests.post(
    baseUrl + path,
    headers={
        "X-Access-Key": accessKey,
        "X-Timestamp": timestamp,
        "X-Sign": sign,
        "Content-Type": "application/json",
    },
    data=body.encode("utf-8"),
)
print(response.text)

Node.js

const crypto = require("crypto");

const baseUrl = "https://api.owlproxy.com";
const accessKey = "ak_xxxxxxxxxxxx";
const secretKey = "sk_xxxxxxxxxxxx";
const path = "/owlproxy/api/openApi/vcProxy/queryByorderIdList";
const body = '{"orderIds":["ORDER-00001"]}';
const timestamp = Math.floor(Date.now() / 1000).toString();
const sign = crypto
  .createHash("sha256")
  .update(secretKey + timestamp + path + body, "utf8")
  .digest("hex");

async function main() {
  const response = await fetch(baseUrl + path, {
    method: "POST",
    headers: {
      "X-Access-Key": accessKey,
      "X-Timestamp": timestamp,
      "X-Sign": sign,
      "Content-Type": "application/json",
    },
    body,
  });
  console.log(await response.text());
}

main().catch(console.error);

GET 请求示例

请求:

GET /owlproxy/api/openApi/vcProxyShop/getProxyTypeList?proxyMode=1

签名字段:

path = /owlproxy/api/openApi/vcProxyShop/getProxyTypeList
bodyOrQuery = proxyMode=1

常见错误码

code说明处理建议
2019验证签名失败检查路径、参数顺序和请求体
2031无效的密钥检查 Access Key ID
2032请求头缺少鉴权信息补充三个 V2 请求头
2033时间戳过期或格式错误使用当前 Unix 秒级时间戳

常见问题

POST 请求体为空时如何签名?

bodyOrQuery 使用空字符串,待签名字符串以 path 结尾。

X-Sign 区分大小写吗?

建议统一输出 64 位小写十六进制字符串。

为什么相同参数仍然验签失败?

请依次检查完整请求路径、时间戳单位、GET 参数顺序、URL 编码及实际发送的 JSON 字符串。

数据加解密示例

Java AES GCM 解密

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;

public class AESUtils {
    private static final String AES = "AES";
    private static final String AES_CIPHER_ALGORITHM = "AES/GCM/NoPadding";
    private static final int GCM_TAG_LENGTH = 16;
    private static final int GCM_IV_LENGTH = 12;

    private static SecretKeySpec getKeyFromPassword(String password) throws NoSuchAlgorithmException {
        MessageDigest sha = MessageDigest.getInstance("SHA-256");
        byte[] key = sha.digest(password.getBytes());
        return new SecretKeySpec(key, AES);
    }

    public static byte[] generateIv() {
        byte[] iv = new byte[GCM_IV_LENGTH];
        new SecureRandom().nextBytes(iv);
        return iv;
    }

    public static String encrypt(String input, String key) {
        try {
            SecretKeySpec secretKeySpec = getKeyFromPassword(key);
            byte[] iv = generateIv();
            Cipher cipher = Cipher.getInstance(AES_CIPHER_ALGORITHM);
            GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmParameterSpec);
            byte[] cipherText = cipher.doFinal(input.getBytes());

            String ivString = Base64.getEncoder().encodeToString(iv);
            String cipherTextString = Base64.getEncoder().encodeToString(cipherText);
            return ivString + ":" + cipherTextString;
        } catch (Exception e) {
            return null;
        }
    }

    public static String decrypt(String encryptedData, String key) {
        try {
            SecretKeySpec secretKeySpec = getKeyFromPassword(key);
            String[] parts = encryptedData.split(":");
            byte[] iv = Base64.getDecoder().decode(parts[0]);
            byte[] cipherText = Base64.getDecoder().decode(parts[1]);

            Cipher cipher = Cipher.getInstance(AES_CIPHER_ALGORITHM);
            GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, gcmParameterSpec);
            return new String(cipher.doFinal(cipherText));
        } catch (Exception e) {
            return null;
        }
    }
}
Prev
接口文档
Next
更新记录