切换外观
快速开始
本页通过 cURL、Python、Node.js 或 Java 标准库完成第一次非流式调用。所有请求都从本地发出,API Key 不会提交给文档站。
1. 准备账号与正确权益
进入控制台完成账号准备,然后确认调用入口:
| 目标 | 请求路径 | 必要权益 |
|---|---|---|
| 通用余额调用 | POST /v1/chat/completions | 可用 API Key 与正余额 |
| Coding Plan 调用 | POST /v1/coding/chat/completions | 可用 API Key 与有效 Coding Plan |
两种入口独立计费,本页示例使用第一行。余额入口不会消耗 Coding Plan,Coding Plan 入口不会改扣余额;任一入口权益不足时都必须补足对应权益或修正请求路径,不能让另一种权益兜底。
2. 创建并保存 API Key
在控制台「API Keys」页创建 Key。新 Key 会完整显示,列表默认使用掩码;符合查看条件的 Key 可以再次查看或复制,无法解密的历史记录需要重新创建。
密钥安全
无论控制台是否允许再次查看,都应按生产密钥管理:只放入秘密管理系统,不写入源码、日志、截图或工单;怀疑泄露时立即停用并重新创建。
3. 从实时目录选择模型 ID
打开实时模型目录,等待目录加载成功,复制其中一个当前返回的 modelId。目录不可用时不要猜测模型名,也不要从本页历史示例推断在售状态或价格。
生产环境由秘密管理器注入 TOKENFACTORY_API_KEY。不要把真实 Key 粘贴到 export ...='...' 命令、脚本或 shell history。仅在本地一次性演示时,在 Bash 会话执行下列命令,以无回显方式读取一把隔离测试 Key:
bash
read -rsp '请输入隔离测试 Key(输入不回显): ' TOKENFACTORY_API_KEY
printf '\n'
export TOKENFACTORY_API_KEY
export TOKENFACTORY_MODEL='替换为实时目录返回的 modelId'
# 可选。默认值就是生产 API Base,一般无需设置。
export TOKENFACTORY_API_BASE='https://tokenfactory.cn/api'4. 选择标准库示例
四个示例发出相同的 POST /v1/chat/completions 请求,只在 HTTP 2xx 且响应包含非空 choices[0].message.content 时报告成功;失败只输出“配置错误”“HTTP 请求失败”“网络或超时”“响应格式错误”四类脱敏结果,不输出 Key、Authorization 请求头或响应正文。
bash
#!/usr/bin/env bash
set -euo pipefail
FAILURE_CONFIGURATION="调用失败:配置错误"
FAILURE_HTTP="调用失败:HTTP 请求失败"
FAILURE_NETWORK="调用失败:网络或超时"
FAILURE_RESPONSE="调用失败:响应格式错误"
if [[ -z "${TOKENFACTORY_API_KEY:-}" || -z "${TOKENFACTORY_MODEL:-}" ]]; then
echo "${FAILURE_CONFIGURATION}" >&2
exit 2
fi
API_BASE="${TOKENFACTORY_API_BASE:-https://tokenfactory.cn/api}"
API_BASE="${API_BASE%/}"
if [[ ! "${TOKENFACTORY_MODEL}" =~ ^[A-Za-z0-9._:/-]+$ ]]; then
echo "${FAILURE_CONFIGURATION}" >&2
exit 2
fi
if [[ "${TOKENFACTORY_API_KEY}" == *$'\r'* || "${TOKENFACTORY_API_KEY}" == *$'\n'* || "${TOKENFACTORY_API_KEY}" == *'"'* || "${TOKENFACTORY_API_KEY}" == *'\'* ]]; then
echo "${FAILURE_CONFIGURATION}" >&2
exit 2
fi
payload=$(printf '{"model":"%s","messages":[{"role":"user","content":"请用一句话说明请求已成功。"}]}' "${TOKENFACTORY_MODEL}")
umask 077
config_file=$(mktemp "${TMPDIR:-/tmp}/tokenfactory-curl.XXXXXX")
trap 'rm -f "$config_file"' EXIT
printf 'header = "Authorization: Bearer %s"\n' "${TOKENFACTORY_API_KEY}" > "${config_file}"
if ! response=$(curl --silent --max-time 60 \
--config "${config_file}" \
"${API_BASE}/v1/chat/completions" \
-H "Content-Type: application/json" \
--data "${payload}" \
--write-out $'\n%{http_code}'); then
echo "${FAILURE_NETWORK}" >&2
exit 1
fi
http_status="${response##*$'\n'}"
response="${response%$'\n'*}"
if [[ ! "${http_status}" =~ ^2[0-9][0-9]$ ]]; then
echo "${FAILURE_HTTP}" >&2
exit 1
fi
if ! printf '%s' "${response}" \
| LC_ALL=C tr '\n\r' ' ' \
| grep -Eq '"choices"[[:space:]]*:[[:space:]]*\[[[:space:]]*\{.*"message"[[:space:]]*:[[:space:]]*\{.*"content"[[:space:]]*:[[:space:]]*"[[:space:]]*[^"[:space:]][^"]*"'; then
echo "${FAILURE_RESPONSE}" >&2
exit 1
fi
echo "调用成功(响应正文未写入日志)"python
#!/usr/bin/env python3
import json
import os
import sys
import urllib.error
import urllib.request
FAILURE_CONFIGURATION = "调用失败:配置错误"
FAILURE_HTTP = "调用失败:HTTP 请求失败"
FAILURE_NETWORK = "调用失败:网络或超时"
FAILURE_RESPONSE = "调用失败:响应格式错误"
class ConfigurationError(Exception):
pass
def required_environment(name: str) -> str:
value = os.environ.get(name, "").strip()
if not value or "\r" in value or "\n" in value:
raise ConfigurationError
return value
def main() -> int:
try:
api_key = required_environment("TOKENFACTORY_API_KEY")
model = required_environment("TOKENFACTORY_MODEL")
api_base = os.environ.get("TOKENFACTORY_API_BASE", "https://tokenfactory.cn/api").rstrip("/")
payload = json.dumps(
{
"model": model,
"messages": [{"role": "user", "content": "请用一句话说明请求已成功。"}],
}
).encode("utf-8")
request = urllib.request.Request(
f"{api_base}/v1/chat/completions",
data=payload,
headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
method="POST",
)
except (ConfigurationError, ValueError, UnicodeError):
print(FAILURE_CONFIGURATION, file=sys.stderr)
return 2
try:
with urllib.request.urlopen(request, timeout=60) as response:
response_body = response.read()
except urllib.error.HTTPError as error:
error.close()
print(FAILURE_HTTP, file=sys.stderr)
return 1
except (ValueError, UnicodeError):
print(FAILURE_CONFIGURATION, file=sys.stderr)
return 2
except (urllib.error.URLError, TimeoutError, OSError):
print(FAILURE_NETWORK, file=sys.stderr)
return 1
try:
result = json.loads(response_body)
except (json.JSONDecodeError, UnicodeDecodeError):
print(FAILURE_RESPONSE, file=sys.stderr)
return 1
choices = result.get("choices") if isinstance(result, dict) else None
first_choice = choices[0] if isinstance(choices, list) and choices else None
message = first_choice.get("message") if isinstance(first_choice, dict) else None
content = message.get("content") if isinstance(message, dict) else None
if not isinstance(content, str) or not content.strip():
print(FAILURE_RESPONSE, file=sys.stderr)
return 1
print("调用成功(响应正文未写入日志)")
return 0
if __name__ == "__main__":
raise SystemExit(main())js
const FAILURE_CONFIGURATION = '调用失败:配置错误'
const FAILURE_HTTP = '调用失败:HTTP 请求失败'
const FAILURE_NETWORK = '调用失败:网络或超时'
const FAILURE_RESPONSE = '调用失败:响应格式错误'
async function main() {
let apiKey
let model
let apiBase
try {
apiKey = requiredEnvironment('TOKENFACTORY_API_KEY')
model = requiredEnvironment('TOKENFACTORY_MODEL')
apiBase = new URL(
(process.env.TOKENFACTORY_API_BASE ?? 'https://tokenfactory.cn/api').replace(/\/$/, ''),
).toString().replace(/\/$/, '')
} catch {
return fail(FAILURE_CONFIGURATION, 2)
}
let response
try {
response = await fetch(`${apiBase}/v1/chat/completions`, {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model,
messages: [{ role: 'user', content: '请用一句话说明请求已成功。' }],
}),
signal: AbortSignal.timeout(60_000),
})
} catch {
return fail(FAILURE_NETWORK)
}
if (!response.ok) return fail(FAILURE_HTTP)
let result
try {
result = await response.json()
} catch {
return fail(FAILURE_RESPONSE)
}
const firstChoice = result?.choices?.[0]
const content = firstChoice?.message?.content
if (typeof content !== 'string' || content.trim() === '') {
return fail(FAILURE_RESPONSE)
}
console.log('调用成功(响应正文未写入日志)')
}
function requiredEnvironment(name) {
const value = process.env[name]?.trim()
if (!value || /[\r\n]/.test(value)) throw new Error('invalid environment')
return value
}
function fail(failureMessage, exitCode = 1) {
console.error(failureMessage)
process.exitCode = exitCode
}
await main()java
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.regex.Pattern;
public final class FirstCall {
private static final String FAILURE_CONFIGURATION = "调用失败:配置错误";
private static final String FAILURE_HTTP = "调用失败:HTTP 请求失败";
private static final String FAILURE_NETWORK = "调用失败:网络或超时";
private static final String FAILURE_RESPONSE = "调用失败:响应格式错误";
private static final Pattern NON_EMPTY_CHAT_CONTENT = Pattern.compile(
"\"choices\"\\s*:\\s*\\[\\s*\\{.*?\"message\"\\s*:\\s*\\{.*?"
+ "\"content\"\\s*:\\s*\"\\s*[^\"\\s][^\"]*\"",
Pattern.DOTALL);
private FirstCall() {}
public static void main(String[] args) {
String apiKey;
String model;
String base;
try {
apiKey = requiredEnvironment("TOKENFACTORY_API_KEY");
model = requiredEnvironment("TOKENFACTORY_MODEL");
if (apiKey.indexOf('\r') >= 0
|| apiKey.indexOf('\n') >= 0
|| !model.matches("[A-Za-z0-9._:/-]+")) {
fail(FAILURE_CONFIGURATION, 2);
return;
}
base = System.getenv().getOrDefault(
"TOKENFACTORY_API_BASE", "https://tokenfactory.cn/api")
.replaceFirst("/$", "");
URI.create(base + "/v1/chat/completions");
} catch (RuntimeException error) {
fail(FAILURE_CONFIGURATION, 2);
return;
}
String body = """
{"model":"%s","messages":[{"role":"user","content":"请用一句话说明请求已成功。"}]}
""".formatted(model).trim();
HttpRequest request;
try {
request = HttpRequest.newBuilder()
.uri(URI.create(base + "/v1/chat/completions"))
.timeout(Duration.ofSeconds(60))
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
} catch (RuntimeException error) {
fail(FAILURE_CONFIGURATION, 2);
return;
}
HttpResponse<String> response;
try {
response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException error) {
fail(FAILURE_NETWORK, 1);
return;
} catch (InterruptedException error) {
Thread.currentThread().interrupt();
fail(FAILURE_NETWORK, 1);
return;
}
if (response.statusCode() < 200 || response.statusCode() >= 300) {
fail(FAILURE_HTTP, 1);
return;
}
if (!NON_EMPTY_CHAT_CONTENT.matcher(response.body()).find()) {
fail(FAILURE_RESPONSE, 1);
return;
}
System.out.println("调用成功(响应正文未写入日志)");
}
private static String requiredEnvironment(String name) {
String value = System.getenv(name);
if (value == null || value.isBlank()) {
throw new IllegalStateException("请先设置 " + name);
}
return value.trim();
}
private static void fail(String failureMessage, int exitCode) {
System.err.println(failureMessage);
System.exit(exitCode);
}
}执行方式:
bash
bash examples/first-call/curl.sh
python3 examples/first-call/python.py
node examples/first-call/node.mjs
java examples/first-call/FirstCall.java
# 全部演示结束后立即从当前 shell 清除测试 Key。
unset TOKENFACTORY_API_KEY5. 判断成功与处理失败
- 输出“调用成功”且进程退出码为 0:首次调用链路完成。
HTTP 401或invalid_api_key:检查 Key 是否完整、是否误带空格,不要把 Key 发到反馈渠道。model_not_found:重新打开实时目录,复制当前返回的模型 ID。insufficient_balance:通用余额入口缺少正余额;不要改用 Coding Plan 路径掩盖权益问题。insufficient_package或套餐额度错误:确认请求是否应该走 Coding Plan 专用路径。- 超时或网络失败:先确认请求是否已经部分交付。流式请求一旦收到内容,禁止无条件自动重放,以免产生重复成本或重复业务效果。