Skip to content

快速开始

本页通过 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_KEY

5. 判断成功与处理失败

  • 输出“调用成功”且进程退出码为 0:首次调用链路完成。
  • HTTP 401invalid_api_key:检查 Key 是否完整、是否误带空格,不要把 Key 发到反馈渠道。
  • model_not_found:重新打开实时目录,复制当前返回的模型 ID。
  • insufficient_balance:通用余额入口缺少正余额;不要改用 Coding Plan 路径掩盖权益问题。
  • insufficient_package 或套餐额度错误:确认请求是否应该走 Coding Plan 专用路径。
  • 超时或网络失败:先确认请求是否已经部分交付。流式请求一旦收到内容,禁止无条件自动重放,以免产生重复成本或重复业务效果。

完整错误语义见错误处理,入口选择见接入指南

Token Factory · 国产合规 AI Gateway