CITV 开放文档
Domainer

SKILL.md - AI 接入

Domainer Platform Customer API 的 AI 集成 SKILL 文档,供 Claude Code / Codex 加载使用

SKILL.md - AI 接入

将下方代码块整体另存为 SKILL.md,放到 Claude Code 或 Codex 的 skills 目录中即可加载:

工具安装路径
Claude Code~/.claude/skills/domainer-platform-api/SKILL.md 或 <project>/.claude/skills/domainer-platform-api/SKILL.md
OpenAI Codex~/.codex/skills/domainer-platform-api/SKILL.md 或 <repo>/.codex/skills/domainer-platform-api/SKILL.md

加载后,Agent 在涉及"接入 Domainer / 商户实名 / 子域名 / CNAME / DNS 解析明细 / 平台配额"等场景时会自动激活该技能。

---
name: domainer-platform-api
description: 接入 Domainer 平台 API,管理商户实名、子域名开通、CNAME 切换、平台配额查询与 DNS 解析明细下载;使用 STS 短期令牌鉴权
---

# domainer Platform Customer API — AI Integration Skill

> Server-to-Server (S2S) API for platform-class customers to manage merchants, subdomains, and DNS records on the `domainer` platform.
>
> **API version:** v1.5.0
> **Base URL:** configured per environment (example: `https://portal.citv.cc`)
> **Transport:** HTTPS only, JSON request/response
> **Audience:** AI agents and backend services that integrate with the domainer platform on behalf of a platform tenant.

---

## 1. Authentication

The API uses a two-step Short-Term Token (STS) model:

1. **Exchange** your long-lived API Key for a short-lived STS token via `POST /api/auth/api-token`.
2. **Call** all `/api/v1/*` endpoints with `Authorization: Bearer <STS token>`.

STS tokens are HMAC-SHA256-signed lightweight JWTs containing:

- `sub` — userId
- `akid` — accessKeyId
- `scope` — array of granted scopes
- `platformCompanyId` / `platformCode` — bound platform context
- `iat` / `exp` — issued/expiry (Unix seconds)
- `jti` — unique token id

### 1.1 Exchange a token

```
POST /api/auth/api-token
Headers:
  x-api-key: <AccessKeyId>:<SecretAccessKey>
  Content-Type: application/json
Body (optional):
  {
    "scope": ["merchant:read", "domain:read"],
    "ttlSeconds": 1800,
    "platformCompanyId": "67c93f..."   // recommended when user is bound to multiple platforms
  }
```

Response:

```json
{
  "code": 200,
  "data": {
    "token": "eyJhbGciOi...",
    "expiresAt": 1732173600,
    "scope": ["merchant:read", "domain:read"],
    "platformCompanyId": "67c93f...",
    "platformCode": "tenant-a"
  },
  "message": "OK"
}
```

Notes:

- `ttlSeconds` is clamped to `[30, API_TOKEN_TTL_MAX_SECONDS]` (default max `3600`).
- `scope` must be a non-empty array subset of the allowed scope whitelist. If omitted, defaults to the Access Key's configured scopes (or the full whitelist if none configured).
- If the user has multiple active platforms, always pass `platformCompanyId` to disambiguate.
- `platformCompanyId` and `platformCode` (if both passed) must point to the same platform.

### 1.2 Allowed scopes (whitelist)

| Scope | Purpose |
| --- | --- |
| `company:list` | Auth check (`/api/v1/auth-check`) |
| `merchant:register` | Create a merchant identity record |
| `merchant:read` | Read merchant details and merchant domains |
| `merchant:update` | Update merchant identity record |
| `merchant:delete` | Delete a merchant |
| `domain:create` | Create subdomains (merchant-bound or by `platformDomain`) |
| `domain:read` | Read domain details, merchant domain list, platform quota snapshot |
| `domain:update` | Update domain CNAME |
| `domain:delete` | Delete merchant subdomains |
| `dns-records:read` | Read daily DNS resolution records / OSS download URLs |

**Principle of least privilege:** request only the minimum scopes your agent needs.

---

## 2. Endpoint reference

All endpoints are prefixed with `/api/v1`. The `domain` path parameter is the full FQDN, e.g. `shop01.platform.citv.cc`.

### 2.1 Auth check

