原生协议调用POST
以厂商原生协议调用少数特殊能力:千问深度研究(流式)与 MiniMax 长文本异步语音合成。
POST https://api.tokentosea.com/v1/invoke
部分能力没有对应的通用协议,本接口按厂商原生格式转发请求体(model 由平台固定),并照常预留、结算。
前提:全能密钥或模型密钥,账户可用余额足以覆盖本次预留。Python 示例需要 pip install requests,Node.js 示例需要 Node.js 18+。
支持的型号#
| 型号 | 能力 | 响应方式 |
|---|---|---|
qwen-deep-research、qwen-deep-research-2025-12-15 | 深度研究:自动规划、检索并撰写研究报告 | 只支持流式(SSE),必须传 "stream": true |
speech-2.8-hd | 长文本异步语音合成(按上传的文本文件合成) | JSON,异步(202) |
截至 2026-10-21,以 GET /v1/models 的 verified_endpoints 为准。
千问深度研究#
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
model | string | 必填 | qwen-deep-research 或 qwen-deep-research-2025-12-15 |
input | object | 必填 | DashScope 原生输入,例如 { "messages": [{ "role": "user", "content": "…" }] } |
parameters | object | 可选 | DashScope 原生参数;平台固定设置 enable_feedback: false(不进入追问环节) |
stream | boolean | 必填 | 必须为 true |
深度研究包含检索与多轮推理,耗时长、用量大。平台按价格表的上限预留费用(预留金额明显高于常见的实际费用),完成后按厂商返回的累计用量结算,因此账户可用余额需覆盖预留。深度研究型号内置的联网检索不会被平台拦截(其他型号的联网搜索参数会被拒绝)。
响应是 DashScope 原生 SSE 帧,研究的各阶段(规划、检索、撰写)依次输出;报告完成的帧在结算后发出。字段含义见千问深度研究文档。
curl -N https://api.tokentosea.com/v1/invoke \
-H "Authorization: Bearer $TOKENTOSEA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"model": "qwen-deep-research",
"input": {"messages": [{"role": "user", "content": "比较太阳能和风能发电的主要特点。"}]},
"parameters": {},
"stream": true
}'import json
import os
import uuid
import requests
response = requests.post(
"https://api.tokentosea.com/v1/invoke",
headers={
"Authorization": "Bearer " + os.environ["TOKENTOSEA_API_KEY"],
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"model": "qwen-deep-research",
"input": {"messages": [{"role": "user", "content": "比较太阳能和风能发电的主要特点。"}]},
"parameters": {},
"stream": True,
},
stream=True,
timeout=1300,
)
response.raise_for_status()
for line in response.iter_lines(decode_unicode=True):
if not line or not line.startswith("data:"):
continue
event = json.loads(line[5:].strip())
if "error" in event: # 结算失败等:用同一个键查询 /v1/results/{key}
raise RuntimeError(event["error"])
print(json.dumps(event, ensure_ascii=False)[:200])import { randomUUID } from 'node:crypto';
const response = await fetch('https://api.tokentosea.com/v1/invoke', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.TOKENTOSEA_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': randomUUID(),
},
body: JSON.stringify({
model: 'qwen-deep-research',
input: { messages: [{ role: 'user', content: '比较太阳能和风能发电的主要特点。' }] },
parameters: {},
stream: true,
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
const decoder = new TextDecoder();
let buffer = '';
for await (const chunk of response.body) {
buffer += decoder.decode(chunk, { stream: true });
let index;
while ((index = buffer.indexOf('\n\n')) >= 0) {
const frame = buffer.slice(0, index);
buffer = buffer.slice(index + 2);
const data = frame.split('\n').filter((line) => line.startsWith('data:')).map((line) => line.slice(5).trim()).join('\n');
if (!data) continue;
const event = JSON.parse(data);
if (event.error) throw new Error(JSON.stringify(event.error)); // 用同一个键查询 /v1/results/{key}
console.log(JSON.stringify(event).slice(0, 200));
}
}MiniMax 长文本语音合成#
长文本先作为文件上传,再提交异步合成任务。
- 上传文本文件
用 POST /v1/files 上传,
model为speech-2.8-hd,purpose为t2a_async_input。文件为 UTF-8 的.txt,或包含.txt/.json的.zip。 - 提交合成任务
用返回的
file_tts_…作为text_file_id调用本接口,返回202。 - 获取音频
用同一个幂等键轮询 /v1/results/{key},完成后
output.data[0]中是音频的下载链接。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
model | string | 必填 | speech-2.8-hd |
text_file_id | string | 必填 | /v1/files 返回的 file_tts_…,必须由同一把密钥上传,purpose 为 t2a_async_input |
voice_setting | object | 必填 | MiniMax 原生音色设置,voice_id 必须是系统音色(male-qn-qingse) |
audio_setting | object | 可选 | MiniMax 原生音频设置,例如 { "format": "mp3" } |
预留费用按上传文件时测得的字符上限计算(每个字符按 2 计),完成后按厂商返回的计费字符数结算。
REQUEST_KEY="$(uuidgen)"
curl https://api.tokentosea.com/v1/invoke \
-H "Authorization: Bearer $TOKENTOSEA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $REQUEST_KEY" \
-d '{
"model": "speech-2.8-hd",
"text_file_id": "file_tts_…",
"voice_setting": {"voice_id": "male-qn-qingse"},
"audio_setting": {"format": "mp3"}
}'
curl "https://api.tokentosea.com/v1/results/$REQUEST_KEY" \
-H "Authorization: Bearer $TOKENTOSEA_API_KEY"import os
import time
import uuid
import requests
API = "https://api.tokentosea.com/v1"
HEADERS = {"Authorization": "Bearer " + os.environ["TOKENTOSEA_API_KEY"]}
with open("chapter.txt", "rb") as text:
uploaded = requests.post(
API + "/files",
headers={**HEADERS, "Idempotency-Key": str(uuid.uuid4())},
data={"model": "speech-2.8-hd", "purpose": "t2a_async_input"},
files={"file": ("chapter.txt", text, "text/plain")},
timeout=120,
)
uploaded.raise_for_status()
request_key = str(uuid.uuid4())
submitted = requests.post(
API + "/invoke",
headers={**HEADERS, "Idempotency-Key": request_key},
json={
"model": "speech-2.8-hd",
"text_file_id": uploaded.json()["id"],
"voice_setting": {"voice_id": "male-qn-qingse"},
"audio_setting": {"format": "mp3"},
},
timeout=120,
)
if submitted.status_code != 202:
raise RuntimeError(f"{submitted.status_code}: {submitted.text}")
while True:
time.sleep(10)
result = requests.get(API + "/results/" + request_key, headers=HEADERS, timeout=30).json()
if result["status"] in ("succeeded", "failed"):
break
print(result["status"], result.get("output", {}).get("data"))import { randomUUID } from 'node:crypto';
import { readFile } from 'node:fs/promises';
const API = 'https://api.tokentosea.com/v1';
const headers = { Authorization: `Bearer ${process.env.TOKENTOSEA_API_KEY}` };
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const form = new FormData();
form.set('model', 'speech-2.8-hd');
form.set('purpose', 't2a_async_input');
form.set('file', new Blob([await readFile('chapter.txt')], { type: 'text/plain' }), 'chapter.txt');
const uploaded = await fetch(`${API}/files`, { method: 'POST', headers: { ...headers, 'Idempotency-Key': randomUUID() }, body: form });
if (!uploaded.ok) throw new Error(await uploaded.text());
const requestKey = randomUUID();
const submitted = await fetch(`${API}/invoke`, {
method: 'POST',
headers: { ...headers, 'Content-Type': 'application/json', 'Idempotency-Key': requestKey },
body: JSON.stringify({
model: 'speech-2.8-hd',
text_file_id: (await uploaded.json()).id,
voice_setting: { voice_id: 'male-qn-qingse' },
audio_setting: { format: 'mp3' },
}),
});
if (submitted.status !== 202) throw new Error(`HTTP ${submitted.status}: ${await submitted.text()}`);
let result;
do {
await sleep(10_000);
result = await (await fetch(`${API}/results/${requestKey}`, { headers })).json();
} while (!['succeeded', 'failed'].includes(result.status));
console.log(result.status, result.output?.data);错误#
| 状态码 | code | 原因与处理 |
|---|---|---|
422 | response_mode_not_verified | 深度研究未传 "stream": true |
422 | text_file_bound_required | text_file_id 不是 t2a_async_input 用途的文件 |
422 | use_owned_platform_resource | text_file_id 不是字符串或不是 file_tts_… |
404 | resource_not_found | 文件不存在、已过期(30 天),或不是用当前密钥上传的 |
422 | voice_not_registered | voice_id 不是该型号的系统音色 |
400 | main_wallet_authorization_failed | 文本超出单次预留上限 |