OKDNS
DEVELOPERS

开发文档

OKDNS OpenAPI:用 AccessKey / SecretKey 签名调用,管理域名和解析记录。

快速开始

OKDNS OpenAPI 让你用程序管理域名和解析记录。

1. 登录控制台,在「开放平台 → API 密钥」中创建 AccessKey 和 SecretKey。
2. 按下面的签名方法给每个请求签名。
3. 调用下面的接口,所有接口都返回 JSON。

接口地址https://openapi-dev.okdns.ai
认证方式AccessKey / SecretKey 签名(HMAC-SHA256)
请求格式HTTPS,请求体和响应均为 JSON(UTF-8)
调用限制每个密钥每分钟最多 600 次;时间戳 5 分钟内有效,每个 Nonce 只能使用一次

签名方法

请求头说明
X-Access-KeyAccessKey
X-Timestamp当前 Unix 时间戳(秒),与服务器时间相差不能超过 5 分钟
X-Nonce8~64 位随机字符串,每个请求不同(防重放)
X-Signature签名
StringToSign = METHOD + "\n" + PATH + "\n" + QUERY + "\n" + TIMESTAMP + "\n" + NONCE + "\n" + HEX(SHA256(BODY))
X-Signature  = BASE64(HMAC-SHA256(SecretKey, StringToSign))

METHOD:大写的请求方法;PATH:请求路径,如 /v1/zones/12/records;QUERY:? 后面的原始查询串,没有时为空;BODY:原始请求体,没有时为空。

调用示例

AK="ak_xxxxxxxx"
SK="sk_xxxxxxxx"
METHOD="POST"
URL_PATH="/v1/zones/12/records"
QUERY=""
BODY='{"host":"www","type":"A","value":"192.0.2.10"}'
TS=$(date +%s)
NONCE=$(openssl rand -hex 12)
BODY_HASH=$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')
SIGN=$(printf '%s\n%s\n%s\n%s\n%s\n%s' "$METHOD" "$URL_PATH" "$QUERY" "$TS" "$NONCE" "$BODY_HASH" \
  | openssl dgst -sha256 -hmac "$SK" -binary | base64)

curl -X "$METHOD" "https://openapi-dev.okdns.ai$URL_PATH" \
  -H "Content-Type: application/json" \
  -H "X-Access-Key: $AK" -H "X-Timestamp: $TS" -H "X-Nonce: $NONCE" -H "X-Signature: $SIGN" \
  -d "$BODY"
import base64, hashlib, hmac, json, secrets, time
import requests  # pip install requests

BASE = "https://openapi-dev.okdns.ai"
AK, SK = "ak_xxxxxxxx", "sk_xxxxxxxx"

def call(method, path, query="", body=None):
    raw = json.dumps(body, separators=(",", ":")).encode() if body is not None else b""
    ts, nonce = str(int(time.time())), secrets.token_hex(12)
    to_sign = "\n".join([method, path, query, ts, nonce, hashlib.sha256(raw).hexdigest()])
    sign = base64.b64encode(hmac.new(SK.encode(), to_sign.encode(), hashlib.sha256).digest()).decode()
    headers = {"Content-Type": "application/json", "X-Access-Key": AK,
               "X-Timestamp": ts, "X-Nonce": nonce, "X-Signature": sign}
    url = BASE + path + ("?" + query if query else "")
    r = requests.request(method, url, data=raw, headers=headers, timeout=30)
    return r.json()

print(call("GET", "/v1/zones"))
print(call("POST", "/v1/zones/12/records", body={"host": "www", "type": "A", "value": "192.0.2.10"}))
package main

import (
	"bytes"
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"io"
	"net/http"
	"strconv"
	"time"
)

const (
	base = "https://openapi-dev.okdns.ai"
	ak   = "ak_xxxxxxxx"
	sk   = "sk_xxxxxxxx"
)

