文档首页/🔌 API 与原生集成

API —原生集成

网页界面所做的一切都只是普通的 HTTP + SSE API。凡是你能点击完成的操作,都能从自己的代码里完成: 运行智能体、流式接收回答、查询知识库、创建智能体、调度任务、获取结果。

基地址https://ai.syctra.com/api/v1
传输仅 HTTPS(HTTP 会被重定向)。TLS 1.2+
编码JSON、UTF-8。所有写操作需 Content-Type: application/json
流式Server-Sent Events(text/event-stream),可断点续传
认证Authorization: Bearer <access_token>
版本/v1 前缀保持稳定。可能新增字段,但不会在同一前缀下移除已有字段

1. 认证

有两种凭据,请选对:

API 密钥邮箱 + 密码
适用服务器、脚本、集成、CI由真人登录的交互式应用
获取设置 → 🔑 API 密钥新建密钥POST /auth/register/auth/login
形态sk_live_…JWT 对(访问 + 刷新)
有效期直到你吊销访问 15 分钟 · 刷新 30 天

切勿把 API 密钥下发到浏览器、移动端或公开仓库。 它拥有创建它的账号的全部权限。任何运行在 客户端设备上的代码都应调用你自己的后端,由后端持有密钥。

1.1 API 密钥 → 访问令牌

API 密钥不是 bearer 令牌:你用它换取一个短期访问令牌,再用该令牌调用任意接口。

export SYCTRA_API_KEY="sk_live_…"

TOKEN=$(curl -sS -X POST https://ai.syctra.com/api/v1/auth/token \
  -H "X-API-Key: $SYCTRA_API_KEY" | jq -r .access_token)

POST /auth/token 是唯一接受密钥本身的接口。它支持三种传法 —— X-API-Key: sk_live_…Authorization: Bearer sk_live_…,或请求体 {"api_key": "sk_live_…"} —— 返回:

{ "access_token": "eyJhbGciOiJSUzI1NiIs…", "token_type": "Bearer", "expires_in": 900 }

expires_in(900 秒)内缓存令牌,过期后再换一次。不要每个请求都换一次:该接口限制为 每个 IP 每分钟 60 次兑换

1.2 管理密钥

方法路径用途
POST/api-keys创建密钥。{"name": "生产服务器"}明文只在这次响应中出现
GET/api-keys列出有效密钥 —— id、名称、前缀、created_atlast_used_at。绝不返回密文
DELETE/api-keys/{key_id}吊销。立即阻止新的兑换

每个用户最多 20 个有效密钥。服务端只保存 sha256(密钥),密钥丢失即不可找回 —— 吊销后重新 创建。吊销会立刻阻断新的兑换;已经签发的访问令牌在其有效期内(≤ 15 分钟)仍然有效,与浏览器 退出登录的行为一致。

1.3 邮箱 + 密码(交互式应用)

curl -X POST https://ai.syctra.com/api/v1/auth/register \
  -H 'content-type: application/json' \
  -d '{"email":"dev@acme.com","password":"…"}'          # → 邮件发送 6 位验证码

curl -X POST https://ai.syctra.com/api/v1/auth/verify \
  -H 'content-type: application/json' \
  -d '{"email":"dev@acme.com","code":"123456"}'

curl -X POST https://ai.syctra.com/api/v1/auth/login \
  -H 'content-type: application/json' \
  -d '{"email":"dev@acme.com","password":"…"}'          # → { access_token, refresh_token }

POST /auth/refresh {"refresh_token": "…"} 续期。Google 与 OIDC/SAML 登录见 /auth/google/start/auth/oidc/start。多次失败后登录按账号限流;注册按 IP 限量。

2. 快速上手 —— 提问并流式接收

模式始终一致:启动一次运行(run),然后订阅它的事件。运行是持久的 —— 进程挂了也可以 重新连接,从中断处继续。

# 1 — 启动
RUN=$(curl -sS -X POST https://ai.syctra.com/api/v1/chat \
  -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -d '{"message":"总结 2024 年营收趋势并作图","app_name":"data_copilot"}')

RUN_ID=$(echo "$RUN" | jq -r .run_id)

# 2 — 流式读取(可续传)
curl -N https://ai.syctra.com/api/v1/runs/$RUN_ID/events \
  -H "Authorization: Bearer $TOKEN"

Python

import json, os, requests

BASE = "https://ai.syctra.com/api/v1"
token = requests.post(f"{BASE}/auth/token",
                      headers={"X-API-Key": os.environ["SYCTRA_API_KEY"]},
                      timeout=30).json()["access_token"]
H = {"Authorization": f"Bearer {token}"}

run = requests.post(f"{BASE}/chat", headers=H, timeout=30, json={
    "message": "起草一份双向保密协议并导出为 DOCX",
    "app_name": "legal_drafting_dz",
}).json()

