DOCUMENTATION

SACTL 使用文档

从拿 sk-xa-* key 到 Claude Code / Codex CLI / Cursor / Anthropic SDK 接入,5 分钟跑通。所有代码示例 copy-paste 能用。

v1.0 · 2026-04

5 分钟上手

从零到第一个成功请求,三步完事。

  1. Telegram 客服 充值(微信 / 支付宝 / USDT / 对公任选),余额到账即可拿 key。
  2. 客服直接发 sk-xa-prod-*** 给你,或者你登录后台一键生成。可设月预算上限,防止 key 泄漏被刷穿。
  3. 把你客户端(Claude Code / Codex CLI / Cursor / Anthropic SDK / curl)的 base_url 改成 https://api.sactl.ai,API key 换成 sk-xa-*。代码不动。Codex CLI 用 OpenAI provider:base_url=https://api.sactl.ai/v1wire_api=responses

第一条 curl 请求

curl -X POST https://api.sactl.ai/v1/messages \
  -H "Authorization: Bearer sk-xa-prod-xxxxxxxxxxxx" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-4-6",
    "max_tokens": 256,
    "messages": [
      {"role": "user", "content": "Hello, SACTL."}
    ]
  }'
一次性显示 Virtual Key 只在创建时明文显示一次。截走丢了只能重新 rotate。服务端只保留 HMAC-SHA256 digest,明文永远不落库。

认证

SACTL 只接受 Bearer 形式的鉴权 header:

  • Authorization: Bearer sk-xa-prod-...

Anthropic SDK / Claude Code 配置 ANTHROPIC_AUTH_TOKEN 即可,SDK 会自动加上 Authorization 头。

Virtual Key 结构

一条完整的 VK 格式:

sk-xa-{env}-{base62}

# 示例
sk-xa-prod-7xK9mQ2vL4pN8wRb
└────┬────┘ └──────┬───────┘
  prefix        secret
  • sk-xa-prod-:固定前缀,区分 env(dev / stage / prod)。
  • 前 12 字符用作 Redis 查表 prefix,O(1) 定位 VK 元数据。
  • 后续是 base62 随机秘密,服务端存 HMAC-SHA256 digest,pepper 在 Vault。
  • 匹配时做常数时间比较,防 timing attack。

详见 API 参考 · 认证

第一次调用

发一条完整请求,看响应结构。

Request

curl -X POST https://api.sactl.ai/v1/messages \
  -H "Authorization: Bearer sk-xa-prod-7xK9mQ2vL4pN8wRb" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-4-6",
    "max_tokens": 256,
    "messages": [
      {"role": "user", "content": "用 20 字解释什么是 HMAC。"}
    ]
  }'

Response

HTTP/1.1 200 OK
Content-Type: application/json
X-SACTL-Request-Id: 01JQX3A8P9M7K2WB3Z
X-SACTL-Usage-Prompt-Tokens: 18
X-SACTL-Usage-Completion-Tokens: 52
X-SACTL-Usage-Cost-USD: 0.00041
X-SACTL-Budget-Remaining-USD: 299.9996

{
  "id": "msg_01ABc...",
  "type": "message",
  "role": "assistant",
  "model": "claude-sonnet-4-6",
  "content": [
    {"type": "text", "text": "HMAC 是带密钥的哈希消息认证码,用于防篡改。"}
  ],
  "stop_reason": "end_turn",
  "usage": {"input_tokens": 18, "output_tokens": 52}
}

SACTL 返回的 headers

Header含义
X-SACTL-Request-Id跨请求 ULID,客户投诉时报这个,30 秒定位。
X-SACTL-Usage-Prompt-Tokens实际输入 tokens(含 system / tools)。
X-SACTL-Usage-Completion-Tokens输出 tokens(含 thinking)。
X-SACTL-Usage-Cost-USD本次 USD 费用(按注册模型单价结算:Claude × 0.30、GPT 低至官方 1/30;Gemini 即将支持)。
X-SACTL-Budget-Remaining-USDVK 配置月度预算时,显示当月剩余额度。
Retry-After仅 429 时出现,GCRA bucket 恢复秒数。

