PAYATHON 2026

YouTube subscription API POST 请求 Content-Type 错误

数据小陈

结论

普通的 HTML <form method="post"> 无法发送这类请求。浏览器会将表单编码为 application/x-www-form-urlencoded 或 multipart/form-data,但旧版 YouTube Data API v2 要求请求正文使用 Atom XML,内容类型为:

Content-Type: application/atom+xml

这类请求需要通过服务端代码、curl,或者支持自定义请求头和原始请求正文的 HTTP 客户端发送。

问题中提到的 gdata.youtube.com、Atom XML 和 GData-Version: 2 都属于已经停用的 YouTube Data API v2。下面的 XML 示例仅用于说明旧接口的请求方式,不能保证现在仍可调用。新项目应改用 YouTube Data API v3 的 subscriptions.insert 方法。

为什么 HTML 表单会报错

普通表单提交的内容通常如下:

Content-Type: application/x-www-form-urlencoded

username=GoogleDevelopers

接口要求的却是 XML 正文:

Content-Type: application/atom+xml

<?xml version="1.0" encoding="UTF-8"?>
<entry ...>
    ...
</entry>

即使为 <form> 设置 enctype,标准 HTML 表单也不能直接生成 application/atom+xml 请求。enctype 常见的可用类型只有:

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

因此,需要直接构造 HTTP 请求。

旧版 API 的 curl 写法

如果使用的是旧接口仍然有效的环境,可以这样发送请求:

curl --request POST \
  'https://gdata.youtube.com/feeds/api/users/default/subscriptions' \
  --header 'Content-Type: application/atom+xml; charset=UTF-8' \
  --header 'Authorization: Bearer ACCESS_TOKEN' \
  --header 'GData-Version: 2' \
  --header 'X-GData-Key: key=DEVELOPER_KEY' \
  --data-binary '<?xml version="1.0" encoding="UTF-8"?>
<entry xmlns="http://www.w3.org/2005/Atom"
       xmlns:yt="http://gdata.youtube.com/schemas/2007">
  <category scheme="http://gdata.youtube.com/schemas/2007/subscriptiontypes.cat"
            term="channel"/>
  <yt:username>GoogleDevelopers</yt:username>
</entry>'

其中:

  • 将 ACCESS_TOKEN 替换为经过授权的 OAuth 访问令牌。
  • 将 DEVELOPER_KEY 替换为开发者密钥。
  • 将 GoogleDevelopers 替换为目标频道或用户。
  • --data-binary 会将 XML 原样作为请求正文发送。
  • curl 通常会自动计算 Content-Length,不需要手动设置。

PHP 示例

<?php

$url = 'https://gdata.youtube.com/feeds/api/users/default/subscriptions';
$accessToken = 'ACCESS_TOKEN';
$developerKey = 'DEVELOPER_KEY';

$xml = <<<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<entry xmlns="http://www.w3.org/2005/Atom"
       xmlns:yt="http://gdata.youtube.com/schemas/2007">
  <category scheme="http://gdata.youtube.com/schemas/2007/subscriptiontypes.cat"
            term="channel"/>
  <yt:username>GoogleDevelopers</yt:username>
</entry>
XML;

$curl = curl_init($url);

curl_setopt_array($curl, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $xml,
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/atom+xml; charset=UTF-8',
        'Authorization: Bearer ' . $accessToken,
        'GData-Version: 2',
        'X-GData-Key: key=' . $developerKey,
    ],
    CURLOPT_RETURNTRANSFER => true,
]);

$response = curl_exec($curl);
$statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);

if ($response === false) {
    throw new RuntimeException(curl_error($curl));
}

curl_close($curl);

echo "HTTP status: {$statusCode}\n";
echo $response;

这里要将 XML 字符串直接赋给 CURLOPT_POSTFIELDS,不要包装成 PHP 数组。传入数组后,cURL 会改用表单或 multipart/form-data 编码。

JavaScript fetch 示例

fetch 在技术上可以直接发送 XML:

const xml = `<?xml version="1.0" encoding="UTF-8"?>
<entry xmlns="http://www.w3.org/2005/Atom"
       xmlns:yt="http://gdata.youtube.com/schemas/2007">
  <category scheme="http://gdata.youtube.com/schemas/2007/subscriptiontypes.cat"
            term="channel"/>
  <yt:username>GoogleDevelopers</yt:username>
</entry>`;

const response = await fetch(
  "https://gdata.youtube.com/feeds/api/users/default/subscriptions",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/atom+xml; charset=UTF-8",
      "Authorization": "Bearer ACCESS_TOKEN",
      "GData-Version": "2",
      "X-GData-Key": "key=DEVELOPER_KEY"
    },
    body: xml
  }
);

const responseBody = await response.text();

if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${responseBody}`);
}

console.log(responseBody);

但从浏览器直接调用时,还可能遇到 CORS 限制。需要保密的凭据也不应写进公开的前端 JavaScript。实际项目通常由后端完成 OAuth 流程并调用 API。

当前应使用 YouTube Data API v3

新项目应发送以下请求:

POST https://www.googleapis.com/youtube/v3/subscriptions?part=snippet
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json

请求正文使用 JSON,通过频道 ID 指定要订阅的频道:

{
  "snippet": {
    "resourceId": {
      "kind": "youtube#channel",
      "channelId": "TARGET_CHANNEL_ID"
    }
  }
}

对应的 curl 示例:

curl --request POST \
  'https://www.googleapis.com/youtube/v3/subscriptions?part=snippet' \
  --header 'Authorization: Bearer ACCESS_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "snippet": {
      "resourceId": {
        "kind": "youtube#channel",
        "channelId": "TARGET_CHANNEL_ID"
      }
    }
  }'

访问令牌必须属于实际执行订阅操作的 YouTube 用户,并包含当前接口要求的 OAuth scope。仅使用 API key 不能代表用户创建订阅。

注意事项

  • 不要手动填写 Content-Length,HTTP 客户端通常会自动计算。
  • XML 中的命名空间、元素名称和大小写必须准确。
  • Content-Type 只用于声明正文格式。仅修改表单数据的请求头,并不会将表单数据自动转换成 XML。
  • OAuth access token 不应出现在 URL、日志或公开源码中。
  • 如果继续请求旧的 gdata.youtube.com 地址,即使 XML 和请求头正确,也可能因为 API v2 已停用而失败。
  • 开发当前系统时,应以 YouTube Data API v3 的 subscriptions.insert 文档和现行 OAuth 要求为准。

备注:内容仅供参考。