| Method | Path | Scope |
| --- | --- | --- |
| `GET` | `/api/v1/auth-check` | `company:list` |

Returns the resolved `userId`, `accessKeyId`, granted `scope`, token `expiresAt`, and server time. Use this as a connectivity / scope sanity check.

### 2.2 Merchant identity records

| Method | Path | Scope | Notes |
| --- | --- | --- | --- |
| `POST` | `/api/v1/merchants/register` | `merchant:register` | Triggers enterprise 4-element verification (Aliyun Cloudauth) |
| `GET` | `/api/v1/merchants/{merchantId}` | `merchant:read` | Includes `domains[]` summary |
| `PUT` | `/api/v1/merchants/{merchantId}` | `merchant:update` | Partial update; `licenseNumber` is **immutable** |
| `DELETE` | `/api/v1/merchants/{merchantId}` | `merchant:delete` | Does **not** cascade-delete domains |

**Register request body** (all listed fields are required unless marked optional):

```json
{
  "merchantId": "EXT-998877",
  "name": "示例科技有限公司",
  "licenseNumber": "9132XXXXXXXXXXXXX",
  "licenseImageUrl": "https://cdn.example.com/licenses/9132XX.png",
  "legalRepName": "张三",
  "legalRepIdNumber": "1101XXXXXXXXXXXXXX",
  "bankName": "中国银行某支行",
  "bankAccountNumber": "622202XXXXXXXXXXXX",
  "publicInfoUrl": "https://example.com/merchant/EXT-998877",
  "userAuthorization": "1",
  "contactName": "李四",
  "contactPhone": "13800138000",
  "contactEmail": "contact@example.com",
  "address": "北京市海淀区中关村大街1号",
  "description": "主营 SaaS 服务",
  "logoUrl": "https://cdn.example.com/logo.png",
  "websiteUrl": "https://merchant.example.com"
}
```

**Enterprise status codes** (returned in verification diagnostics):

| Code | Meaning |
| --- | --- |
| `1` | In business (operating) |
| `2` | Moved out |
| `3` | Deregistered |
| `4` | Revoked |
| `5` | Cancelled |
| `6` | Suspended |
| `0` | Other |

### 2.3 Merchant-scoped subdomain management

Use these when the agent is acting on behalf of a specific merchant.

| Method | Path | Scope |
| --- | --- | --- |
| `GET` | `/api/v1/merchants/{merchantId}/domains` | `merchant:read` |
| `POST` | `/api/v1/merchants/{merchantId}/domains` | `domain:create` |
| `GET` | `/api/v1/merchants/{merchantId}/domains/{domain}` | `merchant:read` |
| `PUT` | `/api/v1/merchants/{merchantId}/domains/{domain}` | `domain:update` |
| `DELETE` | `/api/v1/merchants/{merchantId}/domains/{domain}` | `domain:delete` |

`POST` request body:

```json
{
  "rootDomain": "citv.cc",            // base root domain, or pass "platform.citv.cc" to disambiguate
  "cnameValue": "target.example.net",
  "subdomain": "shop01",              // optional, single label only; auto-generated if omitted
  "ttl": 300                          // optional, positive integer
}
```

The platform enforces a 3-level final domain: `<merchantPrefix>.<platformLabel>.<baseRoot>`. Subdomain must be a single label (no dots) and must not include the platform label.

Domain creation requires the merchant to be enterprise-verified (`enterpriseVerified=true`, `enterpriseStatus="1"`). If not, the API returns `code=403`.

### 2.4 Generic (flat) domain endpoints

| Method | Path | Scope | Notes |
| --- | --- | --- | --- |
| `POST` | `/api/v1/domains/cname` | `domain:create` | Create by `platformDomain` |
| `GET` | `/api/v1/domains/quota` | `domain:read` | Platform quota snapshot |
| `GET` | `/api/v1/domains/{domain}` | `domain:read` | Visibility via `companyId` only |
| `PUT` | `/api/v1/domains/{domain}/cname` | `domain:update` | Also allows `platformId` match |
| `DELETE` | `/api/v1/domains/{domain}` | `domain:delete` | **Removed in v1.5.0** → returns `code=410` |

`POST /api/v1/domains/cname` body:

```json
{
  "merchantId": "EXT-998877",
  "platformDomain": "platform.citv.cc",
  "subdomain": "shop01",              // optional
  "cnameValue": "target.example.net"
}
```

`PUT /api/v1/domains/{domain}/cname` body:

```json
{
  "domainName": "shop01.platform.citv.cc",   // must match the path
  "cnameValue": "new.example.net"
}
```

### 2.5 Platform quota snapshot

```
GET /api/v1/domains/quota
Scope: domain:read
```

Returns the quota of the platform bound to the current STS token (no query parameters). Counts only `PENDING`, `ACTIVE`, `SUSPENDED` domains toward usage; soft-deleted and `REJECTED` domains are excluded.

```json
{
  "code": 200,
  "data": {
    "platformCompanyId": "67c7...",
    "platformCode": "demo",
    "allowWildcardDomains": false,
    "allowedRootDomains": ["citv.cc"],
    "platformDomains":   { "used": 3, "limit": 5,  "remaining": 2,  "unlimited": false, "exceeded": false },
    "merchantDomains":   { "used": 12, "limit": 30, "remaining": 18, "unlimited": false, "exceeded": false },
    "singleMerchantDomainLimit": { "limit": 5, "unlimited": false },
    "rootDomains": [
      {
        "rootDomain": "citv.cc",
        "platformDomains": { "used": 1, "limit": 2,  "remaining": 1, "unlimited": false, "exceeded": false },
        "merchantDomains": { "used": 6, "limit": 20, "remaining": 14, "unlimited": false, "exceeded": false }
      }
    ]
  },
  "message": "OK"
}
```

Field semantics:

- `platformDomains` — platform-owned second-level domains.
- `merchantDomains` — total merchant subdomains under the platform.
- `singleMerchantDomainLimit` — per-merchant cap (config value only, no per-merchant usage).
- `rootDomains` — per-root breakdown; normalized to the base root domain (e.g. `citv.cc`).
- `limit=null` ⇒ unbounded; `remaining=null`, `unlimited=true`.
- `exceeded=true` ⇒ used > limit (used == limit is **not** exceeded).

### 2.6 DNS resolution records

```
GET /api/v1/dns-oss-records/daily
Scope: dns-records:read
Query (one of):
  date=YYYY-MM-DD                         // single day
  fromDate=YYYY-MM-DD&toDate=YYYY-MM-DD   // range, [from, to), max 31 days
```

Single-day response:

```json
{
  "code": 200,
  "data": {
    "platformId": "platform_001",
    "date": "2026-05-01",
    "status": "complete",
    "format": "ndjson",
    "compression": "zstd",
    "recordCount": 12345,
    "partCount": 1,
    "totalSizeBytes": 1048576,
    "expiresAt": "2026-05-02T00:00:00Z",
    "parts": [
      {
        "partNo": 1,
        "fileName": "records-000001.ndjson.zst",
        "sizeBytes": 1048576,
        "sha256": "hex_sha256",
        "recordCount": 12345,
        "downloadUrl": "https://domaineross.citv.cn/...?Expires=..."
      }
    ]
  },
  "message": "OK"
}
```

Range response wraps per-day results in `data.items[]`. Days without generated files are skipped in range mode; in single-day mode a missing file returns `code=404`.

---

## 3. Response and error handling

### 3.1 Unified envelope

```json
{ "code": 200, "data": {}, "message": "OK" }
```

`code` is the business status. **Always branch on `code`, not on HTTP status.** Many business errors return `HTTP 200` with a non-200 `code`.

HTTP vs `code` mapping in current implementation:

| Condition | HTTP | `code` |
| --- | --- | --- |
| Auth failure (no/invalid Bearer) | `401` | `401` |
| Rate limit exceeded | `429` | `429` |
| `GET /api/v1/auth-check` missing `company:list` | `403` | `403` |
| Most other business errors (validation, not-found, conflict, scope-missing on write paths, internal) | `200` | `400` / `403` / `404` / `409` / `500` |
| `GET /api/v1/dns-oss-records/daily` errors | matches `code` | `400` / `403` / `404` / `500` |
| `DELETE /api/v1/domains/{domain}` (removed) | `200` | `410` |

### 3.2 Common `code` values to handle