Virtual Key vs Provider Key

SACTL 的架构前提:你的客户永远不会碰到你的 Anthropic 或 OpenAI 官方 key。(Gemini 上游即将支持,同一模型同样适用。)

Provider Key

  • 你在 Anthropic 或 OpenAI 官方拿到的付费 key,或 Pro / Max / Plus 订阅 OAuth。Gemini / AWS Bedrock 上游即将支持。
  • 在管理控制台里录入,Vault transit 加密后落库。
  • 业务进程运行时向 Vault 请求 decrypt 一次,明文绝不落日志/盘
  • 可以组 Key Pool:按 Tier (RPM/TPM) 分流,某个 key 被熔断时走备用 chain。

Virtual Key (VK)

  • 由 SACTL 签发,sk-xa-* 前缀。
  • 所有限制挂在 VK 上:allowed_modelsip_whitelistmonthly_budget_usdrps / burstexpires_at
  • 所有计费 / 审计 / 限流 都以 VK 为主键。
  • 客户只看见 VK。rotate 一次等于换锁,不影响其他 VK。
数据隔离 即使 VK 泄漏,攻击者最多只能用光该 VK 的预算。Provider Key 从未出现在出站请求 body、错误响应、Inspector 元数据里 — 只在 sidecar 进程内存里存活几毫秒。

租户 / Tenant

SACTL 多租户按三层 hierarchy 组织:

Tenant (你签合同的客户公司)
  └── Users (客户内部的 portal 登录用户)
      └── Virtual Keys (应用使用的 sk-xa-*)

预算聚合

  • VK-level budget:每个 VK 独立的月度 USD 上限。
  • Tenant-level budget:租户下所有 VK 共享的总预算(可选)。任一维度触顶都 402。
  • 两者用同一个 Redis 原子 SUB/ADD 机制,并发安全。

隔离保证

  • Portal 用户登录后,看到的 data 严格限本 tenant 内。
  • URL 里塞别 tenant 的 VK ID?IDOR 防护拒绝并返回 404(不泄漏存在性)。
  • Admin 用户(Console 侧)才能跨 tenant 看聚合数据。

用量上限保护

每个 sk-xa-* key 可以设月预算上限。我们在请求转发到 Claude 之前就预扣预算,如果这次调用会超过上限就直接 402,不会真的发到 Claude 那边花钱。这样即便 key 被盗、被人用大 max_tokens 并发打,损失也卡在你设的上限里。

预扣机制

  1. 请求进入 sidecar,解析出 model + max_tokens
  2. 查 pricing registry 得到该模型 output_per_mtok
  3. 计算最大估值:estimated_cost = max_tokens × price_per_token × safety_multiplier
  4. tenant:{id}:budgetvk:{id}:budget 同时做 Lua 脚本原子 SUB。
  5. 任一不足 → 402 budget_exhausted,请求不转发上游。
  6. 上游返回后,按实际 usage 计算真实成本,原子 ADD 退差额。
  7. 如果是 429/4xx(调用未发生或未产生 usage)则退还全部预扣。
关于 max_tokens max_tokens 字段建议写真实需要的值,不要保险起见填 32k。填太大会把预算临时占满,导致同一 VK 并发请求被 402 拒绝。退还发生在响应阶段,而不是请求阶段。

流式 (SSE)

请求 body 里加 stream: true 即触发流式响应,Content-Type: text/event-stream

Anthropic 原生端点

事件结构遵循 Anthropic 官方:

event: message_start
data: {"type":"message_start","message":{"id":"msg_...",...}}

event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello"}}

event: message_stop
data: {"type":"message_stop"}

Python 示例

import requests

with requests.post(
    "https://api.sactl.ai/v1/messages",
    headers={"Authorization": "Bearer sk-xa-prod-...", "anthropic-version": "2023-06-01"},
    json={"model": "claude-sonnet-4-6", "max_tokens": 512, "stream": True,
          "messages": [{"role": "user", "content": "写首五言绝句"}]},
    stream=True,
) as r:
    for line in r.iter_lines():
        if line.startswith(b"data: "):
            print(line[6:].decode())

