文件POST
上传少数型号所需的输入文件(文档问答、音色复刻参考音频、长文本语音),获得平台文件 ID 后在模型调用中引用。
POST https://api.tokentosea.com/v1/files
GET https://api.tokentosea.com/v1/files/{id}
GET https://api.tokentosea.com/v1/files/{id}/content
平台代你把文件上传到厂商,返回平台文件 ID file_tts_…。之后在模型调用中引用这个 ID,平台负责换成厂商侧的 ID;你不能直接使用厂商的文件 ID。上传本身不扣费。
前提:全能密钥或模型密钥,且密钥的调用范围包含对应型号。Python 示例需要 pip install requests openai,Node.js 示例需要 Node.js 18+ 与 npm install openai。
文件与密钥绑定
文件只能被上传它的那把密钥查询和引用。换一把密钥(即使属于同一账号)会得到 404 resource_not_found。
支持的型号与用途#
model | purpose | 之后如何使用 |
|---|---|---|
qwen-doc-turbo | file-extract | 在 Chat Completions 的 system 消息中写 fileid://file_tts_…,以流式调用文档问答 |
glm-tts-clone | voice-clone-input | 作为 /v1/tools/invoke 的 file_id,复刻音色 |
speech-2.8-hd | t2a_async_input | 作为 /v1/invoke 的 text_file_id,长文本异步语音合成 |
型号与用途必须匹配,否则返回 422 file_purpose_not_supported;型号未开放文件接口时返回 422 endpoint_not_verified。以 GET /v1/models 的 resource_endpoints 为准。
上传文件#
multipart/form-data:
| 字段 | 必填 | 说明 |
|---|---|---|
model | 必填 | 型号 ID |
purpose | 必填 | 用途,见上表 |
file | 必填 | 文件,最大 25 MB。文件名的扩展名会被保留(平台把文件名统一为 input.<扩展名>) |
| 请求头 | 说明 |
|---|---|
Idempotency-Key | 建议提供。同一个键重复上传同一文件返回同一个文件 ID;文件或字段不同返回 409 idempotency_conflict;前一次上传仍在进行时返回 409 file_upload_already_accepted |
长文本文件要求(t2a_async_input)#
| 格式 | 要求 |
|---|---|
.txt | UTF-8 编码,不含 NUL 字符,最多 1,000,000 个字符 |
.zip | 最多 100 个条目;条目全部为 .txt 或全部为 .json(不能混合);不加密,只用存储或 Deflate 压缩;不能包含绝对路径或 ..;解压后合计不超过 6,000,000 字节;全部文本合计不超过 1,000,000 个字符 |
.json(zip 内) | 一个对象,只能包含字符串字段 title、content、extra,且至少一个非空 |
平台按文件的实际字符数(每个字符按 2 计)记录预留上限,合成时据此预留费用。
响应#
{
"id": "file_tts_5b2e…(64 位十六进制)",
"object": "file",
"model": "qwen-doc-turbo",
"purpose": "file-extract",
"created_at": 1792483200,
"expires_at": 1795075200,
"bytes": 48213,
"filename": "input.pdf"
}| 字段 | 说明 |
|---|---|
id | 平台文件 ID |
created_at / expires_at | Unix 秒。文件在 30 天后过期,过期后引用返回 404 |
bytes | 文件大小 |
filename | 平台保存的文件名 |
查询文件#
GET /v1/files/{id} 返回与上传响应相同的对象。
下载文件内容#
GET /v1/files/{id}/content 只适用于 glm-tts-clone 复刻后生成的试听音频(响应中的 file_id):返回 WAV 二进制(Content-Type: audio/wav),并在 X-TokenToSea-Asset-Id 响应头中给出该音频在私有存储中的资产 ID。其他文件返回 422 file_content_not_supported。
示例:文档问答#
# 1. 上传文档
curl https://api.tokentosea.com/v1/files \
-H "Authorization: Bearer $TOKENTOSEA_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-F model=qwen-doc-turbo \
-F purpose=file-extract \
-F "file=@report.pdf;type=application/pdf"
# 2. 引用文档提问(引用文档时只支持流式)
curl -N https://api.tokentosea.com/v1/chat/completions \
-H "Authorization: Bearer $TOKENTOSEA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"model": "qwen-doc-turbo",
"messages": [
{"role": "system", "content": "fileid://file_tts_…"},
{"role": "user", "content": "请概括文档的主要内容。"}
],
"max_tokens": 1024,
"enable_thinking": false,
"stream": true
}'import os
import uuid
import requests
from openai import OpenAI
API_KEY = os.environ["TOKENTOSEA_API_KEY"]
with open("report.pdf", "rb") as document:
uploaded = requests.post(
"https://api.tokentosea.com/v1/files",
headers={"Authorization": "Bearer " + API_KEY, "Idempotency-Key": str(uuid.uuid4())},
data={"model": "qwen-doc-turbo", "purpose": "file-extract"},
files={"file": ("report.pdf", document, "application/pdf")},
timeout=120,
)
uploaded.raise_for_status()
file_id = uploaded.json()["id"]
client = OpenAI(api_key=API_KEY, base_url="https://api.tokentosea.com/v1", max_retries=0)
stream = client.chat.completions.create(
model="qwen-doc-turbo",
messages=[
{"role": "system", "content": "fileid://" + file_id},
{"role": "user", "content": "请概括文档的主要内容。"},
],
max_tokens=1024,
stream=True,
extra_body={"enable_thinking": False},
extra_headers={"Idempotency-Key": str(uuid.uuid4())},
)
for chunk in stream:
for choice in chunk.choices:
print(choice.delta.content or "", end="", flush=True)import OpenAI from 'openai';
import { randomUUID } from 'node:crypto';
import { readFile } from 'node:fs/promises';
const apiKey = process.env.TOKENTOSEA_API_KEY;
const form = new FormData();
form.set('model', 'qwen-doc-turbo');
form.set('purpose', 'file-extract');
form.set('file', new Blob([await readFile('report.pdf')], { type: 'application/pdf' }), 'report.pdf');
const uploaded = await fetch('https://api.tokentosea.com/v1/files', {
method: 'POST',
headers: { Authorization: `Bearer ${apiKey}`, 'Idempotency-Key': randomUUID() },
body: form,
});
if (!uploaded.ok) throw new Error(await uploaded.text());
const { id: fileId } = await uploaded.json();
const client = new OpenAI({ apiKey, baseURL: 'https://api.tokentosea.com/v1', maxRetries: 0 });
const stream = await client.chat.completions.create(
{
model: 'qwen-doc-turbo',
messages: [
{ role: 'system', content: `fileid://${fileId}` },
{ role: 'user', content: '请概括文档的主要内容。' },
],
max_tokens: 1024,
enable_thinking: false,
stream: true,
},
{ headers: { 'Idempotency-Key': randomUUID() } },
);
for await (const chunk of stream) for (const choice of chunk.choices) process.stdout.write(choice.delta?.content ?? '');引用文档的调用按较大的输入上限预留费用(文档长度在调用前未知),结算以厂商返回的用量为准。
配额与错误#
每个账号每个 UTC 自然日最多上传 100 个文件(北京时间每天 08:00 重置),失败的上传同样计数。
| 状态码 | code | 原因与处理 |
|---|---|---|
422 | file_required | 缺少 file 字段 |
422 | file_purpose_not_supported | purpose 与型号不匹配 |
422 | endpoint_not_verified | 型号未开放文件接口 |
422 | utf8_text_required、plain_text_required、text_or_zip_required、text_file_limit、text_file_empty | 长文本文件不符合要求 |
422 | zip_invalid、zip_limit、zip_entry_not_supported、zip_mixed_text_formats、zip_size_mismatch、speech_json_invalid、speech_json_fields_invalid | zip 文件不符合要求 |
403 | model_not_allowed | 型号不在密钥的调用范围内 |
409 | idempotency_conflict、file_upload_already_accepted | 同一幂等键对应不同文件,或上传仍在进行 |
413 | multipart_limit_or_invalid_field | 文件超过 25 MB,或字段不符合要求 |
429 | resource_daily_limit | 今日上传次数已用完 |
4xx / 502 | provider_file_upload_failed | 厂商拒绝了文件,message 为厂商描述 |
404 | resource_not_found | 文件不存在、已过期,或不是用当前密钥上传的 |
422 | file_content_not_supported | 该文件不支持下载内容 |