5 分钟上手
从零到第一个成功请求,三步完事。
- 加 Telegram 客服 充值(微信 / 支付宝 / USDT / 对公任选),余额到账即可拿 key。
- 客服直接发
sk-xa-prod-***给你,或者你登录后台一键生成。可设月预算上限,防止 key 泄漏被刷穿。 - 把你客户端(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/v1、wire_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."} ] }'
认证
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-USD | VK 配置月度预算时,显示当月剩余额度。 |
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_models、ip_whitelist、monthly_budget_usd、rps / burst、expires_at。
- 所有计费 / 审计 / 限流 都以 VK 为主键。
- 客户只看见 VK。rotate 一次等于换锁,不影响其他 VK。
租户 / 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 并发打,损失也卡在你设的上限里。
预扣机制
- 请求进入 sidecar,解析出
model+max_tokens。 - 查 pricing registry 得到该模型
output_per_mtok。 - 计算最大估值:
estimated_cost = max_tokens × price_per_token × safety_multiplier。 - 对
tenant:{id}:budget和vk:{id}:budget同时做 Lua 脚本原子 SUB。 - 任一不足 → 402 budget_exhausted,请求不转发上游。
- 上游返回后,按实际
usage计算真实成本,原子 ADD 退差额。 - 如果是 429/4xx(调用未发生或未产生 usage)则退还全部预扣。
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。
10.0.0.0/8、172.16.0.0/12、192.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"}]}} ] }'
状态机
validates→in_progress→endedended.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/pdf、image/png|jpeg|webp、text/plain、text/markdown。 - 上传体被扫描 magic bytes,不允许 content-type 与实际文件不一致。
审计
所有上传/删除/读取写一条计费日志(file.uploaded、file.deleted、file.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 原子实现):
- 租户聚合
rl:tenant:{tenant_id}— 多 VK 共享容量 - 单 VK
rl:vk:{vk_id}— 客户侧主限制 - VK × 模型
rl:vkm:{vk_id}:{model}— 防止热点模型耗尽 - 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。
| HTTP | code | 说明 |
|---|---|---|
| 400 | bad_request | body 格式 / 字段错误,message 是 schema 错误描述 |
| 400 | ssrf_forbidden | 多模态 URL 命中 SSRF 黑名单(私网 / link-local / loopback) |
| 400 | model_unknown | 请求 model 不在 pricing registry |
| 401 | key_invalid | VK 不存在 / 已 revoke / 已 expire |
| 402 | budget_exhausted | tenant 或 VK 预算不够预扣 |
| 402 | context_too_long | prompt tokens 超过模型 context window |
| 403 | model_forbidden | VK 的 allowed_models 不含此模型 |
| 403 | ip_forbidden | VK 配置了 ip_whitelist 且本次源 IP 不在内 |
| 403 | signature_invalid | VK HMAC 校验失败(pepper 不对齐) |
| 413 | payload_too_large | body > SIDECAR_MAX_BODY_MB 或文件 > SIDECAR_FILES_MAX_MB |
| 415 | unsupported_media | Files API 上传 MIME 不在白名单 |
| 429 | rate_limited | GCRA 任一维度触发,带 Retry-After |
| 500 | internal_error | sidecar 内部异常,已告警 |
| 502 | upstream_error | 上游返回非预期 response,已 sanitize |
| 503 | service_unavailable | 所有可用 provider key 均熔断 |
| 504 | upstream_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(用公网可达的) |
数据怎么处理
透传服务,我们不存内容。下面是具体怎么做的。
不存的东西
- 请求 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/8、127.0.0.0/8、169.254.169.254 等),防止 SSRF 滥用。这是后端策略,你正常用公网图床不用管。
余额怎么退
余额随时可申请退,Telegram 找客服。扣除已消费部分后退回原付款渠道(微信 / 支付宝 / USDT / 对公)。如果哪天我们不做了,会提前 30 天通知,所有余额按比例退。
OAuth / SSO 登录
用户管理后台支持 OAuth 登录,免注册流程。当前可用:
- GitHub
- OIDC(Azure AD / Okta / Auth0 / Keycloak)— 企业内部 SSO 集成
流程
- 浏览器 →
GET /api/v1/auth/oauth/{provider}/start - 跳转到 Provider 授权页
- 授权后 Provider 回调
/callback,我们换出内部 JWT,种 httponly cookie - 之后所有后台请求都带 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 前缀
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_total | upstream 5xx / timeout 归因 |
sactl_breaker_open{provider} | 熔断状态(0=closed,1=open) |
sactl_gateway_budget_exhausted_total | 预扣失败计数 |
sactl_gateway_ssrf_blocked_total | SSRF 过滤拦截次数 |
sactl_gateway_auth_failed_total | VK 认证失败次数 |
Grafana 看板
仓库 deploy/grafana/dashboards/ 提供 5 张现成:
- SACTL Hot Path SLO — 延迟 p50/p95/p99 + error budget 燃烧率
- Rate Limit Heatmap — 各维度拒绝按小时分布
- Upstream Health — 每 provider 5xx 率 + 熔断事件流
- Cost Tracking — 按 tenant/model 月累计
- 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 / 身份证 / 信用卡 / 电话)
internal/redact/logging.go 封装。禁字段在代码级 drop,dev 模式下还会 panic。CI 有 gosec + staticcheck 规则扫 log.Print、fmt.Print、直接 slog key 是否在白名单内。违反即红灯,不进 main。
生产日志走 stdout → 容器 runtime → FluentBit → OpenSearch,保留 90 天。计费/账单相关数据单独存,跟普通日志分路,方便对账查询。