Tool Use / Function Calling

双向协议都支持。SACTL 内部做 tool_use ↔ tool_calls 的 id 映射,确保客户端 SDK 零改动。

Anthropic 原生

{
  "model": "claude-sonnet-4-6",
  "max_tokens": 1024,
  "tools": [
    {
      "name": "get_weather",
      "description": "查询城市天气",
      "input_schema": {
        "type": "object",
        "properties": {"city": {"type": "string"}},
        "required": ["city"]
      }
    }
  ],
  "messages": [{"role": "user", "content": "北京今天天气?"}]
}

响应里 content 数组会包含 {"type":"tool_use","id":"toolu_...","name":"get_weather","input":{"city":"北京"}} 块。

多模态(图片)

支持两种格式,SACTL 内部会翻译为 Anthropic 标准 image 块发给上游。

Anthropic 格式

{
  "role": "user",
  "content": [
    {
      "type": "image",
      "source": {
        "type": "base64",
        "media_type": "image/png",
        "data": "iVBORw0KGgo..."
      }
    },
    {"type": "text", "text": "描述这张图"}
  ]
}

也支持 source.type: "url" 直接引用远端 URL。

SSRF 过滤(强制) 所有外部图片 URL 在解析阶段就会被校验:私网段 10.0.0.0/8172.16.0.0/12192.168.0.0/16、link-local 169.254.0.0/16(含 AWS/GCP instance metadata)、loopback 127.0.0.0/8::1、多播段全部 block,返回 400 ssrf_forbidden。DNS 解析后的 IP 也会被再次检查。HTTP 3xx 禁止跟随

Extended Thinking

Claude 4.x 的推理轨迹,SACTL 原样透传并纳入计费。

Anthropic 原生

{
  "model": "claude-opus-4-6",
  "max_tokens": 8000,
  "thinking": {"type": "enabled", "budget_tokens": 32000},
  "messages": [{"role": "user", "content": "证明 √2 无理"}]
}

Beta header

SACTL 会自动注入 anthropic-beta: 2025-02-07(如客户端没带),无需关心。

计费

thinking tokens 按 output 单价计入 usage.completion_tokens,落计费日志(只记 token 数,不记 thinking 内容)。后台 Inspector 可以看到 thinking_tokens 字段对账。

Prompt Caching

Claude 的 system / tools / 大 context 可以由客户端显式声明 cache_control,SACTL 原样透传给 Anthropic,命中后按 cache write / read 单价计费。

客户端声明

{
  "model": "claude-sonnet-4-6",
  "system": [
    {
      "type": "text",
      "text": "你是一个产品客服助手,用户的问题都按下面的 FAQ 回答...",
      "cache_control": {"type": "ephemeral"}
    }
  ],
  "messages": [...]
}

定价

cache write 单价 = Anthropic 官方 input × 1.25 × 0.30;cache read 单价 = Anthropic 官方 input × 0.10 × 0.30。具体每模型数字见 Pricing · Model 表

Messages Batches

对应 Anthropic 的 Messages Batches API,用于异步批量任务(最多 10,000 request)。

提交

curl -X POST https://api.sactl.ai/v1/messages/batches \
  -H "Authorization: Bearer sk-xa-prod-..." \
  -H "Content-Type: application/json" \
  -d '{
    "requests": [
      {"custom_id": "r-001", "params": {"model":"claude-sonnet-4-6","max_tokens":256,"messages":[{"role":"user","content":"Hi 1"}]}},
      {"custom_id": "r-002", "params": {"model":"claude-sonnet-4-6","max_tokens":256,"messages":[{"role":"user","content":"Hi 2"}]}}
    ]
  }'

状态机

  • validatesin_progressended
  • ended.result:completed | canceled | expired | failed

轮询 / 结果下载

# 轮询状态
curl -H "Authorization: Bearer sk-xa-prod-..." \
  https://api.sactl.ai/v1/messages/batches/msgbatch_abc

# 下载 results JSONL
curl -H "Authorization: Bearer sk-xa-prod-..." \
  https://api.sactl.ai/v1/messages/batches/msgbatch_abc/results