answer, cursor = [], 0
with requests.get(f"{BASE}/runs/{run['run_id']}/events", headers=H, stream=True) as r:
    event = None
    for line in r.iter_lines(decode_unicode=True):
        if not line:
            continue
        if line.startswith("id:"):
            cursor = int(line[3:].strip())          # 请保留:这就是断点续传的位置
        elif line.startswith("event:"):
            event = line[6:].strip()
        elif line.startswith("data:"):
            payload = json.loads(line[5:].strip())
            if event == "token":
                answer.append(payload.get("text", ""))
            elif event == "artifact":
                print("文件:", payload.get("artifact_id"), payload.get("filename"))
            elif event in ("done", "error"):
                break
print("".join(answer))

Node

const BASE = 'https://ai.syctra.com/api/v1';

const { access_token } = await (await fetch(`${BASE}/auth/token`, {
  method: 'POST', headers: { 'X-API-Key': process.env.SYCTRA_API_KEY },
})).json();

const { run_id } = await (await fetch(`${BASE}/chat`, {
  method: 'POST',
  headers: { authorization: `Bearer ${access_token}`, 'content-type': 'application/json' },
  body: JSON.stringify({ message: '列出这份合同的风险', app_name: 'contract_review_dz' }),
})).json();

const res = await fetch(`${BASE}/runs/${run_id}/events`, {
  headers: { authorization: `Bearer ${access_token}` },
});
for await (const chunk of res.body) process.stdout.write(Buffer.from(chunk).toString());

3. 约定

错误。 标准 HTTP 状态码,响应体为 JSON {"detail": "…"}

状态码含义处理方式
400请求格式错误修正载荷;错误信息会指出字段
401令牌缺失 / 过期 / 无效重新兑换密钥
402额度不足充值;该运行未启动也未计费
403已认证但无权限组织不匹配、团队未获授权该智能体,或账号被停用
404不存在或不属于你所有 ID 均按组织隔离
409冲突(如会话忙)查看响应体;排队状态在 POST /chat 中返回
429触发限流指数退避,若有 Retry-After 请遵守
5xx服务端错误退避重试;带 idempotency_key 的运行是幂等的

幂等。POST /chat 上传 idempotency_key —— 重放同一个 key 会返回原来的运行 (resumed: true),而不是再启动并再计费一次。

ID 为 UUID。时间戳 为 ISO-8601 UTC。在 POST /chat 中传 client_tz(IANA,如 "Asia/Shanghai"),智能体便会按你的本地时间推理。

限流。

限制
POST /chat 与运行持续 20 请求/秒,突发 40,按用户
POST /auth/token60/分钟,按客户端 IP
POST /auth/login多次失败后按账号限流
POST /auth/register按 IP 每小时限量
请求体300 MB(含 base64 上传)

并发。 运行按会话串行:上一次还在跑时发送第二条消息会被接收并在服务端排队,随后自动 执行(status: "queued",附 queue_seq)。需要并行请使用不同的 session_id

4. 计费

每一次 AI 操作都会从组织余额中扣除额度 —— 与网页端显示的是同一份余额,无论请求来自浏览器、 API 调用、定时任务还是嵌入式挂件。每次运行都会发出带 token 计数的 usage 事件, GET /balance 返回当前余额。余额不足的运行会在开始工作之前402 拒绝。

5. 安全

平台保证的部分:

  • 密钥哈希存储(SHA-256)。明文只在创建响应里出现一次。
  • 短期令牌。 访问令牌 15 分钟;停用账号或提升其 token 版本会使其会话失效并阻断后续兑换。
  • 凭据脱敏。 智能体无法泄露平台机密:API 密钥、SMTP 密码、连接串和私钥会被一个确定性过滤器 从工具结果、工具错误和模型输出中剥离 —— 而不是靠一条可以被绕过的提示词规则。
  • 提示注入隔离。 一旦某次运行摄入了不可信内容(上传的文件、抓取的网页、搜索结果),对外 动作 —— 发邮件、发布、写入记忆、任意 HTTP —— 都会被阻断,直到你明确确认。
  • 沙箱执行。 智能体编写的代码运行在一次性 gVisor 容器中,没有任何机密、没有直连网络: 出站流量经过带 SSRF 过滤的代理。
  • 租户隔离。 每个 ID 都归属于你的组织并在服务端复核;身份始终来自令牌,绝不取自请求字段。
  • 可审计。 每一次工具调用都会连同运行、组织和状态一起记录。

需要你负责的部分:

  • 密钥只放在服务端,存于密钥管理器或环境变量 —— 不要出现在浏览器、移动端包体、git 仓库或 CI 日志中。
  • 每个系统一把密钥,并以该系统命名,便于单独吊销。
  • 定期轮换:先创建新密钥、部署,再吊销旧的。last_used_at 会告诉你何时可以安全下线一把密钥。
  • 任何出现在日志、截图或工单里的密钥都视为已泄露 —— 立即吊销。
  • 若你把该 API 再暴露给自己的用户,请实施你自己的授权:平台密钥并不认识你的终端用户。

完整接口清单:API 参考