func call(method, path, query string, body []byte) (string, error) {
	ts := strconv.FormatInt(time.Now().Unix(), 10)
	n := make([]byte, 12)
	rand.Read(n)
	nonce := hex.EncodeToString(n)
	h := sha256.Sum256(body)
	toSign := method + "\n" + path + "\n" + query + "\n" + ts + "\n" + nonce + "\n" + hex.EncodeToString(h[:])
	m := hmac.New(sha256.New, []byte(sk))
	m.Write([]byte(toSign))
	url := base + path
	if query != "" {
		url += "?" + query
	}
	req, _ := http.NewRequest(method, url, bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-Access-Key", ak)
	req.Header.Set("X-Timestamp", ts)
	req.Header.Set("X-Nonce", nonce)
	req.Header.Set("X-Signature", base64.StdEncoding.EncodeToString(m.Sum(nil)))
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	out, _ := io.ReadAll(resp.Body)
	return string(out), nil
}

func main() {
	fmt.Println(call("GET", "/v1/zones", "", nil))
	fmt.Println(call("POST", "/v1/zones/12/records", "", []byte(`{"host":"www","type":"A","value":"192.0.2.10"}`)))
}
import crypto from 'node:crypto'

const BASE = 'https://openapi-dev.okdns.ai'
const AK = 'ak_xxxxxxxx'
const SK = 'sk_xxxxxxxx'

async function call(method, path, query = '', body) {
  const raw = body === undefined ? '' : JSON.stringify(body)
  const ts = String(Math.floor(Date.now() / 1000))
  const nonce = crypto.randomBytes(12).toString('hex')
  const hash = crypto.createHash('sha256').update(raw).digest('hex')
  const toSign = [method, path, query, ts, nonce, hash].join('\n')
  const sign = crypto.createHmac('sha256', SK).update(toSign).digest('base64')
  const res = await fetch(BASE + path + (query ? '?' + query : ''), {
    method,
    headers: { 'Content-Type': 'application/json', 'X-Access-Key': AK, 'X-Timestamp': ts, 'X-Nonce': nonce, 'X-Signature': sign },
    body: raw || undefined,
  })
  return res.json()
}

console.log(await call('GET', '/v1/zones'))
console.log(await call('POST', '/v1/zones/12/records', '', { host: 'www', type: 'A', value: '192.0.2.10' }))

响应与错误码

成功时 HTTP 200 / 201,响应为 {"code":0,"message":"ok","data":{…}};失败时 HTTP 状态码即 code,data.error 为错误码,每个响应都带 X-Request-Id 响应头。

HTTP错误码说明
400bad_request参数格式不正确
401missing_signature缺少签名请求头
401invalid_timestamp / timestamp_expiredX-Timestamp 格式不正确,或与服务器时间相差超过 5 分钟
401invalid_nonce / nonce_replayedX-Nonce 格式不正确,或已使用过(重放的请求)
401signature_mismatch签名校验失败:AccessKey 不存在、SecretKey 不对或待签名字符串有误(不区分具体原因)
403key_disabled / key_expired密钥已停用或已过期
403ip_not_allowed来源 IP 不在密钥的 IP 白名单中
403wrong_host访问地址与密钥不匹配:管理平台的密钥使用 OpenAPI 地址,组织的密钥使用组织自己的地址
403read_only_key只读密钥调用了修改接口
403forbidden密钥创建人没有这个操作的权限
403tenant_suspended / key_owner_disabled组织已停用,或密钥创建人账号已停用
404not_found资源不存在,或不属于这个密钥可以操作的组织
409conflict / invalid_state资源已存在,或当前状态不能执行这个操作
422validation_failed解析记录校验不通过,data.details 列出每个问题
422cluster_not_granted / invalid_zone / quota_exceeded集群未授权、域名格式不正确或超出配额
429rate_limited调用过于频繁:每个密钥每分钟最多 600 次

基础数据接口

线路列表

GET/v1/lines

添加解析记录时可用的线路(line_code)

查询参数

参数类型必填说明
qstring否按线路编码或名称搜索

响应示例(data)

{
  "items": [
    {
      "code": "default",
      "name": "默认",
      "category": "DEFAULT"
    }
  ],
  "total": 1
}

可用的 DNS 集群

GET/v1/clusters

添加域名时可选的集群(cluster_id):OKDNS 官方集群(owner=official)和自建集群(owner=self)

响应示例(data)

{
  "items": [
    {
      "id": 1,
      "name": "公共集群",
      "owner": "official",
      "ns_names": [
        "ns1.okdns.net",
        "ns2.okdns.net"
      ]
    }
  ],
  "total": 1
}

域名接口

域名列表

GET/v1/zones

查询参数

参数类型必填说明
qstring否域名包含

响应示例(data)

{
  "items": [
    {
      "id": 12,
      "tenant_id": 3,
      "name": "example.com",
      "status": "pending_ns",
      "cluster_id": 1,
      "soa_serial": 2026110101,
      "created_at": "2026-11-01T08:00:00Z",
      "updated_at": "2026-11-01T08:00:00Z"
    }
  ],
  "total": 1
}

添加域名

POST/v1/zones

添加成功后,请到域名注册商把 NS 修改为返回的 ns_names;生效前状态为 pending_ns

请求体(JSON)

参数类型必填说明
namestring是域名,如 example.com
cluster_idint是DNS 集群 ID(GET /v1/clusters)
group_idint否域名分组 ID

请求示例

{
  "name": "example.com",
  "cluster_id": 1
}

响应示例(data)

{
  "zone": {
    "id": 12,
    "tenant_id": 3,
    "name": "example.com",
    "status": "pending_ns",
    "cluster_id": 1,
    "ns_names": [
      "ns1.okdns.net",
      "ns2.okdns.net"
    ],
    "soa_serial": 2026110101,
    "created_at": "2026-11-01T08:00:00Z",
    "updated_at": "2026-11-01T08:00:00Z"
  }
}

域名详情

GET/v1/zones/{zone_id}

路径参数

参数类型必填说明
zone_idint是域名 ID

响应示例(data)

{
  "zone": {
    "id": 12,
    "tenant_id": 3,
    "name": "example.com",
    "status": "pending_ns",
    "cluster_id": 1,
    "ns_names": [
      "ns1.okdns.net",
      "ns2.okdns.net"
    ],
    "soa_serial": 2026110101,
    "created_at": "2026-11-01T08:00:00Z",
    "updated_at": "2026-11-01T08:00:00Z"
  }
}

暂停解析

POST/v1/zones/{zone_id}/pause

暂停后 DNS 节点不再应答这个域名

路径参数

参数类型必填说明
zone_idint是域名 ID

响应示例(data)

{
  "zone": {
    "id": 12,
    "tenant_id": 3,
    "name": "example.com",
    "status": "paused",
    "cluster_id": 1,
    "soa_serial": 2026110101,
    "created_at": "2026-11-01T08:00:00Z",
    "updated_at": "2026-11-01T08:00:00Z"
  }
}

恢复解析

POST/v1/zones/{zone_id}/resume

路径参数

参数类型必填说明
zone_idint是域名 ID

响应示例(data)

{
  "zone": {
    "id": 12,
    "tenant_id": 3,
    "name": "example.com",
    "status": "active",
    "cluster_id": 1,
    "soa_serial": 2026110101,
    "created_at": "2026-11-01T08:00:00Z",
    "updated_at": "2026-11-01T08:00:00Z"
  }
}

删除域名

DELETE/v1/zones/{zone_id}

删除域名及其全部解析记录,不可恢复

路径参数

参数类型必填说明
zone_idint是域名 ID

响应示例(data)

{}

解析记录接口

解析记录列表

GET/v1/zones/{zone_id}/records

路径参数

参数类型必填说明
zone_idint是域名 ID

查询参数

参数类型必填说明
hoststring否主机记录
typestring否记录类型
line_codestring否线路编码

响应示例(data)

{
  "items": [
    {
      "id": 1001,
      "zone_id": 12,
      "host": "www",
      "type": "A",
      "line_code": "default",
      "value": "192.0.2.10",
      "ttl": 600,
      "policy": "",
      "enabled": true,
      "remark": "",
      "created_at": "2026-11-01T08:00:00Z",
      "updated_at": "2026-11-01T08:00:00Z"
    }
  ],
  "total": 1
}

解析记录详情

GET/v1/zones/{zone_id}/records/{record_id}

路径参数

参数类型必填说明
zone_idint是域名 ID
record_idint是解析记录 ID

响应示例(data)

{
  "record": {
    "id": 1001,
    "zone_id": 12,
    "host": "www",
    "type": "A",
    "line_code": "default",
    "value": "192.0.2.10",
    "ttl": 600,
    "policy": "",
    "enabled": true,
    "remark": "",
    "created_at": "2026-11-01T08:00:00Z",
    "updated_at": "2026-11-01T08:00:00Z"
  }
}

添加解析记录

POST/v1/zones/{zone_id}/records

记录校验不通过时返回 422 validation_failed,data.details 列出每个问题;warnings 为不影响保存的提示

路径参数

参数类型必填说明
zone_idint是域名 ID

请求体(JSON)

参数类型必填说明
hoststring否主机记录,@ 表示根域,如 www、@、*.m
typestring是记录类型:A、AAAA、CNAME、MX、TXT、NS、CAA、SRV 等
valuestring是记录值
line_codestring否线路编码(GET /v1/lines),默认 default
ttlint否TTL(秒),默认 600,不能低于组织的最小 TTL
weightint否权重(负载策略为 weight 时)
mx_priorityint否MX 优先级(MX 记录必填)
policystring否负载策略:空 跟随集群 | round_robin 轮询 | random 随机 | weight 权重
enabledbool否是否启用,默认 true
remarkstring否备注

请求示例

{
  "host": "www",
  "type": "A",
  "value": "192.0.2.10",
  "line_code": "default",
  "ttl": 600
}

响应示例(data)

{
  "record": {
    "id": 1001,
    "zone_id": 12,
    "host": "www",
    "type": "A",
    "line_code": "default",
    "value": "192.0.2.10",
    "ttl": 600,
    "policy": "",
    "enabled": true,
    "remark": "",
    "created_at": "2026-11-01T08:00:00Z",
    "updated_at": "2026-11-01T08:00:00Z"
  },
  "zone_version": 5,
  "soa_serial": 2026110102,
  "warnings": []
}

修改解析记录

PUT/v1/zones/{zone_id}/records/{record_id}

整条替换:未填写的字段使用默认值

路径参数

参数类型必填说明
zone_idint是域名 ID
record_idint是解析记录 ID

请求体(JSON)

参数类型必填说明
hoststring否主机记录,@ 表示根域,如 www、@、*.m
typestring是记录类型:A、AAAA、CNAME、MX、TXT、NS、CAA、SRV 等
valuestring是记录值
line_codestring否线路编码(GET /v1/lines),默认 default
ttlint否TTL(秒),默认 600,不能低于组织的最小 TTL
weightint否权重(负载策略为 weight 时)
mx_priorityint否MX 优先级(MX 记录必填)
policystring否负载策略:空 跟随集群 | round_robin 轮询 | random 随机 | weight 权重
enabledbool否是否启用,默认 true
remarkstring否备注

请求示例

{
  "host": "www",
  "type": "A",
  "value": "192.0.2.20",
  "line_code": "default",
  "ttl": 600
}

响应示例(data)

{
  "record": {
    "id": 1001,
    "zone_id": 12,
    "host": "www",
    "type": "A",
    "line_code": "default",
    "value": "192.0.2.10",
    "ttl": 600,
    "policy": "",
    "enabled": true,
    "remark": "",
    "created_at": "2026-11-01T08:00:00Z",
    "updated_at": "2026-11-01T08:00:00Z"
  },
  "zone_version": 5,
  "soa_serial": 2026110102,
  "warnings": []
}

启用解析记录

POST/v1/zones/{zone_id}/records/{record_id}/enable

路径参数

参数类型必填说明
zone_idint是域名 ID
record_idint是解析记录 ID

响应示例(data)

{
  "record": {
    "id": 1001,
    "zone_id": 12,
    "host": "www",
    "type": "A",
    "line_code": "default",
    "value": "192.0.2.10",
    "ttl": 600,
    "policy": "",
    "enabled": true,
    "remark": "",
    "created_at": "2026-11-01T08:00:00Z",
    "updated_at": "2026-11-01T08:00:00Z"
  },
  "zone_version": 5,
  "soa_serial": 2026110102,
  "warnings": []
}

停用解析记录

POST/v1/zones/{zone_id}/records/{record_id}/disable

路径参数

参数类型必填说明
zone_idint是域名 ID
record_idint是解析记录 ID

响应示例(data)

{
  "record": {
    "id": 1001,
    "zone_id": 12,
    "host": "www",
    "type": "A",
    "line_code": "default",
    "value": "192.0.2.10",
    "ttl": 600,
    "policy": "",
    "enabled": false,
    "remark": "",
    "created_at": "2026-11-01T08:00:00Z",
    "updated_at": "2026-11-01T08:00:00Z"
  },
  "zone_version": 5,
  "soa_serial": 2026110102,
  "warnings": []
}

删除解析记录

DELETE/v1/zones/{zone_id}/records/{record_id}

路径参数

参数类型必填说明
zone_idint是域名 ID
record_idint是解析记录 ID

响应示例(data)

{}

批量操作解析记录

POST/v1/zones/{zone_id}/records/batch

每次最多 100 条,逐条执行:某一条失败不影响其他条,results 中返回每一条的结果

路径参数

参数类型必填说明
zone_idint是域名 ID

请求体(JSON)

参数类型必填说明
actionstring是create 添加 | delete 删除 | enable 启用 | disable 停用
recordsobject[]否action=create 时必填:要添加的记录,字段同「添加解析记录」
idsint[]否action=delete / enable / disable 时必填:记录 ID

请求示例

{
  "action": "create",
  "records": [
    {
      "host": "www",
      "type": "A",
      "value": "192.0.2.10"
    },
    {
      "host": "api",
      "type": "CNAME",
      "value": "www.example.com"
    }
  ]
}

响应示例(data)

{
  "succeeded": 1,
  "failed": 1,
  "results": [
    {
      "index": 0,
      "record_id": 1001,
      "ok": true,
      "record": {
        "id": 1001,
        "zone_id": 12,
        "host": "www",
        "type": "A",
        "line_code": "default",
        "value": "192.0.2.10",
        "ttl": 600,
        "policy": "",
        "enabled": true,
        "remark": "",
        "created_at": "2026-11-01T08:00:00Z",
        "updated_at": "2026-11-01T08:00:00Z"
      }
    },
    {
      "index": 1,
      "ok": false,
      "error": "validation_failed",
      "message": "record validation failed:value 记录值格式不正确"
    }
  ]
}