价格

Anthropic 官方 batch 价 = 标准 × 0.5。SACTL 在此基础上再叠加 3 折:

SACTL batch 价 = 官方标准价 × 0.30 × 0.5 = 官方 × 0.15

Files API

上传文档 / 图片,在后续 /v1/messages 请求里用 file_id 引用。

上传

curl -X POST https://api.sactl.ai/v1/files \
  -H "Authorization: Bearer sk-xa-prod-..." \
  -F "[email protected]" \
  -F "purpose=messages"

限制

  • 单文件 SIDECAR_FILES_MAX_MB 默认 32 MB。
  • 允许的 MIME:application/pdfimage/png|jpeg|webptext/plaintext/markdown
  • 上传体被扫描 magic bytes,不允许 content-type 与实际文件不一致。

审计

所有上传/删除/读取写一条计费日志(file.uploadedfile.deletedfile.accessed),便于对账。文件体本身存独立 bucket,生命周期到期自动清理。

引用

{
  "role": "user",
  "content": [
    {"type": "document", "source": {"type": "file", "file_id": "file_abc123"}},
    {"type": "text", "text": "给这份报告写 200 字摘要"}
  ]
}

模型列表

动态返回当前 VK 有权限的模型(allowed_models ∩ pricing registry)。

curl -H "Authorization: Bearer sk-xa-prod-..." \
  https://api.sactl.ai/v1/models

Response (Anthropic shape)

{
  "data": [
    {"id": "claude-opus-4-7",    "type": "model", "display_name": "Claude Opus 4.7"},
    {"id": "claude-opus-4-6",    "type": "model", "display_name": "Claude Opus 4.6"},
    {"id": "claude-sonnet-4-6",  "type": "model", "display_name": "Claude Sonnet 4.6"},
    {"id": "claude-haiku-4-5",   "type": "model", "display_name": "Claude Haiku 4.5"}
  ],
  "has_more": false
}

限流维度

四维 GCRA(Generic Cell Rate Algorithm,Redis Lua 原子实现):

  1. 租户聚合 rl:tenant:{tenant_id} — 多 VK 共享容量
  2. 单 VK rl:vk:{vk_id} — 客户侧主限制
  3. VK × 模型 rl:vkm:{vk_id}:{model} — 防止热点模型耗尽
  4. VK × IP rl:vki:{vk_id}:{ip} — 防 credential stuffing / bot

任一维度拒绝 → 短路 429,带 Retry-After: {s}。不会打到上游、不会预扣预算。

参数来源

  • VK 维度:virtual_keys.rps / burst(portal 创建时可改)
  • Tenant 维度:tenants.policy.rps(admin console)
  • Model 维度:model_policy.{model}.max_rps(pricing registry,每模型独立)
  • IP 维度:默认 5 RPS / burst 20,global 配置可调

Response headers

HTTP/1.1 429 Too Many Requests
Retry-After: 3
X-SACTL-Request-Id: 01JQX3A8P9M7K2WB3Z

{"error":{"code":"rate_limited","message":"rate limit exceeded","trace_id":"01JQX3A8P9M7K2WB3Z"}}

错误码清单

所有错误统一 shape:{error:{code, message, trace_id}},详细 response shape 见 API 参考 · Errors

HTTPcode说明
400bad_requestbody 格式 / 字段错误,message 是 schema 错误描述
400ssrf_forbidden多模态 URL 命中 SSRF 黑名单(私网 / link-local / loopback)
400model_unknown请求 model 不在 pricing registry
401key_invalidVK 不存在 / 已 revoke / 已 expire
402budget_exhaustedtenant 或 VK 预算不够预扣
402context_too_longprompt tokens 超过模型 context window
403model_forbiddenVK 的 allowed_models 不含此模型
403ip_forbiddenVK 配置了 ip_whitelist 且本次源 IP 不在内
403signature_invalidVK HMAC 校验失败(pepper 不对齐)
413payload_too_largebody > SIDECAR_MAX_BODY_MB 或文件 > SIDECAR_FILES_MAX_MB
415unsupported_mediaFiles API 上传 MIME 不在白名单
429rate_limitedGCRA 任一维度触发,带 Retry-After
500internal_errorsidecar 内部异常,已告警
502upstream_error上游返回非预期 response,已 sanitize
503service_unavailable所有可用 provider key 均熔断
504upstream_timeout上游 > SIDECAR_UPSTREAM_TIMEOUT_SEC (默认 60s)