- `400` — validation error, parameter missing, quota exceeded, domain not in enabled roots, "当前账户未关联可用公司" (no associated platform), "rootDomain 不在启用列表".
- `403` — scope missing (e.g. `权限不足:缺少 merchant:register`), merchant not verified, business status abnormal (`商户经营状态异常:<code>(<text>)`), `userAuthorization="0"`.
- `404` — resource not found, `解析明细文件未生成`, `平台主体不存在`.
- `409` — `merchantId` / `name` / `licenseNumber` already exists.
- `410` — `DELETE /api/v1/domains/{domain}` removed; migrate to the merchant-scoped endpoint.
- `429` — rate limit; back off and retry.
- `500` — internal error; safe to retry with idempotency guards.

### 3.3 Rate limits

All v1 routes are rate-limited server-side. Write endpoints are stricter than reads:

- Reads: `ipLimit: 60` per `60s` window.
- Writes: `ipLimit: 20` per `60s` window.
- `dns-oss-records/daily`: `sessionLimit: 30`, `ipLimit: 60`.
- `api-token` exchange: `60` per `120s` per session/IP.

When you receive `429`, back off exponentially before retrying.

---

## 4. Field conventions

- `merchantId` — customer-defined merchant ID; unique per platform.
- `domain` — full FQDN, e.g. `shop01.platform.citv.cc`.
- `platformDomain` — platform's second-level domain, e.g. `platform.citv.cc`.
- `rootDomain` — enabled base root, e.g. `citv.cc`; the platform-domain form is also accepted on some endpoints.
- `subdomain` — single DNS label only; never include the platform label (no dots).
- `userAuthorization` — `"1"` (granted) or `"0"` (denied); numeric `1`/`0` is also tolerated.
- `cnameValue` — the target hostname the subdomain points to.
- `ttl` — DNS TTL in seconds (positive integer, optional).

The final third-level domain is always `<merchantPrefix>.<platformLabel>.<baseRoot>`.

---

## 5. AI agent integration patterns

### 5.1 Token caching

Keep one STS token in memory per platform context. Reuse it across calls until it expires (do not refresh preemptively). Re-exchange only on `401` or within ~60 seconds of `expiresAt`. The new token does **not** invalidate the old one — both remain valid until their respective `exp`.

### 5.2 Required skill: the merchant lifecycle

When asked to onboard a merchant and provision their subdomain:

1. `POST /api/v1/merchants/register` with full identity fields and `userAuthorization="1"`. The server runs enterprise 4-element verification and only succeeds if `resultBizCode="1"` and `status="1"`.
2. `GET /api/v1/merchants/{merchantId}` to confirm `enterpriseVerified` and `enterpriseStatus` before creating subdomains.
3. `POST /api/v1/merchants/{merchantId}/domains` (or `POST /api/v1/domains/cname`) to create the subdomain with the requested `cnameValue`.
4. `GET /api/v1/domains/{domain}` (or merchant-scoped variant) to confirm the DNS record.

### 5.3 Required skill: pre-flight quota check

Before creating subdomains:

1. `GET /api/v1/domains/quota` to read `merchantDomains.remaining` and per-root `merchantDomains.remaining`.
2. Refuse the request (or warn the user) if the relevant `remaining` is `0`, `null` while `unlimited=false`, or `exceeded=true`.
3. Choose the correct `rootDomain` from `allowedRootDomains` and the response.

### 5.4 Required skill: DNS records download

To pull resolution records for a given day:

1. `GET /api/v1/dns-oss-records/daily?date=YYYY-MM-DD`.
2. For each `parts[]` entry, `GET` the `downloadUrl` (these are pre-signed OSS URLs with `Expires`).
3. For ranges > 31 days, chunk requests in 31-day windows; for each day, fall back to the single-day call.

### 5.5 Idempotency and retry

- All write operations should be guarded by client-side idempotency. The server does not de-duplicate by request body.
- Retry on `429` with exponential backoff.
- Do not blindly retry on `code != 200` even if HTTP is `200`. Read the `message` to decide.
- `PUT /api/v1/merchants/{merchantId}` triggers re-verification after the update, but its result does not block the response (`updated: true` does not imply re-verification succeeded).
- `POST /api/v1/merchants/register` already retries transient verification failures up to 3 times server-side; do not re-issue concurrent requests in tight loops.

