章鱼预算方 API 对接文档
1. 文档下载
注意事项:
价格加密使用的是 base64 没有用 base64-url-safe 编码因此上报曝光时 get 请求可能会造成+号变成空格 这个需特殊处理一下 将空格改为+号
2. 版本历史
| 版本 | 日期 | 备注 |
|---|---|---|
| 1.0.0 | 2019-01-28 | 初版 |
| 2.0.0 | 2019-07-28 | 新增 oaid 等字段标识 |
| 3.0.0 | 2021-04-1 | 支持 RTB 竞价 |
| 3.3.0 | 2022-01-17 | 支持 价格宏替换加密 |
| 3.4.19 | 2023-03-6 | 支持 idfamd5、oaidmd5 等字段标识 |
| 3.4.20 | 2023-06-15 | 新增 paid 参数 |
| 3.4.21 | 2024-04-10 | 新增 caid 相关参数 |
| 3.5.1 | 2024-08-08 | 新增下载类相关参数 |
| 3.5.2 | 2025-09-28 | 新增监测上报时间宏替换 |
| 3.5.3 | 2025-11-07 | 新增请求限制时间、接口超时时间戳 |
| 3.6.1 | 2025-12-10 | 请求: 1、是否支持点击宏替换 2、是否支持点击时间上报(点击宏替换的秒级、毫秒级) 响应: 1、广告来源(用于点击坐标宏替换)1:oppo 2:华为 点击宏替换: 1、点击宏替换 新版本修改为__oct_click_type__ |
3.请求参数生成示例
以 json 为示例 转换成 pb 序列化后的格式 并生成文件
import com.googlecode.protobuf.format.JsonFormat;
import com.octopus.bidder.api.openrtb.Zy_Proto;
import java.io.FileOutputStream;
/**
* @author Kari
* @date 2022年10月10日下午3:04
*/
public class Test {
public static void main(String[] args) {
Zy_Proto.Request.Builder request = Zy_Proto.Request.newBuilder();
String jsonFormat = "{\n" +
" \"version\": 2,\n" +
" \"id\": \"oFcXvbXQFyVR19LuKNnR08sZbEiA8N5IwGs63mZC\",\n" +
" \"imp\": [\n" +
" {\n" +
" \"id\": 0,\n" +
" \"pid\": \"202607310\",\n" +
" \"width\": 720,\n" +
" \"height\": 1280,\n" +
" \"bid_floor\": 0\n" +
" }\n" +
" ],\n" +
" \"device\": {\n" +
" \"ip\": \"223.96.182.139\",\n" +
" \"user_agent\": \"Mozilla%2F5.0%28Linux%3BAndroid10%3BAQM-AL00Build%2FHUAWEIAQM-AL00%3Bwv%29AppleWebKit%2F537.36%28KHTML%2ClikeGecko%29Version%2F4.0Chrome%2F88.0.4324.93MobileSafari%2F537.36\",\n" +
" \"idfa\": \"\",\n" +
" \"imei\": \"\",\n" +
" \"imei_md5\": \"\",\n" +
" \"paid\": \"\",\n" +
" \"mac\": \"94:37:F7:92:A6:1F\",\n" +
" \"mac_md5\": \"\",\n" +
" \"device_type\": 0,\n" +
" \"brand\": \"HUAWEI\",\n" +
" \"model\": \"AQM-AL00\",\n" +
" \"os\": \"Android\",\n" +
" \"osv\": \"10\",\n" +
" \"network\": 1,\n" +
" \"operator\": 0,\n" +
" \"width\": 1080,\n" +
" \"height\": 2198,\n" +
" \"pixel_ratio\": 0,\n" +
" \"geo\": {\n" +
" \"lat\": 0.0,\n" +
" \"lon\": 0.0\n" +
" },\n" +
" \"installed_app\": [\n" +
" \"com.xunmeng.pinduoduo\",\n" +
" \"com.eg.android.AlipayGphone\"\n" +
" ],\n" +
" \"oaid\": \"b7eef53f-67f8-47tf-f7fa-ffe2fef35972\",\n" +
" \"idfa_md5\": \"\",\n" +
" \"android_id\": \"\"\n" +
" },\n" +
" \"app\": {\n" +
" \"package_name\": \"com.saveworry.wifi\",\n" +
" \"app_name\": \"wifishengxin\",\n" +
" \"version\": \"3.4.19\"\n" +
" }\n" +
"}";
try {
JsonFormat.merge(jsonFormat, request);
Zy_Proto.Request rtbRequest = request.build();
rtbRequest.writeTo(System.out);
rtbRequest.writeTo(new FileOutputStream("./zyRequest.txt"));
} catch (Exception e) {
e.printStackTrace();
}
}
}
package main
import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"errors"
"strings"
)
func main() {
ADX_OUT_ZHANGYU_AES_KEY := "19be45687c64a0cd"
auctionPrice := "JQFrGfwBmkgdu389VD84Vw=="
priceEncrypt := ECBPk5DecryptPrice(strings.Replace(auctionPrice, " ", "+", -1), ADX_OUT_ZHANGYU_AES_KEY)
println(priceEncrypt)
_ = priceEncrypt
}
func ECBPk5DecryptPrice(s string, token string) string {
defer func() {
r := recover()
if r != nil {
}
}()
by := []byte(token)
res, _ := base64.StdEncoding.DecodeString(s)
dst, _ := AesECBDecrypt(res, by, 1)
return string(dst)
}
func AesECBDecrypt(ciphertext, key []byte, padding int) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
if len(ciphertext)%aes.BlockSize != 0 {
return nil, errors.New("crypto/cipher: input not full blocks")
}
mode := NewECBDecrypter(block)
plaintext := make([]byte, len(ciphertext))
mode.CryptBlocks(plaintext, ciphertext)
if padding == 1 {
plaintext = PKCS5Unpadding(plaintext)
}
return plaintext, nil
}
func PKCS5Unpadding(src []byte) []byte {
length := len(src)
if length == 0 {
return src
}
unpadding := int(src[length-1])
if unpadding > length {
return src
}
return src[:(length - unpadding)]
}
type ecb struct {
b cipher.Block
blockSize int
}
func newECB(b cipher.Block) *ecb {
return &ecb{
b: b,
blockSize: b.BlockSize(),
}
}
type ecbDecrypter ecb
func NewECBDecrypter(b cipher.Block) cipher.BlockMode {
return (*ecbDecrypter)(newECB(b))
}
func (x *ecbDecrypter) CryptBlocks(dst, src []byte) {
blk := (*ecb)(x)
size := blk.blockSize
if len(src)%size != 0 {
panic("crypto/cipher: input not full blocks")
}
for len(src) > 0 {
blk.b.Decrypt(dst[:size], src[:size])
src = src[size:]
dst = dst[size:]
}
}
func (x *ecbDecrypter) BlockSize() int {
return (*ecb)(x).blockSize
}