重试策略

建议客户端实现:

错误码可重试策略
upstream_error (502)指数退避 2s / 4s / 8s,最多 3 次
upstream_timeout (504)同上,第二次尝试考虑换 model
service_unavailable (503)全部 provider 熔断,建议等 30s+
rate_limited (429)严格按 Retry-After 退避
budget_exhausted (402)充值后才能重试
key_invalid (401)让用户拿新 VK
model_forbidden (403)改 model 或改 VK 配置
ssrf_forbidden (400)改 URL(用公网可达的)
内部自动重试 SACTL 在 sidecar 内部会对 upstream transient(网络抖动、单 key 短暂 429)自动重试一次,换另一条 provider key chain。客户端感知到的 5xx 已经是二次尝试后仍失败。因此客户端不需要重试得太激进。

数据怎么处理

透传服务,我们不存内容。下面是具体怎么做的。

不存的东西

  • 请求 prompt 原文:你发给 Claude 的内容,我们不落地。日志里只记 trace_id、token 数、耗时、用了哪个模型。
  • 响应 content 原文:Claude 返回什么直接转给你,我们不留副本、不做缓存(除非你显式开 prompt cache)、不训练模型。
  • 上游 API key:我们怎么调 Claude 是后端的事,这部分 key 不会出现在响应里、不会回流到你这边。

会存的东西(用于对账和限流)

  • 每次请求的 trace_id、模型、token 数、耗时、消费金额
  • 你充值和消费的余额流水
  • 你 sk-xa-* key 的 HMAC 摘要(不是明文,即便数据库被拖也反推不出)

用量上限保护

每个 sk-xa-* key 可以设月预算上限。上限到了直接 402 拒绝,防止 key 泄漏被人刷穿。

# 在 Telegram 找客服设上限,或后台一键设置
key: sk-xa-prod-***
monthly_budget_usd: 100   # 这把 key 月最多花 $100
allowed_models: ["claude-sonnet-4-6", "claude-haiku-4-5"]

多模态 URL 过滤

你传 image_url 给 Claude 时,我们会拒绝指向私网 / loopback / 元数据服务器的 URL(10.0.0.0/8127.0.0.0/8169.254.169.254 等),防止 SSRF 滥用。这是后端策略,你正常用公网图床不用管。

余额怎么退

余额随时可申请退,Telegram 找客服。扣除已消费部分后退回原付款渠道(微信 / 支付宝 / USDT / 对公)。如果哪天我们不做了,会提前 30 天通知,所有余额按比例退。

OAuth / SSO 登录

用户管理后台支持 OAuth 登录,免注册流程。当前可用:

  • Google
  • GitHub
  • OIDC(Azure AD / Okta / Auth0 / Keycloak)— 企业内部 SSO 集成

流程

  1. 浏览器 → GET /api/v1/auth/oauth/{provider}/start
  2. 跳转到 Provider 授权页
  3. 授权后 Provider 回调 /callback,我们换出内部 JWT,种 httponly cookie
  4. 之后所有后台请求都带 cookie 鉴权

OAuth client_secret 加密存储,不进环境变量也不进 DB 明文。企业 SSO 接入找 Telegram 客服开通,需要你提供 OIDC 元数据 URL。

Anthropic SDK

Python

import anthropic

client = anthropic.Anthropic(
    api_key="sk-xa-prod-xxxxx",
    base_url="https://api.sactl.ai",
)

resp = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Hello"}],
)
print(resp.content[0].text)

Node.js

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic({
    apiKey: "sk-xa-prod-xxxxx",
    baseURL: "https://api.sactl.ai",
});