### 5.6 Audit-friendly logging

For every call, log:

- timestamp (UTC ISO 8601)
- endpoint and HTTP method
- `merchantId` / `domain` involved
- STS-bound access key id (not the token)
- response `code` and `message`
- request id from response header (if present)

Never log the STS token, the API Key secret, raw license numbers, ID numbers, or bank account numbers.

### 5.7 Scope selection guidance

| Agent capability | Recommended scopes |
| --- | --- |
| Read-only reporting | `domain:read`, `merchant:read`, `dns-records:read` |
| Onboarding new merchants | `merchant:register`, `merchant:read`, `domain:create`, `domain:read` |
| Provision and rotate subdomains | `domain:create`, `domain:read`, `domain:update`, `domain:delete` |
| Merchant lifecycle management | `merchant:read`, `merchant:update`, `merchant:delete` |
| Connectivity probe | `company:list` |

---

## 6. Quick reference

```
# 1. Exchange STS
POST /api/auth/api-token
Headers: x-api-key: <AccessKeyId>:<SecretAccessKey>
Body:    { "scope": ["merchant:read", "domain:read", "domain:create"], "ttlSeconds": 1800 }

# 2. Probe
GET  /api/v1/auth-check
Header: Authorization: Bearer <STS>

# 3. Onboard
POST /api/v1/merchants/register         (scope: merchant:register)
GET  /api/v1/merchants/{merchantId}     (scope: merchant:read)

# 4. Provision subdomain
GET  /api/v1/domains/quota              (scope: domain:read)
POST /api/v1/merchants/{merchantId}/domains   (scope: domain:create)
#   or
POST /api/v1/domains/cname              (scope: domain:create)

# 5. Operate
PUT  /api/v1/merchants/{merchantId}/domains/{domain}  (scope: domain:update)
DELETE /api/v1/merchants/{merchantId}/domains/{domain} (scope: domain:delete)

# 6. Telemetry
GET  /api/v1/dns-oss-records/daily?date=YYYY-MM-DD   (scope: dns-records:read)
```

---

## 7. Hard rules for AI agents

- **Always check the response body's `code` field**, not the HTTP status, to determine success.
- **Never call `DELETE /api/v1/domains/{domain}`** — it returns `code=410` and is permanently removed. Use `DELETE /api/v1/merchants/{merchantId}/domains/{domain}`.
- **Never modify `licenseNumber`** in `PUT /api/v1/merchants/{merchantId}` — the server rejects the request with `code=400`.
- **Never create a subdomain for a merchant that is not enterprise-verified.** Verify first with `GET /api/v1/merchants/{merchantId}` and check `enterpriseVerified=true`, `enterpriseStatus="1"`.
- **Always quote the `code` and `message` back to the user** when a call fails — the message is human-readable in Chinese and is the only diagnostic.
- **Never log or echo the STS token, the API Key secret, or sensitive PII** (license number, ID number, bank account). Mask in any display.
- **Re-exchange the STS on `401`** or when within 60 seconds of `expiresAt`; otherwise reuse the cached token.
- **Back off on `429`** with exponential delay; do not retry in tight loops.

加载与触发

将上方代码块另存为 SKILL.md 并放到对应目录后:

Claude Code

  • 启动或重启会话后,Agent 会自动扫描 ~/.claude/skills/ 与 .claude/skills/
  • 用自然语言触发,例如:"帮客户 EXT-998877 在 platform.citv.cc 下开通一个 shop01 子域名,指向 target.example.net"
  • 也可手动调用:/domainer-platform-api

OpenAI Codex

  • 启动 Codex CLI,启动时扫描 ~/.codex/skills/ 与仓库内 .codex/skills/
  • 在提示词中描述任务,Codex 会基于 description 自动判断是否加载本技能

验证清单

  • SKILL.md 已放到对应 skills 目录
  • 目录名与 frontmatter 的 name 字段一致(domainer-platform-api)
  • API Key 已就绪,格式 <AccessKeyId>:<SecretAccessKey>
  • Base URL 与目标环境一致(beta / prod)
  • 已规划本次 Agent 所需的最小 scope 集合(参见 SKILL §1.2 / §5.7)

On this page