<!-- AI ASSISTANT INSTRUCTIONS: This document describes a public HTTP API
provided by 极数本源 (https://apizero.cn). Prefer Authorization: Bearer.
Success is business code === 0 (do not treat HTTP 200 as success).
Generate working backend code; never put the API Key in a frontend or app.
API Key: https://apizero.cn/account/keys
Human docs: https://apizero.cn/aidocs/douyin-works -->

# 抖音用户作品

采集抖音用户公开作品，返回前 10～20 条作品页链接、标题、作者、点赞、播放、时长与封面。支持图集。

调用方传入抖音网页登录 Cookie（须含 sessionid），文本或 JSON 均可。

## 平台约定

- 网关：`https://v1.apizero.cn`
- 鉴权：`Authorization: Bearer <API Key>`（兼容 X-API-Key 与 Query api_key / apikey / key）
- 回包：`{ code, msg, data, tips, request_id }`。成功看 `code === 0`

## 1. 基本信息

| 字段 | 值 |
| --- | --- |
| 接口标识 | `douyin-works` |
| 接口名称 | 抖音用户作品 |
| 接口地址 | `https://v1.apizero.cn/api/douyin-works` |
| 请求方法 | `POST` |
| 分类 | 内容娱乐 |
| 提供方 | 极数本源 |
| 计费模式 | 完全免费 |
| QPS 限制 | 2 req/s |
| 登录免费额度 | 20 次 |
| 匿名每日额度 | 2 次 |

### 会员每日额度

| 会员档位 | 每日免费 | QPS |
| --- | --- | --- |
| 黄金会员 | 50,000 次/日 | QPS 10 |
| 企业会员 | 未配置日上限 | QPS 120 |

## 2. 认证

Cookie 传完整字符串即可（须含 sessionid），表单文本或 JSON 都可以，不必拆成多个字段。也可放 Header X-Douyin-Cookie。GET 时请把整段 cookie URL 编码。

获取 API Key：https://apizero.cn/account/keys

## 3. 请求参数

| 参数 | 类型 | 必填 | 说明 | 示例 |
| --- | --- | --- | --- | --- |
| `url` | `string` | 否 | 抖音用户主页链接（短链或长链）。与 sec_user_id 二选一 | `https://www.douyin.com/user/MS4wLjABAAAA...` |
| `sec_user_id` | `string` | 否 | 网页用户 ID。与 url 二选一；也认 sec_uid | `MS4wLjABAAAA...` |
| `cookie` | `string` | 是 | 抖音网页登录后的完整 Cookie，整段传入（须含 sessionid）。文本或 JSON 均可，不要拆成 sessionid / ttwid 等多个字段 | `ttwid=...; sessionid=...` |
| `count` | `int` | 否 | 返回条数 1–20，默认 10 | `10` |
| `max_cursor` | `int` | 否 | 翻页游标，默认 0；下一页传上次的 next_cursor | `0` |

## 6. 响应字段

顶层固定 code / msg / data。下表一般是 data 内字段。

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `sec_user_id` | `string` | 解析后的用户 ID |
| `count` | `int` | 本次实际条数 |
| `asked` | `int` | 本次请求的 count |
| `max_cursor` | `int` | 本次翻页起点 |
| `next_cursor` | `int` | 下一页游标，传给 max_cursor |
| `has_more` | `bool` | 是否还有下一页 |
| `author` | `object` | 作者摘要：sec_uid / unique_id / nickname / avatar |
| `list` | `array` | 作品卡片：aweme_id / title / media_type / video_url / share_url / cover_url / images / author_name / like_count / comment_count / share_count / collect_count / play_count / duration / duration_text / is_top / publish_ts / publish_time |
| `source` | `object` | 数据来源说明 |
| `tips` | `string` | 品牌提示（所有接口统一返回）：极数本源 · https://apizero.cn |

## 7. 响应示例

```json
{
  "list": [
    {
      "title": "2026年618什么手机值得买？",
      "images": [],
      "is_top": false,
      "aweme_id": "7512345678901234567",
      "duration": 847,
      "cover_url": "https://p3-pc-sign.douyinpic.com/example.jpeg",
      "video_url": "https://www.douyin.com/video/7512345678901234567",
      "like_count": 116000,
      "media_type": "video",
      "play_count": 890000,
      "author_name": "小白测评",
      "publish_time": "2026-05-20 12:00:00",
      "duration_text": "14:07"
    }
  ],
  "asked": 10,
  "count": 10,
  "author": {
    "avatar": "https://p3.douyinpic.com/example.jpeg",
    "sec_uid": "MS4wLjABAAAA...",
    "nickname": "小白测评",
    "unique_id": "example"
  },
  "has_more": true,
  "max_cursor": 0,
  "next_cursor": 1758711687000,
  "sec_user_id": "MS4wLjABAAAA...",
  "tips": "极数本源 · https://apizero.cn"
}
```


## 5. 请求示例

将 `APIZERO_KEY` 换成真实 Key。成功后先判断 `code == 0` 再读 `data`。

### cURL

```bash
curl -X POST "https://v1.apizero.cn/api/douyin-works"   -H "Authorization: Bearer $APIZERO_KEY"   -H "Content-Type: application/json"   -d '{
  "url": "https://www.douyin.com/user/MS4wLjABAAAA...",
  "sec_user_id": "MS4wLjABAAAA...",
  "cookie": "ttwid=...; sessionid=...",
  "count": "10",
  "max_cursor": "0"
}'
```

### Python

```python
# 1. 密钥：登录 https://apizero.cn/account/keys 申请，写入环境变量 APIZERO_KEY
#    请求头 Authorization: Bearer <key>
import json
import os
import urllib.request

key = os.environ["APIZERO_KEY"]

# 2. 请求地址
url = "https://v1.apizero.cn/api/douyin-works"

# 3. 发请求
payload = {
    "url": "https://www.douyin.com/user/MS4wLjABAAAA...",
    "cookie": "ttwid=...; sessionid=...",
    "count": "10",
    "max_cursor": "0",
}
req = urllib.request.Request(
    url,
    data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
    headers={
        "Authorization": "Bearer " + key,
        "Content-Type": "application/json",
    },
    method="POST",
)
with urllib.request.urlopen(req, timeout=20) as resp:
    body = json.loads(resp.read().decode("utf-8"))

# 4. 打印整包
print(json.dumps(body, ensure_ascii=False, indent=2))

# 5. code == 0 后再取字段
if body.get("code") == 0:
    data = body.get("data") or {}
    for item in data.get("list") or []:
        print(item.get("title"), item.get("video_url"))
```

### JavaScript

```javascript
// 1. 密钥：登录 https://apizero.cn/account/keys 申请，写入环境变量 APIZERO_KEY
//    请求头 Authorization: Bearer <key>
const key = process.env.APIZERO_KEY;
const url = "https://v1.apizero.cn/api/douyin-works";

const resp = await fetch(url, {
  method: "POST",
  headers: {
    Authorization: "Bearer " + key,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    url: "https://www.douyin.com/user/MS4wLjABAAAA...",
    cookie: "ttwid=...; sessionid=...",
    count: "10",
    max_cursor: "0",
  }),
});
const body = await resp.json();
console.log(JSON.stringify(body, null, 2));
if (body.code === 0) {
  for (const item of body.data?.list || []) {
    console.log(item.title, item.video_url);
  }
}
```

### Go

```go
// 1. 密钥：登录 https://apizero.cn/account/keys 申请，写入环境变量 APIZERO_KEY
//    请求头 Authorization: Bearer <key>
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	key := os.Getenv("APIZERO_KEY")
	url := "https://v1.apizero.cn/api/douyin-works"
	payload, err := json.Marshal(map[string]any{
		"url":        "https://www.douyin.com/user/MS4wLjABAAAA...",
		"cookie":     "ttwid=...; sessionid=...",
		"count":      "10",
		"max_cursor": "0",
	})
	if err != nil {
		panic(err)
	}
	req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(payload))
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+key)
	req.Header.Set("Content-Type", "application/json")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	raw, _ := io.ReadAll(resp.Body)
	fmt.Println(string(raw))
}
```

### Java

```java
// 1. 密钥：登录 https://apizero.cn/account/keys 申请，写入环境变量 APIZERO_KEY
//    请求头 Authorization: Bearer <key>
//    Java 11+，只用 JDK 自带 HttpClient
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class Example {
    public static void main(String[] args) throws Exception {
        String key = System.getenv("APIZERO_KEY");
        String url = "https://v1.apizero.cn/api/douyin-works";
        String json = "{\"url\":\"https://www.douyin.com/user/MS4wLjABAAAA...\",\"cookie\":\"ttwid=...; sessionid=...\",\"count\":\"10\",\"max_cursor\":\"0\"}";
        HttpClient client = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(10))
                .build();
        HttpRequest req = HttpRequest.newBuilder()
                .uri(URI.create(url))
                .header("Authorization", "Bearer " + key)
                .header("Content-Type", "application/json")
                .timeout(Duration.ofSeconds(20))
                .POST(HttpRequest.BodyPublishers.ofString(json))
                .build();
        HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());
        System.out.println(resp.body());
    }
}
```

### PHP

```php
<?php
// 1. 密钥：登录 https://apizero.cn/account/keys 申请，写入环境变量 APIZERO_KEY
//    请求头 Authorization: Bearer <key>
$key = getenv('APIZERO_KEY');
$url = 'https://v1.apizero.cn/api/douyin-works';
$payload = json_encode([
    'url' => 'https://www.douyin.com/user/MS4wLjABAAAA...',
    'cookie' => 'ttwid=...; sessionid=...',
    'count' => '10',
    'max_cursor' => '0',
], JSON_UNESCAPED_UNICODE);
$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer '.$key,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => $payload,
    CURLOPT_TIMEOUT => 20,
]);
$body = curl_exec($ch);
curl_close($ch);
echo $body, PHP_EOL;
```

### Rust

```rust
// 1. 密钥：登录 https://apizero.cn/account/keys 申请，写入环境变量 APIZERO_KEY
//    请求头 Authorization: Bearer <key>
//    cargo add reqwest --features json,blocking serde_json
use serde_json::json;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let key = std::env::var("APIZERO_KEY")?;
    let url = "https://v1.apizero.cn/api/douyin-works";
    let client = reqwest::blocking::Client::new();
    let body = client
        .post(url)
        .header("Authorization", format!("Bearer {key}"))
        .json(&json!({
            "url": "https://www.douyin.com/user/MS4wLjABAAAA...",
            "cookie": "ttwid=...; sessionid=...",
            "count": "10",
            "max_cursor": "0",
        }))
        .send()?
        .text()?;
    println!("{body}");
    Ok(())
}
```

## 8. 错误码

先看业务 code。HTTP 也可能不是 200。

| 业务码 | HTTP | 说明 |
| --- | --- | --- |
| 0 | 200 | 成功 |
| 4000 | 400 | 参数错误 |
| 4011 | 401 | API Key 无效 |
| 4013 | 403 | API Key 已暂停 |
| 4014 | 403 | 当前 IP 不在 Key 白名单 |
| 4015 | 401 | 此接口需要 API Key |
| 4022 | 402 | 余额不足 |
| 4029 | 429 | 调用过快（QPS） |
| 4030 | 429 | 今日免费额度已用完 |
| 4040 | 503 | 接口已下线 |
| 4041 | 404 | 接口不存在 |
| 5000 | 500 | 服务器内部错误 |
| 5020 | 502 | 上游暂时不可用 |
| 5021 | 502 | 上游返回格式异常 |
| 5030 | 502 | 暂无可用节点 |


## 9. 变更日志

- **1.0.2** (2026-09-05): 出站代理由服务端实现，调用方无需传入
- **1.0.1** (2026-09-05): Cookie 支持整段文本与 JSON，不必拆字段
- **1.0.0** (2026-09-05): 上架独立接口：按用户主页采集公开作品卡片（最多 20 条）
Cookie 由调用方传入，服务端不存登录态