const resp = await client.messages.create({
    model: "claude-sonnet-4-6",
    max_tokens: 1024,
    messages: [{ role: "user", content: "Hello" }],
});
console.log(resp.content[0].text);

Anthropic SDK 不需要加 /v1 后缀;SDK 会自己追加。

curl

最小可用示例。排查 SDK 问题时,先用 curl 验证网络路径。

curl -X POST https://api.sactl.ai/v1/messages \
  -H "Authorization: Bearer sk-xa-prod-xxxxx" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-4-6",
    "max_tokens": 512,
    "messages": [
      {"role": "user", "content": "Hello"}
    ]
  }' \
  -w "\n---\nHTTP %{http_code}  time %{time_total}s\n"

Dashboard / Inspector

Console UI

管理侧 https://console.sactl.ai,给你的 SRE / 商务看:

  • QPS / 成本 / 延迟 p50 p95 p99 曲线(Recharts)
  • 按模型 / 租户 / VK / 时间区间 dice-and-slice
  • Provider key pool 健康度(熔断次数、RPM 余额)
  • Tenant 月度账单导出 CSV

Inspector

trace_id 回放请求元数据:

  • 时间戳、tenant_id、VK 前缀(sk-xa-prod-7xK9...,后 6 位掩码)
  • model、prompt_tokens / completion_tokens / thinking_tokens、cost_usd、duration_ms
  • 限流命中维度 / 预扣 / 退还详情
  • upstream provider 名 + 命中的 key id 前缀
不存内容原文 Inspector 只能查到耗时和成本,看不到具体 prompt / 响应内容 —— 我们后端就不存。这是隐私设计,不是缺失,也不是给监管看的姿态。

Prometheus Metrics

所有 Go 服务暴露 /metrics,label 受白名单约束(不会有 user_id 这种高基数 label)。

重要 series

Metric用途
sactl_gateway_request_duration_seconds热路径 latency histogram,p99 SLO 2ms
sactl_gateway_ratelimit_rejected_total{dimension}限流拒绝按维度分桶
sactl_gateway_upstream_errors_totalupstream 5xx / timeout 归因
sactl_breaker_open{provider}熔断状态(0=closed,1=open)
sactl_gateway_budget_exhausted_total预扣失败计数
sactl_gateway_ssrf_blocked_totalSSRF 过滤拦截次数
sactl_gateway_auth_failed_totalVK 认证失败次数

Grafana 看板

仓库 deploy/grafana/dashboards/ 提供 5 张现成:

  1. SACTL Hot Path SLO — 延迟 p50/p95/p99 + error budget 燃烧率
  2. Rate Limit Heatmap — 各维度拒绝按小时分布
  3. Upstream Health — 每 provider 5xx 率 + 熔断事件流
  4. Cost Tracking — 按 tenant/model 月累计
  5. Audit Event Stream — 安全相关事件实时流

建议 Prometheus scrape 间隔 10s,保留 30 天 raw + 90 天 5min down-sample。

日志字段白名单

结构化日志只允许以下字段落盘:

timestamp, level, trace_id, tenant_id, key_id_prefix,
model, status, duration_ms,
tokens_prompt, tokens_completion, cost_usd

禁止字段

  • prompt / response 原文
  • api_key 明文(VK 或 provider)
  • 上游 error.message 原文(可能含 provider 内部栈)
  • Authorization 头原值
  • 任何 PII(email / 身份证 / 信用卡 / 电话)
防御层级 所有 logger 必须走 internal/redact/logging.go 封装。禁字段在代码级 drop,dev 模式下还会 panic。CI 有 gosec + staticcheck 规则扫 log.Printfmt.Print、直接 slog key 是否在白名单内。违反即红灯,不进 main。

生产日志走 stdout → 容器 runtime → FluentBit → OpenSearch,保留 90 天。计费/账单相关数据单独存,跟普通日志分路,方便对账查询。

5 分钟跑起来,3 折用 Claude、1/30 用 GPT

Telegram 加我们,发"试用"两个字。给你充值入口、API key、Claude Code 接入说明。出问题群里 @,工作时间 5 分钟内回复。